diff options
| author | Carl Hetherington <cth@carlh.net> | 2013-05-25 01:07:35 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2013-05-25 01:07:35 +0100 |
| commit | 996b0c06e23bcb6b300d7b8799df94993692e07d (patch) | |
| tree | 615ff0c372dac97321489e3cb7f316cb4cb9eeec | |
| parent | 4f03da3aa12525cb8389ddefee629f5d0b2ac0aa (diff) | |
| parent | 907735ee6ca162583c7c9d20f5603a6db83a149f (diff) | |
Merge master and multifarious hackery.
41 files changed, 1379 insertions, 1135 deletions
@@ -1,9 +1,18 @@ +2013-05-20 Carl Hetherington <cth@carlh.net> + + * Version 0.93 released. + +2013-05-20 Carl Hetherington <cth@carlh.net> + + * Fix another crash on still images with + no audio. + 2013-05-19 Carl Hetherington <cth@carlh.net> * Version 0.92 released. 2013-05-19 Carl Hetherington <cth@carlh.net> - + * Version 0.91 released. 2013-05-19 Carl Hetherington <cth@carlh.net> @@ -8,7 +8,7 @@ def dependencies(target): else: return (('openjpeg-cdist', None), ('libcxml', None), - ('ffmpeg-cdist', '488d5d4496af5e3a3b9d31d6b221e8eeada6b77e'), + ('ffmpeg-cdist', '7a23ec9c771184ab563cfe24ad9b427f38368961'), ('libdcp', None)) def build(env, target): diff --git a/src/lib/ab_transcode_job.cc b/src/lib/ab_transcode_job.cc index 2bdff47de..9a883fdd9 100644 --- a/src/lib/ab_transcode_job.cc +++ b/src/lib/ab_transcode_job.cc @@ -38,7 +38,8 @@ ABTranscodeJob::ABTranscodeJob (shared_ptr<Film> f) { _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 --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index 8950e1546..9b8d15bf1 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -25,6 +25,8 @@ #include "i18n.h" using std::stringstream; +using std::list; +using std::pair; using boost::optional; using boost::shared_ptr; @@ -141,11 +143,11 @@ AudioDecoder::audio (shared_ptr<const AudioBuffers> data, Time time) assert (film); /* Remap channels */ - shared_ptr<AudioBuffers> dcp_mapped (film->dcp_audio_channels(), data->frames()); + 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 (data, i->first, i->second); + dcp_mapped->accumulate_channel (data.get(), i->first, i->second); } Audio (dcp_mapped, time); diff --git a/src/lib/dcp_video_frame.cc b/src/lib/dcp_video_frame.cc index da51665d1..1c1838df7 100644 --- a/src/lib/dcp_video_frame.cc +++ b/src/lib/dcp_video_frame.cc @@ -79,7 +79,7 @@ using libdcp::Size; DCPVideoFrame::DCPVideoFrame ( shared_ptr<const Image> yuv, shared_ptr<Subtitle> 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<Log> l + Scaler const * s, int f, int dcp_fps, int clut, int bw, shared_ptr<Log> l ) : _input (yuv) , _subtitle (sub) @@ -90,7 +90,6 @@ DCPVideoFrame::DCPVideoFrame ( , _scaler (s) , _frame (f) , _frames_per_second (dcp_fps) - , _post_process (pp) , _colour_lut (clut) , _j2k_bandwidth (bw) , _log (l) @@ -156,10 +155,6 @@ DCPVideoFrame::~DCPVideoFrame () shared_ptr<EncodedData> DCPVideoFrame::encode_locally () { - if (!_post_process.empty ()) { - _input = _input->post_process (_post_process, true); - } - shared_ptr<Image> prepared = _input->scale_and_convert_to_rgb (_out_size, _padding, _scaler, true); if (_subtitle) { @@ -333,10 +328,6 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv) << 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 --git a/src/lib/dcp_video_frame.h b/src/lib/dcp_video_frame.h index 4ceb07d26..ba49c95a4 100644 --- a/src/lib/dcp_video_frame.h +++ b/src/lib/dcp_video_frame.h @@ -107,7 +107,7 @@ class DCPVideoFrame public: DCPVideoFrame ( boost::shared_ptr<const Image>, boost::shared_ptr<Subtitle>, libdcp::Size, - int, int, float, Scaler const *, int, int, std::string, int, int, boost::shared_ptr<Log> + int, int, float, Scaler const *, int, int, int, int, boost::shared_ptr<Log> ); virtual ~DCPVideoFrame (); @@ -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 --git a/src/lib/encoder.cc b/src/lib/encoder.cc index 52927c5d3..270bf3d43 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -207,14 +207,13 @@ Encoder::process_video (shared_ptr<const Image> image, bool same, shared_ptr<Sub frame_done (); } else { /* Queue this new frame for encoding */ - pair<string, string> const s = Filter::ffmpeg_strings (_film->filters()); TIMING ("adding to queue of %1", _queue.size ()); /* XXX: padding */ _queue.push_back (shared_ptr<DCPVideoFrame> ( new DCPVideoFrame ( 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_video_frame_rate(), _film->colour_lut(), _film->j2k_bandwidth(), _film->log() ) diff --git a/src/lib/ffmpeg_compatibility.cc b/src/lib/ffmpeg_compatibility.cc deleted file mode 100644 index 361fa7423..000000000 --- a/src/lib/ffmpeg_compatibility.cc +++ /dev/null @@ -1,119 +0,0 @@ -/* - 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. - -*/ - -extern "C" { -#include <libavfilter/avfiltergraph.h> -} -#include "exceptions.h" - -#include "i18n.h" - -#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15 - -typedef struct { - enum PixelFormat pix_fmt; -} AVSinkContext; - -static int -avsink_init (AVFilterContext* ctx, const char* args, void* opaque) -{ - AVSinkContext* priv = (AVSinkContext *) ctx->priv; - if (!opaque) { - return AVERROR (EINVAL); - } - - *priv = *(AVSinkContext *) opaque; - return 0; -} - -static void -null_end_frame (AVFilterLink *) -{ - -} - -static int -avsink_query_formats (AVFilterContext* ctx) -{ - AVSinkContext* priv = (AVSinkContext *) ctx->priv; - enum PixelFormat pix_fmts[] = { - priv->pix_fmt, - PIX_FMT_NONE - }; - - avfilter_set_common_formats (ctx, avfilter_make_format_list ((int *) pix_fmts)); - return 0; -} - -#endif - -AVFilter* -get_sink () -{ -#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15 - /* XXX does this leak stuff? */ - AVFilter* buffer_sink = new AVFilter; - buffer_sink->name = av_strdup (N_("avsink")); - buffer_sink->priv_size = sizeof (AVSinkContext); - buffer_sink->init = avsink_init; - buffer_sink->query_formats = avsink_query_formats; - buffer_sink->inputs = new AVFilterPad[2]; - AVFilterPad* i0 = const_cast<AVFilterPad*> (&buffer_sink->inputs[0]); - i0->name = N_("default"); - i0->type = AVMEDIA_TYPE_VIDEO; - i0->min_perms = AV_PERM_READ; - i0->rej_perms = 0; - i0->start_frame = 0; - i0->get_video_buffer = 0; - i0->get_audio_buffer = 0; - i0->end_frame = null_end_frame; - i0->draw_slice = 0; - i0->filter_samples = 0; - i0->poll_frame = 0; - i0->request_frame = 0; - i0->config_props = 0; - const_cast<AVFilterPad*> (&buffer_sink->inputs[1])->name = 0; - buffer_sink->outputs = new AVFilterPad[1]; - const_cast<AVFilterPad*> (&buffer_sink->outputs[0])->name = 0; - return buffer_sink; -#else - AVFilter* buffer_sink = avfilter_get_by_name(N_("buffersink")); - if (buffer_sink == 0) { - throw DecodeError (N_("Could not create buffer sink filter")); - } - - return buffer_sink; -#endif -} - -#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15 -AVFilterInOut * -avfilter_inout_alloc () -{ - return (AVFilterInOut *) av_malloc (sizeof (AVFilterInOut)); -} -#endif - -#ifndef HAVE_AV_FRAME_GET_BEST_EFFORT_TIMESTAMP -int64_t av_frame_get_best_effort_timestamp (AVFrame const * f) -{ - return f->best_effort_timestamp; -} - -#endif diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index ad7af07d8..55139ca56 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -25,6 +25,7 @@ #include "compose.hpp" #include "job.h" #include "util.h" +#include "filter.h" #include "log.h" #include "i18n.h" @@ -41,6 +42,7 @@ 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<const Film> f, boost::filesystem::path p) : Content (f, p) @@ -70,6 +72,11 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::N _audio_stream = _audio_streams.back (); } } + + c = node->node_children ("Filter"); + for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) { + _filters.push_back (Filter::from_id ((*i)->content ())); + } } FFmpegContent::FFmpegContent (FFmpegContent const & o) @@ -109,6 +116,10 @@ FFmpegContent::as_xml (xmlpp::Node* node) const } (*i)->as_xml (t); } + + for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { + node->add_child("Filter")->add_child_text ((*i)->id ()); + } } void @@ -335,3 +346,14 @@ FFmpegContent::audio_mapping () const return _audio_stream->mapping; } +void +FFmpegContent::set_filters (vector<Filter const *> const & filters) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _filters = filters; + } + + signal_changed (FFmpegContentProperty::FILTERS); +} + diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index d5b986996..8f5c773ee 100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.h @@ -26,6 +26,8 @@ #include "video_content.h" #include "audio_content.h" +class Filter; + class FFmpegAudioStream { public: @@ -75,6 +77,7 @@ public: 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 @@ -101,6 +104,8 @@ public: int content_audio_frame_rate () const; int output_audio_frame_rate () const; AudioMapping audio_mapping () const; + + void set_filters (std::vector<Filter const *> const &); std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const { boost::mutex::scoped_lock lm (_mutex); @@ -122,6 +127,11 @@ public: return _audio_stream; } + std::vector<Filter const *> filters () const { + boost::mutex::scoped_lock lm (_mutex); + return _filters; + } + void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>); void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>); @@ -130,6 +140,8 @@ private: boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream; std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams; boost::shared_ptr<FFmpegAudioStream> _audio_stream; + /** Video filters that should be used when generating DCPs */ + std::vector<Filter const *> _filters; }; #endif diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index 047829d45..119e82851 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -507,22 +507,6 @@ FFmpegDecoder::do_seek (Time t, bool backwards, bool accurate) 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 */ ContentVideoFrame FFmpegDecoder::video_length () const @@ -582,27 +566,35 @@ FFmpegDecoder::decode_video_packet () } 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<const Film> 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<shared_ptr<Image> > images = graph->process (_frame); + + string post_process = Filter::ffmpeg_strings (_ffmpeg_content->filters()).second; for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) { + + shared_ptr<Image> 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<const Film> film = _film.lock (); assert (film); diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index dbcfe3be0..c37479612 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -112,8 +112,6 @@ private: void maybe_add_subtitle (); boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size); - void film_changed (Film::Property); - std::string stream_name (AVStream* s) const; boost::shared_ptr<const FFmpegContent> _ffmpeg_content; diff --git a/src/lib/film.cc b/src/lib/film.cc index a61a0d53d..fc1d2d8a4 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -142,7 +142,6 @@ Film::Film (Film const & o) , _use_dci_name (o._use_dci_name) , _dcp_content_type (o._dcp_content_type) , _container (o._container) - , _filters (o._filters) , _scaler (o._scaler) , _ab (o._ab) , _with_subtitles (o._with_subtitles) @@ -164,13 +163,10 @@ Film::video_state_identifier () const assert (container ()); LocaleGuard lg; - pair<string, string> f = Filter::ffmpeg_strings (filters()); - stringstream s; s << container()->id() << "_" << _playlist->video_digest() << "_" << _dcp_video_frame_rate - << "_" << f.first << "_" << f.second << "_" << scaler()->id() << "_" << j2k_bandwidth() << "_" << lexical_cast<int> (colour_lut()); @@ -395,10 +391,6 @@ Film::write_metadata () const root->add_child("Container")->add_child_text (_container->id ()); } - for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) { - root->add_child("Filter")->add_child_text ((*i)->id ()); - } - 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"); @@ -447,13 +439,6 @@ Film::read_metadata () } } - { - list<shared_ptr<cxml::Node> > c = f.node_children ("Filter"); - for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) { - _filters.push_back (Filter::from_id ((*i)->content ())); - } - } - _scaler = Scaler::from_id (f.string_child ("Scaler")); _ab = f.bool_child ("AB"); _with_subtitles = f.bool_child ("WithSubtitles"); @@ -636,16 +621,6 @@ Film::set_container (Container const * c) } void -Film::set_filters (vector<Filter const *> f) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _filters = f; - } - signal_changed (FILTERS); -} - -void Film::set_scaler (Scaler const * s) { { @@ -821,7 +796,6 @@ Film::have_dcp () const shared_ptr<Player> Film::player () const { - boost::mutex::scoped_lock lm (_state_mutex); return shared_ptr<Player> (new Player (shared_from_this (), _playlist)); } diff --git a/src/lib/film.h b/src/lib/film.h index f0ccd99e7..84f0b0233 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -103,7 +103,6 @@ public: boost::shared_ptr<Playlist> playlist () const; OutputAudioFrame dcp_audio_frame_rate () const; - int dcp_audio_channels () const; OutputAudioFrame time_to_audio_frames (Time) const; OutputVideoFrame time_to_video_frames (Time) const; @@ -133,7 +132,6 @@ public: LOOP, DCP_CONTENT_TYPE, CONTAINER, - FILTERS, SCALER, AB, WITH_SUBTITLES, @@ -173,11 +171,6 @@ public: return _container; } - std::vector<Filter const *> filters () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _filters; - } - Scaler const * scaler () const { boost::mutex::scoped_lock lm (_state_mutex); return _scaler; @@ -238,7 +231,6 @@ public: void remove_content (boost::shared_ptr<Content>); void set_dcp_content_type (DCPContentType const *); void set_container (Container const *); - void set_filters (std::vector<Filter const *>); void set_scaler (Scaler const *); void set_ab (bool); void set_with_subtitles (bool); @@ -291,8 +283,6 @@ private: DCPContentType const * _dcp_content_type; /** The container to put this Film in (flat, scope, etc.) */ Container const * _container; - /** Video filters that should be used when generating DCPs */ - std::vector<Filter const *> _filters; /** Scaler algorithm to use */ Scaler const * _scaler; /** true to create an A/B comparison DCP, where the left half of the image diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc index df8f1e9dd..4564033d5 100644 --- a/src/lib/filter_graph.cc +++ b/src/lib/filter_graph.cc @@ -23,24 +23,16 @@ extern "C" { #include <libavfilter/avfiltergraph.h> -#ifdef HAVE_BUFFERSRC_H #include <libavfilter/buffersrc.h> -#endif -#if (LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 53 && LIBAVFILTER_VERSION_MINOR <= 77) || LIBAVFILTER_VERSION_MAJOR == 3 #include <libavfilter/avcodec.h> #include <libavfilter/buffersink.h> -#elif LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15 -#include <libavfilter/vsrc_buffer.h> -#endif #include <libavformat/avio.h> } #include "decoder.h" #include "filter_graph.h" -#include "ffmpeg_compatibility.h" #include "filter.h" #include "exceptions.h" #include "image.h" -#include "film.h" #include "ffmpeg_decoder.h" #include "i18n.h" @@ -52,28 +44,26 @@ using boost::shared_ptr; using boost::weak_ptr; using libdcp::Size; -/** Construct a FilterGraph 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<const Film> weak_film, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p) +FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p) : _buffer_src_context (0) , _buffer_sink_context (0) , _size (s) , _pixel_format (p) { - shared_ptr<const Film> 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 (); + 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); @@ -88,17 +78,20 @@ FilterGraph::FilterGraph (weak_ptr<const Film> weak_film, FFmpegDecoder* decoder 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=0/1:" + << "pixel_aspect=0/1"; int r; - if ((r = avfilter_graph_create_filter (&_buffer_src_context, buffer_src, N_("in"), a.str().c_str(), 0, graph)) < 0) { + if ((r = avfilter_graph_create_filter (&_buffer_src_context, buffer_src, "in", a.str().c_str(), 0, graph)) < 0) { throw DecodeError (N_("could not create buffer source")); } @@ -112,6 +105,8 @@ FilterGraph::FilterGraph (weak_ptr<const Film> weak_film, FFmpegDecoder* decoder throw DecodeError (N_("could not create buffer sink.")); } + av_free (sink_params); + AVFilterInOut* outputs = avfilter_inout_alloc (); outputs->name = av_strdup(N_("in")); outputs->filter_ctx = _buffer_src_context; @@ -124,15 +119,9 @@ FilterGraph::FilterGraph (weak_ptr<const Film> weak_film, FFmpegDecoder* decoder inputs->pad_idx = 0; inputs->next = 0; -#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR == 15 - if (avfilter_graph_parse (graph, filters.c_str(), inputs, outputs, 0) < 0) { - throw DecodeError (N_("could not set up filter graph.")); - } -#else if (avfilter_graph_parse (graph, filters.c_str(), &inputs, &outputs, 0) < 0) { throw DecodeError (N_("could not set up filter graph.")); } -#endif if (avfilter_graph_config (graph, 0) < 0) { throw DecodeError (N_("could not configure filter graph.")); @@ -141,66 +130,29 @@ FilterGraph::FilterGraph (weak_ptr<const Film> weak_film, FFmpegDecoder* decoder /* XXX: leaking `inputs' / `outputs' ? */ } +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<shared_ptr<Image> > -FilterGraph::process (AVFrame const * frame) +FilterGraph::process (AVFrame* frame) { list<shared_ptr<Image> > 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.")); } -#endif - -#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 15 && LIBAVFILTER_VERSION_MINOR <= 61 - while (avfilter_poll_frame (_buffer_sink_context->inputs[0])) { -#else - while (av_buffersink_read (_buffer_sink_context, 0)) { -#endif - -#if LIBAVFILTER_VERSION_MAJOR == 2 && LIBAVFILTER_VERSION_MINOR >= 15 - - int r = avfilter_request_frame (_buffer_sink_context->inputs[0]); - if (r < 0) { - throw DecodeError (N_("could not request filtered frame")); - } - - AVFilterBufferRef* filter_buffer = _buffer_sink_context->inputs[0]->cur_buf; - -#else - - AVFilterBufferRef* filter_buffer; - if (av_buffersink_get_buffer_ref (_buffer_sink_context, &filter_buffer, 0) < 0) { - filter_buffer = 0; + while (1) { + if (av_buffersink_get_frame (_buffer_sink_context, _frame) < 0) { + break; } -#endif - - if (filter_buffer) { - /* This takes ownership of filter_buffer */ - images.push_back (shared_ptr<Image> (new FilterBufferImage ((PixelFormat) frame->format, filter_buffer))); - } + images.push_back (shared_ptr<Image> (new SimpleImage (_frame))); } return images; diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h index c7a01f58e..e294812c2 100644 --- a/src/lib/filter_graph.h +++ b/src/lib/filter_graph.h @@ -25,11 +25,9 @@ #define DCPOMATIC_FILTER_GRAPH_H #include "util.h" -#include "ffmpeg_compatibility.h" class Image; class VideoFilter; -class FFmpegDecoder; /** @class FilterGraph * @brief A graph of FFmpeg filters. @@ -37,16 +35,18 @@ class FFmpegDecoder; class FilterGraph { public: - FilterGraph (boost::weak_ptr<const Film>, FFmpegDecoder* decoder, libdcp::Size s, AVPixelFormat p); + FilterGraph (boost::shared_ptr<const FFmpegContent> content, libdcp::Size s, AVPixelFormat p); + ~FilterGraph (); bool can_process (libdcp::Size s, AVPixelFormat p) const; - std::list<boost::shared_ptr<Image> > process (AVFrame const * frame); + std::list<boost::shared_ptr<Image> > 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; }; #endif diff --git a/src/lib/image.cc b/src/lib/image.cc index 1be41fecf..b166dfac6 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -35,6 +35,7 @@ extern "C" { #include <libavfilter/avfiltergraph.h> #include <libpostproc/postprocess.h> #include <libavutil/pixfmt.h> +#include <libavutil/pixdesc.h> } #include "image.h" #include "exceptions.h" @@ -58,55 +59,32 @@ Image::swap (Image& other) int Image::lines (int n) const { - switch (_pixel_format) { - case PIX_FMT_YUV420P: - if (n == 0) { - return size().height; - } else { - return size().height / 2; - } - break; - case PIX_FMT_RGB24: - case PIX_FMT_RGBA: - case PIX_FMT_YUV422P10LE: - case PIX_FMT_YUV422P: - case PIX_FMT_YUV444P: - case PIX_FMT_YUV444P9BE: - case PIX_FMT_YUV444P9LE: - case PIX_FMT_YUV444P10BE: - case PIX_FMT_YUV444P10LE: - case PIX_FMT_UYVY422: + if (n == 0) { return size().height; - default: + } + + AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format); + if (!d) { throw PixelFormatError (N_("lines()"), _pixel_format); } - - return 0; + + return size().height / pow(2, d->log2_chroma_h); } /** @return Number of components */ int Image::components () const { - switch (_pixel_format) { - case PIX_FMT_YUV420P: - case PIX_FMT_YUV422P10LE: - case PIX_FMT_YUV422P: - case PIX_FMT_YUV444P: - case PIX_FMT_YUV444P9BE: - case PIX_FMT_YUV444P9LE: - case PIX_FMT_YUV444P10BE: - case PIX_FMT_YUV444P10LE: - return 3; - case PIX_FMT_RGB24: - case PIX_FMT_RGBA: - case PIX_FMT_UYVY422: - return 1; - default: + AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format); + if (!d) { throw PixelFormatError (N_("components()"), _pixel_format); } - return 0; + if ((d->flags & PIX_FMT_PLANAR) == 0) { + return 1; + } + + return d->nb_components; } shared_ptr<Image> @@ -296,12 +274,12 @@ Image::make_black () { /* U/V black value for 8-bit colour */ static uint8_t const eight_bit_uv = (1 << 7) - 1; - /* U/V black value for 9-bit colour */ static uint16_t const nine_bit_uv = (1 << 8) - 1; - /* U/V black value for 10-bit colour */ static uint16_t const ten_bit_uv = (1 << 9) - 1; + /* U/V black value for 16-bit colour */ + static uint16_t const sixteen_bit_uv = (1 << 15) - 1; switch (_pixel_format) { case PIX_FMT_YUV420P: @@ -326,11 +304,17 @@ Image::make_black () case PIX_FMT_YUV444P10LE: yuv_16_black (ten_bit_uv); break; + + case PIX_FMT_YUV422P16LE: + case PIX_FMT_YUV444P16LE: + yuv_16_black (sixteen_bit_uv); + break; case PIX_FMT_YUV444P10BE: case PIX_FMT_YUV422P10BE: yuv_16_black (swap_16 (ten_bit_uv)); - + break; + case PIX_FMT_RGB24: memset (data()[0], 0, lines(0) * stride()[0]); break; @@ -420,53 +404,36 @@ Image::write_to_socket (shared_ptr<Socket> socket) const float Image::bytes_per_pixel (int c) const { - if (c == 3) { + AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format); + if (!d) { + throw PixelFormatError (N_("lines()"), _pixel_format); + } + + if (c >= components()) { return 0; } + + float bpp[4] = { 0, 0, 0, 0 }; + + bpp[0] = floor ((d->comp[0].depth_minus1 + 1 + 7) / 8); + if (d->nb_components > 1) { + bpp[1] = floor ((d->comp[1].depth_minus1 + 1 + 7) / 8) / pow (2, d->log2_chroma_w); + } + if (d->nb_components > 2) { + bpp[2] = floor ((d->comp[2].depth_minus1 + 1 + 7) / 8) / pow (2, d->log2_chroma_w); + } + if (d->nb_components > 3) { + bpp[3] = floor ((d->comp[3].depth_minus1 + 1 + 7) / 8) / pow (2, d->log2_chroma_w); + } - switch (_pixel_format) { - case PIX_FMT_RGB24: - if (c == 0) { - return 3; - } else { - return 0; - } - case PIX_FMT_RGBA: - if (c == 0) { - return 4; - } else { - return 0; - } - case PIX_FMT_YUV420P: - case PIX_FMT_YUV422P: - if (c == 0) { - return 1; - } else { - return 0.5; - } - case PIX_FMT_YUV422P10LE: - if (c == 0) { - return 2; - } else { - return 1; - } - case PIX_FMT_UYVY422: - return 2; - case PIX_FMT_YUV444P: - return 3; - case PIX_FMT_YUV444P9BE: - case PIX_FMT_YUV444P9LE: - case PIX_FMT_YUV444P10LE: - case PIX_FMT_YUV444P10BE: - return 6; - default: - throw PixelFormatError (N_("bytes_per_pixel()"), _pixel_format); + if ((d->flags & PIX_FMT_PLANAR) == 0) { + /* Not planar; sum them up */ + return bpp[0] + bpp[1] + bpp[2] + bpp[3]; } - return 0; + return bpp[c]; } - /** Construct a SimpleImage of a given size and format, allocating memory * as required. * @@ -502,10 +469,9 @@ SimpleImage::allocate () SimpleImage::SimpleImage (SimpleImage const & other) : Image (other) + , _size (other._size) + , _aligned (other._aligned) { - _size = other._size; - _aligned = other._aligned; - allocate (); for (int i = 0; i < components(); ++i) { @@ -519,6 +485,25 @@ SimpleImage::SimpleImage (SimpleImage const & other) } } +SimpleImage::SimpleImage (AVFrame* frame) + : Image (static_cast<AVPixelFormat> (frame->format)) + , _size (frame->width, frame->height) + , _aligned (true) +{ + allocate (); + + for (int i = 0; i < components(); ++i) { + uint8_t* p = _data[i]; + uint8_t* q = frame->data[i]; + for (int j = 0; j < lines(i); ++j) { + memcpy (p, q, _line_size[i]); + p += stride()[i]; + /* AVFrame's linesize is what we call `stride' */ + q += frame->linesize[i]; + } + } +} + SimpleImage::SimpleImage (shared_ptr<const Image> other) : Image (*other.get()) { @@ -609,59 +594,6 @@ SimpleImage::aligned () const return _aligned; } -FilterBufferImage::FilterBufferImage (AVPixelFormat p, AVFilterBufferRef* b) - : Image (p) - , _buffer (b) -{ - _line_size = (int *) av_malloc (4 * sizeof (int)); - _line_size[0] = _line_size[1] = _line_size[2] = _line_size[3] = 0; - - for (int i = 0; i < components(); ++i) { - _line_size[i] = size().width * bytes_per_pixel(i); - } -} - -FilterBufferImage::~FilterBufferImage () -{ - avfilter_unref_buffer (_buffer); - av_free (_line_size); -} - -uint8_t ** -FilterBufferImage::data () const -{ - return _buffer->data; -} - -int * -FilterBufferImage::line_size () const -{ - return _line_size; -} - -int * -FilterBufferImage::stride () const -{ - /* I've seen images where the _buffer->linesize is larger than the width - (by a small amount), suggesting that _buffer->linesize is what we call - stride. But I'm not sure. - */ - return _buffer->linesize; -} - -libdcp::Size -FilterBufferImage::size () const -{ - return libdcp::Size (_buffer->video->w, _buffer->video->h); -} - -bool -FilterBufferImage::aligned () const -{ - /* XXX? */ - return true; -} - RGBPlusAlphaImage::RGBPlusAlphaImage (shared_ptr<const Image> im) : SimpleImage (im->pixel_format(), im->size(), false) { diff --git a/src/lib/image.h b/src/lib/image.h index de03d0e3f..34f87b188 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -32,10 +32,8 @@ extern "C" { #include <libavfilter/avfilter.h> } #include "util.h" -#include "ffmpeg_compatibility.h" class Scaler; -class RGBFrameImage; class SimpleImage; /** @class Image @@ -92,6 +90,8 @@ protected: virtual void swap (Image &); float bytes_per_pixel (int) const; + friend class pixel_formats_test; + private: void yuv_16_black (uint16_t); static uint16_t swap_16 (uint16_t); @@ -99,30 +99,6 @@ private: AVPixelFormat _pixel_format; ///< FFmpeg's way of describing the pixel format of this Image }; -/** @class FilterBufferImage - * @brief An Image that is held in an AVFilterBufferRef. - */ -class FilterBufferImage : public Image -{ -public: - FilterBufferImage (AVPixelFormat, AVFilterBufferRef *); - ~FilterBufferImage (); - - uint8_t ** data () const; - int * line_size () const; - int * stride () const; - libdcp::Size size () const; - bool aligned () const; - -private: - /* Not allowed */ - FilterBufferImage (FilterBufferImage const &); - FilterBufferImage& operator= (FilterBufferImage const &); - - AVFilterBufferRef* _buffer; - int* _line_size; -}; - /** @class SimpleImage * @brief An Image for which memory is allocated using a `simple' av_malloc(). */ @@ -130,6 +106,7 @@ class SimpleImage : public Image { public: SimpleImage (AVPixelFormat, libdcp::Size, bool); + SimpleImage (AVFrame *); SimpleImage (SimpleImage const &); SimpleImage (boost::shared_ptr<const Image>); SimpleImage& operator= (SimpleImage const &); diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc new file mode 100644 index 000000000..4acb82afa --- /dev/null +++ b/src/lib/matcher.cc @@ -0,0 +1,224 @@ +/* + 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; +} + diff --git a/src/lib/matcher.h b/src/lib/matcher.h new file mode 100644 index 000000000..61fd81436 --- /dev/null +++ b/src/lib/matcher.h @@ -0,0 +1,77 @@ +/* + 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; +}; diff --git a/src/lib/player.cc b/src/lib/player.cc index 032b3d49b..ff13f95db 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -107,6 +107,10 @@ Player::pass () if (((*i)->decoder->next() + (*i)->content->start()) >= (*i)->content->end()) { continue; } + + if (!_audio && dynamic_pointer_cast<SndfileContent> ((*i)->content)) { + continue; + } Time const t = (*i)->content->start() + (*i)->decoder->next(); if (t < earliest_t) { @@ -188,7 +192,7 @@ Player::process_audio (weak_ptr<Content> weak_content, shared_ptr<const AudioBuf /* 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 */ diff --git a/src/lib/po/fr_FR.po b/src/lib/po/fr_FR.po index d69e2f7d5..f7e362eda 100644 --- a/src/lib/po/fr_FR.po +++ b/src/lib/po/fr_FR.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: DCP-o-matic FRENCH\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-05-09 09:51+0100\n" -"PO-Revision-Date: 2013-05-10 14:33+0100\n" +"PO-Revision-Date: 2013-05-21 10:30+0100\n" "Last-Translator: \n" "Language-Team: \n" "Language: \n" @@ -489,7 +489,7 @@ msgstr "lecture du fichier impossible" #: src/lib/exceptions.cc:44 msgid "could not read from file %1 (%2)" -msgstr "création du dossier distant %1 impossible (%2)" +msgstr "lecture du fichier impossible %1 (%2)" #: src/lib/encoder.cc:137 #: src/lib/encoder.cc:314 diff --git a/src/lib/server.cc b/src/lib/server.cc index ca0bec580..07b826946 100644 --- a/src/lib/server.cc +++ b/src/lib/server.cc @@ -111,7 +111,6 @@ Server::process (shared_ptr<Socket> socket) string scaler_id = get_required_string (kv, N_("scaler")); int frame = get_required_int (kv, N_("frame")); int frames_per_second = get_required_int (kv, N_("frames_per_second")); - string post_process = get_optional_string (kv, N_("post_process")); int colour_lut_index = get_required_int (kv, N_("colour_lut")); int j2k_bandwidth = get_required_int (kv, N_("j2k_bandwidth")); Position subtitle_position (get_optional_int (kv, N_("subtitle_x")), get_optional_int (kv, N_("subtitle_y"))); @@ -136,7 +135,7 @@ Server::process (shared_ptr<Socket> socket) DCPVideoFrame dcp_video_frame ( image, sub, out_size, padding, subtitle_offset, subtitle_scale, - scaler, frame, frames_per_second, post_process, colour_lut_index, j2k_bandwidth, _log + scaler, frame, frames_per_second, colour_lut_index, j2k_bandwidth, _log ); shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally (); diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index b6dac2e76..c4c6e5f4e 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -65,11 +65,11 @@ SndfileDecoder::pass () int const channels = _sndfile_content->audio_channels (); - shared_ptr<AudioBuffers> audio (new AudioBuffers (channels, this_time)); + shared_ptr<AudioBuffers> data (new AudioBuffers (channels, this_time)); if (_sndfile_content->audio_channels() == 1) { /* No de-interleaving required */ - sf_read_float (_sndfile, audio->data(0), this_time); + sf_read_float (_sndfile, data->data(0), this_time); } else { /* Deinterleave */ if (!_deinterleave_buffer) { @@ -78,7 +78,7 @@ SndfileDecoder::pass () sf_readf_float (_sndfile, _deinterleave_buffer, this_time); vector<float*> out_ptr (channels); for (int i = 0; i < channels; ++i) { - out_ptr[i] = audio->data(i); + out_ptr[i] = data->data(i); } float* in_ptr = _deinterleave_buffer; for (int i = 0; i < this_time; ++i) { @@ -88,8 +88,8 @@ SndfileDecoder::pass () } } - audio->set_frames (this_time); - Audio (audio, double(_done) / audio_frame_rate()); + data->set_frames (this_time); + audio (data, double(_done) / audio_frame_rate()); _done += this_time; _remaining -= this_time; } diff --git a/src/lib/wscript b/src/lib/wscript index e7349a9c4..54941df42 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -26,7 +26,6 @@ sources = """ examine_content_job.cc exceptions.cc filter_graph.cc - ffmpeg_compatibility.cc ffmpeg_content.cc ffmpeg_decoder.cc film.cc diff --git a/src/tools/dcpomatic_cli.cc b/src/tools/dcpomatic_cli.cc index bc95f622d..c295a011d 100644 --- a/src/tools/dcpomatic_cli.cc +++ b/src/tools/dcpomatic_cli.cc @@ -140,8 +140,8 @@ main (int argc, char* argv[]) } cout << "DCP for " << film->name() << "\n"; // cout << "Content: " << film->content() << "\n"; - pair<string, string> const f = Filter::ffmpeg_strings (film->filters ()); - cout << "Filters: " << f.first << " " << f.second << "\n"; +// pair<string, string> const f = Filter::ffmpeg_strings (film->filters ()); +// cout << "Filters: " << f.first << " " << f.second << "\n"; film->make_dcp (); diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index 5cf0b7897..36d63b805 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -590,18 +590,6 @@ FilmEditor::film_changed (Film::Property p) case Film::CONTAINER: setup_container (); break; - case Film::FILTERS: - { - pair<string, string> p = Filter::ffmpeg_strings (_film->filters ()); - if (p.first.empty () && p.second.empty ()) { - _filters->SetLabel (_("None")); - } else { - string const b = p.first + " " + p.second; - _filters->SetLabel (std_to_wx (b)); - } - _dcp_sizer->Layout (); - break; - } case Film::NAME: checked_set (_name, _film->name()); setup_dcp_name (); @@ -722,6 +710,17 @@ FilmEditor::film_content_changed (weak_ptr<Content> weak_content, int property) } else if (property == FFmpegContentProperty::AUDIO_STREAM) { setup_dcp_name (); setup_show_audio_sensitivity (); + } else if (property == FFmpegContentProperty::FILTERS) { + if (ffmpeg_content) { + pair<string, string> p = Filter::ffmpeg_strings (ffmpeg_content->filters ()); + if (p.first.empty () && p.second.empty ()) { + _filters->SetLabel (_("None")); + } else { + string const b = p.first + " " + p.second; + _filters->SetLabel (std_to_wx (b)); + } + _dcp_sizer->Layout (); + } } } @@ -809,7 +808,6 @@ FilmEditor::set_film (shared_ptr<Film> f) film_changed (Film::LOOP); film_changed (Film::DCP_CONTENT_TYPE); film_changed (Film::CONTAINER); - film_changed (Film::FILTERS); film_changed (Film::SCALER); film_changed (Film::WITH_SUBTITLES); film_changed (Film::SUBTITLE_OFFSET); @@ -826,6 +824,7 @@ FilmEditor::set_film (shared_ptr<Film> f) film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::SUBTITLE_STREAM); film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::AUDIO_STREAMS); film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::AUDIO_STREAM); + film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::FILTERS); } /** Updates the sensitivity of lots of widgets to a given value. @@ -867,8 +866,18 @@ FilmEditor::set_things_sensitive (bool s) void FilmEditor::edit_filters_clicked (wxCommandEvent &) { - FilterDialog* d = new FilterDialog (this, _film->filters()); - d->ActiveChanged.connect (bind (&Film::set_filters, _film, _1)); + shared_ptr<Content> c = selected_content (); + if (!c) { + return; + } + + shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c); + if (!fc) { + return; + } + + FilterDialog* d = new FilterDialog (this, fc->filters()); + d->ActiveChanged.connect (bind (&FFmpegContent::set_filters, fc, _1)); d->ShowModal (); d->Destroy (); } diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index d08afe7a4..4b1fb442e 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -135,7 +135,6 @@ FilmViewer::film_changed (Film::Property p) _panel->Update (); break; case Film::SCALER: - case Film::FILTERS: update_from_decoder (); break; default: @@ -301,15 +300,8 @@ FilmViewer::raw_to_display () return; } - shared_ptr<const Image> input = _raw_frame; - - pair<string, string> const s = Filter::ffmpeg_strings (_film->filters()); - if (!s.second.empty ()) { - input = input->post_process (s.second, true); - } - /* Get a compacted image as we have to feed it to wxWidgets */ - _display_frame = input->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false); + _display_frame = _raw_frame->scale_and_convert_to_rgb (_film_size, 0, _film->scaler(), false); if (_raw_sub) { diff --git a/test/client_server_test.cc b/test/client_server_test.cc new file mode 100644 index 000000000..9c4482d3c --- /dev/null +++ b/test/client_server_test.cc @@ -0,0 +1,105 @@ +/* + 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. + +*/ + +void +do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* description, shared_ptr<EncodedData> locally_encoded) +{ + shared_ptr<EncodedData> remotely_encoded; + BOOST_CHECK_NO_THROW (remotely_encoded = frame->encode_remotely (description)); + BOOST_CHECK (remotely_encoded); + + BOOST_CHECK_EQUAL (locally_encoded->size(), remotely_encoded->size()); + BOOST_CHECK (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()) == 0); +} + +BOOST_AUTO_TEST_CASE (client_server_test) +{ + shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, libdcp::Size (1998, 1080), true)); + uint8_t* p = image->data()[0]; + + for (int y = 0; y < 1080; ++y) { + uint8_t* q = p; + for (int x = 0; x < 1998; ++x) { + *q++ = x % 256; + *q++ = y % 256; + *q++ = (x + y) % 256; + } + p += image->stride()[0]; + } + + shared_ptr<Image> sub_image (new SimpleImage (PIX_FMT_RGBA, libdcp::Size (100, 200), true)); + p = sub_image->data()[0]; + for (int y = 0; y < 200; ++y) { + uint8_t* q = p; + for (int x = 0; x < 100; ++x) { + *q++ = y % 256; + *q++ = x % 256; + *q++ = (x + y) % 256; + *q++ = 1; + } + p += sub_image->stride()[0]; + } + + shared_ptr<Subtitle> subtitle (new Subtitle (Position (50, 60), sub_image)); + + shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test.log")); + + shared_ptr<DCPVideoFrame> frame ( + new DCPVideoFrame ( + image, + subtitle, + libdcp::Size (1998, 1080), + 0, + 0, + 1, + Scaler::from_id ("bicubic"), + 0, + 24, + 0, + 200000000, + log + ) + ); + + shared_ptr<EncodedData> locally_encoded = frame->encode_locally (); + BOOST_ASSERT (locally_encoded); + + Server* server = new Server (log); + + new thread (boost::bind (&Server::run, server, 2)); + + /* Let the server get itself ready */ + dcpomatic_sleep (1); + + ServerDescription description ("localhost", 2); + + list<thread*> threads; + for (int i = 0; i < 8; ++i) { + threads.push_back (new thread (boost::bind (do_remote_encode, frame, &description, locally_encoded))); + } + + for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) { + (*i)->join (); + } + + for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) { + delete *i; + } +} + diff --git a/test/dcp_test.cc b/test/dcp_test.cc new file mode 100644 index 000000000..5a698684b --- /dev/null +++ b/test/dcp_test.cc @@ -0,0 +1,66 @@ +/* + 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. + +*/ + +BOOST_AUTO_TEST_CASE (make_dcp_test) +{ + shared_ptr<Film> film = new_test_film ("make_dcp_test"); + film->set_name ("test_film2"); + film->add_content (shared_ptr<FFmpegContent> (new FFmpegContent (film, "../../../test/test.mp4"))); + film->set_container (Container::from_id ("185")); + film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test")); + film->make_dcp (); + film->write_metadata (); + + while (JobManager::instance()->work_to_do ()) { + dcpomatic_sleep (1); + } + + BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false); +} + +/** Test Film::have_dcp(). Requires the output from make_dcp_test above */ +BOOST_AUTO_TEST_CASE (have_dcp_test) +{ + boost::filesystem::path p = test_film_dir ("make_dcp_test"); + Film f (p.string ()); + BOOST_CHECK (f.have_dcp()); + + p /= f.dcp_name(); + p /= f.dcp_video_mxf_filename(); + boost::filesystem::remove (p); + BOOST_CHECK (!f.have_dcp ()); +} + +BOOST_AUTO_TEST_CASE (make_dcp_with_range_test) +{ + shared_ptr<Film> film = new_test_film ("make_dcp_with_range_test"); + film->set_name ("test_film3"); + film->add_content (shared_ptr<Content> (new FFmpegContent (film, "../../../test/test.mp4"))); +// film->examine_content (); + film->set_container (Container::from_id ("185")); + film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test")); + film->make_dcp (); + + while (JobManager::instance()->work_to_do() && !JobManager::instance()->errors()) { + dcpomatic_sleep (1); + } + + BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false); +} + diff --git a/test/film_metadata_test.cc b/test/film_metadata_test.cc new file mode 100644 index 000000000..8309de7f1 --- /dev/null +++ b/test/film_metadata_test.cc @@ -0,0 +1,54 @@ +/* + 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 (film_metadata_test) +{ + string const test_film = "build/test/film_metadata_test"; + + if (boost::filesystem::exists (test_film)) { + boost::filesystem::remove_all (test_film); + } + + shared_ptr<Film> f (new Film (test_film)); + f->_dci_date = boost::gregorian::from_undelimited_string ("20130211"); + BOOST_CHECK (f->container() == 0); + BOOST_CHECK (f->dcp_content_type() == 0); + + f->set_name ("fred"); +// BOOST_CHECK_THROW (f->add_content ("jim"), OpenFileError); + f->set_dcp_content_type (DCPContentType::from_pretty_name ("Short")); + f->set_container (Container::from_id ("185")); + f->set_ab (true); + f->write_metadata (); + + stringstream s; + s << "diff -u test/metadata.ref " << test_film << "/metadata"; + BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0); + + shared_ptr<Film> g (new Film (test_film)); + g->read_metadata (); + + BOOST_CHECK_EQUAL (g->name(), "fred"); + BOOST_CHECK_EQUAL (g->dcp_content_type(), DCPContentType::from_pretty_name ("Short")); + BOOST_CHECK_EQUAL (g->container(), Container::from_id ("185")); + BOOST_CHECK_EQUAL (g->ab(), true); + + g->write_metadata (); + BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0); +} diff --git a/src/lib/ffmpeg_compatibility.h b/test/format_test.cc index 772d22c33..71bc00359 100644 --- a/src/lib/ffmpeg_compatibility.h +++ b/test/format_test.cc @@ -17,15 +17,17 @@ */ -struct AVFilterInOut; - -extern AVFilter* get_sink (); -extern AVFilterInOut* avfilter_inout_alloc (); - -#ifndef HAVE_AV_PIXEL_FORMAT -#define AVPixelFormat PixelFormat -#endif - -#ifndef HAVE_AV_FRAME_GET_BEST_EFFORT_TIMESTAMP -extern int64_t av_frame_get_best_effort_timestamp (AVFrame const *); -#endif +BOOST_AUTO_TEST_CASE (format_test) +{ + Format::setup_formats (); + + Format const * f = Format::from_nickname ("Flat"); + BOOST_CHECK (f); + BOOST_CHECK_EQUAL (f->dcp_size().width, 1998); + BOOST_CHECK_EQUAL (f->dcp_size().height, 1080); + + f = Format::from_nickname ("Scope"); + BOOST_CHECK (f); + BOOST_CHECK_EQUAL (f->dcp_size().width, 2048); + BOOST_CHECK_EQUAL (f->dcp_size().height, 858); +} diff --git a/test/frame_rate_test.cc b/test/frame_rate_test.cc new file mode 100644 index 000000000..8b04d3763 --- /dev/null +++ b/test/frame_rate_test.cc @@ -0,0 +1,213 @@ +/* + 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. + +*/ + +/* Test best_dcp_frame_rate and FrameRateConversion */ +BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test) +{ +#if 0 + /* Run some tests with a limited range of allowed rates */ + + std::list<int> afr; + afr.push_back (24); + afr.push_back (25); + afr.push_back (30); + Config::instance()->set_allowed_dcp_frame_rates (afr); + + int best = best_dcp_frame_rate (60); + FrameRateConversion frc = FrameRateConversion (60, best); + BOOST_CHECK_EQUAL (best, 30); + BOOST_CHECK_EQUAL (frc.skip, true); + BOOST_CHECK_EQUAL (frc.repeat, false); + BOOST_CHECK_EQUAL (frc.change_speed, false); + + best = best_dcp_frame_rate (50); + frc = FrameRateConversion (50, best); + BOOST_CHECK_EQUAL (best, 25); + BOOST_CHECK_EQUAL (frc.skip, true); + BOOST_CHECK_EQUAL (frc.repeat, false); + BOOST_CHECK_EQUAL (frc.change_speed, false); + + best = best_dcp_frame_rate (48); + frc = FrameRateConversion (48, best); + BOOST_CHECK_EQUAL (best, 24); + BOOST_CHECK_EQUAL (frc.skip, true); + BOOST_CHECK_EQUAL (frc.repeat, false); + BOOST_CHECK_EQUAL (frc.change_speed, false); + + best = best_dcp_frame_rate (30); + frc = FrameRateConversion (30, best); + BOOST_CHECK_EQUAL (best, 30); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, false); + BOOST_CHECK_EQUAL (frc.change_speed, false); + + best = best_dcp_frame_rate (29.97); + frc = FrameRateConversion (29.97, best); + BOOST_CHECK_EQUAL (best, 30); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, false); + BOOST_CHECK_EQUAL (frc.change_speed, true); + + best = best_dcp_frame_rate (25); + frc = FrameRateConversion (25, best); + BOOST_CHECK_EQUAL (best, 25); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, false); + BOOST_CHECK_EQUAL (frc.change_speed, false); + + best = best_dcp_frame_rate (24); + frc = FrameRateConversion (24, best); + BOOST_CHECK_EQUAL (best, 24); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, false); + BOOST_CHECK_EQUAL (frc.change_speed, false); + + best = best_dcp_frame_rate (14.5); + frc = FrameRateConversion (14.5, best); + BOOST_CHECK_EQUAL (best, 30); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, true); + BOOST_CHECK_EQUAL (frc.change_speed, true); + + best = best_dcp_frame_rate (12.6); + frc = FrameRateConversion (12.6, best); + BOOST_CHECK_EQUAL (best, 25); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, true); + BOOST_CHECK_EQUAL (frc.change_speed, true); + + best = best_dcp_frame_rate (12.4); + frc = FrameRateConversion (12.4, best); + BOOST_CHECK_EQUAL (best, 25); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, true); + BOOST_CHECK_EQUAL (frc.change_speed, true); + + best = best_dcp_frame_rate (12); + frc = FrameRateConversion (12, best); + BOOST_CHECK_EQUAL (best, 24); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, true); + BOOST_CHECK_EQUAL (frc.change_speed, false); + + /* Now add some more rates and see if it will use them + in preference to skip/repeat. + */ + + afr.push_back (48); + afr.push_back (50); + afr.push_back (60); + Config::instance()->set_allowed_dcp_frame_rates (afr); + + best = best_dcp_frame_rate (60); + frc = FrameRateConversion (60, best); + BOOST_CHECK_EQUAL (best, 60); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, false); + BOOST_CHECK_EQUAL (frc.change_speed, false); + + best = best_dcp_frame_rate (50); + frc = FrameRateConversion (50, best); + BOOST_CHECK_EQUAL (best, 50); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, false); + BOOST_CHECK_EQUAL (frc.change_speed, false); + + best = best_dcp_frame_rate (48); + frc = FrameRateConversion (48, best); + BOOST_CHECK_EQUAL (best, 48); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, false); + BOOST_CHECK_EQUAL (frc.change_speed, false); + + /* Check some out-there conversions (not the best) */ + + frc = FrameRateConversion (14.99, 24); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, true); + BOOST_CHECK_EQUAL (frc.change_speed, true); + + /* Check some conversions with limited DCP targets */ + + afr.clear (); + afr.push_back (24); + Config::instance()->set_allowed_dcp_frame_rates (afr); + + best = best_dcp_frame_rate (25); + frc = FrameRateConversion (25, best); + BOOST_CHECK_EQUAL (best, 24); + BOOST_CHECK_EQUAL (frc.skip, false); + BOOST_CHECK_EQUAL (frc.repeat, false); + BOOST_CHECK_EQUAL (frc.change_speed, true); +} + +BOOST_AUTO_TEST_CASE (audio_sampling_rate_test) +{ + std::list<int> afr; + afr.push_back (24); + afr.push_back (25); + afr.push_back (30); + Config::instance()->set_allowed_dcp_frame_rates (afr); + + shared_ptr<Film> f = new_test_film ("audio_sampling_rate_test"); + f->set_source_frame_rate (24); + f->set_dcp_frame_rate (24); + + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0))); + BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000); + + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0))); + BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000); + + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0))); + BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 96000); + + f->set_source_frame_rate (23.976); + f->set_dcp_frame_rate (best_dcp_frame_rate (23.976)); + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0))); + BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952); + + f->set_source_frame_rate (29.97); + f->set_dcp_frame_rate (best_dcp_frame_rate (29.97)); + BOOST_CHECK_EQUAL (f->dcp_frame_rate (), 30); + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0))); + BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952); + + f->set_source_frame_rate (25); + f->set_dcp_frame_rate (24); + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0))); + BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 50000); + + f->set_source_frame_rate (25); + f->set_dcp_frame_rate (24); + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0))); + BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 50000); + + /* Check some out-there conversions (not the best) */ + + f->set_source_frame_rate (14.99); + f->set_dcp_frame_rate (25); + f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0))); + /* The FrameRateConversion within target_audio_sample_rate should choose to double-up + the 14.99 fps video to 30 and then run it slow at 25. + */ + BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), rint (48000 * 2 * 14.99 / 25)); +#endif +} + diff --git a/test/image_test.cc b/test/image_test.cc new file mode 100644 index 000000000..a9ec3042a --- /dev/null +++ b/test/image_test.cc @@ -0,0 +1,99 @@ + +BOOST_AUTO_TEST_CASE (aligned_image_test) +{ + SimpleImage* s = new SimpleImage (PIX_FMT_RGB24, libdcp::Size (50, 50), true); + BOOST_CHECK_EQUAL (s->components(), 1); + /* 160 is 150 aligned to the nearest 32 bytes */ + BOOST_CHECK_EQUAL (s->stride()[0], 160); + BOOST_CHECK_EQUAL (s->line_size()[0], 150); + BOOST_CHECK (s->data()[0]); + BOOST_CHECK (!s->data()[1]); + BOOST_CHECK (!s->data()[2]); + BOOST_CHECK (!s->data()[3]); + + /* copy constructor */ + SimpleImage* t = new SimpleImage (*s); + BOOST_CHECK_EQUAL (t->components(), 1); + BOOST_CHECK_EQUAL (t->stride()[0], 160); + BOOST_CHECK_EQUAL (t->line_size()[0], 150); + BOOST_CHECK (t->data()[0]); + BOOST_CHECK (!t->data()[1]); + BOOST_CHECK (!t->data()[2]); + BOOST_CHECK (!t->data()[3]); + BOOST_CHECK (t->data() != s->data()); + BOOST_CHECK (t->data()[0] != s->data()[0]); + BOOST_CHECK (t->line_size() != s->line_size()); + BOOST_CHECK (t->line_size()[0] == s->line_size()[0]); + BOOST_CHECK (t->stride() != s->stride()); + BOOST_CHECK (t->stride()[0] == s->stride()[0]); + + /* assignment operator */ + SimpleImage* u = new SimpleImage (PIX_FMT_YUV422P, libdcp::Size (150, 150), false); + *u = *s; + BOOST_CHECK_EQUAL (u->components(), 1); + BOOST_CHECK_EQUAL (u->stride()[0], 160); + BOOST_CHECK_EQUAL (u->line_size()[0], 150); + BOOST_CHECK (u->data()[0]); + BOOST_CHECK (!u->data()[1]); + BOOST_CHECK (!u->data()[2]); + BOOST_CHECK (!u->data()[3]); + BOOST_CHECK (u->data() != s->data()); + BOOST_CHECK (u->data()[0] != s->data()[0]); + BOOST_CHECK (u->line_size() != s->line_size()); + BOOST_CHECK (u->line_size()[0] == s->line_size()[0]); + BOOST_CHECK (u->stride() != s->stride()); + BOOST_CHECK (u->stride()[0] == s->stride()[0]); + + delete s; + delete t; + delete u; +} + +BOOST_AUTO_TEST_CASE (compact_image_test) +{ + SimpleImage* s = new SimpleImage (PIX_FMT_RGB24, libdcp::Size (50, 50), false); + BOOST_CHECK_EQUAL (s->components(), 1); + BOOST_CHECK_EQUAL (s->stride()[0], 50 * 3); + BOOST_CHECK_EQUAL (s->line_size()[0], 50 * 3); + BOOST_CHECK (s->data()[0]); + BOOST_CHECK (!s->data()[1]); + BOOST_CHECK (!s->data()[2]); + BOOST_CHECK (!s->data()[3]); + + /* copy constructor */ + SimpleImage* t = new SimpleImage (*s); + BOOST_CHECK_EQUAL (t->components(), 1); + BOOST_CHECK_EQUAL (t->stride()[0], 50 * 3); + BOOST_CHECK_EQUAL (t->line_size()[0], 50 * 3); + BOOST_CHECK (t->data()[0]); + BOOST_CHECK (!t->data()[1]); + BOOST_CHECK (!t->data()[2]); + BOOST_CHECK (!t->data()[3]); + BOOST_CHECK (t->data() != s->data()); + BOOST_CHECK (t->data()[0] != s->data()[0]); + BOOST_CHECK (t->line_size() != s->line_size()); + BOOST_CHECK (t->line_size()[0] == s->line_size()[0]); + BOOST_CHECK (t->stride() != s->stride()); + BOOST_CHECK (t->stride()[0] == s->stride()[0]); + + /* assignment operator */ + SimpleImage* u = new SimpleImage (PIX_FMT_YUV422P, libdcp::Size (150, 150), true); + *u = *s; + BOOST_CHECK_EQUAL (u->components(), 1); + BOOST_CHECK_EQUAL (u->stride()[0], 50 * 3); + BOOST_CHECK_EQUAL (u->line_size()[0], 50 * 3); + BOOST_CHECK (u->data()[0]); + BOOST_CHECK (!u->data()[1]); + BOOST_CHECK (!u->data()[2]); + BOOST_CHECK (!u->data()[3]); + BOOST_CHECK (u->data() != s->data()); + BOOST_CHECK (u->data()[0] != s->data()[0]); + BOOST_CHECK (u->line_size() != s->line_size()); + BOOST_CHECK (u->line_size()[0] == s->line_size()[0]); + BOOST_CHECK (u->stride() != s->stride()); + BOOST_CHECK (u->stride()[0] == s->stride()[0]); + + delete s; + delete t; + delete u; +} diff --git a/test/job_test.cc b/test/job_test.cc new file mode 100644 index 000000000..86c6dc9e3 --- /dev/null +++ b/test/job_test.cc @@ -0,0 +1,64 @@ +/* + 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. + +*/ + +class TestJob : public Job +{ +public: + TestJob (shared_ptr<Film> f) + : Job (f) + { + + } + + void set_finished_ok () { + set_state (FINISHED_OK); + } + + void set_finished_error () { + set_state (FINISHED_ERROR); + } + + void run () + { + while (1) { + if (finished ()) { + return; + } + } + } + + string name () const { + return ""; + } +}; + +BOOST_AUTO_TEST_CASE (job_manager_test) +{ + shared_ptr<Film> f; + + /* Single job */ + shared_ptr<TestJob> a (new TestJob (f)); + + JobManager::instance()->add (a); + dcpomatic_sleep (1); + BOOST_CHECK_EQUAL (a->running (), true); + a->set_finished_ok (); + dcpomatic_sleep (2); + BOOST_CHECK_EQUAL (a->finished_ok(), true); +} diff --git a/test/make_black_test.cc b/test/make_black_test.cc new file mode 100644 index 000000000..3c0b979ff --- /dev/null +++ b/test/make_black_test.cc @@ -0,0 +1,56 @@ +/* + 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. + +*/ + +/* Check that Image::make_black works, and doesn't use values which crash + sws_scale(). +*/ +BOOST_AUTO_TEST_CASE (make_black_test) +{ + libdcp::Size in_size (512, 512); + libdcp::Size out_size (1024, 1024); + + list<AVPixelFormat> pix_fmts; + pix_fmts.push_back (AV_PIX_FMT_RGB24); + pix_fmts.push_back (AV_PIX_FMT_YUV420P); + pix_fmts.push_back (AV_PIX_FMT_YUV422P10LE); + pix_fmts.push_back (AV_PIX_FMT_YUV422P16LE); + pix_fmts.push_back (AV_PIX_FMT_YUV444P9LE); + pix_fmts.push_back (AV_PIX_FMT_YUV444P9BE); + pix_fmts.push_back (AV_PIX_FMT_YUV444P10LE); + pix_fmts.push_back (AV_PIX_FMT_YUV444P10BE); + pix_fmts.push_back (AV_PIX_FMT_UYVY422); + + int N = 0; + for (list<AVPixelFormat>::const_iterator i = pix_fmts.begin(); i != pix_fmts.end(); ++i) { + boost::shared_ptr<Image> foo (new SimpleImage (*i, in_size, true)); + foo->make_black (); + boost::shared_ptr<Image> bar = foo->scale_and_convert_to_rgb (out_size, 0, Scaler::from_id ("bicubic"), true); + + uint8_t* p = bar->data()[0]; + for (int y = 0; y < bar->size().height; ++y) { + uint8_t* q = p; + for (int x = 0; x < bar->line_size()[0]; ++x) { + BOOST_CHECK_EQUAL (*q++, 0); + } + p += bar->stride()[0]; + } + + ++N; + } +} diff --git a/test/pixel_formats_test.cc b/test/pixel_formats_test.cc new file mode 100644 index 000000000..08c9f2d2d --- /dev/null +++ b/test/pixel_formats_test.cc @@ -0,0 +1,74 @@ +/* + 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. + +*/ + +using std::list; +using std::cout; + +struct Case +{ + Case (AVPixelFormat f, int c, int l0, int l1, int l2, float b0, float b1, float b2) + : format(f) + , components(c) + { + lines[0] = l0; + lines[1] = l1; + lines[2] = l2; + bpp[0] = b0; + bpp[1] = b1; + bpp[2] = b2; + } + + AVPixelFormat format; + int components; + int lines[3]; + float bpp[3]; +}; + + +BOOST_AUTO_TEST_CASE (pixel_formats_test) +{ + list<Case> cases; + cases.push_back(Case(AV_PIX_FMT_RGB24, 1, 480, 480, 480, 3, 0, 0 )); + cases.push_back(Case(AV_PIX_FMT_RGBA, 1, 480, 480, 480, 4, 0, 0 )); + cases.push_back(Case(AV_PIX_FMT_YUV420P, 3, 480, 240, 240, 1, 0.5, 0.5)); + cases.push_back(Case(AV_PIX_FMT_YUV422P, 3, 480, 480, 480, 1, 0.5, 0.5)); + cases.push_back(Case(AV_PIX_FMT_YUV422P10LE, 3, 480, 480, 480, 2, 1, 1 )); + cases.push_back(Case(AV_PIX_FMT_YUV422P16LE, 3, 480, 480, 480, 2, 1, 1 )); + cases.push_back(Case(AV_PIX_FMT_UYVY422, 1, 480, 480, 480, 2, 0, 0 )); + cases.push_back(Case(AV_PIX_FMT_YUV444P, 3, 480, 480, 480, 1, 1, 1 )); + cases.push_back(Case(AV_PIX_FMT_YUV444P9BE, 3, 480, 480, 480, 2, 2, 2 )); + cases.push_back(Case(AV_PIX_FMT_YUV444P9LE, 3, 480, 480, 480, 2, 2, 2 )); + cases.push_back(Case(AV_PIX_FMT_YUV444P10BE, 3, 480, 480, 480, 2, 2, 2 )); + cases.push_back(Case(AV_PIX_FMT_YUV444P10LE, 3, 480, 480, 480, 2, 2, 2 )); + + for (list<Case>::iterator i = cases.begin(); i != cases.end(); ++i) { + AVFrame* f = av_frame_alloc (); + f->width = 640; + f->height = 480; + f->format = static_cast<int> (i->format); + SimpleImage t (f); + BOOST_CHECK_EQUAL(t.components(), i->components); + BOOST_CHECK_EQUAL(t.lines(0), i->lines[0]); + BOOST_CHECK_EQUAL(t.lines(1), i->lines[1]); + BOOST_CHECK_EQUAL(t.lines(2), i->lines[2]); + BOOST_CHECK_EQUAL(t.bytes_per_pixel(0), i->bpp[0]); + BOOST_CHECK_EQUAL(t.bytes_per_pixel(1), i->bpp[1]); + BOOST_CHECK_EQUAL(t.bytes_per_pixel(2), i->bpp[2]); + } +} diff --git a/test/stream_test.cc b/test/stream_test.cc new file mode 100644 index 000000000..4d97e82af --- /dev/null +++ b/test/stream_test.cc @@ -0,0 +1,54 @@ +/* + 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 (stream_test) +{ +#if 0 + FFmpegAudioStream a ("ffmpeg 4 44100 1 hello there world", boost::optional<int> (1)); + BOOST_CHECK_EQUAL (a.id(), 4); + BOOST_CHECK_EQUAL (a.sample_rate(), 44100); + BOOST_CHECK_EQUAL (a.channel_layout(), 1); + BOOST_CHECK_EQUAL (a.name(), "hello there world"); + BOOST_CHECK_EQUAL (a.to_string(), "ffmpeg 4 44100 1 hello there world"); + + SndfileStream e ("external 44100 1", boost::optional<int> (1)); + BOOST_CHECK_EQUAL (e.sample_rate(), 44100); + BOOST_CHECK_EQUAL (e.channel_layout(), 1); + BOOST_CHECK_EQUAL (e.to_string(), "external 44100 1"); + + SubtitleStream s ("5 a b c", boost::optional<int> (1)); + BOOST_CHECK_EQUAL (s.id(), 5); + BOOST_CHECK_EQUAL (s.name(), "a b c"); + + shared_ptr<AudioStream> ff = audio_stream_factory ("ffmpeg 4 44100 1 hello there world", boost::optional<int> (1)); + shared_ptr<FFmpegAudioStream> cff = dynamic_pointer_cast<FFmpegAudioStream> (ff); + BOOST_CHECK (cff); + BOOST_CHECK_EQUAL (cff->id(), 4); + BOOST_CHECK_EQUAL (cff->sample_rate(), 44100); + BOOST_CHECK_EQUAL (cff->channel_layout(), 1); + BOOST_CHECK_EQUAL (cff->name(), "hello there world"); + BOOST_CHECK_EQUAL (cff->to_string(), "ffmpeg 4 44100 1 hello there world"); + + shared_ptr<AudioStream> fe = audio_stream_factory ("external 44100 1", boost::optional<int> (1)); + BOOST_CHECK_EQUAL (fe->sample_rate(), 44100); + BOOST_CHECK_EQUAL (fe->channel_layout(), 1); + BOOST_CHECK_EQUAL (fe->to_string(), "external 44100 1"); +#endif +} + diff --git a/test/test.cc b/test/test.cc index 269bb374b..cbbd0443d 100644 --- a/test/test.cc +++ b/test/test.cc @@ -53,14 +53,20 @@ using boost::shared_ptr; using boost::thread; using boost::dynamic_pointer_cast; -void -setup_test_config () +struct TestConfig { - Config::instance()->set_num_local_encoding_threads (1); - Config::instance()->set_servers (vector<ServerDescription*> ()); - Config::instance()->set_server_port (61920); - Config::instance()->set_default_dci_metadata (DCIMetadata ()); -} + TestConfig() + { + dcpomatic_setup(); + + Config::instance()->set_num_local_encoding_threads (1); + Config::instance()->set_servers (vector<ServerDescription*> ()); + Config::instance()->set_server_port (61920); + Config::instance()->set_default_dci_metadata (DCIMetadata ()); + } +}; + +BOOST_GLOBAL_FIXTURE (TestConfig); boost::filesystem::path test_film_dir (string name) @@ -85,611 +91,14 @@ new_test_film (string name) return f; } - -/* Check that Image::make_black works, and doesn't use values which crash - sws_scale(). -*/ -BOOST_AUTO_TEST_CASE (make_black_test) -{ - /* This needs to happen in the first test */ - dcpomatic_setup (); - - libdcp::Size in_size (512, 512); - libdcp::Size out_size (1024, 1024); - - list<AVPixelFormat> pix_fmts; - pix_fmts.push_back (AV_PIX_FMT_RGB24); - pix_fmts.push_back (AV_PIX_FMT_YUV420P); - pix_fmts.push_back (AV_PIX_FMT_YUV422P10LE); - pix_fmts.push_back (AV_PIX_FMT_YUV444P9LE); - pix_fmts.push_back (AV_PIX_FMT_YUV444P9BE); - pix_fmts.push_back (AV_PIX_FMT_YUV444P10LE); - pix_fmts.push_back (AV_PIX_FMT_YUV444P10BE); - pix_fmts.push_back (AV_PIX_FMT_UYVY422); - - int N = 0; - for (list<AVPixelFormat>::const_iterator i = pix_fmts.begin(); i != pix_fmts.end(); ++i) { - boost::shared_ptr<Image> foo (new SimpleImage (*i, in_size, true)); - foo->make_black (); - boost::shared_ptr<Image> bar = foo->scale_and_convert_to_rgb (out_size, 0, Scaler::from_id ("bicubic"), true); - - uint8_t* p = bar->data()[0]; - for (int y = 0; y < bar->size().height; ++y) { - uint8_t* q = p; - for (int x = 0; x < bar->line_size()[0]; ++x) { - BOOST_CHECK_EQUAL (*q++, 0); - } - p += bar->stride()[0]; - } - - ++N; - } -} - -BOOST_AUTO_TEST_CASE (film_metadata_test) -{ - setup_test_config (); - - string const test_film = "build/test/film_metadata_test"; - - if (boost::filesystem::exists (test_film)) { - boost::filesystem::remove_all (test_film); - } - - shared_ptr<Film> f (new Film (test_film)); - f->write_metadata (); - f->_dci_date = boost::gregorian::from_undelimited_string ("20130211"); - BOOST_CHECK (f->container() == 0); - BOOST_CHECK (f->dcp_content_type() == 0); - BOOST_CHECK (f->filters ().empty()); - - f->set_name ("fred"); -// BOOST_CHECK_THROW (f->set_content ("jim"), OpenFileError); - f->set_dcp_content_type (DCPContentType::from_pretty_name ("Short")); - f->set_container (Container::from_id ("185")); - vector<Filter const *> f_filters; - f_filters.push_back (Filter::from_id ("pphb")); - f_filters.push_back (Filter::from_id ("unsharp")); - f->set_filters (f_filters); - f->set_ab (true); - f->write_metadata (); - - stringstream s; - s << "diff -u test/metadata.ref " << test_film << "/metadata"; - BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0); - - shared_ptr<Film> g (new Film (test_film)); - g->read_metadata (); - - BOOST_CHECK_EQUAL (g->name(), "fred"); - BOOST_CHECK_EQUAL (g->dcp_content_type(), DCPContentType::from_pretty_name ("Short")); - BOOST_CHECK_EQUAL (g->container(), Container::from_id ("185")); - vector<Filter const *> g_filters = g->filters (); - BOOST_CHECK_EQUAL (g_filters.size(), 2); - BOOST_CHECK_EQUAL (g_filters.front(), Filter::from_id ("pphb")); - BOOST_CHECK_EQUAL (g_filters.back(), Filter::from_id ("unsharp")); - BOOST_CHECK_EQUAL (g->ab(), true); - - g->write_metadata (); - BOOST_CHECK_EQUAL (::system (s.str().c_str ()), 0); -} - -BOOST_AUTO_TEST_CASE (format_test) -{ - Format::setup_formats (); - - Format const * f = Format::from_nickname ("Flat"); - BOOST_CHECK (f); - BOOST_CHECK_EQUAL (f->dcp_size().width, 1998); - BOOST_CHECK_EQUAL (f->dcp_size().height, 1080); - - f = Format::from_nickname ("Scope"); - BOOST_CHECK (f); - BOOST_CHECK_EQUAL (f->dcp_size().width, 2048); - BOOST_CHECK_EQUAL (f->dcp_size().height, 858); -} - -BOOST_AUTO_TEST_CASE (util_test) -{ - string t = "Hello this is a string \"with quotes\" and indeed without them"; - vector<string> b = split_at_spaces_considering_quotes (t); - vector<string>::iterator i = b.begin (); - BOOST_CHECK_EQUAL (*i++, "Hello"); - BOOST_CHECK_EQUAL (*i++, "this"); - BOOST_CHECK_EQUAL (*i++, "is"); - BOOST_CHECK_EQUAL (*i++, "a"); - BOOST_CHECK_EQUAL (*i++, "string"); - BOOST_CHECK_EQUAL (*i++, "with quotes"); - BOOST_CHECK_EQUAL (*i++, "and"); - BOOST_CHECK_EQUAL (*i++, "indeed"); - BOOST_CHECK_EQUAL (*i++, "without"); - BOOST_CHECK_EQUAL (*i++, "them"); -} - -class NullLog : public Log -{ -public: - void do_log (string) {} -}; - -BOOST_AUTO_TEST_CASE (md5_digest_test) -{ - string const t = md5_digest ("test/md5.test"); - BOOST_CHECK_EQUAL (t, "15058685ba99decdc4398c7634796eb0"); - - BOOST_CHECK_THROW (md5_digest ("foobar"), OpenFileError); -} - -void -do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* description, shared_ptr<EncodedData> locally_encoded) -{ - shared_ptr<EncodedData> remotely_encoded; - BOOST_CHECK_NO_THROW (remotely_encoded = frame->encode_remotely (description)); - BOOST_CHECK (remotely_encoded); - - BOOST_CHECK_EQUAL (locally_encoded->size(), remotely_encoded->size()); - BOOST_CHECK (memcmp (locally_encoded->data(), remotely_encoded->data(), locally_encoded->size()) == 0); -} - -BOOST_AUTO_TEST_CASE (client_server_test) -{ - shared_ptr<Image> image (new SimpleImage (PIX_FMT_RGB24, libdcp::Size (1998, 1080), true)); - uint8_t* p = image->data()[0]; - - for (int y = 0; y < 1080; ++y) { - uint8_t* q = p; - for (int x = 0; x < 1998; ++x) { - *q++ = x % 256; - *q++ = y % 256; - *q++ = (x + y) % 256; - } - p += image->stride()[0]; - } - - shared_ptr<Image> sub_image (new SimpleImage (PIX_FMT_RGBA, libdcp::Size (100, 200), true)); - p = sub_image->data()[0]; - for (int y = 0; y < 200; ++y) { - uint8_t* q = p; - for (int x = 0; x < 100; ++x) { - *q++ = y % 256; - *q++ = x % 256; - *q++ = (x + y) % 256; - *q++ = 1; - } - p += sub_image->stride()[0]; - } - - shared_ptr<Subtitle> subtitle (new Subtitle (Position (50, 60), sub_image)); - - shared_ptr<FileLog> log (new FileLog ("build/test/client_server_test.log")); - - shared_ptr<DCPVideoFrame> frame ( - new DCPVideoFrame ( - image, - subtitle, - libdcp::Size (1998, 1080), - 0, - 0, - 1, - Scaler::from_id ("bicubic"), - 0, - 24, - "", - 0, - 200000000, - log - ) - ); - - shared_ptr<EncodedData> locally_encoded = frame->encode_locally (); - BOOST_ASSERT (locally_encoded); - - Server* server = new Server (log); - - new thread (boost::bind (&Server::run, server, 2)); - - /* Let the server get itself ready */ - dcpomatic_sleep (1); - - ServerDescription description ("localhost", 2); - - list<thread*> threads; - for (int i = 0; i < 8; ++i) { - threads.push_back (new thread (boost::bind (do_remote_encode, frame, &description, locally_encoded))); - } - - for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) { - (*i)->join (); - } - - for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) { - delete *i; - } -} - -BOOST_AUTO_TEST_CASE (make_dcp_test) -{ - shared_ptr<Film> film = new_test_film ("make_dcp_test"); - film->set_name ("test_film2"); -// film->set_content ("../../../test/test.mp4"); - film->set_container (Container::from_id ("185")); - film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test")); - film->make_dcp (); - film->write_metadata (); - - while (JobManager::instance()->work_to_do ()) { - dcpomatic_sleep (1); - } - - BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false); -} - -/** Test Film::have_dcp(). Requires the output from make_dcp_test above */ -BOOST_AUTO_TEST_CASE (have_dcp_test) -{ - boost::filesystem::path p = test_film_dir ("make_dcp_test"); - Film f (p.string ()); - BOOST_CHECK (f.have_dcp()); - - p /= f.dcp_name(); - p /= f.dcp_video_mxf_filename(); - boost::filesystem::remove (p); - BOOST_CHECK (!f.have_dcp ()); -} - -BOOST_AUTO_TEST_CASE (make_dcp_with_range_test) -{ - shared_ptr<Film> film = new_test_film ("make_dcp_with_range_test"); - film->set_name ("test_film3"); -// film->set_content ("../../../test/test.mp4"); -// film->examine_content (); - film->set_container (Container::from_id ("185")); - film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test")); - film->make_dcp (); - - while (JobManager::instance()->work_to_do() && !JobManager::instance()->errors()) { - dcpomatic_sleep (1); - } - - BOOST_CHECK_EQUAL (JobManager::instance()->errors(), false); -} - -#if 0 -/* Test best_dcp_frame_rate and FrameRateConversion */ -BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test) -{ - /* Run some tests with a limited range of allowed rates */ - - std::list<int> afr; - afr.push_back (24); - afr.push_back (25); - afr.push_back (30); - Config::instance()->set_allowed_dcp_frame_rates (afr); - - int best = best_dcp_frame_rate (60); - FrameRateConversion frc = FrameRateConversion (60, best); - BOOST_CHECK_EQUAL (best, 30); - BOOST_CHECK_EQUAL (frc.skip, true); - BOOST_CHECK_EQUAL (frc.repeat, false); - BOOST_CHECK_EQUAL (frc.change_speed, false); - - best = best_dcp_frame_rate (50); - frc = FrameRateConversion (50, best); - BOOST_CHECK_EQUAL (best, 25); - BOOST_CHECK_EQUAL (frc.skip, true); - BOOST_CHECK_EQUAL (frc.repeat, false); - BOOST_CHECK_EQUAL (frc.change_speed, false); - - best = best_dcp_frame_rate (48); - frc = FrameRateConversion (48, best); - BOOST_CHECK_EQUAL (best, 24); - BOOST_CHECK_EQUAL (frc.skip, true); - BOOST_CHECK_EQUAL (frc.repeat, false); - BOOST_CHECK_EQUAL (frc.change_speed, false); - - best = best_dcp_frame_rate (30); - frc = FrameRateConversion (30, best); - BOOST_CHECK_EQUAL (best, 30); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, false); - BOOST_CHECK_EQUAL (frc.change_speed, false); - - best = best_dcp_frame_rate (29.97); - frc = FrameRateConversion (29.97, best); - BOOST_CHECK_EQUAL (best, 30); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, false); - BOOST_CHECK_EQUAL (frc.change_speed, true); - - best = best_dcp_frame_rate (25); - frc = FrameRateConversion (25, best); - BOOST_CHECK_EQUAL (best, 25); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, false); - BOOST_CHECK_EQUAL (frc.change_speed, false); - - best = best_dcp_frame_rate (24); - frc = FrameRateConversion (24, best); - BOOST_CHECK_EQUAL (best, 24); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, false); - BOOST_CHECK_EQUAL (frc.change_speed, false); - - best = best_dcp_frame_rate (14.5); - frc = FrameRateConversion (14.5, best); - BOOST_CHECK_EQUAL (best, 30); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, true); - BOOST_CHECK_EQUAL (frc.change_speed, true); - - best = best_dcp_frame_rate (12.6); - frc = FrameRateConversion (12.6, best); - BOOST_CHECK_EQUAL (best, 25); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, true); - BOOST_CHECK_EQUAL (frc.change_speed, true); - - best = best_dcp_frame_rate (12.4); - frc = FrameRateConversion (12.4, best); - BOOST_CHECK_EQUAL (best, 25); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, true); - BOOST_CHECK_EQUAL (frc.change_speed, true); - - best = best_dcp_frame_rate (12); - frc = FrameRateConversion (12, best); - BOOST_CHECK_EQUAL (best, 24); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, true); - BOOST_CHECK_EQUAL (frc.change_speed, false); - - /* Now add some more rates and see if it will use them - in preference to skip/repeat. - */ - - afr.push_back (48); - afr.push_back (50); - afr.push_back (60); - Config::instance()->set_allowed_dcp_frame_rates (afr); - - best = best_dcp_frame_rate (60); - frc = FrameRateConversion (60, best); - BOOST_CHECK_EQUAL (best, 60); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, false); - BOOST_CHECK_EQUAL (frc.change_speed, false); - - best = best_dcp_frame_rate (50); - frc = FrameRateConversion (50, best); - BOOST_CHECK_EQUAL (best, 50); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, false); - BOOST_CHECK_EQUAL (frc.change_speed, false); - - best = best_dcp_frame_rate (48); - frc = FrameRateConversion (48, best); - BOOST_CHECK_EQUAL (best, 48); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, false); - BOOST_CHECK_EQUAL (frc.change_speed, false); - - /* Check some out-there conversions (not the best) */ - - frc = FrameRateConversion (14.99, 24); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, true); - BOOST_CHECK_EQUAL (frc.change_speed, true); - - /* Check some conversions with limited DCP targets */ - - afr.clear (); - afr.push_back (24); - Config::instance()->set_allowed_dcp_frame_rates (afr); - - best = best_dcp_frame_rate (25); - frc = FrameRateConversion (25, best); - BOOST_CHECK_EQUAL (best, 24); - BOOST_CHECK_EQUAL (frc.skip, false); - BOOST_CHECK_EQUAL (frc.repeat, false); - BOOST_CHECK_EQUAL (frc.change_speed, true); -} -#endif - -BOOST_AUTO_TEST_CASE (audio_sampling_rate_test) -{ - std::list<int> afr; - afr.push_back (24); - afr.push_back (25); - afr.push_back (30); - Config::instance()->set_allowed_dcp_frame_rates (afr); - - shared_ptr<Film> f = new_test_film ("audio_sampling_rate_test"); -// f->set_source_frame_rate (24); -// f->set_dcp_frame_rate (24); - -// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0))); -// BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000); - -// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0))); -// BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 48000); - -// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0))); -// BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 96000); - -// f->set_source_frame_rate (23.976); -// f->set_dcp_frame_rate (best_dcp_frame_rate (23.976)); -// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0))); -// BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952); - -// f->set_source_frame_rate (29.97); -// f->set_dcp_frame_rate (best_dcp_frame_rate (29.97)); -// BOOST_CHECK_EQUAL (f->dcp_frame_rate (), 30); -// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0))); -// BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 47952); - -// f->set_source_frame_rate (25); -// f->set_dcp_frame_rate (24); -// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0))); -// BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 50000); - -// f->set_source_frame_rate (25); -// f->set_dcp_frame_rate (24); -// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0))); -// BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), 50000); - - /* Check some out-there conversions (not the best) */ - -// f->set_source_frame_rate (14.99); -// f->set_dcp_frame_rate (25); -// f->set_content_audio_stream (shared_ptr<AudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0))); - /* The FrameRateConversion within target_audio_sample_rate should choose to double-up - the 14.99 fps video to 30 and then run it slow at 25. - */ -// BOOST_CHECK_EQUAL (f->target_audio_sample_rate(), rint (48000 * 2 * 14.99 / 25)); -} - -class TestJob : public Job -{ -public: - TestJob (shared_ptr<Film> f) - : Job (f) - { - - } - - void set_finished_ok () { - set_state (FINISHED_OK); - } - - void set_finished_error () { - set_state (FINISHED_ERROR); - } - - void run () - { - while (1) { - if (finished ()) { - return; - } - } - } - - string name () const { - return ""; - } -}; - -BOOST_AUTO_TEST_CASE (job_manager_test) -{ - shared_ptr<Film> f; - - /* Single job */ - shared_ptr<TestJob> a (new TestJob (f)); - - JobManager::instance()->add (a); - dcpomatic_sleep (1); - BOOST_CHECK_EQUAL (a->running (), true); - a->set_finished_ok (); - dcpomatic_sleep (2); - BOOST_CHECK_EQUAL (a->finished_ok(), true); -} - -BOOST_AUTO_TEST_CASE (compact_image_test) -{ - SimpleImage* s = new SimpleImage (PIX_FMT_RGB24, libdcp::Size (50, 50), false); - BOOST_CHECK_EQUAL (s->components(), 1); - BOOST_CHECK_EQUAL (s->stride()[0], 50 * 3); - BOOST_CHECK_EQUAL (s->line_size()[0], 50 * 3); - BOOST_CHECK (s->data()[0]); - BOOST_CHECK (!s->data()[1]); - BOOST_CHECK (!s->data()[2]); - BOOST_CHECK (!s->data()[3]); - - /* copy constructor */ - SimpleImage* t = new SimpleImage (*s); - BOOST_CHECK_EQUAL (t->components(), 1); - BOOST_CHECK_EQUAL (t->stride()[0], 50 * 3); - BOOST_CHECK_EQUAL (t->line_size()[0], 50 * 3); - BOOST_CHECK (t->data()[0]); - BOOST_CHECK (!t->data()[1]); - BOOST_CHECK (!t->data()[2]); - BOOST_CHECK (!t->data()[3]); - BOOST_CHECK (t->data() != s->data()); - BOOST_CHECK (t->data()[0] != s->data()[0]); - BOOST_CHECK (t->line_size() != s->line_size()); - BOOST_CHECK (t->line_size()[0] == s->line_size()[0]); - BOOST_CHECK (t->stride() != s->stride()); - BOOST_CHECK (t->stride()[0] == s->stride()[0]); - - /* assignment operator */ - SimpleImage* u = new SimpleImage (PIX_FMT_YUV422P, libdcp::Size (150, 150), true); - *u = *s; - BOOST_CHECK_EQUAL (u->components(), 1); - BOOST_CHECK_EQUAL (u->stride()[0], 50 * 3); - BOOST_CHECK_EQUAL (u->line_size()[0], 50 * 3); - BOOST_CHECK (u->data()[0]); - BOOST_CHECK (!u->data()[1]); - BOOST_CHECK (!u->data()[2]); - BOOST_CHECK (!u->data()[3]); - BOOST_CHECK (u->data() != s->data()); - BOOST_CHECK (u->data()[0] != s->data()[0]); - BOOST_CHECK (u->line_size() != s->line_size()); - BOOST_CHECK (u->line_size()[0] == s->line_size()[0]); - BOOST_CHECK (u->stride() != s->stride()); - BOOST_CHECK (u->stride()[0] == s->stride()[0]); - - delete s; - delete t; - delete u; -} - -BOOST_AUTO_TEST_CASE (aligned_image_test) -{ - SimpleImage* s = new SimpleImage (PIX_FMT_RGB24, libdcp::Size (50, 50), true); - BOOST_CHECK_EQUAL (s->components(), 1); - /* 160 is 150 aligned to the nearest 32 bytes */ - BOOST_CHECK_EQUAL (s->stride()[0], 160); - BOOST_CHECK_EQUAL (s->line_size()[0], 150); - BOOST_CHECK (s->data()[0]); - BOOST_CHECK (!s->data()[1]); - BOOST_CHECK (!s->data()[2]); - BOOST_CHECK (!s->data()[3]); - - /* copy constructor */ - SimpleImage* t = new SimpleImage (*s); - BOOST_CHECK_EQUAL (t->components(), 1); - BOOST_CHECK_EQUAL (t->stride()[0], 160); - BOOST_CHECK_EQUAL (t->line_size()[0], 150); - BOOST_CHECK (t->data()[0]); - BOOST_CHECK (!t->data()[1]); - BOOST_CHECK (!t->data()[2]); - BOOST_CHECK (!t->data()[3]); - BOOST_CHECK (t->data() != s->data()); - BOOST_CHECK (t->data()[0] != s->data()[0]); - BOOST_CHECK (t->line_size() != s->line_size()); - BOOST_CHECK (t->line_size()[0] == s->line_size()[0]); - BOOST_CHECK (t->stride() != s->stride()); - BOOST_CHECK (t->stride()[0] == s->stride()[0]); - - /* assignment operator */ - SimpleImage* u = new SimpleImage (PIX_FMT_YUV422P, libdcp::Size (150, 150), false); - *u = *s; - BOOST_CHECK_EQUAL (u->components(), 1); - BOOST_CHECK_EQUAL (u->stride()[0], 160); - BOOST_CHECK_EQUAL (u->line_size()[0], 150); - BOOST_CHECK (u->data()[0]); - BOOST_CHECK (!u->data()[1]); - BOOST_CHECK (!u->data()[2]); - BOOST_CHECK (!u->data()[3]); - BOOST_CHECK (u->data() != s->data()); - BOOST_CHECK (u->data()[0] != s->data()[0]); - BOOST_CHECK (u->line_size() != s->line_size()); - BOOST_CHECK (u->line_size()[0] == s->line_size()[0]); - BOOST_CHECK (u->stride() != s->stride()); - BOOST_CHECK (u->stride()[0] == s->stride()[0]); - - delete s; - delete t; - delete u; -} - +#include "pixel_formats_test.cc" +#include "make_black_test.cc" +#include "film_metadata_test.cc" +#include "stream_test.cc" +#include "format_test.cc" +#include "util_test.cc" +#include "dcp_test.cc" +#include "frame_rate_test.cc" +#include "job_test.cc" +#include "client_server_test.cc" +#include "image_test.cc" diff --git a/test/util_test.cc b/test/util_test.cc new file mode 100644 index 000000000..18c24ac3a --- /dev/null +++ b/test/util_test.cc @@ -0,0 +1,43 @@ +/* + 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. + +*/ + +BOOST_AUTO_TEST_CASE (util_test) +{ + string t = "Hello this is a string \"with quotes\" and indeed without them"; + vector<string> b = split_at_spaces_considering_quotes (t); + vector<string>::iterator i = b.begin (); + BOOST_CHECK_EQUAL (*i++, "Hello"); + BOOST_CHECK_EQUAL (*i++, "this"); + BOOST_CHECK_EQUAL (*i++, "is"); + BOOST_CHECK_EQUAL (*i++, "a"); + BOOST_CHECK_EQUAL (*i++, "string"); + BOOST_CHECK_EQUAL (*i++, "with quotes"); + BOOST_CHECK_EQUAL (*i++, "and"); + BOOST_CHECK_EQUAL (*i++, "indeed"); + BOOST_CHECK_EQUAL (*i++, "without"); + BOOST_CHECK_EQUAL (*i++, "them"); +} + +BOOST_AUTO_TEST_CASE (md5_digest_test) +{ + string const t = md5_digest ("test/md5.test"); + BOOST_CHECK_EQUAL (t, "15058685ba99decdc4398c7634796eb0"); + + BOOST_CHECK_THROW (md5_digest ("foobar"), OpenFileError); +} @@ -174,36 +174,6 @@ def configure(conf): define_name = 'HAVE_G_FORMAT_SIZE', mandatory = False) - conf.check_cc(fragment = """ - extern "C" { - #include <libavutil/avutil.h> - } - int main() { AVPixelFormat f; } - """, msg = 'Checking for AVPixelFormat', - uselib = 'AVUTIL', - define_name = 'HAVE_AV_PIXEL_FORMAT', - mandatory = False) - - conf.check_cc(fragment = """ - extern "C" { - #include <libavcodec/avcodec.h> - } - int main() { AVFrame* f; av_frame_get_best_effort_timestamp(f); } - """, msg = 'Checking for av_frame_get_best_effort_timestamp', - uselib = 'AVCODEC', - define_name = 'HAVE_AV_FRAME_GET_BEST_EFFORT_TIMESTAMP', - mandatory = False) - - conf.check_cc(fragment = """ - extern "C" { - #include <libavfilter/buffersrc.h> - } - int main() { } - """, msg = 'Checking for buffersrc.h', - uselib = 'AVCODEC', - define_name = 'HAVE_BUFFERSRC_H', - mandatory = False) - conf.find_program('msgfmt', var='MSGFMT') datadir = conf.env.DATADIR |
