换个新工作,需求是将实时接收过来的音频和视频封装成mpegts格式,自然想到的是用ffmpeg进行编码,网上找了下这方面资料,无奈找了半天 没找到相应的资料,关于ffmpeg编译命令行的到是非常多,所以自己就研究总结下,参考ffmpeg中的例子,个人用ffmpeg时间不多,理解有限, 可能有错误之处,望指点一二。
输入参数:
输入视频流:h264,YUV420,分辨率1280*720,帧率25,码率4M
输入音频流:pcm,采样率8K,8位,单声道
输出参数:
音频:AAC,采样率44100,16位,双声道
视频:H264
mpegts
ffmpeg中对文件的输入和输出用一个结构体AVFormatContext来指定,其中AVInputFormat指定的是输入,AVOutputFormat指定的是输出,输出格式用函数av_guess_format来查找指定格式
AVOutputFormat *fmt = av_guess_format("mpegts",NULL,NULL);
AVFormatContex *oc = avformat_alloc_context();
oc->oformat= fmt;
其 中无论是AVInputFormat还是AVOutputFormat,都包含一个结构体AVStream,这个结构体表示一个单一的流,音频流,或者视 频流,无论输入文件是用何种容器格式包装存储,他都将视频流和音频流分开放到这个AVStream中,这样我们可以根据这两个流分别对音频或者视频进行处 理,可以对视频进行解码,或者编码,或者什么都不做,原样输出,也就是ffmpeg命令行中的copy。
其中创建输出流分两总情况:
1.音、视频编码:
AVCodec *codec = avcodec_find_encodec(CODEC_ID_AAC);//查找AAC编码器,视频就查找其他格式的编码器
AVStream *audio_st = avformat_new_stream(oc,codec);//其中就已经动态分配AVCodexContext,并将codec指定到AVCodexContext
2.音视频不编码
AVStream *video_st = avformat_new_stream(oc,NULL);//不指定编码器,这样实现就是ffmpeg命令行的copy,当然其他的参数赋值要根据AVInputFormat的AVStream的AVCodecContext来赋值。
完成上诉两个过程
都要进行下一步就是对AVStream中的AVCodexContext进行手动初始化
其 中共有的codec_id,bit_rate,codec_type,id等都需要赋值,其中id和avformat_new_stream初始化顺序有 关,如果先初始化的是视频,那么视频的id就是0,音频就是1,其中的视频AVStream中的AVCodecContext中的time_base这个 表示帧率
这样到后面的AVPacket中的stream_index就要根据这个id来指定当前packet是视频包还是音频包
初始化完毕后需要判断是否对输出标志位进行设置
if(oc->oformat->flags & AVFMT_GLOBALHEADER)
{
vodeo_st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
如果进行编码的话
就需要进行打开编码器,否则就不需要
avcodec_open2
打开编码器的话,就需要一个结构体AVFrame
AVFrame这个存放一个解码后的原始数据,音频就是PCM,视频就是YUV
而AVPacket这个存放的是编码数据,音频可能是AAC或者其他格式,视频就是H264等一桢数据,其中的flag表示是否是关键帧,这些在后续中都要手动设置的。
先说音频编码:
AVFrame有几个值需要赋值
其中的nb_samples要赋值输出流中AVStream的AVCodecContext的frame_size,表示一个AVFrame中包含多少个音频帧,虽然音频没有帧的概念,暂且这么理解吧
format要赋值成输出流AVStream的AVCodecContext的sample_fmt,指定AVFrame的音频格式,可能是AV_SAMPLE_FMT_U8或者是AV_SAMPLE_S16,如果是U8还得需要音频重采样。
channel_layout这个不用说,声道数目,音频专用
这样我们还需要一个缓冲区放入到AVFramleavede中,缓冲区的大小计算可以根据函数av_samples_get_buffer_size来计算
然后将此缓冲区赋值到AVFrame中,通过函数avcodec_fill_audio_frame
这样前期初始化完毕,接下来就处理就收到的视频帧和音频了
写文件第一帧时,一定要关键帧,而一些特殊容器如MP4或flv关键帧前还需要加pps和sps,如果想播放过程中拖放视频,那每个关键帧都得有sps和pps
avio_open,avformat_write_header
对于视频不需要编码,那就直接封装AVPacket
其 中的pts和dts如果没有B帧,值相等即可,而duration就按照90000除以帧率来赋值就可以,其中stream_index要和视频流 AVStream中的AVCodecContext的id要一致,如果是关键帧,就将flags设置AV_PKT_FLAG_KEY
然后直接av_interleaved_write_frame即可。
对 于音频能复杂些,毕竟涉及到编码,重采样,新版ffmpeg中对音频定义有两种格式,一种是平面的,一中是非平面的,目前我这个需求中是对非平面进行处 理,处理音频数据有一点要注意,音频数据一定要填满AVFrame的缓冲区,然后在进行编码。否者出来的效果就不受控制了。
项目中接收到的 音频数据是定长的,所以需要拼接到音频缓冲区大小,在写到音频缓冲区之前,先进行重采样处理,然后将重采样的数据拼接到AVFrame的音频缓冲区中,好 在ffmpeg中提供一个AVFifoBuffer,这个用起来很方便,这样每次收到数据写到av_fifo_generic_write中,另一个判断 当前缓冲区是否达到AVFrame缓冲区大小即可,达到后进行编码,有一点说明的是每次编码前,AVFrame的pts要进行赋值,否则编码出的pts是 负值,就没有办法进行时间基数的转换了。一般来说先定义一个int64_t lastpts = 0;编码前AVFrame->pts = lastpts;然后lastpts = lastpts + AVFrame->nb_samples;
进行avcodec_encode_audio2后,packet就是编码的数据,然后进行时间基的转换,用av_rescale_q函数时间。
最后写到av_interleaved_write_frame即可,因项目音频重采样有点问题,先不上代码了,后期再加上吧。