ffmpeg av_parser_parse2函数分析各种码流测试程序

这篇具有很好参考价值的文章主要介绍了ffmpeg av_parser_parse2函数分析各种码流测试程序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

ffmpeg av_parser_parse2函数分析各种码流测试程序
----------------------------------------
author: hjjdebug
date: 2023年 07月 14日 星期五 16:14:05 CST
date: 曾更新过...
----------------------------------------
测试程序见后, 可编译运行(ffmpeg v4.4)
我需要一个简单的程序,实现能跟入ffmpeg 库.了解av_parser_parse2的工作原理.
下面是跟踪调试部分记录.
关于测流码流的获取,通常我们见到的是音视频在一起的ts流文件,你可以用ffmpeg 工具提取.

例如: 找一个或录一个h264的音视频码率,存为1.ts, 用下面命令抽取出视频扔掉音频.
ffmpeg -i 1.ts -c:v copy -an 1.h264
如果不是h264的码流,也可以转换,方法:
ffmpeg -i not_ffmpeg.ts -c:v h264 -an 1.h264

----------------------------------------
1. avcodec_find_decoder(id);
----------------------------------------
id: AV_CODEC_ID_H264,
根据id来查找AVCodec, 所有的codec 用一个列表指针来管理,依次枚举这个列表,在libavcodec/codec_list.c中定义
看看它们的id与所查的是否一致就可以了,这样就找到了 一个对象指针 ff_h264_decoder, 在libavcodec/h264dec.c中定义
一些关键函数如.init,.decode,.close等自然都有了着落.
AVCodec ff_h264_decoder = {
    .name                  = "h264",
    .long_name             = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
    .type                  = AVMEDIA_TYPE_VIDEO,
    .id                    = AV_CODEC_ID_H264,
    .priv_data_size        = sizeof(H264Context),
    .init                  = h264_decode_init,
    .close                 = h264_decode_end,
    .decode                = h264_decode_frame,
    .capabilities          = /*AV_CODEC_CAP_DRAW_HORIZ_BAND |*/ AV_CODEC_CAP_DR1 |
                             AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |
                             AV_CODEC_CAP_FRAME_THREADS,
    .... //忽略若干硬件加速项
    .caps_internal         = FF_CODEC_CAP_INIT_THREADSAFE | FF_CODEC_CAP_EXPORTS_CROPPING |
                             FF_CODEC_CAP_ALLOCATE_PROGRESS | FF_CODEC_CAP_INIT_CLEANUP,
    .flush                 = h264_decode_flush,
    .update_thread_context = ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context),
    .profiles              = NULL_IF_CONFIG_SMALL(ff_h264_profiles),
    .priv_class            = &h264_class,
};
--------------------------------------------------------------------------------
2.    AVCodecContext *pCodecCtx = avcodec_alloc_context3(pCodec);
--------------------------------------------------------------------------------
依据pCodec分配一个AVCodecContext, 并进行必要的初始化
AVCodecContext, 我看了一眼,1800行的结构定义令人镗目结舌!
可见codec 结构不大,但ctx 结构很大!!
它的第一个重要单元是
typedef struct AVCodecContext {  //1800 行的结构定义
    const AVClass *av_class;  // 它是一个AVClass, 在这个类指针中,会描述该Context类选项信息
    ...
}
这里介绍几个概念,默认值
默认的ctx值,未加入codec信息;
s->time_base = {0,1}
s->framerate = {0,1}
s->pkg_timebase = {0,1}
s->sample_aspect_ratio = {0,1}
s->pix_fmt = AV_PIX_FMT_NONE;
s->sample_fmt = AV_SAMPLE_FMT_NONE;
s->av_class = &av_codec_context_class; //重要!! 由这个指针可以找到这个类叫什么名称,有什么选项等信息
这里,所有的codecCtx 都指向一个av_codec_context_class 对象地址. 这是一个AVClass 对象
av_codec_context_class 定义在libavcodec/options.c中
看一下它的定义:
static const AVClass av_codec_context_class = {
    .class_name              = "AVCodecContext",
    .item_name               = context_to_name,
    .option                  = avcodec_options,
    .version                 = LIBAVUTIL_VERSION_INT,
    .log_level_offset_offset = offsetof(AVCodecContext, log_level_offset),
    .child_next              = codec_child_next,
#if FF_API_CHILD_CLASS_NEXT
    .child_class_next        = codec_child_class_next,
#endif
    .child_class_iterate     = codec_child_class_iterate,
    .category                = AV_CLASS_CATEGORY_ENCODER,
    .get_category            = get_category,
};
小知识: AVClass 类是什么? (是一个描述类)
av_codec_context_class 是一个AVClass 对象, 它描述了一个Context类,说明了该类的名称,选项等信息.及其它一些函数指针。
类名称"AVCodecContext"
codec名称函数:根据codecCtx指针,返回codecCtx->codec->name
查找codec私有数据函数, 返回s->priv_data
查找codec私有类函数, 返回s->priv_class 等函数
通用选项定义表: avcodec_options
由于定义了选项表,一个函数调用就能填充AVCodecCtx对象的一些默认选项. av_opt_set_defaults2(void *s)
opt = av_opt_next(s,opt); //获取s对象的下一个选项地址. s对象是分配的ctx对象,
根据opts 的定义把其值填入s 所在的成员中. 例如填整数值
write_number(s, opt, dst, 1, 1, opt->default_val.i64);
单纯的写num, s指针是不需要的,因为dst位置已经给出,但出错时s指针可以传递给av_log,故保留其指针


//跟codec相关的信息要保留
s->codec = codec;
s->codec_id = codec->id;
s->codec_type = codec->type;
if(codec->priv_data_size)
{
    s->priv_data=av_mallocz(codec->priv_data_size); //H264Context ,大小53k, 看前面定义
    if(codec->priv_class) //codec 的私有类 h264_class,参加前面decoder定义
    {
            *(const AVClass**)s->priv_data = codec->priv_class; //私有类指针填充到私有数据第一项
            av_opt_set_defaults(s->priv_data); // 这个priv_data 是一个含av_optios的AVClass 对象,所以可以对其成员进行初始化.
    }
}
h264_class 定义很简单. 如下,是一个AVClass 对象: 在h264dec.c中定义
static const AVClass h264_class = {
    .class_name = "H264 Decoder",
    .item_name  = av_default_item_name,
    .option     = h264_options,  //该成员是一个选项表.
    .version    = LIBAVUTIL_VERSION_INT,
};
h264_options 选项表中使用了地址偏移OFFSET() 宏
#define OFFSET(x) offsetof(H264Context, x)
它指定了成员x 是类型H264Context 的成员来计算偏移.
例: nal_length_size 选项
    { "nal_length_size", "nal_length_size", OFFSET(nal_length_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 4, 0 },


H264Context 是codec的私有类。codec->priv_class, 可参考后面叙述.


可以依据h264_class对象(它的h264_options信息),对H264Context(它描述的类) 进行一些默认选项设置.
H264Context 定义有200多行就不copy了.

H264Context 与 AVCodecContext 是什么关系,
是包含关系(集合关系).   AVCodecContext 是接口类, H264Context 是实现类,AVCodecContext 也可以不包含H264Context 而包含其它的Context
    avctx->priv_data=av_mallocz(codec->priv_data_size); //H264Context ,大小53k, 看前面定义
    所以后面也会这样获取H264Context指针 const H264Context *h = avctx->priv_data;


小知识:AVCodec 类是什么? 以ff_h264_decoder 为例
AVCodec 是针对某种数据类型而定义的解码或编码框架,定义了codec的ID,TYPE,初始化函数,打开函数,关闭函数等.
它包含一个AVClass 类指针 priv_class, 不为空时代表关联一个私有类.
ff_h264_decoder 是AVCodec 的一个实例
AVCodec ff_h264_decoder = {
    .name                  = "h264",
    .long_name             = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
    .type                  = AVMEDIA_TYPE_VIDEO,
    .id                    = AV_CODEC_ID_H264,
    .priv_data_size        = sizeof(H264Context),
    .priv_class            = &h264_class,  // 关联了h264_class 类对象
    .init                  = h264_decode_init,
    .close                 = h264_decode_end,
    .decode                = h264_decode_frame,
    .capabilities          = /*AV_CODEC_CAP_DRAW_HORIZ_BAND |*/ AV_CODEC_CAP_DR1 |
                             AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |
                             AV_CODEC_CAP_FRAME_THREADS,
    .hw_configs            = (const AVCodecHWConfigInternal *const []) {
                                ...
                               NULL
                           },
    .caps_internal         = FF_CODEC_CAP_INIT_THREADSAFE | FF_CODEC_CAP_EXPORTS_CROPPING |
                             FF_CODEC_CAP_ALLOCATE_PROGRESS | FF_CODEC_CAP_INIT_CLEANUP,
    .flush                 = h264_decode_flush,
    .update_thread_context = ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context),
    .profiles              = NULL_IF_CONFIG_SMALL(ff_h264_profiles),
};

小知识:AVCodecContext 是什么数据类型?
答:是包含AVCodec对象的类,同时也包含解析时数据的分析结果, 它是一个AVClass 类
每个AVCodecContext对象对应于具体的一个文件或数据流,保留了流中具体的宽,高,比特率等属性信息.
所以Context类往往有很大的数据结构.
AVCodecContext 与 H264Context 构成层级关系.

----------------------------------------------------------------------------------------------------
AVCodecParserContext* pCodecParserCtx = av_parser_init(vDecoderID);//根据解码器类型获取解析器上下文
----------------------------------------------------------------------------------------------------
该函数返回一个 AVCodecParserContext, 大小352字节.
typedef struct AVCodecParserContext {
    void *priv_data; // 此例会分配给H264ParseContext
    struct AVCodecParser *parser; //指向对象 ff_h264_parser,该对象下定义有私有数据大小.
    int64_t pts;
    int64_t dts;
....
};
它并不是一个AVClass. 可见ffmpeg 中AVClass 类虽然很多, 但AVCodecParserContext 却不是AVClass 类型.
H264ParseContext 对象被AVCodecParserContext 的私有数据指针(priv_data)所指向.

p sizeof(AVCodecParserContext)
$9 = 352
parseContext 有一个重要成员,那就是parser(AVCodecParser 类型).
parser 类有一个list ,在avcodec/parser_list.c 中定义,我们遍历这张表,根据 AV_CODEC_ID_H264 找到了ff_h264_parser
所以AVCodecParserContext 可以包含任意一种parser. 它们是包含关系.
priv_data 是parser对应的ParseContext, 针对具体的流而需要保存一些具体的数据.参考下面定义

(gdb) p parser->priv_data_size
$10 = 3144
由这个信息创建H264ParseContext

AVCodecParser ff_h264_parser = {   
    .codec_ids      = { AV_CODEC_ID_H264 },
    .priv_data_size = sizeof(H264ParseContext),  //大小3144字节
    .parser_init    = init,                    //分配完私有数据,调用这个init,当然对这个私有数据进行一定的初始化了.
    .parser_parse   = h264_parse,  //这个函数很关键,是264数据的分析函数入口
    .parser_close   = h264_close,
    .split          = h264_split,
};

typedef struct H264ParseContext {
    ParseContext pc; //这个ParseContext 是另一种类型?
    H264ParamSets ps;
    H264DSPContext h264dsp;
    H264POCContext poc;
    H264SEIContext sei;
    int is_avc;
    int nal_length_size;
    int got_first;
    int picture_structure;
    uint8_t parse_history[6];
    int parse_history_count;
    int parse_last_mb;
    int64_t reference_dts;
    int last_frame_num, last_picture_structure;
} H264ParseContext;

问1: H264ParseContext 与 AVCodecParserContext 是什么关系?
答, 是包含关系(集合):  AVCodecParserContext 对象包含了一个H264ParseContext 对象指针.
    也是接口和实现的关系.


问2: AVCodecParserContext 与 AVCodecContext 是什么关系?
答, 没有直接关系(无组合,集合,继承等包含关系), 但可以说有对应关系. 它们可由CodecID 来关联
也就是说,由CodecID 可以找到具体codec -> 由具体codec可以创建具体codecContext
         由CodecID 可以找到具体parser -> 由具体parser 创建具体parseContext
AVCodecContext 是一个接口类,包含了具体的CodecContext对象,例如H264Context
AVCodecParserContext 也是一个接口类,包含了具体的CodecParseContext对象,例如H264ParseContext


补充:
AVCodecParserContext对象 是AVFormatContext 的一个成员, 它们构成组合关系

av_parser_parse2 代码分析:
/*
s,avctx: 上下文,其中avctx在组包中不重要
poutbuf, poutbuf_size 输出. 问? 输出内存何时释放?
buf,buf_size: 输入数据
pts,dts,pos: 输入当前frame的时戳及位置信息,演示程序设置该3参数为常数,忽略之
其实整了半天,发现写这么多代码,只是把原数据进行了分割,每次读一个片段到packet中而已!
简单的说,你如果知道包的长度,喀喀!,从大缓冲中copy到packet中指定长度就可以了.
*/
int av_parser_parse2(AVCodecParserContext *s, AVCodecContext *avctx,
                     uint8_t **poutbuf, int *poutbuf_size,
                     const uint8_t *buf, int buf_size,
                     int64_t pts, int64_t dts, int64_t pos)
{
    int index, i;
    uint8_t dummy_buf[AV_INPUT_BUFFER_PADDING_SIZE];

    if (!(s->flags & PARSER_FLAG_FETCHED_OFFSET)) {
        s->next_frame_offset =
        s->cur_offset        = pos;
        s->flags            |= PARSER_FLAG_FETCHED_OFFSET;
    }

    if (buf_size == 0)
    {
        /* padding is always necessary even if EOF, so we add it here */
        memset(dummy_buf, 0, sizeof(dummy_buf));
        buf = dummy_buf;
    }
    else if (s->cur_offset + buf_size != s->cur_frame_end[s->cur_frame_start_index])
    {
        //代码简单,保留一下cur_offset, frame_end 信息, 有4个槽位供使用,方便你知道数据偏移量
        i = (s->cur_frame_start_index + 1) & (AV_PARSER_PTS_NB - 1);
        s->cur_frame_start_index = i;
        s->cur_frame_offset[i]   = s->cur_offset;
        s->cur_frame_end[i]      = s->cur_offset + buf_size;
        s->cur_frame_pts[i]      = pts;
        s->cur_frame_dts[i]      = dts;
        s->cur_frame_pos[i]      = pos;
    }

    if (s->fetch_timestamp) {
        s->fetch_timestamp = 0;
        s->last_pts        = s->pts;
        s->last_dts        = s->dts;
        s->last_pos        = s->pos;
        ff_fetch_timestamp(s, 0, 0, 0);
    }
/*
由于pCodecParserCtx 被AV_CODEC_ID_H264 初始化过,
s->parser = (AVCodecParser*)parser;  //这个被枚举到的parser就是ff_h264_parser,
这就是接口和实现的耦合函数.前面已经赋值了.
所以 s->parser->parser_parse 会调用到h264_parse()
*/
    /* WARNING: the returned index can be negative */
    // index 返回使用的数据,负值在h264_parse中不会出现,谁要是返回负值其实就是傻冒,没有意义啊.
    // 返回使用的数据长度哪有负数呢?
    // 不过就算返回负值人家也处理了,改成了0, 加这种判断稍微有点乱! 建议作者把这句提醒给删除或给出更明确的提示
    // av_parser_parse2 也就这一句最关键了.
    index = s->parser->parser_parse(s, avctx, (const uint8_t **) poutbuf, poutbuf_size, buf, buf_size);

    /* update the file pointer */
    //要更新 上下文中 frame_offset,next_frame_offset,cur_offset
    if (*poutbuf_size) {
        /* fill the data for the current frame */
        s->frame_offset = s->next_frame_offset;

        /* offset of the next frame */
        s->next_frame_offset = s->cur_offset + index;
        s->fetch_timestamp   = 1;
    } else {
        /* Don't return a pointer to dummy_buf. */
        *poutbuf = NULL;
    }
    if (index < 0)
        index = 0;
    s->cur_offset += index;
    return index;
}

类间关系稍微有点复杂,主要是因为Codec有接口类统领一帮实现类,
Parser 也是接口类统领一帮实现类. 在这里主要关心parse 家族.
parser 分析关键还是数据流向,下面还要解释几个问题:
1. 分析得到一包数据的大小, 包的大小是怎么来的?
答: 要找到包尾,实际是下一包包头00 00 01且包类型满足一定要求判定为包尾,实际上包NAL类型是 NAL_AUD附加的分割符
   得到长度
2. 第一包和第二包数据之间有没有缝隙?
答: 没有

h264_parse()函数注释
/*
s: 分析上下文
poutbuf,poutbuf_size: 输出
buf,buf_size: 待分析数据
avctx: 暂时不用关心,这里无extradata,不考虑额外数据解码问题
返回值: 返回使用的数据长度(整个缓冲的大小或到达包尾的大小).
*/
static int h264_parse(AVCodecParserContext *s,
                      AVCodecContext *avctx,
                      const uint8_t **poutbuf, int *poutbuf_size,
                      const uint8_t *buf, int buf_size)
{
    H264ParseContext *p = s->priv_data;
    ParseContext *pc = &p->pc;
    int next;

    if (!p->got_first) { //第一遍p->got_first 为0, 被赋值成1
        p->got_first = 1;
        if (avctx->extradata_size) { // avctx 相关的先不考虑, 本测试流其大小也是0,故不走这里
            ff_h264_decode_extradata(avctx->extradata, avctx->extradata_size,
                    &p->ps, &p->is_avc, &p->nal_length_size,
                    avctx->err_recognition, avctx);
        }
    }

    if (s->flags & PARSER_FLAG_COMPLETE_FRAMES)  //无此标记,不走这里
    {
        next = buf_size;
    }
    else
    {  // 走这里
        next = h264_find_frame_end(p, buf, buf_size, avctx); // 关键函数1
        // next 有可能是-100: END_NOT_FOUNT 或者找到包尾时包尾长度
        if (ff_combine_frame(pc, next, &buf, &buf_size) < 0)  //关键函数2
        {
            *poutbuf      = NULL; //组包未完成, 返回整个buf_size, 表示数据均被使用
            *poutbuf_size = 0;
            return buf_size; // 返回全部缓冲区长度
        }

        if (next < 0 && next != END_NOT_FOUND)
        { // 完善性处理
            av_assert1(pc->last_index + next >= 0);
            h264_find_frame_end(p, &pc->buffer[pc->last_index + next], -next, avctx); // update state
        }
    }

    parse_nal_units(s, avctx, buf, buf_size); //关键函数3, 能够分析出包的一些信息,这里不是重点.

    if (avctx->framerate.num)
        avctx->time_base = av_inv_q(av_mul_q(avctx->framerate, (AVRational){avctx->ticks_per_frame, 1}));
    if (p->sei.picture_timing.cpb_removal_delay >= 0) { //代码简单,保留sei信息到s
        s->dts_sync_point    = p->sei.buffering_period.present;
        s->dts_ref_dts_delta = p->sei.picture_timing.cpb_removal_delay;
        s->pts_dts_delta     = p->sei.picture_timing.dpb_output_delay;
    }
    else
    {
        s->dts_sync_point    = INT_MIN;
        s->dts_ref_dts_delta = INT_MIN;
        s->pts_dts_delta     = INT_MIN;
    }

    if (s->flags & PARSER_FLAG_ONCE) {
        s->flags &= PARSER_FLAG_COMPLETE_FRAMES;
    }

    if (s->dts_sync_point >= 0)
    {
        int64_t den = avctx->time_base.den * (int64_t)avctx->pkt_timebase.num;
        if (den > 0)
        { //代码简单, 关于时间信息的处理
            int64_t num = avctx->time_base.num * (int64_t)avctx->pkt_timebase.den;
            if (s->dts != AV_NOPTS_VALUE)
            {
                // got DTS from the stream, update reference timestamp
                p->reference_dts = s->dts - av_rescale(s->dts_ref_dts_delta, num, den);
            }
            else if (p->reference_dts != AV_NOPTS_VALUE)
            {
                // compute DTS based on reference timestamp
                s->dts = p->reference_dts + av_rescale(s->dts_ref_dts_delta, num, den);
            }

            if (p->reference_dts != AV_NOPTS_VALUE && s->pts == AV_NOPTS_VALUE)
                s->pts = s->dts + av_rescale(s->pts_dts_delta, num, den);

            if (s->dts_sync_point > 0)
                p->reference_dts = s->dts; // new reference
        }
    }

    *poutbuf      = buf; // 组包时, buf,buf_size被改为存储数据的指针和大小,这里付给输出
    *poutbuf_size = buf_size;
    return next;
}

//查找包尾程序注释, 找包尾, 实际上是找下一个包头00 00 01
//状态机转换
//最后返回找到时的包尾长度或没找到-100
static int h264_find_frame_end(H264ParseContext *p, const uint8_t *buf,
                               int buf_size, void *logctx)
{
    int i, j;
    uint32_t state;
    ParseContext *pc = &p->pc;

    int next_avc = p->is_avc ? 0 : buf_size;
    state = pc->state;
    if (state > 13)
        state = 7;

    for (i = 0; i < buf_size; i++)
    {
        if (i >= next_avc)
        {
            int nalsize = 0;
            i = next_avc;
            for (j = 0; j < p->nal_length_size; j++)
                nalsize = (nalsize << 8) | buf[i++];
            if (nalsize <= 0 || nalsize > buf_size - i) {
                av_log(logctx, AV_LOG_ERROR, "AVC-parser: nal size %d remaining %d\n", nalsize, buf_size - i);
                return buf_size;
            }
            next_avc = i + nalsize;
            state    = 5;
        }

        if (state == 7)
        {  //查找开始码(00 00 00 01)候选项(00),找到从状态7到状态2
            i += p->h264dsp.startcode_find_candidate(buf + i, next_avc - i);
            if (i < next_avc)
                state = 2;
        }
        else if (state <= 2)
        {
            if (buf[i] == 1)    // 碰到一个1,状态可能变成4或5,找到了起始码
                state ^= 5;            // 2->7, 1->4, 0->5
            else if (buf[i])
                state = 7;
            else            // 1个0为状态2,2个0为状态1,3个0为状态0
                state >>= 1;           // 2->1, 1->0, 0->0
        }
        else if (state <= 5)
        {
            int nalu_type = buf[i] & 0x1F;
            if (nalu_type == H264_NAL_SEI || nalu_type == H264_NAL_SPS ||
                nalu_type == H264_NAL_PPS || nalu_type == H264_NAL_AUD)
            { //目前状态是4或5, 且该字节是SPS,PPS,AUD或SEI
                if (pc->frame_start_found)  //且frame_start_found 已经设置过,说明目前一在新包字节中
                {
                    i++;
                    goto found; //跳走到found
                }
            }
            else if (nalu_type == H264_NAL_SLICE || nalu_type == H264_NAL_DPA ||
                       nalu_type == H264_NAL_IDR_SLICE)
            {
                state += 8; //如果当前字节类型(nal类型)是条带,idr条带或DPA,状态上跳8
                continue;
            }
            state = 7; //不是以上类型状态机又回到初始态.
        }
        else
        {  //此时状态机状态是大于7 的(找到过00 00 01 起始码)
            unsigned int mb, last_mb = p->parse_last_mb;
            GetBitContext gb;
            p->parse_history[p->parse_history_count++] = buf[i]; //buf[i]字节保存

            init_get_bits(&gb, p->parse_history, 8*p->parse_history_count); //初始化哥伦布context
            mb= get_ue_golomb_long(&gb); //获取哥伦布数值
            if (get_bits_left(&gb) > 0 || p->parse_history_count > 5)
            {
                p->parse_last_mb = mb; //满足条件,保留mb
                if (pc->frame_start_found) // 前面有过1次起始码
                {
                    if (mb <= last_mb)
                    {
                        i -= p->parse_history_count - 1;
                        p->parse_history_count = 0;
                        goto found; //这里又满足一定条件又找到了起始码表示找到了包尾,goto found
                    }
                }
                else
                    pc->frame_start_found = 1; //第一次找到起始码
                p->parse_history_count = 0;
                state = 7;
            }
        } // end else
    } //end for
    pc->state = state; //保留状态
    if (p->is_avc)
        return next_avc;
    return END_NOT_FOUND; //返回没找到包尾.

found:
    pc->state             = 7; //状态设置为初始态
    pc->frame_start_found = 0;
    if (p->is_avc)
        return next_avc;
//返回包尾长度,去掉发现的00 00 01(也可能是00 00 00 01) 的长度
//即去掉4或5个字节,其中3或4个包头字节加一个NAL_TYPE 字节
    return i - (state & 5);
}

/*
组包程序简明注释.
pc 是操作上下文
next 是到尾巴的大小
buf,buf_size为待处理的数据
功能:未找到包尾时先把数据保留起来. 找到包尾时把所有数据合成一个整包,
*/
int ff_combine_frame(ParseContext *pc, int next,
                     const uint8_t **buf, int *buf_size)
{
//代码有简化
    pc->last_index = pc->index;
    /* copy into buffer end, return */
//传来的数据没有找到尾部,则把旧数据pc->buffer,pc->buffer_size扩大容量到pc->index+*buf_size+AV_INPUT_BUFFER_PADDING_SIZE
//可见pc->index 保留了旧数据的有效数据长度
    if (next == END_NOT_FOUND)  
    {
        void *new_buffer = av_fast_realloc(pc->buffer, &pc->buffer_size,
                                           *buf_size + pc->index +
                                           AV_INPUT_BUFFER_PADDING_SIZE);

        pc->buffer = new_buffer;
        memcpy(&pc->buffer[pc->index], *buf, *buf_size); //把传来的数据copy走,相当于追加到后面
        pc->index += *buf_size; //修改pc->index,
        return -1; //返回-1 组包未完成
    }


    //修改数据长度为已有数据长度加上到尾巴的长度
    *buf_size          = pc->overread_index = pc->index + next;

    /* append to buffer */
    if (pc->index)  //如果上次有数据
    { // 重新分配内存到新长度
        void *new_buffer = av_fast_realloc(pc->buffer, &pc->buffer_size,
                                           next + pc->index +
                                           AV_INPUT_BUFFER_PADDING_SIZE);
        pc->buffer = new_buffer;
// 这里数据copy 多了点,加了AV_INPUT_BUFFER_PADDING_SIZE, 64个bytes
// 那会不会影响下一个组包呢? 不会,这只是填充数据,多copy 的点,不影响原buf数据
        if (next > -AV_INPUT_BUFFER_PADDING_SIZE)
            memcpy(&pc->buffer[pc->index], *buf, next + AV_INPUT_BUFFER_PADDING_SIZE);
        pc->index = 0; //把pc->index 设置成0, 使得下一次它就是新的包头了.
        *buf      = pc->buffer; //修改新分配的内存地址.
    }

    return 0; //返回0,成功文章来源地址https://www.toymoban.com/news/detail-575179.html

/*
 * 了解av_parser_parse2 的工作原理 测试代码
 * 跟踪调试进入ffmpeg 内部才会有收获!
 */
#include <stdio.h>
extern "C"
{
#include "libavcodec/avcodec.h"
};

#define BUF_SIZE 40960
uint8_t 	gBuf[BUF_SIZE];//每次从文件中读取数据的缓冲
AVPacket gPacket;
void print_16byte(FILE *fp, uint8_t *data);

int main()
{
	const char* iFileName = "/opt/test/1.h264";//"1.mpeg4";"1.aac"; 裸码流的获取,可通过ffmpeg 获取
	const char* oFileName = "t1.data";//
	FILE* fp = fopen(iFileName, "rb");
	if (!fp) 
	{
		return -1;
	}

	FILE* fp_out = fopen(oFileName, "wb");
	if (!fp_out) return -1;
	
//	enum AVCodecID vDecoderID = AV_CODEC_ID_MP3;//AV_CODEC_ID_MPEG4;//AV_CODEC_ID_H264;
	enum AVCodecID vDecoderID = AV_CODEC_ID_H264;  //还可以指定mp3,mpeg4类型,看文件数据类型
	AVCodec *pCodec = avcodec_find_decoder(vDecoderID);//获取指定类型的解码器, parser 需要codec_ctx
	if(!pCodec)
	{
		return -1;
	}
	AVCodecContext *pCodecCtx = avcodec_alloc_context3(pCodec);//根据解码器分配一个解码器上下文
	if (!pCodecCtx)
	{
		return -1;
	}
	AVCodecParserContext* pCodecParserCtx = av_parser_init(vDecoderID);//根据解码器类型获取解析器上下文
	if (!pCodecParserCtx)
	{
		return -1;
	}

	uint8_t* pCurBuf = NULL;              //记录缓冲读指针位置
	int dwCurBufSize = 0;           //记录当前缓冲中剩余有效数据长度
	int pos = 0;
	for (;;)
	{
		dwCurBufSize = fread(gBuf, 1, BUF_SIZE, fp);
		if (feof(fp))
		{
			printf("end of file\n");
			break;
		}
		if (dwCurBufSize == 0)
		{
			return -1;
		}
		pCurBuf = gBuf;
		while (dwCurBufSize > 0)
		{
			//解析处理,gPacket.size不为0,说明解析出完整的一帧数据
			//len 是返回的消耗的数据长度
			//gPacket.size, 是怎样来的? 是找到帧尾返回的数据大小
			//gPacket.data, 内存地址为什么每次都变动?是怎样来的?
			//为什么这一帧结尾和下一帧开头之间数据没有直接衔接,缝中的数据是什么?
			int len = av_parser_parse2(pCodecParserCtx, pCodecCtx, 
					&gPacket.data, &gPacket.size,      //输出
					pCurBuf, dwCurBufSize,                //输入
				   AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); //后面3参数不重要,用来填充当前frame时间位置信息

			pCurBuf += len; //移动缓冲读指针
			dwCurBufSize -= len;//剩余缓冲数据长度
			if (gPacket.size == 0)
			{
				//如果输出size为0 说明输入长度不够解析为完整的一帧,需要继续输入数据
				//如果当前缓冲区 dwCurBufSize为0, 则需要继续从文件读取数据到缓冲区
				continue;
			}
			/*
			//打印输出帧信息
			printf("Packet Seq Num:%d\t", pCodecParserCtx->output_picture_number);
			printf("KeyFrame:%d pts:%ld, dts:%ld, duration:%d\t", pCodecParserCtx->key_frame, 
					pCodecParserCtx->pts, pCodecParserCtx->dts, pCodecParserCtx->duration);
			switch(pCodecParserCtx->pict_type)
			{
				case AV_PICTURE_TYPE_I:
					printf("\tPacket Type:I\t");
					break;
				case AV_PICTURE_TYPE_P:
					printf("\tPacket Type:P\t");
					break;
				case AV_PICTURE_TYPE_B:
					printf("\tPacket Type:B\t");
					break;
				default:
					printf("\tPacket Type:error:%d\t", pCodecParserCtx->pict_type);
					break;
			}

			printf("\tPacket Size:%d\n", gPacket.size);
			*/
			
			//将位置偏移,16bytes 数据保存到文件中, 可以与原文件进行对比
			fprintf(fp_out, "%d\n", pos);
			print_16byte(fp_out,gPacket.data);
			pos += gPacket.size;
		}

	}   
}
void print_16byte(FILE *fp, uint8_t *data)
{
	for(int i=0;i<16;i++)
	{
		fprintf(fp,"%02x ",data[i]);
	}
	fprintf(fp,"\n");
}

到了这里,关于ffmpeg av_parser_parse2函数分析各种码流测试程序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 三.ffmpeg 集成av1

    copy from zhujiamin 一、介绍 FFmpeg4.2支持AV1、AVS2等视频编码格式,但本身并不包含解码器,需要自己集成。集成的编解码器要避开GPL开源协议(–enable-gpl),因此不能用x264、AVS2等编解码器 我在研究FFmpeg升级时,寻找能提升多媒体系统表现力的新特性,发现FFmpeg支持的基于BSD协

    2023年04月24日
    浏览(37)
  • ffmpeg中的avs解码器综述

    最近拿了一个avs的视频流,用硬件可以解码,但是ffmpeg自带的却无法解码。 所以研究了一下,首先看ffmpeg的avs解码器: 可以看到avs有两个,第一个是avs 第二个是cavs. 我们先用avs来解码,解码的视频是通过【 avs编码器 】编码的: 结果发现有问题,尺寸本来是640 360,结果被强

    2024年02月08日
    浏览(51)
  • ffmpeg 中 av_log 是怎样工作的?

    ---------------------------------------- author: hjjdebug date:   2023年 07月 27日 星期四 14:56:38 CST descriptor: ffmpeg 中 av_log 是怎样工作的? ---------------------------------------- av_log 功能其实只是添加了颜色,LOG级别,及log上下文名称,没有添加时间,函数名称,行号等信息. 就这一点就引起了血雨腥风的

    2024年02月15日
    浏览(31)
  • FFmpeg5.0源码阅读—— av_read_frame

       摘要 :本文主要描述了FFmpeg中用于打开编解码器接口 av_read_frame 的具体调用流程,详细描述了该接口被调用时所作的具体工作。    : ffmpeg 、 av_read_frame    读者须知 :读者需要了解FFmpeg的基本使用流程,以及一些FFmpeg的基本常识,了解FFmpegIO相关的内容,

    2024年02月16日
    浏览(31)
  • FFmpeg5.0源码阅读——av_interleaved_write_frame

       摘要 :本文主要详细描述FFmpeg中封装时写packet到媒体文件的函数 av_interleaved_write_frame 的实现。    : av_interleaved_write_frame    读者须知 :读者需要熟悉ffmpeg的基本使用。    av_interleaved_write_frame 的基本调用流程图如下。   首先就是根据输入数据是否为空

    2024年02月14日
    浏览(48)
  • 各种窗函数特性分析

    窗函数的三个重要参数: leakage factor 泄露指数 Relative sidelobe attenuation 旁瓣衰减 Mainlobe width(-3dB) 主瓣宽度 从三个指标来看,Hamming都要优于Hanning,但实际上还有一个指标没有加进来,即旁瓣滚降率(sidelobe roll-off rate),可以很明显看出Hanning的滚降率是大于Hamming的。Hanning的滚

    2024年02月09日
    浏览(36)
  • ffmpeg.c源码与函数关系分析

    FFmpeg 是一个可以处理音视频的软件,功能非常强大,主要包括,编解码转换,封装格式转换,滤镜特效。 FFmpeg支持各种网络协议,支持 RTMP ,RTSP,HLS 等高层协议的推拉流,也支持更底层的TCP/UDP 协议推拉流。 FFmpeg 可以在 Windows,Linux,Mac,iOS,Android等操作系统上运行。 F

    2024年02月14日
    浏览(34)
  • FFmpeg进阶:各种输入输出设备

    我们可以通过ffmpeg自带的工具查看系统支持的设备列表信息, 对应的指令如下所示: 通过配置ffmpeg的输入设备,我们可以访问系统中的某个多媒体设备的数据。下面详细介绍一下各个系统中的常见设备。 1.alsa ALSA(Advanced Linux Sound Architecture——高级Linux音频架构)是Linux系统中用来

    2024年02月16日
    浏览(34)
  • FFmpeg基础:获取音视频的各种编码参数

    获取视频编码参数 视频编码参数主要包括:帧率、分辨率、编码格式、码率等,对应的概念如下。 帧率(Frame Rate) 每秒显示帧数(Frames Per Second)。电影的帧率一般是25fps和29.97fps,3D游戏要保持流畅则需要30fps以上的效果。 分辨率 指视频宽高的像素数值。标准1080P的分辨率为1920×

    2024年02月03日
    浏览(48)
  • ffmpeg获取rtsp h265,用ffmpeg从RTSP服务器拉流并保存各种格式文件

    ffmpeg:FFmpeg的名称来自MPEG视频编码标准,前面的“FF”代表“Fast Forward,是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。 库的组成: libavformat:用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构和读取音

    2024年01月22日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包