BitmapToolkit Scol plugin
MediaPlayer.cpp
Go to the documentation of this file.
1/*
2-----------------------------------------------------------------------------
3This source file is part of OpenSpace3D
4For the latest info, see http://www.openspace3d.com
5
6Copyright (c) 2016 I-maginer
7
8This program is free software; you can redistribute it and/or modify it under
9the terms of the GNU Lesser General Public License as published by the Free Software
10Foundation; either version 2 of the License, or (at your option) any later
11version.
12
13This program is distributed in the hope that it will be useful, but WITHOUT
14ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
17You should have received a copy of the GNU Lesser General Public License along with
18this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19Place - Suite 330, Boston, MA 02111-1307, USA, or go to
20http://www.gnu.org/copyleft/lesser.txt
21
22-----------------------------------------------------------------------------
23*/
24
25#include <scolPlugin.h>
26
27#include <sstream>
28#include <boost/lexical_cast.hpp>
29
30#include "MediaPlayer.h"
31#include "ScolConvert.h"
32
33#define QUEUE_BUFFER_SIZE 100
34#define QUEUE_BUFFERING_RATIO 10
35
36/* The av_err2str from FFmpeg uses C99, so we need to define our own
37 * because of VS2010.
38 */
39#if __STDC_VERSION__ < 199901L
40#undef av_err2str
41#define av_err2str(errnum) \
42 av_make_error_string( \
43 boost::array<char, AV_ERROR_MAX_STRING_SIZE>().c_array(), \
44 AV_ERROR_MAX_STRING_SIZE, \
45 errnum)
46#endif
47
48extern int MEDIAPLAYER_END_CB;
49extern int MEDIAPLAYER_LOADED_CB;
50
51const AVRational MediaPlayer::TIME_BASE = { 1, 1000 };
52// Redeclare AV_TIME_BASE_Q to work around VS2010's lack of C99 support
53const AVRational MediaPlayer::FF_AV_TIME_BASE_Q = { 1, AV_TIME_BASE };
54
55std::list<MediaPlayer*> MediaPlayer::players;
56
57bool MediaPlayer::globallyPaused = false;
58
59/*
60static int interrupt_cb(void *ctx)
61{
62 AVFormatContext* formatContext = reinterpret_cast<AVFormatContext*>(ctx);
63 return 0;
64}
65
66static const AVIOInterruptCB int_cb = { interrupt_cb, NULL };
67*/
68
70{
71 av_register_all();
72 avformat_network_init();
73 MMechostr(MSKDEBUG, avformat_configuration());
74}
75
77{
78 avformat_network_deinit();
79}
80
82{
83 if (pause && !globallyPaused)
84 {
85 for (auto it = players.begin(); it != players.end(); it++)
86 (*it)->Pause();
87
88 globallyPaused = true;
89 }
90 else if (!pause && globallyPaused)
91 {
92 for (auto it = players.begin(); it != players.end(); it++)
93 (*it)->Play();
94
95 globallyPaused = false;
96 }
97}
98
100{
101 for (auto it = players.begin(); it != players.end(); it++)
102 {
103 if (*it == player)
104 return true;
105 }
106
107 return false;
108}
109
111 mFile(""),
112 mFilePtr(NULL),
113 mIoContext(NULL),
114 mContext(NULL),
115 mVideoCtx(NULL),
116 mAudioCtx(NULL),
117 mVideoStream(MediaPlayer::STREAM_UNDEFINED),
118 mAudioStream(MediaPlayer::STREAM_UNDEFINED),
119 mUserW(MediaPlayer::VIDEO_SIZE_AUTO),
120 mUserH(MediaPlayer::VIDEO_SIZE_AUTO),
121 mFinalW(0), mFinalH(0),
122 mSourceW(0), mSourceH(0),
123 mSizeChanged(false),
124 mStopped(true),
125 mPaused(false),
126 mLoop(false),
127 mUpdated(false),
128 mVideoClock(0),
129 mVideoPts(AV_NOPTS_VALUE),
130 mSeekPts(AV_NOPTS_VALUE),
131 mSeekRequested(false),
132 mSeekInProgressAudio(false),
133 mSeekInProgressVideo(false),
134 mSeekCompletedAudio(true),
135 mIsLocal(true),
136 mSystemClock(),
137 mFrame(NULL),
138 mScalerCtx(NULL),
139 mAudioPktQ(QUEUE_BUFFER_SIZE * 16 * 1024),
140 mAudioFrameQ(10),
141 mAudioClock(),
142 mResampleCtx(0),
143 mOutChannelLayout(0),
144 mOutSampleFormat(AV_SAMPLE_FMT_NONE),
145 mOutSampleRate(0),
146 mVideoPktQ(QUEUE_BUFFER_SIZE * 256 * 1024)
147{
148 mFrame = av_frame_alloc();
149
150 if (mFrame == NULL)
151 throw std::runtime_error("Couldn't allocate frame");
152
153 mVideoBuffer = av_frame_alloc();
154 if (mVideoBuffer == NULL)
155 throw std::runtime_error("Couldn't allocate frame");
156
157
158 players.push_back(this);
159}
160
162{
163 if (mOpenThread.joinable())
164 mOpenThread.join();
165
166 Close();
167
168 av_frame_free(&mFrame);
169
170 if ((mVideoBuffer != 0) && (mVideoBuffer->data != 0))
171 avpicture_free((AVPicture*)mVideoBuffer);
172
173 av_frame_free(&mVideoBuffer);
174 mVideoBuffer = 0;
175
176 mFrameBuffer.release();
177 mFrameRetrieveBuffer.release();
178
179 players.remove(this);
180}
181
182void MediaPlayer::OpenFileThread(const std::string path)
183{
184 try
185 {
186 Close();
187
188 mFilePtr = fopen(path.c_str(), "rb");
189 if (mFilePtr == NULL)
190 {
191 //throw std::runtime_error(std::string("Could not open file ") + path);
192 MMechostr(MSKRUNTIME, "Could not open file %s", path.c_str());
193
194 //event
195 OBJpostEvent(MEDIAPLAYER_LOADED_CB, SCOL_PTR this, 0);
196 return;
197 }
198
199 mFile = path;
200
201 mIoContext = avio_alloc_context(NULL, 0, 0, this, FileRead, FileWrite, FileSeek);
202 if (mIoContext == NULL)
203 {
204 //throw std::runtime_error(std::string("Could not allocate AVIOContext for ") + path);
205 MMechostr(MSKRUNTIME, "Could not allocate AVIOContext for %s", path.c_str());
206
207 //event
208 OBJpostEvent(MEDIAPLAYER_LOADED_CB, SCOL_PTR this, 0);
209 return;
210 }
211
212 mContext = avformat_alloc_context();
213 if (mContext == NULL)
214 {
215 //throw std::runtime_error("Could not allocate AVFormatContext");
216 MMechostr(MSKRUNTIME, "Could not allocate AVFormatContext for %s", path.c_str());
217
218 //event
219 OBJpostEvent(MEDIAPLAYER_LOADED_CB, SCOL_PTR this, 0);
220 return;
221 }
222
223 //mContext->flags |= AVFMT_FLAG_NONBLOCK;
224 //mContext->interrupt_callback = int_cb;
225 //mContext->interrupt_callback.opaque = mContext;
226 mContext->pb = mIoContext;
227 OpenUrlThread(path, false);
228 }
229 catch (...)
230 {
231 Close();
232
233 //event
234 OBJpostEvent(MEDIAPLAYER_LOADED_CB, SCOL_PTR this, 0);
235 return;
236 }
237}
238
239void MediaPlayer::Open(std::string path)
240{
241 if (mOpenThread.joinable())
242 mOpenThread.join();
243
244 mOpenThread = boost::thread(&MediaPlayer::OpenFileThread, this, path);
245}
246
247void MediaPlayer::OpenUrlThread(std::string url, bool close)
248{
249 try
250 {
251 if (close)
252 Close();
253
254 AVDictionary *stream_opts = 0;
255 av_dict_set(&stream_opts, "rw_timeout", "8000000", 0); // in micros
256
257 if ((url.compare(0, 2, "//") == 0) || (url.compare(0, 2, "\\") == 0) || (url.compare(0, 4, "rtp:") == 0) || (url.compare(0, 4, "udp:") == 0) || (url.compare(0, 4, "tcp:") == 0) || (url.compare(0, 5, "http:") == 0) || (url.compare(0, 6, "https:") == 0))
258 {
259 av_dict_set(&stream_opts, "timeout", "8000000", 0); // in micros
260 mIsLocal = false;
261 }
262 else if (url.compare(0, 5, "rtsp:") == 0)
263 {
264 av_dict_set(&stream_opts, "stimeout", "8000000", 0); // in micros.
265 mIsLocal = false;
266 }
267 else
268 mIsLocal = true;
269
270 int ret = avformat_open_input(&mContext, url.c_str(), NULL, &stream_opts);
271 av_dict_free(&stream_opts);
272
273 if (ret < 0)
274 {
275 //throw std::runtime_error(std::string("Open input error: ") + av_err2str(ret));
276 MMechostr(MSKRUNTIME, "Open input error: %s", av_err2str(ret));
277
278 //event
279 OBJpostEvent(MEDIAPLAYER_LOADED_CB, SCOL_PTR this, 0);
280 return;
281 }
282
283 if (avformat_find_stream_info(mContext, NULL) < 0)
284 {
285 //throw std::runtime_error(std::string("Could not find stream info for ") + url);
286 MMechostr(MSKRUNTIME, "Could not find stream info for %s", av_err2str(ret));
287
288 //event
289 OBJpostEvent(MEDIAPLAYER_LOADED_CB, SCOL_PTR this, 0);
290 return;
291 }
292
293 mFile = url;
294 // Find out if the file has audio and/or video streams
295 if (!ListStreams(AVMEDIA_TYPE_VIDEO).empty())
297 else if (!ListStreams(AVMEDIA_TYPE_AUDIO).empty())
299 else
300 {
301 //throw std::runtime_error(std::string("File ") + url + " contains neither video or audio.");
302 MMechostr(MSKRUNTIME, "File %s contains neither video or audio.", url.c_str());
303
304 //event
305 OBJpostEvent(MEDIAPLAYER_LOADED_CB, SCOL_PTR this, 0);
306 return;
307 }
308 }
309 catch (...)
310 {
311 Close();
312
313 //event
314 OBJpostEvent(MEDIAPLAYER_LOADED_CB, SCOL_PTR this, 0);
315 return;
316 }
317
318 OBJpostEvent(MEDIAPLAYER_LOADED_CB, SCOL_PTR this, 1);
319}
320
321void MediaPlayer::OpenUrl(std::string url)
322{
323 if (mOpenThread.joinable())
324 mOpenThread.join();
325
326 mOpenThread = boost::thread(&MediaPlayer::OpenUrlThread, this, url, true);
327}
328
330{
331 Reset();
332
333 avformat_close_input(&mContext);
334 av_free(mIoContext);
335 mIoContext = NULL;
336
337 if (mFilePtr != NULL)
338 {
339 fclose(mFilePtr);
340 mFilePtr = NULL;
341 }
342 mFile = "";
343}
344
346{
347 return (mContext != NULL) && (HasAudio() || HasVideo());
348}
349
351{
352 if (mStopped)
353 return STOPPED;
354 if (mPaused)
355 return PAUSED;
356
357 return PLAYING;
358}
359
361{
362 return (mResampleCtx != NULL) && (mAudioStream != MediaPlayer::STREAM_UNDEFINED);
363}
364
366{
367 return mVideoStream != MediaPlayer::STREAM_UNDEFINED;
368}
369
371{
372 if (HasVideo())
373 return GetStreamDuration(mVideoStream);
374 else if (HasAudio())
375 return GetStreamDuration(mAudioStream);
376 else
377 return -1;
378}
379
381{
382 if (mContext == NULL)
383 throw std::runtime_error("No open file or stream");
384
385 // Try to get seekable info from AVIOContext
386 if (mContext->pb != NULL)
387 return mContext->pb->seekable != 0;
388 else
389 return GetLength() != -1;
390}
391
393{
394 // Guess : live streams have no duration.
395 return GetLength() == -1;
396}
397
398void MediaPlayer::GetSize(int& width, int& height)
399{
400 if (IsReady())
401 {
402 width = mFinalW;
403 height = mFinalH;
404 }
405 else
406 throw std::runtime_error("No open video stream");
407}
408
409void MediaPlayer::GetSourceSize(int& width, int& height)
410{
411 if (HasVideo())
412 {
413 width = mSourceW;
414 height = mSourceH;
415 }
416 else
417 throw std::runtime_error("No open video stream");
418}
419
420void MediaPlayer::SetSize(int width, int height)
421{
422 if ((width == mUserW) && (height == mUserH))
423 return;
424
425 boost::mutex::scoped_lock l(mSizeMutex);
426 mUserW = width;
427 mUserH = height;
428 // This triggers a size computation and buffer re-allocation in the decoding thread
429 mSizeChanged = true;
430}
431
432void MediaPlayer::SetAudioFormat(AudioFormats format, int sampleRate)
433{
434 uint64_t channelLayout = 0;
435 AVSampleFormat ffmpegFormat = AV_SAMPLE_FMT_NONE;
436
437 switch (format)
438 {
439 case AUDIO_8BIT_MONO:
440 channelLayout = AV_CH_LAYOUT_MONO;
441 ffmpegFormat = AV_SAMPLE_FMT_U8;
442 break;
444 channelLayout = AV_CH_LAYOUT_STEREO;
445 ffmpegFormat = AV_SAMPLE_FMT_U8;
446 break;
447 case AUDIO_16BIT_MONO:
448 channelLayout = AV_CH_LAYOUT_MONO;
449 ffmpegFormat = AV_SAMPLE_FMT_S16;
450 break;
452 channelLayout = AV_CH_LAYOUT_STEREO;
453 ffmpegFormat = AV_SAMPLE_FMT_S16;
454 break;
455 default:
456 throw std::runtime_error(std::string("Unsupported format: ") + boost::lexical_cast<std::string>(format));
457 }
458
459 SetAudioFormat(channelLayout, ffmpegFormat, sampleRate);
460}
461
462void MediaPlayer::SetAudioFormat(uint64_t channelLayout, AVSampleFormat format, int sampleRate)
463{
464 boost::mutex::scoped_lock l(mAudioMutex);
465
466 if (av_sample_fmt_is_planar(format))
467 throw std::logic_error("Planar formats are not supported");
468
469 if ((mOutSampleFormat == format) && (mOutSampleRate == sampleRate) && (mOutChannelLayout == channelLayout))
470 return;
471
472 // If there is no audio stream, the resampler will be configured the next time an audio stream is open
473 if (HasAudio())
474 {
475 AllocResampler(channelLayout, format, sampleRate, mAudioCtx);
476
477 // Seek back to the current position in order to trigger a frame queue flush
478 if (GetState() != STOPPED)
480 }
481
482 mOutSampleFormat = format;
483 mOutSampleRate = sampleRate;
484 mOutChannelLayout = channelLayout;
485}
486
487void MediaPlayer::Play(long startPosition)
488{
489 if (!IsReady())
490 throw std::logic_error("No open video stream");
491
492 if (mStopped)
493 {
494 // Make sure to join with the existing thread (in case Stop hasn't been called already)
495 Stop();
496
497 if (IsSeekable() && (startPosition >= 0))
498 this->SetCurrentTime(startPosition);
499
500 mStopped = false;
501 mDecodingThread = boost::thread(&MediaPlayer::DecodingThread, this);
502 mPauseCond.notify_all();
503 }
504 else if (mPaused)
505 {
506 if (IsLiveStream())
507 av_read_play(mContext);
508
509 mPaused = false;
510 mPauseCond.notify_all();
511 }
512}
513
515{
516 if (!IsReady())
517 throw std::logic_error("No open video stream");
518
519 if (!mStopped)
520 {
521 boost::mutex::scoped_lock l(mVideoMutex);
522
523 // If we're reading a livestream, it might have pause support. Fail if it doesn't.
524 if (IsLiveStream() && (av_read_pause(mContext) < 0))
525 throw std::runtime_error("Stream doesn't have pause support");
526
527 mSystemClock.Pause();
528 mAudioClock.Pause();
529 mPaused = true;
530 }
531}
532
534{
535 mStopped = true;
536
537 if (mPaused || mSystemClock.IsPaused())
538 {
539 mPaused = false;
540 mPauseCond.notify_all();
541 }
542
543 if (mDecodingThread.joinable())
544 {
545 // I don't want Stop to throw anything
546 try
547 {
548 mDecodingThread.join();
549 }
550 catch (...) {}
551 }
552
553 mUpdated = false;
554}
555
556void MediaPlayer::SetLoop(bool loop)
557{
558 mLoop = loop;
559}
560
562{
563 if (!IsReady())
564 throw std::logic_error("No open video stream");
565
566 if (!IsSeekable())
567 return;
568
569 long streamDuration = GetLength();
570
571 // Clamp the requested value to the current stream's bounds
572 time = std::max(0L, time);
573
574 if (streamDuration != -1)
575 time = std::min(streamDuration, time);
576
577 mSeekPts = time;
578
579 if (HasVideo())
580 mSeekInProgressVideo = true;
581 if (HasAudio())
582 mSeekInProgressAudio = true;
583
584 mSeekRequested = true;
585
586 // Restart the clock with the requested time
587 mSystemClock.Reset(time);
588}
589
591{
592 return (long)mSystemClock.Get();
593}
594
595bool MediaPlayer::GetFrame(ObjBitmap* scolBitmap)
596{
597 if (!IsReady() || !mUpdated || mFrameBuffer.empty())
598 return false;
599
600 //needed when pixel format is not the same also
601 cv::Mat imgdest = ConversionTools::ScolBitmapToMat(scolBitmap);
602 if (imgdest.empty())
603 return false;
604
605 {
606 boost::mutex::scoped_lock l(mVideoMutex);
607 mFrameBuffer.copyTo(mFrameRetrieveBuffer);
608 }
609
610 try
611 {
612 cv::resize(mFrameRetrieveBuffer, imgdest, imgdest.size(), 0, 0, cv::INTER_LINEAR);
613 }
614 catch (cv::Exception& e)
615 {
616 MMechostr(MSKDEBUG, "MediaPlayer::GetFrame error %s\n", e.what());
617 return false;
618 }
619
620 mUpdated = false;
621 return true;
622}
623
624bool MediaPlayer::GetFrame(cv::Mat &imgdest)
625{
626 if (!IsReady() || !mUpdated || imgdest.empty())
627 return false;
628
629 {
630 boost::mutex::scoped_lock l(mVideoMutex);
631 mFrameBuffer.copyTo(mFrameRetrieveBuffer);
632 }
633
634 try
635 {
636 cv::resize(mFrameRetrieveBuffer, imgdest, imgdest.size(), 0, 0, cv::INTER_LINEAR);
637 }
638 catch (cv::Exception& e)
639 {
640 MMechostr(MSKDEBUG, "MediaPlayer::GetFrame error %s\n", e.what());
641 return false;
642 }
643
644 mUpdated = false;
645 return true;
646}
647
649{
650 if (!IsReady() || !mUpdated || mFrameBuffer.empty())
651 return cv::Mat();
652
653 {
654 boost::mutex::scoped_lock l(mVideoMutex);
655 mFrameBuffer.copyTo(mFrameRetrieveBuffer);
656 }
657
658 mUpdated = false;
659 return mFrameRetrieveBuffer;
660}
661
662int MediaPlayer::SetVideoStream(int streamIndex)
663{
664 if (mContext == NULL)
665 throw std::logic_error("No opened video file");
666
667 streamIndex = GetActualStreamIndex(streamIndex, AVMEDIA_TYPE_VIDEO);
668
669 int stream = SetStream(AVMEDIA_TYPE_VIDEO, streamIndex);
670
671 State prevState = GetState();
672 Reset();
673 mVideoStream = stream;
674 mVideoCtx = mContext->streams[stream]->codec;
675
676 try
677 {
678 ComputeSize(mVideoCtx->width, mVideoCtx->height);
679 }
680 catch (std::exception&)
681 {
682 // Don't bother keeping anything open if allocations fail
683 Close();
684 throw;
685 }
686
687 // Select the best audio stream for the new video stream
688 try
689 {
691 }
692 catch (std::exception& e)
693 {
694 MMechostr(MSKRUNTIME, "No audio : %s\n", e.what());
695 }
696
697 if (prevState == PLAYING)
698 Play();
699
700 return stream;
701}
702
703int MediaPlayer::SetAudioStream(int streamIndex)
704{
705 if (mContext == NULL)
706 throw std::logic_error("No opened video file");
707
708 streamIndex = GetActualStreamIndex(streamIndex, AVMEDIA_TYPE_AUDIO);
709
710 int stream = SetStream(AVMEDIA_TYPE_AUDIO, streamIndex);
711 AVCodecContext* decContext = mContext->streams[stream]->codec;
712
713 if (!decContext->channel_layout)
714 decContext->channel_layout = av_get_default_channel_layout(decContext->channels);
715
716 // If output format is unspecified, set it to the stream's format
717 if ((mOutChannelLayout == 0) || (mOutSampleFormat == AV_SAMPLE_FMT_NONE) || (mOutSampleRate == 0))
718 {
719 mOutChannelLayout = decContext->channel_layout;
720 mOutSampleFormat = decContext->sample_fmt;
721 mOutSampleRate = decContext->sample_rate;
722 }
723
724 long now = this->GetCurrentTime();
725 State prevState = GetState();
726
727 // Stop and restart the video once the stream has been switched
728 Stop();
729
730 try
731 {
732 AllocResampler(mOutChannelLayout, mOutSampleFormat, mOutSampleRate, decContext);
733 }
734 catch (...)
735 {
736 avcodec_close(decContext);
737 throw;
738 }
739
740 // No errors ? Apply changes.
741 avcodec_close(mAudioCtx);
742
743 mAudioStream = stream;
744 mAudioCtx = decContext;
745
746 if (prevState != STOPPED)
747 {
748 Play(now);
749
750 if (prevState == PAUSED)
751 Pause();
752 }
753
754 return stream;
755}
756
757int MediaPlayer::SetStream(AVMediaType type, int index)
758{
759 AVCodec* decoder = NULL;
760 AVCodecContext* decContext = NULL;
761
762 // Try to find audio and subtitles related to the current video
763 int relatedStream = -1;
764 if ((type == AVMEDIA_TYPE_AUDIO) || (type == AVMEDIA_TYPE_SUBTITLE))
765 relatedStream = mVideoStream;
766
767 int stream = av_find_best_stream(mContext, type, index, relatedStream, NULL, 0);
768 if (stream < 0)
769 throw std::runtime_error("No stream of type " + std::string(av_get_media_type_string(type)));
770
771 // Stop here if we end up selecting an already selected stream.
772 if (stream == mAudioStream || stream == mVideoStream)
773 throw std::runtime_error("Stream already selected");
774
775 decContext = mContext->streams[stream]->codec;
776
777 decoder = avcodec_find_decoder(decContext->codec_id);
778 if (decoder == NULL)
779 throw std::runtime_error("Codec not found : " + std::string(mContext->streams[stream]->codec->codec->name));
780
781 int ret = avcodec_open2(decContext, decoder, NULL);
782 if (ret < 0)
783 throw std::runtime_error("FFmpeg couldn't open context for codec " + std::string(decoder->name));
784
785 return stream;
786}
787
788std::vector<std::string> MediaPlayer::ListStreams(AVMediaType type)
789{
790 std::vector<std::string> streamList;
791 int nbStreams = 0;
792
793 if (mContext == NULL)
794 return streamList;
795
796 for (unsigned int i = 0; i < mContext->nb_streams; i++)
797 {
798 AVStream* stream = mContext->streams[i];
799 if (stream->codec->codec_type != type)
800 continue;
801
802 std::string description("");
803
804 // Try to get stream name
805 AVDictionaryEntry* title = av_dict_get(stream->metadata, "title", NULL, 0);
806 if (title != NULL)
807 description += std::string(title->value) + " ";
808
809 // Try to get language info
810 if (type == AVMEDIA_TYPE_AUDIO)
811 {
812 AVDictionaryEntry* lang = av_dict_get(stream->metadata, "language", NULL, 0);
813 if (lang != NULL)
814 description += std::string("Language: ") + lang->value;
815 }
816
817 if (description.empty())
818 description = "(no info)";
819
820 std::stringstream desc;
821 desc << nbStreams << ". " << description;
822 streamList.push_back(desc.str());
823 nbStreams++;
824 }
825
826 return streamList;
827}
828
829int MediaPlayer::GetActualStreamIndex(int streamIndex, AVMediaType streamType)
830{
831 if (mContext == NULL)
832 throw std::logic_error("No open video file");
833
834 if (streamIndex < -1)
835 throw std::logic_error("Wrong stream index");
836
837 if (streamIndex == STREAM_UNDEFINED)
838 return STREAM_UNDEFINED;
839
840 for (unsigned int i = 0; i < mContext->nb_streams; i++)
841 {
842 if (mContext->streams[i]->codec->codec_type == streamType)
843 {
844 streamIndex--;
845 if (streamIndex < 0)
846 return i;
847 }
848 }
849
850 throw std::logic_error("Stream not found");
851}
852
853long MediaPlayer::GetStreamDuration(int index)
854{
855 if (!IsReady())
856 return 0;
857
858 if (mContext->streams[index]->duration != AV_NOPTS_VALUE)
859 return (long)av_rescale_q(mContext->streams[index]->duration, mContext->streams[index]->time_base, MediaPlayer::TIME_BASE);
860 else if (mContext->duration != AV_NOPTS_VALUE)
861 return (long)av_rescale_q(mContext->duration, FF_AV_TIME_BASE_Q, MediaPlayer::TIME_BASE);
862
863 return -1;
864}
865
866void MediaPlayer::DecodingThread()
867{
868 if (HasVideo())
869 mVideoThread = boost::thread(&MediaPlayer::VideoThread, this);
870 if (HasAudio())
871 mAudioThread = boost::thread(&MediaPlayer::AudioThread, this);
872
873 bool eofReached = false;
874 bool keepGoing = true;
875 int readTries = MAX_READ_ERRORS;
876 bool waitBuffer = !mIsLocal;
877
878 try
879 {
880 while (!mStopped && keepGoing)
881 {
882 // Keep the thread going to respond to seeking and hold the media end event until playback is actually over
883 long length = GetLength();
884 if (length != -1 && !mLoop && !waitBuffer)
885 keepGoing = (this->GetCurrentTime() < length) || !eofReached;
886 else
887 keepGoing = true;
888
889 if (waitBuffer || mSystemClock.IsPaused())
890 {
891 if ((mSystemClock.IsPaused() && !mPaused && (!waitBuffer || eofReached))
892 || (waitBuffer && (((HasVideo() && HasAudio()) && (mVideoPktQ.Size() >= (mVideoPktQ.MaxSize() / 100 * QUEUE_BUFFERING_RATIO)) && (mAudioPktQ.Size() >= (mAudioPktQ.MaxSize() / 100 * QUEUE_BUFFERING_RATIO)))
893 || (HasVideo() && (mVideoPktQ.Size() >= (mVideoPktQ.MaxSize() / 100 * QUEUE_BUFFERING_RATIO)))
894 || (HasAudio() && (mAudioPktQ.Size() >= (mAudioPktQ.MaxSize() / 100 * QUEUE_BUFFERING_RATIO))))))
895 {
896 mSystemClock.Resume();
897 mAudioClock.Resume();
898 waitBuffer = false;
899 mPauseCond.notify_all();
900 }
901 }
902
903 if (mSeekRequested)
904 {
905 // Using anything else than backward could put us after the requested PTS, which we don't want.
906 int seekFlag = AVSEEK_FLAG_BACKWARD;
907
908 // Seek to a position in the video stream if available, otherwise in the audio stream.
909 int stream = HasVideo() ? mVideoStream : mAudioStream;
910
911 int64_t seekPts = av_rescale_q(mSeekPts, MediaPlayer::TIME_BASE, mContext->streams[stream]->time_base);
912 // This will seek only to a keyframe, we need to do additional decoding afterwards to get to the exact position
913 int ret = av_seek_frame(mContext, stream, seekPts, seekFlag);
914 if (ret < 0)
915 {
916 MMechostr(MSKRUNTIME, ">> MediaPlayer: Seek error: %s\n", av_err2str(ret));
917 mSeekRequested = false;
918 mSeekInProgressAudio = mSeekInProgressVideo = false;
919 mSeekCompletedAudio = true;
920 mSeekPts = AV_NOPTS_VALUE;
921
922 continue;
923 }
924
925 mSeekRequested = false;
926 eofReached = false;
927
928 // Tell the audio and video threads to flush their packet queues
929 if (HasVideo())
930 {
931 AVPacket* flushVideo = new AVPacket;
932 flushVideo->data = PacketQueue::FLUSH_DATA;
933 flushVideo->pts = mSeekPts;
934 flushVideo->size = 0;
935 mVideoPktQ.Flush(flushVideo);
936 }
937 if (HasAudio())
938 {
939 AVPacket* flushAudio = new AVPacket;
940 flushAudio->data = PacketQueue::FLUSH_DATA;
941 flushAudio->pts = mSeekPts;
942 flushAudio->size = 0;
943 mAudioPktQ.Flush(flushAudio);
944 }
945 // Momentarily unpause the video/audio threads so that they can display the frame we want to seek to
946 if (mPaused || mSystemClock.IsPaused())
947 mPauseCond.notify_all();
948 }
949
950 if (eofReached || (mAudioPktQ.Size() >= mAudioPktQ.MaxSize()) || (mVideoPktQ.Size() >= mVideoPktQ.MaxSize()))
951 {
952 boost::this_thread::sleep_for(boost::chrono::milliseconds(1));
953 continue;
954 }
955
956 AVPacket* packet = new AVPacket;
957 //av_init_packet(packet);
958
959 int ret = av_read_frame(mContext, packet);
960 if (ret < 0)
961 {
962 SAFE_DELETE(packet);
963 if (ret == AVERROR_EOF && mLoop && IsSeekable())
964 {
965 // Wait for the video to be actually over before seeking to the beginning
966 if (this->GetCurrentTime() >= GetLength())
968 else
969 boost::this_thread::sleep_for(boost::chrono::microseconds(1));
970
971 continue;
972 }
973
974 // Quit now on error, continue until playback is done on EOF (so that the user can still seek)
975 if (ret != AVERROR_EOF)
976 {
977 readTries--;
978 MMechostr(MSKRUNTIME, ">> MediaPlayer: Read error: %s, %d tries left\n", av_err2str(ret), readTries);
979
980 if (readTries > 0)
981 continue;
982 else
983 break;
984 }
985
986 eofReached = true;
987 continue;
988 }
989 else if (!mIsLocal && keepGoing && !eofReached && !waitBuffer && ((HasVideo() && (mVideoPktQ.Size() == 0))
990 || (!HasVideo() && HasAudio() && (mAudioPktQ.Size() == 0))))
991 {
992 waitBuffer = true; // buffering
993 mSystemClock.Pause();
994 mAudioClock.Pause();
995 }
996
997 // Re-init try count on successful read
998 readTries = MAX_READ_ERRORS;
999
1000 if (packet->stream_index == mAudioStream)
1001 mAudioPktQ.Push(packet);
1002 else if (packet->stream_index == mVideoStream)
1003 mVideoPktQ.Push(packet);
1004 else
1005 {
1006 av_packet_unref(packet);
1007 SAFE_DELETE(packet);
1008 continue;
1009 }
1010
1011 //prevent CPU burn
1012 boost::this_thread::sleep_for(boost::chrono::microseconds(1));
1013 }
1014 }
1015 catch (std::exception& e)
1016 {
1017 MMechostr(MSKRUNTIME, ">> MediaPlayer : exception : %s\n", e.what());
1018 }
1019
1020 // trigger end event, if the video wasn't stopped prematurely
1021 if (eofReached && !mStopped)
1022 OBJpostEvent(MEDIAPLAYER_END_CB, SCOL_PTR this, 0);
1023
1024 // We need to set this here if the video was ended by EOF or error.
1025 mStopped = true;
1026
1027 // Terminate audio and video threads
1028 SendQuitPacket();
1029
1030 try
1031 {
1032 if (HasVideo() && mVideoThread.joinable())
1033 mVideoThread.join();
1034
1035 if (HasAudio() && mAudioThread.joinable())
1036 mAudioThread.join();
1037 }
1038 catch (...) {}
1039
1040 // mSystemClock.Reset(0);
1041
1042 MMechostr(MSKRUNTIME, ">> MediaPlayer : video ended\n");
1043}
1044
1045int64_t MediaPlayer::SynchronizeVideo(AVFrame *srcFrame, int64_t pts)
1046{
1047
1048 // If we have pts, set video clock to it
1049 if (pts != AV_NOPTS_VALUE)
1050 mVideoClock = pts;
1051 else
1052 pts = mVideoClock;
1053
1054 if (srcFrame->repeat_pict != 0)
1055 {
1056 AVRational fps = av_guess_frame_rate(mContext, mContext->streams[mVideoStream], srcFrame);
1057 // If we are repeating a frame, adjust clock accordingly
1058 // Note : I changed the original math (dranger) to the one from FFmpeg doc (they should be equivalent?).
1059 double frame_delay = srcFrame->repeat_pict / (2 * av_q2d(fps));
1060 // Convert to milliseconds
1061 mVideoClock += (int64_t)std::floor(frame_delay * MediaPlayer::TIME_BASE.den);
1062 }
1063
1064 return pts;
1065}
1066
1067void MediaPlayer::SendQuitPacket()
1068{
1069 // Tell the audio thread to stop
1070 if (HasAudio())
1071 {
1072 AVPacket* quitPacketAudio = new AVPacket;
1073 quitPacketAudio->data = PacketQueue::QUIT_DATA;
1074 quitPacketAudio->size = 0;
1075 mAudioPktQ.Flush(quitPacketAudio);
1076 }
1077
1078 // Tell the video thread to stop
1079 if (HasVideo())
1080 {
1081 AVPacket* quitPacketVideo = new AVPacket;
1082 quitPacketVideo->data = PacketQueue::QUIT_DATA;
1083 quitPacketVideo->size = 0;
1084 mVideoPktQ.Flush(quitPacketVideo);
1085 }
1086}
1087
1088void MediaPlayer::ComputeSize(int sourceW, int sourceH)
1089{
1090 boost::mutex::scoped_lock lsize(mSizeMutex);
1091 boost::mutex::scoped_lock l(mVideoMutex);
1092
1093 float ratio = (float)sourceW / (float)sourceH;
1094 // First, compute the final size
1096 {
1097 mFinalH = sourceH;
1098 mFinalW = sourceW;
1099 }
1100 else if (mUserH == MediaPlayer::VIDEO_SIZE_AUTO)
1101 {
1102 mFinalH = (int)((float)mUserW / ratio);
1103 mFinalW = mUserW;
1104 }
1105 else if (mUserW == MediaPlayer::VIDEO_SIZE_AUTO)
1106 {
1107 mFinalW = (int)((float)mUserH * ratio);
1108 mFinalH = mUserH;
1109 }
1110 else
1111 {
1112 mFinalW = mUserW;
1113 mFinalH = mUserH;
1114 }
1115
1116 avpicture_free((AVPicture*)mVideoBuffer);
1117 int ret = avpicture_alloc((AVPicture*)mVideoBuffer, MediaPlayer::DEST_PIXEL_FORMAT, mFinalW, mFinalH);
1118 mVideoBuffer->width = mFinalW;
1119 mVideoBuffer->height = mFinalH;
1120 mVideoBuffer->format = MediaPlayer::DEST_PIXEL_FORMAT;
1121 if (ret < 0)
1122 throw std::runtime_error("Couldn't allocate RGB frame");
1123
1124 sws_freeContext(mScalerCtx);
1125 mScalerCtx = sws_getContext(sourceW, sourceH, mVideoCtx->pix_fmt, mFinalW, mFinalH, MediaPlayer::DEST_PIXEL_FORMAT, SWS_FAST_BILINEAR, NULL, NULL, NULL);
1126
1127 mFrameBuffer = cv::Mat(mFinalH, mFinalW, CV_8UC3);
1128 mFrameRetrieveBuffer = cv::Mat(mFinalH, mFinalW, CV_8UC3);
1129 mUpdated = false;
1130
1131 if (mScalerCtx == NULL)
1132 throw std::runtime_error("Couldn't create swscale context");
1133
1134 mSourceW = sourceW;
1135 mSourceH = sourceH;
1136}
1137
1138void MediaPlayer::AllocResampler(uint64_t outChannelLayout, AVSampleFormat outSampleFormat, int outSampleRate, AVCodecContext* context)
1139{
1140 swr_free(&mResampleCtx);
1141
1142 if (av_sample_fmt_is_planar(outSampleFormat))
1143 throw std::runtime_error("Planar formats are not supported");
1144
1145 mResampleCtx = swr_alloc_set_opts(NULL, outChannelLayout, outSampleFormat, outSampleRate, context->channel_layout, context->sample_fmt, context->sample_rate, 0, NULL);
1146 if (mResampleCtx == NULL)
1147 throw std::runtime_error("Couldn't allocate resampler context");
1148
1149 int ret = swr_init(mResampleCtx);
1150 if (ret < 0)
1151 throw std::runtime_error(std::string("Couldn't initialize resampler context: ") + av_err2str(ret));
1152}
1153
1154void MediaPlayer::Reset()
1155{
1156 Stop();
1157
1158 // Video cleanup
1159 avcodec_close(mVideoCtx);
1160 mVideoCtx = NULL;
1161 sws_freeContext(mScalerCtx);
1162 mScalerCtx = NULL;
1163 mFrameBuffer.release();
1164 mVideoStream = MediaPlayer::STREAM_UNDEFINED;
1165
1166 // Audio cleanup
1167 avcodec_close(mAudioCtx);
1168 mAudioCtx = NULL;
1169
1170 swr_free(&mResampleCtx);
1171
1172 mAudioStream = MediaPlayer::STREAM_UNDEFINED;
1173}
1174
1175void MediaPlayer::VideoThread()
1176{
1177 int64_t seekPts = AV_NOPTS_VALUE;
1178 // Timestamp (in ms) at which the last video frame was presented
1179 int64_t presentationTime = 0;
1180 bool skipNext = false;
1181 while (true)
1182 {
1183 AVPacket* packet = mVideoPktQ.Pop();
1184 if (packet->data == PacketQueue::FLUSH_DATA)
1185 {
1186 avcodec_flush_buffers(mVideoCtx);
1187 seekPts = packet->pts;
1188 SAFE_DELETE(packet);
1189 presentationTime = 0;
1190 mSeekInProgressVideo = false;
1191 continue;
1192 }
1193 else if (packet->data == PacketQueue::QUIT_DATA)
1194 {
1195 SAFE_DELETE(packet);
1196 break;
1197 }
1198
1199 //skip outdated packet
1200 //if (HasAudio() && ((av_rescale_q(packet->pts, mContext->streams[mVideoStream]->time_base, MediaPlayer::TIME_BASE)) < (mAudioClock.Get() + MediaPlayer::AV_SYNC_THRESHOLD)))
1201 //{
1202 // av_packet_unref(packet);
1203 // SAFE_DELETE(packet);
1204 // skipNext = true;
1205 // continue;
1206 //}
1207
1208 int ret = avcodec_send_packet(mVideoCtx, packet);
1209 av_packet_unref(packet);
1210 SAFE_DELETE(packet);
1211 while (ret >= 0)
1212 {
1213 ret = avcodec_receive_frame(mVideoCtx, mFrame);
1214 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
1215 {
1216 break;
1217 }
1218 else if (ret < 0)
1219 {
1220 MMechostr(MSKDEBUG, ">> MediaPlayer: Decoding error: %s\n", av_err2str(ret));
1221 break;
1222 }
1223
1224 if (ret < 0)
1225 // Decode packets until we have a whole decoded frame
1226 continue;
1227 else if (mSizeChanged || mFrame->width != mSourceW || mFrame->height != mSourceH)
1228 {
1229 ComputeSize(mFrame->width, mFrame->height);
1230 mSizeChanged = false;
1231 }
1232
1233 // Synchronization
1234 int64_t pts = av_frame_get_best_effort_timestamp(mFrame);
1235 if (pts != AV_NOPTS_VALUE)
1236 pts = av_rescale_q(pts, mContext->streams[mVideoStream]->time_base, MediaPlayer::TIME_BASE);
1237
1238 pts = SynchronizeVideo(mFrame, pts);
1239
1240 // If a seek has been requested, decode until we get to the exact position
1241 if (seekPts != AV_NOPTS_VALUE)
1242 {
1243 if (pts < seekPts)
1244 continue;
1245 else
1246 seekPts = AV_NOPTS_VALUE;
1247 }
1248
1249 //now wait for a real frame not a predicted one
1250 if (skipNext && (mFrame->pict_type == AV_PICTURE_TYPE_I))
1251 {
1252 skipNext = false;
1253 continue;
1254 }
1255
1256 // Write the final image to buffer
1257 sws_scale(mScalerCtx, mFrame->data, mFrame->linesize, 0, mFrame->height, mVideoBuffer->data, mVideoBuffer->linesize);
1258
1259 // Sync to audio
1260 if (HasAudio())
1261 {
1262 // Audio not ready yet (still seeking)
1263 while (!mSeekInProgressVideo && !mSeekCompletedAudio && !mSystemClock.IsPaused() && !mPaused && !mStopped)
1264 boost::this_thread::yield();
1265
1266 int64_t clock = mAudioClock.Get();
1267
1268 // Handle any ninja seek request
1269 if (mSeekInProgressVideo)
1270 continue;
1271
1272 if (pts > (clock + MediaPlayer::AV_SYNC_THRESHOLD) && !mSystemClock.IsPaused() && !mPaused && !mStopped)
1273 {
1274 // secure if the audio didn't start yet
1275 if ((pts - clock) > 250)
1276 boost::this_thread::sleep_for(boost::chrono::milliseconds(250));
1277 else
1278 boost::this_thread::sleep_for(boost::chrono::milliseconds(pts - clock));
1279 }
1280 }
1281 else if (presentationTime != 0 && !mSystemClock.IsPaused())
1282 {
1283 int64_t elapsed = Clock::GetSysTime() - presentationTime;
1284 if (((pts - mVideoPts) - elapsed) > 250)
1285 boost::this_thread::sleep_for(boost::chrono::milliseconds(250));
1286 else
1287 boost::this_thread::sleep_for(boost::chrono::milliseconds((pts - mVideoPts) - elapsed));
1288 }
1289
1290 {
1291 boost::mutex::scoped_lock l(mVideoMutex);
1292
1293 if (mVideoBuffer->linesize[0] != mFrameBuffer.step)
1294 mFrameBuffer = cv::Mat(mVideoBuffer->height, mVideoBuffer->width, CV_8UC3, mVideoBuffer->data[0], mVideoBuffer->linesize[0]);
1295 else
1296 memcpy(mFrameBuffer.data, mVideoBuffer->data[0], mVideoBuffer->linesize[0] * mVideoBuffer->height);
1297
1298 mVideoPts = pts;
1299 mUpdated = true;
1300
1301 if ((mPaused || mSystemClock.IsPaused()) && !mStopped && !mSeekInProgressVideo)
1302 {
1303 mPauseCond.wait(l);
1304 presentationTime = 0;
1305 }
1306 else
1307 presentationTime = Clock::GetSysTime();
1308 }
1309 }
1310
1311 //prevent CPU burn
1312 boost::this_thread::sleep_for(boost::chrono::microseconds(1));
1313 }
1314}
1315
1316MediaPlayer::AudioFrame::AudioFrame() :
1317 data(),
1318 size(0),
1319 pts(AV_NOPTS_VALUE)
1320{}
1321
1322MediaPlayer::AudioFrame::AudioFrame(const AudioFrame& frame) :
1323 size(frame.size),
1324 pts(frame.pts)
1325{
1326 data.reset(new char[size]);
1327 memcpy(data.get(), frame.data.get(), size);
1328}
1329
1330MediaPlayer::AudioFrame::AudioFrame(AudioFrame&& frame) :
1331 data(std::move(frame.data)),
1332 size(frame.size),
1333 pts(frame.pts)
1334{}
1335
1336MediaPlayer::AudioFrame& MediaPlayer::AudioFrame::operator=(AudioFrame&& frame)
1337{
1338 size = frame.size;
1339 pts = frame.pts;
1340 data = std::move(frame.data);
1341 return *this;
1342}
1343
1344void MediaPlayer::AudioThread()
1345{
1346 int64_t firstPts = AV_NOPTS_VALUE;
1347
1348 AVFrame* frame = av_frame_alloc();
1349 if (frame == NULL)
1350 throw std::runtime_error("Out of memory");
1351
1352 int64_t seekPts = AV_NOPTS_VALUE;
1353
1354 while (true)
1355 {
1356 AVPacket* packet = mAudioPktQ.Pop();
1357 if (packet->data == PacketQueue::FLUSH_DATA)
1358 {
1359 avcodec_flush_buffers(mAudioCtx);
1360 seekPts = packet->pts;
1361 SAFE_DELETE(packet);
1362
1363 boost::mutex::scoped_lock l(mAudioMutex);
1364 mAudioFrameQ.clear();
1365 mSeekCompletedAudio = false;
1366 continue;
1367 }
1368 else if (packet->data == PacketQueue::QUIT_DATA)
1369 {
1370 SAFE_DELETE(packet);
1371
1372 boost::mutex::scoped_lock l(mAudioMutex);
1373 mAudioFrameQ.clear();
1374 break;
1375 }
1376
1377 int ret = avcodec_send_packet(mAudioCtx, packet);
1378 av_packet_unref(packet);
1379 SAFE_DELETE(packet);
1380
1381 while (ret >= 0 && !mStopped)
1382 {
1383 ret = avcodec_receive_frame(mAudioCtx, frame);
1384 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
1385 {
1386 break;
1387 }
1388 else if (ret < 0)
1389 {
1390 MMechostr(MSKDEBUG, ">> MediaPlayer: Decoding error: %s\n", av_err2str(ret));
1391 break;
1392 }
1393
1394 if (ret >= 0)
1395 {
1396 int64_t pts = av_frame_get_best_effort_timestamp(frame);
1397 if (pts == AV_NOPTS_VALUE)
1398 continue;
1399
1400 pts = av_rescale_q(pts, mContext->streams[mAudioStream]->time_base, MediaPlayer::TIME_BASE);
1401
1402 if (firstPts == AV_NOPTS_VALUE)
1403 {
1404 firstPts = pts;
1405 mAudioClock.Reset(firstPts);
1406 }
1407
1408 if (seekPts != AV_NOPTS_VALUE)
1409 {
1410 if (pts < seekPts)
1411 continue;
1412 else
1413 {
1414 seekPts = AV_NOPTS_VALUE;
1415 mSeekInProgressAudio = false;
1416 }
1417 }
1418
1419 AudioFrame destFrame;
1420 // Resample if needed
1421 if (mResampleCtx != NULL)
1422 {
1423 //int outSamples = av_rescale_rnd(frame->nb_samples, mAudioCtx->sample_rate, mAudioCtx->sample_rate, AV_ROUND_UP);
1424
1425 int outSamples = swr_get_out_samples(mResampleCtx, frame->nb_samples);
1426 if (outSamples < 0)
1427 continue; // throw std::runtime_error(std::string("swr_get_out_samples : ") + av_err2str(outSamples));
1428
1429 int outSamplesLines = 0;
1430 int buffSize = av_samples_get_buffer_size(&outSamplesLines, av_get_channel_layout_nb_channels(mOutChannelLayout), outSamples, mOutSampleFormat, 1);
1431
1432 destFrame.data.reset(new char[buffSize]);
1433 char* convertedData = destFrame.data.get();
1434
1435 // The actual number of output samples could be different from the upper bound given earlier
1436 int ilen = swr_convert(mResampleCtx, (uint8_t**)&convertedData, outSamples, (const uint8_t**)frame->extended_data, frame->nb_samples);
1437 //avresample_convert(mResampleCtx, (uint8_t**)&convertedData, 0, outSamples, (uint8_t **)frame->extended_data, 0, frame->nb_samples);
1438
1439 if (ilen < 0)
1440 continue; //throw std::runtime_error(std::string("Error converting audio : ") + av_err2str(ilen));
1441
1442 int outBytes = av_samples_get_buffer_size(&outSamplesLines, av_get_channel_layout_nb_channels(mOutChannelLayout), ilen, mOutSampleFormat, 1);
1443
1444 if (outBytes < 0 || outBytes > buffSize)
1445 continue; //throw std::runtime_error(std::string("Error converting audio : Bad buffer size"));
1446
1447 destFrame.size = outBytes;
1448 }
1449 else
1450 {
1451 destFrame.size = frame->nb_samples * av_get_channel_layout_nb_channels(mOutChannelLayout) * av_get_bytes_per_sample(mOutSampleFormat);
1452 destFrame.data.reset(new char[destFrame.size]);
1453 memcpy(destFrame.data.get(), frame->data[0], destFrame.size);
1454 }
1455 destFrame.pts = pts;
1456
1457 // When the queue is full, pop the frame at the head if it's obsolete
1458 while (mAudioFrameQ.full() && !mStopped && !mPaused && !mSystemClock.IsPaused() && !mSeekInProgressAudio)
1459 boost::this_thread::yield();
1460
1461 {
1462 boost::mutex::scoped_lock l(mAudioMutex);
1463 if ((mPaused || mSystemClock.IsPaused()) && !mStopped && !mSeekInProgressAudio)
1464 mPauseCond.wait(l);
1465
1466 if (!mSeekInProgressAudio)
1467 {
1468 mAudioFrameQ.push_back(std::move(destFrame));
1469 }
1470 }
1471 }
1472 }
1473
1474 //prevent CPU burn
1475 boost::this_thread::sleep_for(boost::chrono::microseconds(1));
1476 }
1477
1478 av_frame_free(&frame);
1479}
1480
1481int MediaPlayer::GetAudioThreaded(char* destBuffer, size_t length)
1482{
1483 size_t retrieved = 0;
1484 int64_t firstPts = AV_NOPTS_VALUE;
1485 if (mSeekInProgressAudio || mAudioClock.IsPaused())
1486 return 0;
1487
1488 boost::mutex::scoped_lock l(mAudioMutex);
1489 while (!mAudioFrameQ.empty() && (retrieved < length) && !mStopped)
1490 {
1491 const AudioFrame frame = mAudioFrameQ.front();
1492
1493 if (firstPts == AV_NOPTS_VALUE)
1494 firstPts = frame.pts;
1495
1496 // We only copy whole frames, give up if frame is too large
1497 if (frame.size > length)
1498 {
1499 mAudioFrameQ.pop_front();
1500 break;
1501 }
1502
1503 if (retrieved + frame.size > length)
1504 break;
1505
1506 memcpy(destBuffer + retrieved, frame.data.get(), frame.size);
1507 retrieved += frame.size;
1508 mAudioFrameQ.pop_front();
1509 }
1510
1511 if (firstPts != AV_NOPTS_VALUE)
1512 SynchronizeAudio(firstPts);
1513
1514 return (int)retrieved;
1515}
1516
1517void MediaPlayer::SynchronizeAudio(int64_t pts)
1518{
1519 /*
1520 * The audio clock should keep track of the current audio playback position.
1521 * If the given pts is ahead of the estimated playback time (= buffering), don't update it.
1522 * The only time when we update is if audio playback is too slow, or when seeking.
1523 */
1524 if (!mSeekCompletedAudio)
1525 {
1526 mAudioClock.Reset(pts);
1527 mSeekCompletedAudio = true;
1528 }
1529}
1530
1531int MediaPlayer::FileRead(void* player, uint8_t* buf, int buf_size)
1532{
1533 return fread(buf, 1, buf_size, ((MediaPlayer*)player)->mFilePtr);
1534}
1535
1536int MediaPlayer::FileWrite(void* player, uint8_t* buf, int buf_size)
1537{
1538 return fwrite(buf, 1, buf_size, ((MediaPlayer*)player)->mFilePtr);
1539}
1540
1541int64_t MediaPlayer::FileSeek(void* player, int64_t offset, int whence)
1542{
1543 MediaPlayer* vp = (MediaPlayer*)player;
1544 // Disable "forced seek", we shouldn't need it
1545 whence &= ~AVSEEK_FORCE;
1546
1547 if (whence == AVSEEK_SIZE)
1548 {
1549 int64_t prev = ftell(vp->mFilePtr);
1550 fseek(vp->mFilePtr, 0L, SEEK_END);
1551 int64_t sz = (int64_t)ftell(vp->mFilePtr);
1552 fseek(vp->mFilePtr, (long)prev, SEEK_SET);
1553 return sz;
1554 }
1555
1556 if (fseek(vp->mFilePtr, (long)offset, whence) != 0)
1557 return -1;
1558
1559 return ftell(vp->mFilePtr);
1560}
1561
1562
1564
1565int64_t MediaPlayer::Clock::GetSysTime()
1566{
1567 return cv::getTickCount() / (int64_t)(cv::getTickFrequency() / MediaPlayer::TIME_BASE.den);
1568}
1569
1570MediaPlayer::Clock::Clock() :
1571 mInitialTime(0),
1572 mPauseTime(0),
1573 mSystemTime(0),
1574 mRunning(false),
1575 mMutex()
1576{}
1577
1578int64_t MediaPlayer::Clock::Get()
1579{
1580 boost::mutex::scoped_lock l(mMutex);
1581
1582 if (!mRunning)
1583 return mPauseTime;
1584
1585 return GetSysTime() - mSystemTime + mInitialTime;
1586}
1587
1588void MediaPlayer::Clock::Reset(int64_t initialTime)
1589{
1590 boost::mutex::scoped_lock l(mMutex);
1591
1592 mInitialTime = initialTime;
1593 mSystemTime = GetSysTime();
1594 mPauseTime = initialTime;
1595}
1596
1597void MediaPlayer::Clock::Pause()
1598{
1599 boost::mutex::scoped_lock l(mMutex);
1600
1601 if (mRunning)
1602 {
1603 mPauseTime = GetSysTime() - mSystemTime + mInitialTime;
1604 mRunning = false;
1605 }
1606}
1607
1608void MediaPlayer::Clock::Resume()
1609{
1610 boost::mutex::scoped_lock l(mMutex);
1611
1612 if (!mRunning)
1613 {
1614 mInitialTime = mPauseTime;
1615 mSystemTime = GetSysTime();
1616 mRunning = true;
1617 }
1618}
1619
1620bool MediaPlayer::Clock::IsPaused()
1621{
1622 boost::mutex::scoped_lock l(mMutex);
1623 return !mRunning;
1624}
1625
1627
1628uint8_t* MediaPlayer::PacketQueue::FLUSH_DATA = (uint8_t*)"VP_FLUSH";
1629uint8_t* MediaPlayer::PacketQueue::QUIT_DATA = (uint8_t*)"VP_QUIT";
1630
1631MediaPlayer::PacketQueue::PacketQueue(size_t capacity) :
1632 mCapacity(capacity),
1633 mSize(0)
1634{}
1635
1636MediaPlayer::PacketQueue::~PacketQueue()
1637{
1638 Clear();
1639}
1640
1641bool MediaPlayer::PacketQueue::Empty()
1642{
1643 boost::mutex::scoped_lock l(mMutex);
1644
1645 return mQueue.empty();
1646}
1647
1648bool MediaPlayer::PacketQueue::Push(AVPacket* packet)
1649{
1650 boost::mutex::scoped_lock l(mMutex);
1651
1652 if (mSize >= mCapacity)
1653 return false;
1654
1655 bool wasEmpty = mQueue.empty();
1656 mQueue.push(packet);
1657 mSize += packet->size;
1658
1659 if (wasEmpty)
1660 mCond.notify_one();
1661
1662 return true;
1663}
1664
1665AVPacket* MediaPlayer::PacketQueue::Pop()
1666{
1667 boost::mutex::scoped_lock l(mMutex);
1668
1669 while (mQueue.empty())
1670 mCond.wait(l);
1671
1672 AVPacket* pkt = mQueue.front();
1673 mQueue.pop();
1674 mSize -= pkt->size;
1675
1676 return pkt;
1677}
1678
1679AVPacket* MediaPlayer::PacketQueue::TryPop()
1680{
1681 boost::mutex::scoped_lock l(mMutex);
1682
1683 if (mQueue.empty())
1684 return NULL;
1685
1686 AVPacket* pkt = mQueue.front();
1687 mQueue.pop();
1688 mSize -= pkt->size;
1689
1690 return pkt;
1691}
1692
1693void MediaPlayer::PacketQueue::Flush(AVPacket* flushPacket)
1694{
1695 boost::mutex::scoped_lock l(mMutex);
1696
1697 bool wasEmpty = mQueue.empty();
1698
1699 Clear();
1700
1701 mQueue.push(flushPacket);
1702 if (wasEmpty)
1703 mCond.notify_one();
1704}
1705
1706void MediaPlayer::PacketQueue::Clear()
1707{
1708 while (!mQueue.empty())
1709 {
1710 AVPacket* pkt = mQueue.front();
1711 mQueue.pop();
1712
1713 if ((pkt->data != PacketQueue::FLUSH_DATA) && (pkt->data != PacketQueue::QUIT_DATA))
1714 av_packet_unref(pkt);
1715
1716 SAFE_DELETE(pkt);
1717 }
1718 mSize = 0;
1719}
1720
1721size_t MediaPlayer::PacketQueue::Size()
1722{
1723 return mSize;
1724}
1725
1726size_t MediaPlayer::PacketQueue::MaxSize()
1727{
1728 return mCapacity;
1729}
#define QUEUE_BUFFERING_RATIO
#define QUEUE_BUFFER_SIZE
#define av_err2str(errnum)
static cv::Mat ScolBitmapToMat(PtrObjBitmap scolBitmap)
This class provides media playback functionality.
Definition MediaPlayer.h:53
int GetAudioThreaded(char *destBuffer, size_t length)
Get decoded audio data from the media (using the dedicated audio thread).
void OpenUrl(std::string url)
Open the media at the given URL.
int SetVideoStream(int streamIndex=-1)
Select the video stream to play from the current file.
bool HasAudio()
Check whether the player has an audio track selected and ready to play.
static const int VIDEO_SIZE_AUTO
Definition MediaPlayer.h:55
static const int AV_SYNC_THRESHOLD
Definition MediaPlayer.h:66
static const int STREAM_UNDEFINED
Definition MediaPlayer.h:56
State GetState()
Get the current state of this player.
static const AVRational TIME_BASE
Definition MediaPlayer.h:51
void Play(long startPosition=0)
Play/resume the currently loaded media.
void SetAudioFormat(AudioFormats format, int sampleRate)
Set the output format for audio.
bool IsLiveStream()
Check whether the current media is a live stream.
static const AVPixelFormat DEST_PIXEL_FORMAT
Definition MediaPlayer.h:58
bool IsReady()
Check whether the player has a media ready to play.
std::vector< std::string > ListStreams(AVMediaType type)
void Stop()
Stop media playback.
bool HasVideo()
Check whether the player has a video track selected and ready to play.
MediaPlayer()
Create an empty player.
void GetSize(int &width, int &height)
Get the current size of the video (after resize)
void GetSourceSize(int &width, int &height)
Get the original size of the video (before resize)
long GetCurrentTime()
Get the current playback time of the media.
int SetAudioStream(int streamIndex=-1)
Select the audio stream to play from the current file.
void SetLoop(bool loop)
Set whether media playback should loop or not.
static const int MAX_READ_ERRORS
Definition MediaPlayer.h:69
bool IsSeekable()
Check whether the file or stream is seekable.
void SetCurrentTime(long time)
Set the current playback time of the media.
long GetLength()
Get the length of the current media stream.
static const AVRational FF_AV_TIME_BASE_Q
Definition MediaPlayer.h:53
static void DeInitFFmpeg()
Free resources allocated by init. Call this when you're done with MediaPlayer.
void Pause()
Pause the current media playback.
virtual ~MediaPlayer()
void Close()
Close the current media file, if any.
cv::Mat GetFrame()
Get the current decoded video frame.
static void InitFFmpeg()
Init FFmpeg functionalities. Call this before any other MediaPlayer functions.
void Open(std::string path)
Open the media file at the given path. If a media was already loaded, it will be replaced by the new ...
void SetSize(int width, int height)
Set the target size for the video.
static bool IsValidPlayer(MediaPlayer *player)
Check whether a media player is still valid.
static void SetGlobalPause(bool pause)
Pause/unpause every MediaPlayer.
int MEDIAPLAYER_END_CB
int MEDIAPLAYER_LOADED_CB