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