2 Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
23 #include "audio_buffers.h"
24 #include "content_audio.h"
25 #include "dcp_content.h"
28 #include "raw_image_proxy.h"
31 #include "render_subtitles.h"
33 #include "content_video.h"
34 #include "player_video.h"
35 #include "frame_rate_change.h"
36 #include "audio_processor.h"
38 #include "referenced_reel_asset.h"
39 #include "decoder_factory.h"
41 #include "video_decoder.h"
42 #include "audio_decoder.h"
43 #include "subtitle_content.h"
44 #include "subtitle_decoder.h"
45 #include "ffmpeg_content.h"
46 #include "audio_content.h"
47 #include "content_subtitle.h"
48 #include "dcp_decoder.h"
49 #include "image_decoder.h"
50 #include "resampler.h"
51 #include "compose.hpp"
53 #include <dcp/reel_sound_asset.h>
54 #include <dcp/reel_subtitle_asset.h>
55 #include <dcp/reel_picture_asset.h>
56 #include <boost/foreach.hpp>
63 #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
75 using boost::shared_ptr;
76 using boost::weak_ptr;
77 using boost::dynamic_pointer_cast;
78 using boost::optional;
79 using boost::scoped_ptr;
81 Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist)
83 , _playlist (playlist)
84 , _have_valid_pieces (false)
85 , _ignore_video (false)
86 , _ignore_audio (false)
87 , _always_burn_subtitles (false)
89 , _play_referenced (false)
90 , _audio_merger (_film->audio_channels(), _film->audio_frame_rate())
92 _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
93 _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
94 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::playlist_content_changed, this, _1, _2, _3));
95 set_video_container_size (_film->frame_size ());
97 film_changed (Film::AUDIO_PROCESSOR);
101 Player::setup_pieces ()
105 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
107 if (!i->paths_valid ()) {
111 shared_ptr<Decoder> decoder = decoder_factory (i, _film->log());
112 FrameRateChange frc (i->active_video_frame_rate(), _film->video_frame_rate());
115 /* Not something that we can decode; e.g. Atmos content */
119 if (decoder->video && _ignore_video) {
120 decoder->video->set_ignore ();
123 if (decoder->audio && _ignore_audio) {
124 decoder->audio->set_ignore ();
127 shared_ptr<DCPDecoder> dcp = dynamic_pointer_cast<DCPDecoder> (decoder);
128 if (dcp && _play_referenced) {
129 dcp->set_decode_referenced ();
132 shared_ptr<Piece> piece (new Piece (i, decoder, frc));
133 _pieces.push_back (piece);
135 if (decoder->video) {
136 decoder->video->Data.connect (bind (&Player::video, this, weak_ptr<Piece> (piece), _1));
139 if (decoder->audio) {
140 decoder->audio->Data.connect (bind (&Player::audio, this, weak_ptr<Piece> (piece), _1, _2));
143 if (decoder->subtitle) {
144 decoder->subtitle->ImageData.connect (bind (&Player::image_subtitle, this, weak_ptr<Piece> (piece), _1));
145 decoder->subtitle->TextData.connect (bind (&Player::text_subtitle, this, weak_ptr<Piece> (piece), _1));
149 _have_valid_pieces = true;
153 Player::playlist_content_changed (weak_ptr<Content> w, int property, bool frequent)
155 shared_ptr<Content> c = w.lock ();
161 property == ContentProperty::POSITION ||
162 property == ContentProperty::LENGTH ||
163 property == ContentProperty::TRIM_START ||
164 property == ContentProperty::TRIM_END ||
165 property == ContentProperty::PATH ||
166 property == VideoContentProperty::FRAME_TYPE ||
167 property == DCPContentProperty::NEEDS_ASSETS ||
168 property == DCPContentProperty::NEEDS_KDM ||
169 property == SubtitleContentProperty::COLOUR ||
170 property == SubtitleContentProperty::OUTLINE ||
171 property == SubtitleContentProperty::SHADOW ||
172 property == SubtitleContentProperty::EFFECT_COLOUR ||
173 property == FFmpegContentProperty::SUBTITLE_STREAM ||
174 property == VideoContentProperty::COLOUR_CONVERSION
177 _have_valid_pieces = false;
181 property == SubtitleContentProperty::LINE_SPACING ||
182 property == SubtitleContentProperty::OUTLINE_WIDTH ||
183 property == SubtitleContentProperty::Y_SCALE ||
184 property == SubtitleContentProperty::FADE_IN ||
185 property == SubtitleContentProperty::FADE_OUT ||
186 property == ContentProperty::VIDEO_FRAME_RATE ||
187 property == SubtitleContentProperty::USE ||
188 property == SubtitleContentProperty::X_OFFSET ||
189 property == SubtitleContentProperty::Y_OFFSET ||
190 property == SubtitleContentProperty::X_SCALE ||
191 property == SubtitleContentProperty::FONTS ||
192 property == VideoContentProperty::CROP ||
193 property == VideoContentProperty::SCALE ||
194 property == VideoContentProperty::FADE_IN ||
195 property == VideoContentProperty::FADE_OUT
203 Player::set_video_container_size (dcp::Size s)
205 _video_container_size = s;
207 _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
208 _black_image->make_black ();
212 Player::playlist_changed ()
214 _have_valid_pieces = false;
219 Player::film_changed (Film::Property p)
221 /* Here we should notice Film properties that affect our output, and
222 alert listeners that our output now would be different to how it was
223 last time we were run.
226 if (p == Film::CONTAINER) {
228 } else if (p == Film::VIDEO_FRAME_RATE) {
229 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
230 so we need new pieces here.
232 _have_valid_pieces = false;
234 } else if (p == Film::AUDIO_PROCESSOR) {
235 if (_film->audio_processor ()) {
236 _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
242 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
244 list<PositionImage> all;
246 for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
251 /* We will scale the subtitle up to fit _video_container_size */
252 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
254 /* Then we need a corrective translation, consisting of two parts:
256 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
257 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
259 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
260 * (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
261 * (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
263 * Combining these two translations gives these expressions.
270 dcp::YUV_TO_RGB_REC601,
271 i->image->pixel_format (),
276 lrint (_video_container_size.width * i->rectangle.x),
277 lrint (_video_container_size.height * i->rectangle.y)
286 shared_ptr<PlayerVideo>
287 Player::black_player_video_frame () const
289 return shared_ptr<PlayerVideo> (
291 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
294 _video_container_size,
295 _video_container_size,
298 PresetColourConversion::all().front().conversion
304 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
306 DCPTime s = t - piece->content->position ();
307 s = min (piece->content->length_after_trim(), s);
308 s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
310 /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
311 then convert that ContentTime to frames at the content's rate. However this fails for
312 situations like content at 29.9978733fps, DCP at 30fps. The accuracy of the Time type is not
313 enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
315 Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
317 return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
321 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
323 /* See comment in dcp_to_content_video */
324 DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
325 return max (DCPTime (), d + piece->content->position ());
329 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
331 DCPTime s = t - piece->content->position ();
332 s = min (piece->content->length_after_trim(), s);
333 /* See notes in dcp_to_content_video */
334 return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
338 Player::resampled_audio_to_dcp (shared_ptr<const Piece> piece, Frame f) const
340 /* See comment in dcp_to_content_video */
341 DCPTime const d = DCPTime::from_frames (f, _film->audio_frame_rate()) - DCPTime (piece->content->trim_start (), piece->frc);
342 return max (DCPTime (), d + piece->content->position ());
346 Player::dcp_to_content_time (shared_ptr<const Piece> piece, DCPTime t) const
348 DCPTime s = t - piece->content->position ();
349 s = min (piece->content->length_after_trim(), s);
350 return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
354 Player::content_time_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
356 return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
359 list<shared_ptr<Font> >
360 Player::get_subtitle_fonts ()
362 if (!_have_valid_pieces) {
366 list<shared_ptr<Font> > fonts;
367 BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
368 if (p->content->subtitle) {
369 /* XXX: things may go wrong if there are duplicate font IDs
370 with different font files.
372 list<shared_ptr<Font> > f = p->content->subtitle->fonts ();
373 copy (f.begin(), f.end(), back_inserter (fonts));
380 /** Set this player never to produce any video data */
382 Player::set_ignore_video ()
384 _ignore_video = true;
387 /** Set this player never to produce any audio data */
389 Player::set_ignore_audio ()
391 _ignore_audio = true;
394 /** Set whether or not this player should always burn text subtitles into the image,
395 * regardless of the content settings.
396 * @param burn true to always burn subtitles, false to obey content settings.
399 Player::set_always_burn_subtitles (bool burn)
401 _always_burn_subtitles = burn;
408 _have_valid_pieces = false;
412 Player::set_play_referenced ()
414 _play_referenced = true;
415 _have_valid_pieces = false;
418 list<ReferencedReelAsset>
419 Player::get_reel_assets ()
421 list<ReferencedReelAsset> a;
423 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
424 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
429 scoped_ptr<DCPDecoder> decoder;
431 decoder.reset (new DCPDecoder (j, _film->log()));
437 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
439 DCPOMATIC_ASSERT (j->video_frame_rate ());
440 double const cfr = j->video_frame_rate().get();
441 Frame const trim_start = j->trim_start().frames_round (cfr);
442 Frame const trim_end = j->trim_end().frames_round (cfr);
443 int const ffr = _film->video_frame_rate ();
445 DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
446 if (j->reference_video ()) {
447 shared_ptr<dcp::ReelAsset> ra = k->main_picture ();
448 DCPOMATIC_ASSERT (ra);
449 ra->set_entry_point (ra->entry_point() + trim_start);
450 ra->set_duration (ra->duration() - trim_start - trim_end);
452 ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
456 if (j->reference_audio ()) {
457 shared_ptr<dcp::ReelAsset> ra = k->main_sound ();
458 DCPOMATIC_ASSERT (ra);
459 ra->set_entry_point (ra->entry_point() + trim_start);
460 ra->set_duration (ra->duration() - trim_start - trim_end);
462 ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
466 if (j->reference_subtitle ()) {
467 shared_ptr<dcp::ReelAsset> ra = k->main_subtitle ();
468 DCPOMATIC_ASSERT (ra);
469 ra->set_entry_point (ra->entry_point() + trim_start);
470 ra->set_duration (ra->duration() - trim_start - trim_end);
472 ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
476 /* Assume that main picture duration is the length of the reel */
477 offset += k->main_picture()->duration ();
484 list<shared_ptr<Piece> >
485 Player::overlaps (DCPTime from, DCPTime to, boost::function<bool (Content *)> valid)
487 if (!_have_valid_pieces) {
491 list<shared_ptr<Piece> > overlaps;
492 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
493 if (valid (i->content.get ()) && i->content->position() < to && i->content->end() > from) {
494 overlaps.push_back (i);
504 if (!_have_valid_pieces) {
508 shared_ptr<Piece> earliest;
509 DCPTime earliest_content;
511 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
513 DCPTime const t = i->content->position() + DCPTime (i->decoder->position(), i->frc);
514 if (!earliest || t < earliest_content) {
515 earliest_content = t;
522 /* XXX: fill up to the length of Playlist with black / silence */
526 earliest->done = earliest->decoder->pass ();
528 /* Emit any audio that is ready */
530 optional<DCPTime> earliest_audio;
531 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
532 if (i->decoder->audio) {
533 DCPTime const t = i->content->position() + DCPTime (i->decoder->audio->position(), i->frc);
534 if (!earliest_audio || t < *earliest_audio) {
540 pair<shared_ptr<AudioBuffers>, DCPTime> audio = _audio_merger.pull (earliest_audio.get_value_or(DCPTime()));
541 if (audio.first->frames() > 0) {
542 DCPOMATIC_ASSERT (audio.second >= _last_audio_time);
543 DCPTime t = _last_audio_time;
544 while (t < audio.second) {
545 /* Silence up to the time of this new audio */
546 DCPTime block = min (DCPTime::from_seconds (0.5), audio.second - t);
547 shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), block.frames_round(_film->audio_frame_rate())));
548 silence->make_silent ();
553 Audio (audio.first, audio.second);
554 _last_audio_time = audio.second;
561 Player::video (weak_ptr<Piece> wp, ContentVideo video)
563 shared_ptr<Piece> piece = wp.lock ();
568 /* Time and period of the frame we will emit */
569 DCPTime const time = content_video_to_dcp (piece, video.frame);
570 DCPTimePeriod const period (time, time + DCPTime::from_frames (1, _film->video_frame_rate()));
572 /* Discard if it's outside the content's period */
573 if (time < piece->content->position() || time >= piece->content->end()) {
577 /* Get any subtitles */
579 optional<PositionImage> subtitles;
581 for (list<pair<PlayerSubtitles, DCPTimePeriod> >::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
583 if (!i->second.overlap (period)) {
587 list<PositionImage> sub_images;
589 /* Image subtitles */
590 list<PositionImage> c = transform_image_subtitles (i->first.image);
591 copy (c.begin(), c.end(), back_inserter (sub_images));
593 /* Text subtitles (rendered to an image) */
594 if (!i->first.text.empty ()) {
595 list<PositionImage> s = render_subtitles (i->first.text, i->first.fonts, _video_container_size, time);
596 copy (s.begin (), s.end (), back_inserter (sub_images));
599 if (!sub_images.empty ()) {
600 subtitles = merge (sub_images);
607 /* XXX: this may not work for 3D */
608 DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate());
609 for (DCPTime i = _last_time.get() + frame; i < time; i += frame) {
610 if (_playlist->video_content_at(i) && _last_video) {
611 Video (shared_ptr<PlayerVideo> (new PlayerVideo (*_last_video)), i);
613 Video (black_player_video_frame (), i);
621 piece->content->video->crop (),
622 piece->content->video->fade (video.frame),
623 piece->content->video->scale().size (
624 piece->content->video, _video_container_size, _film->frame_size ()
626 _video_container_size,
629 piece->content->video->colour_conversion ()
634 _last_video->set_subtitle (subtitles.get ());
639 Video (_last_video, *_last_time);
641 /* Discard any subtitles we no longer need */
643 for (list<pair<PlayerSubtitles, DCPTimePeriod> >::iterator i = _subtitles.begin (); i != _subtitles.end(); ) {
644 list<pair<PlayerSubtitles, DCPTimePeriod> >::iterator tmp = i;
647 if (i->second.to < time) {
648 _subtitles.erase (i);
656 Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_audio)
658 shared_ptr<Piece> piece = wp.lock ();
663 shared_ptr<AudioContent> content = piece->content->audio;
664 DCPOMATIC_ASSERT (content);
667 if (content->gain() != 0) {
668 shared_ptr<AudioBuffers> gain (new AudioBuffers (content_audio.audio));
669 gain->apply_gain (content->gain ());
670 content_audio.audio = gain;
674 if (stream->frame_rate() != content->resampled_frame_rate()) {
675 shared_ptr<Resampler> r = resampler (content, stream, true);
676 pair<shared_ptr<const AudioBuffers>, Frame> ro = r->run (content_audio.audio, content_audio.frame);
677 content_audio.audio = ro.first;
678 content_audio.frame = ro.second;
681 /* XXX: end-trimming used to be checked here */
683 /* Compute time in the DCP */
684 DCPTime time = resampled_audio_to_dcp (piece, content_audio.frame) + DCPTime::from_seconds (content->delay() / 1000);
687 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), content_audio.audio->frames()));
688 dcp_mapped->make_silent ();
690 AudioMapping map = stream->mapping ();
691 for (int i = 0; i < map.input_channels(); ++i) {
692 for (int j = 0; j < dcp_mapped->channels(); ++j) {
693 if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
694 dcp_mapped->accumulate_channel (
695 content_audio.audio.get(),
697 static_cast<dcp::Channel> (j),
698 map.get (i, static_cast<dcp::Channel> (j))
704 content_audio.audio = dcp_mapped;
706 if (_audio_processor) {
707 content_audio.audio = _audio_processor->run (content_audio.audio, _film->audio_channels ());
710 /* XXX: this may be nonsense */
711 if (time < _audio_merger.last_pull()) {
712 DCPTime const discard_time = _audio_merger.last_pull() - time;
713 Frame discard_frames = discard_time.frames_round(_film->audio_frame_rate());
714 content_audio.audio.reset (new AudioBuffers (_film->audio_channels(), content_audio.audio->frames() - discard_frames));
715 time += discard_time;
718 if (content_audio.audio->frames() > 0) {
719 _audio_merger.push (content_audio.audio, time);
724 Player::image_subtitle (weak_ptr<Piece> wp, ContentImageSubtitle subtitle)
726 shared_ptr<Piece> piece = wp.lock ();
731 /* Apply content's subtitle offsets */
732 subtitle.sub.rectangle.x += piece->content->subtitle->x_offset ();
733 subtitle.sub.rectangle.y += piece->content->subtitle->y_offset ();
735 /* Apply content's subtitle scale */
736 subtitle.sub.rectangle.width *= piece->content->subtitle->x_scale ();
737 subtitle.sub.rectangle.height *= piece->content->subtitle->y_scale ();
739 /* Apply a corrective translation to keep the subtitle centred after that scale */
740 subtitle.sub.rectangle.x -= subtitle.sub.rectangle.width * (piece->content->subtitle->x_scale() - 1);
741 subtitle.sub.rectangle.y -= subtitle.sub.rectangle.height * (piece->content->subtitle->y_scale() - 1);
744 ps.image.push_back (subtitle.sub);
745 DCPTimePeriod period (content_time_to_dcp (piece, subtitle.period().from), content_time_to_dcp (piece, subtitle.period().to));
747 if (piece->content->subtitle->use() && (piece->content->subtitle->burn() || _always_burn_subtitles)) {
748 _subtitles.push_back (make_pair (ps, period));
750 Subtitle (ps, period);
755 Player::text_subtitle (weak_ptr<Piece> wp, ContentTextSubtitle subtitle)
757 shared_ptr<Piece> piece = wp.lock ();
763 DCPTimePeriod const period (content_time_to_dcp (piece, subtitle.period().from), content_time_to_dcp (piece, subtitle.period().to));
765 BOOST_FOREACH (dcp::SubtitleString s, subtitle.subs) {
766 s.set_h_position (s.h_position() + piece->content->subtitle->x_offset ());
767 s.set_v_position (s.v_position() + piece->content->subtitle->y_offset ());
768 float const xs = piece->content->subtitle->x_scale();
769 float const ys = piece->content->subtitle->y_scale();
770 float size = s.size();
772 /* Adjust size to express the common part of the scaling;
773 e.g. if xs = ys = 0.5 we scale size by 2.
775 if (xs > 1e-5 && ys > 1e-5) {
776 size *= 1 / min (1 / xs, 1 / ys);
780 /* Then express aspect ratio changes */
781 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
782 s.set_aspect_adjust (xs / ys);
785 s.set_in (dcp::Time(period.from.seconds(), 1000));
786 s.set_out (dcp::Time(period.to.seconds(), 1000));
787 ps.text.push_back (SubtitleString (s, piece->content->subtitle->outline_width()));
788 ps.add_fonts (piece->content->subtitle->fonts ());
791 if (piece->content->subtitle->use() && (piece->content->subtitle->burn() || _always_burn_subtitles)) {
792 _subtitles.push_back (make_pair (ps, period));
794 Subtitle (ps, period);
799 Player::seek (DCPTime time, bool accurate)
801 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
802 if (i->content->position() <= time && time < i->content->end()) {
803 i->decoder->seek (dcp_to_content_time (i, time), accurate);
809 _last_time = time - DCPTime::from_frames (1, _film->video_frame_rate ());
811 _last_time = optional<DCPTime> ();
815 shared_ptr<Resampler>
816 Player::resampler (shared_ptr<const AudioContent> content, AudioStreamPtr stream, bool create)
818 ResamplerMap::const_iterator i = _resamplers.find (make_pair (content, stream));
819 if (i != _resamplers.end ()) {
824 return shared_ptr<Resampler> ();
828 "Creating new resampler from %1 to %2 with %3 channels",
829 stream->frame_rate(),
830 content->resampled_frame_rate(),
834 shared_ptr<Resampler> r (
835 new Resampler (stream->frame_rate(), content->resampled_frame_rate(), stream->channels())
838 _resamplers[make_pair(content, stream)] = r;