2 Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include "ffmpeg_decoder.h"
25 #include "audio_buffers.h"
26 #include "ffmpeg_content.h"
27 #include "image_decoder.h"
28 #include "image_content.h"
29 #include "sndfile_decoder.h"
30 #include "sndfile_content.h"
31 #include "subtitle_content.h"
32 #include "subrip_decoder.h"
33 #include "subrip_content.h"
37 #include "image_proxy.h"
41 #include "render_subtitles.h"
43 #include "content_video.h"
44 #include "player_video_frame.h"
45 #include "frame_rate_change.h"
47 #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
58 using boost::shared_ptr;
59 using boost::weak_ptr;
60 using boost::dynamic_pointer_cast;
61 using boost::optional;
63 Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
66 , _have_valid_pieces (false)
67 , _approximate_size (false)
68 , _burn_subtitles (false)
70 _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
71 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
72 _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
73 set_video_container_size (_film->frame_size ());
77 Player::setup_pieces ()
79 list<shared_ptr<Piece> > old_pieces = _pieces;
82 ContentList content = _playlist->content ();
84 for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
86 if (!(*i)->paths_valid ()) {
90 shared_ptr<Decoder> decoder;
91 optional<FrameRateChange> frc;
93 /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
94 DCPTime best_overlap_t;
95 shared_ptr<VideoContent> best_overlap;
96 for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
97 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
102 DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end());
103 if (overlap > best_overlap_t) {
105 best_overlap_t = overlap;
109 optional<FrameRateChange> best_overlap_frc;
111 best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
113 /* No video overlap; e.g. if the DCP is just audio */
114 best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
118 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
120 decoder.reset (new FFmpegDecoder (fc, _film->log()));
121 frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
125 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
127 /* See if we can re-use an old ImageDecoder */
128 for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
129 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
130 if (imd && imd->content() == ic) {
136 decoder.reset (new ImageDecoder (ic));
139 frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
143 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
145 decoder.reset (new SndfileDecoder (sc));
146 frc = best_overlap_frc;
150 shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
152 decoder.reset (new SubRipDecoder (rc));
153 frc = best_overlap_frc;
156 _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
159 _have_valid_pieces = true;
163 Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
165 shared_ptr<Content> c = w.lock ();
171 property == ContentProperty::POSITION ||
172 property == ContentProperty::LENGTH ||
173 property == ContentProperty::TRIM_START ||
174 property == ContentProperty::TRIM_END ||
175 property == ContentProperty::PATH ||
176 property == VideoContentProperty::VIDEO_FRAME_TYPE
179 _have_valid_pieces = false;
183 property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
184 property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
185 property == SubtitleContentProperty::SUBTITLE_SCALE ||
186 property == VideoContentProperty::VIDEO_CROP ||
187 property == VideoContentProperty::VIDEO_SCALE ||
188 property == VideoContentProperty::VIDEO_FRAME_RATE
195 /** @param already_resampled true if this data has already been through the chain up to the resampler */
197 Player::playlist_changed ()
199 _have_valid_pieces = false;
204 Player::set_video_container_size (dcp::Size s)
206 _video_container_size = s;
208 _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
209 _black_image->make_black ();
213 Player::film_changed (Film::Property p)
215 /* Here we should notice Film properties that affect our output, and
216 alert listeners that our output now would be different to how it was
217 last time we were run.
220 if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
226 Player::process_content_image_subtitles (shared_ptr<SubtitleContent> content, list<shared_ptr<ContentImageSubtitle> > subs) const
228 list<PositionImage> all;
230 for (list<shared_ptr<ContentImageSubtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
235 dcpomatic::Rect<double> in_rect = (*i)->rectangle;
236 dcp::Size scaled_size;
238 in_rect.x += content->subtitle_x_offset ();
239 in_rect.y += content->subtitle_y_offset ();
241 /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
242 scaled_size.width = in_rect.width * _video_container_size.width * content->subtitle_scale ();
243 scaled_size.height = in_rect.height * _video_container_size.height * content->subtitle_scale ();
245 /* Then we need a corrective translation, consisting of two parts:
247 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
248 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
250 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
251 * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
252 * (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
254 * Combining these two translations gives these expressions.
261 Scaler::from_id ("bicubic"),
262 (*i)->image->pixel_format (),
266 rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - content->subtitle_scale ()) / 2))),
267 rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - content->subtitle_scale ()) / 2)))
277 Player::process_content_text_subtitles (list<shared_ptr<ContentTextSubtitle> > sub) const
279 list<PositionImage> all;
280 for (list<shared_ptr<ContentTextSubtitle> >::const_iterator i = sub.begin(); i != sub.end(); ++i) {
281 if (!(*i)->subs.empty ()) {
282 all.push_back (render_subtitles ((*i)->subs, _video_container_size));
290 Player::set_approximate_size ()
292 _approximate_size = true;
295 shared_ptr<PlayerVideoFrame>
296 Player::black_player_video_frame () const
298 return shared_ptr<PlayerVideoFrame> (
299 new PlayerVideoFrame (
300 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image, _film->log ())),
302 _video_container_size,
303 _video_container_size,
304 Scaler::from_id ("bicubic"),
307 Config::instance()->colour_conversions().front().conversion
312 shared_ptr<PlayerVideoFrame>
313 Player::content_to_player_video_frame (
314 shared_ptr<VideoContent> content,
315 ContentVideo content_video,
316 list<shared_ptr<Piece> > subs,
318 dcp::Size image_size) const
320 shared_ptr<PlayerVideoFrame> pvf (
321 new PlayerVideoFrame (
325 _video_container_size,
329 content->colour_conversion ()
336 list<PositionImage> sub_images;
338 for (list<shared_ptr<Piece> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
339 shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*i)->decoder);
340 shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*i)->content);
341 ContentTime const from = dcp_to_content_subtitle (*i, time);
342 ContentTime const to = from + ContentTime::from_frames (1, content->video_frame_rate ());
344 list<shared_ptr<ContentImageSubtitle> > image_subtitles = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to));
345 if (!image_subtitles.empty ()) {
346 list<PositionImage> im = process_content_image_subtitles (
351 copy (im.begin(), im.end(), back_inserter (sub_images));
354 if (_burn_subtitles) {
355 list<shared_ptr<ContentTextSubtitle> > text_subtitles = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to));
356 if (!text_subtitles.empty ()) {
357 list<PositionImage> im = process_content_text_subtitles (text_subtitles);
358 copy (im.begin(), im.end(), back_inserter (sub_images));
363 if (!sub_images.empty ()) {
364 pvf->set_subtitle (merge (sub_images));
370 /** @return All PlayerVideoFrames at the given time (there may be two frames for 3D) */
371 list<shared_ptr<PlayerVideoFrame> >
372 Player::get_video (DCPTime time, bool accurate)
374 if (!_have_valid_pieces) {
378 list<shared_ptr<Piece> > ov = overlaps<VideoContent> (
380 time + DCPTime::from_frames (1, _film->video_frame_rate ())
383 list<shared_ptr<PlayerVideoFrame> > pvf;
386 /* No video content at this time */
387 pvf.push_back (black_player_video_frame ());
391 /* Create a PlayerVideoFrame from the content's video at this time */
393 shared_ptr<Piece> piece = ov.back ();
394 shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
396 shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
399 list<ContentVideo> content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate);
400 if (content_video.empty ()) {
401 pvf.push_back (black_player_video_frame ());
405 dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
406 if (_approximate_size) {
407 image_size.width &= ~3;
408 image_size.height &= ~3;
411 for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
412 list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (
414 time + DCPTime::from_frames (1, _film->video_frame_rate ())
417 pvf.push_back (content_to_player_video_frame (content, *i, subs, time, image_size));
423 shared_ptr<AudioBuffers>
424 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
426 if (!_have_valid_pieces) {
430 AudioFrame const length_frames = length.frames (_film->audio_frame_rate ());
432 shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
433 audio->make_silent ();
435 list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length);
440 for (list<shared_ptr<Piece> >::iterator i = ov.begin(); i != ov.end(); ++i) {
442 shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> ((*i)->content);
444 shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
447 if (content->audio_frame_rate() == 0) {
448 /* This AudioContent has no audio (e.g. if it is an FFmpegContent with no
454 /* The time that we should request from the content */
455 DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0);
457 if (request < DCPTime ()) {
458 /* We went off the start of the content, so we will need to offset
459 the stuff we get back.
462 request = DCPTime ();
465 AudioFrame const content_frame = dcp_to_content_audio (*i, request);
467 /* Audio from this piece's decoder (which might be more or less than what we asked for) */
468 shared_ptr<ContentAudio> all = decoder->get_audio (content_frame, length_frames, accurate);
471 if (content->audio_gain() != 0) {
472 shared_ptr<AudioBuffers> gain (new AudioBuffers (all->audio));
473 gain->apply_gain (content->audio_gain ());
478 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all->audio->frames()));
479 dcp_mapped->make_silent ();
480 AudioMapping map = content->audio_mapping ();
481 for (int i = 0; i < map.content_channels(); ++i) {
482 for (int j = 0; j < _film->audio_channels(); ++j) {
483 if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
484 dcp_mapped->accumulate_channel (
488 map.get (i, static_cast<dcp::Channel> (j))
494 all->audio = dcp_mapped;
496 audio->accumulate_frames (
498 content_frame - all->frame,
499 offset.frames (_film->audio_frame_rate()),
500 min (AudioFrame (all->audio->frames()), length_frames) - offset.frames (_film->audio_frame_rate ())
506 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
508 /* s is the offset of t from the start position of this content */
509 DCPTime s = t - piece->content->position ();
510 s = DCPTime (max (int64_t (0), s.get ()));
511 s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
513 /* Convert this to the content frame */
514 return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor ();
518 Player::dcp_to_content_audio (shared_ptr<const Piece> piece, DCPTime t) const
520 /* s is the offset of t from the start position of this content */
521 DCPTime s = t - piece->content->position ();
522 s = DCPTime (max (int64_t (0), s.get ()));
523 s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
525 /* Convert this to the content frame */
526 return DCPTime (s + piece->content->trim_start()).frames (_film->audio_frame_rate());
530 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
532 /* s is the offset of t from the start position of this content */
533 DCPTime s = t - piece->content->position ();
534 s = DCPTime (max (int64_t (0), s.get ()));
535 s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
537 return ContentTime (s + piece->content->trim_start(), piece->frc);
541 PlayerStatistics::dump (shared_ptr<Log> log) const
543 log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat), Log::TYPE_GENERAL);
544 log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds()), Log::TYPE_GENERAL);
547 PlayerStatistics const &
548 Player::statistics () const