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