1 // Created by: Kirill GAVRILOV
2 // Copyright (c) 2019 OPEN CASCADE SAS
3 //
4 // This file is part of Open CASCADE Technology software library.
5 //
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.
11 //
12 // Alternatively, this file may be used under the terms of Open CASCADE
13 // commercial license or contractual agreement.
14 
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
18 #endif
19 
20 #ifdef _WIN32
21   #include <windows.h>
22 #endif
23 
24 #include <Media_PlayerContext.hxx>
25 
26 #include <Image_AlienPixMap.hxx>
27 #include <Media_BufferPool.hxx>
28 #include <Media_FormatContext.hxx>
29 #include <Media_CodecContext.hxx>
30 #include <Media_Scaler.hxx>
31 #include <Message.hxx>
32 #include <Message_Messenger.hxx>
33 #include <OSD.hxx>
34 
35 #ifdef HAVE_FFMPEG
36 #include <Standard_WarningsDisable.hxx>
37 extern "C"
38 {
39   #include <libavcodec/avcodec.h>
40   #include <libavformat/avformat.h>
41   #include <libavutil/imgutils.h>
42 };
43 #include <Standard_WarningsRestore.hxx>
44 #endif
45 
IMPLEMENT_STANDARD_RTTIEXT(Media_PlayerContext,Standard_Transient)46 IMPLEMENT_STANDARD_RTTIEXT(Media_PlayerContext, Standard_Transient)
47 
48 //================================================================
49 // Function : Media_PlayerContext
50 // Purpose  :
51 //================================================================
52 Media_PlayerContext::Media_PlayerContext (Media_IFrameQueue* theFrameQueue)
53 : myFrameQueue (theFrameQueue),
54   myThread (doThreadWrapper),
55   myWakeEvent (false),
56   myNextEvent (false),
57   myDuration  (0.0),
58   myToForceRgb(true),
59   myToShutDown(false),
60   mySeekTo    (0.0),
61   myPlayEvent (Media_PlayerEvent_NONE)
62 {
63   myThread.Run (this);
64 
65 #if defined(_WIN32) && !defined(OCCT_UWP)
66   // Adjust system timer
67   // By default Windows2K+ timer has ugly precision
68   // Thus - Sleep(1) may be long 14ms!
69   // We force best available precision to make Sleep() more adequate
70   // This affect whole system while running application!
71   TIMECAPS aTimeCaps = {0, 0};
72   if (timeGetDevCaps (&aTimeCaps, sizeof(aTimeCaps)) == TIMERR_NOERROR)
73   {
74     timeBeginPeriod (aTimeCaps.wPeriodMin);
75   }
76   else
77   {
78     timeBeginPeriod (1);
79   }
80 #endif
81 }
82 
83 //================================================================
84 // Function : ~Media_PlayerContext
85 // Purpose  :
86 //================================================================
~Media_PlayerContext()87 Media_PlayerContext::~Media_PlayerContext()
88 {
89   myToShutDown = Standard_True;
90   myWakeEvent.Set();
91   myThread.Wait();
92 
93 #if defined(_WIN32) && !defined(OCCT_UWP)
94   // restore timer adjustments
95   TIMECAPS aTimeCaps = {0, 0};
96   if (timeGetDevCaps (&aTimeCaps, sizeof(aTimeCaps)) == TIMERR_NOERROR)
97   {
98     timeEndPeriod (aTimeCaps.wPeriodMin);
99   }
100   else
101   {
102     timeEndPeriod (1);
103   }
104 #endif
105 }
106 
107 //================================================================
108 // Function : DumpFirstFrame
109 // Purpose  :
110 //================================================================
Handle(Media_Frame)111 Handle(Media_Frame) Media_PlayerContext::DumpFirstFrame (const TCollection_AsciiString& theSrcVideo,
112                                                          TCollection_AsciiString& theMediaInfo)
113 {
114   theMediaInfo.Clear();
115   Handle(Media_FormatContext) aFormatCtx = new Media_FormatContext();
116   if (!aFormatCtx->OpenInput (theSrcVideo))
117   {
118     return Handle(Media_Frame)();
119   }
120 
121   Handle(Media_CodecContext) aVideoCtx;
122 #ifdef HAVE_FFMPEG
123   for (unsigned int aStreamId = 0; aStreamId < aFormatCtx->NbSteams(); ++aStreamId)
124   {
125     const AVStream& aStream = aFormatCtx->Stream (aStreamId);
126     const AVMediaType aCodecType = aStream.codecpar->codec_type;
127     if (aCodecType == AVMEDIA_TYPE_VIDEO)
128     {
129       aVideoCtx = new Media_CodecContext();
130       if (!aVideoCtx->Init (aStream, aFormatCtx->PtsStartBase(), 1))
131       {
132         return Handle(Media_Frame)();
133       }
134 
135       theMediaInfo = aFormatCtx->StreamInfo (aStreamId, aVideoCtx->Context());
136       break;
137     }
138   }
139 #endif
140   if (aVideoCtx.IsNull())
141   {
142     Message::SendFail (TCollection_AsciiString ("FFmpeg: no video stream in '") + theSrcVideo + "'");
143     return Handle(Media_Frame)();
144   }
145 
146   Handle(Media_Packet) aPacket = new Media_Packet();
147   Handle(Media_Frame)  aFrame  = new Media_Frame();
148   for (;;)
149   {
150     if (!aFormatCtx->ReadPacket (aPacket))
151     {
152       Message::SendFail (TCollection_AsciiString ("FFmpeg: unable to read from '") + theSrcVideo + "'");
153       return Handle(Media_Frame)();
154     }
155     if (!aVideoCtx->CanProcessPacket (aPacket))
156     {
157       continue;
158     }
159 
160     if (aVideoCtx->SendPacket (aPacket)
161      && aVideoCtx->ReceiveFrame (aFrame))
162     {
163       break;
164     }
165   }
166   if (aFrame->IsEmpty()
167    || aFrame->SizeX() < 1
168    || aFrame->SizeY() < 1)
169   {
170     Message::SendFail (TCollection_AsciiString ("FFmpeg: unable to decode first video frame from '") + theSrcVideo + "'");
171     return Handle(Media_Frame)();
172   }
173   return aFrame;
174 }
175 
176 //================================================================
177 // Function : DumpFirstFrame
178 // Purpose  :
179 //================================================================
DumpFirstFrame(const TCollection_AsciiString & theSrcVideo,const TCollection_AsciiString & theOutImage,TCollection_AsciiString & theMediaInfo,int theMaxSize)180 bool Media_PlayerContext::DumpFirstFrame (const TCollection_AsciiString& theSrcVideo,
181                                           const TCollection_AsciiString& theOutImage,
182                                           TCollection_AsciiString& theMediaInfo,
183                                           int theMaxSize)
184 {
185   Handle(Media_Frame) aFrame = DumpFirstFrame (theSrcVideo, theMediaInfo);
186   if (aFrame.IsNull())
187   {
188     return false;
189   }
190 
191   Handle(Image_AlienPixMap) aPixMap = new Image_AlienPixMap();
192   int aResSizeX = aFrame->SizeX(), aResSizeY = aFrame->SizeY();
193   if (theMaxSize > 0)
194   {
195     if (aResSizeX > aResSizeY)
196     {
197       aResSizeX = theMaxSize;
198       aResSizeY = int((double(aFrame->SizeY()) / double(aFrame->SizeX())) * double(aResSizeX));
199     }
200     else
201     {
202       aResSizeY = theMaxSize;
203       aResSizeX = int((double(aFrame->SizeX()) / double(aFrame->SizeY())) * double(aResSizeY));
204     }
205   }
206   if (!aPixMap->InitZero (Image_Format_RGB, aResSizeX, aResSizeY))
207   {
208     Message::SendFail ("FFmpeg: Failed allocation of RGB frame (out of memory)");
209     return false;
210   }
211 
212   //Image_Format aFormat = aFrame->FormatFFmpeg2Occt (aFrame->Format());
213   //if (aFormat == Image_Format_UNKNOWN || theMaxSize > 0)
214   {
215     Handle(Media_Frame) anRgbFrame = new Media_Frame();
216     anRgbFrame->InitWrapper (aPixMap);
217 
218     Media_Scaler aScaler;
219     if (!aScaler.Convert (aFrame, anRgbFrame))
220     {
221       Message::SendFail (TCollection_AsciiString ("FFmpeg: unable to convert frame into RGB '") + theSrcVideo + "'");
222       return false;
223     }
224   }
225 
226   aPixMap->SetTopDown (true);
227   return aPixMap->Save (theOutImage);
228 }
229 
230 //================================================================
231 // Function : SetInput
232 // Purpose  :
233 //================================================================
SetInput(const TCollection_AsciiString & theInputPath,Standard_Boolean theToWait)234 void Media_PlayerContext::SetInput (const TCollection_AsciiString& theInputPath,
235                                     Standard_Boolean theToWait)
236 {
237   {
238     Standard_Mutex::Sentry aLock (myMutex);
239     if (theToWait)
240     {
241       myNextEvent.Reset();
242     }
243     myInputPath = theInputPath;
244     myPlayEvent = Media_PlayerEvent_NEXT;
245     myWakeEvent.Set();
246   }
247   if (theToWait)
248   {
249     myNextEvent.Wait();
250   }
251 }
252 
253 //================================================================
254 // Function : PlaybackState
255 // Purpose  :
256 //================================================================
PlaybackState(Standard_Boolean & theIsPaused,Standard_Real & theProgress,Standard_Real & theDuration)257 void Media_PlayerContext::PlaybackState (Standard_Boolean& theIsPaused,
258                                          Standard_Real& theProgress,
259                                          Standard_Real& theDuration)
260 {
261   Standard_Mutex::Sentry aLock (myMutex);
262   theIsPaused = !myTimer.IsStarted();
263   theProgress =  myTimer.ElapsedTime();
264   theDuration =  myDuration;
265 }
266 
267 //================================================================
268 // Function : pushPlayEvent
269 // Purpose  :
270 //================================================================
PlayPause(Standard_Boolean & theIsPaused,Standard_Real & theProgress,Standard_Real & theDuration)271 void Media_PlayerContext::PlayPause (Standard_Boolean& theIsPaused,
272                                      Standard_Real& theProgress,
273                                      Standard_Real& theDuration)
274 {
275   Standard_Mutex::Sentry aLock (myMutex);
276   theProgress = myTimer.ElapsedTime();
277   theDuration = myDuration;
278   if (myTimer.IsStarted())
279   {
280     pushPlayEvent (Media_PlayerEvent_PAUSE);
281     theIsPaused = true;
282   }
283   else
284   {
285     pushPlayEvent (Media_PlayerEvent_RESUME);
286     theIsPaused = false;
287   }
288 }
289 
290 //================================================================
291 // Function : Seek
292 // Purpose  :
293 //================================================================
Seek(Standard_Real thePosSec)294 void Media_PlayerContext::Seek (Standard_Real thePosSec)
295 {
296   Standard_Mutex::Sentry aLock (myMutex);
297   mySeekTo = thePosSec;
298   pushPlayEvent (Media_PlayerEvent_SEEK);
299 }
300 
301 //================================================================
302 // Function : pushPlayEvent
303 // Purpose  :
304 //================================================================
pushPlayEvent(Media_PlayerEvent thePlayEvent)305 void Media_PlayerContext::pushPlayEvent (Media_PlayerEvent thePlayEvent)
306 {
307   Standard_Mutex::Sentry aLock (myMutex);
308   myPlayEvent = thePlayEvent;
309   myWakeEvent.Set();
310 }
311 
312 //================================================================
313 // Function : popPlayEvent
314 // Purpose  :
315 //================================================================
popPlayEvent(Media_PlayerEvent & thePlayEvent,const Handle (Media_FormatContext)& theFormatCtx,const Handle (Media_CodecContext)& theVideoCtx,const Handle (Media_Frame)& theFrame)316 bool Media_PlayerContext::popPlayEvent (Media_PlayerEvent& thePlayEvent,
317                                         const Handle(Media_FormatContext)& theFormatCtx,
318                                         const Handle(Media_CodecContext)& theVideoCtx,
319                                         const Handle(Media_Frame)& theFrame)
320 {
321   if (myPlayEvent == Media_PlayerEvent_NONE)
322   {
323     thePlayEvent = Media_PlayerEvent_NONE;
324     return false;
325   }
326 
327   Standard_Mutex::Sentry aLock (myMutex);
328   thePlayEvent = myPlayEvent;
329   if (thePlayEvent == Media_PlayerEvent_PAUSE)
330   {
331     myTimer.Pause();
332   }
333   else if (thePlayEvent == Media_PlayerEvent_RESUME)
334   {
335     myTimer.Start();
336   }
337   else if (thePlayEvent == Media_PlayerEvent_SEEK)
338   {
339     if (!theFormatCtx.IsNull()
340      && !theVideoCtx.IsNull())
341     {
342       if (!theFormatCtx->SeekStream (theVideoCtx->StreamIndex(), mySeekTo, false))
343       {
344         theFormatCtx->Seek (mySeekTo, false);
345       }
346       theVideoCtx->Flush();
347       if (!theFrame.IsNull())
348       {
349         theFrame->Unref();
350       }
351       myTimer.Seek (mySeekTo);
352     }
353   }
354 
355   myPlayEvent = Media_PlayerEvent_NONE;
356   return thePlayEvent != Media_PlayerEvent_NONE;
357 }
358 
359 //! Returns nearest (greater or equal) aligned number.
getAligned(size_t theNumber,size_t theAlignment=32)360 static int getAligned (size_t theNumber,
361                        size_t theAlignment = 32)
362 {
363   return int(theNumber + theAlignment - 1 - (theNumber - 1) % theAlignment);
364 }
365 
366 //================================================================
367 // Function : receiveFrame
368 // Purpose  :
369 //================================================================
receiveFrame(const Handle (Media_Frame)& theFrame,const Handle (Media_CodecContext)& theVideoCtx)370 bool Media_PlayerContext::receiveFrame (const Handle(Media_Frame)& theFrame,
371                                         const Handle(Media_CodecContext)& theVideoCtx)
372 {
373   if (myFrameTmp.IsNull())
374   {
375     myFrameTmp = new Media_Frame();
376   }
377   if (!theVideoCtx->ReceiveFrame (myFrameTmp))
378   {
379     return false;
380   }
381 
382   theFrame->SetPts (myFrameTmp->Pts());
383   theFrame->SetPixelAspectRatio (myFrameTmp->PixelAspectRatio());
384 
385   Image_Format anOcctFmt = Media_Frame::FormatFFmpeg2Occt (myFrameTmp->Format());
386   if (anOcctFmt != Image_Format_UNKNOWN)
387   {
388     Media_Frame::Swap (theFrame, myFrameTmp);
389     return true;
390   }
391 #ifdef HAVE_FFMPEG
392   else if (!myToForceRgb
393         && (myFrameTmp->Format() == AV_PIX_FMT_YUV420P
394          || myFrameTmp->Format() == AV_PIX_FMT_YUVJ420P))
395   {
396     Media_Frame::Swap (theFrame, myFrameTmp);
397     return true;
398   }
399 #endif
400 
401   theFrame->Unref();
402   if (myFrameTmp->IsEmpty()
403    || myFrameTmp->Size().x() < 1
404    || myFrameTmp->Size().y() < 1)
405   {
406     theFrame->Unref();
407     return false;
408   }
409 
410   const Graphic3d_Vec2i aSize   = myFrameTmp->Size();
411   const Graphic3d_Vec2i aSizeUV = myFrameTmp->Size() / 2;
412   AVFrame* aFrame = theFrame->ChangeFrame();
413   if (myToForceRgb)
414   {
415     if (myBufferPools[0].IsNull())
416     {
417       myBufferPools[0] = new Media_BufferPool();
418     }
419 
420     const int aLineSize = getAligned (aSize.x() * 3);
421     const int aBufSize  = aLineSize * aSize.y();
422     if (!myBufferPools[0]->Init (aBufSize))
423     {
424       Message::SendFail ("FFmpeg: unable to allocate RGB24 frame buffer");
425       return false;
426     }
427 
428   #ifdef HAVE_FFMPEG
429     aFrame->buf[0] = myBufferPools[0]->GetBuffer();
430     if (aFrame->buf[0] == NULL)
431     {
432       theFrame->Unref();
433       Message::SendFail ("FFmpeg: unable to allocate RGB24 frame buffer");
434       return false;
435     }
436 
437     aFrame->format = AV_PIX_FMT_RGB24;
438     aFrame->width  = aSize.x();
439     aFrame->height = aSize.y();
440     aFrame->linesize[0] = aLineSize;
441     aFrame->data[0] = aFrame->buf[0]->data;
442   #else
443     (void )aFrame;
444   #endif
445   }
446   else
447   {
448     for (int aPlaneIter = 0; aPlaneIter < 3; ++aPlaneIter)
449     {
450       if (myBufferPools[aPlaneIter].IsNull())
451       {
452         myBufferPools[aPlaneIter] = new Media_BufferPool();
453       }
454     }
455 
456     const int aLineSize   = getAligned (aSize.x());
457     const int aLineSizeUV = getAligned (aSizeUV.x());
458     const int aBufSize    = aLineSize   * aSize.y();
459     const int aBufSizeUV  = aLineSizeUV * aSizeUV.y();
460     if (!myBufferPools[0]->Init (aBufSize)
461      || !myBufferPools[1]->Init (aBufSizeUV)
462      || !myBufferPools[2]->Init (aBufSizeUV))
463     {
464       Message::SendFail ("FFmpeg: unable to allocate YUV420P frame buffers");
465       return false;
466     }
467 
468   #ifdef HAVE_FFMPEG
469     aFrame->buf[0] = myBufferPools[0]->GetBuffer();
470     aFrame->buf[1] = myBufferPools[1]->GetBuffer();
471     aFrame->buf[2] = myBufferPools[2]->GetBuffer();
472     if (aFrame->buf[0] == NULL
473      || aFrame->buf[1] == NULL
474      || aFrame->buf[2] == NULL)
475     {
476       theFrame->Unref();
477       Message::SendFail ("FFmpeg: unable to allocate YUV420P frame buffers");
478       return false;
479     }
480 
481     aFrame->format = AV_PIX_FMT_YUV420P;
482     aFrame->width  = aSize.x();
483     aFrame->height = aSize.y();
484     aFrame->linesize[0] = aLineSize;
485     aFrame->linesize[1] = aLineSizeUV;
486     aFrame->linesize[2] = aLineSizeUV;
487     aFrame->data[0] = aFrame->buf[0]->data;
488     aFrame->data[1] = aFrame->buf[1]->data;
489     aFrame->data[2] = aFrame->buf[2]->data;
490   #endif
491   }
492 
493   if (myScaler.IsNull())
494   {
495     myScaler = new Media_Scaler();
496   }
497   if (!myScaler->Convert (myFrameTmp, theFrame))
498   {
499     return false;
500   }
501   myFrameTmp->Unref();
502   return true;
503 }
504 
505 //================================================================
506 // Function : doThreadLoop
507 // Purpose  :
508 //================================================================
doThreadLoop()509 void Media_PlayerContext::doThreadLoop()
510 {
511   // always set OCCT signal handler to catch signals if any;
512   // this is safe (for thread local handler) since the thread
513   // is owned by this class
514   OSD::SetThreadLocalSignal (OSD_SignalMode_Set, false);
515 
516   Handle(Media_Frame) aFrame;
517   bool wasSeeked = false;
518   for (;;)
519   {
520     myWakeEvent.Wait();
521     myWakeEvent.Reset();
522     if (myToShutDown)
523     {
524       return;
525     }
526 
527     TCollection_AsciiString anInput;
528     {
529       Standard_Mutex::Sentry aLock (myMutex);
530       std::swap (anInput, myInputPath);
531       if (myPlayEvent == Media_PlayerEvent_NEXT)
532       {
533         myPlayEvent = Media_PlayerEvent_NONE;
534       }
535     }
536     myNextEvent.Set();
537     if (anInput.IsEmpty())
538     {
539       continue;
540     }
541 
542     Handle(Media_FormatContext) aFormatCtx = new Media_FormatContext();
543     if (!aFormatCtx->OpenInput (anInput))
544     {
545       continue;
546     }
547 
548     Handle(Media_CodecContext) aVideoCtx;
549   #ifdef HAVE_FFMPEG
550     for (unsigned int aStreamId = 0; aStreamId < aFormatCtx->NbSteams(); ++aStreamId)
551     {
552       const AVStream& aStream = aFormatCtx->Stream (aStreamId);
553       const AVMediaType aCodecType = aStream.codecpar->codec_type;
554       if (aCodecType == AVMEDIA_TYPE_VIDEO)
555       {
556         aVideoCtx = new Media_CodecContext();
557         if (!aVideoCtx->Init (aStream, aFormatCtx->PtsStartBase(), 1))
558         {
559           aVideoCtx.Nullify();
560         }
561         else
562         {
563           break;
564         }
565       }
566     }
567   #endif
568     if (aVideoCtx.IsNull())
569     {
570       Message::SendFail (TCollection_AsciiString ("FFmpeg: no video stream in '") + anInput + "'");
571       continue;
572     }
573 
574     Handle(Media_Packet) aPacket = new Media_Packet();
575     Media_PlayerEvent aPlayEvent = Media_PlayerEvent_NONE;
576     {
577       Standard_Mutex::Sentry aLock (myMutex);
578       myTimer.Stop();
579       myTimer.Start();
580       myDuration = aFormatCtx->Duration();
581     }
582     if (!aFrame.IsNull())
583     {
584       aFrame->Unref();
585     }
586     const double anUploadDelaySec = 1.0 / 60.0 + 0.0001;
587     for (;;)
588     {
589       if (myToShutDown)
590       {
591         return;
592       }
593       else if (!aFormatCtx->ReadPacket (aPacket))
594       {
595         break;
596       }
597 
598       popPlayEvent (aPlayEvent, aFormatCtx, aVideoCtx, aFrame);
599       if (aPlayEvent == Media_PlayerEvent_NEXT)
600       {
601         break;
602       }
603       else if (aPlayEvent == Media_PlayerEvent_SEEK)
604       {
605         wasSeeked = true;
606       }
607 
608       bool isAccepted = false;
609       if (aVideoCtx->CanProcessPacket (aPacket))
610       {
611         isAccepted = true;
612         aVideoCtx->SendPacket (aPacket);
613       }
614       aPacket->Unref();
615       if (!isAccepted)
616       {
617         continue;
618       }
619       for (;;)
620       {
621         if (myToShutDown)
622         {
623           return;
624         }
625         else if (popPlayEvent (aPlayEvent, aFormatCtx, aVideoCtx, aFrame))
626         {
627           if (aPlayEvent == Media_PlayerEvent_NEXT)
628           {
629             break;
630           }
631           else if (aPlayEvent == Media_PlayerEvent_SEEK)
632           {
633             wasSeeked = true;
634           }
635         }
636 
637         if (aFrame.IsNull())
638         {
639           aFrame = myFrameQueue->LockFrame();
640           if (aFrame.IsNull())
641           {
642             OSD::MilliSecSleep (1);
643             continue;
644           }
645           aFrame->Unref();
646         }
647         if (aFrame->IsEmpty()
648         && !receiveFrame (aFrame, aVideoCtx))
649         {
650           break;
651         }
652 
653         const double aTime = myTimer.ElapsedTime() - anUploadDelaySec;
654         if (wasSeeked
655          || (aFrame->Pts() <= aTime
656           && myTimer.IsStarted()))
657         {
658           wasSeeked = false;
659           myFrameQueue->ReleaseFrame (aFrame);
660           aFrame.Nullify();
661           break;
662         }
663 
664         OSD::MilliSecSleep (1);
665       }
666       if (aPlayEvent == Media_PlayerEvent_NEXT)
667       {
668         break;
669       }
670     }
671   }
672 }
673