音频转码函数transcode_audio(),其主要处理流程如下(已删除大量细节代码):
static int transcode_audio_with_afifo(const stream_ctx_t *sctx, AVPacket *ipacket) { AVFrame *frame_dec = av_frame_alloc(); AVFrame *frame_flt = av_frame_alloc(); AVFrame *frame_enc = NULL; AVPacket opacket; int enc_frame_size = sctx->o_codec_ctx->frame_size; AVAudioFifo* p_fifo = sctx->aud_fifo; static int s_pts = 0; while (1) // 处理一个packet,一个音频packet可能包含多个音频frame,循环每次处理一个frame { // 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. 使用音频fifo,从而保证每次送入编码器的音频帧尺寸满足编码器要求 // 3.1 将音频帧写入fifo,音频帧尺寸是解码格式中音频帧尺寸 if (!dec_finished) { uint8_t** new_data = frame_flt->extended_data; // 本帧中多个声道音频数据 int new_size = frame_flt->nb_samples; // 本帧中单个声道的采样点数 // FIFO中可读数据小于编码器帧尺寸,则继续往FIFO中写数据 ret = write_frame_to_audio_fifo(p_fifo, new_data, new_size); } // 3.2 从fifo中取出音频帧,音频帧尺寸是编码格式中音频帧尺寸 // FIFO中可读数据大于编码器帧尺寸,则从FIFO中读走数据进行处理 while ((av_audio_fifo_size(p_fifo) >= enc_frame_size) || dec_finished) { // 从FIFO中读取数据,编码,写入输出文件 ret = read_frame_from_audio_fifo(p_fifo, sctx->o_codec_ctx, &frame_enc); // 4. fifo中读取的音频帧没有时间戳信息,重新生成pts frame_enc->pts = s_pts; s_pts += ret; flush_encoder: // 5. 编码 ret = av_encode_frame(sctx->o_codec_ctx, frame_enc, &opacket); // 5.1 更新编码帧中流序号,并进行时间基转换 // AVPacket.pts和AVPacket.dts的单位是AVStream.time_base,不同的封装格式其AVStream.time_base不同 // 所以输出文件中,每个packet需要根据输出封装格式重新计算pts和dts opacket.stream_index = sctx->stream_idx; av_packet_rescale_ts(&opacket, sctx->o_codec_ctx->time_base, sctx->o_stream->time_base); // 6. 将编码后的packet写入输出媒体文件 ret = av_interleaved_write_frame(sctx->o_fmt_ctx, &opacket); } if (finished) { break; } } return ret; } 2.3 转码过程中的时间戳处理在封装格式处理例程中,不深入理解时间戳也没有关系。但在编解码处理例程中,时间戳处理是很重要的一个细节,必须要搞清楚。
容器(文件层)中的时间基(AVStream.time_base)与编解码器上下文(视频层)里的时间基(AVCodecContex.time_base)不一样,解码编码过程中需要进行时间基转换。
视频按帧进行播放,所以原始视频帧时间基为 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,然后再送入编码器。
解码前的时间基转换:
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);关于时间基与时间戳的详细内容可参考“FFmpeg时间戳详解”
编解码过程主要关注音视频帧的pts,用户可不关注dts,详细说明可参考“FFmpeg编解码处理3-编解码API详解”
在SHELL中运行如下命令下载例程源码:
svn checkout https://github.com/leichn/exercises/trunk/source/ffmpeg/ffmpeg_transcode在源码目录执行make命令,生成transcode可执行文件
下载测试文件(右键另存为):tnmil2.flv