-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvideo_encoder.cpp
More file actions
307 lines (255 loc) · 9.25 KB
/
Copy pathvideo_encoder.cpp
File metadata and controls
307 lines (255 loc) · 9.25 KB
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#include "video_encoder.h"
uint8_t VideoEncoder::ENDCODE[] = {0, 0, 1, 0xb7};
string VideoEncoder::DEFAULT_H264_PRESET = "medium";
string VideoEncoder::DEFAULT_H264_QP = "0";
string VideoEncoder::DEFAULT_H264_PROFILE = "high444";
VideoEncoder::VideoEncoder(string filepath, Size resolution, int video_codec): resolution(resolution), video_codec(video_codec), c(NULL), frameN(-1), disable_bitrate_control(false), initialization_flag(0), h264_profile(DEFAULT_H264_PROFILE), h264_preset(DEFAULT_H264_PRESET), b_frame(DEFAULT_B_FRAME), size_gop(DEFAULT_SIZE_GOP), fps(DEFAULT_FPS), h264_qp(DEFAULT_H264_QP), bitrate(DEFAULT_BITRATE) {
output = fopen(filepath.c_str(), "wb");
if(!output) {
cerr<<"Error: video_encoder.cpp: Could not open "<<filepath<<endl;
initialization_flag = -1;
return;
}
}
VideoEncoder::~VideoEncoder() {
fclose(output);
}
void VideoEncoder::init() {
/* register all the codecs */
avcodec_register_all();
AVCodecID video_codec_id;
if(video_codec == VIDEO_CODEC_H264)
video_codec_id = AV_CODEC_ID_H264;
else { // invalid codec option. use default video codec
cerr<<"Warning: video_encoder.cpp: Invalid video codec option: "<<video_codec<<". Default video codec H.264 is used."<<endl;
video_codec_id = AV_CODEC_ID_H264;
initialization_flag = 1;
return;
}
AVCodec* codec;
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec) {
cerr<<"Error: VideoEncoder::VideoEncoder: codec not found."<<endl;
initialization_flag = -2;
return;
}
c = avcodec_alloc_context3(codec);
if(!c) {
cerr<<"Error: video_encoder.cpp: Could not allocate video codec context"<<endl;
initialization_flag = -3;
return;
}
/* put sample parameters */
c->bit_rate = bitrate; // bitrate control
/* resolution must be a multiple of two */
c->width = resolution.width;
c->height = resolution.height;
/* frames per second */
c->time_base = (AVRational){1,fps};
/* emit one intra frame every ten frames
* check frame pict_type before passing frame
* to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
* then gop_size is ignored and the output of encoder
* will always be I frame irrespective to gop_size
*/
c->gop_size = size_gop;
c->max_b_frames = b_frame;
c->pix_fmt = AV_PIX_FMT_YUV444P; // use AV_PIX_FMT_YUV444P to facilitate data transfer from cv::Mat
if (video_codec_id == AV_CODEC_ID_H264) {
av_opt_set(c->priv_data, "profile", h264_profile.c_str(), 0);
av_opt_set(c->priv_data, "preset", h264_preset.c_str(), 0); // use H.264 preset
if(disable_bitrate_control)
// to set a parameter in x264
// use either
// av_opt_set(c->priv_data, "qp", "30", 0);
// or
// av_opt_set(c->priv_data, "x264opts", "qp=30", 0);
av_opt_set(c->priv_data, "qp", h264_qp.c_str(), 0); // set quantization parameter
}
/* open codec */
if (avcodec_open2(c, codec, NULL) < 0) {
cerr<<"Error: video_encoder.cpp: Could not open codec"<<endl;
initialization_flag = -4;
return;
}
// allocate frame
av_frame = av_frame_alloc();
if(!av_frame) {
cerr<<"Error: video_encoder.cpp: Could not allocate video frame"<<endl;
initialization_flag = -5;
return;
}
av_frame->format = c->pix_fmt;
av_frame->width = c->width;
av_frame->height = c->height;
// allocate raw picture buffer
int result = av_image_alloc(av_frame->data, av_frame->linesize, c->width, c->height, c->pix_fmt, 32);
if(result < 0) {
cerr<<"Error: video_encoder: Could not allocate raw picture buffer"<<endl;
initialization_flag = -6;
return;
}
}
bool VideoEncoder::write(const Mat& frame) {
// convert frame to YUV format
Mat frame_yuv = BGR2YUV(frame);
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
// size of the input frame
Size size = frame_yuv.size();
if(size.width != c->width || size.height != c->height){
cerr<<"Error: video_encoder.cpp: The resolution of frame changed."<<endl;
return false;
}
/* prepare image */
vector<Mat> mv;
split(frame_yuv, mv); // split channels to facilitate the memory copy
if(mv[0].isContinuous()) // if the matrix is in a single row in memory
memcpy(av_frame->data[0], mv[0].data, c->height*c->width);
else // if there are gaps between rows
for(int row = 0; row < c->height; row++) {
memcpy(&(av_frame->data[0][row*c->width]), mv[0].ptr<uchar>(row), c->width);
}
if(mv[1].isContinuous()) // if the matrix is in a single row in memory
memcpy(av_frame->data[1], mv[1].data, c->height*c->width);
else // if there are gaps between rows
for(int row = 0; row < c->height; row++) {
memcpy(&(av_frame->data[1][row*c->width]), mv[1].ptr<uchar>(row), c->width);
}
if(mv[2].isContinuous()) // if the matrix is in a single row in memory
memcpy(av_frame->data[2], mv[2].data, c->height*c->width);
else // if there are gaps between rows
for(int row = 0; row < c->height; row++) {
memcpy(&(av_frame->data[2][row*c->width]), mv[2].ptr<uchar>(row), c->width);
}
// presentation timestamp. starts from 0.
av_frame->pts = ++frameN;
/* encode the image */
int result;
result = avcodec_encode_video2(c, &pkt, av_frame, &got_output);
if (result < 0) {
cerr<<"Error: video_encoder.cpp: Error in encoding frame #"<<frameN<<"."<<endl;
return false;
}
if (got_output) {
fwrite(pkt.data, 1, pkt.size, output);
av_free_packet(&pkt);
}
return true;
}
bool VideoEncoder::flush() {
/* flush the delayed frames */
int result;
while(got_output) {
result = avcodec_encode_video2(c, &pkt, NULL, &got_output);
if (result < 0) {
cerr<<"Error: video_encoder.cpp: Error in encoding frame #"<<frameN<<"."<<endl;
return false;
}
if (got_output) {
fwrite(pkt.data, 1, pkt.size, output);
av_free_packet(&pkt);
}
}
/* add sequence end code to have a real mpeg file */
fwrite(ENDCODE, 1, sizeof(ENDCODE), output);
avcodec_close(c);
av_free(c);
av_freep(&av_frame->data[0]);
av_frame_free(&av_frame);
return true;
}
bool VideoEncoder::flush_close() {
bool result = flush();
fclose(output); // close the file no matter flush is successful or not.
return result;
}
int VideoEncoder::getInitializationFlag() {
return initialization_flag;
}
Mat VideoEncoder::BGR2YUV(const Mat& input){
Size inputSize = input.size();
Mat output(inputSize, CV_8UC3);
// split input Mat
// the input is BGR Mat, because imread() returns a BGR Mat in OpenCV
// inputChannels[0] is blue channel
// inputChannels[1] is green channel
// inputChannels[2] is red channel
vector<Mat> inputChannels;
split(input, inputChannels);
// calculate YUV Mat
// conversion formulae can be found in https://msdn.microsoft.com/en-ca/library/aa917087.aspx
for(int i = 0; i < inputSize.height; i++){
for(int j = 0; j < inputSize.width; j++){
// prepare the input parameter of RGB2YUV
vector<uchar> rgb(3);
rgb[0] = inputChannels[2].at<uchar>(i,j); // note that OpenCV convention is BGR, instead of RGB
rgb[1] = inputChannels[1].at<uchar>(i,j);
rgb[2] = inputChannels[0].at<uchar>(i,j); // note that OpenCV convention is BGR, instead of RGB
vector<uchar> yuv = RGB2YUV(rgb);
uchar y = yuv[0];
uchar u = yuv[1];
uchar v = yuv[2];
output.at<Vec3b>(i,j)[0] = y;
output.at<Vec3b>(i,j)[1] = u;
output.at<Vec3b>(i,j)[2] = v;
}
}
return output;
}
void VideoEncoder::setQP(int qp) {
// pre-condition
if(qp < 0)
qp = 0;
if(qp > 69)
qp = 69;
h264_qp = to_string(qp);
}
void VideoEncoder::setBitrate(int bitrate) {
this->bitrate = bitrate;
}
void VideoEncoder::setFPS(int fps) {
// pre-condition
if(fps < 1)
fps = 1;
this->fps = fps;
}
void VideoEncoder::setSizeGOP(int size_gop) {
// pre-condition
if(size_gop < 1)
size_gop = 1;
this->size_gop = size_gop;
}
void VideoEncoder::setBFrame(int b_frame) {
// pre-condition
if(b_frame < 0)
b_frame = 0;
this->b_frame = b_frame;
}
void VideoEncoder::setPreset(string preset) {
this->h264_preset = preset;
}
void VideoEncoder::setProfile(string profile) {
this->h264_profile = profile;
}
void VideoEncoder::disableBitrateControl() {
disable_bitrate_control = true;
}
vector<unsigned char> VideoEncoder::RGB2YUV(vector<unsigned char> rgb) {
unsigned char r = rgb[0];
unsigned char g = rgb[1];
unsigned char b = rgb[2];
unsigned char y;
unsigned char u;
unsigned char v;
y = ((66*r + 129*g +25*b + 128) >> 8) + 16;
u = ((-38*r - 74*g + 112*b + 128) >> 8) + 128;
v = ((112*r - 94*g - 18*b + 128) >> 8) + 128;
// construct the return value
vector<unsigned char> yuv(3);
yuv[0] = y;
yuv[1] = u;
yuv[2] = v;
return yuv;
}