Vuo  2.0.0
VuoVideoPlayer.cc
Go to the documentation of this file.
1 
10 #include "VuoVideoPlayer.h"
11 #include "VuoAvDecoder.h"
12 #include "VuoFfmpegDecoder.h"
13 #include <unistd.h>
14 
15 extern "C"
16 {
17 #ifdef VUO_COMPILER
19  "title" : "VuoVideoPlayer",
20  "dependencies" : [
21  "VuoFfmpegDecoder",
22  "VuoAvDecoder",
23  "VuoImage",
24  "VuoAudioSamples",
25  "VuoReal",
26  "VuoList_VuoAudioSamples",
27  "VuoList_VuoReal"
28  ]
29  });
30 #endif
31 }
32 
34 #define TRY_RELEASE_VIDEO(v) { if(v.image != NULL) { VuoRelease(v.image); v.image = NULL; v.timestamp = -1.0; } }
35 
37 #define TRY_RELEASE_AUDIO(a) { if (a.channels != NULL) { VuoRelease(a.channels); a.channels = NULL; a.timestamp = -1.0; } }
38 
40 {
41  VuoVideoPlayer* player = new VuoVideoPlayer(url);
42 
43  player->video_semaphore = dispatch_semaphore_create(0);
44  player->audio_semaphore = dispatch_semaphore_create(0);
45 
46  player->playbackRate = 1.;
47  player->audioEnabled = true;
48  player->isPlaying = false;
49  player->lastVideoTimestamp = 0.;
50  player->lastVideoFrameDelta = 0.;
51 
53  player->preferFfmpeg = optimization == VuoVideoOptimization_Random;
54 
55  player->decoder = player->preferFfmpeg ? (VuoVideoDecoder*) VuoFfmpegDecoder::Create(player->videoPath) : (VuoVideoDecoder*) VuoAvDecoder::Create(player->videoPath);
56 
57  if(player->decoder == NULL || !player->decoder->CanPlayAudio())
58  {
60 
61  if(alt != NULL && (player->decoder == NULL || alt->CanPlayAudio()) )
62  {
63  if(player->decoder != NULL)
64  delete player->decoder;
65 
66  player->decoder = alt;
67 
68  if(player->preferFfmpeg)
69  VDebugLog("Using AvFoundation video decoder despite optimization preference.");
70  else
71  VDebugLog("Using FFmpeg video decoder despite optimization preference.");
72  }
73  else if (!player->decoder && !alt)
74  {
75  VUserLog("AVFoundation and FFmpeg video decoders failed to open the movie.");
76  player->failedLoading = true;
77  }
78  }
79  else
80  {
81  if(player->preferFfmpeg)
82  VDebugLog("Using first choice video decoder FFmpeg");
83  else
84  VDebugLog("Using first choice video decoder AVFoundation");
85  }
86 
87  player->isReady = player->decoder != NULL && player->decoder->IsReady();
88 
89  player->audioFrame.channels = NULL;
90  player->videoFrame.image = NULL;
91 
92  if(player->decoder == NULL)
93  {
94  delete player;
95  return NULL;
96  }
97  else
98  {
99  player->decoder->videoPlayer = player;
100  player->decoder->onReadyToPlay = &VuoVideoPlayer::OnDecoderPlaybackReady;
101  }
102 
103  return player;
104 }
105 
107 {
109  while( !IsReady() && !failedLoading ) {
110  usleep(USEC_PER_SEC / 24); // @@@
111  }
112 
113  delete this;
114 }
115 
116 VuoVideoPlayer::~VuoVideoPlayer()
117 {
118  pthread_mutex_lock(&decoderMutex);
119 
120  if(isPlaying)
121  _Pause();
122 
123  if (video_timer)
124  stopTimer(&video_timer, &video_semaphore);
125  dispatch_release(video_semaphore);
126 
127  if (audio_timer)
128  stopTimer(&audio_timer, &audio_semaphore);
129  dispatch_release(audio_semaphore);
130 
131  if(decoder != NULL)
132  delete decoder;
133 
134  if(videoPath != NULL)
136 
137  TRY_RELEASE_VIDEO(videoFrame)
138  TRY_RELEASE_AUDIO(audioFrame)
139 
140 
141  pthread_mutex_unlock(&decoderMutex);
142 
143  int ret = pthread_mutex_destroy(&decoderMutex);
144 
145  switch(ret)
146  {
147  case EBUSY:
148  VUserLog("Failed destroying decoder mutex: EBUSY");
149  break;
150 
151  case EINVAL:
152  VUserLog("Failed destroying decoder mutex: EINVAL");
153  break;
154  }
155 
156 }
157 
159 {
160  return isReady;
161 }
162 
163 void VuoVideoPlayer::OnDecoderPlaybackReady(bool canPlayMedia)
164 {
165  pthread_mutex_lock(&decoderMutex);
166 
167  if(canPlayMedia)
168  {
169  if(!decoder->CanPlayAudio())
170  {
172 
173  if(alt != NULL && alt->CanPlayAudio())
174  {
175  if(decoder != NULL)
176  delete decoder;
177 
178  decoder = alt;
179 
180  if(!alt->IsReady())
181  return;
182  }
183  }
184 
185  _SetPlaybackRate(onReadyPlaybackRate);
186 
187  if(onReadySeek > -1)
188  if (!_Seek(onReadySeek))
189  VUserLog("Error: Couldn't seek.");
190 
191  if(playOnReady)
192  _Play( dispatch_time(DISPATCH_TIME_NOW, 0) );
193 
194  isReady = true;
195  }
196  else
197  {
198  if(!preferFfmpeg)
199  {
200  if(decoder != NULL)
201  {
202  delete decoder;
203  decoder = NULL;
204  }
205 
207 
208  if(dec != NULL)
209  {
210  decoder = dec;
211 
212  _SetPlaybackRate(onReadyPlaybackRate);
213 
214  if(onReadySeek > -1)
215  if (!_Seek(onReadySeek))
216  VUserLog("Error: Couldn't seek.");
217 
218  if(playOnReady)
219  _Play(dispatch_time(DISPATCH_TIME_NOW, 0));
220 
221  isReady = true;
222 
223  VUserLog("On second thought, AVFoundation decided it couldn't play this video. Fell back on FFmpeg successfully.");
224  }
225  else
226  {
227  VUserLog("Both AvFoundation and FFmpeg failed to load this video. Try using a different video or audio codec.");
228  failedLoading = true;
229  }
230  }
231  else
232  {
233  VUserLog("Both AvFoundation and FFmpeg failed to load this video. Try using a different video or audio codec.");
234  failedLoading = true;
235  }
236  }
237 
238  pthread_mutex_unlock(&decoderMutex);
239 }
240 
242 {
243  pthread_mutex_lock(&decoderMutex);
244 
245  if(!isPlaying)
246  {
247  if(!IsReady())
248  {
249  playOnReady = true;
250 
251  pthread_mutex_unlock(&decoderMutex);
252 
253  return;
254  }
255 
256  _Play( dispatch_time(DISPATCH_TIME_NOW, 0) );
257  }
258 
259  pthread_mutex_unlock(&decoderMutex);
260 }
261 
263 {
264  pthread_mutex_lock(&decoderMutex);
265 
266  if(isPlaying)
267  _Pause();
268 
269  pthread_mutex_unlock(&decoderMutex);
270 }
271 
273 {
274  pthread_mutex_lock(&decoderMutex);
275 
276  userSetPlaybackRate = rate;
277 
278  if(!IsReady())
279  onReadyPlaybackRate = rate;
280  else
281  _SetPlaybackRate(rate);
282 
283  pthread_mutex_unlock(&decoderMutex);
284 }
285 
286 bool VuoVideoPlayer::Seek(double second)
287 {
288  pthread_mutex_lock(&decoderMutex);
289 
290  bool success = false;
291 
292  if( !IsReady() )
293  onReadySeek = second;
294  else
295  success = _Seek(second);
296 
297  pthread_mutex_unlock(&decoderMutex);
298 
299  return success;
300 }
301 
302 void VuoVideoPlayer::_Play(dispatch_time_t start)
303 {
304  isPlaying = true;
305  playbackStart = start; // dispatch_time(DISPATCH_TIME_NOW, 0);
306  timestampStart = decoder->GetLastDecodedVideoTimeStamp();
307  lastVideoTimestamp = timestampStart;
308 
309  startTimer(&video_timer, start, &video_semaphore, &VuoVideoPlayer::sendVideoFrame);
310  startTimer(&audio_timer, start, &audio_semaphore, &VuoVideoPlayer::sendAudioFrame);
311 }
312 
313 void VuoVideoPlayer::_Pause()
314 {
315  isPlaying = false;
316 
317  if(video_timer != NULL)
318  stopTimer(&video_timer, &video_semaphore);
319 
320  if(audio_timer != NULL)
321  stopTimer(&audio_timer, &audio_semaphore);
322 }
323 
324 void VuoVideoPlayer::_SetPlaybackRate(double rate)
325 {
326  bool wasPlaying = isPlaying;
327 
328  if(isPlaying)
329  _Pause();
330 
331  playbackRate = rate < 0 ? fmin(rate, -.00001) : fmax(rate, .00001);
332  audioEnabled = VuoReal_areEqual(1., rate);
333 
334  decoder->SetPlaybackRate(rate);
335 
336  if(wasPlaying)
337  {
338  if(lastSentVideoTime != DISPATCH_TIME_FOREVER)
339  {
340  int64_t frame_delta = ((1./decoder->GetFrameRate()) / playbackRate) * NSEC_PER_SEC;
341  dispatch_time_t elapsed = MIN( dispatch_time(DISPATCH_TIME_NOW, 0) - lastSentVideoTime, frame_delta );
342  dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, frame_delta);
343  _Play(timer - elapsed);
344  }
345  else
346  {
347  _Play(dispatch_time(DISPATCH_TIME_NOW, 0));
348  }
349  }
350 }
351 
352 bool VuoVideoPlayer::_Seek(double second)
353 {
354  bool wasPlaying = isPlaying;
355 
356  if (wasPlaying)
357  {
358  _Pause();
359  if (decoder->SeekToSecond(second, NULL))
360  {
361  lastSentVideoTime = DISPATCH_TIME_FOREVER;
362  _Play( dispatch_time(DISPATCH_TIME_NOW, 0) );
363  return true;
364  }
365  }
366  else
367  {
368  VuoVideoFrame f;
369  if (decoder->SeekToSecond(second, &f))
370  {
371  TRY_RELEASE_VIDEO(videoFrame);
372  TRY_RELEASE_AUDIO(audioFrame);
373  videoFrame = f;
374  return true;
375  }
376  }
377 
378  return false;
379 }
380 
382 {
383  return lastVideoFrameDelta;
384 }
385 
387 {
388  return lastVideoTimestamp;
389 }
390 
392 {
393  double dur = 0;
394 
396  while(!IsReady() && !failedLoading)
397  {
398  usleep(USEC_PER_SEC / 24); // @@@
399  }
400 
401  pthread_mutex_lock(&decoderMutex);
402  dur = failedLoading ? 0 : decoder->GetDuration();
403  pthread_mutex_unlock(&decoderMutex);
404 
405  return dur;
406 }
407 
409 {
410  unsigned int channelCount = 0;
411 
412  while(!IsReady() && !failedLoading)
413  {
414  usleep(USEC_PER_SEC / 24); // @@@
415  }
416 
417  pthread_mutex_lock(&decoderMutex);
418  channelCount = failedLoading ? 0 : decoder->GetAudioChannelCount();
419  pthread_mutex_unlock(&decoderMutex);
420 
421  return channelCount;
422 }
423 
425 {
426  const int MAX_FRAMES_TO_STEP_BEFORE_SEEK = 5;
427  const double floatingPointError = 0.0001;
428  double averageFrameDuration = 1. / decoder->GetFrameRate();
429  double duration = GetDuration();
430  double target = fmax(0, fmin(duration, second));
431 
432  // first check if the last decoded frame is the one we want, or close to it
433  if(videoFrame.image != NULL)
434  {
435  double frameDelta = fabs(target - videoFrame.timestamp);
436 
437  // asking for the frame that's already stored. send it.
438  if ( ((videoFrame.timestamp < target + floatingPointError) && (target + floatingPointError < videoFrame.timestamp + videoFrame.duration))
439  || (VuoReal_areEqual(videoFrame.timestamp + videoFrame.duration, duration) && VuoReal_areEqual(target, duration)) )
440  {
441  // VLog("next frame procured by already matching current (sent %.3f, %.3f)", videoFrame.timestamp, target);
442  *frame = videoFrame;
443  return true;
444  }
445  // asking for the next frame, or very close to it.
446  // this depends on the caller having set an appropriate playback rate (either forwards or backwards)
447  else if( frameDelta < averageFrameDuration * MAX_FRAMES_TO_STEP_BEFORE_SEEK && (target - videoFrame.timestamp > 0 == playbackRate > 0) )
448  {
449  for(int i = 0; i < MAX_FRAMES_TO_STEP_BEFORE_SEEK; i++)
450  {
451  VuoVideoFrame f;
452  bool gotNextFrame = NextVideoFrame(&f);
453 
454  if(gotNextFrame)
455  {
456  TRY_RELEASE_VIDEO(videoFrame);
457  videoFrame = f;
458 
459  if (videoFrame.timestamp + videoFrame.duration > target + floatingPointError)
460  {
461  *frame = videoFrame;
462  // VLog("next frame procured by stepping (sent %.3f, %.3f)", videoFrame.timestamp, target);
463  return true;
464  }
465  }
466  }
467  }
468  }
469 
470  bool seekSuccess = false;
471 
472  // if the seek target is ≥ duration seek to a little before the end timestamp and step.
473  // that way you usually end up with a valid frame to send.
474  if(target > duration - .5 && playbackRate > 0)
475  {
476  seekSuccess = Seek(duration - .5);
477 
478  while (videoFrame.timestamp + videoFrame.duration < target + floatingPointError)
479  {
480  VuoVideoFrame f;
481 
482  if( NextVideoFrame(&f) )
483  {
484  TRY_RELEASE_VIDEO(videoFrame);
485  videoFrame = f;
486  }
487  else
488  {
489  break;
490  }
491  }
492  // VLog("next frame procured by seek to end & backpedal (sent %.3f, %.3f)", videoFrame.timestamp, target);
493  }
494  else
495  {
496  seekSuccess = Seek(target);
497  // VLog("next frame procured by plain ol' seek (sent %.3f, %.3f)", videoFrame.timestamp, target);
498  }
499 
500  *frame = videoFrame;
501 
502  return seekSuccess;
503 }
504 
506 {
507  if(videoFrame.image != NULL)
508  {
509  *frame = videoFrame;
510  return true;
511  }
512  else
513  {
514  return false;
515  }
516 }
517 
519 {
520  if(isPlaying || !IsReady())
521  return false;
522 
523  return decoder->NextVideoFrame(frame);
524 }
525 
527 {
528  return !isPlaying && IsReady() && decoder->NextAudioFrame(frame);
529 }
530 
531 void VuoVideoPlayer::startTimer(dispatch_source_t* timer, dispatch_time_t start, dispatch_semaphore_t* semaphore, void (VuoVideoPlayer::*func)(void))
532 {
533  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
534 
535  *timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, queue);
536 
537  dispatch_source_set_timer(
538  *timer,
539  start,
540  DISPATCH_TIME_FOREVER,
541  0);
542 
543  dispatch_source_set_event_handler(*timer, ^{ ((*this).*func)(); });
544  dispatch_source_set_cancel_handler(*timer, ^{ dispatch_semaphore_signal(*semaphore); });
545 
546  dispatch_resume(*timer);
547 }
548 
549 void VuoVideoPlayer::stopTimer(dispatch_source_t* timer, dispatch_semaphore_t* semaphore)
550 {
551  dispatch_source_cancel(*timer);
552  dispatch_semaphore_wait(*semaphore, DISPATCH_TIME_FOREVER);
553  dispatch_release(*timer);
554 
555  *timer = NULL;
556 }
557 
562 void VuoVideoPlayer::sendVideoFrame()
563 {
564  if(videoFrame.image != NULL)
565  {
566  if(videoFrameReceivedDelegate != NULL)
567  {
568  lastSentVideoTime = dispatch_time(DISPATCH_TIME_NOW, 0);
569  videoFrameReceivedDelegate(videoFrame);
570  }
571 
572  VuoRelease(videoFrame.image);
573  videoFrame.image = NULL;
574  }
575 
576  double nextFrameEvent = 0;
577 
578  if( decoder->NextVideoFrame(&videoFrame) )
579  {
580  // translate timestamp to one considering playback rate and direction (forawrds or backwards)
581  double presentationRelativeTimestamp = ((videoFrame.timestamp - timestampStart) / playbackRate);
582  lastVideoFrameDelta = videoFrame.timestamp - lastVideoTimestamp;
583  lastVideoTimestamp = videoFrame.timestamp;
584  nextFrameEvent = NSEC_PER_SEC * presentationRelativeTimestamp;
585  }
586  else
587  {
588  videoFrame.image = NULL;
589 
590  // didn't get a frame, so check what the loop type is and do something
591 
592  if (!isPlaying)
593  return;
594 
595  pthread_mutex_lock(&decoderMutex);
596 
598  {
599  if (playbackRate < 0 && timestampStart == 0)
600  {
601  // Don't fire finishedPlayback after firing the first frame when playing backwards from the beginning.
602  }
603  else
605  }
606 
607  switch(loop)
608  {
609  case VuoLoopType_None:
610  // our work here is done, hang out and wait for further instruction
611  isPlaying = false;
612  break;
613 
614  case VuoLoopType_Loop:
615  // seek back to the video start and resume playback
616  stopTimer(&audio_timer, &audio_semaphore);
617  if( !VuoReal_areEqual(playbackRate, userSetPlaybackRate) )
618  {
619  playbackRate = userSetPlaybackRate;
620  decoder->SetPlaybackRate(userSetPlaybackRate);
621  }
622  decoder->SeekToSecond(playbackRate > 0 ? 0 : decoder->GetDuration(), NULL);
623  playbackStart = dispatch_time(DISPATCH_TIME_NOW, 0);
624  timestampStart = decoder->GetLastDecodedVideoTimeStamp();
625  lastVideoTimestamp = timestampStart;
626  nextFrameEvent = 0;
627  startTimer(&audio_timer, dispatch_time(DISPATCH_TIME_NOW, 0), &audio_semaphore, &VuoVideoPlayer::sendAudioFrame);
628  break;
629 
630  case VuoLoopType_Mirror:
631  stopTimer(&audio_timer, &audio_semaphore);
632  playbackRate = -playbackRate;
633  audioEnabled = VuoReal_areEqual(1., playbackRate);
634  decoder->SetPlaybackRate(playbackRate);
635  playbackStart = dispatch_time(DISPATCH_TIME_NOW, 0);
636  timestampStart = decoder->GetLastDecodedVideoTimeStamp();
637  lastVideoTimestamp = timestampStart;
638  nextFrameEvent = 0;
640  startTimer(&audio_timer, dispatch_time(DISPATCH_TIME_NOW, 0), &audio_semaphore, &VuoVideoPlayer::sendAudioFrame);
641  break;
642  }
643 
644  pthread_mutex_unlock(&decoderMutex);
645  }
646 
647  if(isPlaying && fabs(playbackRate) > .0001)
648  {
649  // schedule next frame
650  dispatch_source_set_timer(
651  video_timer,
652  dispatch_time(playbackStart, nextFrameEvent),
653  DISPATCH_TIME_FOREVER,
654  0);
655  }
656 }
657 
658 void VuoVideoPlayer::sendAudioFrame()
659 {
660  if (audioFrame.channels != NULL && VuoListGetCount_VuoAudioSamples(audioFrame.channels) > 0)
661  {
662  if(audioFrameReceivedDelegate != NULL)
663  audioFrameReceivedDelegate(audioFrame.channels);
664 
665  VuoRelease(audioFrame.channels);
666  audioFrame.channels = NULL;
667  }
668 
670  VuoRetain(audioFrame.channels);
671 
672  if( decoder->NextAudioFrame(&audioFrame) )
673  {
674  double presentationRelativeTimestamp = ((audioFrame.timestamp - timestampStart) / playbackRate);
675  lastAudioTimestamp = audioFrame.timestamp;
676 
677  dispatch_source_set_timer(
678  audio_timer,
679  dispatch_time(playbackStart, NSEC_PER_SEC * presentationRelativeTimestamp),
680  DISPATCH_TIME_FOREVER,
681  0);
682  }
683  else
684  {
685  VuoRelease(audioFrame.channels);
686  audioFrame.channels = NULL;
687  }
688 }
689