ffmpeg针对视频和音频裸码封装一点感想

来源:互联网 发布:c语言调用数学函数 编辑:程序博客网 时间:2024/05/16 06:59

换个新工作,需求是将实时接收过来的音频和视频封装成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

完成上诉两个过程

都要进行下一步就是对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即可,因项目音频重采样有点问题,先不上代码了,后期再加上吧。



0 0
原创粉丝点击