项目中,需要把一路音频流及一路视频流分别转码,生成指定格式(MP4)文件。在使用ffmpeg转码生成mp4文件的过程中,碰到了不少的问题,主要如下:
1. 生成的mp4文件无法正常播放
2. 生成的mp4,用ffmpeg分析,发现码率、帧率等参数不对(编码后的pkt结构体无pts,手动赋值错误,如果是mp4文件,不考虑B帧的情况下,pts是按512往上累加,如果是ts文件,则是按3600累加)
3. 生成的mp4文件,没有声音(编码后的pkt结构体无pts,手动赋值错误,如果是AAC文件,pts按1024累加)
在利用ffmpeg转码的时候,首先要初始化一些结构体,在生成一个MP4文件的时候,也需要创建并初始化一些结构体,具体为:
int open_out_put(const char* filename)
{
int ret, i;
int video_index = 0;
int audio_index = 1;
AVStream *out_stream;
//编码参数的上下文
AVCodecContext *enc_ctx;
AVCodec *encoder;
//这个参数最为重要,AVFormatContxt*,它为全局变量,该函数主要目的就是初始化它
ofmt_ctx = NULL;
//为ofmt_ctx申请空间
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename);
if(ofmt_ctx == NULL)
{
av_log(NULL,AV_LOG_ERROR,"Could not create output context\n");
return AVERROR_UNKNOWN;
}
//申请好空间之后,现在开始需要为它添加音频流及视频流了
int nb_streams = 2;
for(i = 0; i < nb_streams; i++)
{
//在ofmt_ctx结构体中添加一路媒体流,这路流的实际参数通过out_stream来设置
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if(!out_stream)//添加流失败了
{
av_log(NULL,AV_LOG_ERROR,"Failed allocating output stream\n");
return AVERROR_UNKNOWN;
}
//获取这路流的编码参数结构体的指针,然后初始化它
enc_ctx = out_stream->codec;
if(i == video_index)
{
//添加了第一路流,认为它是视频流
encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
if(!encoder)
{
av_log(NULL,AV_LOG_ERROR,"Video encoder not found\n");
return AVERROR_UNKNOWN;
}
enc_ctx->bit_rate = 2000000;//2M码率
enc_ctx->width = 1920;
enc_tx->height = 1080;
enc_ctx->time_base = (AVRational)(1,25);//说明帧率为25fps
enc_ctx->gop_size = 50;
enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
enc_ctx->thread_count = 2;//编码器的线程数,正常情况下可以使编码速度变快
av_opt_set(enc_ctx->priv_data,"preset","untrafast",0);
}
else if(i == audio_index)
{
//添加了第二路流,认为它是音频流
encoder = avcodec_find_encoder(AV_CODEC_ID_AAC);
if(!encoder)
{
av_log(NULL,AV_LOG_ERROR,"Audio encoder not found\n");
return AVERROR_UNKNOWN;
}
enc_ctx->sample_rate = 44100;
enc_ctx->channels = 2;
enc_ctx->channel_layout = av_get_default_channel_layout(enc_ctx->channels);
enc_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;//根据实际的编码格式来设置,AAC为该值
enc_ctx->time_base = (AVRational){1,44100};
enc_ctx->bit_rate = 128000;
}
//初始完各个流的编码参数之后,该设置其它媒体文件相关的参数了
//ofmt_ctx->oformat结构体的值,根据filename的后缀名不同而有所不同,记住,这个判断在放在avcodec_open2之前,否则就算判断了,也不会生效,最终生成的mp4文件,无法正常播放
if(ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
ret = avcodec_open2(enc_ctx, encoder, NULL);
}
av_dump_format(ofmt_ctx, 0, filename, 1);
if(!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
{
ret = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE);
if(ret < 0)
{
av_log(NULL,AV_LOG_ERROR,"Could not open output file\n");
return ret;
}
}
AVDictionary *opt = NULL;
//设置媒体文件的视频帧率信息
av_dict_set_int(&opt, "video_track_timescale",25, 0);
ret = avformat_write_header(ofmt_ctx, &opt);
if(ret < 0)
{
av_log(NULL,AV_LOG_ERROR,"Write file header failed\n");
return ret;
}
return 1;
}
初始化好了之后,就开始编码了,编码成功后,调用函数
av_interleaved_write_frame(ofmt_ctx, &pkt);
把编码成功后的数据写入媒体文件中。
当编码结束后,再调用函数
av_write_trailer(fmt_ctx);
如果没调用此函数写入文件尾部,生成的mp4文件无法正常播放。
最后记得释放fmt_ctx结构体的内存空间。