未经过编码处理的yuv数据直接存储将占用大量的磁盘空间,而且需要记录像素格式、分辨率等额为信息,一般的播放器也不支持直接打开。因此需要对原始数据进行编码处理,放入特定的文件中,也称为容器。
典型的多媒体容器格式如FLV、MP4、AVI和RMVB等,容器可以包含不同的类型的音视频流或者字幕流,以及正常播放所需要的同步信息。容器格式在此不是重点,待有需要的时候再做研究。
生成YUV数据
我们先利用命令行获取一个yuv数据,命令如下所示。该命令可以从src.mp4文件中提取出10秒钟的视频数据,像素格式为YUV420P,分辨率为640*480,帧率为25。
ffmpeg -i src.mp4 -t 10 -pixel_format yuv420p -framerate 25
-video_size 640*480 640_480_yuv420p.yuv
封装YUV 数据
使用命令行将刚刚得到的yuv数据封装成mp4文件,命令如下
ffmpeg -pixel_format yuv420p -video_size 640*480 -r 25 -i 640_480_yuv420p.yuv
out.mp4
使用代码的方式相对命令行要复杂一些,主要的代码如下:
#define PRINT_ERROR(ret)\
if (ret < 0) {\
char buf[100]={0};\
av_strerror(ret,buf,100);\
qDebug()<<buf;\
return ;\
}\
static int check_pixel_fmt(const AVCodec *codec,
enum AVPixelFormat pixel_fmt) {
const enum AVPixelFormat *p = codec->pix_fmts;
while (*p != AV_PIX_FMT_NONE) {
if (*p == pixel_fmt) return 1;
p++;
}
return 0;
}
static int write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c,
AVStream *st, AVFrame *frame)
{
int ret;
// send the frame to the encoder
ret = avcodec_send_frame(c, frame);
if (ret < 0) {
return -1;
}
while (ret >= 0) {
AVPacket pkt = { 0 };
ret = avcodec_receive_packet(c, &pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0) {
return -1;
}
/* rescale output packet timestamp values from codec to stream timebase */
av_packet_rescale_ts(&pkt, c->time_base, st->time_base);
pkt.stream_index = st->index;
/* Write the compressed frame to the media file. */
ret = av_interleaved_write_frame(fmt_ctx, &pkt);
av_packet_unref(&pkt);
if (ret < 0) {
return -1;
}
}
return ret == AVERROR_EOF ? 1 : 0;
}
void muxing()
{
AVOutputFormat * outputFmt;
AVFormatContext *formatCtx;
const char* outFileName = "d:/CODE/useFfmpeg/out2.mp4";
avformat_alloc_output_context2(&formatCtx,NULL,"mp4",outFileName);
outputFmt = formatCtx->oformat;
/*查找编码器,初始化编码器上下文*/
AVCodecContext* codecCtx;
AVCodec* codec ;
AVCodecID codecId = outputFmt->video_codec;
codec = avcodec_find_encoder(codecId);
codecCtx = avcodec_alloc_context3(codec);
codecCtx->codec_id = codecId;
//codecCtx->bit_rate = 400000;
codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
codecCtx->width = 640;
codecCtx->height = 480;
codecCtx->time_base = AVRational{1,25};
/*检查编码器是否支持设置的像素格式*/
if(check_pixel_fmt(codec,codecCtx->pix_fmt) == 0)
{
return;
}
int ret = avcodec_open2(codecCtx,codec,nullptr);
PRINT_ERROR(ret)
/*创建输出流*/
AVStream * st;
st = avformat_new_stream(formatCtx,NULL);
st->id = formatCtx->nb_streams-1;
st->time_base =(AVRational){ 1, 25 };
/*将AVCodecContext中的音视频流参数复制到AVCodecParameter中,如果没有这一步操作,avformat_write_header会失败并返回-22*/
avcodec_parameters_from_context(st->codecpar,codecCtx);
/*打开输出文件*/
ret = avio_open(&formatCtx->pb, outFileName, AVIO_FLAG_WRITE);
PRINT_ERROR(ret);
/*写入文件头信息 */
ret = avformat_write_header(formatCtx, NULL);
PRINT_ERROR(ret);
/*创建AVFrame,用于接收从文件读取的YUV数据*/
AVFrame* frame = av_frame_alloc();
frame->width = codecCtx->width;
frame->height = codecCtx->height;
frame->format = codecCtx->pix_fmt;
frame->pts = 0;
ret= av_image_alloc(&frame->data[0],&frame->linesize[0],codecCtx->width,codecCtx->height,codecCtx->pix_fmt,1);
//ret = av_frame_get_buffer(frame,0);
PRINT_ERROR(ret);
QFile inFile("d:/CODE/useFfmpeg/640_480_yuv420p.yuv");
inFile.open(QIODevice::ReadOnly);
/*一帧图片所占用的字节数*/
int imageSize = av_image_get_buffer_size(codecCtx->pix_fmt,codecCtx->width,codecCtx->height,1);
while (1) {
ret = inFile.read((char*)frame->data[0],imageSize);
if(ret <=0)
{
break;
}
if( write_frame(formatCtx,codecCtx,st,frame) < 0)
{
break;
}
frame->pts++;
}
write_frame(formatCtx,codecCtx,st,NULL);
av_write_trailer(formatCtx);
/*释放资源*/
avio_close(formatCtx->pb);
av_freep(&frame->data[0]);
av_frame_free(&frame);
avcodec_free_context(&codecCtx);
avformat_free_context(formatCtx);
}
问题记录
两种方式输出的mp4文件有些许差异,代码输出的mp4文件帧率为25.11,比特率也比命令行输出的要高。
我以为是比特率不同导致,于是将命令行和代码输出的文件比特率都设置成一样,但是最终代码输出的视频帧率还是不为整数。
ps:设置输出比特率,命令行需要增加参数 -b:v ,代码中需要设置
AVCodecContext结构体的bit_rate参数。
ffmpeg -pixel_format yuv420p -video_size 640*480 -r 25 -i 640_
480_yuv420p.yuv -b:v 400000 out1.mp4
关于这个帧率的问题目前还没查找到原因。如果有大佬知道如何修改,请不吝赐教。