0a74aafdc6063e46fca083f662a19e566ab9f54a
[occt.git] / src / Image / Image_VideoRecorder.cxx
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
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
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   {
286     aCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
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   {
351     av_dict_set (&anOptions, "crf", "20", 0); // quality 4-63, 10 is normal
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);
459   if ((myAVContext->oformat->flags & AVFMT_RAWPICTURE) != 0
460    && !theToFlush)
461   {
462     // raw video case - directly store the picture in the packet
463     aPacket.flags        |= AV_PKT_FLAG_KEY;
464     aPacket.stream_index  = myVideoStream->index;
465     aPacket.data          = myFrame->data[0];
466     aPacket.size          = sizeof(AVPicture);
467
468     aResAv = av_interleaved_write_frame (myAVContext, &aPacket);
469   }
470   else
471   {
472     // encode the image
473     myFrame->pts = myFrameCount;
474     int isGotPacket = 0;
475     aResAv = avcodec_encode_video2 (aCodecCtx, &aPacket, theToFlush ? NULL : myFrame, &isGotPacket);
476     if (aResAv < 0)
477     {
478       const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not encode video frame, ") + formatAvError (aResAv);
479       ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
480       return Standard_False;
481     }
482
483     // if size is zero, it means the image was buffered
484     if (isGotPacket)
485     {
486       const AVRational& aTimeBase = aCodecCtx->time_base;
487
488       // rescale output packet timestamp values from codec to stream timebase
489       aPacket.pts          = av_rescale_q_rnd (aPacket.pts,      aTimeBase, myVideoStream->time_base, AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
490       aPacket.dts          = av_rescale_q_rnd (aPacket.dts,      aTimeBase, myVideoStream->time_base, AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
491       aPacket.duration     = av_rescale_q     (aPacket.duration, aTimeBase, myVideoStream->time_base);
492       aPacket.stream_index = myVideoStream->index;
493
494       // write the compressed frame to the media file
495       aResAv = av_interleaved_write_frame (myAVContext, &aPacket);
496     }
497     else
498     {
499       aResAv = 0;
500     }
501   }
502
503   if (aResAv < 0)
504   {
505     const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not write video frame, ") + formatAvError (aResAv);
506     ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
507     return Standard_False;
508   }
509
510   ++myFrameCount;
511   return Standard_True;
512 #else
513   (void)theToFlush;
514   return Standard_False;
515 #endif
516 }