--- /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)
+ , _audio_position (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);
+ _audio_position = 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 _audio_position;
};
#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
+++ /dev/null
-/*
- Copyright (C) 2013 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 "black_decoder.h"
-#include "image.h"
-#include "null_content.h"
-
-using boost::shared_ptr;
-
-BlackDecoder::BlackDecoder (shared_ptr<const Film> f, shared_ptr<NullContent> c)
- : Decoder (f)
- , VideoDecoder (f, c)
-{
-
-}
-
-void
-BlackDecoder::pass ()
-{
- if (!_image) {
- _image.reset (new SimpleImage (AV_PIX_FMT_RGB24, video_size(), true));
- _image->make_black ();
- video (_image, false, _next_video);
- } else {
- video (_image, true, _next_video);
- }
-}
-
-float
-BlackDecoder::video_frame_rate () const
-{
- boost::shared_ptr<const Film> f = _film.lock ();
- if (!f) {
- return 24;
- }
-
- return f->dcp_video_frame_rate ();
-}
-
-ContentVideoFrame
-BlackDecoder::video_length () const
-{
- return _video_content->length() * video_frame_rate() / TIME_HZ;
-}
-
-Time
-BlackDecoder::position () const
-{
- return _next_video;
-}
-
-void
-BlackDecoder::seek (Time t)
-{
- _next_video = t;
-}
-
-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;
- }
-
- _next_video += f->video_frames_to_time (1);
-}
-
-bool
-BlackDecoder::done () const
-{
- return video_done ();
-}
-
-
+++ /dev/null
-/*
- Copyright (C) 2013 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_decoder.h"
-
-class NullContent;
-
-class BlackDecoder : public VideoDecoder
-{
-public:
- BlackDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<NullContent>);
-
- /* Decoder */
-
- void pass ();
- void seek (Time);
- void seek_back ();
- void seek_forward ();
- Time position () const;
- bool done () const;
-
- /* VideoDecoder */
-
- float video_frame_rate () const;
- libdcp::Size video_size () const {
- return libdcp::Size (256, 256);
- }
- ContentVideoFrame video_length () const;
-
-private:
- 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) {
frame_rate = node->number_child<int> ("FrameRate");
channels = node->number_child<int64_t> ("Channels");
mapping = AudioMapping (node->node_child ("Mapping"));
- start = node->optional_number_child<Time> ("Start");
+ first_audio = node->optional_number_child<Time> ("FirstAudio");
}
void
root->add_child("Id")->add_child_text (lexical_cast<string> (id));
root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
- if (start) {
- root->add_child("Start")->add_child_text (lexical_cast<string> (start));
+ if (first_audio) {
+ root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio));
}
mapping.as_xml (root->add_child("Mapping"));
}
int frame_rate;
int channels;
AudioMapping mapping;
- boost::optional<Time> start;
+ boost::optional<double> first_audio;
};
extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
/* 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;
void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>);
+
+ boost::optional<Time> first_video () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _first_video;
+ }
private:
std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
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)
, _decode_video (video)
, _decode_audio (audio)
+ , _pts_offset (0)
{
setup_subtitle ();
+
+ if (video && audio && c->audio_stream() && c->first_video() && c->audio_stream()->first_audio) {
+ _pts_offset = compute_pts_offset (c->first_video().get(), c->audio_stream()->first_audio.get(), c->video_frame_rate());
+ }
+}
+
+double
+FFmpegDecoder::compute_pts_offset (double first_video, double first_audio, float video_frame_rate)
+{
+ assert (first_video >= 0);
+ assert (first_audio >= 0);
+
+ double const old_first_video = first_video;
+
+ /* Round the first video to a frame boundary */
+ if (fabs (rint (first_video * video_frame_rate) - first_video * video_frame_rate) > 1e-6) {
+ first_video = ceil (first_video * video_frame_rate) / video_frame_rate;
+ }
+
+ /* Compute the required offset (also removing any common start delay) */
+ return first_video - old_first_video - min (first_video, first_audio);
}
FFmpegDecoder::~FFmpegDecoder ()
}
/* Stop us being asked for any more data */
- _next_video = _next_audio = _ffmpeg_content->length ();
+ _video_position = _ffmpeg_content->video_length ();
+ _audio_position = _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())) {
- return;
- }
-
- do_seek (position() - 2.5 * TIME_HZ / _ffmpeg_content->video_frame_rate(), true, true);
- VideoDecoder::seek_back ();
-}
-
-void
-FFmpegDecoder::seek_forward ()
-{
- if (position() >= (_ffmpeg_content->length() - 0.5 * TIME_HZ / _ffmpeg_content->video_frame_rate())) {
+ if (_video_position == 0) {
return;
}
- do_seek (position() - 0.5 * TIME_HZ / _ffmpeg_content->video_frame_rate(), true, true);
- VideoDecoder::seek_forward ();
+ do_seek (_video_position - 1, true, true);
}
void
-FFmpegDecoder::do_seek (Time t, bool backwards, bool accurate)
+FFmpegDecoder::do_seek (VideoContent::Frame frame, 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());
int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, ©_packet);
if (decode_result >= 0) {
if (frame_finished) {
-
- /* Where we are in the source, in seconds */
- double const source_pts_seconds = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame);
+
+ if (_audio_position == 0) {
+ /* Where we are in the source, in seconds */
+ double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
+ * av_frame_get_best_effort_timestamp(_frame) - _pts_offset;
+
+ if (pts > 0) {
+ /* Emit some silence */
+ shared_ptr<AudioBuffers> silence (
+ new AudioBuffers (
+ _ffmpeg_content->audio_channels(),
+ pts * _ffmpeg_content->content_audio_frame_rate()
+ )
+ );
+
+ silence->make_silent ();
+ audio (silence, _audio_position);
+ }
+ }
+
int const data_size = av_samples_get_buffer_size (
0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
);
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), _audio_position);
}
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) - _pts_offset;
+ double const next = _video_position / _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, _video_position);
+ 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, _video_position);
+ }
} 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 || (_video_position >= _ffmpeg_content->video_length());
+ bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
+ return vd && ad;
+}
+
#include "ffmpeg.h"
class Film;
+class ffmpeg_pts_offset_test;
/** @class FFmpegDecoder
* @brief A decoder using FFmpeg to decode content.
~FFmpegDecoder ();
void pass ();
- void seek (Time);
+ void seek (VideoContent::Frame);
void seek_back ();
- void seek_forward ();
- Time position () const;
bool done () const;
private:
+ friend class ::ffmpeg_pts_offset_test;
/* No copy construction */
FFmpegDecoder (FFmpegDecoder const &);
FFmpegDecoder& operator= (FFmpegDecoder const &);
+ static double compute_pts_offset (double, double, float);
+
void setup_subtitle ();
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 ();
bool _decode_video;
bool _decode_audio;
+
+ double _pts_offset;
};
}
} else {
for (size_t i = 0; i < _audio_streams.size(); ++i) {
- if (_packet.stream_index == _audio_streams[i]->id && !_audio_streams[i]->start) {
+ if (_packet.stream_index == _audio_streams[i]->id && !_audio_streams[i]->first_audio) {
if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
- _audio_streams[i]->start = frame_time (_audio_streams[i]->id);
+ _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->id);
}
}
}
bool have_all_audio = true;
size_t i = 0;
while (i < _audio_streams.size() && have_all_audio) {
- have_all_audio = _audio_streams[i]->start;
+ have_all_audio = _audio_streams[i]->first_audio;
++i;
}
}
}
-optional<Time>
+optional<double>
FFmpegExaminer::frame_time (int stream) const
{
- optional<Time> t;
+ optional<double> t;
int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
if (bet != AV_NOPTS_VALUE) {
- t = bet * av_q2d (_format_context->streams[stream]->time_base) * TIME_HZ;
+ t = bet * av_q2d (_format_context->streams[stream]->time_base);
}
return t;
}
/** @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;
return _audio_streams;
}
- boost::optional<Time> first_video () const {
+ boost::optional<double> first_video () const {
return _first_video;
}
private:
std::string stream_name (AVStream* s) const;
- boost::optional<Time> frame_time (int) const;
+ boost::optional<double> frame_time (int) const;
std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
- boost::optional<Time> _first_video;
+ boost::optional<double> _first_video;
};
#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 (_video_position >= _imagemagick_content->video_length ()) {
return;
}
if (_image) {
- video (_image, true, _next_video);
+ video (_image, true, _video_position);
return;
}
delete magick_image;
- _image = _image->crop (_imagemagick_content->crop(), true);
- video (_image, false, _next_video);
+ video (_image, false, _video_position);
}
void
-ImageMagickDecoder::seek (Time t)
+ImageMagickDecoder::seek (VideoContent::Frame frame)
{
- _next_video = t;
+ _video_position = 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 (_video_position > 0) {
+ _video_position--;
}
-
- _next_video += f->video_frames_to_time (1);
}
-Time
-ImageMagickDecoder::position () const
-{
- return _next_video;
-}
-
-
bool
ImageMagickDecoder::done () const
{
- return video_done ();
+ return _video_position >= _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;
-};
+++ /dev/null
-/*
- Copyright (C) 2013 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 "null_content.h"
-#include "film.h"
-
-using boost::shared_ptr;
-
-NullContent::NullContent (shared_ptr<const Film> f, Time s, Time len)
- : Content (f, s)
- , VideoContent (f, s, f->time_to_video_frames (len))
- , AudioContent (f, s)
- , _audio_length (f->time_to_audio_frames (len))
- , _length (len)
-{
-
-}
-
-int
-NullContent::content_audio_frame_rate () const
-{
- return output_audio_frame_rate ();
-}
-
-
-int
-NullContent::output_audio_frame_rate () const
-{
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- return film->dcp_audio_frame_rate ();
-}
-
-int
-NullContent::audio_channels () const
-{
- shared_ptr<const Film> film = _film.lock ();
- assert (film);
- return film->dcp_audio_channels ();
-}
-
+++ /dev/null
-/*
- Copyright (C) 2013 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 <string>
-#include <boost/shared_ptr.hpp>
-#include "video_content.h"
-#include "audio_content.h"
-
-class NullContent : public VideoContent, public AudioContent
-{
-public:
- NullContent (boost::shared_ptr<const Film>, Time, Time);
-
- std::string summary () const {
- return "";
- }
-
- std::string information () const {
- return "";
- }
-
- void as_xml (xmlpp::Node *) const {}
-
- boost::shared_ptr<Content> clone () const {
- return boost::shared_ptr<Content> ();
- }
-
- int audio_channels () const;
-
- ContentAudioFrame audio_length () const {
- return _audio_length;
- }
-
- int content_audio_frame_rate () const;
-
- int output_audio_frame_rate () const;
-
- AudioMapping audio_mapping () const {
- return AudioMapping ();
- }
-
- void set_audio_mapping (AudioMapping) {}
-
- Time length () const {
- return _length;
- }
-
-private:
- ContentAudioFrame _audio_length;
- Time _length;
-};
#include "playlist.h"
#include "job.h"
#include "image.h"
-#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;
#define DEBUG_PLAYER 1
-struct Piece
+class Piece
{
+public:
+ Piece (shared_ptr<Content> c)
+ : content (c)
+ , video_position (c->start ())
+ , audio_position (c->start ())
+ {}
+
Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
: content (c)
, decoder (d)
+ , video_position (c->start ())
+ , audio_position (c->start ())
{}
shared_ptr<Content> content;
shared_ptr<Decoder> decoder;
+ Time video_position;
+ Time audio_position;
};
-
#ifdef DEBUG_PLAYER
std::ostream& operator<<(std::ostream& s, Piece const & p)
{
- if (dynamic_pointer_cast<NullContent> (p.content)) {
- if (dynamic_pointer_cast<SilenceDecoder> (p.decoder)) {
- s << "\tsilence ";
- } else {
- s << "\tblack ";
- }
- } else if (dynamic_pointer_cast<FFmpegContent> (p.content)) {
+ if (dynamic_pointer_cast<FFmpegContent> (p.content)) {
s << "\tffmpeg ";
} else if (dynamic_pointer_cast<ImageMagickContent> (p.content)) {
s << "\timagemagick";
, _video (true)
, _audio (true)
, _have_valid_pieces (false)
- , _position (0)
+ , _video_position (0)
+ , _audio_position (0)
, _audio_buffers (f->dcp_audio_channels(), 0)
- , _next_audio (0)
{
_playlist->Changed.connect (bind (&Player::playlist_changed, this));
_playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2));
+ set_video_container_size (_film->container()->size (_film->full_frame ()));
}
void
_have_valid_pieces = true;
}
- /* Here we are just finding the active decoder with the earliest last emission time, then
- calling pass on it.
- */
+#ifdef DEBUG_PLAYER
+ cout << "= PASS\n";
+#endif
Time earliest_t = TIME_MAX;
shared_ptr<Piece> earliest;
+ enum {
+ VIDEO,
+ AUDIO
+ } type = VIDEO;
for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
if ((*i)->decoder->done ()) {
continue;
}
- if (!_audio && dynamic_pointer_cast<AudioDecoder> ((*i)->decoder) && !dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
- continue;
+ if (dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
+ if ((*i)->video_position < earliest_t) {
+ earliest_t = (*i)->video_position;
+ earliest = *i;
+ type = VIDEO;
+ }
}
-
- Time const t = (*i)->content->start() + (*i)->decoder->position();
- if (t < earliest_t) {
- earliest_t = t;
- earliest = *i;
+
+ if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
+ if ((*i)->audio_position < earliest_t) {
+ earliest_t = (*i)->audio_position;
+ earliest = *i;
+ type = AUDIO;
+ }
}
}
if (!earliest) {
+#ifdef DEBUG_PLAYER
+ cout << "no earliest piece.\n";
+#endif
+
flush ();
return true;
}
- earliest->decoder->pass ();
- _position = earliest->content->start() + earliest->decoder->position ();
+ switch (type) {
+ case VIDEO:
+ if (earliest_t > _video_position) {
+#ifdef DEBUG_PLAYER
+ cout << "no video here; emitting black frame.\n";
+#endif
+ emit_black ();
+ } else {
+#ifdef DEBUG_PLAYER
+ cout << "Pass " << *earliest << "\n";
+#endif
+ earliest->decoder->pass ();
+ }
+ break;
+
+ case AUDIO:
+ if (earliest_t > _audio_position) {
+#ifdef DEBUG_PLAYER
+ cout << "no audio here; emitting silence.\n";
+#endif
+ emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
+ } else {
+#ifdef DEBUG_PLAYER
+ cout << "Pass " << *earliest << "\n";
+#endif
+ earliest->decoder->pass ();
+ }
+ break;
+ }
+
+#ifdef DEBUG_PLAYER
+ cout << "\tpost pass " << _video_position << " " << _audio_position << "\n";
+#endif
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 image_size = content->ratio()->size (_video_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 != _video_container_size) {
+ assert (image_size.width <= _video_container_size.width);
+ assert (image_size.height <= _video_container_size.height);
+ shared_ptr<Image> im (new SimpleImage (PIX_FMT_RGB24, _video_container_size, true));
+ im->make_black ();
+ im->copy (image, Position ((_video_container_size.width - image_size.width) / 2, (_video_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);
+ time += TIME_HZ / _film->dcp_video_frame_rate();
+
+ if (frc.repeat) {
+ Video (image, true, time);
+ time += TIME_HZ / _film->dcp_video_frame_rate();
+ }
+
+ _video_position = piece->video_position = 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());
- cout << "Player gets " << audio->frames() << " @ " << time << " cf " << _next_audio << "\n";
-
- if (time > _next_audio) {
+ if (time > _audio_position) {
/* We can emit some audio from our buffers */
- OutputAudioFrame const N = _film->time_to_audio_frames (time - _next_audio);
+ OutputAudioFrame const N = _film->time_to_audio_frames (time - _audio_position);
assert (N <= _audio_buffers.frames());
shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), N));
emit->copy_from (&_audio_buffers, N, 0, 0);
- Audio (emit, _next_audio);
- _next_audio += _film->audio_frames_to_time (N);
+ Audio (emit, _audio_position);
+ _audio_position = piece->audio_position = time + _film->audio_frames_to_time (N);
/* And remove it from our buffers */
if (_audio_buffers.frames() > N) {
if (_audio_buffers.frames() > 0) {
shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), _audio_buffers.frames()));
emit->copy_from (&_audio_buffers, _audio_buffers.frames(), 0, 0);
- Audio (emit, _next_audio);
- _next_audio += _film->audio_frames_to_time (_audio_buffers.frames ());
+ Audio (emit, _audio_position);
+ _audio_position += _film->audio_frames_to_time (_audio_buffers.frames ());
_audio_buffers.set_frames (0);
}
+
+ while (_video_position < _audio_position) {
+ emit_black ();
+ }
+
+ while (_audio_position < _video_position) {
+ emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+ }
+
}
/** @return true on error */
}
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)));
-}
-
-void
-Player::add_silent_piece (Time s, Time len)
-{
- 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)));
-}
-
-
void
Player::setup_pieces ()
{
for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) {
- shared_ptr<Decoder> decoder;
-
+ shared_ptr<Piece> piece (new Piece (*i));
+
/* XXX: into content? */
shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
if (fc) {
shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
- 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 ());
- }
+ fd->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3));
+ fd->Audio.connect (bind (&Player::process_audio, this, piece, _1, _2));
- decoder = fd;
+ piece->decoder = fd;
}
shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i);
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 ());
- }
+ id->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3));
}
- decoder = id;
+ piece->decoder = id;
}
shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
if (sc) {
shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
- sd->Audio.connect (bind (&Player::process_audio, this, *i, _1, _2));
+ sd->Audio.connect (bind (&Player::process_audio, this, piece, _1, _2));
- decoder = sd;
+ piece->decoder = sd;
}
- _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder)));
- }
-
- /* Fill in visual gaps with black and audio gaps with silence */
-
- Time video_pos = 0;
- Time audio_pos = 0;
- list<shared_ptr<Piece> > pieces_copy = _pieces;
- for (list<shared_ptr<Piece> >::iterator i = pieces_copy.begin(); i != pieces_copy.end(); ++i) {
- if (dynamic_pointer_cast<VideoContent> ((*i)->content)) {
- Time const diff = (*i)->content->start() - video_pos;
- if (diff > 0) {
- add_black_piece (video_pos, diff);
- }
- video_pos = (*i)->content->end();
- }
-
- shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> ((*i)->content);
- if (ac && ac->audio_channels()) {
- Time const diff = (*i)->content->start() - audio_pos;
- if (diff > 0) {
- add_silent_piece (video_pos, diff);
- }
- audio_pos = (*i)->content->end();
- }
- }
-
- if (video_pos < audio_pos) {
- add_black_piece (video_pos, audio_pos - video_pos);
- } else if (audio_pos < video_pos) {
- add_silent_piece (audio_pos, video_pos - audio_pos);
+ _pieces.push_back (piece);
}
#ifdef DEBUG_PLAYER
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);
- }
+ _black_frame.reset (new SimpleImage (PIX_FMT_RGB24, _video_container_size, true));
+ _black_frame->make_black ();
+}
+
+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;
}
+
+void
+Player::emit_black ()
+{
+ /* XXX: use same here */
+ Video (_black_frame, false, _video_position);
+ _video_position += _film->video_frames_to_time (1);
+}
+
+void
+Player::emit_silence (OutputAudioFrame most)
+{
+ OutputAudioFrame N = min (most, _film->dcp_audio_frame_rate() / 2);
+ shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->dcp_audio_channels(), N));
+ silence->make_silent ();
+ Audio (silence, _audio_position);
+ _audio_position += _film->audio_frames_to_time (N);
+}
+
+
+
#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 {
- return _position;
+ Time video_position () const {
+ return _video_position;
}
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 do_seek (Time, bool);
- void add_black_piece (Time, Time);
- void add_silent_piece (Time, Time);
void flush ();
+ void emit_black ();
+ void emit_silence (OutputAudioFrame);
+ boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>);
boost::shared_ptr<const Film> _film;
boost::shared_ptr<const Playlist> _playlist;
/** Our pieces are ready to go; if this is false the pieces must be (re-)created before they are used */
bool _have_valid_pieces;
std::list<boost::shared_ptr<Piece> > _pieces;
- Time _position;
+
+ /** The time after the last video that we emitted */
+ Time _video_position;
+ /** The time after the last audio that we emitted */
+ Time _audio_position;
+
AudioBuffers _audio_buffers;
- Time _next_audio;
- boost::optional<libdcp::Size> _video_container_size;
+
+ libdcp::Size _video_container_size;
+ boost::shared_ptr<Image> _black_frame;
+ 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;
+};
+++ /dev/null
-/*
- Copyright (C) 2013 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 "silence_decoder.h"
-#include "null_content.h"
-#include "audio_buffers.h"
-
-using std::min;
-using std::cout;
-using boost::shared_ptr;
-
-SilenceDecoder::SilenceDecoder (shared_ptr<const Film> f, shared_ptr<NullContent> c)
- : Decoder (f)
- , AudioDecoder (f, c)
-{
-
-}
-
-void
-SilenceDecoder::pass ()
-{
- 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)));
- 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;
-}
-
-bool
-SilenceDecoder::done () const
-{
- return audio_done ();
-}
-
+++ /dev/null
-/*
- Copyright (C) 2013 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/shared_ptr.hpp>
-#include "audio_decoder.h"
-
-class Film;
-class NullContent;
-
-class SilenceDecoder : public AudioDecoder
-{
-public:
- 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;
-};
, 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 _audio_position >= _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)
+ , _video_position (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);
+ _video_position = 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 _video_position;
};
#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
void
Writer::write (shared_ptr<const AudioBuffers> audio)
{
- cout << "W: audio " << audio->frames() << "\n";
_sound_asset_writer->write (audio->data(), audio->frames());
}
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
job_manager.cc
log.cc
lut.cc
- null_content.cc
player.cc
playlist.cc
ratio.cc
+ resampler.cc
scp_dcp_job.cc
scaler.cc
server.cc
- silence_decoder.cc
sndfile_content.cc
sndfile_decoder.cc
sound_processor.cc
util.cc
video_content.cc
video_decoder.cc
- video_source.cc
writer.cc
"""
return;
}
- _player->seek (_player->position() - _film->video_frames_to_time (1));
+ _player->seek (_player->video_position() - _film->video_frames_to_time (1));
get_frame ();
_panel->Refresh ();
_panel->Update ();
get_frame ();
if (_film->length()) {
- int const new_slider_position = 4096 * _player->position() / _film->length();
+ int const new_slider_position = 4096 * _player->video_position() / _film->length();
if (new_slider_position != _slider->GetValue()) {
_slider->SetValue (new_slider_position);
}
return;
}
- _player->seek_forward ();
get_frame ();
_panel->Refresh ();
_panel->Update ();
shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/count300bd24.m2ts"));
shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (content));
- BOOST_CHECK_EQUAL (examiner->first_video().get(), 57604000);
+ BOOST_CHECK_EQUAL (examiner->first_video().get(), 600.04166666666674);
BOOST_CHECK_EQUAL (examiner->audio_streams().size(), 1);
- BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->start.get(), 57600000);
+ BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->first_audio.get(), 600);
}
--- /dev/null
+/*
+ Copyright (C) 2013 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.
+
+*/
+
+BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
+{
+ /* Sound == video so no offset required */
+ BOOST_CHECK_EQUAL (FFmpegDecoder::compute_pts_offset (0, 0, 24), 0);
+
+ /* Common offset should be removed */
+ BOOST_CHECK_CLOSE (FFmpegDecoder::compute_pts_offset (42, 42, 24), -42, 1e-9);
+
+ /* Video is on a frame boundary */
+ BOOST_CHECK_EQUAL (FFmpegDecoder::compute_pts_offset (1.0 / 24.0, 0, 24), 0);
+
+ /* Again, video is on a frame boundary */
+ BOOST_CHECK_EQUAL (FFmpegDecoder::compute_pts_offset (1.0 / 23.97, 0, 23.97), 0);
+
+ /* And again, video is on a frame boundary */
+ BOOST_CHECK_EQUAL (FFmpegDecoder::compute_pts_offset (3.0 / 23.97, 0, 23.97), 0);
+
+ /* Off a frame boundary */
+ BOOST_CHECK_CLOSE (FFmpegDecoder::compute_pts_offset (1.0 / 24.0 - 0.0215, 0, 24), 0.0215, 1e-9);
+}
BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2)));
}
+#include "ffmpeg_pts_offset.cc"
#include "ffmpeg_examiner_test.cc"
#include "black_fill_test.cc"
#include "scaling_test.cc"