24 ffmpeg 解析器和编解码器

本文详细介绍了FFmpeg中H264编解码器的注册过程及解析器的工作原理。重点讲述了如何通过解析器将带有NAL单元的H264数据转换为可用于解码的有效帧数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

avcodec_register_all被av_register_all调用了,主要是注册编解码器。

#define REGISTER_ENCODER(X,x) { \
          extern AVCodec ff_##x##_encoder; \
          if(CONFIG_##X##_ENCODER)  avcodec_register(&ff_##x##_encoder); }
#define REGISTER_DECODER(X,x) { \
          extern AVCodec ff_##x##_decoder; \
          if(CONFIG_##X##_DECODER)  avcodec_register(&ff_##x##_decoder); }
#define REGISTER_ENCDEC(X,x)  REGISTER_ENCODER(X,x); REGISTER_DECODER(X,x)

#define REGISTER_PARSER(X,x) { \
          extern AVCodecParser ff_##x##_parser; \
          if(CONFIG_##X##_PARSER)  av_register_codec_parser(&ff_##x##_parser); }

void avcodec_register_all(void)
{
    。。。
    REGISTER_DECODER (H264, h264);
    REGISTER_ENCODER (LIBX264, libx264);

    REGISTER_PARSER  (H264, h264);
    。。。
}

用h264举个例子,在ffmpeg中,h264编码用的libx264,解码用的自身的h264解码。在解码之前需要调用h264 parser。h264自身封装了一层NAL, parser是解析NAL的,解析后才是真正的h264编码数据。

parser

AVCodecParser ff_h264_parser = {
    .codec_ids      = { AV_CODEC_ID_H264 },
    .priv_data_size = sizeof(H264ParseContext),
    .parser_init    = init,
    .parser_parse   = h264_parse,
    .parser_close   = h264_close,
    .split          = h264_split,
};

其中的parser_init函数原型是:

//参数为 AVCodecParserContext
int (*parser_init)(AVCodecParserContext *s);

在 av_parser_init 中被调用,根据 codec_id 来选择AVCodecParser。

AVCodecParserContext *av_parser_init(int codec_id)
{
    AVCodecParserContext *s = NULL;
    AVCodecParser *parser;
    int ret;

    if (codec_id == AV_CODEC_ID_NONE)
        return NULL;

    for (parser = av_first_parser; parser; parser = parser->next) {
        if (parser->codec_ids[0] == codec_id ||
            parser->codec_ids[1] == codec_id ||
            parser->codec_ids[2] == codec_id ||
            parser->codec_ids[3] == codec_id ||
            parser->codec_ids[4] == codec_id)
            goto found;
    }
    return NULL;

found:
    s = av_mallocz(sizeof(AVCodecParserContext));
    s->parser = parser;
    s->priv_data = av_mallocz(parser->priv_data_size);

    //在这里调用了parser_init
    if (parser->parser_init) {
        ret = parser->parser_init(s);
        if (ret != 0)
            goto err_out;
    }

    return s;

    ....
}

parser_parse的函数原型是:

//输入参数:parser context和codec context,内存:buf+buf_size
//输出参数:内存:*poutbuf + *poutbuf_size
int (*parser_parse)(AVCodecParserContext *s,
                        AVCodecContext *avctx,
                        const uint8_t **poutbuf, int *poutbuf_size,
                        const uint8_t *buf, int buf_size);

在 av_parser_parse2 中调用,av_parser_parse2的参数比parser_parser的参数多了pts,dts,pos。

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)
{
    ......
    /* WARNING: the returned index can be negative */
    index = s->parser->parser_parse(s, avctx, (const uint8_t **) poutbuf,
                                    poutbuf_size, buf, buf_size);

    ......

    return index;
}

在libavcodec/parser.c中封装了对于解析器的调用,av_parser_init和av_parser_parse2函数都在这个文件中。下面看一下两个函数的调用。从read_frame_internal入手,read_frame_internal返回的是一个帧的数据,这是一个完整的帧。那获取到一个完整的帧,肯定是需要调用parser的,只有通过parser才能解析出一个完整的帧。

av_parser_init 使用

read_frame_internal 进入 while循环:调用 ff_read_packet 来读取包,这个包只能说是数据包,没有分界线。后根据need_parsing来判断是否进行初始化av_parser_init。

static int read_frame_internal(AVFormatContext *s, AVPacket *pkt) {
    int ret = 0, i, got_packet = 0;

    av_init_packet(pkt);

    while (!got_packet && !s->internal->parse_queue) {
        AVStream *st;
        AVPacket cur_pkt;

        /* read next packet */
        ret = ff_read_packet(s, &cur_pkt);

        st  = s->streams[cur_pkt.stream_index];

        if (st->need_parsing && !st->parser && !(s->flags & AVFMT_FLAG_NOPARSE)) {
            st->parser = av_parser_init(st->codec->codec_id);
            if (!st->parser) {
                st->need_parsing = AVSTREAM_PARSE_NONE;
            } else if (st->need_parsing == AVSTREAM_PARSE_HEADERS)
                st->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
            else if (st->need_parsing == AVSTREAM_PARSE_FULL_ONCE)
                st->parser->flags |= PARSER_FLAG_ONCE;
            else if (st->need_parsing == AVSTREAM_PARSE_FULL_RAW)
                st->parser->flags |= PARSER_FLAG_USE_CODEC_TS;
        }
    }

    ......
}

那need_parsing在哪里配置,在AVInputFormat中进行赋值的。以flv举例:

static int flv_set_video_codec(AVFormatContext *s, AVStream *vstream,
                               int flv_codecid, int read)
{
    AVCodecContext *vcodec = vstream->codec;
    switch (flv_codecid) {

    case FLV_CODECID_H264:
        vcodec->codec_id = AV_CODEC_ID_H264;
        vstream->need_parsing = AVSTREAM_PARSE_HEADERS;
        return 3;     // not 4, reading packet type will consume one byte

    }

    return 0;
}

当h264时,设置了need_parsing为AVSTREAM_PARSE_HEADERS,代表是需要解析头部。

parse_packet 调用

parser初始化后,该调用parse_packet了。这个里面使用起来很简单。

static int read_frame_internal(AVFormatContext *s, AVPacket *pkt)
{
    int ret = 0, i, got_packet = 0;

    av_init_packet(pkt);

    while (!got_packet && !s->internal->parse_queue) {
        AVStream *st;
        AVPacket cur_pkt;

        /* read next packet */
        ret = ff_read_packet(s, &cur_pkt);

        st  = s->streams[cur_pkt.stream_index];

        if (st->need_parsing && !st->parser && !(s->flags & AVFMT_FLAG_NOPARSE)) {
            st->parser = av_parser_init(st->codec->codec_id);
            if (!st->parser) {
                /* no parser available: just output the raw packets */
                st->need_parsing = AVSTREAM_PARSE_NONE;
            } else if (st->need_parsing == AVSTREAM_PARSE_HEADERS)
                st->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
            else if (st->need_parsing == AVSTREAM_PARSE_FULL_ONCE)
                st->parser->flags |= PARSER_FLAG_ONCE;
            else if (st->need_parsing == AVSTREAM_PARSE_FULL_RAW)
                st->parser->flags |= PARSER_FLAG_USE_CODEC_TS;
        }

        if (!st->need_parsing || !st->parser) {

        } else if (st->discard < AVDISCARD_ALL) {
            //在这里
            if ((ret = parse_packet(s, &cur_pkt, cur_pkt.stream_index)) < 0)
                return ret;
        } else {
            /* free packet */
            av_packet_unref(&cur_pkt);
        }

        ......
    }
}

有一个h264的问题,一个帧里面包含了为多个slice,而parser是一个个slice来解析的。那parser返回的数据包是一个slice吗?

这个就的分析h264的parser具体逻辑啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值