0030612: Visualization - provide texture map with video as image source
[occt.git] / src / Image / Image_VideoRecorder.cxx
CommitLineData
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
37extern "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
63IMPLEMENT_STANDARD_RTTIEXT(Image_VideoRecorder, Standard_Transient)
64
65//=============================================================================
66//function : Constructor
67//purpose :
68//=============================================================================
69Image_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//=============================================================================
90Image_VideoRecorder::~Image_VideoRecorder()
91{
92 Close();
93}
94
95//=============================================================================
96//function : formatAvError
97//purpose :
98//=============================================================================
99TCollection_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//=============================================================================
114void 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//=============================================================================
166Standard_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//=============================================================================
236Standard_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//=============================================================================
300Standard_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//=============================================================================
441Standard_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}