we just count samples, as it seems that ContentTimes are unreliable from
FFmpegDecoder (not quite continuous; perhaps due to some rounding error).
*/
+ if (_content->delay() > 0) {
+ /* Insert silence to give the delay */
+ silence (_content->delay ());
+ }
+ time += ContentTime::from_seconds (_content->delay() / 1000.0);
_positions[stream] = time.frames_round (stream->frame_rate ());
}
_positions[i->first] += ro->frames();
}
}
+
+ if (_content->delay() < 0) {
+ /* Finish off with the gap caused by the delay */
+ silence (-_content->delay ());
+ }
+}
+
+void
+AudioDecoder::silence (int milliseconds)
+{
+ BOOST_FOREACH (AudioStreamPtr i, _content->streams ()) {
+ int const samples = ContentTime::from_seconds(milliseconds / 1000.0).frames_round(i->frame_rate());
+ shared_ptr<AudioBuffers> silence (new AudioBuffers (i->channels(), samples));
+ silence->make_silent ();
+ Data (i, ContentAudio (silence, _positions[i]));
+ }
}
boost::signals2::signal<void (AudioStreamPtr, ContentAudio)> Data;
private:
+ void silence (int milliseconds);
+
boost::shared_ptr<const AudioContent> _content;
/** Frame after the last one that was emitted from Data for each AudioStream */
std::map<AudioStreamPtr, Frame> _positions;
--- /dev/null
+/*
+ Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "dcpomatic_time.h"
+#include <iostream>
+
+/** @param periods Set of periods in ascending order of from time */
+template <class T>
+std::list<TimePeriod<T> > coalesce (std::list<TimePeriod<T> > periods)
+{
+ bool did_something;
+ std::list<TimePeriod<T> > coalesced;
+ do {
+ coalesced.clear ();
+ did_something = false;
+ for (typename std::list<TimePeriod<T> >::const_iterator i = periods.begin(); i != periods.end(); ++i) {
+ typename std::list<TimePeriod<T> >::const_iterator j = i;
+ ++j;
+ if (j != periods.end() && (i->overlap(*j) || i->to == j->from)) {
+ coalesced.push_back (TimePeriod<T> (i->from, j->to));
+ did_something = true;
+ ++i;
+ } else {
+ coalesced.push_back (*i);
+ }
+ }
+ periods = coalesced;
+ } while (did_something);
+
+ return periods;
+}
--- /dev/null
+/*
+ Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "empty.h"
+#include "playlist.h"
+#include "content.h"
+#include "content_part.h"
+#include "dcp_content.h"
+#include "dcpomatic_time_coalesce.h"
+#include <boost/foreach.hpp>
+#include <iostream>
+
+using std::cout;
+using std::list;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::function;
+
+Empty::Empty (shared_ptr<const Playlist> playlist, function<shared_ptr<ContentPart> (Content *)> part)
+{
+ list<DCPTimePeriod> full;
+ BOOST_FOREACH (shared_ptr<Content> i, playlist->content()) {
+ if (part (i.get())) {
+ full.push_back (DCPTimePeriod (i->position(), i->end()));
+ }
+ }
+
+ _periods = subtract (DCPTimePeriod(DCPTime(), playlist->length()), coalesce(full));
+}
+
+void
+Empty::set_position (DCPTime position)
+{
+ _position = position;
+
+ BOOST_FOREACH (DCPTimePeriod i, _periods) {
+ if (i.contains(_position)) {
+ return;
+ }
+ }
+
+ BOOST_FOREACH (DCPTimePeriod i, _periods) {
+ if (i.from > _position) {
+ _position = i.from;
+ return;
+ }
+ }
+}
+
+DCPTimePeriod
+Empty::period_at_position () const
+{
+ BOOST_FOREACH (DCPTimePeriod i, _periods) {
+ if (i.contains(_position)) {
+ return DCPTimePeriod (_position, i.to);
+ }
+ }
+
+ DCPOMATIC_ASSERT (false);
+}
+
+bool
+Empty::done () const
+{
+ BOOST_FOREACH (DCPTimePeriod i, _periods) {
+ if (i.contains(_position)) {
+ return false;
+ }
+ }
+
+ return true;
+}
--- /dev/null
+/*
+ Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "playlist.h"
+#include "dcpomatic_time.h"
+#include "content_part.h"
+#include <list>
+
+struct empty_test1;
+
+class Empty
+{
+public:
+ Empty () {}
+ Empty (boost::shared_ptr<const Playlist> playlist, boost::function<boost::shared_ptr<ContentPart> (Content *)> part);
+
+ DCPTime position () const {
+ return _position;
+ }
+
+ DCPTimePeriod period_at_position () const;
+
+ bool done () const;
+
+ void set_position (DCPTime amount);
+
+private:
+ friend struct ::empty_test1;
+
+ std::list<DCPTimePeriod> _periods;
+ DCPTime _position;
+};
}
}
+ _black = Empty (_playlist, bind(&Content::video, _1));
+ _silent = Empty (_playlist, bind(&Content::audio, _1));
+
_last_video_time = DCPTime ();
_last_audio_time = DCPTime ();
_have_valid_pieces = true;
Player::resampled_audio_to_dcp (shared_ptr<const Piece> piece, Frame f) const
{
/* See comment in dcp_to_content_video */
- DCPTime const d = DCPTime::from_frames (f, _film->audio_frame_rate()) - DCPTime (piece->content->trim_start(), piece->frc);
- return max (DCPTime (), d + piece->content->position ());
+ return DCPTime::from_frames (f, _film->audio_frame_rate())
+ - DCPTime (piece->content->trim_start(), piece->frc)
+ + piece->content->position();
}
ContentTime
setup_pieces ();
}
- bool filled = false;
-
- if (_last_video_time && !_playlist->video_content_at(*_last_video_time) && *_last_video_time < _playlist->length()) {
- /* _last_video_time is the time just after the last video we emitted, and there is no video content
- at this time so we need to emit some black.
- */
- emit_video (black_player_video_frame(), *_last_video_time);
- filled = true;
- } else if (_playlist->length() == DCPTime()) {
- /* Special case of an empty Film; just give one black frame */
- emit_video (black_player_video_frame(), DCPTime());
- filled = true;
- }
-
- if (_last_audio_time && !_playlist->audio_content_at(*_last_audio_time) && *_last_audio_time < _playlist->length()) {
- /* _last_audio_time is the time just after the last audio we emitted. There is no audio here
- so we need to emit some silence.
- */
- shared_ptr<Content> next = _playlist->next_audio_content(*_last_audio_time);
- DCPTimePeriod period (*_last_audio_time, next ? next->position() : _playlist->length());
- if (period.duration() > one_video_frame()) {
- period = DCPTimePeriod (*_last_audio_time, *_last_audio_time + one_video_frame());
- }
- fill_audio (period);
- filled = true;
- }
-
- /* Now pass() the decoder which is farthest behind where we are */
+ /* Find the decoder or empty which is farthest behind where we are and make it emit some data */
shared_ptr<Piece> earliest;
DCPTime earliest_content;
}
}
- if (!filled && earliest) {
+ bool done = false;
+
+ if (!_black.done() && (!earliest ||_black.position() < earliest_content)) {
+ /* There is some black that must be emitted */
+ emit_video (black_player_video_frame(), _black.position());
+ _black.set_position (_black.position() + one_video_frame());
+ } else if (!_silent.done() && (!earliest || _silent.position() < earliest_content)) {
+ /* There is some silence that must be emitted */
+ DCPTimePeriod period (_silent.period_at_position());
+ if (period.duration() > one_video_frame()) {
+ period.to = period.from + one_video_frame();
+ }
+ fill_audio (period);
+ _silent.set_position (period.to);
+ } else if (_playlist->length() == DCPTime()) {
+ /* Special case of an empty Film; just give one black frame */
+ emit_video (black_player_video_frame(), DCPTime());
+ } else if (earliest) {
earliest->done = earliest->decoder->pass ();
+ } else {
+ done = true;
}
/* Emit any audio that is ready */
*i = cut;
}
- if (_last_audio_time) {
- /* Fill in the gap before delayed audio; this doesn't need to take into account
- periods with no audio as it should only occur in delayed audio case.
- */
- fill_audio (DCPTimePeriod (*_last_audio_time, i->second));
- }
-
emit_audio (i->first, i->second);
}
- return !earliest && !filled;
+ return done;
}
optional<PositionImage>
emit_video (_last_video[wp], time);
}
-/** Do our common processing on some audio */
-void
-Player::audio_transform (shared_ptr<AudioContent> content, AudioStreamPtr stream, ContentAudio content_audio, DCPTime time)
-{
- DCPOMATIC_ASSERT (content_audio.audio->frames() > 0);
-
- /* Gain */
-
- if (content->gain() != 0) {
- shared_ptr<AudioBuffers> gain (new AudioBuffers (content_audio.audio));
- gain->apply_gain (content->gain ());
- content_audio.audio = gain;
- }
-
- /* Remap */
-
- content_audio.audio = remap (content_audio.audio, _film->audio_channels(), stream->mapping());
-
- /* Process */
-
- if (_audio_processor) {
- content_audio.audio = _audio_processor->run (content_audio.audio, _film->audio_channels ());
- }
-
- /* Push */
-
- _audio_merger.push (content_audio.audio, time);
- DCPOMATIC_ASSERT (_stream_states.find (stream) != _stream_states.end ());
- _stream_states[stream].last_push_end = time + DCPTime::from_frames (content_audio.audio->frames(), _film->audio_frame_rate());
-}
-
void
Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_audio)
{
DCPOMATIC_ASSERT (content);
/* Compute time in the DCP */
- DCPTime time = resampled_audio_to_dcp (piece, content_audio.frame) + DCPTime::from_seconds (content->delay() / 1000.0);
+ DCPTime time = resampled_audio_to_dcp (piece, content_audio.frame);
/* And the end of this block in the DCP */
DCPTime end = time + DCPTime::from_frames(content_audio.audio->frames(), content->resampled_frame_rate());
content_audio.audio = cut;
}
- audio_transform (content, stream, content_audio, time);
+ DCPOMATIC_ASSERT (content_audio.audio->frames() > 0);
+
+ /* Gain */
+
+ if (content->gain() != 0) {
+ shared_ptr<AudioBuffers> gain (new AudioBuffers (content_audio.audio));
+ gain->apply_gain (content->gain ());
+ content_audio.audio = gain;
+ }
+
+ /* Remap */
+
+ content_audio.audio = remap (content_audio.audio, _film->audio_channels(), stream->mapping());
+
+ /* Process */
+
+ if (_audio_processor) {
+ content_audio.audio = _audio_processor->run (content_audio.audio, _film->audio_channels ());
+ }
+
+ /* Push */
+
+ _audio_merger.push (content_audio.audio, time);
+ DCPOMATIC_ASSERT (_stream_states.find (stream) != _stream_states.end ());
+ _stream_states[stream].last_push_end = time + DCPTime::from_frames (content_audio.audio->frames(), _film->audio_frame_rate());
}
void
_last_audio_time = optional<DCPTime>();
}
+ _black.set_position (time);
+ _silent.set_position (time);
+
_last_video.clear ();
}
#include "content_subtitle.h"
#include "audio_stream.h"
#include "audio_merger.h"
+#include "empty.h"
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <list>
void subtitle_stop (boost::weak_ptr<Piece>, ContentTime);
DCPTime one_video_frame () const;
void fill_audio (DCPTimePeriod period);
- void audio_transform (boost::shared_ptr<AudioContent> content, AudioStreamPtr stream, ContentAudio content_audio, DCPTime time);
std::pair<boost::shared_ptr<AudioBuffers>, DCPTime> discard_audio (
boost::shared_ptr<const AudioBuffers> audio, DCPTime time, DCPTime discard_to
) const;
};
std::map<AudioStreamPtr, StreamState> _stream_states;
+ Empty _black;
+ Empty _silent;
+
ActiveSubtitles _active_subtitles;
boost::shared_ptr<AudioProcessor> _audio_processor;
if (!i->audio) {
continue;
}
- DCPTime end = i->end ();
- if (i->audio->delay() < 0) {
- end += DCPTime::from_seconds (i->audio->delay() / 1000.0);
- }
- if (i->position() <= time && time < end) {
+ if (i->position() <= time && time < i->end()) {
return true;
}
}
dkdm_wrapper.cc
dolby_cp750.cc
emailer.cc
+ empty.cc
encoder.cc
encode_server.cc
encode_server_finder.cc
*/
#include "lib/dcpomatic_time.h"
+#include "lib/dcpomatic_time_coalesce.h"
#include <boost/test/unit_test.hpp>
#include <list>
+#include <iostream>
using std::list;
+using std::cout;
BOOST_AUTO_TEST_CASE (dcpomatic_time_test)
{
++i;
BOOST_REQUIRE (i == r.end ());
}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_subtract_test8)
+{
+ DCPTimePeriod A (DCPTime(0), DCPTime(32000));
+ list<DCPTimePeriod> B;
+ B.push_back (DCPTimePeriod (DCPTime(8000), DCPTime(20000)));
+ B.push_back (DCPTimePeriod (DCPTime(28000), DCPTime(32000)));
+ list<DCPTimePeriod> r = subtract (A, B);
+ list<DCPTimePeriod>::const_iterator i = r.begin ();
+ BOOST_REQUIRE (i != r.end ());
+ BOOST_CHECK (*i == DCPTimePeriod(DCPTime(0), DCPTime(8000)));
+ ++i;
+ BOOST_REQUIRE (i != r.end ());
+ BOOST_CHECK (*i == DCPTimePeriod(DCPTime(20000), DCPTime(28000)));
+ ++i;
+ BOOST_REQUIRE (i == r.end ());
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_coalesce_test1)
+{
+ DCPTimePeriod A (DCPTime(14), DCPTime(29));
+ DCPTimePeriod B (DCPTime(45), DCPTime(91));
+ list<DCPTimePeriod> p;
+ p.push_back (A);
+ p.push_back (B);
+ list<DCPTimePeriod> q = coalesce (p);
+ BOOST_REQUIRE_EQUAL (q.size(), 2);
+ BOOST_CHECK (q.front() == DCPTimePeriod(DCPTime(14), DCPTime(29)));
+ BOOST_CHECK (q.back () == DCPTimePeriod(DCPTime(45), DCPTime(91)));
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_coalesce_test2)
+{
+ DCPTimePeriod A (DCPTime(14), DCPTime(29));
+ DCPTimePeriod B (DCPTime(26), DCPTime(91));
+ list<DCPTimePeriod> p;
+ p.push_back (A);
+ p.push_back (B);
+ list<DCPTimePeriod> q = coalesce (p);
+ BOOST_REQUIRE_EQUAL (q.size(), 1);
+ BOOST_CHECK (q.front() == DCPTimePeriod(DCPTime(14), DCPTime(91)));
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_coalesce_test3)
+{
+ DCPTimePeriod A (DCPTime(14), DCPTime(29));
+ DCPTimePeriod B (DCPTime(29), DCPTime(91));
+ list<DCPTimePeriod> p;
+ p.push_back (A);
+ p.push_back (B);
+ list<DCPTimePeriod> q = coalesce (p);
+ BOOST_REQUIRE_EQUAL (q.size(), 1);
+ BOOST_CHECK (q.front() == DCPTimePeriod(DCPTime(14), DCPTime(91)));
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_coalesce_test4)
+{
+ DCPTimePeriod A (DCPTime(14), DCPTime(29));
+ DCPTimePeriod B (DCPTime(20), DCPTime(91));
+ DCPTimePeriod C (DCPTime(35), DCPTime(106));
+ list<DCPTimePeriod> p;
+ p.push_back (A);
+ p.push_back (B);
+ p.push_back (C);
+ list<DCPTimePeriod> q = coalesce (p);
+ BOOST_REQUIRE_EQUAL (q.size(), 1);
+ BOOST_CHECK (q.front() == DCPTimePeriod(DCPTime(14), DCPTime(106)));
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_coalesce_test5)
+{
+ DCPTimePeriod A (DCPTime(14), DCPTime(29));
+ DCPTimePeriod B (DCPTime(20), DCPTime(91));
+ DCPTimePeriod C (DCPTime(100), DCPTime(106));
+ list<DCPTimePeriod> p;
+ p.push_back (A);
+ p.push_back (B);
+ p.push_back (C);
+ list<DCPTimePeriod> q = coalesce (p);
+ BOOST_REQUIRE_EQUAL (q.size(), 2);
+ BOOST_CHECK (q.front() == DCPTimePeriod(DCPTime(14), DCPTime(91)));
+ BOOST_CHECK (q.back() == DCPTimePeriod(DCPTime(100), DCPTime(106)));
+}
--- /dev/null
+/*
+ Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "lib/film.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ratio.h"
+#include "lib/video_content.h"
+#include "lib/image_content.h"
+#include "lib/empty.h"
+#include "test.h"
+#include <boost/test/unit_test.hpp>
+
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (empty_test1)
+{
+ shared_ptr<Film> film = new_test_film ("empty_test1");
+ film->set_dcp_content_type (DCPContentType::from_isdcf_name ("FTR"));
+ film->set_name ("empty_test1");
+ film->set_container (Ratio::from_id ("185"));
+ film->set_sequence (false);
+ shared_ptr<ImageContent> contentA (new ImageContent (film, "test/data/simple_testcard_640x480.png"));
+ shared_ptr<ImageContent> contentB (new ImageContent (film, "test/data/simple_testcard_640x480.png"));
+
+ film->examine_and_add_content (contentA);
+ film->examine_and_add_content (contentB);
+ wait_for_jobs ();
+
+ contentA->video->set_scale (VideoContentScale (Ratio::from_id ("185")));
+ contentA->video->set_length (3);
+ contentA->set_position (DCPTime::from_frames (2, film->video_frame_rate ()));
+ contentB->video->set_scale (VideoContentScale (Ratio::from_id ("185")));
+ contentB->video->set_length (1);
+ contentB->set_position (DCPTime::from_frames (7, film->video_frame_rate ()));
+
+ Empty black (film->playlist(), bind(&Content::video, _1));
+ BOOST_REQUIRE_EQUAL (black._periods.size(), 2);
+ BOOST_CHECK (black._periods.front().from == DCPTime());
+ BOOST_CHECK (black._periods.front().to == DCPTime::from_frames(2, film->video_frame_rate()));
+ BOOST_CHECK (black._periods.back().from == DCPTime::from_frames(5, film->video_frame_rate()));
+ BOOST_CHECK (black._periods.back().to == DCPTime::from_frames(7, film->video_frame_rate()));
+}
dcpomatic_time_test.cc
dcp_subtitle_test.cc
digest_test.cc
+ empty_test.cc
ffmpeg_audio_only_test.cc
ffmpeg_audio_test.cc
ffmpeg_dcp_test.cc