FFmpeg编解码处理系列笔记:
[0]. FFmpeg时间戳详解
[1]. FFmpeg编解码处理1-转码全流程简介
[2]. FFmpeg编解码处理3-编解码API详解
[3]. FFmpeg编解码处理4-视频编码
[4]. FFmpeg编解码处理5-音频编码
基于FFmpeg 4.1版本。
1. 转码全流程简介看一下FFmpeg常规处理流程:
大流程可以划分为输入、输出、转码、播放四大块。其中转码涉及比较多的处理环节,从图中可以看出,转码功能在整个功能图中占比很大。转码的核心功能在解码和编码两个部分,但在一个可用的示例程序中,编码解码与输入输出是难以分割的。解复用为解码器提供输入,解码器输出原始帧,可进行各种复杂的滤镜处理,滤镜处理后的帧经编码器生成编码帧,多路流的编码帧经复用器输出到输出文件。
1.1 解复用从输入文件中读取编码帧,判断流类型,根据流类型将编码帧送入视频解码器或音频解码器。
av_read_frame(ictx.fmt_ctx, &ipacket); if (codec_type == AVMEDIA_TYPE_VIDEO) { transcode_video(&stream, &ipacket); } else if (codec_type == AVMEDIA_TYPE_AUDIO) { transcode_audio(&stream, &ipacket); } else { av_interleaved_write_frame(octx.fmt_ctx, &ipacket); } 1.2 解码将视音频编码帧解码生成原始帧。后文详述。
1.3 滤镜FFmpeg提供多种多样的滤镜,用来处理原始帧数据。
本例中,为每个音频流/视频流使用空滤镜,即滤镜图中将buffer滤镜和buffersink滤镜直接相连。目的是:通过视频buffersink滤镜将视频流输出像素格式转换为编码器采用的像素格式;通过音频abuffersink滤镜将音频流输出声道布局转换为编码器采用的声道布局。为下一步的编码操作作好准备。如果不使用这种方法,则需要处理图像格式转换和音频重采样,从而确保进入编码器的帧是编码器支持的格式。
当然,例程可扩展,可以很容易的在buffer滤镜和buffersink滤镜中间插入其他功能滤镜,实现丰富的视音频处理功能。
滤镜的使用方法不是本实验关注的重点。详细用法可参考:
“FFmpeg原始帧处理-滤镜API用法”
将原始视音频帧编码生成编码帧。后文详述。
1.5 复用将编码帧按不同流类型交织写入输出文件。
av_interleaved_write_frame(octx.fmt_ctx, &ipacket); 2. 转码例程简介转码功能复杂,示例程序很难写得简短,这几篇笔记共用同一份示例代码。在SHELL中运行如下命令下载例程源码:
svn checkout https://github.com/leichn/exercises/trunk/source/ffmpeg/ffmpeg_transcode例程支持在命令行中指定视音频编码格式以及输出文件封装格式。如果编码格式指定为“copy”,则输出流使用与输入流相同的编码格式。与ffmpeg命令不同的是,ffmpeg命令指定编码器参数为“copy”时,将不会启动编解码过程,而仅启用转封装过程,整个过程很快执行完毕;本例程指定编码格式为“copy”时,则会使用相同的编码格式进行解码与编码,整个过程比较耗时。
例程验证方法:
./transcode -i input.flv -c:v mpeg2video -c:a mp2 output.ts和如下命令效果大致一样:
ffmpeg -i input.flv -c:v mpeg2video -c:a mp2 output.ts源代码文件说明:
Makefile main.c 转复用转码功能 av_codec.c 编码解码功能 av_filter.c 滤镜处理 open_file.c 打开输入输出文件转码的主流程主要在main.c中transcode_video()、transcode_audio()和transcode_audio_with_afifo()三个函数中。当输入音频帧尺寸能被音频编码器接受时,使用transcode_audio()函数;否则,引入音频fifo,使每次从fifo中取出的音频帧尺寸能被音频编码器接受,使用transcode_audio_with_afifo()函数实现此功能。这几个函数仅提供示意功能,演示音视频转码功能的实现方法,源码纠结、可读性差,暂无时间优化。
2.1 视频转码流程视频转码函数transcode_video(),其主要处理流程如下(已删除大量细节代码):
static int transcode_video(const stream_ctx_t *sctx, AVPacket *ipacket) { AVFrame *frame_dec = av_frame_alloc(); AVFrame *frame_flt = av_frame_alloc(); AVPacket opacket; // 一个视频packet只包含一个视频frame,但冲洗解码器时一个flush packet会取出 // 多个frame出来,每次循环取处理一个frame while (1) { // 1. 时间基转换,解码 av_packet_rescale_ts(ipacket, sctx->i_stream->time_base, sctx->o_codec_ctx->time_base); ret = av_decode_frame(sctx->i_codec_ctx, ipacket, &new_packet, frame_dec); // 2. 滤镜处理 ret = filtering_frame(sctx->flt_ctx, frame_dec, frame_flt); // 3. 编码 // 3.1 设置帧类型 frame_flt->pict_type = AV_PICTURE_TYPE_NONE; // 3.2 编码 ret = av_encode_frame(sctx->o_codec_ctx, frame_flt, &opacket); // 3.3 更新编码帧中流序号 opacket.stream_index = sctx->stream_idx; // 3.4 时间基转换,AVPacket.pts和AVPacket.dts的单位是AVStream.time_base,不同的封装格式其 // AVStream.time_base不同所以输出文件中,每个packet需要根据输出封装格式重新计算pts和dts av_packet_rescale_ts(&opacket, sctx->o_codec_ctx->time_base, sctx->o_stream->time_base); // 4. 将编码后的packet写入输出媒体文件 ret = av_interleaved_write_frame(sctx->o_fmt_ctx, &opacket); av_packet_unref(&opacket); } return ret; } 2.2 音频转码流程