ffmpeg源码简析(五)编码——avformat_alloc_output_context2(),avcodec_encode_video2()

1.avformat_alloc_output_context2()

在基于FFmpeg的视音频编码器程序中,该函数通常是第一个调用的函数(除了组件注册函数av_register_all())。avformat_alloc_output_context2()函数可以初始化一个用于输出的AVFormatContext结构体。它的声明位于libavformat\avformat.h,如下所示。

int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat,  
                                   const char *format_name, const char *filename);  
  • 1
  • 2

ctx:函数调用成功之后创建的AVFormatContext结构体。 
oformat:指定AVFormatContext中的AVOutputFormat,用于确定输出格式。如果指定为NULL,可以设定后两个参数(format_name或者filename)由FFmpeg猜测输出格式。 
PS:使用该参数需要自己手动获取AVOutputFormat,相对于使用后两个参数来说要麻烦一些。 
format_name:指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等。 
filename:指定输出文件的名称。根据文件名称,FFmpeg会推测输出格式。文件名称可以是“xx.flv”,“yy.mkv”等等。

函数执行成功的话,其返回值大于等于0。

下面看一下avformat_alloc_output_context2()的函数定义。该函数的定义位于libavformat\mux.c中,如下所示。

int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat,  
                                   const char *format, const char *filename)  
{  
    AVFormatContext *s = avformat_alloc_context();  
    int ret = 0;  


    *avctx = NULL;  
    if (!s)  
        goto nomem;  


    if (!oformat) {  
        if (format) {  
            oformat = av_guess_format(format, NULL, NULL);  
            if (!oformat) {  
                av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format);  
                ret = AVERROR(EINVAL);  
                goto error;  
            }  
        } else {  
            oformat = av_guess_format(NULL, filename, NULL);  
            if (!oformat) {  
                ret = AVERROR(EINVAL);  
                av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n",  
                       filename);  
                goto error;  
            }  
        }  
    }  


    s->oformat = oformat;  
    if (s->oformat->priv_data_size > 0) {  
        s->priv_data = av_mallocz(s->oformat->priv_data_size);  
        if (!s->priv_data)  
            goto nomem;  
        if (s->oformat->priv_class) {  
            *(const AVClass**)s->priv_data= s->oformat->priv_class;  
            av_opt_set_defaults(s->priv_data);  
        }  
    } else  
        s->priv_data = NULL;  


    if (filename)  
        av_strlcpy(s->filename, filename, sizeof(s->filename));  
    *avctx = s;  
    return 0;  
nomem:  
    av_log(s, AV_LOG_ERROR, "Out of memory\n");  
    ret = AVERROR(ENOMEM);  
error:  
    avformat_free_context(s);  
    return ret;  
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

从代码中可以看出,avformat_alloc_output_context2()的流程如要包含以下2步:

1)  调用avformat_alloc_context()初始化一个默认的AVFormatContext。

2)  如果指定了输入的AVOutputFormat,则直接将输入的AVOutputFormat赋值给AVOutputFormat的oformat。如果没有指定输入的AVOutputFormat,就需要根据文件格式名称或者文件名推测输出的AVOutputFormat。无论是通过文件格式名称还是文件名推测输出格式,都会调用一个函数av_guess_format()。

下面我们分别看看上文步骤中提到的两个重要的函数:avformat_alloc_context()和av_guess_format()。 
avformat_alloc_context()

    AVFormatContext *avformat_alloc_context(void)  
    {  
        AVFormatContext *ic;  
        ic = av_malloc(sizeof(AVFormatContext));  
        if (!ic) return ic;  
        avformat_get_context_defaults(ic);  


        ic->internal = av_mallocz(sizeof(*ic->internal));  
        if (!ic->internal) {  
            avformat_free_context(ic);  
            return NULL;  
        }  


        return ic;  
    } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

从代码中可以看出,avformat_alloc_context()首先调用av_malloc()为AVFormatContext分配一块内存。然后调用了一个函数avformat_get_context_defaults()用于给AVFormatContext设置默认值。avformat_get_context_defaults()的定义如下。



    static void avformat_get_context_defaults(AVFormatContext *s)  
    {  
        memset(s, 0, sizeof(AVFormatContext));  


        s->av_class = &av_format_context_class;  


        av_opt_set_defaults(s);  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

从代码中可以看出,avformat_alloc_context()首先调用memset()将AVFormatContext的内存置零;然后指定它的AVClass(指定了AVClass之后,该结构体就支持和AVOption相关的功能);最后调用av_opt_set_defaults()给AVFormatContext的成员变量设置默认值(av_opt_set_defaults()就是和AVOption有关的一个函数,专门用于给指定的结构体设定默认值,此处暂不分析)。

av_guess_format()

AVOutputFormat *av_guess_format(const char *short_name,  
                                const char *filename,  
                                const char *mime_type);  
  • 1
  • 2
  • 3
short_name:格式的名称。
filename:文件的名称。
mime_type:MIME类型。

返回最匹配的AVOutputFormat。如果没有很匹配的AVOutputFormat,则返回NULL。

AVOutputFormat *av_guess_format(const char *short_name, const char *filename,  
                                const char *mime_type)  
{  
    AVOutputFormat *fmt = NULL, *fmt_found;  
    int score_max, score;  


    /* specific test for image sequences */  
#if CONFIG_IMAGE2_MUXER  
    if (!short_name && filename &&  
        av_filename_number_test(filename) &&  
        ff_guess_image2_codec(filename) != AV_CODEC_ID_NONE) {  
        return av_guess_format("image2", NULL, NULL);  
    }  
#endif  
    /* Find the proper file type. */  
    fmt_found = NULL;  
    score_max = 0;  
    while ((fmt = av_oformat_next(fmt))) {  
        score = 0;  
        if (fmt->name && short_name && av_match_name(short_name, fmt->name))  
            score += 100;  
        if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type))  
            score += 10;  
        if (filename && fmt->extensions &&  
            av_match_ext(filename, fmt->extensions)) {  
            score += 5;  
        }  
        if (score > score_max) {  
            score_max = score;  
            fmt_found = fmt;  
        }  
    }  
    return fmt_found;  
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

从代码中可以看出,av_guess_format()中使用一个整型变量score记录每种输出格式的匹配程度。函数中包含了一个while()循环,该循环利用函数av_oformat_next()遍历FFmpeg中所有的AVOutputFormat,并逐一计算每个输出格式的score。具体的计算过程分成如下几步:

1)  如果封装格式名称匹配,score增加100。匹配中使用了函数av_match_name()。
2)  如果mime类型匹配,score增加10。匹配直接使用字符串比较函数strcmp()。
3)  如果文件名称的后缀匹配,score增加5。匹配中使用了函数av_match_ext()。

while()循环结束后,得到得分最高的格式,就是最匹配的格式

av_guess_format()最终可以得到最合适的AVOutputFormat并且返回给avformat_alloc_output_context2()。avformat_alloc_output_context2()接下来将获得的AVOutputFormat赋值给刚刚新建的AVFormatContext,即可完成初始化工作。

2.avformat_write_header()

其中av_write_frame()用于写视频数据,avformat_write_header()用于写视频文件头,而av_write_trailer()用于写视频文件尾。

avformat_write_header()的声明位于libavformat\avformat.h,如下所示

int avformat_write_header(AVFormatContext *s, AVDictionary **options); 
  • 1

简单解释一下它的参数的含义:

s:用于输出的AVFormatContext。
options:额外的选项,目前没有深入研究过,一般为NULL。

函数正常执行后返回值等于0。

avformat_write_header()的定义位于libavformat\mux.c,如下所示。

    int avformat_write_header(AVFormatContext *s, AVDictionary **options)  
    {  
        int ret = 0;  

        if (ret = init_muxer(s, options))  
            return ret;  

        if (s->oformat->write_header) {  
            ret = s->oformat->write_header(s);  
            if (ret >= 0 && s->pb && s->pb->error < 0)  
                ret = s->pb->error;  
            if (ret < 0)  
                return ret;  
            if (s->flush_packets && s->pb && s->pb->error >= 0 && s->flags & AVFMT_FLAG_FLUSH_PACKETS)  
                avio_flush(s->pb);  
        }  

        if ((ret = init_pts(s)) < 0)  
            return ret;  

        if (s->avoid_negative_ts < 0) {  
            av_assert2(s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_AUTO);  
            if (s->oformat->flags & (AVFMT_TS_NEGATIVE | AVFMT_NOTIMESTAMPS)) {  
                s->avoid_negative_ts = 0;  
            } else  
                s->avoid_negative_ts = AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE;  
        }  

        return 0;  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

从源代码可以看出,avformat_write_header()完成了以下工作:

(1)调用init_muxer()初始化复用器
(2)调用AVOutputFormat的write_header()

init_muxer() 
init_muxer()代码很长,但是它所做的工作比较简单,可以概括成两个字:检查。函数的流程可以概括成以下几步:

(1)将传入的AVDictionary形式的选项设置到AVFormatContext
(2)遍历AVFormatContext中的每个AVStream,并作如下检查:

    a)AVStream的time_base是否正确设置。如果发现AVStream的time_base没有设置,则会调用avpriv_set_pts_info()进行设置。

    b)对于音频,检查采样率设置是否正确;对于视频,检查宽、高、宽高比。

    c)其他一些检查,不再详述。

AVOutputFormat->write_header() 
avformat_write_header()中最关键的地方就是调用了AVOutputFormat的write_header()。write_header()是AVOutputFormat中的一个函数指针,指向写文件头的函数。不同的AVOutputFormat有不同的write_header()的实现方法。

3.avcodec_encode_video2()

该函数用于编码一帧视频数据。avcodec_encode_video2()函数的声明位于libavcodec\avcodec.h,如下所示。

int avcodec_encode_video2(AVCodecContext *avctx, AVPacket *avpkt,  
                          const AVFrame *frame, int *got_packet_ptr); 
  • 1
  • 2
avctx:编码器的AVCodecContext。
avpkt:编码输出的AVPacket。
frame:编码输入的AVFrame。
got_packet_ptr:成功编码一个AVPacket的时候设置为1。

  函数返回0代表编码成功。

avcodec_encode_video2()的定义位于libavcodec\utils.c,如下所示。

    int attribute_align_arg avcodec_encode_video2(AVCodecContext *avctx,  
                                                  AVPacket *avpkt,  
                                                  const AVFrame *frame,  
                                                  int *got_packet_ptr)  
    {  
        int ret;  
        AVPacket user_pkt = *avpkt;  
        int needs_realloc = !user_pkt.data;  

        *got_packet_ptr = 0;  

        if(CONFIG_FRAME_THREAD_ENCODER &&  
           avctx->internal->frame_thread_encoder && (avctx->active_thread_type&FF_THREAD_FRAME))  
            return ff_thread_video_encode_frame(avctx, avpkt, frame, got_packet_ptr);  

        if ((avctx->flags&CODEC_FLAG_PASS1) && avctx->stats_out)  
            avctx->stats_out[0] = '\0';  

        if (!(avctx->codec->capabilities & CODEC_CAP_DELAY) && !frame) {  
            av_free_packet(avpkt);  
            av_init_packet(avpkt);  
            avpkt->size = 0;  
            return 0;  
        }  
        //检查输入  
        if (av_image_check_size(avctx->width, avctx->height, 0, avctx))  
            return AVERROR(EINVAL);  

        av_assert0(avctx->codec->encode2);  
        //编码  
        ret = avctx->codec->encode2(avctx, avpkt, frame, got_packet_ptr);  
        av_assert0(ret <= 0);  

        if (avpkt->data && avpkt->data == avctx->internal->byte_buffer) {  
            needs_realloc = 0;  
            if (user_pkt.data) {  
                if (user_pkt.size >= avpkt->size) {  
                    memcpy(user_pkt.data, avpkt->data, avpkt->size);  
                } else {  
                    av_log(avctx, AV_LOG_ERROR, "Provided packet is too small, needs to be %d\n", avpkt->size);  
                    avpkt->size = user_pkt.size;  
                    ret = -1;  
                }  
                avpkt->buf      = user_pkt.buf;  
                avpkt->data     = user_pkt.data;  
    #if FF_API_DESTRUCT_PACKET  
    FF_DISABLE_DEPRECATION_WARNINGS  
                avpkt->destruct = user_pkt.destruct;  
    FF_ENABLE_DEPRECATION_WARNINGS  
    #endif  
            } else {  
                if (av_dup_packet(avpkt) < 0) {  
                    ret = AVERROR(ENOMEM);  
                }  
            }  
        }  

        if (!ret) {  
            if (!*got_packet_ptr)  
                avpkt->size = 0;  
            else if (!(avctx->codec->capabilities & CODEC_CAP_DELAY))  
                avpkt->pts = avpkt->dts = frame->pts;  

            if (needs_realloc && avpkt->data) {  
                ret = av_buffer_realloc(&avpkt->buf, avpkt->size + FF_INPUT_BUFFER_PADDING_SIZE);  
                if (ret >= 0)  
                    avpkt->data = avpkt->buf->data;  
            }  

            avctx->frame_number++;  
        }  

        if (ret < 0 || !*got_packet_ptr)  
            av_free_packet(avpkt);  
        else  
            av_packet_merge_side_data(avpkt);  

        emms_c();  
        return ret;  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80

从函数的定义可以看出,avcodec_encode_video2()首先调用了av_image_check_size()检查设置的宽高参数是否合理,然后调用了AVCodec的encode2()调用具体的解码器。

av_image_check_size() 
av_image_check_size()是一个很简单的函数,用于检查图像宽高是否正常,它的定义如下所示。

    int av_image_check_size(unsigned int w, unsigned int h, int log_offset, void *log_ctx)  
    {  
        ImgUtils imgutils = { &imgutils_class, log_offset, log_ctx };  

        if ((int)w>0 && (int)h>0 && (w+128)*(uint64_t)(h+128) < INT_MAX/8)  
            return 0;  

        av_log(&imgutils, AV_LOG_ERROR, "Picture size %ux%u is invalid\n", w, h);  
        return AVERROR(EINVAL);  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

从代码中可以看出,av_image_check_size()主要是要求图像宽高必须为正数,而且取值不能太大。

AVCodec->encode2() 
AVCodec的encode2()是一个函数指针,指向特定编码器的编码函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值