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