--- /dev/null
+\documentclass{article}
+\begin{document}
+
+We are trying to implement full-ish playlist based content specification. The timing is awkward.
+
+\section{Reference timing}
+
+Frame rates of things can vary a lot; content can be in pretty much
+anything, and DCP video and audio frame rates may change on a whim
+depending on what is best for a given set of content. This suggests
+(albeit without strong justification) the need for a frame-rate-independent unit of time.
+
+So far we've been using a time type called \texttt{Time} expressed in
+$\mathtt{TIME\_HZ}^{-1}$; e.g. \texttt{TIME\_HZ} units is 1 second.
+\texttt{TIME\_HZ} is chosen to be divisible by lots of frame and
+sample rates.
+
+We express content start time as a \texttt{Time}.
+
+
+\section{Timing at different stages of the chain}
+
+Let's try this: decoders produce sequences of (perhaps) video frames
+and (perhaps) audio frames. There are no gaps. They are at the
+content's native frame rates and are synchronised (meaning that if
+they are played together, at the content's frame rates, they will be
+in sync). The decoders give timestamps for each piece of their
+output, which are \emph{simple indices} (\texttt{ContentVideoFrame}
+and \texttt{ContentAudioFrame}). Decoders know nothing of \texttt{Time}.
+
+
+\section{Split of stuff between decoders and player}
+
+In some ways it seems nice to have decoders which produce the rawest
+possible data and make the player sort it out (e.g.\ cropping and
+scaling video, resampling audio). The resampling is awkward, though,
+as you really need one resampler per source. So it might make more sense
+to put stuff in the decoder. But then, what's one map of resamplers between friends?
+
+
+
+\end{document}
class AudioContent : public virtual Content
{
public:
+ typedef int64_t Frame;
+
AudioContent (boost::shared_ptr<const Film>, Time);
AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
void as_xml (xmlpp::Node *) const;
virtual int audio_channels () const = 0;
- virtual ContentAudioFrame audio_length () const = 0;
+ virtual AudioContent::Frame audio_length () const = 0;
virtual int content_audio_frame_rate () const = 0;
virtual int output_audio_frame_rate () const = 0;
virtual AudioMapping audio_mapping () const = 0;
using boost::optional;
using boost::shared_ptr;
-AudioDecoder::AudioDecoder (shared_ptr<const Film> f, shared_ptr<const AudioContent> c)
+AudioDecoder::AudioDecoder (shared_ptr<const Film> f)
: Decoder (f)
- , _next_audio (0)
- , _audio_content (c)
+ , _next_audio_frame (0)
{
- if (_audio_content->content_audio_frame_rate() != _audio_content->output_audio_frame_rate()) {
-
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- stringstream s;
- s << String::compose (
- "Will resample audio from %1 to %2",
- _audio_content->content_audio_frame_rate(), _audio_content->output_audio_frame_rate()
- );
-
- film->log()->log (s.str ());
-
- /* We will be using planar float data when we call the
- resampler. As far as I can see, the audio channel
- layout is not necessary for our purposes; it seems
- only to be used get the number of channels and
- decide if rematrixing is needed. It won't be, since
- input and output layouts are the same.
- */
-
- _swr_context = swr_alloc_set_opts (
- 0,
- av_get_default_channel_layout (_audio_content->audio_channels ()),
- AV_SAMPLE_FMT_FLTP,
- _audio_content->output_audio_frame_rate(),
- av_get_default_channel_layout (_audio_content->audio_channels ()),
- AV_SAMPLE_FMT_FLTP,
- _audio_content->content_audio_frame_rate(),
- 0, 0
- );
-
- swr_init (_swr_context);
- } else {
- _swr_context = 0;
- }
}
-AudioDecoder::~AudioDecoder ()
-{
- if (_swr_context) {
- swr_free (&_swr_context);
- }
-}
-
-
#if 0
void
AudioDecoder::process_end ()
#endif
void
-AudioDecoder::audio (shared_ptr<const AudioBuffers> data, Time time)
-{
- /* Maybe resample */
- if (_swr_context) {
-
- /* Compute the resampled frames count and add 32 for luck */
- int const max_resampled_frames = ceil (
- (int64_t) data->frames() * _audio_content->output_audio_frame_rate() / _audio_content->content_audio_frame_rate()
- ) + 32;
-
- shared_ptr<AudioBuffers> resampled (new AudioBuffers (data->channels(), max_resampled_frames));
-
- /* Resample audio */
- int const resampled_frames = swr_convert (
- _swr_context, (uint8_t **) resampled->data(), max_resampled_frames, (uint8_t const **) data->data(), data->frames()
- );
-
- if (resampled_frames < 0) {
- throw EncodeError (_("could not run sample-rate converter"));
- }
-
- resampled->set_frames (resampled_frames);
-
- /* And point our variables at the resampled audio */
- data = resampled;
- }
-
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- /* Remap channels */
- shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (film->dcp_audio_channels(), data->frames()));
- dcp_mapped->make_silent ();
- list<pair<int, libdcp::Channel> > map = _audio_content->audio_mapping().content_to_dcp ();
- for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
- dcp_mapped->accumulate_channel (data.get(), i->first, i->second);
- }
-
- Audio (dcp_mapped, time);
- _next_audio = time + film->audio_frames_to_time (data->frames());
-}
-
-bool
-AudioDecoder::audio_done () const
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
{
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- return (_audio_content->length() - _next_audio) < film->audio_frames_to_time (1);
+ Audio (data, frame);
+ _next_audio_frame = frame + data->frames ();
}
-
#ifndef DCPOMATIC_AUDIO_DECODER_H
#define DCPOMATIC_AUDIO_DECODER_H
-#include "audio_source.h"
#include "decoder.h"
-extern "C" {
-#include <libswresample/swresample.h>
-}
+#include "content.h"
-class AudioContent;
+class AudioBuffers;
/** @class AudioDecoder.
* @brief Parent class for audio decoders.
*/
-class AudioDecoder : public AudioSource, public virtual Decoder
+class AudioDecoder : public virtual Decoder
{
public:
- AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>);
- ~AudioDecoder ();
+ AudioDecoder (boost::shared_ptr<const Film>);
-protected:
-
- void audio (boost::shared_ptr<const AudioBuffers>, Time);
- bool audio_done () const;
+ /** Emitted when some audio data is ready */
+ boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
- Time _next_audio;
- boost::shared_ptr<const AudioContent> _audio_content;
+protected:
-private:
- SwrContext* _swr_context;
+ void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+ AudioContent::Frame _next_audio_frame;
};
#endif
+++ /dev/null
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#ifndef DCPOMATIC_AUDIO_SINK_H
-#define DCPOMATIC_AUDIO_SINK_H
-
-class AudioBuffers;
-
-class AudioSink
-{
-public:
- /** Call with some audio data */
- virtual void process_audio (boost::shared_ptr<const AudioBuffers>, Time) = 0;
-};
-
-#endif
+++ /dev/null
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "audio_source.h"
-#include "audio_sink.h"
-
-using boost::shared_ptr;
-using boost::weak_ptr;
-using boost::bind;
-
-static void
-process_audio_proxy (weak_ptr<AudioSink> sink, shared_ptr<const AudioBuffers> audio, Time time)
-{
- shared_ptr<AudioSink> p = sink.lock ();
- if (p) {
- p->process_audio (audio, time);
- }
-}
-
-void
-AudioSource::connect_audio (shared_ptr<AudioSink> s)
-{
- Audio.connect (bind (process_audio_proxy, weak_ptr<AudioSink> (s), _1, _2));
-}
-
-
+++ /dev/null
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/audio_source.h
- * @brief Parent class for classes which emit audio data.
- */
-
-#ifndef DCPOMATIC_AUDIO_SOURCE_H
-#define DCPOMATIC_AUDIO_SOURCE_H
-
-#include <boost/signals2.hpp>
-#include "types.h"
-
-class AudioBuffers;
-class AudioSink;
-
-/** A class that emits audio data */
-class AudioSource
-{
-public:
- /** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
-
- void connect_audio (boost::shared_ptr<AudioSink>);
-};
-
-#endif
BlackDecoder::BlackDecoder (shared_ptr<const Film> f, shared_ptr<NullContent> c)
: Decoder (f)
- , VideoDecoder (f, c)
+ , VideoDecoder (f)
+ , _null_content (c)
{
}
if (!_image) {
_image.reset (new SimpleImage (AV_PIX_FMT_RGB24, video_size(), true));
_image->make_black ();
- video (_image, false, _next_video);
+ video (_image, false, _next_video_frame);
} else {
- video (_image, true, _next_video);
+ video (_image, true, _next_video_frame);
}
}
return f->dcp_video_frame_rate ();
}
-ContentVideoFrame
+VideoContent::Frame
BlackDecoder::video_length () const
{
- return _video_content->length() * video_frame_rate() / TIME_HZ;
-}
-
-Time
-BlackDecoder::position () const
-{
- return _next_video;
+ return _null_content->length() * video_frame_rate() / TIME_HZ;
}
void
-BlackDecoder::seek (Time t)
+BlackDecoder::seek (VideoContent::Frame frame)
{
- _next_video = t;
+ _next_video_frame = frame;
}
void
BlackDecoder::seek_back ()
{
- boost::shared_ptr<const Film> f = _film.lock ();
- if (!f) {
- return;
- }
-
- _next_video -= f->video_frames_to_time (2);
-}
-
-void
-BlackDecoder::seek_forward ()
-{
- boost::shared_ptr<const Film> f = _film.lock ();
- if (!f) {
- return;
+ if (_next_video_frame > 0) {
+ --_next_video_frame;
}
-
- _next_video += f->video_frames_to_time (1);
}
-
+
bool
BlackDecoder::done () const
{
- return video_done ();
+ return _next_video_frame >= _null_content->video_length ();
}
-
-
/* Decoder */
void pass ();
- void seek (Time);
- void seek_back ();
- void seek_forward ();
- Time position () const;
bool done () const;
/* VideoDecoder */
+ void seek (VideoContent::Frame);
+ void seek_back ();
float video_frame_rate () const;
libdcp::Size video_size () const {
return libdcp::Size (256, 256);
}
- ContentVideoFrame video_length () const;
+ VideoContent::Frame video_length () const;
private:
+ boost::shared_ptr<NullContent> _null_content;
boost::shared_ptr<Image> _image;
};
+++ /dev/null
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "combiner.h"
-#include "image.h"
-
-using boost::shared_ptr;
-
-Combiner::Combiner ()
-{
-
-}
-
-/** Process video for the left half of the frame.
- * Subtitle parameter will be ignored.
- * @param image Frame image.
- */
-void
-Combiner::process_video (shared_ptr<const Image> image, bool, Time)
-{
- _image.reset (new SimpleImage (image, true));
-}
-
-/** Process video for the right half of the frame.
- * @param image Frame image.
- * @param sub Subtitle (which will be put onto the whole frame)
- */
-void
-Combiner::process_video_b (shared_ptr<const Image> image, bool, Time t)
-{
- /* Copy the right half of this image into our _image */
- /* XXX: this should probably be in the Image class */
- for (int i = 0; i < image->components(); ++i) {
- int const line_size = image->line_size()[i];
- int const half_line_size = line_size / 2;
-
- uint8_t* p = _image->data()[i];
- uint8_t* q = image->data()[i];
-
- for (int j = 0; j < image->lines (i); ++j) {
- memcpy (p + half_line_size, q + half_line_size, half_line_size);
- p += _image->stride()[i];
- q += image->stride()[i];
- }
- }
-
- Video (_image, false, t);
- _image.reset ();
-}
+++ /dev/null
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/lib/combiner.h
- * @brief Class for combining two video streams.
- */
-
-#include "video_source.h"
-#include "video_sink.h"
-
-/** @class Combiner
- * @brief A class which can combine two video streams into one, with
- * one image used for the left half of the screen and the other for
- * the right.
- */
-class Combiner : public VideoSource, public VideoSink
-{
-public:
- Combiner ();
-
- void process_video (boost::shared_ptr<const Image> i, bool, Time);
- void process_video_b (boost::shared_ptr<const Image> i, bool, Time);
-
-private:
- /** The image that we are currently working on */
- boost::shared_ptr<Image> _image;
-};
#include <stdint.h>
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
-#include "video_source.h"
-#include "audio_source.h"
#include "film.h"
class Image;
*/
virtual void pass () = 0;
- /** Seek this decoder to as close as possible to some time,
- * expressed relative to our source's start.
- * @param t Time.
- * @param a true to try hard to be accurate, otherwise false.
- */
- virtual void seek (Time) = 0;
-
- /** Seek back one video frame */
- virtual void seek_back () = 0;
-
- /** Seek forward one video frame */
- virtual void seek_forward () = 0;
-
- /** @return Approximate time of the next content that we will emit,
- * expressed relative to the start of our source.
- */
- virtual Time position () const = 0;
-
virtual bool done () const = 0;
protected:
}
void
-Encoder::process_video (shared_ptr<const Image> image, bool same, Time)
+Encoder::process_video (shared_ptr<const Image> image, bool same)
{
boost::mutex::scoped_lock lock (_mutex);
}
void
-Encoder::process_audio (shared_ptr<const AudioBuffers> data, Time)
+Encoder::process_audio (shared_ptr<const AudioBuffers> data)
{
_writer->write (data);
}
#include <libswresample/swresample.h>
}
#include "util.h"
-#include "video_sink.h"
-#include "audio_sink.h"
class Image;
class AudioBuffers;
* is supplied as uncompressed PCM in blocks of various sizes.
*/
-class Encoder : public VideoSink, public AudioSink
+class Encoder
{
public:
Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<Job>);
* @param i Video frame image.
* @param same true if i is the same as the last time we were called.
*/
- void process_video (boost::shared_ptr<const Image> i, bool same, Time);
+ void process_video (boost::shared_ptr<const Image> i, bool same);
/** Call with some audio data */
- void process_audio (boost::shared_ptr<const AudioBuffers>, Time);
+ void process_audio (boost::shared_ptr<const AudioBuffers>);
/** Called when a processing run has finished */
void process_end ();
shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
- ContentVideoFrame video_length = 0;
+ VideoContent::Frame video_length = 0;
video_length = examiner->video_length ();
film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
signal_changed (FFmpegContentProperty::AUDIO_STREAM);
}
-ContentAudioFrame
+AudioContent::Frame
FFmpegContent::audio_length () const
{
int const cafr = content_audio_frame_rate ();
int const vfr = video_frame_rate ();
- ContentVideoFrame const vl = video_length ();
+ VideoContent::Frame const vl = video_length ();
boost::mutex::scoped_lock lm (_mutex);
if (!_audio_stream) {
/* AudioContent */
int audio_channels () const;
- ContentAudioFrame audio_length () const;
+ AudioContent::Frame audio_length () const;
int content_audio_frame_rate () const;
int output_audio_frame_rate () const;
AudioMapping audio_mapping () const;
FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
: Decoder (f)
- , VideoDecoder (f, c)
- , AudioDecoder (f, c)
+ , VideoDecoder (f)
+ , AudioDecoder (f)
, FFmpeg (c)
, _subtitle_codec_context (0)
, _subtitle_codec (0)
}
/* Stop us being asked for any more data */
- _next_video = _next_audio = _ffmpeg_content->length ();
+ _next_video_frame = _ffmpeg_content->video_length ();
+ _next_audio_frame = _ffmpeg_content->audio_length ();
return;
}
} else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->id && _decode_audio) {
decode_audio_packet ();
} else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id) {
+#if 0
int got_subtitle;
AVSubtitle sub;
}
avsubtitle_free (&sub);
}
+#endif
}
av_free_packet (&_packet);
}
void
-FFmpegDecoder::seek (Time t)
+FFmpegDecoder::seek (VideoContent::Frame frame)
{
- do_seek (t, false, false);
- VideoDecoder::seek (t);
+ do_seek (frame, false, false);
}
void
FFmpegDecoder::seek_back ()
{
- if (position() < (2.5 * TIME_HZ / _ffmpeg_content->video_frame_rate())) {
+ if (_next_video_frame == 0) {
return;
}
- do_seek (position() - 2.5 * TIME_HZ / _ffmpeg_content->video_frame_rate(), true, true);
+ do_seek (_next_video_frame - 1, true, true);
VideoDecoder::seek_back ();
}
void
-FFmpegDecoder::seek_forward ()
+FFmpegDecoder::do_seek (VideoContent::Frame frame, bool backwards, bool accurate)
{
- if (position() >= (_ffmpeg_content->length() - 0.5 * TIME_HZ / _ffmpeg_content->video_frame_rate())) {
- return;
- }
-
- do_seek (position() - 0.5 * TIME_HZ / _ffmpeg_content->video_frame_rate(), true, true);
- VideoDecoder::seek_forward ();
-}
-
-void
-FFmpegDecoder::do_seek (Time t, bool backwards, bool accurate)
-{
- int64_t const vt = t / (av_q2d (_format_context->streams[_video_stream]->time_base) * TIME_HZ);
+ int64_t const vt = frame * _ffmpeg_content->video_frame_rate() / av_q2d (_format_context->streams[_video_stream]->time_base);
av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
avcodec_flush_buffers (video_codec_context());
);
assert (audio_codec_context()->channels == _ffmpeg_content->audio_channels());
- audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds * TIME_HZ);
+ Audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds * _ffmpeg_content->content_audio_frame_rate());
}
copy_packet.data += decode_result;
int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
if (bet != AV_NOPTS_VALUE) {
- /* XXX: may need to insert extra frames / remove frames here ...
- (as per old Matcher)
- */
- Time const t = bet * av_q2d (_format_context->streams[_video_stream]->time_base) * TIME_HZ;
- video (image, false, t);
+
+ double const pts = bet * av_q2d (_format_context->streams[_video_stream]->time_base);
+ double const next = _next_video_frame / _ffmpeg_content->video_frame_rate();
+ double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
+ double delta = pts - next;
+
+ while (delta > one_frame) {
+ /* This PTS is more than one frame forward in time of where we think we should be; emit
+ a black frame.
+ */
+ boost::shared_ptr<Image> black (
+ new SimpleImage (
+ static_cast<AVPixelFormat> (_frame->format),
+ libdcp::Size (video_codec_context()->width, video_codec_context()->height),
+ true
+ )
+ );
+
+ black->make_black ();
+ video (image, false, _next_video_frame);
+ delta -= one_frame;
+ }
+
+ if (delta > -one_frame) {
+ /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
+ video (image, false, _next_video_frame);
+ }
} else {
shared_ptr<const Film> film = _film.lock ();
assert (film);
return true;
}
-Time
-FFmpegDecoder::position () const
-{
- if (_decode_video && _decode_audio && _ffmpeg_content->audio_stream()) {
- return min (_next_video, _next_audio);
- }
-
- if (_decode_audio && _ffmpeg_content->audio_stream()) {
- return _next_audio;
- }
-
- return _next_video;
-}
-
-bool
-FFmpegDecoder::done () const
-{
- bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || audio_done();
- bool const vd = !_decode_video || video_done();
- return ad && vd;
-}
void
FFmpegDecoder::setup_subtitle ()
throw DecodeError (N_("could not open subtitle decoder"));
}
}
+
+bool
+FFmpegDecoder::done () const
+{
+ bool const vd = !_decode_video || (_next_video_frame >= _ffmpeg_content->video_length());
+ bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_next_audio_frame >= _ffmpeg_content->audio_length());
+ return vd && ad;
+}
+
~FFmpegDecoder ();
void pass ();
- void seek (Time);
+ void seek (VideoContent::Frame);
void seek_back ();
- void seek_forward ();
- Time position () const;
bool done () const;
private:
AVSampleFormat audio_sample_format () const;
int bytes_per_audio_sample () const;
- void do_seek (Time, bool, bool);
+ void do_seek (VideoContent::Frame, bool, bool);
bool decode_video_packet ();
void decode_audio_packet ();
}
/** @return Length (in video frames) according to our content's header */
-ContentVideoFrame
+VideoContent::Frame
FFmpegExaminer::video_length () const
{
return (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
float video_frame_rate () const;
libdcp::Size video_size () const;
- ContentVideoFrame video_length () const;
+ VideoContent::Frame video_length () const;
std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
return _subtitle_streams;
#include "i18n.h"
-using namespace std;
-using namespace boost;
+using std::string;
+using std::min;
+using boost::shared_ptr;
using libdcp::Size;
void
}
void
-ImageMagickContent::set_video_length (ContentVideoFrame len)
+ImageMagickContent::set_video_length (VideoContent::Frame len)
{
{
boost::mutex::scoped_lock lm (_mutex);
boost::shared_ptr<Content> clone () const;
Time length () const;
- void set_video_length (ContentVideoFrame);
+ void set_video_length (VideoContent::Frame);
static bool valid_file (boost::filesystem::path);
};
ImageMagickDecoder::ImageMagickDecoder (shared_ptr<const Film> f, shared_ptr<const ImageMagickContent> c)
: Decoder (f)
- , VideoDecoder (f, c)
+ , VideoDecoder (f)
, ImageMagick (c)
{
void
ImageMagickDecoder::pass ()
{
- if (_next_video >= _imagemagick_content->length ()) {
+ if (_next_video_frame >= _imagemagick_content->video_length ()) {
return;
}
if (_image) {
- video (_image, true, _next_video);
+ video (_image, true, _next_video_frame);
return;
}
delete magick_image;
- _image = _image->crop (_imagemagick_content->crop(), true);
- video (_image, false, _next_video);
+ video (_image, false, _next_video_frame);
}
void
-ImageMagickDecoder::seek (Time t)
+ImageMagickDecoder::seek (VideoContent::Frame frame)
{
- _next_video = t;
+ _next_video_frame = frame;
}
void
ImageMagickDecoder::seek_back ()
{
- boost::shared_ptr<const Film> f = _film.lock ();
- if (!f) {
- return;
- }
-
- _next_video -= f->video_frames_to_time (2);
-}
-
-void
-ImageMagickDecoder::seek_forward ()
-{
- boost::shared_ptr<const Film> f = _film.lock ();
- if (!f) {
- return;
+ if (_next_video_frame > 0) {
+ _next_video_frame--;
}
-
- _next_video += f->video_frames_to_time (1);
}
-Time
-ImageMagickDecoder::position () const
-{
- return _next_video;
-}
-
-
bool
ImageMagickDecoder::done () const
{
- return video_done ();
+ return _next_video_frame > _imagemagick_content->video_length ();
}
-
/* Decoder */
void pass ();
- void seek (Time);
+ void seek (VideoContent::Frame);
void seek_back ();
- void seek_forward ();
- Time position () const;
bool done () const;
private:
float video_frame_rate () const;
libdcp::Size video_size () const;
- ContentVideoFrame video_length () const;
+ VideoContent::Frame video_length () const;
private:
boost::weak_ptr<const Film> _film;
+++ /dev/null
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "matcher.h"
-#include "image.h"
-#include "log.h"
-
-#include "i18n.h"
-
-using std::min;
-using std::cout;
-using std::list;
-using boost::shared_ptr;
-
-Matcher::Matcher (shared_ptr<Log> log, int sample_rate, float frames_per_second)
- : Processor (log)
- , _sample_rate (sample_rate)
- , _frames_per_second (frames_per_second)
- , _video_frames (0)
- , _audio_frames (0)
- , _had_first_video (false)
- , _had_first_audio (false)
-{
-
-}
-
-void
-Matcher::process_video (boost::shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
-{
- _pixel_format = image->pixel_format ();
- _size = image->size ();
-
- _log->log(String::compose("Matcher video @ %1 [audio=%2, video=%3, pending_audio=%4]", t, _audio_frames, _video_frames, _pending_audio.size()));
-
- if (!_first_input || t < _first_input.get()) {
- _first_input = t;
- }
-
- bool const this_is_first_video = !_had_first_video;
- _had_first_video = true;
-
- if (!_had_first_audio) {
- /* No audio yet; we must postpone these data until we have some */
- _pending_video.push_back (VideoRecord (image, same, sub, t));
- } else if (this_is_first_video && _had_first_audio) {
- /* First video since we got audio */
- _pending_video.push_back (VideoRecord (image, same, sub, t));
- fix_start ();
- } else {
- /* Normal running */
-
- /* Difference between where this video is and where it should be */
- double const delta = t - _first_input.get() - _video_frames / _frames_per_second;
- double const one_frame = 1 / _frames_per_second;
-
- if (delta > one_frame) {
- /* Insert frames to make up the difference */
- int const extra = rint (delta / one_frame);
- for (int i = 0; i < extra; ++i) {
- repeat_last_video ();
- _log->log (String::compose ("Extra video frame inserted at %1s", _video_frames / _frames_per_second));
- }
- }
-
- if (delta > -one_frame) {
- Video (image, same, sub);
- ++_video_frames;
- } else {
- /* We are omitting a frame to keep things right */
- _log->log (String::compose ("Frame removed at %1s; delta %2; first input was at %3", t, delta, _first_input.get()));
- }
-
- _last_image = image;
- _last_subtitle = sub;
- }
-}
-
-void
-Matcher::process_audio (boost::shared_ptr<const AudioBuffers> b, double t)
-{
- _channels = b->channels ();
-
- _log->log (String::compose (
- "Matcher audio (%1 frames) @ %2 [video=%3, audio=%4, pending_video=%5, pending_audio=%6]",
- b->frames(), t, _video_frames, _audio_frames, _pending_video.size(), _pending_audio.size()
- )
- );
-
- if (!_first_input || t < _first_input.get()) {
- _first_input = t;
- }
-
- bool const this_is_first_audio = !_had_first_audio;
- _had_first_audio = true;
-
- if (!_had_first_video) {
- /* No video yet; we must postpone these data until we have some */
- _pending_audio.push_back (AudioRecord (b, t));
- } else if (this_is_first_audio && _had_first_video) {
- /* First audio since we got video */
- _pending_audio.push_back (AudioRecord (b, t));
- fix_start ();
- } else {
- /* Normal running. We assume audio time stamps are consecutive, so there's no equivalent of
- the checking / insertion of repeat frames that there is for video.
- */
- Audio (b);
- _audio_frames += b->frames ();
- }
-}
-
-void
-Matcher::process_end ()
-{
- if (_audio_frames == 0 || !_pixel_format || !_size || !_channels) {
- /* We won't do anything */
- return;
- }
-
- _log->log (String::compose ("Matcher has seen %1 video frames (which equals %2 audio frames) and %3 audio frames",
- _video_frames, video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second), _audio_frames));
-
- match ((double (_audio_frames) / _sample_rate) - (double (_video_frames) / _frames_per_second));
-}
-
-void
-Matcher::fix_start ()
-{
- assert (!_pending_video.empty ());
- assert (!_pending_audio.empty ());
-
- _log->log (String::compose ("Fixing start; video at %1, audio at %2", _pending_video.front().time, _pending_audio.front().time));
-
- match (_pending_video.front().time - _pending_audio.front().time);
-
- for (list<VideoRecord>::iterator i = _pending_video.begin(); i != _pending_video.end(); ++i) {
- process_video (i->image, i->same, i->subtitle, i->time);
- }
-
- _pending_video.clear ();
-
- for (list<AudioRecord>::iterator i = _pending_audio.begin(); i != _pending_audio.end(); ++i) {
- process_audio (i->audio, i->time);
- }
-
- _pending_audio.clear ();
-}
-
-void
-Matcher::match (double extra_video_needed)
-{
- _log->log (String::compose ("Match %1", extra_video_needed));
-
- if (extra_video_needed > 0) {
-
- /* Emit black video frames */
-
- int const black_video_frames = ceil (extra_video_needed * _frames_per_second);
-
- _log->log (String::compose (N_("Emitting %1 frames of black video"), black_video_frames));
-
- shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), true));
- black->make_black ();
- for (int i = 0; i < black_video_frames; ++i) {
- Video (black, i != 0, shared_ptr<Subtitle>());
- ++_video_frames;
- }
-
- extra_video_needed -= black_video_frames / _frames_per_second;
- }
-
- if (extra_video_needed < 0) {
-
- /* Emit silence */
-
- int64_t to_do = -extra_video_needed * _sample_rate;
- _log->log (String::compose (N_("Emitting %1 frames of silence"), to_do));
-
- /* Do things in half second blocks as I think there may be limits
- to what FFmpeg (and in particular the resampler) can cope with.
- */
- int64_t const block = _sample_rate / 2;
- shared_ptr<AudioBuffers> b (new AudioBuffers (_channels.get(), block));
- b->make_silent ();
-
- while (to_do > 0) {
- int64_t const this_time = min (to_do, block);
- b->set_frames (this_time);
- Audio (b);
- _audio_frames += b->frames ();
- to_do -= this_time;
- }
- }
-}
-
-void
-Matcher::repeat_last_video ()
-{
- if (!_last_image) {
- shared_ptr<Image> im (new SimpleImage (_pixel_format.get(), _size.get(), true));
- im->make_black ();
- _last_image = im;
- }
-
- Video (_last_image, true, _last_subtitle);
- ++_video_frames;
-}
-
+++ /dev/null
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <boost/optional.hpp>
-#include "processor.h"
-
-class Matcher : public Processor, public TimedAudioSink, public TimedVideoSink, public AudioSource, public VideoSource
-{
-public:
- Matcher (boost::shared_ptr<Log> log, int sample_rate, float frames_per_second);
- void process_video (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s, double);
- void process_audio (boost::shared_ptr<const AudioBuffers>, double);
- void process_end ();
-
-private:
- void fix_start ();
- void match (double);
- void repeat_last_video ();
-
- int _sample_rate;
- float _frames_per_second;
- int _video_frames;
- int64_t _audio_frames;
- boost::optional<AVPixelFormat> _pixel_format;
- boost::optional<libdcp::Size> _size;
- boost::optional<int> _channels;
-
- struct VideoRecord {
- VideoRecord (boost::shared_ptr<const Image> i, bool s, boost::shared_ptr<Subtitle> u, double t)
- : image (i)
- , same (s)
- , subtitle (u)
- , time (t)
- {}
-
- boost::shared_ptr<const Image> image;
- bool same;
- boost::shared_ptr<Subtitle> subtitle;
- double time;
- };
-
- struct AudioRecord {
- AudioRecord (boost::shared_ptr<const AudioBuffers> a, double t)
- : audio (a)
- , time (t)
- {}
-
- boost::shared_ptr<const AudioBuffers> audio;
- double time;
- };
-
- std::list<VideoRecord> _pending_video;
- std::list<AudioRecord> _pending_audio;
-
- boost::optional<double> _first_input;
- boost::shared_ptr<const Image> _last_image;
- boost::shared_ptr<Subtitle> _last_subtitle;
-
- bool _had_first_video;
- bool _had_first_audio;
-};
int audio_channels () const;
- ContentAudioFrame audio_length () const {
+ AudioContent::Frame audio_length () const {
return _audio_length;
}
}
private:
- ContentAudioFrame _audio_length;
+ AudioContent::Frame _audio_length;
Time _length;
};
#include "null_content.h"
#include "black_decoder.h"
#include "silence_decoder.h"
+#include "ratio.h"
+#include "resampler.h"
using std::list;
using std::cout;
using std::min;
using std::max;
using std::vector;
+using std::pair;
+using std::map;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
: content (c)
, decoder (d)
+ , last_emission (0)
{}
shared_ptr<Content> content;
shared_ptr<Decoder> decoder;
+ Time last_emission;
};
continue;
}
- if (!_audio && dynamic_pointer_cast<AudioDecoder> ((*i)->decoder) && !dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
- continue;
- }
-
- Time const t = (*i)->content->start() + (*i)->decoder->position();
- if (t < earliest_t) {
- earliest_t = t;
+ if ((*i)->last_emission < earliest_t) {
+ earliest_t = (*i)->last_emission;
earliest = *i;
}
}
}
earliest->decoder->pass ();
- _position = earliest->content->start() + earliest->decoder->position ();
+ _position = earliest->last_emission;
return false;
}
void
-Player::process_video (weak_ptr<Content> weak_content, shared_ptr<const Image> image, bool same, Time time)
+Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
{
- shared_ptr<Content> content = weak_content.lock ();
- if (!content) {
+ shared_ptr<Piece> piece = weak_piece.lock ();
+ if (!piece) {
+ return;
+ }
+
+ shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
+ assert (content);
+
+ FrameRateConversion frc (content->video_frame_rate(), _film->dcp_video_frame_rate());
+ if (frc.skip && (frame % 2) == 1) {
return;
}
+
+ image = image->crop (content->crop(), true);
+
+ libdcp::Size const container_size = _video_container_size.get_value_or (_film->container()->size (_film->full_frame ()));
+ libdcp::Size const image_size = content->ratio()->size (container_size);
- time += content->start ();
+ image = image->scale_and_convert_to_rgb (image_size, _film->scaler(), true);
+
+#if 0
+ if (film->with_subtitles ()) {
+ shared_ptr<Subtitle> sub;
+ if (_timed_subtitle && _timed_subtitle->displayed_at (t)) {
+ sub = _timed_subtitle->subtitle ();
+ }
+
+ if (sub) {
+ dcpomatic::Rect const tx = subtitle_transformed_area (
+ float (image_size.width) / content->video_size().width,
+ float (image_size.height) / content->video_size().height,
+ sub->area(), film->subtitle_offset(), film->subtitle_scale()
+ );
+
+ shared_ptr<Image> im = sub->image()->scale (tx.size(), film->scaler(), true);
+ image->alpha_blend (im, tx.position());
+ }
+ }
+#endif
+
+ if (image_size != container_size) {
+ assert (image_size.width <= container_size.width);
+ assert (image_size.height <= container_size.height);
+ shared_ptr<Image> im (new SimpleImage (PIX_FMT_RGB24, container_size, true));
+ im->make_black ();
+ im->copy (image, Position ((container_size.width - image_size.width) / 2, (container_size.height - image_size.height) / 2));
+ image = im;
+ }
+
+ Time time = content->start() + (frame * frc.factor() * TIME_HZ / _film->dcp_video_frame_rate());
Video (image, same, time);
+
+ if (frc.repeat) {
+ time += TIME_HZ / _film->dcp_video_frame_rate();
+ Video (image, true, time);
+ }
+
+ piece->last_emission = min (piece->last_emission, time);
}
void
-Player::process_audio (weak_ptr<Content> weak_content, shared_ptr<const AudioBuffers> audio, Time time)
+Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
{
- shared_ptr<Content> content = weak_content.lock ();
- if (!content) {
+ shared_ptr<Piece> piece = weak_piece.lock ();
+ if (!piece) {
return;
}
-
+
+ shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
+ assert (content);
+
+ if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
+ audio = resampler(content)->run (audio);
+ }
+
+ /* Remap channels */
+ shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->dcp_audio_channels(), audio->frames()));
+ dcp_mapped->make_silent ();
+ list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp ();
+ for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
+ dcp_mapped->accumulate_channel (audio.get(), i->first, i->second);
+ }
+
/* The time of this audio may indicate that some of our buffered audio is not going to
be added to any more, so it can be emitted.
*/
- time += content->start ();
+ Time const time = content->start() + (frame * TIME_HZ / _film->dcp_audio_frame_rate());
+ piece->last_emission = min (piece->last_emission, time);
- cout << "Player gets " << audio->frames() << " @ " << time << " cf " << _next_audio << "\n";
+ cout << "Player gets " << dcp_mapped->frames() << " @ " << time << " cf " << _next_audio << "\n";
if (time > _next_audio) {
/* We can emit some audio from our buffers */
}
for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- Time s = t - (*i)->content->start ();
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
+ if (!vc) {
+ continue;
+ }
+
+ Time s = t - vc->start ();
s = max (static_cast<Time> (0), s);
- s = min ((*i)->content->length(), s);
- (*i)->decoder->seek (s);
+ s = min (vc->length(), s);
+
+ FrameRateConversion frc (vc->video_frame_rate(), _film->dcp_video_frame_rate());
+ VideoContent::Frame f = s * _film->dcp_video_frame_rate() / (frc.factor() * TIME_HZ);
+ dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (f);
}
/* XXX: don't seek audio because we don't need to... */
}
-void
-Player::seek_forward ()
-{
-
-}
-
void
Player::add_black_piece (Time s, Time len)
{
shared_ptr<NullContent> nc (new NullContent (_film, s, len));
nc->set_ratio (_film->container ());
shared_ptr<BlackDecoder> bd (new BlackDecoder (_film, nc));
- bd->Video.connect (bind (&Player::process_video, this, nc, _1, _2, _3));
- _pieces.push_back (shared_ptr<Piece> (new Piece (nc, bd)));
+ shared_ptr<Piece> p (new Piece (nc, bd));
+ _pieces.push_back (p);
+ bd->Video.connect (bind (&Player::process_video, this, p, _1, _2, _3));
}
void
{
shared_ptr<NullContent> nc (new NullContent (_film, s, len));
shared_ptr<SilenceDecoder> sd (new SilenceDecoder (_film, nc));
- sd->Audio.connect (bind (&Player::process_audio, this, nc, _1, _2));
- _pieces.push_back (shared_ptr<Piece> (new Piece (nc, sd)));
+ shared_ptr<Piece> p (new Piece (nc, sd));
+ _pieces.push_back (p);
+ sd->Audio.connect (bind (&Player::process_audio, this, p, _1, _2));
}
for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) {
shared_ptr<Decoder> decoder;
-
+
/* XXX: into content? */
shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
fd->Video.connect (bind (&Player::process_video, this, *i, _1, _2, _3));
fd->Audio.connect (bind (&Player::process_audio, this, *i, _1, _2));
- if (_video_container_size) {
- fd->set_video_container_size (_video_container_size.get ());
- }
decoder = fd;
}
if (!id) {
id.reset (new ImageMagickDecoder (_film, ic));
id->Video.connect (bind (&Player::process_video, this, *i, _1, _2, _3));
- if (_video_container_size) {
- id->set_video_container_size (_video_container_size.get ());
- }
}
decoder = id;
Player::set_video_container_size (libdcp::Size s)
{
_video_container_size = s;
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
- if (vd) {
- vd->set_video_container_size (s);
- }
+}
+
+shared_ptr<Resampler>
+Player::resampler (shared_ptr<AudioContent> c)
+{
+ map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
+ if (i != _resamplers.end ()) {
+ return i->second;
}
+
+ shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
+ _resamplers[c] = r;
+ return r;
}
#include <list>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
-#include "video_source.h"
-#include "audio_source.h"
-#include "video_sink.h"
-#include "audio_sink.h"
#include "playlist.h"
#include "audio_buffers.h"
+#include "content.h"
class Job;
class Film;
class Playlist;
class AudioContent;
class Piece;
+class Image;
+class Resampler;
/** @class Player
* @brief A class which can `play' a Playlist; emitting its audio and video.
*/
-class Player : public VideoSource, public AudioSource, public boost::enable_shared_from_this<Player>
+class Player : public boost::enable_shared_from_this<Player>
{
public:
Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
bool pass ();
void seek (Time);
void seek_back ();
- void seek_forward ();
/** @return position that we are at; ie the time of the next thing we will emit on pass() */
Time position () const {
void set_video_container_size (libdcp::Size);
+ /** Emitted when a video frame is ready.
+ * First parameter is the video image.
+ * Second parameter is true if the image is the same as the last one that was emitted.
+ * Third parameter is the time.
+ */
+ boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, Time)> Video;
+
+ /** Emitted when some audio data is ready */
+ boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
+
private:
- void process_video (boost::weak_ptr<Content>, boost::shared_ptr<const Image>, bool, Time);
- void process_audio (boost::weak_ptr<Content>, boost::shared_ptr<const AudioBuffers>, Time);
+ void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+ void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
void setup_pieces ();
void playlist_changed ();
void content_changed (boost::weak_ptr<Content>, int);
void add_black_piece (Time, Time);
void add_silent_piece (Time, Time);
void flush ();
+ boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>);
boost::shared_ptr<const Film> _film;
boost::shared_ptr<const Playlist> _playlist;
AudioBuffers _audio_buffers;
Time _next_audio;
boost::optional<libdcp::Size> _video_container_size;
+ std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
};
#endif
#include <list>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
-#include "video_source.h"
-#include "audio_source.h"
-#include "video_sink.h"
-#include "audio_sink.h"
#include "ffmpeg_content.h"
#include "audio_mapping.h"
--- /dev/null
+extern "C" {
+#include "libavutil/channel_layout.h"
+}
+#include "resampler.h"
+#include "audio_buffers.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using boost::shared_ptr;
+
+Resampler::Resampler (int in, int out, int channels)
+ : _in_rate (in)
+ , _out_rate (out)
+ , _channels (channels)
+{
+ /* We will be using planar float data when we call the
+ resampler. As far as I can see, the audio channel
+ layout is not necessary for our purposes; it seems
+ only to be used get the number of channels and
+ decide if rematrixing is needed. It won't be, since
+ input and output layouts are the same.
+ */
+
+ _swr_context = swr_alloc_set_opts (
+ 0,
+ av_get_default_channel_layout (_channels),
+ AV_SAMPLE_FMT_FLTP,
+ _out_rate,
+ av_get_default_channel_layout (_channels),
+ AV_SAMPLE_FMT_FLTP,
+ _in_rate,
+ 0, 0
+ );
+
+ swr_init (_swr_context);
+}
+
+Resampler::~Resampler ()
+{
+ swr_free (&_swr_context);
+}
+
+shared_ptr<const AudioBuffers>
+Resampler::run (shared_ptr<const AudioBuffers> in)
+{
+ /* Compute the resampled frames count and add 32 for luck */
+ int const max_resampled_frames = ceil ((double) in->frames() * _out_rate / _in_rate) + 32;
+ shared_ptr<AudioBuffers> resampled (new AudioBuffers (_channels, max_resampled_frames));
+
+ int const resampled_frames = swr_convert (
+ _swr_context, (uint8_t **) resampled->data(), max_resampled_frames, (uint8_t const **) in->data(), in->frames()
+ );
+
+ if (resampled_frames < 0) {
+ throw EncodeError (_("could not run sample-rate converter"));
+ }
+
+ resampled->set_frames (resampled_frames);
+ return resampled;
+}
--- /dev/null
+#include <boost/shared_ptr.hpp>
+extern "C" {
+#include <libswresample/swresample.h>
+}
+
+class AudioBuffers;
+
+class Resampler
+{
+public:
+ Resampler (int, int, int);
+ ~Resampler ();
+
+ boost::shared_ptr<const AudioBuffers> run (boost::shared_ptr<const AudioBuffers>);
+
+private:
+ SwrContext* _swr_context;
+ int _in_rate;
+ int _out_rate;
+ int _channels;
+};
SilenceDecoder::SilenceDecoder (shared_ptr<const Film> f, shared_ptr<NullContent> c)
: Decoder (f)
- , AudioDecoder (f, c)
+ , AudioDecoder (f)
+ , _null_content (c)
{
}
shared_ptr<const Film> film = _film.lock ();
assert (film);
- Time const this_time = min (_audio_content->length() - _next_audio, TIME_HZ / 2);
- cout << "silence emit " << this_time << " from " << _audio_content->length() << "\n";
- shared_ptr<AudioBuffers> data (new AudioBuffers (film->dcp_audio_channels(), film->time_to_audio_frames (this_time)));
+ AudioContent::Frame const this_time = min (_null_content->audio_length() - _next_audio_frame, int64_t (_null_content->output_audio_frame_rate() / 2));
+ shared_ptr<AudioBuffers> data (new AudioBuffers (film->dcp_audio_channels(), this_time));
data->make_silent ();
- audio (data, _next_audio);
-}
-
-void
-SilenceDecoder::seek (Time t)
-{
- _next_audio = t;
-}
-
-void
-SilenceDecoder::seek_back ()
-{
- boost::shared_ptr<const Film> f = _film.lock ();
- if (!f) {
- return;
- }
-
- _next_audio -= f->video_frames_to_time (2);
-}
-
-void
-SilenceDecoder::seek_forward ()
-{
- boost::shared_ptr<const Film> f = _film.lock ();
- if (!f) {
- return;
- }
-
- _next_audio += f->video_frames_to_time (1);
-}
-
-Time
-SilenceDecoder::position () const
-{
- return _next_audio;
+ audio (data, _next_audio_frame);
}
bool
SilenceDecoder::done () const
{
- return audio_done ();
+ return _next_audio_frame > _null_content->audio_length ();
}
-
SilenceDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<NullContent>);
void pass ();
- void seek (Time);
- void seek_back ();
- void seek_forward ();
- Time position () const;
bool done () const;
+
+private:
+ boost::shared_ptr<NullContent> _null_content;
};
, AudioContent (f, node)
{
_audio_channels = node->number_child<int> ("AudioChannels");
- _audio_length = node->number_child<ContentAudioFrame> ("AudioLength");
+ _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
_audio_frame_rate = node->number_child<int> ("AudioFrameRate");
_audio_mapping = AudioMapping (node->node_child ("AudioMapping"));
}
return _audio_channels;
}
- ContentAudioFrame audio_length () const {
+ AudioContent::Frame audio_length () const {
boost::mutex::scoped_lock lm (_mutex);
return _audio_length;
}
private:
int _audio_channels;
- ContentAudioFrame _audio_length;
+ AudioContent::Frame _audio_length;
int _audio_frame_rate;
AudioMapping _audio_mapping;
};
SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c)
: Decoder (f)
- , AudioDecoder (f, c)
+ , AudioDecoder (f)
, _sndfile_content (c)
, _deinterleave_buffer (0)
{
return _info.channels;
}
-ContentAudioFrame
+AudioContent::Frame
SndfileDecoder::audio_length () const
{
return _info.frames;
return _info.samplerate;
}
-Time
-SndfileDecoder::position () const
-{
- return _next_audio;
-}
-
bool
SndfileDecoder::done () const
{
- return audio_done ();
+ return _next_audio_frame > _sndfile_content->audio_length ();
}
~SndfileDecoder ();
void pass ();
- void seek (Time) {}
- void seek_back () {}
- void seek_forward () {}
- Time position () const;
bool done () const;
int audio_channels () const;
- ContentAudioFrame audio_length () const;
+ AudioContent::Frame audio_length () const;
int audio_frame_rate () const;
private:
boost::shared_ptr<const SndfileContent> _sndfile_content;
SNDFILE* _sndfile;
SF_INFO _info;
- ContentAudioFrame _done;
- ContentAudioFrame _remaining;
+ AudioContent::Frame _done;
+ AudioContent::Frame _remaining;
float* _deinterleave_buffer;
};
#include "i18n.h"
-using namespace std;
-using namespace boost;
+using boost::shared_ptr;
using libdcp::Size;
/** Construct a TimedSubtitle. This is a subtitle image, position,
using std::string;
using boost::shared_ptr;
+using boost::weak_ptr;
using boost::dynamic_pointer_cast;
+static void
+video_proxy (weak_ptr<Encoder> encoder, shared_ptr<const Image> image, bool same)
+{
+ shared_ptr<Encoder> e = encoder.lock ();
+ if (e) {
+ e->process_video (image, same);
+ }
+}
+
+static void
+audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio)
+{
+ shared_ptr<Encoder> e = encoder.lock ();
+ if (e) {
+ e->process_audio (audio);
+ }
+}
+
/** Construct a transcoder using a Decoder that we create and a supplied Encoder.
* @param f Film that we are transcoding.
* @param j Job that we are running under, or 0.
, _player (f->player ())
, _encoder (new Encoder (f, j))
{
- _player->connect_video (_encoder);
- _player->connect_audio (_encoder);
+ _player->Video.connect (bind (video_proxy, _encoder, _1, _2));
+ _player->Audio.connect (bind (audio_proxy, _encoder, _1));
}
void
class Content;
-typedef int64_t ContentAudioFrame;
-typedef int ContentVideoFrame;
typedef int64_t Time;
#define TIME_MAX INT64_MAX
#define TIME_HZ ((Time) 96000)
* @return Equivalent number of audio frames for `v'.
*/
int64_t
-video_frames_to_audio_frames (ContentVideoFrame v, float audio_sample_rate, float frames_per_second)
+video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
{
return ((int64_t) v * audio_sample_rate / frames_per_second);
}
}
#include "compose.hpp"
#include "types.h"
+#include "video_content.h"
#ifdef DCPOMATIC_DEBUG
#define TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TIMING);
int _timeout;
};
-extern int64_t video_frames_to_audio_frames (ContentVideoFrame v, float audio_sample_rate, float frames_per_second);
+extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
class LocaleGuard
{
#include "video_content.h"
#include "video_examiner.h"
#include "ratio.h"
+#include "compose.hpp"
#include "i18n.h"
using boost::lexical_cast;
using boost::optional;
-VideoContent::VideoContent (shared_ptr<const Film> f, Time s, ContentVideoFrame len)
+VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
: Content (f, s)
, _video_length (len)
, _video_frame_rate (0)
VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
: Content (f, node)
{
- _video_length = node->number_child<ContentVideoFrame> ("VideoLength");
+ _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
_video_size.width = node->number_child<int> ("VideoWidth");
_video_size.height = node->number_child<int> ("VideoHeight");
_video_frame_rate = node->number_child<float> ("VideoFrameRate");
#define DCPOMATIC_VIDEO_CONTENT_H
#include "content.h"
-#include "util.h"
class VideoExaminer;
class Ratio;
class VideoContent : public virtual Content
{
public:
- VideoContent (boost::shared_ptr<const Film>, Time, ContentVideoFrame);
+ typedef int Frame;
+
+ VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
VideoContent (VideoContent const &);
void as_xml (xmlpp::Node *) const;
virtual std::string information () const;
- ContentVideoFrame video_length () const {
+ VideoContent::Frame video_length () const {
boost::mutex::scoped_lock lm (_mutex);
return _video_length;
}
protected:
void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
- ContentVideoFrame _video_length;
+ VideoContent::Frame _video_length;
private:
libdcp::Size _video_size;
using std::cout;
using boost::shared_ptr;
-VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
+VideoDecoder::VideoDecoder (shared_ptr<const Film> f)
: Decoder (f)
- , _next_video (0)
- , _video_content (c)
- , _frame_rate_conversion (c->video_frame_rate(), f->dcp_video_frame_rate())
- , _odd (false)
+ , _next_video_frame (0)
{
}
-/** Called by subclasses when some video is ready.
- * @param image frame to emit.
- * @param same true if this frame is the same as the last one passed to this call.
- * @param t Time of the frame within the source.
- */
void
-VideoDecoder::video (shared_ptr<Image> image, bool same, Time t)
+VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
{
- if (_frame_rate_conversion.skip && _odd) {
- _odd = !_odd;
- return;
- }
-
- image = image->crop (_video_content->crop(), true);
-
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- libdcp::Size const container_size = _video_container_size.get_value_or (film->container()->size (film->full_frame ()));
- libdcp::Size const image_size = _video_content->ratio()->size (container_size);
-
- shared_ptr<Image> out = image->scale_and_convert_to_rgb (image_size, film->scaler(), true);
-
- if (film->with_subtitles ()) {
- shared_ptr<Subtitle> sub;
- if (_timed_subtitle && _timed_subtitle->displayed_at (t)) {
- sub = _timed_subtitle->subtitle ();
- }
-
- if (sub) {
- dcpomatic::Rect const tx = subtitle_transformed_area (
- float (image_size.width) / _video_content->video_size().width,
- float (image_size.height) / _video_content->video_size().height,
- sub->area(), film->subtitle_offset(), film->subtitle_scale()
- );
-
- shared_ptr<Image> im = sub->image()->scale (tx.size(), film->scaler(), true);
- out->alpha_blend (im, tx.position());
- }
- }
-
- if (image_size != container_size) {
- assert (image_size.width <= container_size.width);
- assert (image_size.height <= container_size.height);
- shared_ptr<Image> im (new SimpleImage (PIX_FMT_RGB24, container_size, true));
- im->make_black ();
- im->copy (out, Position ((container_size.width - image_size.width) / 2, (container_size.height - image_size.height) / 2));
- out = im;
- }
-
- Video (out, same, t);
-
- if (_frame_rate_conversion.repeat) {
- Video (image, true, t + film->video_frames_to_time (1));
- _next_video = t + film->video_frames_to_time (2);
- } else {
- _next_video = t + film->video_frames_to_time (1);
- }
-
- _odd = !_odd;
+ Video (image, same, frame);
+ _next_video_frame = frame + 1;
}
+#if 0
+
/** Called by subclasses when a subtitle is ready.
* s may be 0 to say that there is no current subtitle.
* @param s New current subtitle, or 0.
_timed_subtitle->subtitle()->set_position (Position (p.x - _video_content->crop().left, p.y - _video_content->crop().top));
}
}
+#endif
-bool
-VideoDecoder::video_done () const
-{
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
-
- return (_video_content->length() - _next_video) < film->video_frames_to_time (1);
-}
-
-void
-VideoDecoder::seek (Time t)
-{
- _next_video = t;
-}
-
-void
-VideoDecoder::seek_back ()
-{
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- _next_video -= film->video_frames_to_time (1);
-}
-
-void
-VideoDecoder::seek_forward ()
-{
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- _next_video += film->video_frames_to_time (1);
-}
-
-void
-VideoDecoder::set_video_container_size (libdcp::Size s)
-{
- _video_container_size = s;
-}
#ifndef DCPOMATIC_VIDEO_DECODER_H
#define DCPOMATIC_VIDEO_DECODER_H
-#include "video_source.h"
#include "decoder.h"
#include "util.h"
class VideoContent;
-class VideoDecoder : public VideoSource, public virtual Decoder
+class VideoDecoder : public virtual Decoder
{
public:
- VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
+ VideoDecoder (boost::shared_ptr<const Film>);
- virtual void seek (Time);
- virtual void seek_back ();
- virtual void seek_forward ();
-
- void set_video_container_size (libdcp::Size);
+ virtual void seek (VideoContent::Frame) = 0;
+ virtual void seek_back () = 0;
-protected:
+ /** Emitted when a video frame is ready.
+ * First parameter is the video image.
+ * Second parameter is true if the image is the same as the last one that was emitted.
+ * Third parameter is the frame within our source.
+ */
+ boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, VideoContent::Frame)> Video;
- void video (boost::shared_ptr<Image>, bool, Time);
- void subtitle (boost::shared_ptr<TimedSubtitle>);
- bool video_done () const;
-
- Time _next_video;
- boost::shared_ptr<const VideoContent> _video_content;
-
-private:
- boost::shared_ptr<TimedSubtitle> _timed_subtitle;
- FrameRateConversion _frame_rate_conversion;
- bool _odd;
- boost::optional<libdcp::Size> _video_container_size;
+protected:
+
+ void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+ VideoContent::Frame _next_video_frame;
};
#endif
#include <libdcp/types.h>
#include "types.h"
+#include "video_content.h"
class VideoExaminer
{
public:
virtual float video_frame_rate () const = 0;
virtual libdcp::Size video_size () const = 0;
- virtual ContentVideoFrame video_length () const = 0;
+ virtual VideoContent::Frame video_length () const = 0;
};
+++ /dev/null
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#ifndef DCPOMATIC_VIDEO_SINK_H
-#define DCPOMATIC_VIDEO_SINK_H
-
-#include <boost/shared_ptr.hpp>
-#include "util.h"
-
-class Subtitle;
-class Image;
-
-class VideoSink
-{
-public:
- /** Call with a frame of video.
- * @param i Video frame image.
- * @param same true if i is the same as last time we were called.
- */
- virtual void process_video (boost::shared_ptr<const Image> i, bool same, Time) = 0;
-};
-
-#endif
+++ /dev/null
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "video_source.h"
-#include "video_sink.h"
-
-using boost::shared_ptr;
-using boost::weak_ptr;
-using boost::bind;
-
-static void
-process_video_proxy (weak_ptr<VideoSink> sink, shared_ptr<const Image> image, bool same, Time time)
-{
- shared_ptr<VideoSink> p = sink.lock ();
- if (p) {
- p->process_video (image, same, time);
- }
-}
-
-void
-VideoSource::connect_video (shared_ptr<VideoSink> s)
-{
- /* If we bind, say, a Player (as the VideoSink) to a Decoder (which is owned
- by the Player) we create a cycle. Use a weak_ptr to break it.
- */
- Video.connect (bind (process_video_proxy, weak_ptr<VideoSink> (s), _1, _2, _3));
-}
-
+++ /dev/null
-/*
- Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-/** @file src/video_source.h
- * @brief Parent class for classes which emit video data.
- */
-
-#ifndef DCPOMATIC_VIDEO_SOURCE_H
-#define DCPOMATIC_VIDEO_SOURCE_H
-
-#include <boost/shared_ptr.hpp>
-#include <boost/signals2.hpp>
-#include "util.h"
-
-class VideoSink;
-class Subtitle;
-class Image;
-
-/** @class VideoSource
- * @param A class that emits video data.
- */
-class VideoSource
-{
-public:
-
- /** Emitted when a video frame is ready.
- * First parameter is the video image.
- * Second parameter is true if the image is the same as the last one that was emitted.
- * Third parameter is the time relative to the start of this source's content.
- */
- boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, Time)> Video;
-
- void connect_video (boost::shared_ptr<VideoSink>);
-};
-
-#endif
audio_content.cc
audio_decoder.cc
audio_mapping.cc
- audio_source.cc
black_decoder.cc
config.cc
- combiner.cc
content.cc
cross.cc
dci_metadata.cc
player.cc
playlist.cc
ratio.cc
+ resampler.cc
scp_dcp_job.cc
scaler.cc
server.cc
util.cc
video_content.cc
video_decoder.cc
- video_source.cc
writer.cc
"""