FFmpeg入门教程10.11:yuv编码为h264

系列索引:FFmpeg入门系列索引

上一篇:FFmpeg入门教程10.10:CUDA硬解并使用Qt播放视频(YUV420SP->RGB32)

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。

操作流程图为:

flowchart TB

A(开始) --> B[打开yuv文件]
A --> C[创建h264文件]
C --> D[打开文件]
D --> E[创建h264流]
E --> F[打开编码器]
F --> G{读一帧}
G --Yes--> H[输入编码器]
H --> I[获取编码结果]
I --> J[写入文件]
J --> G
G --No--> L[刷新编码器]
L --> M(结束)
B --> G

函数调用流程图为:

flowchart TB

A(Start) --> B[fopen]
A --> C[avformat_alloc_output_context2]
C --> D[avio_open]
D --> E[avformat_new_stream]
E --> F[avcodec_find_encoder]
F --> J[avcodec_alloc_context3]
J --> O[avcodec_open2]
O --> G{av_read_frame}
G --Yes--> H[avcodec_send_frame]
H --> I[avcodec_receive_packet]
I -->  G
G --No--> L[flush_encoder]
L --> N(av_write_trailer)
N --> M(End)
B --> G

开发

打开文件、设置编码器参数、打开编码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
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部分不要好像对编码没有影响,但是官方代码有这部分就保留。

结果

测试输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[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。

源视频为:

source

编码结果为:

result

如果需要使用x265编码,只需要调整部分参数就可以了。

完整代码在ffmpeg_beginner中的10.11.video_encode_yuv2h264中。

下一篇:FFmpeg入门教程10.12:h264编码为mp4


FFmpeg入门教程10.11:yuv编码为h264
https://blog.jackeylea.com/ffmpeg/ffmpeg-encode-yuv-to-h264/
作者
JackeyLea
发布于
2020年10月30日
许可协议