From: Carl Hetherington Date: Sat, 25 May 2013 00:07:35 +0000 (+0100) Subject: Merge master and multifarious hackery. X-Git-Tag: v2.0.48~1337^2~347 X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=996b0c06e23bcb6b300d7b8799df94993692e07d Merge master and multifarious hackery. --- 996b0c06e23bcb6b300d7b8799df94993692e07d diff --cc cscript index 17f267628,521bc54f9..ffbca4168 --- a/cscript +++ b/cscript @@@ -7,9 -7,8 +7,9 @@@ def dependencies(target) return () else: return (('openjpeg-cdist', None), + ('libcxml', None), - ('ffmpeg-cdist', '488d5d4496af5e3a3b9d31d6b221e8eeada6b77e'), + ('ffmpeg-cdist', '7a23ec9c771184ab563cfe24ad9b427f38368961'), - ('libdcp', 'v0.49')) + ('libdcp', None)) def build(env, target): cmd = './waf configure --prefix=%s' % env.work_dir_cscript() diff --cc src/lib/ab_transcode_job.cc index 2bdff47de,4ffdd9af6..9a883fdd9 --- a/src/lib/ab_transcode_job.cc +++ b/src/lib/ab_transcode_job.cc @@@ -38,7 -39,8 +38,8 @@@ ABTranscodeJob::ABTranscodeJob (shared_ { _film_b.reset (new Film (*_film)); _film_b->set_scaler (Config::instance()->reference_scaler ()); -- _film_b->set_filters (Config::instance()->reference_filters ()); ++ /* XXX */ ++// _film_b->set_filters (Config::instance()->reference_filters ()); } string diff --cc src/lib/audio_decoder.cc index 8950e1546,a54c14843..9b8d15bf1 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@@ -18,138 -18,19 +18,140 @@@ */ #include "audio_decoder.h" -#include "stream.h" +#include "audio_buffers.h" +#include "exceptions.h" +#include "log.h" +#include "i18n.h" + +using std::stringstream; ++using std::list; ++using std::pair; using boost::optional; using boost::shared_ptr; -AudioDecoder::AudioDecoder (shared_ptr f, DecodeOptions o) - : Decoder (f, o) +AudioDecoder::AudioDecoder (shared_ptr f, shared_ptr c) + : Decoder (f) + , _next_audio (0) + , _audio_content (c) { + if (_audio_content->content_audio_frame_rate() != _audio_content->output_audio_frame_rate()) { + + shared_ptr 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 (MAX_AUDIO_CHANNELS), + AV_SAMPLE_FMT_FLTP, + _audio_content->output_audio_frame_rate(), + av_get_default_channel_layout (MAX_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::set_audio_stream (shared_ptr s) +AudioDecoder::process_end () { - _audio_stream = s; + if (_swr_context) { + + shared_ptr film = _film.lock (); + assert (film); + + shared_ptr out (new AudioBuffers (film->audio_mapping().dcp_channels(), 256)); + + while (1) { + int const frames = swr_convert (_swr_context, (uint8_t **) out->data(), 256, 0, 0); + + if (frames < 0) { + throw EncodeError (_("could not run sample-rate converter")); + } + + if (frames == 0) { + break; + } + + out->set_frames (frames); + _writer->write (out); + } + + } } +#endif + +void +AudioDecoder::audio (shared_ptr 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 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 film = _film.lock (); + assert (film); + + /* Remap channels */ - shared_ptr dcp_mapped (film->dcp_audio_channels(), data->frames()); ++ shared_ptr dcp_mapped (new AudioBuffers (film->dcp_audio_channels(), data->frames())); + dcp_mapped->make_silent (); + list > map = _audio_content->audio_mapping().content_to_dcp (); + for (list >::iterator i = map.begin(); i != map.end(); ++i) { - dcp_mapped->accumulate (data, i->first, i->second); ++ dcp_mapped->accumulate_channel (data.get(), i->first, i->second); + } + + Audio (dcp_mapped, time); + _next_audio = time + film->audio_frames_to_time (data->frames()); +} + + diff --cc src/lib/dcp_video_frame.cc index da51665d1,d674393a9..1c1838df7 --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video_frame.cc @@@ -79,7 -80,7 +79,7 @@@ using libdcp::Size DCPVideoFrame::DCPVideoFrame ( shared_ptr yuv, shared_ptr sub, Size out, int p, int subtitle_offset, float subtitle_scale, -- Scaler const * s, int f, int dcp_fps, string pp, int clut, int bw, shared_ptr l ++ Scaler const * s, int f, int dcp_fps, int clut, int bw, shared_ptr l ) : _input (yuv) , _subtitle (sub) @@@ -90,7 -91,7 +90,6 @@@ , _scaler (s) , _frame (f) , _frames_per_second (dcp_fps) -- , _post_process (pp) , _colour_lut (clut) , _j2k_bandwidth (bw) , _log (l) @@@ -156,10 -157,10 +155,6 @@@ DCPVideoFrame::~DCPVideoFrame ( shared_ptr DCPVideoFrame::encode_locally () { -- if (!_post_process.empty ()) { -- _input = _input->post_process (_post_process, true); -- } -- shared_ptr prepared = _input->scale_and_convert_to_rgb (_out_size, _padding, _scaler, true); if (_subtitle) { @@@ -333,10 -334,10 +328,6 @@@ DCPVideoFrame::encode_remotely (ServerD << N_("frame ") << _frame << N_("\n") << N_("frames_per_second ") << _frames_per_second << N_("\n"); -- if (!_post_process.empty()) { -- s << N_("post_process ") << _post_process << N_("\n"); -- } -- s << N_("colour_lut ") << _colour_lut << N_("\n") << N_("j2k_bandwidth ") << _j2k_bandwidth << N_("\n"); diff --cc src/lib/dcp_video_frame.h index 4ceb07d26,4ceb07d26..ba49c95a4 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/dcp_video_frame.h @@@ -107,7 -107,7 +107,7 @@@ class DCPVideoFram public: DCPVideoFrame ( boost::shared_ptr, boost::shared_ptr, libdcp::Size, -- int, int, float, Scaler const *, int, int, std::string, int, int, boost::shared_ptr ++ int, int, float, Scaler const *, int, int, int, int, boost::shared_ptr ); virtual ~DCPVideoFrame (); @@@ -131,7 -131,7 +131,6 @@@ private Scaler const * _scaler; ///< scaler to use int _frame; ///< frame index within the DCP's intrinsic duration int _frames_per_second; ///< Frames per second that we will use for the DCP -- std::string _post_process; ///< FFmpeg post-processing string to use int _colour_lut; ///< Colour look-up table to use int _j2k_bandwidth; ///< J2K bandwidth to use diff --cc src/lib/encoder.cc index 52927c5d3,8549962ff..270bf3d43 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@@ -207,14 -267,13 +207,13 @@@ Encoder::process_video (shared_ptr const s = Filter::ffmpeg_strings (_film->filters()); TIMING ("adding to queue of %1", _queue.size ()); - _queue.push_back (boost::shared_ptr ( + /* XXX: padding */ + _queue.push_back (shared_ptr ( new DCPVideoFrame ( - image, sub, _film->format()->dcp_size(), _film->format()->dcp_padding (_film), + image, sub, _film->container()->dcp_size(), 0, _film->subtitle_offset(), _film->subtitle_scale(), - _film->scaler(), _video_frames_out, _film->dcp_video_frame_rate(), s.second, - _film->scaler(), _video_frames_out, _film->dcp_frame_rate(), s.second, ++ _film->scaler(), _video_frames_out, _film->dcp_video_frame_rate(), _film->colour_lut(), _film->j2k_bandwidth(), _film->log() ) diff --cc src/lib/ffmpeg_content.cc index ad7af07d8,000000000..55139ca56 mode 100644,000000..100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@@ -1,337 -1,0 +1,359 @@@ +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */ + +/* + Copyright (C) 2013 Carl Hetherington + + 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 +#include "ffmpeg_content.h" +#include "ffmpeg_decoder.h" +#include "compose.hpp" +#include "job.h" +#include "util.h" ++#include "filter.h" +#include "log.h" + +#include "i18n.h" + +using std::string; +using std::stringstream; +using std::vector; +using std::list; +using std::cout; +using boost::shared_ptr; +using boost::lexical_cast; + +int const FFmpegContentProperty::SUBTITLE_STREAMS = 100; +int const FFmpegContentProperty::SUBTITLE_STREAM = 101; +int const FFmpegContentProperty::AUDIO_STREAMS = 102; +int const FFmpegContentProperty::AUDIO_STREAM = 103; ++int const FFmpegContentProperty::FILTERS = 104; + +FFmpegContent::FFmpegContent (shared_ptr f, boost::filesystem::path p) + : Content (f, p) + , VideoContent (f, p) + , AudioContent (f, p) +{ + +} + +FFmpegContent::FFmpegContent (shared_ptr f, shared_ptr node) + : Content (f, node) + , VideoContent (f, node) + , AudioContent (f, node) +{ + list > c = node->node_children ("SubtitleStream"); + for (list >::const_iterator i = c.begin(); i != c.end(); ++i) { + _subtitle_streams.push_back (shared_ptr (new FFmpegSubtitleStream (*i))); + if ((*i)->optional_number_child ("Selected")) { + _subtitle_stream = _subtitle_streams.back (); + } + } + + c = node->node_children ("AudioStream"); + for (list >::const_iterator i = c.begin(); i != c.end(); ++i) { + _audio_streams.push_back (shared_ptr (new FFmpegAudioStream (*i))); + if ((*i)->optional_number_child ("Selected")) { + _audio_stream = _audio_streams.back (); + } + } ++ ++ c = node->node_children ("Filter"); ++ for (list >::iterator i = c.begin(); i != c.end(); ++i) { ++ _filters.push_back (Filter::from_id ((*i)->content ())); ++ } +} + +FFmpegContent::FFmpegContent (FFmpegContent const & o) + : Content (o) + , VideoContent (o) + , AudioContent (o) + , _subtitle_streams (o._subtitle_streams) + , _subtitle_stream (o._subtitle_stream) + , _audio_streams (o._audio_streams) + , _audio_stream (o._audio_stream) +{ + +} + +void +FFmpegContent::as_xml (xmlpp::Node* node) const +{ + node->add_child("Type")->add_child_text ("FFmpeg"); + Content::as_xml (node); + VideoContent::as_xml (node); + AudioContent::as_xml (node); + + boost::mutex::scoped_lock lm (_mutex); + + for (vector >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) { + xmlpp::Node* t = node->add_child("SubtitleStream"); + if (_subtitle_stream && *i == _subtitle_stream) { + t->add_child("Selected")->add_child_text("1"); + } + (*i)->as_xml (t); + } + + for (vector >::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) { + xmlpp::Node* t = node->add_child("AudioStream"); + if (_audio_stream && *i == _audio_stream) { + t->add_child("Selected")->add_child_text("1"); + } + (*i)->as_xml (t); + } ++ ++ for (vector::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { ++ node->add_child("Filter")->add_child_text ((*i)->id ()); ++ } +} + +void +FFmpegContent::examine (shared_ptr job) +{ + job->set_progress_unknown (); + + Content::examine (job); + + shared_ptr film = _film.lock (); + assert (film); + + shared_ptr decoder (new FFmpegDecoder (film, shared_from_this (), true, false, false)); + + ContentVideoFrame video_length = 0; + video_length = decoder->video_length (); + film->log()->log (String::compose ("Video length obtained from header as %1 frames", decoder->video_length ())); + + { + boost::mutex::scoped_lock lm (_mutex); + + _video_length = video_length; + + _subtitle_streams = decoder->subtitle_streams (); + if (!_subtitle_streams.empty ()) { + _subtitle_stream = _subtitle_streams.front (); + } + + _audio_streams = decoder->audio_streams (); + if (!_audio_streams.empty ()) { + _audio_stream = _audio_streams.front (); + } + } + + take_from_video_decoder (decoder); + + signal_changed (VideoContentProperty::VIDEO_LENGTH); + signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS); + signal_changed (FFmpegContentProperty::SUBTITLE_STREAM); + signal_changed (FFmpegContentProperty::AUDIO_STREAMS); + signal_changed (FFmpegContentProperty::AUDIO_STREAM); + signal_changed (AudioContentProperty::AUDIO_CHANNELS); +} + +string +FFmpegContent::summary () const +{ + return String::compose (_("Movie: %1"), file().filename().string()); +} + +string +FFmpegContent::information () const +{ + if (video_length() == 0 || video_frame_rate() == 0) { + return ""; + } + + stringstream s; + + s << String::compose (_("%1 frames; %2 frames per second"), video_length(), video_frame_rate()) << "\n"; + s << VideoContent::information (); + + return s.str (); +} + +void +FFmpegContent::set_subtitle_stream (shared_ptr s) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _subtitle_stream = s; + } + + signal_changed (FFmpegContentProperty::SUBTITLE_STREAM); +} + +void +FFmpegContent::set_audio_stream (shared_ptr s) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_stream = s; + } + + signal_changed (FFmpegContentProperty::AUDIO_STREAM); +} + +ContentAudioFrame +FFmpegContent::audio_length () const +{ + int const cafr = content_audio_frame_rate (); + int const vfr = video_frame_rate (); + ContentVideoFrame const vl = video_length (); + + boost::mutex::scoped_lock lm (_mutex); + if (!_audio_stream) { + return 0; + } + + return video_frames_to_audio_frames (vl, cafr, vfr); +} + +int +FFmpegContent::audio_channels () const +{ + boost::mutex::scoped_lock lm (_mutex); + + if (!_audio_stream) { + return 0; + } + + return _audio_stream->channels; +} + +int +FFmpegContent::content_audio_frame_rate () const +{ + boost::mutex::scoped_lock lm (_mutex); + + if (!_audio_stream) { + return 0; + } + + return _audio_stream->frame_rate; +} + +int +FFmpegContent::output_audio_frame_rate () const +{ + shared_ptr film = _film.lock (); + assert (film); + + /* Resample to a DCI-approved sample rate */ + double t = dcp_audio_frame_rate (content_audio_frame_rate ()); + + FrameRateConversion frc (video_frame_rate(), film->dcp_video_frame_rate()); + + /* Compensate if the DCP is being run at a different frame rate + to the source; that is, if the video is run such that it will + look different in the DCP compared to the source (slower or faster). + skip/repeat doesn't come into effect here. + */ + + if (frc.change_speed) { + t *= video_frame_rate() * frc.factor() / film->dcp_video_frame_rate(); + } + + return rint (t); +} + +bool +operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b) +{ + return a.id == b.id; +} + +bool +operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b) +{ + return a.id == b.id; +} + +FFmpegAudioStream::FFmpegAudioStream (shared_ptr node) +{ + name = node->string_child ("Name"); + id = node->number_child ("Id"); + frame_rate = node->number_child ("FrameRate"); + channels = node->number_child ("Channels"); + mapping = AudioMapping (node->node_child ("Mapping")); +} + +void +FFmpegAudioStream::as_xml (xmlpp::Node* root) const +{ + root->add_child("Name")->add_child_text (name); + root->add_child("Id")->add_child_text (lexical_cast (id)); + root->add_child("FrameRate")->add_child_text (lexical_cast (frame_rate)); + root->add_child("Channels")->add_child_text (lexical_cast (channels)); + mapping.as_xml (root->add_child("Mapping")); +} + +/** Construct a SubtitleStream from a value returned from to_string(). + * @param t String returned from to_string(). + * @param v State file version. + */ +FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr node) +{ + name = node->string_child ("Name"); + id = node->number_child ("Id"); +} + +void +FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const +{ + root->add_child("Name")->add_child_text (name); + root->add_child("Id")->add_child_text (lexical_cast (id)); +} + +shared_ptr +FFmpegContent::clone () const +{ + return shared_ptr (new FFmpegContent (*this)); +} + +Time +FFmpegContent::length () const +{ + shared_ptr film = _film.lock (); + assert (film); + + FrameRateConversion frc (video_frame_rate (), film->dcp_video_frame_rate ()); + return video_length() * frc.factor() * TIME_HZ / film->dcp_video_frame_rate (); +} + +AudioMapping +FFmpegContent::audio_mapping () const +{ + boost::mutex::scoped_lock lm (_mutex); + + if (!_audio_stream) { + return AudioMapping (); + } + + return _audio_stream->mapping; +} + ++void ++FFmpegContent::set_filters (vector const & filters) ++{ ++ { ++ boost::mutex::scoped_lock lm (_mutex); ++ _filters = filters; ++ } ++ ++ signal_changed (FFmpegContentProperty::FILTERS); ++} ++ diff --cc src/lib/ffmpeg_content.h index d5b986996,000000000..8f5c773ee mode 100644,000000..100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.h @@@ -1,135 -1,0 +1,147 @@@ +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */ + +/* + Copyright (C) 2013 Carl Hetherington + + 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_FFMPEG_CONTENT_H +#define DCPOMATIC_FFMPEG_CONTENT_H + +#include +#include "video_content.h" +#include "audio_content.h" + ++class Filter; ++ +class FFmpegAudioStream +{ +public: + FFmpegAudioStream (std::string n, int i, int f, int c) + : name (n) + , id (i) + , frame_rate (f) + , channels (c) + , mapping (c) + {} + + FFmpegAudioStream (boost::shared_ptr); + + void as_xml (xmlpp::Node *) const; + + std::string name; + int id; + int frame_rate; + int channels; + AudioMapping mapping; +}; + +extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b); + +class FFmpegSubtitleStream +{ +public: + FFmpegSubtitleStream (std::string n, int i) + : name (n) + , id (i) + {} + + FFmpegSubtitleStream (boost::shared_ptr); + + void as_xml (xmlpp::Node *) const; + + std::string name; + int id; +}; + +extern bool operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b); + +class FFmpegContentProperty : public VideoContentProperty +{ +public: + static int const SUBTITLE_STREAMS; + static int const SUBTITLE_STREAM; + static int const AUDIO_STREAMS; + static int const AUDIO_STREAM; ++ static int const FILTERS; +}; + +class FFmpegContent : public VideoContent, public AudioContent +{ +public: + FFmpegContent (boost::shared_ptr, boost::filesystem::path); + FFmpegContent (boost::shared_ptr, boost::shared_ptr); + FFmpegContent (FFmpegContent const &); + + boost::shared_ptr shared_from_this () { + return boost::dynamic_pointer_cast (Content::shared_from_this ()); + } + + void examine (boost::shared_ptr); + std::string summary () const; + std::string information () const; + void as_xml (xmlpp::Node *) const; + boost::shared_ptr clone () const; + Time length () const; + + /* AudioContent */ + int audio_channels () const; + ContentAudioFrame audio_length () const; + int content_audio_frame_rate () const; + int output_audio_frame_rate () const; + AudioMapping audio_mapping () const; ++ ++ void set_filters (std::vector const &); + + std::vector > subtitle_streams () const { + boost::mutex::scoped_lock lm (_mutex); + return _subtitle_streams; + } + + boost::shared_ptr subtitle_stream () const { + boost::mutex::scoped_lock lm (_mutex); + return _subtitle_stream; + } + + std::vector > audio_streams () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_streams; + } + + boost::shared_ptr audio_stream () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_stream; + } + ++ std::vector filters () const { ++ boost::mutex::scoped_lock lm (_mutex); ++ return _filters; ++ } ++ + void set_subtitle_stream (boost::shared_ptr); + void set_audio_stream (boost::shared_ptr); + +private: + std::vector > _subtitle_streams; + boost::shared_ptr _subtitle_stream; + std::vector > _audio_streams; + boost::shared_ptr _audio_stream; ++ /** Video filters that should be used when generating DCPs */ ++ std::vector _filters; +}; + +#endif diff --cc src/lib/ffmpeg_decoder.cc index 047829d45,982139515..119e82851 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@@ -503,31 -595,85 +503,15 @@@ FFmpegDecoder::do_seek (Time t, bool ba av_free_packet (&_packet); } } - - return r < 0; -} - -shared_ptr -FFmpegAudioStream::create (string t, optional v) -{ - if (!v) { - /* version < 1; no type in the string, and there's only FFmpeg streams anyway */ - return shared_ptr (new FFmpegAudioStream (t, v)); - } - - stringstream s (t); - string type; - s >> type; - if (type != N_("ffmpeg")) { - return shared_ptr (); - } - - return shared_ptr (new FFmpegAudioStream (t, v)); -} - -FFmpegAudioStream::FFmpegAudioStream (string t, optional version) -{ - stringstream n (t); - - int name_index = 4; - if (!version) { - name_index = 2; - int channels; - n >> _id >> channels; - _channel_layout = av_get_default_channel_layout (channels); - _sample_rate = 0; - } else { - string type; - /* Current (marked version 1) */ - n >> type >> _id >> _sample_rate >> _channel_layout; - assert (type == N_("ffmpeg")); - } - - for (int i = 0; i < name_index; ++i) { - size_t const s = t.find (' '); - if (s != string::npos) { - t = t.substr (s + 1); - } - } - - _name = t; -} - -string -FFmpegAudioStream::to_string () const -{ - return String::compose (N_("ffmpeg %1 %2 %3 %4"), _id, _sample_rate, _channel_layout, _name); -} - -void -FFmpegDecoder::film_changed (Film::Property p) -{ - switch (p) { - case Film::CROP: - case Film::FILTERS: - { - boost::mutex::scoped_lock lm (_filter_graphs_mutex); - _filter_graphs.clear (); - } - OutputChanged (); - break; - default: - break; - } + return; } - void - FFmpegDecoder::film_changed (Film::Property p) - { - switch (p) { - case Film::FILTERS: - { - boost::mutex::scoped_lock lm (_filter_graphs_mutex); - _filter_graphs.clear (); - } - break; - - default: - break; - } - } - /** @return Length (in video frames) according to our content's header */ -SourceFrame -FFmpegDecoder::length () const +ContentVideoFrame +FFmpegDecoder::video_length () const { - return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second(); + return (double(_format_context->duration) / AV_TIME_BASE) * video_frame_rate(); } void @@@ -563,66 -712,3 +547,74 @@@ FFmpegDecoder::decode_audio_packet ( } } } + +bool +FFmpegDecoder::decode_video_packet () +{ + int frame_finished; + if (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) < 0 || !frame_finished) { + return false; + } + + boost::mutex::scoped_lock lm (_filter_graphs_mutex); + + shared_ptr graph; + + list >::iterator i = _filter_graphs.begin(); + while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) { + ++i; + } + + if (i == _filter_graphs.end ()) { - graph.reset (new FilterGraph (_film, this, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)); - _filter_graphs.push_back (graph); - + shared_ptr film = _film.lock (); + assert (film); ++ ++ graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)); ++ _filter_graphs.push_back (graph); ++ + film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format)); + } else { + graph = *i; + } + - + list > images = graph->process (_frame); ++ ++ string post_process = Filter::ffmpeg_strings (_ffmpeg_content->filters()).second; + + for (list >::iterator i = images.begin(); i != images.end(); ++i) { ++ ++ shared_ptr image = *i; ++ if (!post_process.empty ()) { ++ image = image->post_process (post_process, true); ++ } ++ + 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 (*i, false, t); ++ video (image, false, t); + } else { + shared_ptr film = _film.lock (); + assert (film); + film->log()->log ("Dropping frame without PTS"); + } + } + + return true; +} + +Time +FFmpegDecoder::next () const +{ + if (_decode_video && _decode_audio) { + return min (_next_video, _next_audio); + } + + if (_decode_audio) { + return _next_audio; + } + + return _next_video; +} diff --cc src/lib/ffmpeg_decoder.h index dbcfe3be0,0c89b973d..c37479612 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@@ -112,12 -125,10 +112,10 @@@ private void maybe_add_subtitle (); boost::shared_ptr deinterleave_audio (uint8_t** data, int size); -- void film_changed (Film::Property); -- std::string stream_name (AVStream* s) const; + boost::shared_ptr _ffmpeg_content; + AVFormatContext* _format_context; int _video_stream; diff --cc src/lib/film.cc index a61a0d53d,81c7de77f..fc1d2d8a4 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@@ -140,11 -157,24 +140,10 @@@ Film::Film (Film const & o , _directory (o._directory) , _name (o._name) , _use_dci_name (o._use_dci_name) - , _content (o._content) - , _trust_content_header (o._trust_content_header) , _dcp_content_type (o._dcp_content_type) - , _format (o._format) - , _crop (o._crop) - , _filters (o._filters) + , _container (o._container) - , _filters (o._filters) , _scaler (o._scaler) - , _trim_start (o._trim_start) - , _trim_end (o._trim_end) - , _trim_type (o._trim_type) - , _dcp_ab (o._dcp_ab) - , _content_audio_stream (o._content_audio_stream) - , _external_audio (o._external_audio) - , _use_content_audio (o._use_content_audio) - , _audio_gain (o._audio_gain) - , _audio_delay (o._audio_delay) - , _still_duration (o._still_duration) - , _subtitle_stream (o._subtitle_stream) + , _ab (o._ab) , _with_subtitles (o._with_subtitles) , _subtitle_offset (o._subtitle_offset) , _subtitle_scale (o._subtitle_scale) @@@ -161,21 -203,22 +160,18 @@@ string Film::video_state_identifier () const { - assert (format ()); + assert (container ()); LocaleGuard lg; -- pair f = Filter::ffmpeg_strings (filters()); -- stringstream s; - s << format()->id() - << "_" << content_digest() - << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom - << "_" << _dcp_frame_rate - << "_" << f.first << "_" << f.second + s << container()->id() + << "_" << _playlist->video_digest() + << "_" << _dcp_video_frame_rate - << "_" << f.first << "_" << f.second << "_" << scaler()->id() << "_" << j2k_bandwidth() - << "_" << boost::lexical_cast (colour_lut()); + << "_" << lexical_cast (colour_lut()); - if (dcp_ab()) { + if (ab()) { pair fa = Filter::ffmpeg_strings (Config::instance()->reference_filters()); s << "ab_" << Config::instance()->reference_scaler()->id() << "_" << fa.first << "_" << fa.second; } @@@ -380,39 -433,81 +376,35 @@@ Film::write_metadata () cons boost::filesystem::create_directories (directory()); - string const m = file ("metadata"); - ofstream f (m.c_str ()); - if (!f.good ()) { - throw CreateFileError (m); - } + xmlpp::Document doc; + xmlpp::Element* root = doc.create_root_node ("Metadata"); - f << "version " << state_version << endl; + root->add_child("Version")->add_child_text (lexical_cast (state_version)); + root->add_child("Name")->add_child_text (_name); + root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0"); - /* User stuff */ - f << "name " << _name << endl; - f << "use_dci_name " << _use_dci_name << endl; - f << "content " << _content << endl; - f << "trust_content_header " << (_trust_content_header ? "1" : "0") << endl; if (_dcp_content_type) { - f << "dcp_content_type " << _dcp_content_type->dci_name () << endl; - } - if (_format) { - f << "format " << _format->as_metadata () << endl; - } - f << "left_crop " << _crop.left << endl; - f << "right_crop " << _crop.right << endl; - f << "top_crop " << _crop.top << endl; - f << "bottom_crop " << _crop.bottom << endl; - for (vector::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { - f << "filter " << (*i)->id () << endl; - } - f << "scaler " << _scaler->id () << endl; - f << "trim_start " << _trim_start << endl; - f << "trim_end " << _trim_end << endl; - switch (_trim_type) { - case CPL: - f << "trim_type cpl\n"; - break; - case ENCODE: - f << "trim_type encode\n"; - break; - } - f << "dcp_ab " << (_dcp_ab ? "1" : "0") << endl; - if (_content_audio_stream) { - f << "selected_content_audio_stream " << _content_audio_stream->to_string() << endl; - } - for (vector::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) { - f << "external_audio " << *i << endl; - } - f << "use_content_audio " << (_use_content_audio ? "1" : "0") << endl; - f << "audio_gain " << _audio_gain << endl; - f << "audio_delay " << _audio_delay << endl; - f << "still_duration " << _still_duration << endl; - if (_subtitle_stream) { - f << "selected_subtitle_stream " << _subtitle_stream->to_string() << endl; - } - f << "with_subtitles " << _with_subtitles << endl; - f << "subtitle_offset " << _subtitle_offset << endl; - f << "subtitle_scale " << _subtitle_scale << endl; - f << "colour_lut " << _colour_lut << endl; - f << "j2k_bandwidth " << _j2k_bandwidth << endl; - _dci_metadata.write (f); - f << "dci_date " << boost::gregorian::to_iso_string (_dci_date) << endl; - f << "dcp_frame_rate " << _dcp_frame_rate << endl; - f << "width " << _size.width << endl; - f << "height " << _size.height << endl; - f << "length " << _length.get_value_or(0) << endl; - f << "content_digest " << _content_digest << endl; - - for (vector >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) { - f << "content_audio_stream " << (*i)->to_string () << endl; + root->add_child("DCPContentType")->add_child_text (_dcp_content_type->dci_name ()); } - f << "external_audio_stream " << _sndfile_stream->to_string() << endl; - - for (vector >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) { - f << "subtitle_stream " << (*i)->to_string () << endl; + if (_container) { + root->add_child("Container")->add_child_text (_container->id ()); } - for (vector::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { - root->add_child("Filter")->add_child_text ((*i)->id ()); - } - - f << "source_frame_rate " << _source_frame_rate << endl; + root->add_child("Scaler")->add_child_text (_scaler->id ()); + root->add_child("AB")->add_child_text (_ab ? "1" : "0"); + root->add_child("WithSubtitles")->add_child_text (_with_subtitles ? "1" : "0"); + root->add_child("SubtitleOffset")->add_child_text (lexical_cast (_subtitle_offset)); + root->add_child("SubtitleScale")->add_child_text (lexical_cast (_subtitle_scale)); + root->add_child("ColourLUT")->add_child_text (lexical_cast (_colour_lut)); + root->add_child("J2KBandwidth")->add_child_text (lexical_cast (_j2k_bandwidth)); + _dci_metadata.as_xml (root->add_child ("DCIMetadata")); + root->add_child("DCPVideoFrameRate")->add_child_text (lexical_cast (_dcp_video_frame_rate)); + root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date)); + root->add_child("DCPAudioChannels")->add_child_text (lexical_cast (_dcp_audio_channels)); + _playlist->as_xml (root->add_child ("Playlist")); + + doc.write_to_file_formatted (file ("metadata.xml")); _dirty = false; } @@@ -447,27 -659,25 +439,20 @@@ Film::read_metadata ( } } - { - list > c = f.node_children ("Filter"); - for (list >::iterator i = c.begin(); i != c.end(); ++i) { - _filters.push_back (Filter::from_id ((*i)->content ())); - if (!version) { - if (audio_sample_rate) { - /* version < 1 didn't specify sample rate in the audio streams, so fill it in here */ - for (vector >::iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) { - (*i)->set_sample_rate (audio_sample_rate.get()); - } -- } - } - + _scaler = Scaler::from_id (f.string_child ("Scaler")); + _ab = f.bool_child ("AB"); + _with_subtitles = f.bool_child ("WithSubtitles"); + _subtitle_offset = f.number_child ("SubtitleOffset"); + _subtitle_scale = f.number_child ("SubtitleScale"); + _colour_lut = f.number_child ("ColourLUT"); + _j2k_bandwidth = f.number_child ("J2KBandwidth"); + _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata")); + _dcp_video_frame_rate = f.number_child ("DCPVideoFrameRate"); + _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate")); + _dcp_audio_channels = f.number_child ("DCPAudioChannels"); - /* also the selected stream was specified as an index */ - if (audio_stream_index && audio_stream_index.get() >= 0 && audio_stream_index.get() < (int) _content_audio_streams.size()) { - _content_audio_stream = _content_audio_streams[audio_stream_index.get()]; - } + _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist")); - /* similarly the subtitle */ - if (subtitle_stream_index && subtitle_stream_index.get() >= 0 && subtitle_stream_index.get() < (int) _subtitle_streams.size()) { - _subtitle_stream = _subtitle_streams[subtitle_stream_index.get()]; - } - } - _dirty = false; } @@@ -630,21 -1103,11 +615,11 @@@ Film::set_container (Container const * { { boost::mutex::scoped_lock lm (_state_mutex); - _filters = f; + _container = c; } - signal_changed (FILTERS); + signal_changed (CONTAINER); } - void - Film::set_filters (vector f) - { - { - boost::mutex::scoped_lock lm (_state_mutex); - _filters = f; - } - signal_changed (FILTERS); - } - void Film::set_scaler (Scaler const * s) { @@@ -818,114 -1471,20 +793,113 @@@ Film::have_dcp () cons return true; } +shared_ptr +Film::player () const +{ - boost::mutex::scoped_lock lm (_state_mutex); + return shared_ptr (new Player (shared_from_this (), _playlist)); +} + +shared_ptr +Film::playlist () const +{ + boost::mutex::scoped_lock lm (_state_mutex); + return _playlist; +} + +Playlist::ContentList +Film::content () const +{ + return _playlist->content (); +} + +void +Film::add_content (shared_ptr c) +{ + _playlist->add (c); + examine_content (c); +} + +void +Film::remove_content (shared_ptr c) +{ + _playlist->remove (c); +} + +Time +Film::length () const +{ + return _playlist->length (); +} + bool -Film::has_audio () const +Film::has_subtitles () const { - if (use_content_audio()) { - return audio_stream(); - } + return _playlist->has_subtitles (); +} - vector const e = external_audio (); - for (vector::const_iterator i = e.begin(); i != e.end(); ++i) { - if (!i->empty ()) { - return true; - } +OutputVideoFrame +Film::best_dcp_video_frame_rate () const +{ + return _playlist->best_dcp_frame_rate (); +} + +void +Film::playlist_content_changed (boost::weak_ptr c, int p) +{ + if (p == VideoContentProperty::VIDEO_FRAME_RATE) { + set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ()); + } + + if (ui_signaller) { + ui_signaller->emit (boost::bind (boost::ref (ContentChanged), c, p)); } +} + +void +Film::playlist_changed () +{ + signal_changed (CONTENT); +} + +int +Film::loop () const +{ + return _playlist->loop (); +} + +void +Film::set_loop (int c) +{ + _playlist->set_loop (c); +} - return false; +OutputAudioFrame +Film::time_to_audio_frames (Time t) const +{ + return t * dcp_audio_frame_rate () / TIME_HZ; +} + +OutputVideoFrame +Film::time_to_video_frames (Time t) const +{ + return t * dcp_video_frame_rate () / TIME_HZ; +} + +Time +Film::audio_frames_to_time (OutputAudioFrame f) const +{ + return f * TIME_HZ / dcp_audio_frame_rate (); +} + +Time +Film::video_frames_to_time (OutputVideoFrame f) const +{ + return f * TIME_HZ / dcp_video_frame_rate (); } +OutputAudioFrame +Film::dcp_audio_frame_rate () const +{ + /* XXX */ + return 48000; +} diff --cc src/lib/film.h index f0ccd99e7,dd0a83d94..84f0b0233 --- a/src/lib/film.h +++ b/src/lib/film.h @@@ -97,29 -106,16 +97,28 @@@ public return _dirty; } - int audio_channels () const; + bool have_dcp () const; + + boost::shared_ptr player () const; + boost::shared_ptr playlist () const; - void set_dci_date_today (); + OutputAudioFrame dcp_audio_frame_rate () const; - int dcp_audio_channels () const; - bool have_dcp () const; + OutputAudioFrame time_to_audio_frames (Time) const; + OutputVideoFrame time_to_video_frames (Time) const; + Time video_frames_to_time (OutputVideoFrame) const; + Time audio_frames_to_time (OutputAudioFrame) const; - enum TrimType { - CPL, - ENCODE - }; + /* Proxies for some Playlist methods */ + + Playlist::ContentList content () const; + + Time length () const; + bool has_subtitles () const; + OutputVideoFrame best_dcp_video_frame_rate () const; + + void set_loop (int); + int loop () const; /** Identifiers for the parts of our state; used for signalling changes. @@@ -128,14 -124,24 +127,13 @@@ NONE, NAME, USE_DCI_NAME, + /** The playlist's content list has changed (i.e. content has been added, moved around or removed) */ CONTENT, - TRUST_CONTENT_HEADER, + LOOP, DCP_CONTENT_TYPE, - FORMAT, - CROP, - FILTERS, + CONTAINER, - FILTERS, SCALER, - TRIM_START, - TRIM_END, - TRIM_TYPE, - DCP_AB, - CONTENT_AUDIO_STREAM, - EXTERNAL_AUDIO, - USE_CONTENT_AUDIO, - AUDIO_GAIN, - AUDIO_DELAY, - STILL_DURATION, - SUBTITLE_STREAM, + AB, WITH_SUBTITLES, SUBTITLE_OFFSET, SUBTITLE_SCALE, @@@ -168,16 -189,21 +166,11 @@@ return _dcp_content_type; } - Format const * format () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _format; - } - - Crop crop () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _crop; - } - - std::vector filters () const { + Container const * container () const { boost::mutex::scoped_lock lm (_state_mutex); - return _filters; + return _container; } - std::vector filters () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _filters; - } - Scaler const * scaler () const { boost::mutex::scoped_lock lm (_state_mutex); return _scaler; @@@ -234,13 -343,28 +227,12 @@@ void set_directory (std::string); void set_name (std::string); void set_use_dci_name (bool); - void set_content (std::string); - void set_trust_content_header (bool); + void add_content (boost::shared_ptr); + void remove_content (boost::shared_ptr); void set_dcp_content_type (DCPContentType const *); - void set_format (Format const *); - void set_crop (Crop); - void set_left_crop (int); - void set_right_crop (int); - void set_top_crop (int); - void set_bottom_crop (int); - void set_filters (std::vector); + void set_container (Container const *); - void set_filters (std::vector); void set_scaler (Scaler const *); - void set_trim_start (int); - void set_trim_end (int); - void set_trim_type (TrimType); - void set_dcp_ab (bool); - void set_content_audio_stream (boost::shared_ptr); - void set_external_audio (std::vector); - void set_use_content_audio (bool); - void set_audio_gain (float); - void set_audio_delay (int); - void set_still_duration (int); - void set_subtitle_stream (boost::shared_ptr); + void set_ab (bool); void set_with_subtitles (bool); void set_subtitle_offset (int); void set_subtitle_scale (float); @@@ -287,14 -415,30 +279,12 @@@ private std::string _name; /** True if a auto-generated DCI-compliant name should be used for our DCP */ bool _use_dci_name; - /** File or directory containing content; may be relative to our directory - * or an absolute path. - */ - std::string _content; - /** If this is true, we will believe the length specified by the content - * file's header; if false, we will run through the whole content file - * the first time we see it in order to obtain the length. - */ - bool _trust_content_header; /** The type of content that this Film represents (feature, trailer etc.) */ DCPContentType const * _dcp_content_type; - /** The format to present this Film in (flat, scope, etc.) */ - Format const * _format; - /** The crop to apply to the source */ - Crop _crop; - /** Video filters that should be used when generating DCPs */ - std::vector _filters; + /** The container to put this Film in (flat, scope, etc.) */ + Container const * _container; - /** Video filters that should be used when generating DCPs */ - std::vector _filters; /** Scaler algorithm to use */ Scaler const * _scaler; - /** Frames to trim off the start of the DCP */ - int _trim_start; - /** Frames to trim off the end of the DCP */ - int _trim_end; - TrimType _trim_type; /** true to create an A/B comparison DCP, where the left half of the image is the video without any filters or post-processing, and the right half has the specified filters and post-processing. diff --cc src/lib/filter_graph.cc index df8f1e9dd,b0427a23d..4564033d5 --- a/src/lib/filter_graph.cc +++ b/src/lib/filter_graph.cc @@@ -39,8 -33,7 +33,6 @@@ extern "C" #include "filter.h" #include "exceptions.h" #include "image.h" --#include "film.h" #include "ffmpeg_decoder.h" #include "i18n.h" @@@ -49,34 -42,28 +41,32 @@@ using std::stringstream using std::string; using std::list; using boost::shared_ptr; +using boost::weak_ptr; using libdcp::Size; - /** Construct a FilterGraph for the settings in a film. -/** Construct a FFmpegFilterGraph for the settings in a film. -- * @param film Film. -- * @param decoder Decoder that we are using. ++/** Construct a FilterGraph for the settings in a piece of content. ++ * @param content Content. * @param s Size of the images to process. * @param p Pixel format of the images to process. */ - FilterGraph::FilterGraph (weak_ptr weak_film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p) -FFmpegFilterGraph::FFmpegFilterGraph (shared_ptr film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p) ++FilterGraph::FilterGraph (shared_ptr content, libdcp::Size s, AVPixelFormat p) : _buffer_src_context (0) , _buffer_sink_context (0) , _size (s) , _pixel_format (p) { - shared_ptr film = weak_film.lock (); - assert (film); + _frame = av_frame_alloc (); -- string filters = Filter::ffmpeg_strings (film->filters()).first; ++ string filters = Filter::ffmpeg_strings (content->filters()).first; if (!filters.empty ()) { -- filters += N_(","); ++ filters += ","; } - Crop crop = decoder->ffmpeg_content()->crop (); - libdcp::Size cropped_size = decoder->video_size (); - filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size())); ++ Crop crop = content->crop (); ++ libdcp::Size cropped_size = _size; + cropped_size.width -= crop.left + crop.right; + cropped_size.height -= crop.top + crop.bottom; + filters += crop_string (Position (crop.left, crop.top), cropped_size); AVFilterGraph* graph = avfilter_graph_alloc(); if (graph == 0) { @@@ -88,13 -75,16 +78,16 @@@ throw DecodeError (N_("could not find buffer src filter")); } - AVFilter* buffer_sink = get_sink (); + AVFilter* buffer_sink = avfilter_get_by_name(N_("buffersink")); + if (buffer_sink == 0) { + throw DecodeError (N_("Could not create buffer sink filter")); + } stringstream a; - a << _size.width << N_(":") - << _size.height << N_(":") - << _pixel_format << N_(":") - << "0:1:0:1"; + a << "video_size=" << _size.width << "x" << _size.height << ":" + << "pix_fmt=" << _pixel_format << ":" - << "time_base=" << decoder->time_base_numerator() << "/" << decoder->time_base_denominator() << ":" - << "pixel_aspect=" << decoder->sample_aspect_ratio_numerator() << "/" << decoder->sample_aspect_ratio_denominator(); ++ << "time_base=0/1:" ++ << "pixel_aspect=0/1"; int r; @@@ -141,31 -127,18 +130,18 @@@ /* XXX: leaking `inputs' / `outputs' ? */ } -FFmpegFilterGraph::~FFmpegFilterGraph () ++FilterGraph::~FilterGraph () + { + av_frame_free (&_frame); + } + /** Take an AVFrame and process it using our configured filters, returning a - * set of Images. + * set of Images. Caller handles memory management of the input frame. */ list > - FilterGraph::process (AVFrame const * frame) -FFmpegFilterGraph::process (AVFrame* frame) ++FilterGraph::process (AVFrame* frame) { list > images; - - #if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 53 && LIBAVFILTER_VERSION_MINOR <= 61 - - if (av_vsrc_buffer_add_frame (_buffer_src_context, frame, 0) < 0) { - throw DecodeError (N_("could not push buffer into filter chain.")); - } - - #elif LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15 - - AVRational par; - par.num = sample_aspect_ratio_numerator (); - par.den = sample_aspect_ratio_denominator (); - - if (av_vsrc_buffer_add_frame (_buffer_src_context, frame, 0, par) < 0) { - throw DecodeError (N_("could not push buffer into filter chain.")); - } - - #else if (av_buffersrc_write_frame (_buffer_src_context, frame) < 0) { throw DecodeError (N_("could not push buffer into filter chain.")); diff --cc src/lib/filter_graph.h index c7a01f58e,2138943e4..e294812c2 --- a/src/lib/filter_graph.h +++ b/src/lib/filter_graph.h @@@ -21,32 -21,52 +21,32 @@@ * @brief A graph of FFmpeg filters. */ -#ifndef DVDOMATIC_FILTER_GRAPH_H -#define DVDOMATIC_FILTER_GRAPH_H +#ifndef DCPOMATIC_FILTER_GRAPH_H +#define DCPOMATIC_FILTER_GRAPH_H #include "util.h" - #include "ffmpeg_compatibility.h" class Image; class VideoFilter; --class FFmpegDecoder; -class FilterGraph -{ -public: - virtual bool can_process (libdcp::Size, AVPixelFormat) const = 0; - virtual std::list > process (AVFrame *) = 0; -}; - -class EmptyFilterGraph : public FilterGraph -{ -public: - bool can_process (libdcp::Size, AVPixelFormat) const { - return true; - } - - std::list > process (AVFrame *); -}; - -/** @class FFmpegFilterGraph +/** @class FilterGraph * @brief A graph of FFmpeg filters. */ -class FFmpegFilterGraph : public FilterGraph +class FilterGraph { public: - FilterGraph (boost::weak_ptr, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p); - FFmpegFilterGraph (boost::shared_ptr film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p); - ~FFmpegFilterGraph (); ++ FilterGraph (boost::shared_ptr content, libdcp::Size s, AVPixelFormat p); ++ ~FilterGraph (); bool can_process (libdcp::Size s, AVPixelFormat p) const; - std::list > process (AVFrame const * frame); + std::list > process (AVFrame * frame); private: AVFilterContext* _buffer_src_context; AVFilterContext* _buffer_sink_context; libdcp::Size _size; ///< size of the images that this chain can process AVPixelFormat _pixel_format; ///< pixel format of the images that this chain can process + AVFrame* _frame; }; -boost::shared_ptr filter_graph_factory (boost::shared_ptr, FFmpegDecoder *, libdcp::Size, AVPixelFormat); - #endif diff --cc src/lib/player.cc index 032b3d49b,000000000..ff13f95db mode 100644,000000..100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@@ -1,351 -1,0 +1,355 @@@ +/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */ + +/* + Copyright (C) 2013 Carl Hetherington + + 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 "player.h" +#include "film.h" +#include "ffmpeg_decoder.h" +#include "ffmpeg_content.h" +#include "imagemagick_decoder.h" +#include "imagemagick_content.h" +#include "sndfile_decoder.h" +#include "sndfile_content.h" +#include "playlist.h" +#include "job.h" +#include "image.h" +#include "null_content.h" +#include "black_decoder.h" +#include "silence_decoder.h" + +using std::list; +using std::cout; +using std::min; +using std::max; +using std::vector; +using boost::shared_ptr; +using boost::weak_ptr; +using boost::dynamic_pointer_cast; + +struct Piece +{ + Piece (shared_ptr c, shared_ptr d) + : content (c) + , decoder (d) + {} + + shared_ptr content; + shared_ptr decoder; +}; + +Player::Player (shared_ptr f, shared_ptr p) + : _film (f) + , _playlist (p) + , _video (true) + , _audio (true) + , _subtitles (true) + , _have_valid_pieces (false) + , _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)); +} + +void +Player::disable_video () +{ + _video = false; +} + +void +Player::disable_audio () +{ + _audio = false; +} + +void +Player::disable_subtitles () +{ + _subtitles = false; +} + +bool +Player::pass () +{ + if (!_have_valid_pieces) { + setup_pieces (); + _have_valid_pieces = true; + } + + /* Here we are just finding the active decoder with the earliest last emission time, then + calling pass on it. + */ + + Time earliest_t = TIME_MAX; + shared_ptr earliest; + + for (list >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { + cout << "check " << (*i)->content->file() << " start=" << (*i)->content->start() << ", next=" << (*i)->decoder->next() << ", end=" << (*i)->content->end() << "\n"; + if (((*i)->decoder->next() + (*i)->content->start()) >= (*i)->content->end()) { + continue; + } ++ ++ if (!_audio && dynamic_pointer_cast ((*i)->content)) { ++ continue; ++ } + + Time const t = (*i)->content->start() + (*i)->decoder->next(); + if (t < earliest_t) { + cout << "\t candidate; " << t << " " << (t / TIME_HZ) << ".\n"; + earliest_t = t; + earliest = *i; + } + } + + if (!earliest) { + return true; + } + + cout << "PASS:\n"; + cout << "\tpass " << earliest->content->file() << " "; + if (dynamic_pointer_cast (earliest->content)) { + cout << " FFmpeg.\n"; + } else if (dynamic_pointer_cast (earliest->content)) { + cout << " ImageMagickContent.\n"; + } else if (dynamic_pointer_cast (earliest->content)) { + cout << " SndfileContent.\n"; + } else if (dynamic_pointer_cast (earliest->decoder)) { + cout << " Black.\n"; + } else if (dynamic_pointer_cast (earliest->decoder)) { + cout << " Silence.\n"; + } + + earliest->decoder->pass (); + _position = earliest->content->start() + earliest->decoder->next (); + cout << "\tpassed to " << _position << " " << (_position / TIME_HZ) << "\n"; + + return false; +} + +void +Player::process_video (weak_ptr weak_content, shared_ptr image, bool same, shared_ptr sub, Time time) +{ + cout << "[V]\n"; + + shared_ptr content = weak_content.lock (); + if (!content) { + return; + } + + time += content->start (); + + Video (image, same, sub, time); +} + +void +Player::process_audio (weak_ptr weak_content, shared_ptr audio, Time time) +{ + shared_ptr content = weak_content.lock (); + if (!content) { + return; + } + + /* 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 (); + + if (time > _next_audio) { + /* We can emit some audio from our buffers */ + assert (_film->time_to_audio_frames (time - _next_audio) <= _audio_buffers.frames()); + OutputAudioFrame const N = _film->time_to_audio_frames (time - _next_audio); + shared_ptr 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); + + /* And remove it from our buffers */ + if (_audio_buffers.frames() > N) { + _audio_buffers.move (N, 0, _audio_buffers.frames() - N); + } + _audio_buffers.set_frames (_audio_buffers.frames() - N); + } + + /* Now accumulate the new audio into our buffers */ + _audio_buffers.ensure_size (_audio_buffers.frames() + audio->frames()); - _audio_buffers.accumulate_frames (audio, 0, 0, audio->frames ()); ++ _audio_buffers.accumulate_frames (audio.get(), 0, 0, audio->frames ()); +} + +/** @return true on error */ +void +Player::seek (Time t) +{ + if (!_have_valid_pieces) { + setup_pieces (); + _have_valid_pieces = true; + } + + if (_pieces.empty ()) { + return; + } + + cout << "seek to " << t << " " << (t / TIME_HZ) << "\n"; + + for (list >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { + Time s = t - (*i)->content->start (); + s = max (static_cast