ffmpeg源码分析-open_output_file

《FFmpeg原理》的社群来了,想加入社群的朋友请购买 VIP 版,VIP 版有更高级的内容与答疑服务。


本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8

ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑。

a.mp4下载链接:百度网盘,提取码:nl0s 。


本文章主要分析 open_output_file() 的内部逻辑。

简单转码命令 的 open_output_file() 流程图如下,一些参数赋值的内容没有画出来,只有主干。

理解 open_output_file(),必须先了解 2个新的数据结构,OutputFileOutputStream,请先看源码自行阅读这两个结构体的声明。跑完 open_output_file() 之后形成的数据关系图如下,跟上一章的输入非常类似。

下面再贴一个 open_output_file() 的代码图,因为 open_output_file() 里面的逻辑分支比较多,在 ffmpeg -i a.mp4 b.flv 下并不会跑进去某些 if 条件,所以代码图为了简洁,会删减一些if,有一些参数赋值也会删掉。

接下来会逐行代码分析 open_output_file() 的内部逻辑。

  1. 图片1873~1894 行,申明一个 OutputFile 结构 ,丢进去全局变量数组 outpur_files[],然后把 OptionsContext 的一些值赋值给 of
  2. 图片1914~2083 行,这个代码块是比较重要的,首先根据算法,选出输入流里最好的 video 流 idx,然后调用 new_video_stream(o, oc, idx) 来创建输出流。new_video_stream() 函数比较复杂,这里简单介绍,下一段再仔细分析。 audio流类似 video流,也是根据算法选出最好 idx,然后调 new_audio_stream(o, oc, idx);
  3. 图片2090~2132 行,这部分代码主要对命令行参数做些检测,检测哪些option 没有用到,命令行写错了,给提示。不是主要逻辑。
  4. 图片2133~2213 行,这部分代码就是遍历输出流,然后调 init_simple_filtergraph(ist, ost) 初始化Filter,这块比较重要,要细看。
  5. 后面的代码都不重要,都是一些检测,参数赋值,处理metadata,处理chapter,后面的代码最重要的是调了 avio_open2() 打开输出文件

如此看来,open_output_file() 的内部逻辑就变得极其简单了,创建 OutputFile,用OptionsContext来赋值,调new_video_stream() ,new_audio_stream() 创建输出流,最后初始化 filter,然后avio_open2()打开输出文件。


上面有两个函数没有仔细介绍 new_video_stream() new_audio_stream() ,接下来仔细分析。

new_video_stream()的逻辑相对简单。如下面的流程图所示,new_video_stream() 里面有个 new_output_stream()

new_output_stream() 主要创建 stream,编码器context,然后做一些初始化。

new_output_stream() 是一个公共函数,音频流,数据流,字幕流都用了它,所以 ffmpeg 把他分出来。video_stream 的一些初始化会在调用 new_output_stream() 后执行。

比较注意的一点是,new_video_stream() 跟 new_output_stream() 里面用了大量的 MATCH_PER_STREAM_OPT(),把 OptionsContex 的参数提取出来,赋值给 stream参数,或者赋值给编码器参数。所以一定要理解 MATCH_PER_STREAM_OPT() 的实现。

new_audio_stream()跟 new_video_stream() 类似,只是 new_audio_stream() 调了 new_output_stream() ,对音频流或编码器的某些参数赋值。


最后还有一个重要函数没有分析,init_simple_filtergraph(),即使是简单的转码命令,ffmpeg 为了通用性,也是会创建 filter 的,只不过是 null filter 跟 anull filter。空的视频流 filter,空的音频流filter

init_simple_filtergraph() 的逻辑比较简单,只是看 init_simple_filtergraph() ,不太容易看出filter的整体逻辑跟关系结构,需要后面分析 transcode()转码函数 ,ifilter_send_frame() 调用了 configure_filtergraph() 才能形成完整的关系结构体。这里尽量简单介绍,有个印象。

init_simple_filtergraph() 跑完之后形成的关系结构如下。

如图所示,有一个全局变量数组 filtergraphs[],一个 FilterGraph 里有一个InputFilter 跟一个 OutputFilter 。InputFilter 跟 OutputFilter 又各自有输入跟输出流,所以代码跑到这里,已经确定把输入输出流关联在一起了。

只要在输入流读数据,然后往输入流关联的输出流丢数据就完成转码了。其实之前 OutputStream 里面有个 source_index 成员本身已经关联了输入流,还要把 输入输出流 关联到 filter ,然后丢进去 FilterGraph 是因为 输入输出流虽然关联了,但他们的filter 还没关联起来。

FilterGraph::InputFilter 跟 FilterGraph::OutputFilter 在 init_simple_filtergraph() 里面也还没开始关联,关联之后就可以 发送 frame 到 buffersrc ,从 buffersink 出口读frame,不了解filter的,请先自己了解filter的基本用法。

关联 FilterGraph::InputFilter 跟 FilterGraph::OutputFilter 是在后面的 configure_filtergraph() 里完成的。

至此,open_output_file() 内部逻辑已经分析完毕


©版权所属:知识星球:弦外之音,QQ:2338195090。

由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是将给定的FFmpeg命令使用最新的API转换为C语言代码的示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/opt.h> int main(int argc, char *argv[]) { AVFormatContext *fmt_ctx = NULL; AVCodecContext *video_dec_ctx = NULL, *video_enc_ctx = NULL; AVCodec *video_dec = NULL, *video_enc = NULL; AVStream *video_stream = NULL; AVOutputFormat *output_fmt = NULL; AVFormatContext *output_fmt_ctx = NULL; AVPacket pkt; int ret, i; // Initialize the AVFormatContext if ((ret = avformat_open_input(&fmt_ctx, "/home/tsdl/Downloads/FPV_2021-0-1_12-37-23.h264", NULL, NULL)) < 0) { fprintf(stderr, "Could not open input file\n"); exit(1); } if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) { fprintf(stderr, "Could not find stream information\n"); exit(1); } // Find the video stream for (i = 0; i < fmt_ctx->nb_streams; i++) { if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream = fmt_ctx->streams[i]; break; } } if (!video_stream) { fprintf(stderr, "Could not find video stream\n"); exit(1); } // Initialize the video decoder video_dec = avcodec_find_decoder(video_stream->codecpar->codec_id); if (!video_dec) { fprintf(stderr, "Could not find video decoder\n"); exit(1); } video_dec_ctx = avcodec_alloc_context3(video_dec); if (!video_dec_ctx) { fprintf(stderr, "Could not allocate video decoder context\n"); exit(1); } if ((ret = avcodec_parameters_to_context(video_dec_ctx, video_stream->codecpar)) < 0) { fprintf(stderr, "Could not initialize video decoder context\n"); exit(1); } if ((ret = avcodec_open2(video_dec_ctx, video_dec, NULL)) < 0) { fprintf(stderr, "Could not open video decoder\n"); exit(1); } // Initialize the video encoder video_enc = avcodec_find_encoder_by_name("libx264"); if (!video_enc) { fprintf(stderr, "Could not find video encoder\n"); exit(1); } video_enc_ctx = avcodec_alloc_context3(video_enc); if (!video_enc_ctx) { fprintf(stderr, "Could not allocate video encoder context\n"); exit(1); } // Set video encoder options av_opt_set(video_enc_ctx->priv_data, "profile", "baseline", 0); av_opt_set(video_enc_ctx->priv_data, "tune", "zerolatency", 0); video_enc_ctx->width = video_dec_ctx->width; video_enc_ctx->height = video_dec_ctx->height; video_enc_ctx->sample_aspect_ratio = video_dec_ctx->sample_aspect_ratio; video_enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P; video_enc_ctx->time_base = av_inv_q(video_stream->avg_frame_rate); video_enc_ctx->gop_size = 50; video_enc_ctx->framerate = video_stream->avg_frame_rate; if ((ret = avcodec_open2(video_enc_ctx, video_enc, NULL)) < 0) { fprintf(stderr, "Could not open video encoder\n"); exit(1); } // Initialize the output format context if ((ret = avformat_alloc_output_context2(&output_fmt_ctx, NULL, "rtc", "http://192.168.114.34:1985/rtc/v1/whip/?app=live&stream=livestream")) < 0) { fprintf(stderr, "Could not allocate output format context\n"); exit(1); } output_fmt = output_fmt_ctx->oformat; // Add the video stream to the output format context video_stream = avformat_new_stream(output_fmt_ctx, NULL); if (!video_stream) { fprintf(stderr, "Could not create video stream\n"); exit(1); } video_stream->codecpar->codec_id = video_enc->id; video_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; video_stream->codecpar->width = video_enc_ctx->width; video_stream->codecpar->height = video_enc_ctx->height; video_stream->codecpar->format = video_enc_ctx->pix_fmt; video_stream->codecpar->codec_tag = 0; avcodec_parameters_to_context(video_stream->codec, video_stream->codecpar); // Write the output format header if ((ret = avformat_write_header(output_fmt_ctx, NULL)) < 0) { fprintf(stderr, "Could not write output format header\n"); exit(1); } // Read frames from input, encode and write to output while (1) { if ((ret = av_read_frame(fmt_ctx, &pkt)) < 0) { break; } if (pkt.stream_index == video_stream->index) { av_packet_rescale_ts(&pkt, video_stream->time_base, video_enc_ctx->time_base); pkt.flags |= AV_PKT_FLAG_KEY; if ((ret = avcodec_send_packet(video_enc_ctx, &pkt)) < 0) { fprintf(stderr, "Could not send packet to video encoder\n"); exit(1); } while (ret >= 0) { ret = avcodec_receive_packet(video_enc_ctx, &pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { fprintf(stderr, "Error encoding video frame\n"); exit(1); } av_packet_rescale_ts(&pkt, video_enc_ctx->time_base, video_stream->time_base); pkt.stream_index = video_stream->index; if ((ret = av_interleaved_write_frame(output_fmt_ctx, &pkt)) < 0) { fprintf(stderr, "Error writing video frame\n"); exit(1); } av_packet_unref(&pkt); } } av_packet_unref(&pkt); } // Write the output format trailer av_write_trailer(output_fmt_ctx); // Cleanup avformat_close_input(&fmt_ctx); avcodec_free_context(&video_dec_ctx); avcodec_free_context(&video_enc_ctx); avformat_free_context(output_fmt_ctx); return 0; } ``` 注意:以上示例代码仅供参考,具体实现可能需要根据您的需求进行修改。同时,您需要添加FFmpeg库的链接,并将FFmpeg头文件和库文件添加到编译路径中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Loken2020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值