• FFmpeg源代码简单分析-编码-avformat_alloc_output_context2()


    参考链接

    avformat_alloc_output_context2()

    • 在基于FFmpeg的视音频编码器程序中,该函数通常是第一个调用的函数(除了组件注册函数av_register_all())
    • avformat_alloc_output_context2()函数可以初始化一个用于输出的AVFormatContext结构体
    • 它的声明位于libavformat\avformat.h,如下所示
    1. /**
    2. * Allocate an AVFormatContext for an output format.
    3. * avformat_free_context() can be used to free the context and
    4. * everything allocated by the framework within it.
    5. *
    6. * @param *ctx is set to the created format context, or to NULL in
    7. * case of failure
    8. * @param oformat format to use for allocating the context, if NULL
    9. * format_name and filename are used instead
    10. * @param format_name the name of output format to use for allocating the
    11. * context, if NULL filename is used instead
    12. * @param filename the name of the filename to use for allocating the
    13. * context, may be NULL
    14. * @return >= 0 in case of success, a negative AVERROR code in case of
    15. * failure
    16. */
    17. int avformat_alloc_output_context2(AVFormatContext **ctx, const AVOutputFormat *oformat,
    18. const char *format_name, const char *filename);
    • 参数介绍
      • 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中
    1. int avformat_alloc_output_context2(AVFormatContext **avctx, const AVOutputFormat *oformat,
    2. const char *format, const char *filename)
    3. {
    4. AVFormatContext *s = avformat_alloc_context();
    5. int ret = 0;
    6. *avctx = NULL;
    7. if (!s)
    8. goto nomem;
    9. if (!oformat) {
    10. if (format) {
    11. oformat = av_guess_format(format, NULL, NULL);
    12. if (!oformat) {
    13. av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format);
    14. ret = AVERROR(EINVAL);
    15. goto error;
    16. }
    17. } else {
    18. oformat = av_guess_format(NULL, filename, NULL);
    19. if (!oformat) {
    20. ret = AVERROR(EINVAL);
    21. av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n",
    22. filename);
    23. goto error;
    24. }
    25. }
    26. }
    27. s->oformat = oformat;
    28. if (s->oformat->priv_data_size > 0) {
    29. s->priv_data = av_mallocz(s->oformat->priv_data_size);
    30. if (!s->priv_data)
    31. goto nomem;
    32. if (s->oformat->priv_class) {
    33. *(const AVClass**)s->priv_data= s->oformat->priv_class;
    34. av_opt_set_defaults(s->priv_data);
    35. }
    36. } else
    37. s->priv_data = NULL;
    38. if (filename) {
    39. if (!(s->url = av_strdup(filename)))
    40. goto nomem;
    41. }
    42. *avctx = s;
    43. return 0;
    44. nomem:
    45. av_log(s, AV_LOG_ERROR, "Out of memory\n");
    46. ret = AVERROR(ENOMEM);
    47. error:
    48. avformat_free_context(s);
    49. return ret;
    50. }
    • 从代码中可以看出,avformat_alloc_output_context2()的流程如要包含以下2步:
    • 1)  调用avformat_alloc_context()初始化一个默认的AVFormatContext。
    • 2) 如果指定了输入的AVOutputFormat,则直接将输入的AVOutputFormat赋值给AVOutputFormat的oformat。如果没有指定输入的AVOutputFormat,就需要根据文件格式名称或者文件名推测输出的AVOutputFormat。无论是通过文件格式名称还是文件名推测输出格式,都会调用一个函数av_guess_format()。 

    函数调用结构图

    • 函数调用结构图如下所示

     

    avformat_alloc_context()

    • avformat_alloc_context()的是一个FFmpeg的API,它的定义如下。
    1. AVFormatContext *avformat_alloc_context(void)
    2. {
    3. FFFormatContext *const si = av_mallocz(sizeof(*si));
    4. AVFormatContext *s;
    5. if (!si)
    6. return NULL;
    7. s = &si->pub;
    8. s->av_class = &av_format_context_class;
    9. s->io_open = io_open_default;
    10. s->io_close = ff_format_io_close_default;
    11. s->io_close2= io_close2_default;
    12. av_opt_set_defaults(s);
    13. si->pkt = av_packet_alloc();
    14. si->parse_pkt = av_packet_alloc();
    15. if (!si->pkt || !si->parse_pkt) {
    16. avformat_free_context(s);
    17. return NULL;
    18. }
    19. si->shortest_end = AV_NOPTS_VALUE;
    20. return s;
    21. }
    • 从代码中可以看出,avformat_alloc_context()首先调用av_malloc()为AVFormatContext分配一块内存。
    • 然后调用了一个函数av_opt_set_defaults()用于给AVFormatContext设置默认值。
    • av_opt_set_defaults()的定义如下。 
    1. void av_opt_set_defaults(void *s)
    2. {
    3. av_opt_set_defaults2(s, 0, 0);
    4. }
    5. void av_opt_set_defaults2(void *s, int mask, int flags)
    6. {
    7. const AVOption *opt = NULL;
    8. while ((opt = av_opt_next(s, opt))) {
    9. void *dst = ((uint8_t*)s) + opt->offset;
    10. if ((opt->flags & mask) != flags)
    11. continue;
    12. if (opt->flags & AV_OPT_FLAG_READONLY)
    13. continue;
    14. switch (opt->type) {
    15. case AV_OPT_TYPE_CONST:
    16. /* Nothing to be done here */
    17. break;
    18. case AV_OPT_TYPE_BOOL:
    19. case AV_OPT_TYPE_FLAGS:
    20. case AV_OPT_TYPE_INT:
    21. case AV_OPT_TYPE_INT64:
    22. case AV_OPT_TYPE_UINT64:
    23. case AV_OPT_TYPE_DURATION:
    24. #if FF_API_OLD_CHANNEL_LAYOUT
    25. FF_DISABLE_DEPRECATION_WARNINGS
    26. case AV_OPT_TYPE_CHANNEL_LAYOUT:
    27. FF_ENABLE_DEPRECATION_WARNINGS
    28. #endif
    29. case AV_OPT_TYPE_PIXEL_FMT:
    30. case AV_OPT_TYPE_SAMPLE_FMT:
    31. write_number(s, opt, dst, 1, 1, opt->default_val.i64);
    32. break;
    33. case AV_OPT_TYPE_DOUBLE:
    34. case AV_OPT_TYPE_FLOAT: {
    35. double val;
    36. val = opt->default_val.dbl;
    37. write_number(s, opt, dst, val, 1, 1);
    38. }
    39. break;
    40. case AV_OPT_TYPE_RATIONAL: {
    41. AVRational val;
    42. val = av_d2q(opt->default_val.dbl, INT_MAX);
    43. write_number(s, opt, dst, 1, val.den, val.num);
    44. }
    45. break;
    46. case AV_OPT_TYPE_COLOR:
    47. set_string_color(s, opt, opt->default_val.str, dst);
    48. break;
    49. case AV_OPT_TYPE_STRING:
    50. set_string(s, opt, opt->default_val.str, dst);
    51. break;
    52. case AV_OPT_TYPE_IMAGE_SIZE:
    53. set_string_image_size(s, opt, opt->default_val.str, dst);
    54. break;
    55. case AV_OPT_TYPE_VIDEO_RATE:
    56. set_string_video_rate(s, opt, opt->default_val.str, dst);
    57. break;
    58. case AV_OPT_TYPE_BINARY:
    59. set_string_binary(s, opt, opt->default_val.str, dst);
    60. break;
    61. case AV_OPT_TYPE_CHLAYOUT:
    62. set_string_channel_layout(s, opt, opt->default_val.str, dst);
    63. break;
    64. case AV_OPT_TYPE_DICT:
    65. set_string_dict(s, opt, opt->default_val.str, dst);
    66. break;
    67. default:
    68. av_log(s, AV_LOG_DEBUG, "AVOption type %d of option %s not implemented yet\n",
    69. opt->type, opt->name);
    70. }
    71. }
    72. }
    • 从代码中可以看出,avformat_alloc_context()首先调用memset()将AVFormatContext的内存置零
    • 然后指定它的AVClass(指定了AVClass之后,该结构体就支持和AVOption相关的功能)
    • 最后调用av_opt_set_defaults()给AVFormatContext的成员变量设置默认值(av_opt_set_defaults()就是和AVOption有关的一个函数,专门用于给指定的结构体设定默认值,此处暂不分析)。 

    av_guess_format()

    • av_guess_format()是FFmpeg的一个API
    • 它的声明如下
    1. /**
    2. * Return the output format in the list of registered output formats
    3. * which best matches the provided parameters, or return NULL if
    4. * there is no match.
    5. *
    6. * @param short_name if non-NULL checks if short_name matches with the
    7. * names of the registered formats
    8. * @param filename if non-NULL checks if filename terminates with the
    9. * extensions of the registered formats
    10. * @param mime_type if non-NULL checks if mime_type matches with the
    11. * MIME type of the registered formats
    12. */
    13. const AVOutputFormat *av_guess_format(const char *short_name,
    14. const char *filename,
    15. const char *mime_type);
    • 拿中文简单解释一下参数。
      • short_name:格式的名称。
      • filename:文件的名称。
      • mime_type:MIME类型。
    • 返回最匹配的AVOutputFormat。如果没有很匹配的AVOutputFormat,则返回NULL。
    • av_guess_format()的代码如下所示。
    1. const AVOutputFormat *av_guess_format(const char *short_name, const char *filename,
    2. const char *mime_type)
    3. {
    4. const AVOutputFormat *fmt = NULL;
    5. const AVOutputFormat *fmt_found = NULL;
    6. void *i = 0;
    7. int score_max, score;
    8. /* specific test for image sequences */
    9. #if CONFIG_IMAGE2_MUXER
    10. if (!short_name && filename &&
    11. av_filename_number_test(filename) &&
    12. ff_guess_image2_codec(filename) != AV_CODEC_ID_NONE) {
    13. return av_guess_format("image2", NULL, NULL);
    14. }
    15. #endif
    16. /* Find the proper file type. */
    17. score_max = 0;
    18. while ((fmt = av_muxer_iterate(&i))) {
    19. score = 0;
    20. if (fmt->name && short_name && av_match_name(short_name, fmt->name))
    21. score += 100;
    22. if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type))
    23. score += 10;
    24. if (filename && fmt->extensions &&
    25. av_match_ext(filename, fmt->extensions)) {
    26. score += 5;
    27. }
    28. if (score > score_max) {
    29. score_max = score;
    30. fmt_found = fmt;
    31. }
    32. }
    33. return fmt_found;
    34. }
    • 从代码中可以看出,av_guess_format()中使用一个整型变量score记录每种输出格式的匹配程度。
    • 函数中包含了一个while()循环,该循环利用函数av_muxer_iterate()遍历FFmpeg中所有的AVOutputFormat,并逐一计算每个输出格式的score。
    • 具体的计算过程分成如下几步:
      • 1)  如果封装格式名称匹配,score增加100。匹配中使用了函数av_match_name()。
      • 2)  如果mime类型匹配,score增加10。匹配直接使用字符串比较函数strcmp()。
      • 3)  如果文件名称的后缀匹配,score增加5。匹配中使用了函数av_match_ext()。
    • while()循环结束后,得到得分最高的格式,就是最匹配的格式。
    • 下面看一下一个AVOutputFormat的实例,就可以理解“封装格式名称”,“mine类型”,“文件名称后缀”这些概念了。
    • 下面是flv格式的视音频复用器(Muxer)对应的AVOutputFormat格式的变量ff_flv_muxer。
    1. const AVOutputFormat ff_flv_muxer = {
    2. .name = "flv",
    3. .long_name = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    4. .mime_type = "video/x-flv",
    5. .extensions = "flv",
    6. .priv_data_size = sizeof(FLVContext),
    7. .audio_codec = CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_ADPCM_SWF,
    8. .video_codec = AV_CODEC_ID_FLV1,
    9. .init = flv_init,
    10. .write_header = flv_write_header,
    11. .write_packet = flv_write_packet,
    12. .write_trailer = flv_write_trailer,
    13. .check_bitstream= flv_check_bitstream,
    14. .codec_tag = (const AVCodecTag* const []) {
    15. flv_video_codec_ids, flv_audio_codec_ids, 0
    16. },
    17. .flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
    18. AVFMT_TS_NONSTRICT,
    19. .priv_class = &flv_muxer_class,
    20. };

    av_muxer_iterate

    • 参数不为NULL的时候用于获得下一个AVOutputFormat
    1. const AVOutputFormat *av_muxer_iterate(void **opaque)
    2. {
    3. static const uintptr_t size = sizeof(muxer_list)/sizeof(muxer_list[0]) - 1;
    4. uintptr_t i = (uintptr_t)*opaque;
    5. const AVOutputFormat *f = NULL;
    6. uintptr_t tmp;
    7. if (i < size) {
    8. f = muxer_list[i];
    9. } else if (tmp = atomic_load_explicit(&outdev_list_intptr, memory_order_relaxed)) {
    10. const AVOutputFormat *const *outdev_list = (const AVOutputFormat *const *)tmp;
    11. f = outdev_list[i - size];
    12. }
    13. if (f)
    14. *opaque = (void*)(i + 1);
    15. return f;
    16. }

    av_match_name()

    • av_match_name()是一个API函数,定义如下所示。
    • av_match_name()用于比较两个格式的名称。简单地说就是比较字符串。
    • 注意该函数的字符串是不区分大小写的:字符都转换为小写进行比较。
    1. int av_match_name(const char *name, const char *names)
    2. {
    3. const char *p;
    4. int len, namelen;
    5. if (!name || !names)
    6. return 0;
    7. namelen = strlen(name);
    8. while (*names) {
    9. int negate = '-' == *names;
    10. p = strchr(names, ',');
    11. if (!p)
    12. p = names + strlen(names);
    13. names += negate;
    14. len = FFMAX(p - names, namelen);
    15. if (!av_strncasecmp(name, names, len) || !strncmp("ALL", names, FFMAX(3, p - names)))
    16. return !negate;
    17. names = p + (*p == ',');
    18. }
    19. return 0;
    20. }
    • 上述函数还有一点需要注意,其中使用了一个while()循环,用于搜索“,”。
    • 这是因为FFmpeg中有些格式是对应多种格式名称的,例如MKV格式的解复用器(Demuxer)的定义如下。
    1. const AVInputFormat ff_matroska_demuxer = {
    2. .name = "matroska,webm",
    3. .long_name = NULL_IF_CONFIG_SMALL("Matroska / WebM"),
    4. .extensions = "mkv,mk3d,mka,mks,webm",
    5. .priv_data_size = sizeof(MatroskaDemuxContext),
    6. .flags_internal = FF_FMT_INIT_CLEANUP,
    7. .read_probe = matroska_probe,
    8. .read_header = matroska_read_header,
    9. .read_packet = matroska_read_packet,
    10. .read_close = matroska_read_close,
    11. .read_seek = matroska_read_seek,
    12. .mime_type = "audio/webm,audio/x-matroska,video/webm,video/x-matroska"
    13. };
    • 从代码可以看出,ff_matroska_demuxer中的name字段对应“matroska,webm”。
    • av_match_name()函数对于这样的字符串,会把它按照“,”截断成一个个封装格式名称,然后一一进行比较。

    av_match_ext()

    • av_match_ext()是一个API函数,声明如下所示。
    1. /**
    2. * Return a positive value if the given filename has one of the given
    3. * extensions, 0 otherwise.
    4. *
    5. * @param filename file name to check against the given extensions
    6. * @param extensions a comma-separated list of filename extensions
    7. */
    8. int av_match_ext(const char *filename, const char *extensions);
    • av_match_ext()用于比较文件的后缀。
    • 该函数首先通过反向查找的方式找到输入文件名中的“.”,就可以通过获取“.”后面的字符串来得到该文件的后缀。
    • 然后调用av_match_name(),采用和比较格式名称的方法比较两个后缀。 
    1. /**
    2. * @file
    3. * Format register and lookup
    4. */
    5. int av_match_ext(const char *filename, const char *extensions)
    6. {
    7. const char *ext;
    8. if (!filename)
    9. return 0;
    10. ext = strrchr(filename, '.');
    11. if (ext)
    12. return av_match_name(ext + 1, extensions);
    13. return 0;
    14. }
    • 经过以上几步之后,av_guess_format()最终可以得到最合适的AVOutputFormat并且返回给avformat_alloc_output_context2()。
    • avformat_alloc_output_context2()接下来将获得的AVOutputFormat赋值给刚刚新建的AVFormatContext,即可完成初始化工作
  • 相关阅读:
    《代码随想录》刷题笔记——字符串篇【java实现】
    工厂模式,装饰模式(新手)
    yolov7训练自定义数据集时的注意事项
    [面试直通版]设计模式-2-
    Spring boot实现Activemq死信队列
    【C++进阶学习】第六弹——set和map——体会用C++来构建二叉搜索树
    ElasticSearch学习(五): 分词器
    品达通用_9. pd-tools-log
    idea安装汉化插件
    1、instant-ngp(kwea123)代码运行
  • 原文地址:https://blog.csdn.net/CHYabc123456hh/article/details/125407759