编解码层:理论与实战


***【在线视频教程】***

好文章,来自【福优学苑@音视频+流媒体】

FFmpeg解码流程

image.png

得到输入文件 -> 解封格式 -> 得到编码的数据包 -> 

解码数据包 -> 得到解码后的数据帧 ->

处理数据帧 -> 编码 -> 得到编码后的数据包 -> 

封装格式 -> 输出文件

(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()


image.png




FFmpeg转码流程

image.png

大流程可以划分为输入、输出、转码、播放四大块。

其中转码涉及比较多的处理环节,从图中可以看出,转码功能在整个功能图中占比很大。转码的核心功能在解码和编码两个部分,但在一个可用的示例程序中,编码解码与输入输出是难以分割的。

解复用器为解码器提供输入,解码器会输出原始帧,对原始帧可进行各种复杂的滤镜处理,滤镜处理后的帧经编码器生成编码帧,多路流的编码帧经复用器输出到输出文件。



编解码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















好文章,来自【福优学苑@音视频+流媒体】
***【在线视频教程】***