Add test for audio delay, and do it in the player rather than the decoder.
authorCarl Hetherington <cth@carlh.net>
Thu, 11 Jul 2013 14:50:18 +0000 (15:50 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 11 Jul 2013 14:50:18 +0000 (15:50 +0100)
src/lib/audio_buffers.cc
src/lib/audio_buffers.h
src/lib/audio_decoder.cc
src/lib/audio_decoder.h
src/lib/player.cc
test/audio_delay_test.cc [new file with mode: 0644]
test/test.cc

index 403babaf7f84bd4b0ff0974d67a4fb5471b93234..6d4eb85147545fab2a3610f86d285e944dd275b3 100644 (file)
@@ -144,6 +144,18 @@ AudioBuffers::make_silent (int c)
        }
 }
 
+void
+AudioBuffers::make_silent (int from, int frames)
+{
+       assert ((from + frames) <= _allocated_frames);
+
+       for (int c = 0; c < _channels; ++c) {
+               for (int i = from; i < (from + frames); ++i) {
+                       _data[c][i] = 0;
+               }
+       }
+}
+
 /** Copy data from another AudioBuffers to this one.  All channels are copied.
  *  @param from AudioBuffers to copy from; must have the same number of channels as this.
  *  @param frames_to_copy Number of frames to copy.
index 47b8145a1de2892e6126ca3cea8738a034514eff..b450b83ec603f4ffd2f9e8ae578f0488cbf9972b 100644 (file)
@@ -50,6 +50,7 @@ public:
 
        void make_silent ();
        void make_silent (int c);
+       void make_silent (int from, int frames);
 
        void copy_from (AudioBuffers const * from, int frames_to_copy, int read_offset, int write_offset);
        void move (int from, int to, int frames);
index 2fe347cf2a50af794fbd261ad9587d31ed35b574..3b97cc16f2b47b2a26eac06338c689d7b90180eb 100644 (file)
@@ -35,17 +35,12 @@ AudioDecoder::AudioDecoder (shared_ptr<const Film> f, shared_ptr<const AudioCont
        : Decoder (f)
        , _audio_position (0)
 {
-       _delay_frames = c->audio_delay() * c->content_audio_frame_rate() * f->dcp_audio_frame_rate() / (c->output_audio_frame_rate() * 1000);
+
 }
 
 void
 AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
 {
-       frame += _delay_frames;
-       
        Audio (data, frame);
        _audio_position = frame + data->frames ();
-       if (_audio_position < 0) {
-               _audio_position = 0;
-       }
 }
index a7849b9cc93a180110a4b7e902ecb08572369d2f..d3f12ab846e2406ebe051536f3e18f3d0edfa486 100644 (file)
@@ -44,9 +44,6 @@ protected:
 
        void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
        AudioContent::Frame _audio_position;
-
-private:
-       AudioContent::Frame _delay_frames;
 };
 
 #endif
index 18c42296f5f2b92964e15ce020b82813b979da60..6db7ff693e1adaf76c87cdbb8de7e09338eccd8c 100644 (file)
@@ -260,6 +260,7 @@ Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers
        shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
        assert (content);
 
+       /* Resample */
        if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
                shared_ptr<Resampler> r = resampler (content);
                audio = r->run (audio);
@@ -278,15 +279,40 @@ Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers
 
        audio = dcp_mapped;
 
+       Time time = content->start() + (frame * TIME_HZ / _film->dcp_audio_frame_rate()) + (content->audio_delay() * TIME_HZ / 1000);
+
+       /* We must cut off anything that comes before the start of all time */
+       if (time < 0) {
+               int const frames = - time * _film->dcp_audio_frame_rate() / TIME_HZ;
+               if (frames >= audio->frames ()) {
+                       return;
+               }
+
+               shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
+               trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
+
+               audio = trimmed;
+               time = 0;
+       }
+
         /* The time of this audio may indicate that some of our buffered audio is not going to
            be added to any more, so it can be emitted.
         */
 
-       Time const time = content->start() + (frame * TIME_HZ / _film->dcp_audio_frame_rate());
-
         if (time > _audio_position) {
                 /* We can emit some audio from our buffers */
                 OutputAudioFrame const N = _film->time_to_audio_frames (time - _audio_position);
+               if (N > _audio_buffers.frames()) {
+                       /* We need some extra silence before whatever is in the buffers */
+                       _audio_buffers.ensure_size (N);
+                       _audio_buffers.move (0, N - _audio_buffers.frames(), _audio_buffers.frames ());
+                       _audio_buffers.make_silent (0, _audio_buffers.frames());
+                       _audio_buffers.set_frames (N);
+               }
+                       
+               if (N > _audio_buffers.frames()) {
+                       cout << "N=" << N << ", ab=" << _audio_buffers.frames() << "\n";
+               }
                assert (N <= _audio_buffers.frames());
                 shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), N));
                 emit->copy_from (&_audio_buffers, N, 0, 0);
@@ -318,9 +344,9 @@ Player::flush ()
        }
 
        if (_last_resampler) {
-               shared_ptr<const AudioBuffers> resamp = _last_resampler->flush ();
-               Audio (resamp, _audio_position);
-               _audio_position += _film->audio_frames_to_time (resamp->frames ());
+//             shared_ptr<const AudioBuffers> resamp = _last_resampler->flush ();
+//             Audio (resamp, _audio_position);
+//             _audio_position += _film->audio_frames_to_time (resamp->frames ());
        }
        
        while (_video_position < _audio_position) {
diff --git a/test/audio_delay_test.cc b/test/audio_delay_test.cc
new file mode 100644 (file)
index 0000000..5574bd6
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <libdcp/sound_frame.h>
+#include <libdcp/cpl.h>
+#include <libdcp/reel.h>
+#include <libdcp/sound_asset.h>
+#include "sndfile_content.h"
+
+using boost::lexical_cast;
+
+static
+void test_audio_delay (int delay_in_ms)
+{
+       string const film_name = "audio_delay_test_" + lexical_cast<string> (delay_in_ms);
+       shared_ptr<Film> film = new_test_film (film_name);
+       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_container (Ratio::from_id ("185"));
+       film->set_name (film_name);
+
+       shared_ptr<SndfileContent> content (new SndfileContent (film, "test/data/staircase.wav"));
+       content->set_audio_delay (delay_in_ms);
+       film->examine_and_add_content (content);
+       wait_for_jobs ();
+
+       film->make_dcp ();
+       wait_for_jobs ();
+
+       boost::filesystem::path path = "build/test";
+       path /= film_name;
+       path /= film->dcp_name ();
+       libdcp::DCP check (path.string ());
+       check.read ();
+
+       shared_ptr<const libdcp::SoundAsset> sound_asset = check.cpls().front()->reels().front()->main_sound ();
+       BOOST_CHECK (sound_asset);
+
+       /* Sample index in the DCP */
+       int n = 0;
+       /* DCP sound asset frame */
+       int frame = 0;
+       /* Delay in frames */
+       int const delay_in_frames = delay_in_ms * 48000 / 1000;
+       bool done = false;
+
+       while (n < sound_asset->intrinsic_duration()) {
+               shared_ptr<const libdcp::SoundFrame> sound_frame = sound_asset->get_frame (frame++);
+               uint8_t const * d = sound_frame->data ();
+               
+               for (int i = 0; i < sound_frame->size(); i += (3 * sound_asset->channels())) {
+
+                       /* Mono input so it will appear on centre */
+                       int const sample = d[i + 7] | (d[i + 8] << 8);
+
+                       int delayed = n - delay_in_frames;
+                       if (delayed < 0 || delayed >= 4800) {
+                               delayed = 0;
+                       }
+
+                       BOOST_CHECK_EQUAL (sample, delayed);
+                       ++n;
+               }
+       }
+}
+
+
+/* Test audio delay when specified in a piece of audio content */
+BOOST_AUTO_TEST_CASE (audio_delay_test)
+{
+       test_audio_delay (0);
+       test_audio_delay (42);
+       test_audio_delay (-66);
+}
index 0a682383a2aea7139b0aca6f044a89583a86e1e5..dfa38c8f4a488b3e179d5cf21734ec043187d356 100644 (file)
@@ -75,7 +75,7 @@ struct TestConfig
 
 BOOST_GLOBAL_FIXTURE (TestConfig);
 
-boost::filesystem::path
+static boost::filesystem::path
 test_film_dir (string name)
 {
        boost::filesystem::path p;
@@ -85,7 +85,7 @@ test_film_dir (string name)
        return p;
 }
 
-shared_ptr<Film>
+static shared_ptr<Film>
 new_test_film (string name)
 {
        boost::filesystem::path p = test_film_dir (name);
@@ -98,7 +98,7 @@ new_test_film (string name)
        return f;
 }
 
-void
+static void
 check_file (string ref, string check)
 {
        uintmax_t N = boost::filesystem::file_size (ref);
@@ -136,7 +136,7 @@ note (libdcp::NoteType, string n)
        cout << n << "\n";
 }
 
-void
+static void
 check_dcp (string ref, string check)
 {
        libdcp::DCP ref_dcp (ref);
@@ -154,6 +154,13 @@ check_dcp (string ref, string check)
        BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2)));
 }
 
+static void
+wait_for_jobs ()
+{
+       while (JobManager::instance()->work_to_do ()) {}
+}
+
+#include "audio_delay_test.cc"
 #include "ffmpeg_pts_offset.cc"
 #include "ffmpeg_examiner_test.cc"
 #include "black_fill_test.cc"