X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2Flib%2Fplayer.cc;h=5d4635e5a865b0a7334e5381f50f59f5aa873ddd;hb=2255aedd15f985796d2e6f7fcc7fb412a5d98812;hp=92c2929acacc18e6cf29f1565b08ca76aa3074c4;hpb=164bf3eaae49f654d609c747850b3f564fa20102;p=dcpomatic.git diff --git a/src/lib/player.cc b/src/lib/player.cc index 92c2929ac..5d4635e5a 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -1,3 +1,5 @@ +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */ + /* Copyright (C) 2013 Carl Hetherington @@ -27,9 +29,12 @@ #include "sndfile_content.h" #include "playlist.h" #include "job.h" +#include "image.h" using std::list; using std::cout; +using std::min; +using std::vector; using boost::shared_ptr; using boost::weak_ptr; using boost::dynamic_pointer_cast; @@ -41,7 +46,11 @@ Player::Player (shared_ptr f, shared_ptr p) , _audio (true) , _subtitles (true) , _have_valid_decoders (false) - , _video_sync (true) + , _position (0) + , _audio_buffers (MAX_AUDIO_CHANNELS, 0) + , _last_video (0) + , _last_was_black (false) + , _last_audio (0) { _playlist->Changed.connect (bind (&Player::playlist_changed, this)); _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2)); @@ -72,180 +81,191 @@ Player::pass () setup_decoders (); _have_valid_decoders = true; } - - bool done = true; - - if (_video_decoder != _video_decoders.end ()) { - if ((*_video_decoder)->pass ()) { - _video_decoder++; - } - - if (_video_decoder != _video_decoders.end ()) { - done = false; - } - } - if (_playlist->audio_from() == Playlist::AUDIO_SNDFILE) { - for (list >::iterator i = _sndfile_decoders.begin(); i != _sndfile_decoders.end(); ++i) { - if (!(*i)->pass ()) { - done = false; - } - } - - Audio (_sndfile_buffers); - _sndfile_buffers.reset (); - } - - return done; + /* Here we are just finding the active decoder with the earliest last emission time, then + calling pass on it. If there is no decoder, we skip our position on until there is. + Hence this method will cause video and audio to be emitted, and it is up to the + process_{video,audio} methods to tidy it up. + */ + + Time earliest_pos = TIME_MAX; + shared_ptr earliest; + Time next_wait = TIME_MAX; + + for (list >::iterator i = _decoders.begin(); i != _decoders.end(); ++i) { + Time const ts = (*i)->content->time(); + Time const te = (*i)->content->time() + (*i)->content->length (_film); + if (ts <= _position && te > _position) { + Time const pos = ts + (*i)->last; + if (pos < earliest_pos) { + earliest_pos = pos; + earliest = *i; + } + } + + if (ts > _position) { + next_wait = min (next_wait, ts - _position); + } + } + + if (earliest) { + earliest->decoder->pass (); + _position = earliest->last; + } else if (next_wait < TIME_MAX) { + _position += next_wait; + } else { + return true; + } + + return false; } void -Player::set_progress (shared_ptr job) +Player::process_video (shared_ptr dr, shared_ptr image, bool same, shared_ptr sub, Time time) { - /* Assume progress can be divined from how far through the video we are */ - - if (_video_decoder == _video_decoders.end() || !_playlist->video_length()) { - return; - } - - ContentVideoFrame p = 0; - list >::iterator i = _video_decoders.begin (); - while (i != _video_decoders.end() && i != _video_decoder) { - p += (*i)->video_length (); - } - - job->set_progress (float ((*_video_decoder)->video_frame ()) / _playlist->video_length ()); + shared_ptr vd = dynamic_pointer_cast (dr->decoder); + + Time const global_time = dr->content->time() + time; + while ((global_time - _last_video) > 1) { + /* Fill in with black */ + emit_black_frame (); + } + + Video (image, same, sub, global_time); + dr->last = time; + _last_video = global_time; + _last_was_black = false; } void -Player::process_video (shared_ptr i, bool same, shared_ptr s) +Player::process_audio (shared_ptr dr, shared_ptr audio, Time time) { - Video (i, same, s); -} - -void -Player::process_audio (weak_ptr c, shared_ptr b) -{ - if (_playlist->audio_from() == Playlist::AUDIO_SNDFILE) { - AudioMapping mapping = _film->audio_mapping (); - if (!_sndfile_buffers) { - _sndfile_buffers.reset (new AudioBuffers (mapping.dcp_channels(), b->frames ())); - _sndfile_buffers->make_silent (); - } - - for (int i = 0; i < b->channels(); ++i) { - list dcp = mapping.content_to_dcp (AudioMapping::Channel (c, i)); - for (list::iterator j = dcp.begin(); j != dcp.end(); ++j) { - _sndfile_buffers->accumulate (b, i, static_cast (*j)); - } - } - - } else { - Audio (b); - } + /* XXX: mapping */ + + /* The time of this audio may indicate that some of our buffered audio is not going to + be added to any more, so it can be emitted. + */ + + if (time > _last_audio) { + /* We can emit some audio from our buffers */ + OutputAudioFrame const N = min (_film->time_to_audio_frames (time - _last_audio), static_cast (_audio_buffers.frames())); + shared_ptr emit (new AudioBuffers (_audio_buffers.channels(), N)); + emit->copy_from (&_audio_buffers, N, 0, 0); + Audio (emit, _last_audio); + _last_audio += _film->audio_frames_to_time (N); + + /* And remove it from our buffers */ + if (_audio_buffers.frames() > N) { + _audio_buffers.move (N, 0, _audio_buffers.frames() - N); + } + _audio_buffers.set_frames (_audio_buffers.frames() - N); + } + + /* Now accumulate the new audio into our buffers */ + + if (_audio_buffers.frames() == 0) { + /* We have no remaining data. Emit silence up to the start of this new data */ + if ((time - _last_audio) > 0) { + emit_silence (time - _last_audio); + } + } + + _audio_buffers.ensure_size (time - _last_audio + audio->frames()); + _audio_buffers.accumulate (audio.get(), 0, _film->time_to_audio_frames (time - _last_audio)); + dr->last = time + _film->audio_frames_to_time (audio->frames ()); } /** @return true on error */ bool -Player::seek (double t) +Player::seek (Time t) { if (!_have_valid_decoders) { setup_decoders (); _have_valid_decoders = true; } - /* Find the decoder that contains this position */ - _video_decoder = _video_decoders.begin (); - while (_video_decoder != _video_decoders.end ()) { - double const this_length = double ((*_video_decoder)->video_length()) / _film->video_frame_rate (); - if (t < this_length) { - break; - } - t -= this_length; - ++_video_decoder; - } - - if (_video_decoder != _video_decoders.end()) { - (*_video_decoder)->seek (t); - } else { + if (_decoders.empty ()) { return true; } + /* XXX */ + /* XXX: don't seek audio because we don't need to... */ return false; } + +void +Player::seek_back () +{ + /* XXX */ +} + +void +Player::seek_forward () +{ + /* XXX */ +} + + void Player::setup_decoders () { - _video_decoders.clear (); - _video_decoder = _video_decoders.end (); - _sndfile_decoders.clear (); - - if (_video) { - list > vc = _playlist->video (); - for (list >::iterator i = vc.begin(); i != vc.end(); ++i) { - - shared_ptr d; + list > old_decoders = _decoders; + + _decoders.clear (); + + Playlist::ContentList content = _playlist->content (); + for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) { + + shared_ptr dr (new DecoderRecord); + dr->content = *i; + + /* XXX: into content? */ + + shared_ptr fc = dynamic_pointer_cast (*i); + if (fc) { + shared_ptr fd (new FFmpegDecoder (_film, fc, _video, _audio, _subtitles)); - /* XXX: into content? */ + fd->Video.connect (bind (&Player::process_video, this, dr, _1, _2, _3, _4)); + fd->Audio.connect (bind (&Player::process_audio, this, dr, _1, _2)); + + dr->decoder = fd; + } + + shared_ptr ic = dynamic_pointer_cast (*i); + if (ic) { + shared_ptr id; - shared_ptr fc = dynamic_pointer_cast (*i); - if (fc) { - shared_ptr fd ( - new FFmpegDecoder ( - _film, fc, _video, - _audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG, - _subtitles, - _video_sync - ) - ); - - if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) { - fd->Audio.connect (bind (&Player::process_audio, this, fc, _1)); + /* See if we can re-use an old ImageMagickDecoder */ + for (list >::const_iterator i = old_decoders.begin(); i != old_decoders.end(); ++i) { + shared_ptr imd = dynamic_pointer_cast ((*i)->decoder); + if (imd && imd->content() == ic) { + id = imd; } - - d = fd; } - shared_ptr ic = dynamic_pointer_cast (*i); - if (ic) { - d.reset (new ImageMagickDecoder (_film, ic)); + if (!id) { + id.reset (new ImageMagickDecoder (_film, ic)); + id->Video.connect (bind (&Player::process_video, this, dr, _1, _2, _3, _4)); } - d->connect_video (shared_from_this ()); - _video_decoders.push_back (d); + dr->decoder = id; } - _video_decoder = _video_decoders.begin (); - } + shared_ptr sc = dynamic_pointer_cast (*i); + if (sc) { + shared_ptr sd (new SndfileDecoder (_film, sc)); + sd->Audio.connect (bind (&Player::process_audio, this, dr, _1, _2)); - if (_audio && _playlist->audio_from() == Playlist::AUDIO_SNDFILE) { - list > sc = _playlist->sndfile (); - for (list >::iterator i = sc.begin(); i != sc.end(); ++i) { - shared_ptr d (new SndfileDecoder (_film, *i)); - _sndfile_decoders.push_back (d); - d->Audio.connect (bind (&Player::process_audio, this, *i, _1)); + dr->decoder = sd; } - } -} - -void -Player::disable_video_sync () -{ - _video_sync = false; -} -double -Player::last_video_time () const -{ - double t = 0; - for (list >::const_iterator i = _video_decoders.begin(); i != _video_decoder; ++i) { - t += (*i)->video_length() / (*i)->video_frame_rate (); + _decoders.push_back (dr); } - return t + (*_video_decoder)->last_content_time (); + _position = 0; } void @@ -257,10 +277,7 @@ Player::content_changed (weak_ptr w, int p) } if (p == VideoContentProperty::VIDEO_LENGTH) { - if (dynamic_pointer_cast (c)) { - /* FFmpeg content length changes are serious; we need new decoders */ - _have_valid_decoders = false; - } + _have_valid_decoders = false; } } @@ -269,3 +286,26 @@ Player::playlist_changed () { _have_valid_decoders = false; } + +void +Player::emit_black_frame () +{ + shared_ptr image (new SimpleImage (AV_PIX_FMT_RGB24, libdcp::Size (128, 128), true)); + Video (image, _last_was_black, shared_ptr (), _last_video); + _last_video += _film->video_frames_to_time (1); +} + +void +Player::emit_silence (Time t) +{ + OutputAudioFrame frames = _film->time_to_audio_frames (t); + while (frames) { + /* Do this in half-second chunks so we don't overwhelm anybody */ + OutputAudioFrame this_time = min (_film->dcp_audio_frame_rate() / 2, frames); + shared_ptr silence (new AudioBuffers (MAX_AUDIO_CHANNELS, this_time)); + silence->make_silent (); + Audio (silence, _last_audio); + _last_audio += _film->audio_frames_to_time (this_time); + frames -= this_time; + } +}