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