X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Fplayer.cc;h=7bf78c905248487e2e9d3723c38d267827e4a11b;hb=e7bc3bd16456c17bc6fe1d7981040b14e820505e;hp=56145f5bdec1f7a6fa7cd218702d8f211d98907b;hpb=057400eb1718e8769592e34e07d90405eb95605f;p=dcpomatic.git diff --git a/src/lib/player.cc b/src/lib/player.cc index 56145f5bd..7bf78c905 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 Carl Hetherington + Copyright (C) 2013-2014 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,10 +30,15 @@ #include "playlist.h" #include "job.h" #include "image.h" +#include "image_proxy.h" #include "ratio.h" #include "resampler.h" #include "log.h" #include "scaler.h" +#include "player_video_frame.h" +#include "frame_rate_change.h" + +#define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); using std::list; using std::cout; @@ -46,70 +51,6 @@ using boost::shared_ptr; using boost::weak_ptr; using boost::dynamic_pointer_cast; -class Piece -{ -public: - Piece (shared_ptr c) - : content (c) - , video_position (c->position ()) - , audio_position (c->position ()) - , repeat_to_do (0) - , repeat_done (0) - {} - - Piece (shared_ptr c, shared_ptr d) - : content (c) - , decoder (d) - , video_position (c->position ()) - , audio_position (c->position ()) - {} - - /** Set this piece to repeat a video frame a given number of times */ - void set_repeat (IncomingVideo video, int num) - { - repeat_video = video; - repeat_to_do = num; - repeat_done = 0; - } - - void reset_repeat () - { - repeat_video.image.reset (); - repeat_to_do = 0; - repeat_done = 0; - } - - bool repeating () const - { - return repeat_done != repeat_to_do; - } - - void repeat (Player* player) - { - player->process_video ( - repeat_video.weak_piece, - repeat_video.image, - repeat_video.eyes, - repeat_done > 0, - repeat_video.frame, - (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ()) - ); - - ++repeat_done; - } - - shared_ptr content; - shared_ptr decoder; - /** Time of the last video we emitted relative to the start of the DCP */ - Time video_position; - /** Time of the last audio we emitted relative to the start of the DCP */ - Time audio_position; - - IncomingVideo repeat_video; - int repeat_to_do; - int repeat_done; -}; - Player::Player (shared_ptr f, shared_ptr p) : _film (f) , _playlist (p) @@ -124,7 +65,7 @@ Player::Player (shared_ptr f, shared_ptr p) _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this)); _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3)); _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1)); - set_video_container_size (fit_ratio_within (_film->container()->ratio (), _film->full_frame ())); + set_video_container_size (_film->frame_size ()); } void @@ -154,7 +95,7 @@ Player::pass () } type = VIDEO; for (list >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - if ((*i)->decoder->done ()) { + if ((*i)->decoder->done () || (*i)->content->length_after_trim() == 0) { continue; } @@ -209,7 +150,7 @@ Player::pass () if (re) { shared_ptr b = re->flush (); if (b->frames ()) { - process_audio (earliest, b, ac->audio_length ()); + process_audio (earliest, b, ac->audio_length (), true); } } } @@ -224,7 +165,8 @@ Player::pass () continue; } - if (dynamic_pointer_cast ((*i)->decoder)) { + shared_ptr ad = dynamic_pointer_cast ((*i)->decoder); + if (ad && ad->has_audio ()) { audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position); } } @@ -241,12 +183,13 @@ Player::pass () /** @param extra Amount of extra time to add to the content frame's time (for repeat) */ void -Player::process_video (weak_ptr weak_piece, shared_ptr image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra) +Player::process_video (weak_ptr weak_piece, shared_ptr image, Eyes eyes, Part part, bool same, VideoContent::Frame frame, Time extra) { /* Keep a note of what came in so that we can repeat it if required */ _last_incoming_video.weak_piece = weak_piece; _last_incoming_video.image = image; _last_incoming_video.eyes = eyes; + _last_incoming_video.part = part; _last_incoming_video.same = same; _last_incoming_video.frame = frame; _last_incoming_video.extra = extra; @@ -259,7 +202,7 @@ Player::process_video (weak_ptr weak_piece, shared_ptr image shared_ptr content = dynamic_pointer_cast (piece->content); assert (content); - FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate()); + FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate()); if (frc.skip && (frame % 2) == 1) { return; } @@ -270,35 +213,54 @@ Player::process_video (weak_ptr weak_piece, shared_ptr image } Time const time = content->position() + relative_time + extra - content->trim_start (); - float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio(); - libdcp::Size const image_size = fit_ratio_within (ratio, _video_container_size); + libdcp::Size const image_size = content->scale().size (content, _video_container_size, _film->frame_size ()); - shared_ptr pi ( - new PlayerImage ( + shared_ptr pi ( + new PlayerVideoFrame ( image, content->crop(), image_size, _video_container_size, - _film->scaler() + _film->scaler(), + eyes, + part, + content->colour_conversion() ) ); - if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) { + if (_film->with_subtitles ()) { + for (list::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + if (i->covers (time)) { + /* This may be true for more than one of _subtitles, but the last (latest-starting) + one is the one we want to use, so that's ok. + */ + Position const container_offset ( + (_video_container_size.width - image_size.width) / 2, + (_video_container_size.height - image_size.width) / 2 + ); + + pi->set_subtitle (i->out_image(), i->out_position() + container_offset); + } + } + } - Position const container_offset ( - (_video_container_size.width - image_size.width) / 2, - (_video_container_size.height - image_size.width) / 2 - ); + /* Clear out old subtitles */ + for (list::iterator i = _subtitles.begin(); i != _subtitles.end(); ) { + list::iterator j = i; + ++j; + + if (i->ends_before (time)) { + _subtitles.erase (i); + } - pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset); + i = j; } - - + #ifdef DCPOMATIC_DEBUG _last_video = piece->content; #endif - Video (pi, eyes, content->colour_conversion(), same, time); + Video (pi, same, time); _last_emit_was_black = false; _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate()); @@ -308,8 +270,9 @@ Player::process_video (weak_ptr weak_piece, shared_ptr image } } +/** @param already_resampled true if this data has already been through the chain up to the resampler */ void -Player::process_audio (weak_ptr weak_piece, shared_ptr audio, AudioContent::Frame frame) +Player::process_audio (weak_ptr weak_piece, shared_ptr audio, AudioContent::Frame frame, bool already_resampled) { shared_ptr piece = weak_piece.lock (); if (!piece) { @@ -319,19 +282,21 @@ Player::process_audio (weak_ptr weak_piece, shared_ptr content = dynamic_pointer_cast (piece->content); assert (content); - /* Gain */ - if (content->audio_gain() != 0) { - shared_ptr gain (new AudioBuffers (audio)); - gain->apply_gain (content->audio_gain ()); - audio = gain; - } - - /* Resample */ - if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) { - shared_ptr r = resampler (content, true); - pair, AudioContent::Frame> ro = r->run (audio, frame); - audio = ro.first; - frame = ro.second; + if (!already_resampled) { + /* Gain */ + if (content->audio_gain() != 0) { + shared_ptr gain (new AudioBuffers (audio)); + gain->apply_gain (content->audio_gain ()); + audio = gain; + } + + /* Resample */ + if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) { + shared_ptr r = resampler (content, true); + pair, AudioContent::Frame> ro = r->run (audio, frame); + audio = ro.first; + frame = ro.second; + } } Time const relative_time = _film->audio_frames_to_time (frame); @@ -453,6 +418,10 @@ Player::setup_pieces () for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { + if (!(*i)->paths_valid ()) { + continue; + } + shared_ptr piece (new Piece (*i)); /* XXX: into content? */ @@ -461,8 +430,8 @@ Player::setup_pieces () if (fc) { shared_ptr fd (new FFmpegDecoder (_film, fc, _video, _audio)); - fd->Video.connect (bind (&Player::process_video, this, weak_ptr (piece), _1, _2, _3, _4, 0)); - fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr (piece), _1, _2)); + fd->Video.connect (bind (&Player::process_video, this, weak_ptr (piece), _1, _2, _3, _4, _5, 0)); + fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr (piece), _1, _2, false)); fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr (piece), _1, _2, _3, _4)); fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true); @@ -484,7 +453,7 @@ Player::setup_pieces () if (!reusing) { shared_ptr id (new ImageDecoder (_film, ic)); - id->Video.connect (bind (&Player::process_video, this, weak_ptr (piece), _1, _2, _3, _4, 0)); + id->Video.connect (bind (&Player::process_video, this, weak_ptr (piece), _1, _2, _3, _4, _5, 0)); piece->decoder = id; } } @@ -492,7 +461,7 @@ Player::setup_pieces () shared_ptr sc = dynamic_pointer_cast (*i); if (sc) { shared_ptr sd (new SndfileDecoder (_film, sc)); - sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr (piece), _1, _2)); + sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr (piece), _1, _2, false)); piece->decoder = sd; } @@ -520,13 +489,20 @@ Player::content_changed (weak_ptr w, int property, bool frequent) _have_valid_pieces = false; Changed (frequent); - } else if (property == SubtitleContentProperty::SUBTITLE_OFFSET || property == SubtitleContentProperty::SUBTITLE_SCALE) { + } else if ( + property == SubtitleContentProperty::SUBTITLE_X_OFFSET || + property == SubtitleContentProperty::SUBTITLE_Y_OFFSET || + property == SubtitleContentProperty::SUBTITLE_SCALE + ) { - update_subtitle (); + for (list::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + i->update (_film, _video_container_size); + } + Changed (frequent); } else if ( - property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_RATIO || + property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_SCALE || property == VideoContentProperty::VIDEO_FRAME_RATE ) { @@ -534,6 +510,7 @@ Player::content_changed (weak_ptr w, int property, bool frequent) } else if (property == ContentProperty::PATH) { + _have_valid_pieces = false; Changed (frequent); } } @@ -554,12 +531,15 @@ Player::set_video_container_size (libdcp::Size s) im->make_black (); _black_frame.reset ( - new PlayerImage ( - im, + new PlayerVideoFrame ( + shared_ptr (new RawImageProxy (im, _film->log ())), Crop(), _video_container_size, _video_container_size, - Scaler::from_id ("bicubic") + Scaler::from_id ("bicubic"), + EYES_BOTH, + PART_WHOLE, + ColourConversion () ) ); } @@ -576,12 +556,10 @@ Player::resampler (shared_ptr c, bool create) return shared_ptr (); } - _film->log()->log ( - String::compose ( - "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels() - ) + LOG_GENERAL ( + "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels() ); - + shared_ptr r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels())); _resamplers[c] = r; return r; @@ -594,7 +572,7 @@ Player::emit_black () _last_video.reset (); #endif - Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position); + Video (_black_frame, _last_emit_was_black, _video_position); _video_position += _film->video_frames_to_time (1); _last_emit_was_black = true; } @@ -629,73 +607,14 @@ Player::film_changed (Film::Property p) void Player::process_subtitle (weak_ptr weak_piece, shared_ptr image, dcpomatic::Rect rect, Time from, Time to) { - _in_subtitle.piece = weak_piece; - _in_subtitle.image = image; - _in_subtitle.rect = rect; - _in_subtitle.from = from; - _in_subtitle.to = to; - - update_subtitle (); -} - -void -Player::update_subtitle () -{ - shared_ptr piece = _in_subtitle.piece.lock (); - if (!piece) { - return; - } - - if (!_in_subtitle.image) { - _out_subtitle.image.reset (); - return; - } - - shared_ptr sc = dynamic_pointer_cast (piece->content); - assert (sc); - - dcpomatic::Rect in_rect = _in_subtitle.rect; - libdcp::Size scaled_size; - - in_rect.y += sc->subtitle_offset (); - - /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */ - scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale (); - scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale (); - - /* Then we need a corrective translation, consisting of two parts: - * - * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be - * rect.x * _video_container_size.width and rect.y * _video_container_size.height. - * - * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be - * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and - * (height_before_subtitle_scale * (1 - subtitle_scale) / 2). - * - * Combining these two translations gives these expressions. - */ - - _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2))); - _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2))); - - _out_subtitle.image = _in_subtitle.image->scale ( - scaled_size, - Scaler::from_id ("bicubic"), - _in_subtitle.image->pixel_format (), - true - ); - - /* XXX: hack */ - Time from = _in_subtitle.from; - Time to = _in_subtitle.to; - shared_ptr vc = dynamic_pointer_cast (piece->content); - if (vc) { - from = rint (from * vc->video_frame_rate() / _film->video_frame_rate()); - to = rint (to * vc->video_frame_rate() / _film->video_frame_rate()); + if (!image) { + /* A null image means that we should stop any current subtitles at `from' */ + for (list::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) { + i->set_stop (from); + } + } else { + _subtitles.push_back (Subtitle (_film, _video_container_size, weak_piece, image, rect, from, to)); } - - _out_subtitle.from = from * piece->content->position (); - _out_subtitle.to = to + piece->content->position (); } /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles. @@ -712,6 +631,7 @@ Player::repeat_last_video () _last_incoming_video.weak_piece, _last_incoming_video.image, _last_incoming_video.eyes, + _last_incoming_video.part, _last_incoming_video.same, _last_incoming_video.frame, _last_incoming_video.extra @@ -719,40 +639,3 @@ Player::repeat_last_video () return true; } - -PlayerImage::PlayerImage ( - shared_ptr in, - Crop crop, - libdcp::Size inter_size, - libdcp::Size out_size, - Scaler const * scaler - ) - : _in (in) - , _crop (crop) - , _inter_size (inter_size) - , _out_size (out_size) - , _scaler (scaler) -{ - -} - -void -PlayerImage::set_subtitle (shared_ptr image, Position pos) -{ - _subtitle_image = image; - _subtitle_position = pos; -} - -shared_ptr -PlayerImage::image () -{ - shared_ptr out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false); - - Position const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2); - - if (_subtitle_image) { - out->alpha_blend (_subtitle_image, _subtitle_position); - } - - return out; -}