260476242fcb91acacebb87a1cb50b61d90367bc
[dcpomatic.git] / src / lib / player.cc
1 /*
2     Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
3
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.
8
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.
13
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.
17
18 */
19
20 #include <stdint.h>
21 #include <algorithm>
22 #include "player.h"
23 #include "film.h"
24 #include "ffmpeg_decoder.h"
25 #include "ffmpeg_content.h"
26 #include "image_decoder.h"
27 #include "image_content.h"
28 #include "sndfile_decoder.h"
29 #include "sndfile_content.h"
30 #include "subtitle_content.h"
31 #include "playlist.h"
32 #include "job.h"
33 #include "image.h"
34 #include "ratio.h"
35 #include "log.h"
36 #include "scaler.h"
37
38 using std::list;
39 using std::cout;
40 using std::min;
41 using std::max;
42 using std::vector;
43 using std::pair;
44 using std::map;
45 using boost::shared_ptr;
46 using boost::weak_ptr;
47 using boost::dynamic_pointer_cast;
48 using boost::optional;
49
50 class Piece
51 {
52 public:
53         Piece (shared_ptr<Content> c, shared_ptr<Decoder> d, FrameRateChange f)
54                 : content (c)
55                 , decoder (d)
56                 , frc (f)
57         {}
58
59         shared_ptr<Content> content;
60         shared_ptr<Decoder> decoder;
61         FrameRateChange frc;
62 };
63
64 Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
65         : _film (f)
66         , _playlist (p)
67         , _video (true)
68         , _audio (true)
69         , _have_valid_pieces (false)
70         , _video_position (0)
71         , _audio_position (0)
72         , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
73         , _last_emit_was_black (false)
74         , _just_did_inaccurate_seek (false)
75         , _approximate_size (false)
76 {
77         _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
78         _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
79         _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
80         set_video_container_size (fit_ratio_within (_film->container()->ratio (), _film->full_frame ()));
81 }
82
83 void
84 Player::disable_video ()
85 {
86         _video = false;
87 }
88
89 void
90 Player::disable_audio ()
91 {
92         _audio = false;
93 }
94
95 bool
96 Player::pass ()
97 {
98         if (!_have_valid_pieces) {
99                 setup_pieces ();
100         }
101
102         /* Interrogate all our pieces to find the one with the earliest decoded data */
103
104         shared_ptr<Piece> earliest_piece;
105         shared_ptr<Decoded> earliest_decoded;
106         DCPTime earliest_time = TIME_MAX;
107         DCPTime earliest_audio = TIME_MAX;
108
109         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
110
111                 DCPTime const offset = (*i)->content->position() - (*i)->content->trim_start();
112                 
113                 bool done = false;
114                 shared_ptr<Decoded> dec;
115                 while (!done) {
116                         dec = (*i)->decoder->peek ();
117                         if (!dec) {
118                                 /* Decoder has nothing else to give us */
119                                 break;
120                         }
121
122                         dec->set_dcp_times (_film->video_frame_rate(), _film->audio_frame_rate(), (*i)->frc, offset);
123                         DCPTime const t = dec->dcp_time - offset;
124                         if (t >= ((*i)->content->full_length() - (*i)->content->trim_end ())) {
125                                 /* In the end-trimmed part; decoder has nothing else to give us */
126                                 dec.reset ();
127                                 done = true;
128                         } else if (t >= (*i)->content->trim_start ()) {
129                                 /* Within the un-trimmed part; everything's ok */
130                                 done = true;
131                         } else {
132                                 /* Within the start-trimmed part; get something else */
133                                 (*i)->decoder->consume ();
134                         }
135                 }
136
137                 if (!dec) {
138                         continue;
139                 }
140
141                 if (dec->dcp_time < earliest_time) {
142                         earliest_piece = *i;
143                         earliest_decoded = dec;
144                         earliest_time = dec->dcp_time;
145                 }
146
147                 if (dynamic_pointer_cast<DecodedAudio> (dec) && dec->dcp_time < earliest_audio) {
148                         earliest_audio = dec->dcp_time;
149                 }
150         }
151                 
152         if (!earliest_piece) {
153                 flush ();
154                 return true;
155         }
156
157         if (earliest_audio != TIME_MAX) {
158                 TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (max (int64_t (0), earliest_audio));
159                 Audio (tb.audio, tb.time);
160                 /* This assumes that the audio_frames_to_time conversion is exact
161                    so that there are no accumulated errors caused by rounding.
162                 */
163                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
164         }
165
166         /* Emit the earliest thing */
167
168         shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded);
169         shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded);
170         shared_ptr<DecodedSubtitle> ds = dynamic_pointer_cast<DecodedSubtitle> (earliest_decoded);
171
172         /* Will be set to false if we shouldn't consume the peeked DecodedThing */
173         bool consume = true;
174
175         if (dv && _video) {
176
177                 if (_just_did_inaccurate_seek) {
178
179                         /* Just emit; no subtlety */
180                         emit_video (earliest_piece, dv);
181                         step_video_position (dv);
182                         
183                 } else if (dv->dcp_time > _video_position) {
184
185                         /* Too far ahead */
186
187                         list<shared_ptr<Piece> >::iterator i = _pieces.begin();
188                         while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
189                                 ++i;
190                         }
191
192                         if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
193                                 /* We're outside all video content */
194                                 emit_black ();
195                                 _statistics.video.black++;
196                         } else {
197                                 /* We're inside some video; repeat the frame */
198                                 _last_incoming_video.video->dcp_time = _video_position;
199                                 emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
200                                 step_video_position (_last_incoming_video.video);
201                                 _statistics.video.repeat++;
202                         }
203
204                         consume = false;
205
206                 } else if (dv->dcp_time == _video_position) {
207                         /* We're ok */
208                         emit_video (earliest_piece, dv);
209                         step_video_position (dv);
210                         _statistics.video.good++;
211                 } else {
212                         /* Too far behind: skip */
213                         _statistics.video.skip++;
214                 }
215
216                 _just_did_inaccurate_seek = false;
217
218         } else if (da && _audio) {
219
220                 if (da->dcp_time > _audio_position) {
221                         /* Too far ahead */
222                         emit_silence (da->dcp_time - _audio_position);
223                         consume = false;
224                         _statistics.audio.silence += (da->dcp_time - _audio_position);
225                 } else if (da->dcp_time == _audio_position) {
226                         /* We're ok */
227                         emit_audio (earliest_piece, da);
228                         _statistics.audio.good += da->data->frames();
229                 } else {
230                         /* Too far behind: skip */
231                         _statistics.audio.skip += da->data->frames();
232                 }
233                 
234         } else if (ds && _video) {
235                 _in_subtitle.piece = earliest_piece;
236                 _in_subtitle.subtitle = ds;
237                 update_subtitle ();
238         }
239
240         if (consume) {
241                 earliest_piece->decoder->consume ();
242         }                       
243         
244         return false;
245 }
246
247 void
248 Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video)
249 {
250         /* Keep a note of what came in so that we can repeat it if required */
251         _last_incoming_video.weak_piece = weak_piece;
252         _last_incoming_video.video = video;
253         
254         shared_ptr<Piece> piece = weak_piece.lock ();
255         if (!piece) {
256                 return;
257         }
258
259         shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
260         assert (content);
261
262         FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
263
264         float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
265         libdcp::Size image_size = fit_ratio_within (ratio, _video_container_size);
266         if (_approximate_size) {
267                 image_size.width &= ~3;
268                 image_size.height &= ~3;
269         }
270
271         shared_ptr<PlayerImage> pi (
272                 new PlayerImage (
273                         video->image,
274                         content->crop(),
275                         image_size,
276                         _video_container_size,
277                         _film->scaler()
278                         )
279                 );
280         
281         if (
282                 _film->with_subtitles () &&
283                 _out_subtitle.image &&
284                 video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to
285                 ) {
286
287                 Position<int> const container_offset (
288                         (_video_container_size.width - image_size.width) / 2,
289                         (_video_container_size.height - image_size.height) / 2
290                         );
291
292                 pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
293         }
294                                             
295 #ifdef DCPOMATIC_DEBUG
296         _last_video = piece->content;
297 #endif
298
299         Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time);
300         
301         _last_emit_was_black = false;
302 }
303
304 void
305 Player::step_video_position (shared_ptr<DecodedVideo> video)
306 {
307         /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */
308         if (video->eyes != EYES_LEFT) {
309                 /* This assumes that the video_frames_to_time conversion is exact
310                    so that there are no accumulated errors caused by rounding.
311                 */
312                 _video_position += _film->video_frames_to_time (1);
313         }
314 }
315
316 void
317 Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
318 {
319         shared_ptr<Piece> piece = weak_piece.lock ();
320         if (!piece) {
321                 return;
322         }
323
324         shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
325         assert (content);
326
327         /* Gain */
328         if (content->audio_gain() != 0) {
329                 shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data));
330                 gain->apply_gain (content->audio_gain ());
331                 audio->data = gain;
332         }
333
334         /* Remap channels */
335         shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames()));
336         dcp_mapped->make_silent ();
337         AudioMapping map = content->audio_mapping ();
338         for (int i = 0; i < map.content_channels(); ++i) {
339                 for (int j = 0; j < _film->audio_channels(); ++j) {
340                         if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) {
341                                 dcp_mapped->accumulate_channel (
342                                         audio->data.get(),
343                                         i,
344                                         static_cast<libdcp::Channel> (j),
345                                         map.get (i, static_cast<libdcp::Channel> (j))
346                                         );
347                         }
348                 }
349         }
350
351         audio->data = dcp_mapped;
352
353         /* Delay */
354         audio->dcp_time += content->audio_delay() * TIME_HZ / 1000;
355         if (audio->dcp_time < 0) {
356                 int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ;
357                 if (frames >= audio->data->frames ()) {
358                         return;
359                 }
360
361                 shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames));
362                 trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0);
363
364                 audio->data = trimmed;
365                 audio->dcp_time = 0;
366         }
367
368         _audio_merger.push (audio->data, audio->dcp_time);
369 }
370
371 void
372 Player::flush ()
373 {
374         TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
375         if (_audio && tb.audio) {
376                 Audio (tb.audio, tb.time);
377                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
378         }
379
380         while (_video && _video_position < _audio_position) {
381                 emit_black ();
382         }
383
384         while (_audio && _audio_position < _video_position) {
385                 emit_silence (_video_position - _audio_position);
386         }
387         
388 }
389
390 /** Seek so that the next pass() will yield (approximately) the requested frame.
391  *  Pass accurate = true to try harder to get close to the request.
392  *  @return true on error
393  */
394 void
395 Player::seek (DCPTime t, bool accurate)
396 {
397         if (!_have_valid_pieces) {
398                 setup_pieces ();
399         }
400
401         if (_pieces.empty ()) {
402                 return;
403         }
404
405         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
406                 /* s is the offset of t from the start position of this content */
407                 DCPTime s = t - (*i)->content->position ();
408                 s = max (static_cast<DCPTime> (0), s);
409                 s = min ((*i)->content->length_after_trim(), s);
410
411                 /* Convert this to the content time */
412                 ContentTime ct = (s + (*i)->content->trim_start()) * (*i)->frc.speed_up;
413
414                 /* And seek the decoder */
415                 (*i)->decoder->seek (ct, accurate);
416         }
417
418         _video_position = time_round_up (t, TIME_HZ / _film->video_frame_rate());
419         _audio_position = time_round_up (t, TIME_HZ / _film->audio_frame_rate());
420
421         _audio_merger.clear (_audio_position);
422
423         if (!accurate) {
424                 /* We just did an inaccurate seek, so it's likely that the next thing seen
425                    out of pass() will be a fair distance from _{video,audio}_position.  Setting
426                    this flag stops pass() from trying to fix that: we assume that if it
427                    was an inaccurate seek then the caller does not care too much about
428                    inserting black/silence to keep the time tidy.
429                 */
430                 _just_did_inaccurate_seek = true;
431         }
432 }
433
434 void
435 Player::setup_pieces ()
436 {
437         list<shared_ptr<Piece> > old_pieces = _pieces;
438         _pieces.clear ();
439
440         ContentList content = _playlist->content ();
441
442         for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
443
444                 shared_ptr<Decoder> decoder;
445                 optional<FrameRateChange> frc;
446
447                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
448                 if (fc) {
449                         decoder.reset (new FFmpegDecoder (_film, fc, _video, _audio));
450                         frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
451                 }
452                 
453                 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
454                 if (ic) {
455                         /* See if we can re-use an old ImageDecoder */
456                         for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
457                                 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
458                                 if (imd && imd->content() == ic) {
459                                         decoder = imd;
460                                 }
461                         }
462
463                         if (!decoder) {
464                                 decoder.reset (new ImageDecoder (_film, ic));
465                         }
466
467                         frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
468                 }
469
470                 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
471                 if (sc) {
472                         decoder.reset (new SndfileDecoder (_film, sc));
473
474                         /* Working out the frc for this content is a bit tricky: what if it overlaps
475                            two pieces of video content with different frame rates?  For now, use
476                            the one with the best overlap.
477                         */
478
479                         DCPTime best_overlap_t = 0;
480                         shared_ptr<VideoContent> best_overlap;
481                         for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
482                                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
483                                 if (!vc) {
484                                         continue;
485                                 }
486
487                                 DCPTime const overlap = max (vc->position(), sc->position()) - min (vc->end(), sc->end());
488                                 if (overlap > best_overlap_t) {
489                                         best_overlap = vc;
490                                         best_overlap_t = overlap;
491                                 }
492                         }
493
494                         if (best_overlap) {
495                                 frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
496                         } else {
497                                 /* No video overlap; e.g. if the DCP is just audio */
498                                 frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
499                         }
500                 }
501
502                 ContentTime st = (*i)->trim_start() * frc->speed_up;
503                 decoder->seek (st, true);
504                 
505                 _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
506         }
507
508         _have_valid_pieces = true;
509
510         /* The Piece for the _last_incoming_video will no longer be valid */
511         _last_incoming_video.video.reset ();
512
513         _video_position = _audio_position = 0;
514 }
515
516 void
517 Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
518 {
519         shared_ptr<Content> c = w.lock ();
520         if (!c) {
521                 return;
522         }
523
524         if (
525                 property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
526                 property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
527                 property == VideoContentProperty::VIDEO_FRAME_TYPE 
528                 ) {
529                 
530                 _have_valid_pieces = false;
531                 Changed (frequent);
532
533         } else if (property == SubtitleContentProperty::SUBTITLE_OFFSET || property == SubtitleContentProperty::SUBTITLE_SCALE) {
534
535                 update_subtitle ();
536                 Changed (frequent);
537
538         } else if (
539                 property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_RATIO ||
540                 property == VideoContentProperty::VIDEO_FRAME_RATE
541                 ) {
542                 
543                 Changed (frequent);
544
545         } else if (property == ContentProperty::PATH) {
546
547                 Changed (frequent);
548         }
549 }
550
551 void
552 Player::playlist_changed ()
553 {
554         _have_valid_pieces = false;
555         Changed (false);
556 }
557
558 void
559 Player::set_video_container_size (libdcp::Size s)
560 {
561         _video_container_size = s;
562
563         shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
564         im->make_black ();
565         
566         _black_frame.reset (
567                 new PlayerImage (
568                         im,
569                         Crop(),
570                         _video_container_size,
571                         _video_container_size,
572                         Scaler::from_id ("bicubic")
573                         )
574                 );
575 }
576
577 void
578 Player::emit_black ()
579 {
580 #ifdef DCPOMATIC_DEBUG
581         _last_video.reset ();
582 #endif
583
584         Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
585         _video_position += _film->video_frames_to_time (1);
586         _last_emit_was_black = true;
587 }
588
589 void
590 Player::emit_silence (DCPTime most)
591 {
592         if (most == 0) {
593                 return;
594         }
595         
596         DCPTime t = min (most, TIME_HZ / 2);
597         shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), t * _film->audio_frame_rate() / TIME_HZ));
598         silence->make_silent ();
599         Audio (silence, _audio_position);
600         
601         _audio_position += t;
602 }
603
604 void
605 Player::film_changed (Film::Property p)
606 {
607         /* Here we should notice Film properties that affect our output, and
608            alert listeners that our output now would be different to how it was
609            last time we were run.
610         */
611
612         if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
613                 Changed (false);
614         }
615 }
616
617 void
618 Player::update_subtitle ()
619 {
620         shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
621         if (!piece) {
622                 return;
623         }
624
625         if (!_in_subtitle.subtitle->image) {
626                 _out_subtitle.image.reset ();
627                 return;
628         }
629
630         shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
631         assert (sc);
632
633         dcpomatic::Rect<double> in_rect = _in_subtitle.subtitle->rect;
634         libdcp::Size scaled_size;
635
636         in_rect.y += sc->subtitle_offset ();
637
638         /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
639         scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale ();
640         scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale ();
641
642         /* Then we need a corrective translation, consisting of two parts:
643          *
644          * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
645          *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
646          *
647          * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
648          *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
649          *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
650          *
651          * Combining these two translations gives these expressions.
652          */
653         
654         _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
655         _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
656
657         _out_subtitle.image = _in_subtitle.subtitle->image->scale (
658                 scaled_size,
659                 Scaler::from_id ("bicubic"),
660                 PIX_FMT_RGBA,
661                 true
662                 );
663
664         _out_subtitle.from = _in_subtitle.subtitle->dcp_time;
665         _out_subtitle.to = _in_subtitle.subtitle->dcp_time_to;
666 }
667
668 /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
669  *  @return false if this could not be done.
670  */
671 bool
672 Player::repeat_last_video ()
673 {
674         if (!_last_incoming_video.video || !_have_valid_pieces) {
675                 return false;
676         }
677
678         emit_video (
679                 _last_incoming_video.weak_piece,
680                 _last_incoming_video.video
681                 );
682
683         return true;
684 }
685
686 void
687 Player::set_approximate_size ()
688 {
689         _approximate_size = true;
690 }
691                               
692
693 PlayerImage::PlayerImage (
694         shared_ptr<const Image> in,
695         Crop crop,
696         libdcp::Size inter_size,
697         libdcp::Size out_size,
698         Scaler const * scaler
699         )
700         : _in (in)
701         , _crop (crop)
702         , _inter_size (inter_size)
703         , _out_size (out_size)
704         , _scaler (scaler)
705 {
706
707 }
708
709 void
710 PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
711 {
712         _subtitle_image = image;
713         _subtitle_position = pos;
714 }
715
716 shared_ptr<Image>
717 PlayerImage::image (AVPixelFormat format, bool aligned)
718 {
719         shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned);
720         
721         Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
722
723         if (_subtitle_image) {
724                 out->alpha_blend (_subtitle_image, _subtitle_position);
725         }
726
727         return out;
728 }
729
730 void
731 PlayerStatistics::dump (shared_ptr<Log> log) const
732 {
733         log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
734         log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence));
735 }
736
737 PlayerStatistics const &
738 Player::statistics () const
739 {
740         return _statistics;
741 }