Fix _audio_position after seek.
[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         /* This is the margin either side of _{video,audio}_position that we will accept
176            as a starting point for a frame consecutive to the previous.
177         */
178         DCPTime const margin = TIME_HZ / (2 * _film->video_frame_rate ());
179         
180         if (dv && _video) {
181
182                 if (_just_did_inaccurate_seek) {
183
184                         /* Just emit; no subtlety */
185                         emit_video (earliest_piece, dv);
186                         step_video_position (dv);
187                         
188                 } else if (dv->dcp_time - _video_position > margin) {
189
190                         /* Too far ahead */
191
192                         list<shared_ptr<Piece> >::iterator i = _pieces.begin();
193                         while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
194                                 ++i;
195                         }
196
197                         if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
198                                 /* We're outside all video content */
199                                 emit_black ();
200                                 _statistics.video.black++;
201                         } else {
202                                 /* We're inside some video; repeat the frame */
203                                 _last_incoming_video.video->dcp_time = _video_position;
204                                 emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
205                                 step_video_position (_last_incoming_video.video);
206                                 _statistics.video.repeat++;
207                         }
208
209                         consume = false;
210
211                 } else if (abs (dv->dcp_time - _video_position) < margin) {
212                         /* We're ok */
213                         emit_video (earliest_piece, dv);
214                         step_video_position (dv);
215                         _statistics.video.good++;
216                 } else {
217                         /* Too far behind: skip */
218                         _statistics.video.skip++;
219                 }
220
221                 _just_did_inaccurate_seek = false;
222
223         } else if (da && _audio) {
224
225                 if (da->dcp_time - _audio_position > margin) {
226                         /* Too far ahead */
227                         emit_silence (da->dcp_time - _audio_position);
228                         consume = false;
229                         _statistics.audio.silence += (da->dcp_time - _audio_position);
230                 } else if (abs (da->dcp_time - _audio_position) < margin) {
231                         /* We're ok */
232                         emit_audio (earliest_piece, da);
233                         _statistics.audio.good += da->data->frames();
234                 } else {
235                         /* Too far behind: skip */
236                         _statistics.audio.skip += da->data->frames();
237                 }
238                 
239         } else if (ds && _video) {
240                 _in_subtitle.piece = earliest_piece;
241                 _in_subtitle.subtitle = ds;
242                 update_subtitle ();
243         }
244
245         if (consume) {
246                 earliest_piece->decoder->consume ();
247         }                       
248         
249         return false;
250 }
251
252 void
253 Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video)
254 {
255         /* Keep a note of what came in so that we can repeat it if required */
256         _last_incoming_video.weak_piece = weak_piece;
257         _last_incoming_video.video = video;
258         
259         shared_ptr<Piece> piece = weak_piece.lock ();
260         if (!piece) {
261                 return;
262         }
263
264         shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
265         assert (content);
266
267         FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
268
269         float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
270         libdcp::Size image_size = fit_ratio_within (ratio, _video_container_size);
271         if (_approximate_size) {
272                 image_size.width &= ~3;
273                 image_size.height &= ~3;
274         }
275
276         shared_ptr<PlayerImage> pi (
277                 new PlayerImage (
278                         video->image,
279                         content->crop(),
280                         image_size,
281                         _video_container_size,
282                         _film->scaler()
283                         )
284                 );
285         
286         if (
287                 _film->with_subtitles () &&
288                 _out_subtitle.image &&
289                 video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to
290                 ) {
291
292                 Position<int> const container_offset (
293                         (_video_container_size.width - image_size.width) / 2,
294                         (_video_container_size.height - image_size.height) / 2
295                         );
296
297                 pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
298         }
299                                             
300 #ifdef DCPOMATIC_DEBUG
301         _last_video = piece->content;
302 #endif
303
304         Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time);
305         
306         _last_emit_was_black = false;
307 }
308
309 void
310 Player::step_video_position (shared_ptr<DecodedVideo> video)
311 {
312         /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */
313         if (video->eyes != EYES_LEFT) {
314                 /* This assumes that the video_frames_to_time conversion is exact
315                    so that there are no accumulated errors caused by rounding.
316                 */
317                 _video_position += _film->video_frames_to_time (1);
318         }
319 }
320
321 void
322 Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
323 {
324         shared_ptr<Piece> piece = weak_piece.lock ();
325         if (!piece) {
326                 return;
327         }
328
329         shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
330         assert (content);
331
332         /* Gain */
333         if (content->audio_gain() != 0) {
334                 shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data));
335                 gain->apply_gain (content->audio_gain ());
336                 audio->data = gain;
337         }
338
339         /* Remap channels */
340         shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames()));
341         dcp_mapped->make_silent ();
342         list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp ();
343         for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
344                 if (i->first < audio->data->channels() && i->second < dcp_mapped->channels()) {
345                         dcp_mapped->accumulate_channel (audio->data.get(), i->first, i->second);
346                 }
347         }
348
349         audio->data = dcp_mapped;
350
351         /* Delay */
352         audio->dcp_time += content->audio_delay() * TIME_HZ / 1000;
353         if (audio->dcp_time < 0) {
354                 int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ;
355                 if (frames >= audio->data->frames ()) {
356                         return;
357                 }
358
359                 shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames));
360                 trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0);
361
362                 audio->data = trimmed;
363                 audio->dcp_time = 0;
364         }
365
366         _audio_merger.push (audio->data, audio->dcp_time);
367 }
368
369 void
370 Player::flush ()
371 {
372         TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
373         if (_audio && tb.audio) {
374                 Audio (tb.audio, tb.time);
375                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
376         }
377
378         while (_video && _video_position < _audio_position) {
379                 emit_black ();
380         }
381
382         while (_audio && _audio_position < _video_position) {
383                 emit_silence (_video_position - _audio_position);
384         }
385         
386 }
387
388 /** Seek so that the next pass() will yield (approximately) the requested frame.
389  *  Pass accurate = true to try harder to get close to the request.
390  *  @return true on error
391  */
392 void
393 Player::seek (DCPTime t, bool accurate)
394 {
395         if (!_have_valid_pieces) {
396                 setup_pieces ();
397         }
398
399         if (_pieces.empty ()) {
400                 return;
401         }
402
403         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
404                 /* s is the offset of t from the start position of this content */
405                 DCPTime s = t - (*i)->content->position ();
406                 s = max (static_cast<DCPTime> (0), s);
407                 s = min ((*i)->content->length_after_trim(), s);
408
409                 /* Convert this to the content time */
410                 ContentTime ct = (s + (*i)->content->trim_start()) * (*i)->frc.speed_up;
411
412                 /* And seek the decoder */
413                 (*i)->decoder->seek (ct, accurate);
414         }
415
416         _video_position = time_round_up (t, TIME_HZ / _film->video_frame_rate());
417         _audio_position = time_round_up (t, TIME_HZ / _film->audio_frame_rate());
418
419         _audio_merger.clear (_audio_position);
420
421         if (!accurate) {
422                 /* We just did an inaccurate seek, so it's likely that the next thing seen
423                    out of pass() will be a fair distance from _{video,audio}_position.  Setting
424                    this flag stops pass() from trying to fix that: we assume that if it
425                    was an inaccurate seek then the caller does not care too much about
426                    inserting black/silence to keep the time tidy.
427                 */
428                 _just_did_inaccurate_seek = true;
429         }
430 }
431
432 void
433 Player::setup_pieces ()
434 {
435         list<shared_ptr<Piece> > old_pieces = _pieces;
436         _pieces.clear ();
437
438         ContentList content = _playlist->content ();
439
440         for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
441
442                 shared_ptr<Decoder> decoder;
443                 optional<FrameRateChange> frc;
444
445                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
446                 if (fc) {
447                         decoder.reset (new FFmpegDecoder (_film, fc, _video, _audio));
448                         frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
449                 }
450                 
451                 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
452                 if (ic) {
453                         /* See if we can re-use an old ImageDecoder */
454                         for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
455                                 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
456                                 if (imd && imd->content() == ic) {
457                                         decoder = imd;
458                                 }
459                         }
460
461                         if (!decoder) {
462                                 decoder.reset (new ImageDecoder (_film, ic));
463                         }
464
465                         frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
466                 }
467
468                 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
469                 if (sc) {
470                         decoder.reset (new SndfileDecoder (_film, sc));
471
472                         /* Working out the frc for this content is a bit tricky: what if it overlaps
473                            two pieces of video content with different frame rates?  For now, use
474                            the one with the best overlap.
475                         */
476
477                         DCPTime best_overlap_t = 0;
478                         shared_ptr<VideoContent> best_overlap;
479                         for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
480                                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
481                                 if (!vc) {
482                                         continue;
483                                 }
484
485                                 DCPTime const overlap = max (vc->position(), sc->position()) - min (vc->end(), sc->end());
486                                 if (overlap > best_overlap_t) {
487                                         best_overlap = vc;
488                                         best_overlap_t = overlap;
489                                 }
490                         }
491
492                         if (best_overlap) {
493                                 frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
494                         } else {
495                                 /* No video overlap; e.g. if the DCP is just audio */
496                                 frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
497                         }
498                 }
499
500                 ContentTime st = (*i)->trim_start() * frc->speed_up;
501                 decoder->seek (st, true);
502                 
503                 _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
504         }
505
506         _have_valid_pieces = true;
507
508         /* The Piece for the _last_incoming_video will no longer be valid */
509         _last_incoming_video.video.reset ();
510
511         _video_position = _audio_position = 0;
512 }
513
514 void
515 Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
516 {
517         shared_ptr<Content> c = w.lock ();
518         if (!c) {
519                 return;
520         }
521
522         if (
523                 property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
524                 property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
525                 property == VideoContentProperty::VIDEO_FRAME_TYPE 
526                 ) {
527                 
528                 _have_valid_pieces = false;
529                 Changed (frequent);
530
531         } else if (property == SubtitleContentProperty::SUBTITLE_OFFSET || property == SubtitleContentProperty::SUBTITLE_SCALE) {
532
533                 update_subtitle ();
534                 Changed (frequent);
535
536         } else if (property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_RATIO) {
537                 
538                 Changed (frequent);
539
540         } else if (property == ContentProperty::PATH) {
541
542                 Changed (frequent);
543         }
544 }
545
546 void
547 Player::playlist_changed ()
548 {
549         _have_valid_pieces = false;
550         Changed (false);
551 }
552
553 void
554 Player::set_video_container_size (libdcp::Size s)
555 {
556         _video_container_size = s;
557
558         shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
559         im->make_black ();
560         
561         _black_frame.reset (
562                 new PlayerImage (
563                         im,
564                         Crop(),
565                         _video_container_size,
566                         _video_container_size,
567                         Scaler::from_id ("bicubic")
568                         )
569                 );
570 }
571
572 void
573 Player::emit_black ()
574 {
575 #ifdef DCPOMATIC_DEBUG
576         _last_video.reset ();
577 #endif
578
579         Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
580         _video_position += _film->video_frames_to_time (1);
581         _last_emit_was_black = true;
582 }
583
584 void
585 Player::emit_silence (DCPTime most)
586 {
587         if (most == 0) {
588                 return;
589         }
590         
591         DCPTime t = min (most, TIME_HZ / 2);
592         shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), t * _film->audio_frame_rate() / TIME_HZ));
593         silence->make_silent ();
594         Audio (silence, _audio_position);
595         
596         _audio_position += t;
597 }
598
599 void
600 Player::film_changed (Film::Property p)
601 {
602         /* Here we should notice Film properties that affect our output, and
603            alert listeners that our output now would be different to how it was
604            last time we were run.
605         */
606
607         if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER) {
608                 Changed (false);
609         }
610 }
611
612 void
613 Player::update_subtitle ()
614 {
615         shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
616         if (!piece) {
617                 return;
618         }
619
620         if (!_in_subtitle.subtitle->image) {
621                 _out_subtitle.image.reset ();
622                 return;
623         }
624
625         shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
626         assert (sc);
627
628         dcpomatic::Rect<double> in_rect = _in_subtitle.subtitle->rect;
629         libdcp::Size scaled_size;
630
631         in_rect.y += sc->subtitle_offset ();
632
633         /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
634         scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale ();
635         scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale ();
636
637         /* Then we need a corrective translation, consisting of two parts:
638          *
639          * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
640          *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
641          *
642          * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
643          *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
644          *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
645          *
646          * Combining these two translations gives these expressions.
647          */
648         
649         _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
650         _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
651
652         _out_subtitle.image = _in_subtitle.subtitle->image->scale (
653                 scaled_size,
654                 Scaler::from_id ("bicubic"),
655                 PIX_FMT_RGBA,
656                 true
657                 );
658
659         _out_subtitle.from = _in_subtitle.subtitle->dcp_time;
660         _out_subtitle.to = _in_subtitle.subtitle->dcp_time_to;
661 }
662
663 /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
664  *  @return false if this could not be done.
665  */
666 bool
667 Player::repeat_last_video ()
668 {
669         if (!_last_incoming_video.video || !_have_valid_pieces) {
670                 return false;
671         }
672
673         emit_video (
674                 _last_incoming_video.weak_piece,
675                 _last_incoming_video.video
676                 );
677
678         return true;
679 }
680
681 void
682 Player::set_approximate_size ()
683 {
684         _approximate_size = true;
685 }
686                               
687
688 PlayerImage::PlayerImage (
689         shared_ptr<const Image> in,
690         Crop crop,
691         libdcp::Size inter_size,
692         libdcp::Size out_size,
693         Scaler const * scaler
694         )
695         : _in (in)
696         , _crop (crop)
697         , _inter_size (inter_size)
698         , _out_size (out_size)
699         , _scaler (scaler)
700 {
701
702 }
703
704 void
705 PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
706 {
707         _subtitle_image = image;
708         _subtitle_position = pos;
709 }
710
711 shared_ptr<Image>
712 PlayerImage::image (AVPixelFormat format, bool aligned)
713 {
714         shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned);
715         
716         Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
717
718         if (_subtitle_image) {
719                 out->alpha_blend (_subtitle_image, _subtitle_position);
720         }
721
722         return out;
723 }
724
725 void
726 PlayerStatistics::dump (shared_ptr<Log> log) const
727 {
728         log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
729         log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence));
730 }
731
732 PlayerStatistics const &
733 Player::statistics () const
734 {
735         return _statistics;
736 }