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"
55 using boost::shared_ptr;
56 using boost::weak_ptr;
57 using boost::dynamic_pointer_cast;
58 using boost::optional;
60 Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
63 , _have_valid_pieces (false)
64 , _approximate_size (false)
65 , _burn_subtitles (false)
67 _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
68 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
69 _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
70 set_video_container_size (_film->frame_size ());
74 Player::setup_pieces ()
76 list<shared_ptr<Piece> > old_pieces = _pieces;
79 ContentList content = _playlist->content ();
81 for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
83 if (!(*i)->paths_valid ()) {
87 shared_ptr<Decoder> decoder;
88 optional<FrameRateChange> frc;
90 /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
91 DCPTime best_overlap_t;
92 shared_ptr<VideoContent> best_overlap;
93 for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
94 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
99 DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end());
100 if (overlap > best_overlap_t) {
102 best_overlap_t = overlap;
106 optional<FrameRateChange> best_overlap_frc;
108 best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
110 /* No video overlap; e.g. if the DCP is just audio */
111 best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
115 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
117 decoder.reset (new FFmpegDecoder (fc, _film->log()));
118 frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
122 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
124 /* See if we can re-use an old ImageDecoder */
125 for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
126 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
127 if (imd && imd->content() == ic) {
133 decoder.reset (new ImageDecoder (ic));
136 frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
140 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
142 decoder.reset (new SndfileDecoder (sc));
143 frc = best_overlap_frc;
147 shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
149 decoder.reset (new SubRipDecoder (rc));
150 frc = best_overlap_frc;
153 _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
156 _have_valid_pieces = true;
160 Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
162 shared_ptr<Content> c = w.lock ();
168 property == ContentProperty::POSITION ||
169 property == ContentProperty::LENGTH ||
170 property == ContentProperty::TRIM_START ||
171 property == ContentProperty::TRIM_END ||
172 property == ContentProperty::PATH ||
173 property == VideoContentProperty::VIDEO_FRAME_TYPE
176 _have_valid_pieces = false;
180 property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
181 property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
182 property == SubtitleContentProperty::SUBTITLE_SCALE ||
183 property == VideoContentProperty::VIDEO_CROP ||
184 property == VideoContentProperty::VIDEO_SCALE ||
185 property == VideoContentProperty::VIDEO_FRAME_RATE
193 Player::playlist_changed ()
195 _have_valid_pieces = false;
200 Player::set_video_container_size (dcp::Size s)
202 _video_container_size = s;
204 _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
205 _black_image->make_black ();
209 Player::film_changed (Film::Property p)
211 /* Here we should notice Film properties that affect our output, and
212 alert listeners that our output now would be different to how it was
213 last time we were run.
216 if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
222 Player::process_content_image_subtitles (shared_ptr<SubtitleContent> content, list<shared_ptr<ContentImageSubtitle> > subs) const
224 list<PositionImage> all;
226 for (list<shared_ptr<ContentImageSubtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
231 dcpomatic::Rect<double> in_rect = (*i)->rectangle;
232 dcp::Size scaled_size;
234 in_rect.x += content->subtitle_x_offset ();
235 in_rect.y += content->subtitle_y_offset ();
237 /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
238 scaled_size.width = in_rect.width * _video_container_size.width * content->subtitle_scale ();
239 scaled_size.height = in_rect.height * _video_container_size.height * content->subtitle_scale ();
241 /* Then we need a corrective translation, consisting of two parts:
243 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
244 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
246 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
247 * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
248 * (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
250 * Combining these two translations gives these expressions.
257 Scaler::from_id ("bicubic"),
258 (*i)->image->pixel_format (),
262 rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - content->subtitle_scale ()) / 2))),
263 rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - content->subtitle_scale ()) / 2)))
273 Player::process_content_text_subtitles (list<shared_ptr<ContentTextSubtitle> > sub) const
275 list<PositionImage> all;
276 for (list<shared_ptr<ContentTextSubtitle> >::const_iterator i = sub.begin(); i != sub.end(); ++i) {
277 if (!(*i)->subs.empty ()) {
278 all.push_back (render_subtitles ((*i)->subs, _video_container_size));
286 Player::set_approximate_size ()
288 _approximate_size = true;
291 shared_ptr<PlayerVideoFrame>
292 Player::black_player_video_frame () const
294 return shared_ptr<PlayerVideoFrame> (
295 new PlayerVideoFrame (
296 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
298 _video_container_size,
299 _video_container_size,
300 Scaler::from_id ("bicubic"),
303 Config::instance()->colour_conversions().front().conversion
308 shared_ptr<PlayerVideoFrame>
309 Player::content_to_player_video_frame (
310 shared_ptr<VideoContent> content,
311 ContentVideo content_video,
312 list<shared_ptr<Piece> > subs,
314 dcp::Size image_size) const
316 shared_ptr<PlayerVideoFrame> pvf (
317 new PlayerVideoFrame (
321 _video_container_size,
325 content->colour_conversion ()
332 list<PositionImage> sub_images;
334 for (list<shared_ptr<Piece> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
335 shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*i)->decoder);
336 shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*i)->content);
337 ContentTime const from = dcp_to_content_subtitle (*i, time);
338 ContentTime const to = from + ContentTime::from_frames (1, content->video_frame_rate ());
340 list<shared_ptr<ContentImageSubtitle> > image_subtitles = subtitle_decoder->get_image_subtitles (from, to);
341 if (!image_subtitles.empty ()) {
342 list<PositionImage> im = process_content_image_subtitles (
347 copy (im.begin(), im.end(), back_inserter (sub_images));
350 if (_burn_subtitles) {
351 list<shared_ptr<ContentTextSubtitle> > text_subtitles = subtitle_decoder->get_text_subtitles (from, to);
352 if (!text_subtitles.empty ()) {
353 list<PositionImage> im = process_content_text_subtitles (text_subtitles);
354 copy (im.begin(), im.end(), back_inserter (sub_images));
359 if (!sub_images.empty ()) {
360 pvf->set_subtitle (merge (sub_images));
366 /** @return All PlayerVideoFrames at the given time (there may be two frames for 3D) */
367 list<shared_ptr<PlayerVideoFrame> >
368 Player::get_video (DCPTime time, bool accurate)
370 if (!_have_valid_pieces) {
374 list<shared_ptr<Piece> > ov = overlaps<VideoContent> (
376 time + DCPTime::from_frames (1, _film->video_frame_rate ())
379 list<shared_ptr<PlayerVideoFrame> > pvf;
382 /* No video content at this time */
383 pvf.push_back (black_player_video_frame ());
387 /* Create a PlayerVideoFrame from the content's video at this time */
389 shared_ptr<Piece> piece = ov.back ();
390 shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
392 shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
395 list<ContentVideo> content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate);
396 if (content_video.empty ()) {
397 pvf.push_back (black_player_video_frame ());
401 dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
402 if (_approximate_size) {
403 image_size.width &= ~3;
404 image_size.height &= ~3;
407 for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
408 list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (
410 time + DCPTime::from_frames (1, _film->video_frame_rate ())
413 pvf.push_back (content_to_player_video_frame (content, *i, subs, time, image_size));
419 shared_ptr<AudioBuffers>
420 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
422 if (!_have_valid_pieces) {
426 AudioFrame const length_frames = length.frames (_film->audio_frame_rate ());
428 shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
429 audio->make_silent ();
431 list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length);
436 for (list<shared_ptr<Piece> >::iterator i = ov.begin(); i != ov.end(); ++i) {
438 shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> ((*i)->content);
440 shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
443 if (content->audio_frame_rate() == 0) {
444 /* This AudioContent has no audio (e.g. if it is an FFmpegContent with no
450 /* The time that we should request from the content */
451 DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0);
453 if (request < DCPTime ()) {
454 /* We went off the start of the content, so we will need to offset
455 the stuff we get back.
458 request = DCPTime ();
461 AudioFrame const content_frame = dcp_to_content_audio (*i, request);
463 /* Audio from this piece's decoder (which might be more or less than what we asked for) */
464 shared_ptr<ContentAudio> all = decoder->get_audio (content_frame, length_frames, accurate);
467 if (content->audio_gain() != 0) {
468 shared_ptr<AudioBuffers> gain (new AudioBuffers (all->audio));
469 gain->apply_gain (content->audio_gain ());
474 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all->audio->frames()));
475 dcp_mapped->make_silent ();
476 AudioMapping map = content->audio_mapping ();
477 for (int i = 0; i < map.content_channels(); ++i) {
478 for (int j = 0; j < _film->audio_channels(); ++j) {
479 if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
480 dcp_mapped->accumulate_channel (
484 map.get (i, static_cast<dcp::Channel> (j))
490 all->audio = dcp_mapped;
492 audio->accumulate_frames (
494 content_frame - all->frame,
495 offset.frames (_film->audio_frame_rate()),
496 min (AudioFrame (all->audio->frames()), length_frames) - offset.frames (_film->audio_frame_rate ())
504 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
506 /* s is the offset of t from the start position of this content */
507 DCPTime s = t - piece->content->position ();
508 s = DCPTime (max (int64_t (0), s.get ()));
509 s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
511 /* Convert this to the content frame */
512 return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor ();
516 Player::dcp_to_content_audio (shared_ptr<const Piece> piece, DCPTime t) const
518 /* s is the offset of t from the start position of this content */
519 DCPTime s = t - piece->content->position ();
520 s = DCPTime (max (int64_t (0), s.get ()));
521 s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
523 /* Convert this to the content frame */
524 return DCPTime (s + piece->content->trim_start()).frames (_film->audio_frame_rate());
528 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
530 /* s is the offset of t from the start position of this content */
531 DCPTime s = t - piece->content->position ();
532 s = DCPTime (max (int64_t (0), s.get ()));
533 s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
535 return ContentTime (s, piece->frc);
539 PlayerStatistics::dump (shared_ptr<Log> log) const
541 log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
542 log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds()));
545 PlayerStatistics const &
546 Player::statistics () const