FFmpeg开发
时间基与时间戳
关联:
- 时间基 (time_base) 乘以时间戳 (timestamp) 即为实际时刻(单位s)
- 如:总时长 = duration * av_q2d(AV_TIME_BASE_Q)
- 如:显示时间 = PTS * av_q2d(stream->time_base)
时间戳:
- PTS 显示时间戳,DTS 解码时间戳
时间基:
- FFmpeg内部时间基:
- 用于FFmpeg内部计算,AV_TIME_BASE = 10^6,AV_TIME_BASE_Q为其倒数
- 编解码器中的时间基tbc:
- 对应AVCodecContext,根据帧率来设定如25帧,则{num = 1,den=25}
- 容器中的时间基tbn:
- 对应AVStream,根据采样频率设定如 (1,90000),是AVPacket中PTS和DTS的单位
- 确认输入输出流的时间基
avformat_find_stream_info:获得输入流时间基avformat_write_header:根据输出文件封装格式确定输出流时间基 - 输入输出时间基不同,需要转换
av_packet_rescale_ts():将 AVPacket 根据时间基重新计算时间戳PTS、DTS
时间基转换:
- 视频按帧进行播放,所以原始视频帧时间基为 1/framerate。视频解码前需要处理输入 AVPacket 中各时间参数,将输入容器中的时间基转换为 1/framerate 时间基;视频编码后再处理输出 AVPacket 中各时间参数,将 1/framerate 时间基转换为输出容器中的时间基。
- 音频按采样点进行播放,所以原始音频帧时间为 1/sample_rate。音频解码前需要处理输入 AVPacket 中各时间参数,将输入容器中的时间基转换为 1/sample_rate 时间基;音频编码后再处理输出 AVPacket 中各时间参数,将 1/sample_rate 时间基转换为输出容器中的时间基。如果引入音频 FIFO,从 FIFO 从读出的音频帧时间戳信息会丢失,需要使用 1/sample_rate 时间基重新为每一个音频帧生成 pts,然后再送入编码器。
1
2
3
4// 解码前的时间基转换
av_packet_rescale_ts(ipacket, sctx->i_stream->time_base, sctx->o_codec_ctx->time_base);
// 编码后的时间基转换
av_packet_rescale_ts(&opacket, sctx->o_codec_ctx->time_base, sctx->o_stream->time_base);
工具函数
1 | //获取音频样本大小(单个声道) |
主要流程

解码过程
- 连接和打开视频流
avformat_network_init:初始化并启动TLS库,用来打开网络流avformat_open_input:为AVFormatContext分配空间、打开输入媒体流、探测封装格式、读数据文件头、创建AVStream
- 定位视频流数据
avformat_find_stream_info:该函数内部已经做了一套完整的解码流程,获取了多媒体流的信息
- 准备解码器codec
avcodec_find_decoder:寻找解码器avcodec_alloc_context3:创建 AVCodecContextavcodec_parameters_to_context:内容拷贝,流的参数avcodec_open2:打卡解码器,分配相关变量内存、检查解码器状态等。
- 解码
av_read_frame:取出一个完整帧,包括压缩后的流数据和附加信息avcodec_send_packet:解码,按dts递增顺序输入avcodec_receive_frame:从缓存或解码器内存中取出解压数据,按pts递增顺序输出sws_scale:进行尺寸缩放和转码工作av_packet_unref:解除引用
刷新缓冲区:avcodec_send_packet传一个空packet,再解码就能拿到剩余帧数据
编码流程
- 打开输出文件
avformat_alloc_output_context2avio_open
- 查找编码器、创建输出流
avcodec_find_encoderavformat_new_stream:设置时间基和编码参数 codecpar(宽、高)avcodec_parameters_to_context:参数拷贝到编码器上下文,还需设置时间基、格式、码率avcodec_open2
- 写文件头
avformat_write_header - 编码
- 读帧
avcodec_send_frameavcodec_receive_packetav_packet_rescale_ts:重新计算时间戳av_interleaved_write_frame:交替写入帧
- 写文件尾
av_write_trailer
音频流视频流混合进输出媒体时,需要确保音频帧和视频帧按照 dts 递增的顺序交错排列,这就是交织问题
av_interleaved_write_frame()函数会缓存一定数量的帧,将缓存的帧按照 dts 递增的顺序写入输出媒体,调用者不必关注交织问题(小范围的 dts 顺序错误问题这个函数可以修正)
av_write_frame()函数会直接将帧写入输出媒体,用户必须自行处理交织问题
转码流程

FFmpeg源码解析
初始化
avformat_open_input() :分配空间、打开输入媒体流、探测封装格式、读数据文件头、创建AVStream
- 分配AVFormatContext、设置options
init_input():打开输入媒体流、探测封装格式- 当使用自定义AVIOContext时(s->pb!=NULL),如果指定了AVInputFormat就直接返回,否则调用
av_probe_input_buffer2()推测AVInputFormat(如从内存中读取数据的时候) - 如果指定了AVInputFormat就直接返回,否则根据文件路径推测AVInputFormat,调用
av_probe_input_format2()- read_probe、av_match_ext、av_match_name匹配得分
- 如果路径判断不出文件格式,则调用
avio_open2()打开文件,再用av_probe_input_buffer2()推测AVInputFormat
- 当使用自定义AVIOContext时(s->pb!=NULL),如果指定了AVInputFormat就直接返回,否则调用
- s->iformat->
read_header():读取多媒体数据文件头avformat_new_stream():根据视音频流创建相应的AVStream
- 拷贝白名单与黑名单协议、读取ID3V2参数
avformat_alloc_output_context2() :分配空间、设默认值、根据文件名猜AVOutputFormat
avformat_alloc_context()- av_opt_set_defaults()
- 若参数指定了AVOutputFormat,则直接赋值给oformat
- 否则调用
av_guess_format()遍历所有AVOutputFormat,计算匹配度- av_match_name():封装格式名匹配,score加100
- strcmp():mime类型匹配,score加10
- av_match_ext():如果文件名称的后缀匹配,score加5
avio_open2() :根据文件名找URLProtocol,再用对应协议打开(file、rtmp),初始化AVIOContext
ffurl_open_whitelist():初始化URLContextffurl_alloc()- url_find_protocol():根据文件路径查找合适的URLProtocol
- url_alloc_for_protocol():为URLProtocol创建URLContext
ffurl_connect()- url_open()/url_open2():打开URLProtocol(根据协议调用
file_open()或rtmp_open()等)
- url_open()/url_open2():打开URLProtocol(根据协议调用
ffio_fdopen():根据URLContext初始化AVIOContextavio_alloc_context()
avformat_find_stream_info() :做了一套完整的解码流程,获取了多媒体流的信息
- find_decoder()
avcodec_find_decoder():获取解码器
avcodec_open2()read_frame_internal():读取完整的一帧压缩编码的数据try_decode_frame():解码一些压缩编码数据- has_codec_parameters():检查AVStream成员变量是否设置完毕
- estimate_timings():通过PTS、已知流时长或码率估算AVFormatContext及AVStream的duration
avcodec_open2()
- 通过各种av_malloc()分配结构体
- 将AVDictionary选项设置到AVCodecContext
- 各种检查,比如检查编解码器是否处于“实验”阶段
- 如果是编码器,检查输入参数是否符合编码器的要求
- 调用AVCodec的
init()初始化具体的解码器
读写帧
av_read_frame()
把文件拆分为若干个帧,每次调用返回一帧数据包,但不校验是否有效
返回的数据包被引用计数,必须使用av_packet_unref()进行释放。
如果音频是可变大小,则只包含一帧。
如果视频中存在B帧,pkt->pts可能为AV_NOPTS_VALUE,所以最好使用pkt->dts作为依赖。
- avpriv_packet_list_get()
av_read_frame_internal()- ff_read_packet()
iformat->read_packet():调用AVInputFormat指向的read_packet()读取数据包
- parse_packet()
av_parser_parse2():解析出视频一帧(音频若干帧)
- ff_read_packet()
avformat_write_header() :初始化复用器、检查AVStream的time_base,采样率,宽高、写入封装头
init_muxer():初始化复用器- 将AVDictionary选项设置到AVFormatContext
- 遍历AVFormatContext中的每个AVStream,并检查:
- time_base是否正确设置,否则调用avpriv_set_pts_info()进行设置
- 音频采样率,视频宽、高、宽高比
- AVOutputFormat->
write_header():写入相应封装头
av_write_frame() / av_interleaved_write_frame() :检查 stream_index, codec_type, pts, dts,调用write_packets_common()写入帧,interleaved参数传1交错写入,总会调用 av_packet_unref() 解引用
write_packets_common()
- 检查pkt的stream_index和codec_type
- 检查pkt的pts和dts
- 调用s->oformat->
check_bitstream()检查码流 - 调用函数二选一写数据包
write_packets_from_bsfs():经过bitstream filter处理,如h264要处理startcode起始码write_packet_common()- 若interleaved标志位交错,调用
interleave_packet()排序 - s->oformat->
write_packet():校正时间戳,根据封装格式写入未/压缩编码的数据包
- 若interleaved标志位交错,调用
av_write_trailer()
- 如果有AVBSFContext,最后写入bitstream filter数据包
- 填充空数据包
- 如果有AVIOContext,
avio_write_marker写入marker标志 s->oformat->write_trailer()写文件尾deinit_muxer():释放muxer资源- 释放priv_data和index_entries
编码
avcodec_send_frame() :检查编码器、frame,不对frame赋值
- 编码器是否打开、是否为编码器
encode_send_frame_internal():解析音频metadata、检查frame是否有效- 真正frame的赋值操作在后续
ff_encode_get_frame()
avcodec_receive_packet() :检查编码器、w,h,f、取出未压缩的一帧
- 编码器是否打开、是否为编码器
- encode_receive_packet_internal():检测视频宽高、像素格式,判断调用二选一
receive_packet()- 或encode_simple_receive_packet()->
encode_simple_internal()- frame为空则调用
ff_encode_get_frame()取出一帧未压缩的数据 - ff_thread_video_encode_frame或avctx->codec->
encode2()编码
- frame为空则调用
转码
FFmpeg结构体
分类
解协议(http,rtsp,rtmp)
- AVIOContext:管理输入输出数据,硬盘数据读到其buffer中再送给解码器
- URLContext:对具体资源文件进行操作的上下文
- URLProtocol:广义的输入文件(文件、网络数据流等),每种协议都对应一个URLProtocol
- URLContext:对具体资源文件进行操作的上下文
- AVIOContext:管理输入输出数据,硬盘数据读到其buffer中再送给解码器
解封装(flv,avi,rmvb,mp4)
- AVFormatContext:存储视音视频封装格式中包含的信息
- AVInputFormat、AVOutputFormat:对应一种封装格式
- AVIOContext
- AVStream
- AVDictionary:元数据
- AVFormatContext:存储视音视频封装格式中包含的信息
解码(h264,mpeg2,aac,mp3)
- AVStream:存储每一个视频/音频流信息的结构体
- AVCodecContext:编解码器上下文结构体,存储音视频流的解码方式的相关数据
- AVCodec:每种编解码器对应一个该结构体(如ff_h264_decoer)
- AVRational,宽高,声道数,采样率
- profile,level
- AVCodecContext:编解码器上下文结构体,存储音视频流的解码方式的相关数据
- AVStream:存储每一个视频/音频流信息的结构体
存数据 对于视频,每个结构一般是存一帧;音频可能有好几帧
- AVPacket
- AVFrame
AVPacket
uint8_t* data:本身不包含压缩的帧数据,而是指向缓存空间int sizeAVBufferRef *buf:对data指向的数据缓冲区引用管理av_packet_ref缓存空间引用+1,复制packet中的其他字段,仅首次将src->data复制到buf->buffer中av_packet_unref引用-1,变为0时释放缓存空间。
pts、dtsstream_indexint key_frame:是否关键帧duration(解码后帧播放时长)
AVFrame
AVFrame 通常只需分配一次,然后可以多次重用,每次重用前应调用 av_frame_unref() 将 frame 复位到原始的干净可用的状态
uint8_t *data[]:原始帧存在若干plane中,对应planar、packet格式和单双声道int linesize[]:对齐填充,可能比实际原数据大AVBufferRef *buf[]:uint8_t **extended_data:存多声道音频width,heightnb_samples,channel_layoutint format:帧格式AV_PIX_FMT_YUV420P、AV_SAMPLE_FMT_S16int key_frame:是否关键帧- 宏块类型表、运动矢量表等
FFmpeg相关文章
Tools:MediaInfo, VLC media player, wireshark, Elecard StreamEye Tools
FFmpeg指令
分解与复用
1.格式转换
1 | ## 音视频编码处理方式都是copy,不改变 |
2.抽取视频
1 | ## -an表示不需要音频数据 |
3.抽取音频
1 | ## -vn表示不需要视频数据 |
处理原始数据
1.提取YUV数据
1 | ## -c:v 表示对v(视频)进行c(编码), -pix_fmt 指定了视频的像素格式 |
2.提取PCM数据
1 | ## -ar采样率 -ac双声道 -f:PCM数据存储格式 s有符号 16位 le小端存储 |
音视频裁剪
1.裁剪
1 | ## -ss开始时间 -t时间长度(单位s) |
2.合并
1 | ## 文件片段为.ts |
input.txt
1
2 file "out.ts"
file "out2.ts"
图片视频互转
1.视频转图片
1 | ## -r 指定转换图片频率(每秒转出几张) -f 指定转出图片格式image2 |
2.图片转视频
1 | #可以通过-r指定每秒放的帧数 |