在写了几个avfilter以后,本来觉得对ffmpeg应该算是入门了。 结果今天想对一个视频文件进行转码操做,才发现基本的视频读取,输出都搞不定。 痛定思痛,仔细研究了一下ffmpeg提供的example,总结概括读取处理视频文件的简要思路。git
在读取,处理视频文件时,如下四个结构体是很是重要的,因此放在片首提一下。github
AVFormatContextshell
媒体源的抽象描述,能够理解成视频/音频文件信息描述ide
AVInputFormat / AVOutputFormat函数
容器的抽象描述oop
AVCodecContext / AVCodecParameters学习
编解码的抽象描述,ffmpeg使用率最高的结构体(AVCodecContext被AVCodecParameters所取代)编码
AVStreamcode
每一个音视频的抽象描述orm
AVCodec
编解码器的抽象描述
四个结构体的包含关系大体以下:
|AVFormatContext | |---------> AVInputFormat / AVOutputFormat | |---------> AVStream |-------> time_base (解码时不须要设置, 编码时须要用户设置) | |-------> AVCodecParameters|--------> codec id | | |AVCodec ------------经过 codec id 关联----------------------------+ | | |-------------->AVCodecContext
读取/输出视频时,基本就是围绕这五个结构体来进行操做的。 而不一样点在于,读取文件时,ffmpeg
会经过读取容器metadata来完成AVFormateContext
的初始化。输出文件时,咱们须要根据实际状况自行封装AVFormateContext
里面的数据。封装时的数据来源,一部分来自于实际状况(例如time_base,framerate等等),另一部分则来自于数据源。
下面分别来描述读取和输出的差别。
先看读取的大体流程:
|------------------------------------------------------loop---------------------------------------------------| | |-------------------------------------------------------------------- | | | | | avformat_open_input ----------> AVFormatContext ----------> stream ----avcodec_find_decoder----> codec -------avcodec_alloc_context3-----------> codecContent ---avcodec_open2--> | | |------------------------------------avcodec_parameters_to_context--------------------------|
avformat_open_input
会尝试根据指定文件的metadata完成AVFormatContext
的部分初始化,若是视频源是包含header的,那么此时的AVFormatContext
数据基本都齐了。若是是不包含header的容器格式(例如MPEG),AVFormatContext
此时就没有AVStream
的数据,须要单独使用avformat_find_stream_info
来完成AVStream
的初始化。
不管怎样,待AVFormatContext
完成了初始化,就能够经过轮询AVStream
来单独处理每个stream
数据,也就是上面的loop
。下面单拎一条stream
来聊。
解码视频只须要AVCodecContext
就能够了,从包含图能够得知根据AVCodec
能够生成AVCodecContext
,而avcodec_find_decoder
又能够生成对应的codec
。因此大体的思路就清晰了,首先经过inStream->codecpar(AVCodecParameters)->codec_id
和avcodec_find_decoder
生成指定的解码器AVCodec
, 而后经过avcodec_alloc_context3
就能够生成能够解码视频源的AVCodecContext
。
此时产生了第一个误区:生成的AVCodecContext
就能够直接解码视频! 这是错误的
如今的AVCodecContext
只是一个通用Codec
描述,没有视频源的特定信息(avcodec_parameters_to_context的代码有些长,我也没搞明白具体是哪些信息)。 因此须要调用avcodec_parameters_to_context
将inStream->codecpar
和AVCodecContext
糅合到一块儿(俗称merge)。这时的AVCodecContext
才能打开特定的视频文件。
对于没有header的容器。 framerate 和 time_base 仍然须要特别设定。
fraterate 能够经过av_guess_frame_rate获取。 time_base能够直接使用AVStream的time_base;
最后就是使用avcodec_open2
打开AVCodecContext
并处于待机状态。
输出的流程和读取的流程类似,但又有不一样。 读取读取参数较多,而输出更多的是封装参数。 下面是输出的大体流程:
|----------------------------------avcodec_parameters_from_context-----------------| | | stream(enc)---avcodec_find_encoder ---> codec(enc)---avcodec_alloc_context3---> codecContent(enc)----avcodec_open2----> ----------- | | | avformat_alloc_output_context2 -------> AVFormatContext --------avformat_new_stream--------> stream -------copy dec to enc--- | | |---------------------loop--------------|
不管是读取仍是输出,首要任务都是构建AVFormateContext
。有所不一样的是此时(输出),咱们先构建一个模板,而后往里面填值,所以使用的是avformat_alloc_output_context2
函数。
avformat_alloc_output_context2和avformat_open_input 都是用来生成AVFormatContext的。不一样的是,一个生成模板往里面填值,另外一个生成的是已经完成初始化的。
编码一个视频文件,须要的也只是一个AVCodecContext
. 但此时离生成AVCodecContext
还差不少东西。因此须要咱们逐一进行准备,按照最上面的包含图,须要先生成一个AVStream
。所以调用avformat_new_stream
生成一个空AVStream
。
有了AVStream
以后,就须要将这个Stream
与具体的Codec
关联起来。 其次,根据须要咱们指定avcodec_find_encoder
生成一个标准的AVCodec
,然后使用avcodec_alloc_context3
生成对应的AVCodecContext
。
第二个误区:生成的AVCodecContext
就能够直接解码视频! 这是错误的
如今生成的AVCodecContext
不能直接使用,由于还有参数是标准参数没有适配。如下参数是常常容易出错的:
width height framerate time_base sample_aspect_ratio pix_fmt time_base(AVSteam)
在对codecpar
和AVCodecContext
进行反向
merge. 反向指的是从AVCodecContext
读取参数填充到codecpar
中因此才须要提早设置AVCodecContext
中的参数。
最后调用avcodec_open2
处于待输出状态。
上面是读取/输出的流程,下面来补充说一下如何从视频源读数据,再写到目标视频中。
真正读取视频数据涉及到的结构体是:
AVPacket
可能包含一个或多个 frame。 若是包含多个,则读取第一个
AVFrame
保存当前帧的数据格式
一个典型的读取处理代码,看起来应该是下面的样子:
while (1){ av_read_frame (读取数据) ... avcodec_decode_video2 (对读到的数据进行解码) ... avcodec_encode_video2 (对数据进行编码) ... av_interleaved_write_frame (写到文件中) } av_write_trailer (写metadata)
avcodec_decode_video2 和 avcodec_encode_video2 是即将废弃的函数,替代函数的使用可参看前几篇文章
在这里也有几个误区:
第三个误区,AVPacket声明后就可用。 这是错误的
AVPacket 声明后须要手动设置{.data = NULL, .size = 0}.
第四个误区,AVPacket time_base直接设置 通过验证,这也是错误的
直接设置很差使。 仍是老老实实经过av_packet_rescale_ts来调整 AVPacket的time base吧。同理,在写文件以前也须要调用av_packet_rescale_ts来修改time base。
以上就是今天学习的结果,但愿对之后解析/输出视频能有所帮助。示例代码能够参考 https://andy-zhangtao.github.io/ffmpeg-examples