Fix merging of audio in various circumstances.
authorCarl Hetherington <cth@carlh.net>
Sun, 26 Feb 2017 01:40:30 +0000 (01:40 +0000)
committerCarl Hetherington <cth@carlh.net>
Wed, 19 Apr 2017 22:04:32 +0000 (23:04 +0100)
18 files changed:
src/lib/audio_buffers.cc
src/lib/audio_buffers.h
src/lib/audio_delay.cc
src/lib/audio_filter.cc
src/lib/audio_merger.cc
src/lib/audio_merger.h
src/lib/dcpomatic_time.h
src/lib/decoder.cc
src/lib/decoder.h
src/lib/ffmpeg_decoder.cc
src/lib/player.cc
src/lib/player.h
src/lib/upmixer_a.cc
src/lib/upmixer_b.cc
test/audio_buffers_test.cc
test/audio_merger_test.cc
test/dcpomatic_time_test.cc
test/vf_test.cc

index 546abbb54a767a9a3a4b2d039ada7f0634792d35..f01f8baaff3c9b1ac834805875015763957a38b8 100644 (file)
@@ -200,7 +200,7 @@ AudioBuffers::copy_from (AudioBuffers const * from, int32_t frames_to_copy, int3
  */
 
 void
-AudioBuffers::move (int32_t from, int32_t to, int32_t frames)
+AudioBuffers::move (int32_t frames, int32_t from, int32_t to)
 {
        if (frames == 0) {
                return;
@@ -273,7 +273,7 @@ AudioBuffers::ensure_size (int32_t frames)
 }
 
 void
-AudioBuffers::accumulate_frames (AudioBuffers const * from, int32_t read_offset, int32_t write_offset, int32_t frames)
+AudioBuffers::accumulate_frames (AudioBuffers const * from, int32_t frames, int32_t read_offset, int32_t write_offset)
 {
        DCPOMATIC_ASSERT (_channels == from->channels ());
        DCPOMATIC_ASSERT (read_offset >= 0);
@@ -325,3 +325,19 @@ AudioBuffers::clone () const
        b->copy_from (this, frames (), 0, 0);
        return b;
 }
+
+void
+AudioBuffers::append (shared_ptr<const AudioBuffers> other)
+{
+       ensure_size (_frames + other->frames());
+       copy_from (other.get(), other->frames(), 0, _frames);
+       _frames += other->frames();
+}
+
+void
+AudioBuffers::trim_start (int32_t frames)
+{
+       DCPOMATIC_ASSERT (frames <= _frames);
+       move (_frames - frames, frames, 0);
+       set_frames (_frames - frames);
+}
index a294ff9144944c804d805ce2f7d82f8c7efc1aa8..991ef5334fb8a092658e12d815b40956f90d101a 100644 (file)
@@ -74,9 +74,11 @@ public:
 
        void copy_from (AudioBuffers const * from, int32_t frames_to_copy, int32_t read_offset, int32_t write_offset);
        void copy_channel_from (AudioBuffers const * from, int from_channel, int to_channel);
-       void move (int32_t from, int32_t to, int32_t frames);
+       void move (int32_t frames, int32_t from, int32_t to);
        void accumulate_channel (AudioBuffers const * from, int from_channel, int to_channel, float gain = 1);
-       void accumulate_frames (AudioBuffers const *, int32_t read_offset, int32_t write_offset, int32_t frames);
+       void accumulate_frames (AudioBuffers const * from, int32_t frames, int32_t read_offset, int32_t write_offset);
+       void append (boost::shared_ptr<const AudioBuffers> other);
+       void trim_start (int32_t frames);
 
 private:
        void allocate (int channels, int32_t frames);
index 893773ddd27a5304539ff3d2e0d95f59f7394cb7..391a201a25253e980931431798aa948cf09ba9b6 100644 (file)
@@ -71,7 +71,7 @@ AudioDelay::run (shared_ptr<const AudioBuffers> in)
                }
 
                /* Shuffle the tail down */
-               _tail->move (out->frames(), 0, _tail->frames() - out->frames());
+               _tail->move (_tail->frames() - out->frames(), out->frames(), 0);
 
                /* Copy input into the tail */
                _tail->copy_from (in.get(), in->frames(), 0, _tail->frames() - in->frames());
index 44345fc9d11f4e817865a402266d2b2bedff1b41..b3916d9bd86c2bb5a2a0fd0aae483d07ef936555 100644 (file)
@@ -105,7 +105,7 @@ AudioFilter::run (shared_ptr<const AudioBuffers> in)
 
        int const amount = min (in->frames(), _tail->frames());
        if (amount < _tail->frames ()) {
-               _tail->move (amount, 0, _tail->frames() - amount);
+               _tail->move (_tail->frames() - amount, amount, 0);
        }
        _tail->copy_from (in.get(), amount, in->frames() - amount, _tail->frames () - amount);
 
index 49cdea6a354ebe6c4c4b45503c3122795d2e1198..5e0589bb12941e39481e74ac104700ab7a711ce7 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2017 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
 using std::pair;
 using std::min;
 using std::max;
+using std::list;
+using std::cout;
 using std::make_pair;
 using boost::shared_ptr;
+using boost::optional;
 
-AudioMerger::AudioMerger (int channels, int frame_rate)
-       : _buffers (new AudioBuffers (channels, 0))
-       , _last_pull (0)
+AudioMerger::AudioMerger (int frame_rate)
+       : _last_pull (0)
        , _frame_rate (frame_rate)
 {
 
@@ -38,31 +40,36 @@ AudioMerger::AudioMerger (int channels, int frame_rate)
 /** Pull audio up to a given time; after this call, no more data can be pushed
  *  before the specified time.
  */
-pair<shared_ptr<AudioBuffers>, DCPTime>
+list<pair<shared_ptr<AudioBuffers>, DCPTime> >
 AudioMerger::pull (DCPTime time)
 {
-       /* Number of frames to return */
-       Frame const to_return = time.frames_floor (_frame_rate) - _last_pull.frames_floor (_frame_rate);
-       shared_ptr<AudioBuffers> out (new AudioBuffers (_buffers->channels(), to_return));
-
-       /* And this is how many we will get from our buffer */
-       Frame const to_return_from_buffers = min (to_return, Frame (_buffers->frames()));
-
-       /* Copy the data that we have to the back end of the return buffer */
-       out->copy_from (_buffers.get(), to_return_from_buffers, 0, to_return - to_return_from_buffers);
-       /* Silence any gap at the start */
-       out->make_silent (0, to_return - to_return_from_buffers);
-
-       DCPTime out_time = _last_pull;
-       _last_pull = time;
-
-       /* And remove the data we're returning from our buffers */
-       if (_buffers->frames() > to_return_from_buffers) {
-               _buffers->move (to_return_from_buffers, 0, _buffers->frames() - to_return_from_buffers);
+       list<pair<shared_ptr<AudioBuffers>, DCPTime> > out;
+
+       DCPTimePeriod period (_last_pull, time);
+       _buffers.sort (AudioMerger::BufferComparator());
+
+       list<Buffer> new_buffers;
+
+       BOOST_FOREACH (Buffer i, _buffers) {
+               if (i.period().to < time) {
+                       /* Completely within the pull period */
+                       out.push_back (make_pair (i.audio, i.time));
+               } else if (i.time < time) {
+                       /* Overlaps the end of the pull period */
+                       shared_ptr<AudioBuffers> audio (new AudioBuffers (i.audio->channels(), DCPTime(time - i.time).frames_floor(_frame_rate)));
+                       audio->copy_from (i.audio.get(), audio->frames(), 0, 0);
+                       out.push_back (make_pair (audio, i.time));
+                       i.audio->trim_start (audio->frames ());
+                       i.time += DCPTime::from_frames(audio->frames(), _frame_rate);
+                       new_buffers.push_back (i);
+               } else {
+                       /* Not involved */
+                       new_buffers.push_back (i);
+               }
        }
-       _buffers->set_frames (_buffers->frames() - to_return_from_buffers);
 
-       return make_pair (out, out_time);
+       _buffers = new_buffers;
+       return out;
 }
 
 void
@@ -70,9 +77,60 @@ AudioMerger::push (boost::shared_ptr<const AudioBuffers> audio, DCPTime time)
 {
        DCPOMATIC_ASSERT (time >= _last_pull);
 
-       Frame const frame = time.frames_floor (_frame_rate);
-       Frame after = max (Frame (_buffers->frames()), frame + audio->frames() - _last_pull.frames_floor (_frame_rate));
-       _buffers->ensure_size (after);
-       _buffers->accumulate_frames (audio.get(), 0, frame - _last_pull.frames_floor (_frame_rate), audio->frames ());
-       _buffers->set_frames (after);
+       DCPTimePeriod period (time, time + DCPTime::from_frames (audio->frames(), _frame_rate));
+
+       /* Mix any parts of this new block with existing ones */
+       BOOST_FOREACH (Buffer i, _buffers) {
+               optional<DCPTimePeriod> overlap = i.period().overlap (period);
+               if (overlap) {
+                       int32_t const offset = DCPTime(overlap->from - i.time).frames_floor(_frame_rate);
+                       int32_t const frames = overlap->duration().frames_floor(_frame_rate);
+                       if (i.time < time) {
+                               i.audio->accumulate_frames(audio.get(), frames, 0, offset);
+                       } else {
+                               i.audio->accumulate_frames(audio.get(), frames, offset, 0);
+                       }
+               }
+       }
+
+       list<DCPTimePeriod> periods;
+       BOOST_FOREACH (Buffer i, _buffers) {
+               periods.push_back (i.period ());
+       }
+
+       /* Add the non-overlapping parts */
+       BOOST_FOREACH (DCPTimePeriod i, subtract (period, periods)) {
+               list<Buffer>::iterator before = _buffers.end();
+               list<Buffer>::iterator after = _buffers.end();
+               for (list<Buffer>::iterator j = _buffers.begin(); j != _buffers.end(); ++j) {
+                       if (j->period().to == i.from) {
+                               before = j;
+                       }
+                       if (j->period().from == i.to) {
+                               after = j;
+                       }
+               }
+
+               /* Get the part of audio that we want to use */
+               shared_ptr<AudioBuffers> part (new AudioBuffers (audio->channels(), i.to.frames_floor(_frame_rate) - i.from.frames_floor(_frame_rate)));
+               part->copy_from (audio.get(), part->frames(), DCPTime(i.from - time).frames_floor(_frame_rate), 0);
+
+               if (before == _buffers.end() && after == _buffers.end()) {
+                       /* New buffer */
+                       _buffers.push_back (Buffer (part, time, _frame_rate));
+               } else if (before != _buffers.end() && after == _buffers.end()) {
+                       /* We have an existing buffer before this one; append new data to it */
+                       before->audio->append (part);
+               } else if (before ==_buffers.end() && after != _buffers.end()) {
+                       /* We have an existing buffer after this one; append it to the new data and replace */
+                       part->append (after->audio);
+                       after->audio = part;
+                       after->time = time;
+               } else {
+                       /* We have existing buffers both before and after; coalesce them all */
+                       before->audio->append (part);
+                       before->audio->append (after->audio);
+                       _buffers.erase (after);
+               }
+       }
 }
index 6db28b6c3cb2e841fa4b8996ff3c383075dda5f8..87bda7f8b2f58fa190379d85ac21cd2e1e16e0a1 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2017 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
 class AudioMerger
 {
 public:
-       AudioMerger (int channels, int frame_rate);
+       AudioMerger (int frame_rate);
 
        /** Pull audio up to a given time; after this call, no more data can be pushed
         *  before the specified time.
         */
-       std::pair<boost::shared_ptr<AudioBuffers>, DCPTime> pull (DCPTime time);
+       std::list<std::pair<boost::shared_ptr<AudioBuffers>, DCPTime> > pull (DCPTime time);
        void push (boost::shared_ptr<const AudioBuffers> audio, DCPTime time);
-       DCPTime last_pull () const {
-               return _last_pull;
-       }
 
 private:
-       boost::shared_ptr<AudioBuffers> _buffers;
+       class Buffer
+       {
+       public:
+               /** @param c Channels
+                *  @param f Frames
+                *  @param t Time
+                *  @param r Frame rate.
+                */
+               Buffer (int c, int32_t f, DCPTime t, int r)
+                       : audio (new AudioBuffers (c, f))
+                       , time (t)
+                       , frame_rate (r)
+               {}
+
+               Buffer (boost::shared_ptr<AudioBuffers> a, DCPTime t, int r)
+                       : audio (a)
+                       , time (t)
+                       , frame_rate (r)
+               {}
+
+               boost::shared_ptr<AudioBuffers> audio;
+               DCPTime time;
+               int frame_rate;
+
+               DCPTimePeriod period () const {
+                       return DCPTimePeriod (time, time + DCPTime::from_frames (audio->frames(), frame_rate));
+               }
+       };
+
+       class BufferComparator
+       {
+       public:
+               bool operator() (AudioMerger::Buffer const & a, AudioMerger::Buffer const & b)
+               {
+                       return a.time < b.time;
+               }
+       };
+
+       std::list<Buffer> _buffers;
        DCPTime _last_pull;
        int _frame_rate;
 };
index 35ddd0199e448ae65420f17ad8bfd637fe24bdc3..cc31755cb6c3e2f84d79ef62f7c20cb9640c8f3c 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2014-2016 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2014-2017 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -28,6 +28,7 @@
 #include "frame_rate_change.h"
 #include "dcpomatic_assert.h"
 #include <boost/optional.hpp>
+#include <boost/foreach.hpp>
 #include <stdint.h>
 #include <cmath>
 #include <ostream>
@@ -291,6 +292,45 @@ public:
        }
 };
 
+/** @param B Periods to subtract from `A', must be in ascending order of start time and must not overlap */
+template <class T>
+std::list<TimePeriod<T> > subtract (TimePeriod<T> A, std::list<TimePeriod<T> > const & B)
+{
+       std::list<TimePeriod<T> > result;
+       result.push_back (A);
+
+       BOOST_FOREACH (TimePeriod<T> i, B) {
+               std::list<TimePeriod<T> > new_result;
+               BOOST_FOREACH (TimePeriod<T> j, result) {
+                       boost::optional<TimePeriod<T> > ov = i.overlap (j);
+                       if (ov) {
+                               if (*ov == i) {
+                                       /* A contains all of B */
+                                       if (i.from != j.from) {
+                                               new_result.push_back (TimePeriod<T> (j.from, i.from));
+                                       }
+                                       if (i.to != j.to) {
+                                               new_result.push_back (TimePeriod<T> (i.to, j.to));
+                                       }
+                               } else if (*ov == j) {
+                                       /* B contains all of A */
+                               } else if (i.from < j.from) {
+                                       /* B overlaps start of A */
+                                       new_result.push_back (TimePeriod<T> (i.to, j.to));
+                               } else if (i.to > j.to) {
+                                       /* B overlaps end of A */
+                                       new_result.push_back (TimePeriod<T> (j.from, i.from));
+                               }
+                       } else {
+                               new_result.push_back (j);
+                       }
+               }
+               result = new_result;
+       }
+
+       return result;
+}
+
 typedef TimePeriod<ContentTime> ContentTimePeriod;
 typedef TimePeriod<DCPTime> DCPTimePeriod;
 
index ee03a1579c59bfe399927fb76a85e316fba21edb..fef5e2a99f9c64271f95986490ab9f68ab333185 100644 (file)
@@ -49,7 +49,7 @@ Decoder::position () const
 }
 
 void
-Decoder::seek (ContentTime time, bool accurate)
+Decoder::seek (ContentTime, bool)
 {
        if (audio) {
                audio->seek ();
index d87ff610ac47d3a9eae2eb795ac072a397c26b3f..8b901f70cb4169632b9eaeab3dcb99dcbdc1a0a3 100644 (file)
@@ -53,7 +53,7 @@ public:
        virtual bool pass () = 0;
        virtual void seek (ContentTime time, bool accurate);
 
-       ContentTime position () const;
+       virtual ContentTime position () const;
 };
 
 #endif
index 1bae99d6368b5b3f3031ba1450e40d2144a7eda5..604864a148ffa88fa9875381948a3309b49c5595 100644 (file)
@@ -398,7 +398,7 @@ FFmpegDecoder::decode_audio_packet ()
                        if (ct < ContentTime ()) {
                                /* Discard audio data that comes before time 0 */
                                Frame const remove = min (int64_t (data->frames()), (-ct).frames_ceil(double((*stream)->frame_rate ())));
-                               data->move (remove, 0, data->frames() - remove);
+                               data->move (data->frames() - remove, remove, 0);
                                data->set_frames (data->frames() - remove);
                                ct += ContentTime::from_frames (remove, (*stream)->frame_rate ());
                        }
index 7d53b07bfe104ce90c113958023087c1cb72d27a..c14b55be01c1cf6d900080e002fd222066fb6def 100644 (file)
@@ -87,7 +87,7 @@ Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist
        , _always_burn_subtitles (false)
        , _fast (false)
        , _play_referenced (false)
-       , _audio_merger (_film->audio_channels(), _film->audio_frame_rate())
+       , _audio_merger (_film->audio_frame_rate())
 {
        _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
        _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
@@ -156,6 +156,20 @@ Player::setup_pieces ()
                }
        }
 
+       if (!_play_referenced) {
+               BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
+                       shared_ptr<DCPContent> dc = dynamic_pointer_cast<DCPContent> (i->content);
+                       if (dc) {
+                               if (dc->reference_video()) {
+                                       _no_video.push_back (DCPTimePeriod (dc->position(), dc->end()));
+                               }
+                               if (dc->reference_audio()) {
+                                       _no_audio.push_back (DCPTimePeriod (dc->position(), dc->end()));
+                               }
+                       }
+               }
+       }
+
        _have_valid_pieces = true;
 }
 
@@ -529,31 +543,13 @@ Player::pass ()
        }
 
        if (!earliest) {
-               /* No more content; fill up to the length of our playlist with silent black */
-
-               DCPTime const length = _playlist->length ();
-
-               DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate());
-               DCPTime from;
+               /* No more content; fill up with silent black */
+               DCPTimePeriod remaining_video (DCPTime(), _playlist->length());
                if (_last_time) {
-                       from = _last_time.get() + frame;
-               }
-               for (DCPTime i = from; i < length; i += frame) {
-                       Video (black_player_video_frame (), i);
-               }
-
-               DCPTime t = _last_audio_time;
-               while (t < length) {
-                       DCPTime block = min (DCPTime::from_seconds (0.5), length - t);
-                       Frame const samples = block.frames_round(_film->audio_frame_rate());
-                       if (samples) {
-                               shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), samples));
-                               silence->make_silent ();
-                               Audio (silence, t);
-                       }
-                       t += block;
+                       remaining_video.from = _last_time.get() + one_video_frame();
                }
-
+               fill_video (remaining_video);
+               fill_audio (DCPTimePeriod (_last_audio_time, _playlist->length()));
                return true;
        }
 
@@ -568,22 +564,12 @@ Player::pass ()
                }
        }
 
-//     cout << "PULL " << to_string(pull_from) << "\n";
-       pair<shared_ptr<AudioBuffers>, DCPTime> audio = _audio_merger.pull (pull_from);
-       if (audio.first->frames() > 0) {
-               DCPOMATIC_ASSERT (audio.second >= _last_audio_time);
-               DCPTime t = _last_audio_time;
-               while (t < audio.second) {
-                       /* Silence up to the time of this new audio */
-                       DCPTime block = min (DCPTime::from_seconds (0.5), audio.second - t);
-                       shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), block.frames_round(_film->audio_frame_rate())));
-                       silence->make_silent ();
-                       Audio (silence, t);
-                       t += block;
-               }
-
-               Audio (audio.first, audio.second);
-               _last_audio_time = audio.second + DCPTime::from_frames(audio.first->frames(), _film->audio_frame_rate());
+       list<pair<shared_ptr<AudioBuffers>, DCPTime> > audio = _audio_merger.pull (pull_from);
+       for (list<pair<shared_ptr<AudioBuffers>, DCPTime> >::iterator i = audio.begin(); i != audio.end(); ++i) {
+               DCPOMATIC_ASSERT (i->second >= _last_audio_time);
+               fill_audio (DCPTimePeriod (_last_audio_time, i->second));
+               Audio (i->first, i->second);
+               _last_audio_time = i->second + DCPTime::from_frames(i->first->frames(), _film->audio_frame_rate());
        }
 
        return false;
@@ -604,7 +590,7 @@ Player::video (weak_ptr<Piece> wp, ContentVideo video)
 
        /* Time and period of the frame we will emit */
        DCPTime const time = content_video_to_dcp (piece, video.frame);
-       DCPTimePeriod const period (time, time + DCPTime::from_frames (1, _film->video_frame_rate()));
+       DCPTimePeriod const period (time, time + one_video_frame());
 
        /* Discard if it's outside the content's period */
        if (time < piece->content->position() || time >= piece->content->end()) {
@@ -641,15 +627,7 @@ Player::video (weak_ptr<Piece> wp, ContentVideo video)
        /* Fill gaps */
 
        if (_last_time) {
-               /* XXX: this may not work for 3D */
-               DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate());
-               for (DCPTime i = _last_time.get() + frame; i < time; i += frame) {
-                       if (_playlist->video_content_at(i) && _last_video) {
-                               Video (shared_ptr<PlayerVideo> (new PlayerVideo (*_last_video)), i);
-                       } else {
-                               Video (black_player_video_frame (), i);
-                       }
-               }
+               fill_video (DCPTimePeriod (_last_time.get() + one_video_frame(), time));
        }
 
        _last_video.reset (
@@ -759,7 +737,6 @@ Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_a
                content_audio.audio = _audio_processor->run (content_audio.audio, _film->audio_channels ());
        }
 
-//     cout << "PUSH " << content_audio.audio->frames() << " @ " << to_string(time) << "\n";
        _audio_merger.push (content_audio.audio, time);
 
        DCPOMATIC_ASSERT (_stream_states.find (stream) != _stream_states.end ());
@@ -852,7 +829,7 @@ Player::seek (DCPTime time, bool accurate)
        }
 
        if (accurate) {
-               _last_time = time - DCPTime::from_frames (1, _film->video_frame_rate ());
+               _last_time = time - one_video_frame ();
        } else {
                _last_time = optional<DCPTime> ();
        }
@@ -884,3 +861,42 @@ Player::resampler (shared_ptr<const AudioContent> content, AudioStreamPtr stream
        _resamplers[make_pair(content, stream)] = r;
        return r;
 }
+
+void
+Player::fill_video (DCPTimePeriod period)
+{
+       /* XXX: this may not work for 3D */
+       BOOST_FOREACH (DCPTimePeriod i, subtract(period, _no_video)) {
+               for (DCPTime j = i.from; j < i.to; j += one_video_frame()) {
+                       if (_playlist->video_content_at(j) && _last_video) {
+                               Video (shared_ptr<PlayerVideo> (new PlayerVideo (*_last_video)), j);
+                       } else {
+                               Video (black_player_video_frame(), j);
+                       }
+               }
+       }
+}
+
+void
+Player::fill_audio (DCPTimePeriod period)
+{
+       BOOST_FOREACH (DCPTimePeriod i, subtract(period, _no_audio)) {
+               DCPTime t = i.from;
+               while (t < i.to) {
+                       DCPTime block = min (DCPTime::from_seconds (0.5), i.to - t);
+                       Frame const samples = block.frames_round(_film->audio_frame_rate());
+                       if (samples) {
+                               shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), samples));
+                               silence->make_silent ();
+                               Audio (silence, t);
+                       }
+                       t += block;
+               }
+       }
+}
+
+DCPTime
+Player::one_video_frame () const
+{
+       return DCPTime::from_frames (1, _film->video_frame_rate ());
+}
index 69149d0393aefe2b8863eca73f0661fe8d34f37f..c10f7adaa21d0ed597ab4f75553acf0133d82da8 100644 (file)
@@ -107,6 +107,9 @@ private:
        void image_subtitle (boost::weak_ptr<Piece>, ContentImageSubtitle);
        void text_subtitle (boost::weak_ptr<Piece>, ContentTextSubtitle);
        boost::shared_ptr<Resampler> resampler (boost::shared_ptr<const AudioContent> content, AudioStreamPtr stream, bool create);
+       DCPTime one_video_frame () const;
+       void fill_video (DCPTimePeriod period);
+       void fill_audio (DCPTimePeriod period);
 
        boost::shared_ptr<const Film> _film;
        boost::shared_ptr<const Playlist> _playlist;
@@ -154,6 +157,9 @@ private:
        };
        std::map<AudioStreamPtr, StreamState> _stream_states;
 
+       std::list<DCPTimePeriod> _no_video;
+       std::list<DCPTimePeriod> _no_audio;
+
        std::list<std::pair<PlayerSubtitles, DCPTimePeriod> > _subtitles;
 
        boost::shared_ptr<AudioProcessor> _audio_processor;
index a1221e5ac580e78b5acc6966169458189583c628..ca42cd3867ab7e935b1713ea83d36f51a810a7c9 100644 (file)
@@ -73,7 +73,7 @@ UpmixerA::run (shared_ptr<const AudioBuffers> in, int channels)
 
        /* Mix of L and R; -6dB down in amplitude (3dB in terms of power) */
        shared_ptr<AudioBuffers> in_LR = in_L->clone ();
-       in_LR->accumulate_frames (in_R.get(), 0, 0, in_R->frames ());
+       in_LR->accumulate_frames (in_R.get(), in_R->frames(), 0, 0);
        in_LR->apply_gain (-6);
 
        /* Run filters */
index 90e1267cdb7af8021ac9ab16484ee340e24b3c28..2847da03b520341a1f557a1bab5b04285421a084 100644 (file)
@@ -68,7 +68,7 @@ UpmixerB::run (shared_ptr<const AudioBuffers> in, int channels)
 
        /* L + R minus 6dB (in terms of amplitude) */
        shared_ptr<AudioBuffers> in_LR = in->channel(0);
-       in_LR->accumulate_frames (in->channel(1).get(), 0, 0, in->frames());
+       in_LR->accumulate_frames (in->channel(1).get(), in->frames(), 0, 0);
        in_LR->apply_gain (-6);
 
        if (channels > 0) {
index 25be3fe4bcfd0671849dc58f66e5fc4feaf83785..c9231ae7f71f1eee49705cbb7f6d295e1235819b 100644 (file)
@@ -228,7 +228,7 @@ BOOST_AUTO_TEST_CASE (audio_buffers_move)
        int const to = 666;
        int const frames = 444;
 
-       buffers.move (from, to, frames);
+       buffers.move (frames, from, to);
 
        /* Re-seed and check the un-moved parts */
        srand (84);
@@ -288,7 +288,7 @@ BOOST_AUTO_TEST_CASE (audio_buffers_accumulate_frames)
        AudioBuffers b (3, 256);
        random_fill (b);
 
-       a.accumulate_frames (&b, 91, 44, 129);
+       a.accumulate_frames (&b, 129, 91, 44);
 
        srand (38);
        for (int i = 0; i < 256; ++i) {
index a03d3b30fab575ec0a161ceb1e78bcb8101abfcb..2b6cdc267b1461b6c2518115c0737ae2a2e4db35 100644 (file)
 #include <boost/bind.hpp>
 #include <boost/function.hpp>
 #include <boost/signals2.hpp>
+#include <iostream>
 
 using std::pair;
+using std::list;
+using std::cout;
 using boost::shared_ptr;
 using boost::bind;
 
@@ -33,34 +36,39 @@ static shared_ptr<const AudioBuffers> last_audio;
 
 int const sampling_rate = 48000;
 
-BOOST_AUTO_TEST_CASE (audio_merger_test1)
+static void
+push (AudioMerger& merger, int from, int to, int at)
 {
-       AudioMerger merger (1, sampling_rate);
-
-       /* Push 64 samples, 0 -> 63 at time 0 */
-       shared_ptr<AudioBuffers> buffers (new AudioBuffers (1, 64));
-       for (int i = 0; i < 64; ++i) {
-               buffers->data()[0][i] = i;
+       shared_ptr<AudioBuffers> buffers (new AudioBuffers (1, to - from));
+       for (int i = 0; i < (to - from); ++i) {
+               buffers->data()[0][i] = from + i;
        }
-       merger.push (buffers, DCPTime());
+       merger.push (buffers, DCPTime(at, sampling_rate));
+}
 
-       /* Push 64 samples, 0 -> 63 at time 22 */
-       merger.push (buffers, DCPTime::from_frames (22, sampling_rate));
+/* Basic mixing, 2 overlapping pushes */
+BOOST_AUTO_TEST_CASE (audio_merger_test1)
+{
+       AudioMerger merger (sampling_rate);
 
-       pair<shared_ptr<AudioBuffers>, DCPTime> tb = merger.pull (DCPTime::from_frames (22, sampling_rate));
-       BOOST_CHECK (tb.first != shared_ptr<const AudioBuffers> ());
-       BOOST_CHECK_EQUAL (tb.first->frames(), 22);
-       BOOST_CHECK_EQUAL (tb.second.get(), 0);
+       push (merger, 0, 64, 0);
+       push (merger, 0, 64, 22);
+
+       list<pair<shared_ptr<AudioBuffers>, DCPTime> > tb = merger.pull (DCPTime::from_frames (22, sampling_rate));
+       BOOST_REQUIRE (tb.size() == 1);
+       BOOST_CHECK (tb.front().first != shared_ptr<const AudioBuffers> ());
+       BOOST_CHECK_EQUAL (tb.front().first->frames(), 22);
+       BOOST_CHECK_EQUAL (tb.front().second.get(), 0);
 
        /* And they should be a staircase */
        for (int i = 0; i < 22; ++i) {
-               BOOST_CHECK_EQUAL (tb.first->data()[0][i], i);
+               BOOST_CHECK_EQUAL (tb.front().first->data()[0][i], i);
        }
 
        tb = merger.pull (DCPTime::from_frames (22 + 64, sampling_rate));
-
-       BOOST_CHECK_EQUAL (tb.first->frames(), 64);
-       BOOST_CHECK_EQUAL (tb.second.get(), DCPTime::from_frames(22, sampling_rate).get());
+       BOOST_REQUIRE (tb.size() == 1);
+       BOOST_CHECK_EQUAL (tb.front().first->frames(), 64);
+       BOOST_CHECK_EQUAL (tb.front().second.get(), DCPTime::from_frames(22, sampling_rate).get());
 
        /* Check the sample values */
        for (int i = 0; i < 64; ++i) {
@@ -68,36 +76,56 @@ BOOST_AUTO_TEST_CASE (audio_merger_test1)
                if (i < (64 - 22)) {
                        correct += i + 22;
                }
-               BOOST_CHECK_EQUAL (tb.first->data()[0][i], correct);
+               BOOST_CHECK_EQUAL (tb.front().first->data()[0][i], correct);
        }
 }
 
+/* Push at non-zero time */
 BOOST_AUTO_TEST_CASE (audio_merger_test2)
 {
-       AudioMerger merger (1, sampling_rate);
+       AudioMerger merger (sampling_rate);
+
+       push (merger, 0, 64, 9);
 
-       /* Push 64 samples, 0 -> 63 at time 9 */
-       shared_ptr<AudioBuffers> buffers (new AudioBuffers (1, 64));
+       /* There's nothing from 0 to 9 */
+       list<pair<shared_ptr<AudioBuffers>, DCPTime> > tb = merger.pull (DCPTime::from_frames (9, sampling_rate));
+       BOOST_CHECK_EQUAL (tb.size(), 0);
+
+       /* Then there's our data at 9 */
+       tb = merger.pull (DCPTime::from_frames (9 + 64, sampling_rate));
+
+       BOOST_CHECK_EQUAL (tb.front().first->frames(), 64);
+       BOOST_CHECK_EQUAL (tb.front().second.get(), DCPTime::from_frames(9, sampling_rate).get());
+
+       /* Check the sample values */
        for (int i = 0; i < 64; ++i) {
-               buffers->data()[0][i] = i;
+               BOOST_CHECK_EQUAL (tb.front().first->data()[0][i], i);
        }
-       merger.push (buffers, DCPTime::from_frames (9, sampling_rate));
+}
 
-       pair<shared_ptr<AudioBuffers>, DCPTime> tb = merger.pull (DCPTime::from_frames (9, sampling_rate));
-       BOOST_CHECK_EQUAL (tb.first->frames(), 9);
-       BOOST_CHECK_EQUAL (tb.second.get(), 0);
+/* Push two non contiguous blocks */
+BOOST_AUTO_TEST_CASE (audio_merger_test3)
+{
+       AudioMerger merger (sampling_rate);
 
-       for (int i = 0; i < 9; ++i) {
-               BOOST_CHECK_EQUAL (tb.first->data()[0][i], 0);
-       }
+       push (merger, 0, 64, 17);
+       push (merger, 0, 64, 114);
 
-       tb = merger.pull (DCPTime::from_frames (9 + 64, sampling_rate));
+       /* Get them back */
 
-       BOOST_CHECK_EQUAL (tb.first->frames(), 64);
-       BOOST_CHECK_EQUAL (tb.second.get(), DCPTime::from_frames(9, sampling_rate).get());
+       list<pair<shared_ptr<AudioBuffers>, DCPTime> > tb = merger.pull (DCPTime::from_frames (100, sampling_rate));
+       BOOST_REQUIRE (tb.size() == 1);
+       BOOST_CHECK_EQUAL (tb.front().first->frames(), 64);
+       BOOST_CHECK_EQUAL (tb.front().second.get(), DCPTime::from_frames(17, sampling_rate).get());
+       for (int i = 0; i < 64; ++i) {
+               BOOST_CHECK_EQUAL (tb.front().first->data()[0][i], i);
+       }
 
-       /* Check the sample values */
+       tb = merger.pull (DCPTime::from_frames (200, sampling_rate));
+       BOOST_REQUIRE (tb.size() == 1);
+       BOOST_CHECK_EQUAL (tb.front().first->frames(), 64);
+       BOOST_CHECK_EQUAL (tb.front().second.get(), DCPTime::from_frames(114, sampling_rate).get());
        for (int i = 0; i < 64; ++i) {
-               BOOST_CHECK_EQUAL (tb.first->data()[0][i], i);
+               BOOST_CHECK_EQUAL (tb.front().first->data()[0][i], i);
        }
 }
index ae03d91c0e09a3c9ec36f76d508ae7210d2876a1..7489e7a24db94a102cec10de1ab05ce6b149edd3 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2015 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2015-2017 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 
 */
 
-#include <boost/test/unit_test.hpp>
 #include "lib/dcpomatic_time.h"
+#include <boost/test/unit_test.hpp>
+#include <list>
+
+using std::list;
 
 BOOST_AUTO_TEST_CASE (dcpomatic_time_test)
 {
@@ -72,3 +75,137 @@ BOOST_AUTO_TEST_CASE (dcpomatic_time_period_overlaps_test)
        BOOST_CHECK (a.overlap(b));
        BOOST_CHECK (a.overlap(b).get() == DCPTimePeriod(DCPTime(1), DCPTime(9)));
 }
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_subtract_test1)
+{
+       DCPTimePeriod A (DCPTime (0), DCPTime (106));
+       list<DCPTimePeriod> B;
+       B.push_back (DCPTimePeriod (DCPTime (0), DCPTime (42)));
+       B.push_back (DCPTimePeriod (DCPTime (52), DCPTime (91)));
+       B.push_back (DCPTimePeriod (DCPTime (94), DCPTime (106)));
+       list<DCPTimePeriod> r = subtract (A, B);
+       list<DCPTimePeriod>::const_iterator i = r.begin ();
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (42));
+       BOOST_CHECK (i->to == DCPTime (52));
+       ++i;
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (91));
+       BOOST_CHECK (i->to == DCPTime (94));
+       ++i;
+       BOOST_REQUIRE (i == r.end ());
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_subtract_test2)
+{
+       DCPTimePeriod A (DCPTime (0), DCPTime (106));
+       list<DCPTimePeriod> B;
+       B.push_back (DCPTimePeriod (DCPTime (14), DCPTime (42)));
+       B.push_back (DCPTimePeriod (DCPTime (52), DCPTime (91)));
+       B.push_back (DCPTimePeriod (DCPTime (94), DCPTime (106)));
+       list<DCPTimePeriod> r = subtract (A, B);
+       list<DCPTimePeriod>::const_iterator i = r.begin ();
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (0));
+       BOOST_CHECK (i->to == DCPTime (14));
+       ++i;
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (42));
+       BOOST_CHECK (i->to == DCPTime (52));
+       ++i;
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (91));
+       BOOST_CHECK (i->to == DCPTime (94));
+       ++i;
+       BOOST_REQUIRE (i == r.end ());
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_subtract_test3)
+{
+       DCPTimePeriod A (DCPTime (0), DCPTime (106));
+       list<DCPTimePeriod> B;
+       B.push_back (DCPTimePeriod (DCPTime (14), DCPTime (42)));
+       B.push_back (DCPTimePeriod (DCPTime (52), DCPTime (91)));
+       B.push_back (DCPTimePeriod (DCPTime (94), DCPTime (99)));
+       list<DCPTimePeriod> r = subtract (A, B);
+       list<DCPTimePeriod>::const_iterator i = r.begin ();
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (0));
+       BOOST_CHECK (i->to == DCPTime (14));
+       ++i;
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (42));
+       BOOST_CHECK (i->to == DCPTime (52));
+       ++i;
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (91));
+       BOOST_CHECK (i->to == DCPTime (94));
+       ++i;
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (99));
+       BOOST_CHECK (i->to == DCPTime (106));
+       ++i;
+       BOOST_REQUIRE (i == r.end ());
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_subtract_test4)
+{
+       DCPTimePeriod A (DCPTime (0), DCPTime (106));
+       list<DCPTimePeriod> B;
+       list<DCPTimePeriod> r = subtract (A, B);
+       list<DCPTimePeriod>::const_iterator i = r.begin ();
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (0));
+       BOOST_CHECK (i->to == DCPTime (106));
+       ++i;
+       BOOST_REQUIRE (i == r.end ());
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_subtract_test5)
+{
+       DCPTimePeriod A (DCPTime (0), DCPTime (106));
+       list<DCPTimePeriod> B;
+       B.push_back (DCPTimePeriod (DCPTime (14), DCPTime (42)));
+       B.push_back (DCPTimePeriod (DCPTime (42), DCPTime (91)));
+       B.push_back (DCPTimePeriod (DCPTime (94), DCPTime (99)));
+       list<DCPTimePeriod> r = subtract (A, B);
+       list<DCPTimePeriod>::const_iterator i = r.begin ();
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (0));
+       BOOST_CHECK (i->to == DCPTime (14));
+       ++i;
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from ==DCPTime (91));
+       BOOST_CHECK (i->to == DCPTime (94));
+       ++i;
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (99));
+       BOOST_CHECK (i->to == DCPTime (106));
+       ++i;
+       BOOST_REQUIRE (i == r.end ());
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_subtract_test6)
+{
+       DCPTimePeriod A (DCPTime (0), DCPTime (106));
+       list<DCPTimePeriod> B;
+       B.push_back (DCPTimePeriod (DCPTime (0), DCPTime (42)));
+       B.push_back (DCPTimePeriod (DCPTime (42), DCPTime (91)));
+       B.push_back (DCPTimePeriod (DCPTime (91), DCPTime (106)));
+       list<DCPTimePeriod> r = subtract (A, B);
+       BOOST_CHECK (r.empty());
+}
+
+BOOST_AUTO_TEST_CASE (dcpomatic_time_period_subtract_test7)
+{
+       DCPTimePeriod A (DCPTime (228), DCPTime (356));
+       list<DCPTimePeriod> B;
+       B.push_back (DCPTimePeriod (DCPTime (34), DCPTime (162)));
+       list<DCPTimePeriod> r = subtract (A, B);
+       list<DCPTimePeriod>::const_iterator i = r.begin ();
+       BOOST_REQUIRE (i != r.end ());
+       BOOST_CHECK (i->from == DCPTime (228));
+       BOOST_CHECK (i->to == DCPTime (356));
+       ++i;
+       BOOST_REQUIRE (i == r.end ());
+}
index f4f8fb2a68f55e702dd34309877adf18db121ffc..539b2b9fad23fbf2fe3bed2c404f4f67601a91a7 100644 (file)
@@ -94,12 +94,14 @@ BOOST_AUTO_TEST_CASE (vf_test2)
        ov->make_dcp ();
        wait_for_jobs ();
 
+       std::cout << "incoming vf.\n";
+
        /* Make the VF */
        shared_ptr<Film> vf = new_test_film ("vf_test2_vf");
        vf->set_name ("vf_test2_vf");
        vf->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
        vf->set_reel_type (REELTYPE_BY_VIDEO_CONTENT);
-       shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (content_factory (vf, ov->dir (ov->dcp_name ())).front());
+       shared_ptr<DCPContent> dcp (new DCPContent (vf, ov->dir (ov->dcp_name ())));
        BOOST_REQUIRE (dcp);
        vf->examine_and_add_content (dcp);
        wait_for_jobs ();
@@ -156,7 +158,7 @@ BOOST_AUTO_TEST_CASE (vf_test3)
        vf->set_name ("vf_test3_vf");
        vf->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
        vf->set_reel_type (REELTYPE_BY_VIDEO_CONTENT);
-       shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (content_factory(vf, ov->dir (ov->dcp_name ())).front());
+       shared_ptr<DCPContent> dcp (new DCPContent (vf, ov->dir (ov->dcp_name ())));
        BOOST_REQUIRE (dcp);
        dcp->set_trim_start (ContentTime::from_seconds (1));
        dcp->set_trim_end (ContentTime::from_seconds (1));