0%

FFmpeg实战 H264

基本步骤:

  1. 打开编码器,设置编码器参数;
  2. 转换图像格式,NV12->YUV420P;
  3. 准备编码数据AVFrame;
  4. 进行编码;

重点是前三步。

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
static void
alloc_data_for_encode(AVFrame **in_frame, AVPacket **out_pkt) {
int ret = 0;

// 定义编码器输入
AVFrame *frame = NULL;
// 创建
frame = av_frame_alloc();
if (!frame) {
log_error(ret);
printf("Error, No Memory!\n");
goto __ERROR;
}

// 配置输入参数
frame->width = kWidth;
frame->height = kHeight;
frame->format = in_fmt;
ret = av_frame_get_buffer(frame, 32); // 按32位对齐
if (!frame->buf[0]) {
log_error(ret);
printf("无法读取输入数据\n");
goto __ERROR;
}

*in_frame = frame;

// 创建编码器输出
AVPacket *newpkt = av_packet_alloc();
if (!newpkt) {
log_error(ret);
printf("无法创建输出数据\n");
goto __ERROR;
}

*out_pkt = newpkt;

// !!!: 一定要在标签前返回
return;

__ERROR:
if (frame) av_frame_free(&frame);
if (newpkt) av_packet_free(&newpkt);

return;
}

static int
encode_frame(AVFrame *frame, AVPacket *newpkt, FILE *output_file) {
if (frame) printf("send frame to encoder. pts=%lld\n", frame->pts);

int ret = 0;
// 输入frame到编码器
ret = avcodec_send_frame(c_ctx, frame);

while (ret >= 0) {
// 获取编码后的数据,如果成功,则重复获取
ret = avcodec_receive_packet(c_ctx, newpkt);
if (ret < 0) {
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else {
printf("error in encoding audio frame\n");
goto __ERROR;
//exit(-1);
}
}

// 写入文件
fwrite(newpkt->data, 1, (size_t)newpkt->size, output_file);
}

return ret;

__ERROR:
// 释放编码输入输出
if (frame) av_frame_free(&frame);
if (newpkt) av_packet_free(&newpkt);

return ret;
}

void read_video() {
if (!fmt_ctx) {
printf("不能使用设备\n");
goto __ERROR;
}

/// 准备文件
// 创建文件,权限:w(写入)b(写入二进制)+(若文件不存在则创建)
FILE *output_yuv = fopen("/Users/bq/Movies/test/video.yuv", "wb+");
if (!output_yuv) {
printf("文件创建失败\n");
}
FILE *output_h264 = fopen("/Users/bq/Movies/test/video.h264", "wb+");
if (!output_h264) {
printf("文件创建失败\n");
}

// 使用栈空间分配AVPacket
AVPacket pkt;
av_init_packet(&pkt);

int count = 0;
int ret = 0;

/// 编码输入、输出数据
AVFrame *frame = NULL;
AVPacket *newpkt = NULL;
alloc_data_for_encode(&frame, &newpkt);

int base_pts = 0;
while ((ret == 0 || ret == AVERROR(EAGAIN)) && count < 100) {
// 读取视频数据
ret = av_read_frame(fmt_ctx, &pkt);

if (ret == AVERROR(EAGAIN)) continue;
printf("[%d] pkt size is %d\n", count, pkt.size);
count++;

/**
* NV12 -> YUV420
* NV12: YYYYYYYYUVUV
* YUV420: YYYYYYYYUUVV
* */

// 拷贝数据到输入
const size_t y_length = kWidth * kHeight;
const size_t u_v_length = y_length / 4;
// 拷贝Y数据
memcpy(frame->data[0], pkt.data, y_length);
// 处理UV,Y数据后面是UV,对YUV数据分层
const int stride = 2;
for (int i = 0; i < u_v_length; i++) {
const size_t base = y_length + i * stride;
frame->data[1][i] = pkt.data[base];
frame->data[2][i] = pkt.data[base + 1];
}
// 输出YUV
fwrite(frame->data[0], 1, y_length, output_yuv);
fwrite(frame->data[1], 1, u_v_length, output_yuv);
fwrite(frame->data[2], 1, u_v_length, output_yuv);

// 编码,需将pts重置为顺序的值
frame->pts = base_pts++;
ret = encode_frame(frame, newpkt, output_h264);

av_packet_unref(&pkt);
}
// 再次调用一次编码,防止丢帧
encode_frame(NULL, newpkt, output_h264);

printf("完成写入\n");

// 这里不写return,会一直执行下去
__ERROR:
// 关闭文件
fclose(output_yuv);
fclose(output_h264);
// 释放编码输入输出
if (frame)av_frame_free(&frame);
if (newpkt) av_packet_free(&newpkt);
}

欢迎关注我的其它发布渠道