1080p画质的视频帧有1920 * 1080=2073600个像素点,每个像素点为3通道,每个通道每个像素点为8位。那么,一张图片为1920 * 1080 * 3 * 8bit=4976400bit=6220800B=6075KB=6MB。那么一个普通三通道1080P的30帧的1秒的视频尺寸为:6 * 30 * 1=180M。
这个大小对于本地存储和网络传输都是一个考验,为了减少传输带宽、减小存储空间,就需要将视频压缩。这个压缩的过程叫编码。常用的是H264编码。h264编码器编码完成的文件格式为*.h264
,与音频文件*.mp3
和字幕文件*.ass
打包后就是常见的MP4视频文件了。
yuv格式测试视频下载地址:yuv下载
根据网站提供的参数:宽为352,高为288,300帧,大小为44550kb。352* 288* 3* 8bit=2433024bit=304128B=297kB。300* 297kb=89100kb,yuv420p采样方式,大小为89100kb/2=44550kb。
编码流程为:
编码函数调用流程:
开发
打开文件、设置编码器参数、打开编码器
int main()
{
AVFormatContext *fmtCtx = NULL;
AVOutputFormat *outFmt = NULL;
AVStream *vStream = NULL;
AVCodecContext *codecCtx = NULL;
AVCodec *codec = NULL;
AVPacket *pkt=av_packet_alloc(); //创建已编码帧
uint8_t *picture_buf = NULL;
AVFrame *picFrame = NULL;
size_t size;
int ret = -1;
//[1]!打开视频文件
FILE *in_file = fopen("akiyo_cif.yuv", "rb");
if (!in_file) {
printf("can not open file!\n");
return -1;
}
//[1]!
do{
//[2]!打开输出文件,并填充fmtCtx数据
int in_w = 352,in_h=288,frameCnt=300;
const char *outFile = "result.h264";
if(avformat_alloc_output_context2(&fmtCtx,NULL,NULL,outFile)<0){
printf("Cannot alloc output file context.\n");
break;
}
outFmt=fmtCtx->oformat;
//[2]!
//[3]!打开输出文件
if(avio_open(&fmtCtx->pb,outFile,AVIO_FLAG_READ_WRITE)<0){
printf("output file open failed.\n");
break;
}
//[3]!
//[4]!创建h264视频流,并设置参数
vStream = avformat_new_stream(fmtCtx,codec);
if(vStream ==NULL){
printf("failed create new video stream.\n");
break;
}
vStream->time_base.den=25;
vStream->time_base.num=1;
//[4]!
//[5]!编码参数相关
AVCodecParameters *codecPara= fmtCtx->streams[vStream->index]->codecpar;
codecPara->codec_type=AVMEDIA_TYPE_VIDEO;
codecPara->width=in_w;
codecPara->height=in_h;
//[5]!
//[6]!查找编码器
codec = avcodec_find_encoder(outFmt->video_codec);
if(codec == NULL){
printf("Cannot find any endcoder.\n");
break;
}
//[6]!
//[7]!设置编码器内容
codecCtx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codecCtx,codecPara);
if(codecCtx==NULL){
printf("Cannot alloc context.\n");
break;
}
codecCtx->codec_id = outFmt->video_codec;
codecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
codecCtx->width = in_w;
codecCtx->height = in_h;
codecCtx->time_base.num = 1;
codecCtx->time_base.den = 25;
codecCtx->bit_rate = 400000;
codecCtx->gop_size = 12;
if (codecCtx->codec_id == AV_CODEC_ID_H264) {
codecCtx->qmin = 10;
codecCtx->qmax = 51;
codecCtx->qcompress = (float)0.6;
}
if (codecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
codecCtx->max_b_frames = 2;
if (codecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
codecCtx->mb_decision = 2;
//[7]!
//[8]!打开编码器
if(avcodec_open2(codecCtx,codec,NULL)<0){
printf("Open encoder failed.\n");
break;
}
//[8]!
av_dump_format(fmtCtx,0,outFile,1);//输出 输出文件流信息
//初始化帧
picFrame = av_frame_alloc();
picFrame->width = codecCtx->width;
picFrame->height = codecCtx->height;
picFrame->format = codecCtx->pix_fmt;
size = (size_t)av_image_get_buffer_size(codecCtx->pix_fmt,codecCtx->width,codecCtx->height,1);
picture_buf = (uint8_t *)av_malloc(size);
av_image_fill_arrays(picFrame->data,picFrame->linesize,
picture_buf,codecCtx->pix_fmt,
codecCtx->width,codecCtx->height,1);
//[9] --写头文件
ret = avformat_write_header(fmtCtx, NULL);
//[9]
int y_size = codecCtx->width * codecCtx->height;
av_new_packet(pkt, (int)(size * 3));
//[10] --循环编码每一帧
for (int i = 0; i < frameCnt; i++) {
//读入YUV
if (fread(picture_buf, 1, (unsigned long)(y_size * 3 / 2), in_file) <= 0) {
printf("read file fail!\n");
return -1;
} else if (feof(in_file))
break;
picFrame->data[ 0 ] = picture_buf; //亮度Y
picFrame->data[ 1 ] = picture_buf + y_size; // U
picFrame->data[ 2 ] = picture_buf + y_size * 5 / 4; // V
// AVFrame PTS
picFrame->pts = i;
//编码
if(avcodec_send_frame(codecCtx,picFrame)>=0){
while(avcodec_receive_packet(codecCtx,pkt)>=0){
printf("encoder success!\n");
// parpare packet for muxing
pkt->stream_index = vStream->index;
av_packet_rescale_ts(pkt, codecCtx->time_base, vStream->time_base);
pkt->pos = -1;
ret = av_interleaved_write_frame(fmtCtx, pkt);
if(ret<0){
printf("error is: %s.\n",av_err2str(ret));
}
av_packet_unref(pkt);//刷新缓存
}
}
}
//[10]
//[11] --Flush encoder
ret = flush_encoder(fmtCtx,codecCtx, vStream->index);
if (ret < 0) {
printf("flushing encoder failed!\n");
break;
}
//[11]
//[12] --写文件尾
av_write_trailer(fmtCtx);
//[12]
}while(0);
//释放内存
av_packet_free(&pkt);
avcodec_close(codecCtx);
av_free(picFrame);
av_free(picture_buf);
if(fmtCtx){
avio_close(fmtCtx->pb);
avformat_free_context(fmtCtx);
}
fclose(in_file);
return 0;
}
flush_encode部分不要好像对编码没有影响,但是官方代码有这部分就保留。
结果
测试输出为:
[libx264 @ 0xfd8900] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
[libx264 @ 0xfd8900] profile High, level 1.3
Output #0, h264, to 'result.h264':
Stream #0:0: Video: h264, none, 352x288, q=2-31, 400 kb/s, 25 tbn
encoder success!
...
encoder success!
Flushing stream #0 encoder
success encoder 1 frame.
...
success encoder 1 frame.
[libx264 @ 0xfd8900] frame I:25 Avg QP:15.23 size: 19296
[libx264 @ 0xfd8900] frame P:76 Avg QP:21.48 size: 1428
[libx264 @ 0xfd8900] frame B:199 Avg QP:24.15 size: 208
[libx264 @ 0xfd8900] consecutive B-frames: 8.7% 0.7% 24.0% 66.7%
[libx264 @ 0xfd8900] mb I I16..4: 5.9% 67.9% 26.2%
[libx264 @ 0xfd8900] mb P I16..4: 0.0% 0.2% 0.0% P16..4: 12.7% 10.8% 7.8% 0.0% 0.0% skip:68.4%
[libx264 @ 0xfd8900] mb B I16..4: 0.0% 0.0% 0.0% B16..8: 14.4% 1.9% 0.5% direct: 0.8% skip:82.4% L0:37.6% L1:46.7% BI:15.7%
[libx264 @ 0xfd8900] final ratefactor: 17.30
[libx264 @ 0xfd8900] 8x8 transform intra:68.0% inter:51.4%
[libx264 @ 0xfd8900] coded y,uvDC,uvAC intra: 95.7% 95.6% 89.7% inter: 4.7% 3.3% 0.6%
[libx264 @ 0xfd8900] i16 v,h,dc,p: 50% 12% 9% 29%
[libx264 @ 0xfd8900] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 25% 27% 18% 4% 4% 6% 5% 6% 6%
[libx264 @ 0xfd8900] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 28% 9% 10% 7% 7% 14% 6% 14% 6%
[libx264 @ 0xfd8900] i8c dc,h,v,p: 42% 23% 26% 10%
[libx264 @ 0xfd8900] Weighted P-Frames: Y:0.0% UV:0.0%
[libx264 @ 0xfd8900] ref P L0: 74.8% 10.2% 9.7% 5.2%
[libx264 @ 0xfd8900] ref B L0: 90.0% 8.2% 1.8%
[libx264 @ 0xfd8900] ref B L1: 96.8% 3.2%
[libx264 @ 0xfd8900] kb/s:421.54
按 <RETURN> 来关闭窗口...
编码后h264文件大小为617.5kb。
源视频为:
编码结果为:
如果需要使用x265编码,只需要调整部分参数就可以了。
完整代码在ffmpeg_Beginner中的12.video_encode_yuv2h264
中。