08f8a185 |
1 | // Created on: 2016-04-01 |
2 | // Created by: Anastasia BORISOVA |
3 | // Copyright (c) 2016 OPEN CASCADE SAS |
4 | // |
5 | // This file is part of Open CASCADE Technology software library. |
6 | // |
7 | // This library is free software; you can redistribute it and/or modify it under |
8 | // the terms of the GNU Lesser General Public License version 2.1 as published |
9 | // by the Free Software Foundation, with special exception defined in the file |
10 | // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT |
11 | // distribution for complete text of the license and disclaimer of any warranty. |
12 | // |
13 | // Alternatively, this file may be used under the terms of Open CASCADE |
14 | // commercial license or contractual agreement. |
15 | |
16 | // activate some C99 macros like UINT64_C in "stdint.h" which used by FFmpeg |
17 | #ifndef __STDC_CONSTANT_MACROS |
18 | #define __STDC_CONSTANT_MACROS |
19 | #endif |
20 | |
21 | #include <Image_VideoRecorder.hxx> |
22 | |
23 | #include <Message.hxx> |
24 | #include <Message_Messenger.hxx> |
25 | |
26 | #ifdef HAVE_FFMPEG |
27 | |
28 | // Suppress deprecation warnings - it is difficult to provide compatibility with old and new API at once |
29 | // since new APIs are introduced too often. |
30 | // Should be disabled from time to time to clean up usage of old APIs. |
31 | #if (defined(__GNUC__) && (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)) |
32 | _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") |
33 | #else |
34 | Standard_DISABLE_DEPRECATION_WARNINGS |
35 | #endif |
36 | |
37 | extern "C" |
38 | { |
39 | #ifdef _MSC_VER |
40 | // suppress some common warnings in FFmpeg headers |
41 | #pragma warning(disable : 4244) |
42 | #endif |
43 | |
44 | #include <libavcodec/avcodec.h> |
45 | #include <libavformat/avformat.h> |
46 | #include <libswscale/swscale.h> |
47 | #include <libavutil/imgutils.h> |
48 | |
49 | #ifdef _MSC_VER |
50 | #pragma warning(default : 4244) |
51 | #endif |
52 | }; |
53 | |
746f3d7a |
54 | // Undefine macro that clashes with name used by field of Image_VideoParams; |
55 | // this macro is defined in headers of older versions of libavutil |
56 | // (see definition of macro FF_API_PIX_FMT in version.h) |
57 | #ifdef PixelFormat |
58 | #undef PixelFormat |
59 | #endif |
60 | |
08f8a185 |
61 | #endif |
62 | |
63 | IMPLEMENT_STANDARD_RTTIEXT(Image_VideoRecorder, Standard_Transient) |
64 | |
65 | //============================================================================= |
66 | //function : Constructor |
67 | //purpose : |
68 | //============================================================================= |
69 | Image_VideoRecorder::Image_VideoRecorder() |
70 | : myAVContext (NULL), |
71 | myVideoStream (NULL), |
72 | myVideoCodec (NULL), |
73 | myFrame (NULL), |
74 | myScaleCtx (NULL), |
75 | myFrameCount (0) |
76 | { |
77 | myFrameRate.num = 1; |
78 | myFrameRate.den = 30; |
79 | |
80 | #ifdef HAVE_FFMPEG |
81 | // initialize libavcodec, and register all codecs and formats, should be done once |
82 | av_register_all(); |
83 | #endif |
84 | } |
85 | |
86 | //============================================================================= |
87 | //function : ~Image_VideoRecorder |
88 | //purpose : |
89 | //============================================================================= |
90 | Image_VideoRecorder::~Image_VideoRecorder() |
91 | { |
92 | Close(); |
93 | } |
94 | |
95 | //============================================================================= |
96 | //function : formatAvError |
97 | //purpose : |
98 | //============================================================================= |
99 | TCollection_AsciiString Image_VideoRecorder::formatAvError (const int theError) const |
100 | { |
101 | #ifdef HAVE_FFMPEG |
102 | char anErrBuf[AV_ERROR_MAX_STRING_SIZE] = {}; |
103 | av_strerror (theError, anErrBuf, AV_ERROR_MAX_STRING_SIZE); |
104 | return anErrBuf; |
105 | #else |
106 | return TCollection_AsciiString(theError); |
107 | #endif |
108 | } |
109 | |
110 | //============================================================================= |
111 | //function : Close |
112 | //purpose : |
113 | //============================================================================= |
114 | void Image_VideoRecorder::Close() |
115 | { |
116 | #ifdef HAVE_FFMPEG |
117 | if (myScaleCtx != NULL) |
118 | { |
119 | sws_freeContext (myScaleCtx); |
120 | myScaleCtx = NULL; |
121 | } |
122 | |
123 | if (myAVContext == NULL) |
124 | { |
125 | myFrameCount = 0; |
126 | return; |
127 | } |
128 | |
129 | // Write the trailer, if any. The trailer must be written before you close the CodecContexts open when you wrote the header; |
130 | // otherwise av_write_trailer() may try to use memory that was freed on av_codec_close(). |
131 | if (myFrameCount != 0) |
132 | { |
133 | av_write_trailer (myAVContext); |
134 | myFrameCount = 0; |
135 | } |
136 | |
137 | // close each codec |
138 | if (myVideoStream != NULL) |
139 | { |
140 | avcodec_close (myVideoStream->codec); |
141 | myVideoStream = NULL; |
142 | } |
143 | if (myFrame != NULL) |
144 | { |
145 | av_free (myFrame->data[0]); |
146 | av_frame_free (&myFrame); |
147 | myFrame = NULL; |
148 | } |
149 | |
150 | if (!(myAVContext->oformat->flags & AVFMT_NOFILE)) |
151 | { |
152 | // close the output file |
153 | avio_close (myAVContext->pb); |
154 | } |
155 | |
156 | // free the stream |
157 | avformat_free_context (myAVContext); |
158 | myAVContext = NULL; |
159 | #endif |
160 | } |
161 | |
162 | //============================================================================= |
163 | //function : Open |
164 | //purpose : |
165 | //============================================================================= |
166 | Standard_Boolean Image_VideoRecorder::Open (const char* theFileName, |
167 | const Image_VideoParams& theParams) |
168 | { |
169 | #ifdef HAVE_FFMPEG |
170 | Close(); |
171 | if (theParams.Width <= 0 |
172 | || theParams.Height <= 0) |
173 | { |
174 | return Standard_False; |
175 | } |
176 | |
177 | // allocate the output media context |
178 | avformat_alloc_output_context2 (&myAVContext, NULL, theParams.Format.IsEmpty() ? NULL : theParams.Format.ToCString(), theFileName); |
179 | if (myAVContext == NULL) |
180 | { |
181 | ::Message::DefaultMessenger()->Send ("ViewerTest_VideoRecorder, could not deduce output format from file extension", Message_Fail); |
182 | return Standard_False; |
183 | } |
184 | |
185 | // add the audio stream using the default format codecs and initialize the codecs |
186 | if (!addVideoStream (theParams, myAVContext->oformat->video_codec)) |
187 | { |
188 | Close(); |
189 | return Standard_False; |
190 | } |
191 | |
192 | // open video codec and allocate the necessary encode buffers |
193 | if (!openVideoCodec (theParams)) |
194 | { |
195 | Close(); |
196 | return Standard_False; |
197 | } |
198 | |
199 | #ifdef OCCT_DEBUG |
200 | av_dump_format (myAVContext, 0, theFileName, 1); |
201 | #endif |
202 | |
203 | // open the output file, if needed |
204 | if ((myAVContext->oformat->flags & AVFMT_NOFILE) == 0) |
205 | { |
206 | const int aResAv = avio_open (&myAVContext->pb, theFileName, AVIO_FLAG_WRITE); |
207 | if (aResAv < 0) |
208 | { |
209 | const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: could not open '") + theFileName + "', " + formatAvError (aResAv); |
210 | ::Message::DefaultMessenger()->Send (aMsg, Message_Fail); |
211 | Close(); |
212 | return Standard_False; |
213 | } |
214 | } |
215 | |
216 | // write the stream header, if any |
217 | const int aResAv = avformat_write_header (myAVContext, NULL); |
218 | if (aResAv < 0) |
219 | { |
220 | const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not open output file '") + theFileName + "', " + formatAvError (aResAv); |
221 | ::Message::DefaultMessenger()->Send (aMsg, Message_Fail); |
222 | Close(); |
223 | return Standard_False; |
224 | } |
225 | #else |
226 | (void )theFileName; |
227 | (void )theParams; |
228 | #endif |
229 | return Standard_True; |
230 | } |
231 | |
232 | //============================================================================= |
233 | //function : addVideoStream |
234 | //purpose : |
235 | //============================================================================= |
236 | Standard_Boolean Image_VideoRecorder::addVideoStream (const Image_VideoParams& theParams, |
237 | const Standard_Integer theDefCodecId) |
238 | { |
239 | myFrameRate.num = theParams.FpsNum; |
240 | myFrameRate.den = theParams.FpsDen; |
241 | |
242 | #ifdef HAVE_FFMPEG |
243 | // find the encoder |
244 | TCollection_AsciiString aCodecName; |
245 | if (!theParams.VideoCodec.IsEmpty()) |
246 | { |
247 | myVideoCodec = avcodec_find_encoder_by_name (theParams.VideoCodec.ToCString()); |
248 | aCodecName = theParams.VideoCodec; |
249 | } |
250 | else |
251 | { |
252 | myVideoCodec = avcodec_find_encoder ((AVCodecID )theDefCodecId); |
253 | aCodecName = avcodec_get_name ((AVCodecID )theDefCodecId); |
254 | } |
255 | if (myVideoCodec == NULL) |
256 | { |
257 | const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not find encoder for ") + aCodecName; |
258 | ::Message::DefaultMessenger()->Send (aMsg, Message_Fail); |
259 | return Standard_False; |
260 | } |
261 | |
262 | const AVCodecID aCodecId = myVideoCodec->id; |
263 | myVideoStream = avformat_new_stream (myAVContext, myVideoCodec); |
264 | if (myVideoStream == NULL) |
265 | { |
266 | const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not allocate stream!"); |
267 | ::Message::DefaultMessenger()->Send (aMsg, Message_Fail); |
268 | return Standard_False; |
269 | } |
270 | myVideoStream->id = myAVContext->nb_streams - 1; |
271 | |
272 | AVCodecContext* aCodecCtx = myVideoStream->codec; |
273 | aCodecCtx->codec_id = aCodecId; |
274 | // resolution must be a multiple of two |
275 | aCodecCtx->width = theParams.Width; |
276 | aCodecCtx->height = theParams.Height; |
277 | // Timebase is the fundamental unit of time (in seconds) in terms of which frame timestamps are represented. |
278 | // For fixed-fps content, timebase should be 1/framerate and timestamp increments should be identical to 1. |
279 | aCodecCtx->time_base.den = myFrameRate.num; |
280 | aCodecCtx->time_base.num = myFrameRate.den; |
281 | aCodecCtx->gop_size = 12; // emit one intra frame every twelve frames at most |
282 | |
283 | // some formats want stream headers to be separate |
284 | if (myAVContext->oformat->flags & AVFMT_GLOBALHEADER) |
285 | { |
98e6c6d1 |
286 | aCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; |
08f8a185 |
287 | } |
288 | return Standard_True; |
289 | #else |
290 | (void )theParams; |
291 | (void )theDefCodecId; |
292 | return Standard_False; |
293 | #endif |
294 | } |
295 | |
296 | //============================================================================= |
297 | //function : openVideoCodec |
298 | //purpose : |
299 | //============================================================================= |
300 | Standard_Boolean Image_VideoRecorder::openVideoCodec (const Image_VideoParams& theParams) |
301 | { |
302 | #ifdef HAVE_FFMPEG |
303 | AVDictionary* anOptions = NULL; |
304 | AVCodecContext* aCodecCtx = myVideoStream->codec; |
305 | |
306 | // setup default values |
307 | aCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; |
308 | //av_dict_set (&anOptions, "threads", "auto", 0); |
309 | if (aCodecCtx->codec == avcodec_find_encoder_by_name ("mpeg2video")) |
310 | { |
311 | // just for testing, we also add B frames |
312 | aCodecCtx->max_b_frames = 2; |
313 | aCodecCtx->bit_rate = 6000000; |
314 | } |
315 | else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("mpeg4")) |
316 | { |
317 | // |
318 | } |
319 | else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("mjpeg")) |
320 | { |
321 | aCodecCtx->pix_fmt = AV_PIX_FMT_YUVJ420P; |
322 | aCodecCtx->qmin = aCodecCtx->qmax = 5; |
323 | } |
324 | else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("huffyuv")) |
325 | { |
326 | aCodecCtx->pix_fmt = AV_PIX_FMT_RGB24; |
327 | } |
328 | else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("png")) |
329 | { |
330 | aCodecCtx->pix_fmt = AV_PIX_FMT_RGB24; |
331 | aCodecCtx->compression_level = 9; // 0..9 |
332 | } |
333 | else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("h264") |
334 | || aCodecCtx->codec == avcodec_find_encoder_by_name ("libx264")) |
335 | { |
336 | // use CRF (Constant Rate Factor) as best single-pass compressing method |
337 | av_dict_set (&anOptions, "crf", "20", 0); // quality 18-28, 23 is default (normal), 18 is almost lossless |
338 | av_dict_set (&anOptions, "preset", "slow", 0); // good compression (see also "veryslow", "ultrafast") |
339 | |
340 | // live-capturing |
341 | //av_dict_set (&anOptions, "qp", "0", 0); // instead of crf |
342 | //av_dict_set (&anOptions, "preset", "ultrafast", 0); |
343 | |
344 | // compatibility with devices |
345 | //av_dict_set (&anOptions, "profile", "baseline", 0); |
346 | //av_dict_set (&anOptions, "level", "3.0", 0); |
347 | } |
348 | else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("vp8") |
349 | || aCodecCtx->codec == avcodec_find_encoder_by_name ("vp9")) |
350 | { |
fcd9a94e |
351 | av_dict_set (&anOptions, "crf", "20", 0); // quality 4-63, 10 is normal |
08f8a185 |
352 | } |
353 | |
354 | // override defaults with specified options |
355 | if (!theParams.PixelFormat.IsEmpty()) |
356 | { |
357 | const AVPixelFormat aPixFormat = av_get_pix_fmt (theParams.PixelFormat.ToCString()); |
358 | if (aPixFormat == AV_PIX_FMT_NONE) |
359 | { |
360 | const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: unknown pixel format has been specified '") + theParams.PixelFormat + "'"; |
361 | ::Message::DefaultMessenger()->Send (aMsg, Message_Fail); |
362 | return Standard_False; |
363 | } |
364 | |
365 | aCodecCtx->pix_fmt = aPixFormat; |
366 | for (Resource_DataMapOfAsciiStringAsciiString::Iterator aParamIter (theParams.VideoCodecParams); |
367 | aParamIter.More(); aParamIter.Next()) |
368 | { |
369 | av_dict_set (&anOptions, aParamIter.Key().ToCString(), aParamIter.Value().ToCString(), 0); |
370 | } |
371 | } |
372 | |
373 | // open codec |
374 | int aResAv = avcodec_open2 (aCodecCtx, myVideoCodec, &anOptions); |
375 | if (anOptions != NULL) |
376 | { |
377 | av_dict_free (&anOptions); |
378 | } |
379 | if (aResAv < 0) |
380 | { |
381 | const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not open video codec, ") + formatAvError (aResAv); |
382 | ::Message::DefaultMessenger()->Send (aMsg, Message_Fail); |
383 | return Standard_False; |
384 | } |
385 | |
386 | // allocate and init a re-usable frame |
387 | myFrame = av_frame_alloc(); |
388 | if (myFrame == NULL) |
389 | { |
390 | const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not allocate video frame!"); |
391 | ::Message::DefaultMessenger()->Send (aMsg, Message_Fail); |
392 | return Standard_False; |
393 | } |
394 | |
395 | // allocate the encoded raw picture |
396 | aResAv = av_image_alloc (myFrame->data, myFrame->linesize, |
397 | aCodecCtx->width, aCodecCtx->height, aCodecCtx->pix_fmt, 1); |
398 | if (aResAv < 0) |
399 | { |
400 | memset (myFrame->data, 0, sizeof(myFrame->data)); |
401 | memset (myFrame->linesize, 0, sizeof(myFrame->linesize)); |
402 | const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not allocate picture ") |
403 | + aCodecCtx->width+ "x" + aCodecCtx->height + ", " + formatAvError (aResAv); |
404 | ::Message::DefaultMessenger()->Send (aMsg, Message_Fail); |
405 | return Standard_False; |
406 | } |
407 | // copy data and linesize picture pointers to frame |
408 | myFrame->format = aCodecCtx->pix_fmt; |
409 | myFrame->width = aCodecCtx->width; |
410 | myFrame->height = aCodecCtx->height; |
411 | |
412 | const Standard_Size aStride = aCodecCtx->width + 16 - (aCodecCtx->width % 16); |
413 | if (!myImgSrcRgba.InitZero (Image_PixMap::ImgRGBA, aCodecCtx->width, aCodecCtx->height, aStride)) |
414 | { |
415 | const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not allocate RGBA32 picture ") |
416 | + aCodecCtx->width+ "x" + aCodecCtx->height; |
417 | ::Message::DefaultMessenger()->Send (aMsg, Message_Fail); |
418 | return Standard_False; |
419 | } |
420 | |
421 | myScaleCtx = sws_getContext (aCodecCtx->width, aCodecCtx->height, AV_PIX_FMT_RGBA, |
422 | aCodecCtx->width, aCodecCtx->height, aCodecCtx->pix_fmt, |
423 | SWS_BICUBIC, NULL, NULL, NULL); |
424 | if (myScaleCtx == NULL) |
425 | { |
426 | const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not initialize the conversion context!"); |
427 | ::Message::DefaultMessenger()->Send (aMsg, Message_Fail); |
428 | return Standard_False; |
429 | } |
430 | return Standard_True; |
431 | #else |
432 | (void )theParams; |
433 | return Standard_False; |
434 | #endif |
435 | } |
436 | |
437 | //============================================================================= |
438 | //function : writeVideoFrame |
439 | //purpose : |
440 | //============================================================================= |
441 | Standard_Boolean Image_VideoRecorder::writeVideoFrame (const Standard_Boolean theToFlush) |
442 | { |
443 | #ifdef HAVE_FFMPEG |
444 | int aResAv = 0; |
445 | AVCodecContext* aCodecCtx = myVideoStream->codec; |
446 | if (!theToFlush) |
447 | { |
448 | uint8_t* aSrcData[4] = { (uint8_t*)myImgSrcRgba.ChangeData(), NULL, NULL, NULL }; |
449 | int aSrcLinesize[4] = { (int )myImgSrcRgba.SizeRowBytes(), 0, 0, 0 }; |
450 | sws_scale (myScaleCtx, |
451 | aSrcData, aSrcLinesize, |
452 | 0, aCodecCtx->height, |
453 | myFrame->data, myFrame->linesize); |
454 | } |
455 | |
456 | AVPacket aPacket; |
457 | memset (&aPacket, 0, sizeof(aPacket)); |
458 | av_init_packet (&aPacket); |
08f8a185 |
459 | { |
460 | // encode the image |
461 | myFrame->pts = myFrameCount; |
462 | int isGotPacket = 0; |
463 | aResAv = avcodec_encode_video2 (aCodecCtx, &aPacket, theToFlush ? NULL : myFrame, &isGotPacket); |
464 | if (aResAv < 0) |
465 | { |
466 | const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not encode video frame, ") + formatAvError (aResAv); |
467 | ::Message::DefaultMessenger()->Send (aMsg, Message_Fail); |
468 | return Standard_False; |
469 | } |
470 | |
471 | // if size is zero, it means the image was buffered |
472 | if (isGotPacket) |
473 | { |
474 | const AVRational& aTimeBase = aCodecCtx->time_base; |
475 | |
476 | // rescale output packet timestamp values from codec to stream timebase |
477 | aPacket.pts = av_rescale_q_rnd (aPacket.pts, aTimeBase, myVideoStream->time_base, AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); |
478 | aPacket.dts = av_rescale_q_rnd (aPacket.dts, aTimeBase, myVideoStream->time_base, AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); |
479 | aPacket.duration = av_rescale_q (aPacket.duration, aTimeBase, myVideoStream->time_base); |
480 | aPacket.stream_index = myVideoStream->index; |
481 | |
482 | // write the compressed frame to the media file |
483 | aResAv = av_interleaved_write_frame (myAVContext, &aPacket); |
484 | } |
485 | else |
486 | { |
487 | aResAv = 0; |
488 | } |
489 | } |
490 | |
491 | if (aResAv < 0) |
492 | { |
493 | const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not write video frame, ") + formatAvError (aResAv); |
494 | ::Message::DefaultMessenger()->Send (aMsg, Message_Fail); |
495 | return Standard_False; |
496 | } |
497 | |
498 | ++myFrameCount; |
499 | return Standard_True; |
500 | #else |
501 | (void)theToFlush; |
502 | return Standard_False; |
503 | #endif |
504 | } |