65ed0383cbe62f35f94f87e152b81c4acac00021
[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::SendFail ("ViewerTest_VideoRecorder, could not deduce output format from file extension");
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       ::Message::SendFail (TCollection_AsciiString ("Error: could not open '") + theFileName + "', " + formatAvError (aResAv));
210       Close();
211       return Standard_False;
212     }
213   }
214
215   // write the stream header, if any
216   const int aResAv = avformat_write_header (myAVContext, NULL);
217   if (aResAv < 0)
218   {
219     ::Message::SendFail (TCollection_AsciiString ("Error: can not open output file '") + theFileName + "', " + formatAvError (aResAv));
220     Close();
221     return Standard_False;
222   }
223 #else
224   (void )theFileName;
225   (void )theParams;
226 #endif
227   return Standard_True;
228 }
229
230 //=============================================================================
231 //function : addVideoStream
232 //purpose  :
233 //=============================================================================
234 Standard_Boolean Image_VideoRecorder::addVideoStream (const Image_VideoParams& theParams,
235                                                       const Standard_Integer   theDefCodecId)
236 {
237   myFrameRate.num = theParams.FpsNum;
238   myFrameRate.den = theParams.FpsDen;
239
240 #ifdef HAVE_FFMPEG
241   // find the encoder
242   TCollection_AsciiString aCodecName;
243   if (!theParams.VideoCodec.IsEmpty())
244   {
245     myVideoCodec = avcodec_find_encoder_by_name (theParams.VideoCodec.ToCString());
246     aCodecName   = theParams.VideoCodec;
247   }
248   else
249   {
250     myVideoCodec = avcodec_find_encoder ((AVCodecID )theDefCodecId);
251     aCodecName   = avcodec_get_name     ((AVCodecID )theDefCodecId);
252   }
253   if (myVideoCodec == NULL)
254   {
255     ::Message::SendFail (TCollection_AsciiString ("Error: can not find encoder for ") + aCodecName);
256     return Standard_False;
257   }
258
259   const AVCodecID aCodecId = myVideoCodec->id;
260   myVideoStream = avformat_new_stream (myAVContext, myVideoCodec);
261   if (myVideoStream == NULL)
262   {
263     ::Message::SendFail ("Error: can not allocate stream");
264     return Standard_False;
265   }
266   myVideoStream->id = myAVContext->nb_streams - 1;
267
268   AVCodecContext* aCodecCtx = myVideoStream->codec;
269   aCodecCtx->codec_id = aCodecId;
270   // resolution must be a multiple of two
271   aCodecCtx->width    = theParams.Width;
272   aCodecCtx->height   = theParams.Height;
273   // Timebase is the fundamental unit of time (in seconds) in terms of which frame timestamps are represented.
274   // For fixed-fps content, timebase should be 1/framerate and timestamp increments should be identical to 1.
275   aCodecCtx->time_base.den = myFrameRate.num;
276   aCodecCtx->time_base.num = myFrameRate.den;
277   aCodecCtx->gop_size      = 12; // emit one intra frame every twelve frames at most
278
279   // some formats want stream headers to be separate
280   if (myAVContext->oformat->flags & AVFMT_GLOBALHEADER)
281   {
282     aCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
283   }
284   return Standard_True;
285 #else
286   (void )theParams;
287   (void )theDefCodecId;
288   return Standard_False;
289 #endif
290 }
291
292 //=============================================================================
293 //function : openVideoCodec
294 //purpose  :
295 //=============================================================================
296 Standard_Boolean Image_VideoRecorder::openVideoCodec (const Image_VideoParams& theParams)
297 {
298 #ifdef HAVE_FFMPEG
299   AVDictionary* anOptions = NULL;
300   AVCodecContext* aCodecCtx = myVideoStream->codec;
301
302   // setup default values
303   aCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
304   //av_dict_set (&anOptions, "threads", "auto", 0);
305   if (aCodecCtx->codec == avcodec_find_encoder_by_name ("mpeg2video"))
306   {
307     // just for testing, we also add B frames
308     aCodecCtx->max_b_frames = 2;
309     aCodecCtx->bit_rate = 6000000;
310   }
311   else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("mpeg4"))
312   {
313     //
314   }
315   else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("mjpeg"))
316   {
317     aCodecCtx->pix_fmt = AV_PIX_FMT_YUVJ420P;
318     aCodecCtx->qmin = aCodecCtx->qmax = 5;
319   }
320   else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("huffyuv"))
321   {
322     aCodecCtx->pix_fmt = AV_PIX_FMT_RGB24;
323   }
324   else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("png"))
325   {
326     aCodecCtx->pix_fmt = AV_PIX_FMT_RGB24;
327     aCodecCtx->compression_level = 9; // 0..9
328   }
329   else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("h264")
330         || aCodecCtx->codec == avcodec_find_encoder_by_name ("libx264"))
331   {
332     // use CRF (Constant Rate Factor) as best single-pass compressing method
333     av_dict_set (&anOptions, "crf",     "20",        0); // quality 18-28, 23 is default (normal), 18 is almost lossless
334     av_dict_set (&anOptions, "preset",  "slow",      0); // good compression (see also "veryslow", "ultrafast")
335
336     // live-capturing
337     //av_dict_set (&anOptions, "qp",     "0",         0); // instead of crf
338     //av_dict_set (&anOptions, "preset", "ultrafast", 0);
339
340     // compatibility with devices
341     //av_dict_set (&anOptions, "profile", "baseline",  0);
342     //av_dict_set (&anOptions, "level",   "3.0",       0);
343   }
344   else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("vp8")
345         || aCodecCtx->codec == avcodec_find_encoder_by_name ("vp9"))
346   {
347     av_dict_set (&anOptions, "crf", "20", 0); // quality 4-63, 10 is normal
348   }
349
350   // override defaults with specified options
351   if (!theParams.PixelFormat.IsEmpty())
352   {
353     const AVPixelFormat aPixFormat = av_get_pix_fmt (theParams.PixelFormat.ToCString());
354     if (aPixFormat == AV_PIX_FMT_NONE)
355     {
356       ::Message::SendFail (TCollection_AsciiString ("Error: unknown pixel format has been specified '") + theParams.PixelFormat + "'");
357       return Standard_False;
358     }
359
360     aCodecCtx->pix_fmt = aPixFormat;
361     for (Resource_DataMapOfAsciiStringAsciiString::Iterator aParamIter (theParams.VideoCodecParams);
362          aParamIter.More(); aParamIter.Next())
363     {
364       av_dict_set (&anOptions, aParamIter.Key().ToCString(), aParamIter.Value().ToCString(), 0);
365     }
366   }
367
368   // open codec
369   int aResAv = avcodec_open2 (aCodecCtx, myVideoCodec, &anOptions);
370   if (anOptions != NULL)
371   {
372     av_dict_free (&anOptions);
373   }
374   if (aResAv < 0)
375   {
376     ::Message::SendFail (TCollection_AsciiString ("Error: can not open video codec, ") + formatAvError (aResAv));
377     return Standard_False;
378   }
379
380   // allocate and init a re-usable frame
381   myFrame = av_frame_alloc();
382   if (myFrame == NULL)
383   {
384     ::Message::SendFail ("Error: can not allocate video frame");
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     ::Message::SendFail (TCollection_AsciiString ("Error: can not allocate picture ")
396                        + aCodecCtx->width+ "x" + aCodecCtx->height + ", " + formatAvError (aResAv));
397     return Standard_False;
398   }
399   // copy data and linesize picture pointers to frame
400   myFrame->format = aCodecCtx->pix_fmt;
401   myFrame->width  = aCodecCtx->width;
402   myFrame->height = aCodecCtx->height;
403
404   const Standard_Size aStride = aCodecCtx->width + 16 - (aCodecCtx->width % 16);
405   if (!myImgSrcRgba.InitZero (Image_Format_RGBA, aCodecCtx->width, aCodecCtx->height, aStride))
406   {
407     ::Message::SendFail (TCollection_AsciiString ("Error: can not allocate RGBA32 picture ")
408                        + aCodecCtx->width+ "x" + aCodecCtx->height);
409     return Standard_False;
410   }
411
412   myScaleCtx = sws_getContext (aCodecCtx->width, aCodecCtx->height, AV_PIX_FMT_RGBA,
413                                aCodecCtx->width, aCodecCtx->height, aCodecCtx->pix_fmt,
414                                SWS_BICUBIC, NULL, NULL, NULL);
415   if (myScaleCtx == NULL)
416   {
417     ::Message::SendFail ("Error: can not initialize the conversion context");
418     return Standard_False;
419   }
420   return Standard_True;
421 #else
422   (void )theParams;
423   return Standard_False;
424 #endif
425 }
426
427 //=============================================================================
428 //function : writeVideoFrame
429 //purpose  :
430 //=============================================================================
431 Standard_Boolean Image_VideoRecorder::writeVideoFrame (const Standard_Boolean theToFlush)
432 {
433 #ifdef HAVE_FFMPEG
434   int aResAv = 0;
435   AVCodecContext* aCodecCtx = myVideoStream->codec;
436   if (!theToFlush)
437   {
438     uint8_t* aSrcData[4]     = { (uint8_t*)myImgSrcRgba.ChangeData(),   NULL, NULL, NULL };
439     int      aSrcLinesize[4] = { (int     )myImgSrcRgba.SizeRowBytes(), 0,    0,    0    };
440     sws_scale (myScaleCtx,
441                aSrcData, aSrcLinesize,
442                0, aCodecCtx->height,
443                myFrame->data, myFrame->linesize);
444   }
445
446   AVPacket aPacket;
447   memset (&aPacket, 0, sizeof(aPacket));
448   av_init_packet (&aPacket);
449   {
450     // encode the image
451     myFrame->pts = myFrameCount;
452     int isGotPacket = 0;
453     aResAv = avcodec_encode_video2 (aCodecCtx, &aPacket, theToFlush ? NULL : myFrame, &isGotPacket);
454     if (aResAv < 0)
455     {
456       ::Message::SendFail (TCollection_AsciiString ("Error: can not encode video frame, ") + formatAvError (aResAv));
457       return Standard_False;
458     }
459
460     // if size is zero, it means the image was buffered
461     if (isGotPacket)
462     {
463       const AVRational& aTimeBase = aCodecCtx->time_base;
464
465       // rescale output packet timestamp values from codec to stream timebase
466       aPacket.pts          = av_rescale_q_rnd (aPacket.pts,      aTimeBase, myVideoStream->time_base, AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
467       aPacket.dts          = av_rescale_q_rnd (aPacket.dts,      aTimeBase, myVideoStream->time_base, AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
468       aPacket.duration     = av_rescale_q     (aPacket.duration, aTimeBase, myVideoStream->time_base);
469       aPacket.stream_index = myVideoStream->index;
470
471       // write the compressed frame to the media file
472       aResAv = av_interleaved_write_frame (myAVContext, &aPacket);
473     }
474     else
475     {
476       aResAv = 0;
477     }
478   }
479
480   if (aResAv < 0)
481   {
482     ::Message::SendFail (TCollection_AsciiString ("Error: can not write video frame, ") + formatAvError (aResAv));
483     return Standard_False;
484   }
485
486   ++myFrameCount;
487   return Standard_True;
488 #else
489   (void)theToFlush;
490   return Standard_False;
491 #endif
492 }