Extract audio merging code from Player.
authorCarl Hetherington <cth@carlh.net>
Sat, 27 Jul 2013 17:25:11 +0000 (18:25 +0100)
committerCarl Hetherington <cth@carlh.net>
Sat, 27 Jul 2013 17:25:11 +0000 (18:25 +0100)
src/lib/audio_buffers.h
src/lib/audio_merger.h [new file with mode: 0644]
src/lib/player.cc
src/lib/player.h
test/audio_merger_test.cc [new file with mode: 0644]
test/wscript

index 6b57bd142ff5bf674f75c78f2bf987ce7c0cc1a8..0950f5d6775abdb15584b7af6e652aef1245a095 100644 (file)
@@ -17,6 +17,9 @@
 
 */
 
+#ifndef DVDOMATIC_AUDIO_BUFFERS_H
+#define DVDOMATIC_AUDIO_BUFFERS_H
+
 #include <boost/shared_ptr.hpp>
 
 /** @class AudioBuffers
@@ -72,3 +75,5 @@ private:
        /** Audio data (so that, e.g. _data[2][6] is channel 2, sample 6) */
        float** _data;
 };
+
+#endif
diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h
new file mode 100644 (file)
index 0000000..126325a
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+    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 "audio_buffers.h"
+
+template <class T, class F>
+class AudioMerger
+{
+public:
+       AudioMerger (int channels, boost::function<F (T)> t_to_f, boost::function<T (F)> f_to_t)
+               : _buffers (new AudioBuffers (channels, 0))
+               , _next_emission (0)
+               , _t_to_f (t_to_f)
+               , _f_to_t (f_to_t)
+       {}
+
+       void push (boost::shared_ptr<const AudioBuffers> audio, T time)
+       {
+               if (time > _next_emission) {
+                       /* We can emit some audio from our buffer; this is how many frames
+                          we are going to emit.
+                       */
+                       F const to_emit = _t_to_f (time - _next_emission);
+                       boost::shared_ptr<AudioBuffers> emit (new AudioBuffers (_buffers->channels(), to_emit));
+
+                       /* And this is how many we will get from our buffer */
+                       F const to_emit_from_buffers = min (to_emit, _buffers->frames ());
+
+                       /* Copy the data that we have to the back end of `emit' */
+                       emit->copy_from (_buffers.get(), to_emit_from_buffers, 0, to_emit - to_emit_from_buffers);
+
+                       /* Silence any gap at the start */
+                       emit->make_silent (0, to_emit - to_emit_from_buffers);
+
+                       /* Emit that */
+                       Audio (emit, _next_emission);
+
+                       _next_emission += _f_to_t (to_emit);
+
+                       /* And remove the data we've emitted from our buffers */
+                       if (_buffers->frames() > to_emit_from_buffers) {
+                               _buffers->move (to_emit_from_buffers, 0, _buffers->frames() - to_emit_from_buffers);
+                       }
+                       _buffers->set_frames (_buffers->frames() - to_emit_from_buffers);
+               }
+
+               /* Now accumulate the new audio into our buffers */
+               F frame = _t_to_f (time);
+               F after = max (_buffers->frames(), frame + audio->frames() - _t_to_f (_next_emission));
+               _buffers->ensure_size (after);
+               _buffers->accumulate_frames (audio.get(), 0, frame - _t_to_f (_next_emission), audio->frames ());
+               _buffers->set_frames (after);
+       }
+
+       F min (F a, int b)
+       {
+               if (a < b) {
+                       return a;
+               }
+
+               return b;
+       }
+
+       F max (int a, F b)
+       {
+               if (a > b) {
+                       return a;
+               }
+
+               return b;
+       }
+               
+       void flush ()
+       {
+               if (_buffers->frames() > 0) {
+                       Audio (_buffers, _next_emission);
+               }
+       }
+       
+       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, T)> Audio;
+
+private:
+       boost::shared_ptr<AudioBuffers> _buffers;
+       T _next_emission;
+       boost::function<F (T)> _t_to_f;
+       boost::function<T (F)> _f_to_t;
+};
index 647095793b9ac7e2874c8e3169d28f2de376c4c6..dbc78b8d02593f5abb90e925b057499c1dd0ede2 100644 (file)
@@ -97,11 +97,12 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
        , _have_valid_pieces (false)
        , _video_position (0)
        , _audio_position (0)
-       , _audio_buffers (f->audio_channels(), 0)
+       , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
 {
        _playlist->Changed.connect (bind (&Player::playlist_changed, this));
        _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
        _film->Changed.connect (bind (&Player::film_changed, this, _1));
+       _audio_merger.Audio.connect (bind (&Player::merger_process_audio, this, _1, _2));
        set_video_container_size (_film->container()->size (_film->full_frame ()));
 }
 
@@ -318,52 +319,20 @@ Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers
                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.
-       */
-
-       if (time > _audio_position) {
-               /* We can emit some audio from our buffers; this is how many frames */
-               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);
-               }
-               assert (N <= _audio_buffers.frames());
-
-               /* XXX: not convinced that a copy is necessary here */
-               shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), N));
-               emit->copy_from (&_audio_buffers, N, 0, 0);
-               Audio (emit, _audio_position);
-
-               _audio_position = piece->audio_position = _audio_position + _film->audio_frames_to_time (N);
-
-               /* And remove it from our buffers */
-               if (_audio_buffers.frames() > N) {
-                       _audio_buffers.move (N, 0, _audio_buffers.frames() - N);
-               }
-               _audio_buffers.set_frames (_audio_buffers.frames() - N);
-       }
+       _audio_merger.push (audio, time);
+}
 
-       /* Now accumulate the new audio into our buffers */
-       _audio_buffers.ensure_size (_audio_buffers.frames() + audio->frames());
-       _audio_buffers.accumulate_frames (audio.get(), 0, 0, audio->frames ());
-       _audio_buffers.set_frames (_audio_buffers.frames() + audio->frames());
+void
+Player::merger_process_audio (shared_ptr<const AudioBuffers> audio, Time time)
+{
+       Audio (audio, time);
+       _audio_position += _film->audio_frames_to_time (audio->frames ());
 }
 
 void
 Player::flush ()
 {
-       if (_audio_buffers.frames() > 0) {
-               shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), _audio_buffers.frames()));
-               emit->copy_from (&_audio_buffers, _audio_buffers.frames(), 0, 0);
-               Audio (emit, _audio_position);
-               _audio_position += _film->audio_frames_to_time (_audio_buffers.frames ());
-               _audio_buffers.set_frames (0);
-       }
+       _audio_merger.flush ();
 
        while (_video_position < _audio_position) {
                emit_black ();
index cd480dd1a78c659a6a16479e6d95759d4d0858a3..206254713cac368f8e26db375e44e20e48f9427d 100644 (file)
 #include <boost/shared_ptr.hpp>
 #include <boost/enable_shared_from_this.hpp>
 #include "playlist.h"
-#include "audio_buffers.h"
 #include "content.h"
 #include "film.h"
 #include "rect.h"
+#include "audio_merger.h"
+#include "audio_content.h"
 
 class Job;
 class Film;
@@ -93,6 +94,7 @@ private:
        boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool);
        void film_changed (Film::Property);
        void update_subtitle ();
+       void merger_process_audio (boost::shared_ptr<const AudioBuffers>, Time);
 
        boost::shared_ptr<const Film> _film;
        boost::shared_ptr<const Playlist> _playlist;
@@ -109,7 +111,7 @@ private:
        /** The time after the last audio that we emitted */
        Time _audio_position;
 
-       AudioBuffers _audio_buffers;
+       AudioMerger<Time, AudioContent::Frame> _audio_merger;
 
        libdcp::Size _video_container_size;
        boost::shared_ptr<Image> _black_frame;
diff --git a/test/audio_merger_test.cc b/test/audio_merger_test.cc
new file mode 100644 (file)
index 0000000..9c3fd3a
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+    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 <boost/test/unit_test.hpp>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/signals2.hpp>
+#include "lib/audio_merger.h"
+#include "lib/audio_buffers.h"
+
+using boost::shared_ptr;
+using boost::bind;
+
+static shared_ptr<const AudioBuffers> last_audio;
+static int last_time;
+
+static int
+pass_through (int x)
+{
+       return x;
+}
+
+static void
+process_audio (shared_ptr<const AudioBuffers> audio, int time)
+{
+       last_audio = audio;
+       last_time = time;
+}
+
+static void
+reset ()
+{
+       last_audio.reset ();
+       last_time = 0;
+}
+
+BOOST_AUTO_TEST_CASE (audio_merger_test1)
+{
+       AudioMerger<int, int> merger (1, bind (&pass_through, _1), boost::bind (&pass_through, _1));
+       merger.Audio.connect (bind (&process_audio, _1, _2));
+
+       reset ();
+       
+       /* 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;
+       }
+       merger.push (buffers, 0);
+
+       /* That should not have caused an emission */
+       BOOST_CHECK_EQUAL (last_audio, shared_ptr<const AudioBuffers> ());
+       BOOST_CHECK_EQUAL (last_time, 0);
+
+       /* Push 64 samples, 0 -> 63 at time 22 */
+       merger.push (buffers, 22);
+
+       /* That should have caused an emission of 22 samples at 0 */
+       BOOST_CHECK (last_audio != shared_ptr<const AudioBuffers> ());
+       BOOST_CHECK_EQUAL (last_audio->frames(), 22);
+       BOOST_CHECK_EQUAL (last_time, 0);
+
+       /* And they should be a staircase */
+       for (int i = 0; i < 22; ++i) {
+               BOOST_CHECK_EQUAL (last_audio->data()[0][i], i);
+       }
+
+       reset ();
+       merger.flush ();
+
+       /* That flush should give us 64 samples at 22 */
+       BOOST_CHECK_EQUAL (last_audio->frames(), 64);
+       BOOST_CHECK_EQUAL (last_time, 22);
+
+       /* Check the sample values */
+       for (int i = 0; i < 64; ++i) {
+               int correct = i;
+               if (i < (64 - 22)) {
+                       correct += i + 22;
+               }
+               BOOST_CHECK_EQUAL (last_audio->data()[0][i], correct);
+       }
+}
+
+BOOST_AUTO_TEST_CASE (audio_merger_test2)
+{
+       AudioMerger<int, int> merger (1, bind (&pass_through, _1), boost::bind (&pass_through, _1));
+       merger.Audio.connect (bind (&process_audio, _1, _2));
+
+       reset ();
+       
+       /* Push 64 samples, 0 -> 63 at time 9 */
+       shared_ptr<AudioBuffers> buffers (new AudioBuffers (1, 64));
+       for (int i = 0; i < 64; ++i) {
+               buffers->data()[0][i] = i;
+       }
+       merger.push (buffers, 9);
+
+       /* That flush should give us 9 samples at 0 */
+       BOOST_CHECK_EQUAL (last_audio->frames(), 9);
+       BOOST_CHECK_EQUAL (last_time, 0);
+       
+       for (int i = 0; i < 9; ++i) {
+               BOOST_CHECK_EQUAL (last_audio->data()[0][i], 0);
+       }
+       
+       reset ();
+       merger.flush ();
+
+       /* That flush should give us 64 samples at 9 */
+       BOOST_CHECK_EQUAL (last_audio->frames(), 64);
+       BOOST_CHECK_EQUAL (last_time, 9);
+       
+       /* Check the sample values */
+       for (int i = 0; i < 64; ++i) {
+               BOOST_CHECK_EQUAL (last_audio->data()[0][i], i);
+       }
+}
index fef1584f32c49e01045594bf44c54e66b16e2ab7..0fcb185f841a340eeeb4459ccacda813d681bea3 100644 (file)
@@ -16,12 +16,13 @@ def build(bld):
     obj.use    = 'libdcpomatic'
     obj.source = """
                  test.cc
+                 silence_padding_test.cc
+                 audio_merger_test.cc
                  resampler_test.cc
                  ffmpeg_audio_test.cc
                  threed_test.cc
                  play_test.cc
                  frame_rate_test.cc
-                 silence_padding_test.cc
                  audio_delay_test.cc
                  ffmpeg_pts_offset.cc
                  ffmpeg_examiner_test.cc