新手学习FFmpeg - 调用API完成视频的读取和输出
在写了几个avfilter之后,原本以为对ffmpeg应该算是入门了。 结果今天想对一个视频文件进行转码操作,才发现基本的视频读取,输出都搞不定。 痛定思痛,仔细研究了一下ffmpeg提供的example,总结归纳读取处理视频文件的简要思路。
在读取,处理视频文件时,以下四个结构体是非常重要的,所以放在片首提一下。
- AVFormatContext
媒体源的抽象描述,可以理解成视频/音频文件信息描述
- AVInputFormat / AVOutputFormat
容器的抽象描述
- AVCodecContext / AVCodecParameters
编解码的抽象描述,ffmpeg使用率最高的结构体(AVCodecContext被AVCodecParameters所取代)
- AVStream
每个音视频的抽象描述
- 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
新手学习FFmpeg - 调用API完成视频的读取和输出的更多相关文章
- 新手学习FFmpeg - 调用API调整视频局部速率
通过修改setpts代码实现调整视频部分的播放速率. 完整代码可参考: https://andy-zhangtao.github.io/ffmpeg-examples/ 在前面提到了PTS/DTS/T ...
- 新手学习FFmpeg - 调用API完成两个视频的任意合并
本次尝试在视频A中的任意位置插入视频B. 在上一篇中,我们通过调整PTS可以实现视频的加减速.这只是对同一个视频的调转,本次我们尝试对多个视频进行合并处理. Concat如何运行 ffmpeg提供了一 ...
- 新手学习FFmpeg - 调用API完成录屏
调用FFMPEG Device API完成Mac录屏功能. 调用FFMPEG提供的API来完成录屏功能,大致的思路是: 打开输入设备. 打开输出设备. 从输入设备读取视频流,然后经过解码->编码 ...
- 新手学习FFmpeg - 调用API编写实现多次淡入淡出效果的滤镜
前面几篇文章聊了聊FFmpeg的基础知识,我也是接触FFmpeg不久,除了时间处理之外,很多高深(滤镜)操作都没接触到.在学习时间处理的时候,都是通过在ffmpeg目前提供的avfilter基础上面修 ...
- 新手学习FFmpeg - 调用API完成录屏并进行H.264编码
Screen Record H.264 目前在网络传输视频/音频流都一般会采用H.264进行编码,所以尝试调用FFMPEG API完成Mac录屏功能,同时编码为H.264格式. 在上一篇文章中,通过调 ...
- 新手学习FFmpeg - 调用API计算关键帧渲染时间点
通过简单的计算来,线上I帧在视频中出现的时间点. 完整代码请参考 https://andy-zhangtao.github.io/ffmpeg-examples/ 名词解释 首先需要明确以下名词概念: ...
- 新手学习FFmpeg - 通过API实现可控的Filter调用链
虽然通过声明[x][y]avfilter=a=x:b=y;avfilter=xxx的方式可以创建一个可用的Filter调用链,并且在绝大多数场合下这种方式都是靠谱和实用的. 但如果想精细化的管理AVF ...
- 新手学习FFmpeg - 通过API完成filter-complex功能
本篇尝试通过API实现Filter Graph功能. 源码请参看 https://andy-zhangtao.github.io/ffmpeg-examples/ FFmpeg提供了很多实用且强大的滤 ...
- 新手学习FFmpeg - 如何编写Kubernetes资源文件
Kubernetes API的使用方式 Kubernetes API属于声明式API编程, 它和常用的命令式编程有一些区别. 通俗的说,命令式编程是第一人称,我要做什么,我要怎么做. 操作系统最喜欢这 ...
随机推荐
- NDK Cmake
CMake与NDK搭配使用时,可以配置的部分变量: 1. `ANDROID_PLATFORM`:指定Android的目标版本,对应`$NDK/platforms/`目录下的版本.通常情况下是`defa ...
- 使用Python的Django和layim实现即时通讯
看到Django和layim实现websocketde资料很少,自己就琢磨了下,顺便搭建出来了.自己要去找闲心大神授权呀. 先来看图 这是初次搭建的,今天一天就搞定.我自己接入了图灵机器人. Pyt ...
- NFS挂载报错
nfs共享的时候,无论怎么检查都报错:mount.nfs: access denied by server while mounting NFS版本问题编辑/etc/sysconfig/nfs文件,找 ...
- Python Web Flask源码解读(三)——模板渲染过程
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
- 解决 java Could not resolve resource location pattern [classpath:sql/*.xml] 的问题
时间过的真快,转眼间已经有2年了.2年里发生了太多的事,有些事依稀还记得,但更多的已经忘记.忘记了那些烦恼与忧愁,以为自己会快乐,才发现一切并不是以自己的意志为转移.终于在迷途中迷失了自己, 再也回不 ...
- 【linux】【qt5】【信号槽示例】
什么叫信号槽: 信号槽是 Qt 框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式.当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal).这种发出是没有目的的, ...
- Linux搭建nginx负载均衡(两台服务器之间)
负载均衡种类 第一种:通过硬件负载解决,常见的有NetScaler.F5.Radware和Array等商用的负载均衡器,价格比较昂贵 第二种:通过软件负载解决,常见的软件有LVS.Nginx.apac ...
- hdu6333 Harvest of Apples 离线+分块+组合数学(求组合数模板)
Problem B. Harvest of Apples Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 262144/262144 K ...
- yzoj P2344 斯卡布罗集市 题解
共t条街对于每一条街上有n个店铺(n可能不相同),每次只能选两端第一个营业的店铺采购,采购第i个店铺会获得幸福度ai,采购完后,这个店铺和它相邻的店铺便会关门,问最大幸福度? 考场想了一下dp,一开始 ...
- angular关于Bootstrap样式不起作用问题
跟着慕课网的课程学习Angular,简直要被bootstrap的问题整死了,样式一直出不来,导航完全没有背景颜色.. 我在网上找了很多都试了,以下方法特别受用 1.把 "../node_mo ...