1 // Created by: Kirill GAVRILOV
2 // Copyright (c) 2019 OPEN CASCADE SAS
4 // This file is part of Open CASCADE Technology software library.
6 // This library is free software; you can redistribute it and/or modify it under
7 // the terms of the GNU Lesser General Public License version 2.1 as published
8 // by the Free Software Foundation, with special exception defined in the file
9 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
10 // distribution for complete text of the license and disclaimer of any warranty.
12 // Alternatively, this file may be used under the terms of Open CASCADE
13 // commercial license or contractual agreement.
15 // activate some C99 macros like UINT64_C in "stdint.h" which used by FFmpeg
16 #ifndef __STDC_CONSTANT_MACROS
17 #define __STDC_CONSTANT_MACROS
20 #include <Media_FormatContext.hxx>
22 #include <Message.hxx>
23 #include <Message_Messenger.hxx>
26 #include <Standard_WarningsDisable.hxx>
29 #include <libavformat/avformat.h>
31 #include <Standard_WarningsRestore.hxx>
34 IMPLEMENT_STANDARD_RTTIEXT(Media_FormatContext, Standard_Transient)
38 static const double THE_SECONDS_IN_HOUR = 3600.0;
39 static const double THE_SECONDS_IN_MINUTE = 60.0;
40 static const double THE_SECOND_IN_HOUR = 1.0 / THE_SECONDS_IN_HOUR;
41 static const double THE_SECOND_IN_MINUTE = 1.0 / THE_SECONDS_IN_MINUTE;
44 static const AVRational ST_AV_TIME_BASE_Q = {1, AV_TIME_BASE};
45 static const double ST_AV_TIME_BASE_D = av_q2d (ST_AV_TIME_BASE_Q);
47 //! Format framerate value.
48 static TCollection_AsciiString formatFps (double theVal)
50 const uint64_t aVal = uint64_t(theVal * 100.0 + 0.5);
54 Sprintf (aBuff, "%1.4f", theVal);
58 Sprintf (aBuff, "%3.2f", theVal);
60 else if (aVal % (100 * 1000))
62 Sprintf (aBuff, "%1.0f", theVal);
66 Sprintf (aBuff, "%1.0fk", theVal / 1000);
73 // =======================================================================
74 // function : FormatAVErrorDescription
76 // =======================================================================
77 TCollection_AsciiString Media_FormatContext::FormatAVErrorDescription (int theErrCodeAV)
81 memset (aBuff, 0, sizeof(aBuff));
82 if (av_strerror (theErrCodeAV, aBuff, 4096) != -1)
84 return TCollection_AsciiString (aBuff);
89 memset (aBuffW, 0, sizeof(aBuffW));
90 if (_wcserror_s (aBuffW, 4096, AVUNERROR(theErrCodeAV)) == 0)
92 return TCollection_AsciiString (aBuffW);
95 // MinGW has only thread-unsafe variant
96 char* anErrDesc = strerror (AVUNERROR(theErrCodeAV));
97 if (anErrDesc != NULL)
99 return TCollection_AsciiString (anErrDesc);
102 return TCollection_AsciiString (aBuff);
104 return TCollection_AsciiString ("AVError #") + theErrCodeAV;
108 // =======================================================================
109 // function : FormatUnitsToSeconds
111 // =======================================================================
112 double Media_FormatContext::FormatUnitsToSeconds (int64_t theTimeUnits)
115 return (theTimeUnits != AV_NOPTS_VALUE)
116 ? (ST_AV_TIME_BASE_D * theTimeUnits) : 0.0;
123 // =======================================================================
124 // function : UnitsToSeconds
126 // =======================================================================
127 double Media_FormatContext::UnitsToSeconds (const AVRational& theTimeBase,
128 int64_t theTimeUnits)
131 return (theTimeUnits != AV_NOPTS_VALUE)
132 ? (av_q2d (theTimeBase) * theTimeUnits) : 0.0;
140 // =======================================================================
141 // function : StreamUnitsToSeconds
143 // =======================================================================
144 double Media_FormatContext::StreamUnitsToSeconds (const AVStream& theStream,
145 int64_t theTimeUnits)
148 return UnitsToSeconds (theStream.time_base, theTimeUnits);
156 // =======================================================================
157 // function : SecondsToUnits
159 // =======================================================================
160 int64_t Media_FormatContext::SecondsToUnits (double theTimeSeconds)
163 return int64_t(theTimeSeconds / ST_AV_TIME_BASE_D);
165 (void )theTimeSeconds;
170 // =======================================================================
171 // function : SecondsToUnits
173 // =======================================================================
174 int64_t Media_FormatContext::SecondsToUnits (const AVRational& theTimeBase,
175 double theTimeSeconds)
178 return int64_t(theTimeSeconds / av_q2d (theTimeBase));
181 (void )theTimeSeconds;
186 // =======================================================================
187 // function : Media_FormatContext
189 // =======================================================================
190 int64_t Media_FormatContext::StreamSecondsToUnits (const AVStream& theStream,
191 double theTimeSeconds)
194 return SecondsToUnits (theStream.time_base, theTimeSeconds);
197 (void )theTimeSeconds;
202 // =======================================================================
203 // function : Media_FormatContext
205 // =======================================================================
206 Media_FormatContext::Media_FormatContext()
207 : myFormatCtx (NULL),
214 // =======================================================================
215 // function : ~Media_FormatContext
217 // =======================================================================
218 Media_FormatContext::~Media_FormatContext()
223 // =======================================================================
224 // function : NbSteams
226 // =======================================================================
227 unsigned int Media_FormatContext::NbSteams() const
230 return myFormatCtx->nb_streams;
236 // =======================================================================
239 // =======================================================================
240 const AVStream& Media_FormatContext::Stream (unsigned int theIndex) const
243 return *myFormatCtx->streams[theIndex];
246 throw Standard_ProgramError("Media_FormatContext::Stream()");
250 // =======================================================================
251 // function : OpenInput
253 // =======================================================================
254 bool Media_FormatContext::OpenInput (const TCollection_AsciiString& theInput)
257 const int avErrCode = avformat_open_input (&myFormatCtx, theInput.ToCString(), NULL, NULL);
260 Message::SendFail (TCollection_AsciiString ("FFmpeg: Couldn't open video file '") + theInput
261 + "'\nError: " + FormatAVErrorDescription (avErrCode));
266 // retrieve stream information
267 if (avformat_find_stream_info (myFormatCtx, NULL) < 0)
269 Message::SendFail (TCollection_AsciiString ("FFmpeg: Couldn't find stream information in '") + theInput + "'");
275 av_dump_format (myFormatCtx, 0, theInput.ToCString(), false);
279 myPtsStartBase = 0.0;
281 TCollection_AsciiString anExt = theInput;
283 if (anExt.EndsWith (".png")
284 || anExt.EndsWith (".jpg")
285 || anExt.EndsWith (".jpeg")
286 || anExt.EndsWith (".mpo")
287 || anExt.EndsWith (".bmp")
288 || anExt.EndsWith (".tif")
289 || anExt.EndsWith (".tiff"))
291 // black-list images to workaround non-zero duration
295 myDuration = FormatUnitsToSeconds (myFormatCtx->duration);
296 if (myFormatCtx->nb_streams != 0)
298 myPtsStartBase = 2.e+100;
299 for (unsigned int aStreamId = 0; aStreamId < myFormatCtx->nb_streams; ++aStreamId)
301 const AVStream& aStream = *myFormatCtx->streams[aStreamId];
302 myPtsStartBase = Min (myPtsStartBase, StreamUnitsToSeconds (aStream, aStream.start_time));
303 myDuration = Max (myDuration, StreamUnitsToSeconds (aStream, aStream.duration));
309 Message::SendFail ("Error: FFmpeg library is unavailable");
315 // =======================================================================
318 // =======================================================================
319 void Media_FormatContext::Close()
321 if (myFormatCtx != NULL)
324 avformat_close_input (&myFormatCtx);
325 //avformat_free_context (myFormatCtx);
330 // =======================================================================
331 // function : FormatTime
333 // =======================================================================
334 TCollection_AsciiString Media_FormatContext::FormatTime (double theSeconds)
336 double aSecIn = theSeconds;
337 unsigned int aHours = (unsigned int )(aSecIn * THE_SECOND_IN_HOUR);
338 aSecIn -= double(aHours) * THE_SECONDS_IN_HOUR;
339 unsigned int aMinutes = (unsigned int )(aSecIn * THE_SECOND_IN_MINUTE);
340 aSecIn -= double(aMinutes) * THE_SECONDS_IN_MINUTE;
341 unsigned int aSeconds = (unsigned int )aSecIn;
342 aSecIn -= double(aSeconds);
343 double aMilliSeconds = 1000.0 * aSecIn;
348 Sprintf (aBuffer, "%02u:%02u:%02u", aHours, aMinutes, aSeconds);
351 else if (aMinutes > 0)
353 Sprintf (aBuffer, "%02u:%02u", aMinutes, aSeconds);
356 else if (aSeconds > 0)
358 Sprintf (aBuffer, "%2u s", aSeconds);
362 return TCollection_AsciiString (aMilliSeconds) + " ms";
365 // =======================================================================
366 // function : FormatTimeProgress
368 // =======================================================================
369 TCollection_AsciiString Media_FormatContext::FormatTimeProgress (double theProgress,
372 double aSecIn1 = theProgress;
373 unsigned int aHours1 = (unsigned int )(aSecIn1 * THE_SECOND_IN_HOUR);
374 aSecIn1 -= double(aHours1) * THE_SECONDS_IN_HOUR;
375 unsigned int aMinutes1 = (unsigned int )(aSecIn1 * THE_SECOND_IN_MINUTE);
376 aSecIn1 -= double(aMinutes1) * THE_SECONDS_IN_MINUTE;
377 unsigned int aSeconds1 = (unsigned int )aSecIn1;
378 aSecIn1 -= double(aSeconds1);
380 double aSecIn2 = theDuration;
381 unsigned int aHours2 = (unsigned int )(aSecIn2 * THE_SECOND_IN_HOUR);
382 aSecIn2 -= double(aHours2) * THE_SECONDS_IN_HOUR;
383 unsigned int aMinutes2 = (unsigned int )(aSecIn2 * THE_SECOND_IN_MINUTE);
384 aSecIn2 -= double(aMinutes2) * THE_SECONDS_IN_MINUTE;
385 unsigned int aSeconds2 = (unsigned int )aSecIn2;
386 aSecIn2 -= double(aSeconds2);
392 Sprintf (aBuffer, "%02u:%02u:%02u / %02u:%02u:%02u", aHours1, aMinutes1, aSeconds1, aHours2, aMinutes2, aSeconds2);
395 Sprintf (aBuffer, "%02u:%02u / %02u:%02u", aMinutes1, aSeconds1, aMinutes2, aSeconds2);
399 // =======================================================================
400 // function : StreamInfo
402 // =======================================================================
403 TCollection_AsciiString Media_FormatContext::StreamInfo (unsigned int theIndex,
404 AVCodecContext* theCodecCtx) const
407 const AVStream& aStream = *myFormatCtx->streams[theIndex];
409 AVCodecContext* aCodecCtx = theCodecCtx;
410 if (aCodecCtx == NULL)
412 Standard_DISABLE_DEPRECATION_WARNINGS
413 aCodecCtx = aStream.codec;
414 Standard_ENABLE_DEPRECATION_WARNINGS
417 char aFrmtBuff[4096] = {};
418 avcodec_string (aFrmtBuff, sizeof(aFrmtBuff), aCodecCtx, 0);
419 TCollection_AsciiString aStreamInfo (aFrmtBuff);
421 if (aStream.sample_aspect_ratio.num && av_cmp_q(aStream.sample_aspect_ratio, aStream.codecpar->sample_aspect_ratio))
423 AVRational aDispAspectRatio;
424 av_reduce (&aDispAspectRatio.num, &aDispAspectRatio.den,
425 aStream.codecpar->width * int64_t(aStream.sample_aspect_ratio.num),
426 aStream.codecpar->height * int64_t(aStream.sample_aspect_ratio.den),
428 aStreamInfo = aStreamInfo + ", SAR " + aStream.sample_aspect_ratio.num + ":" + aStream.sample_aspect_ratio.den
429 + " DAR " + aDispAspectRatio.num + ":" + aDispAspectRatio.den;
432 if (aStream.codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
434 if (aStream.avg_frame_rate.den != 0 && aStream.avg_frame_rate.num != 0)
436 aStreamInfo += TCollection_AsciiString(", ") + formatFps (av_q2d (aStream.avg_frame_rate)) + " fps";
438 if (aStream.r_frame_rate.den != 0 && aStream.r_frame_rate.num != 0)
440 aStreamInfo += TCollection_AsciiString(", ") + formatFps (av_q2d (aStream.r_frame_rate)) + " tbr";
442 if (aStream.time_base.den != 0 && aStream.time_base.num != 0)
444 aStreamInfo += TCollection_AsciiString(", ") + formatFps(1 / av_q2d (aStream.time_base)) + " tbn";
446 if (aCodecCtx->time_base.den != 0 && aCodecCtx->time_base.num != 0)
448 aStreamInfo += TCollection_AsciiString(", ") + formatFps(1 / av_q2d (aCodecCtx->time_base)) + " tbc";
451 if (myDuration > 0.0)
453 aStreamInfo += TCollection_AsciiString(", duration: ") + FormatTime (myDuration);
459 return TCollection_AsciiString();
463 // =======================================================================
464 // function : ReadPacket
466 // =======================================================================
467 bool Media_FormatContext::ReadPacket (const Handle(Media_Packet)& thePacket)
469 if (thePacket.IsNull())
475 return av_read_frame (myFormatCtx, thePacket->ChangePacket()) >= 0;
481 // =======================================================================
482 // function : SeekStream
484 // =======================================================================
485 bool Media_FormatContext::SeekStream (unsigned int theStreamId,
490 const int aFlags = theToSeekBack ? AVSEEK_FLAG_BACKWARD : 0;
491 AVStream& aStream = *myFormatCtx->streams[theStreamId];
492 if ((aStream.disposition & AV_DISPOSITION_ATTACHED_PIC) != 0)
497 int64_t aSeekTarget = StreamSecondsToUnits (aStream, theSeekPts + StreamUnitsToSeconds (aStream, aStream.start_time));
498 bool isSeekDone = av_seek_frame (myFormatCtx, theStreamId, aSeekTarget, aFlags) >= 0;
500 // try 10 more times in backward direction to work-around huge duration between key frames
501 // will not work for some streams with undefined cur_dts (AV_NOPTS_VALUE)!!!
502 for (int aTries = 10; isSeekDone && theToSeekBack && aTries > 0 && (aStream.cur_dts > aSeekTarget); --aTries)
504 aSeekTarget -= StreamSecondsToUnits (aStream, 1.0);
505 isSeekDone = av_seek_frame (myFormatCtx, theStreamId, aSeekTarget, aFlags) >= 0;
512 TCollection_AsciiString aStreamType = aStream.codecpar->codec_type == AVMEDIA_TYPE_VIDEO
514 : (aStream.codecpar->codec_type == AVMEDIA_TYPE_AUDIO
517 Message::SendWarning (TCollection_AsciiString ("Error while seeking ") + aStreamType + " stream to "
518 + theSeekPts + " sec (" + (theSeekPts + StreamUnitsToSeconds (aStream, aStream.start_time)) + " sec)");
523 (void )theToSeekBack;
528 // =======================================================================
531 // =======================================================================
532 bool Media_FormatContext::Seek (double theSeekPts,
536 const int aFlags = theToSeekBack ? AVSEEK_FLAG_BACKWARD : 0;
537 int64_t aSeekTarget = SecondsToUnits (theSeekPts);
538 if (av_seek_frame (myFormatCtx, -1, aSeekTarget, aFlags) >= 0)
543 const char* aFileName =
544 #if(LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100))
547 myFormatCtx->filename;
550 Message::SendWarning (TCollection_AsciiString("Disaster! Seeking to ") + theSeekPts + " [" + aFileName + "] has failed.");
554 (void )theToSeekBack;