编解码层:理论与实战
好文章,来自【福优学苑@音视频+流媒体】
FFmpeg解码流程
得到输入文件 -> 解封格式 -> 得到编码的数据包 ->
解码数据包 -> 得到解码后的数据帧 ->
处理数据帧 -> 编码 -> 得到编码后的数据包 ->
封装格式 -> 输出文件
(1)注册所有容器格式和CODEC:av_register_all()
(2)打开文件:av_open_input_file()
(3)从文件中提取流信息:av_find_stream_info()
(4)穷举所有的流,查找其中种类为CODEC_TYPE_VIDEO
(5)查找对应的解码器:avcodec_find_decoder()
(6)打开编解码器:avcodec_open()
(7)为解码帧分配内存:avcodec_alloc_frame()
(8)不停地从码流中提取出帧数据:av_read_frame()
(9)判断帧的类型,对于视频帧调用:avcodec_decode_video()
(10)解码完后,释放解码器:avcodec_close()
(11)关闭输入文件:av_close_input_file()
FFmpeg转码流程
大流程可以划分为输入、输出、转码、播放四大块。
其中转码涉及比较多的处理环节,从图中可以看出,转码功能在整个功能图中占比很大。转码的核心功能在解码和编码两个部分,但在一个可用的示例程序中,编码解码与输入输出是难以分割的。
解复用器为解码器提供输入,解码器会输出原始帧,对原始帧可进行各种复杂的滤镜处理,滤镜处理后的帧经编码器生成编码帧,多路流的编码帧经复用器输出到输出文件。
编解码API详解
解码使用 avcodec_send_packet() 和 avcodec_receive_frame() 两个函数。
编码使用 avcodec_send_frame() 和 avcodec_receive_packet() 两个函数。
4.2.1 解码API使用详解
关于 avcodec_send_packet() 与 avcodec_receive_frame() 的使用说明:
[1] 按 dts 递增的顺序向解码器送入编码帧 packet,解码器按 pts 递增的顺序输出原始帧 frame,实际上解码器不关注输入 packe t的 dts(错值都没关系),它只管依次处理收到的 packet,按需缓冲和解码
[2] avcodec_receive_frame() 输出 frame 时,会根据各种因素设置好 frame->best_effort_timestamp(文档明确说明),实测 frame->pts 也会被设置(通常直接拷贝自对应的 packet.pts,文档未明确说明)用户应确保 avcodec_send_packet() 发送的 packet 具有正确的 pts,编码帧 packet 与原始帧 frame 间的对应关系通过 pts 确定
[3] avcodec_receive_frame() 输出 frame 时,frame->pkt_dts 拷贝自当前avcodec_send_packet() 发送的 packet 中的 dts,如果当前 packet 为 NULL(flush packet),解码器进入 flush 模式,当前及剩余的 frame->pkt_dts 值总为 AV_NOPTS_VALUE。因为解码器中有缓存帧,当前输出的 frame 并不是由当前输入的 packet 解码得到的,所以这个 frame->pkt_dts 没什么实际意义,可以不必关注
[4] avcodec_send_packet() 发送第一个 NULL 会返回成功,后续的 NULL 会返回 AVERROR_EOF
[5] avcodec_send_packet() 多次发送 NULL 并不会导致解码器中缓存的帧丢失,使用 avcodec_flush_buffers() 可以立即丢掉解码器中缓存帧。因此播放完毕时应 avcodec_send_packet(NULL) 来取完缓存的帧,而 SEEK 操作或切换流时应调用 avcodec_flush_buffers() 来直接丢弃缓存帧
[6] 解码器通常的冲洗方法:调用一次 avcodec_send_packet(NULL)(返回成功),然后不停调用 avcodec_receive_frame() 直到其返回 AVERROR_EOF,取出所有缓存帧,avcodec_receive_frame() 返回 AVERROR_EOF 这一次是没有有效数据的,仅仅获取到一个结束标志
4.2.2 编码API使用详解
关于 avcodec_send_frame() 与 avcodec_receive_packet() 的使用说明:
[1] 按 pts 递增的顺序向编码器送入原始帧 frame,编码器按 dts 递增的顺序输出编码帧 packet,实际上编码器关注输入 frame 的 pts 不关注其 dts,它只管依次处理收到的 frame,按需缓冲和编码
[2] avcodec_receive_packet() 输出 packet 时,会设置 packet.dts,从 0 开始,每次输出的 packet 的 dts 加 1,这是视频层的 dts,用户写输出前应将其转换为容器层的 dts
[3] avcodec_receive_packet() 输出 packet 时,packet.pts 拷贝自对应的 frame.pts,这是视频层的 pts,用户写输出前应将其转换为容器层的 pts
[4] avcodec_send_frame() 发送 NULL frame 时,编码器进入 flush 模式
[5] avcodec_send_frame() 发送第一个 NULL 会返回成功,后续的 NULL 会返回 AVERROR_EOF
[6] avcodec_send_frame() 多次发送 NULL 并不会导致编码器中缓存的帧丢失,使用 avcodec_flush_buffers() 可以立即丢掉编码器中缓存帧。因此编码完毕时应使用 avcodec_send_frame(NULL) 来取完缓存的帧,而SEEK操作或切换流时应调用 avcodec_flush_buffers() 来直接丢弃缓存帧
[7] 编码器通常的冲洗方法:调用一次 avcodec_send_frame(NULL)(返回成功),然后不停调用 avcodec_receive_packet() 直到其返回 AVERROR_EOF,取出所有缓存帧,avcodec_receive_packet() 返回 AVERROR_EOF 这一次是没有有效数据的,仅仅获取到一个结束标志
[8]对音频来说,如果 AV_CODEC_CAP_VARIABLE_FRAME_SIZE(在 AVCodecContext.codec.capabilities 变量中,只读)标志有效,表示编码器支持可变尺寸音频帧,送入编码器的音频帧可以包含任意数量的采样点。如果此标志无效,则每一个音频帧的采样点数目(frame->nb_samples)必须等于编码器设定的音频帧尺寸(avctx->frame_size),最后一帧除外,最后一帧音频帧采样点数可以小于 avctx->frame_size
编码案例实战
Encode_video.c
解码案例实战
Demuxing_decoding.c
ffplay -f f32le -ac 1 -ar 44100 ande111.pcm
ffplay -f rawvideo -pix_fmt yuv420p -video_size 1280x720 ande111.yuv
***【在线视频教程】***