Handle multiple audio streams in a single piece of content
authorCarl Hetherington <cth@carlh.net>
Wed, 27 May 2015 19:55:51 +0000 (20:55 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 2 Jun 2015 12:38:21 +0000 (13:38 +0100)
in a similar way to the V1 patch.

55 files changed:
doc/design/decoder_structures.tex
run/tests
src/lib/audio_content.cc
src/lib/audio_content.h
src/lib/audio_decoder.cc
src/lib/audio_decoder.h
src/lib/audio_decoder_stream.cc [new file with mode: 0644]
src/lib/audio_decoder_stream.h [new file with mode: 0644]
src/lib/audio_examiner.h
src/lib/audio_mapping.cc
src/lib/audio_mapping.h
src/lib/audio_stream.cc [new file with mode: 0644]
src/lib/audio_stream.h [new file with mode: 0644]
src/lib/content.cc
src/lib/content_audio.h
src/lib/dcp_content.cc
src/lib/dcp_decoder.cc
src/lib/dcpomatic_time.h
src/lib/decoder.h
src/lib/ffmpeg.cc
src/lib/ffmpeg.h
src/lib/ffmpeg_audio_stream.cc
src/lib/ffmpeg_audio_stream.h
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_decoder.h
src/lib/film.cc
src/lib/player.cc
src/lib/single_stream_audio_content.cc
src/lib/single_stream_audio_content.h
src/lib/sndfile_base.cc [new file with mode: 0644]
src/lib/sndfile_base.h [new file with mode: 0644]
src/lib/sndfile_content.cc
src/lib/sndfile_content.h
src/lib/sndfile_decoder.cc
src/lib/sndfile_decoder.h
src/lib/sndfile_examiner.cc [new file with mode: 0644]
src/lib/sndfile_examiner.h [new file with mode: 0644]
src/lib/wscript
src/wx/audio_dialog.cc
src/wx/audio_panel.cc
src/wx/audio_panel.h
src/wx/content_properties_dialog.cc
src/wx/dcp_panel.cc
src/wx/timeline.cc
test/audio_decoder_test.cc
test/audio_delay_test.cc
test/ffmpeg_audio_test.cc
test/ffmpeg_decoder_seek_test.cc
test/ffmpeg_decoder_sequential_test.cc
test/ffmpeg_pts_offset_test.cc
test/frame_rate_test.cc
test/seek_zero_test.cc
test/upmixer_a_test.cc

index 1f7ae0ec48dcef5cb9ff6f5a7338e10639c95dde..588b33695ac2a24300d87c074c3f7da0cebe9ff2 100644 (file)
@@ -28,6 +28,7 @@ This suggests that decode-and-see is a better match, even if it feels
 a bit ridiculous when most of the decoders have slightly clunky seek
 and pass methods.
 
 a bit ridiculous when most of the decoders have slightly clunky seek
 and pass methods.
 
+
 \section{Multiple streams}
 
 Another thing unique to FFmpeg is multiple audio streams, possibly at
 \section{Multiple streams}
 
 Another thing unique to FFmpeg is multiple audio streams, possibly at
@@ -50,4 +51,8 @@ and the merging of all audio content inside the player.
 
 These disadvantages suggest that the first approach is better.
 
 
 These disadvantages suggest that the first approach is better.
 
+One might think that the logical conclusion is to take streams all the
+way back to the player and resample them there, but the resampling
+must occur on the other side of the get-stuff-at-time API.
+
 \end{document}
 \end{document}
index e9c02589375a47d2a60d291390b890153b73ada6..7993facf9fefdd56e8727f6f0bd7f0e7ac39735e 100755 (executable)
--- a/run/tests
+++ b/run/tests
@@ -11,6 +11,6 @@ elif [ "$1" == "--valgrind" ]; then
     shift;
     valgrind --tool="memcheck" --leak-check=full build/test/unit-tests $*
 else
     shift;
     valgrind --tool="memcheck" --leak-check=full build/test/unit-tests $*
 else
-    build/test/unit-tests --catch_system_errors=no $*
+    build/test/unit-tests --catch_system_errors=no --log_level=test_suite $*
 fi
 
 fi
 
index e4b5a804e98a448d94975a538ea6c176f30a3c1b..3ea31f673457f4200642f29ab8640c04ce240512 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
-    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2015 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
 
     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
@@ -24,9 +24,9 @@
 #include "exceptions.h"
 #include "config.h"
 #include "frame_rate_change.h"
 #include "exceptions.h"
 #include "config.h"
 #include "frame_rate_change.h"
-#include "audio_processor.h"
 #include "raw_convert.h"
 #include <libcxml/cxml.h>
 #include "raw_convert.h"
 #include <libcxml/cxml.h>
+#include <boost/foreach.hpp>
 
 #include "i18n.h"
 
 
 #include "i18n.h"
 
@@ -38,19 +38,17 @@ using std::fixed;
 using std::setprecision;
 using boost::shared_ptr;
 using boost::dynamic_pointer_cast;
 using std::setprecision;
 using boost::shared_ptr;
 using boost::dynamic_pointer_cast;
+using boost::optional;
 
 
-int const AudioContentProperty::AUDIO_CHANNELS = 200;
-int const AudioContentProperty::AUDIO_FRAME_RATE = 201;
-int const AudioContentProperty::AUDIO_GAIN = 202;
-int const AudioContentProperty::AUDIO_DELAY = 203;
-int const AudioContentProperty::AUDIO_MAPPING = 204;
-int const AudioContentProperty::AUDIO_PROCESSOR = 205;
+/** Something stream-related has changed */
+int const AudioContentProperty::AUDIO_STREAMS = 200;
+int const AudioContentProperty::AUDIO_GAIN = 201;
+int const AudioContentProperty::AUDIO_DELAY = 202;
 
 AudioContent::AudioContent (shared_ptr<const Film> f)
        : Content (f)
        , _audio_gain (0)
        , _audio_delay (Config::instance()->default_audio_delay ())
 
 AudioContent::AudioContent (shared_ptr<const Film> f)
        : Content (f)
        , _audio_gain (0)
        , _audio_delay (Config::instance()->default_audio_delay ())
-       , _audio_processor (0)
 {
 
 }
 {
 
 }
@@ -59,7 +57,6 @@ AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s)
        : Content (f, s)
        , _audio_gain (0)
        , _audio_delay (Config::instance()->default_audio_delay ())
        : Content (f, s)
        , _audio_gain (0)
        , _audio_delay (Config::instance()->default_audio_delay ())
-       , _audio_processor (0)
 {
 
 }
 {
 
 }
@@ -68,20 +65,15 @@ AudioContent::AudioContent (shared_ptr<const Film> f, boost::filesystem::path p)
        : Content (f, p)
        , _audio_gain (0)
        , _audio_delay (Config::instance()->default_audio_delay ())
        : Content (f, p)
        , _audio_gain (0)
        , _audio_delay (Config::instance()->default_audio_delay ())
-       , _audio_processor (0)
 {
 
 }
 
 AudioContent::AudioContent (shared_ptr<const Film> f, cxml::ConstNodePtr node)
        : Content (f, node)
 {
 
 }
 
 AudioContent::AudioContent (shared_ptr<const Film> f, cxml::ConstNodePtr node)
        : Content (f, node)
-       , _audio_processor (0)
 {
        _audio_gain = node->number_child<float> ("AudioGain");
        _audio_delay = node->number_child<int> ("AudioDelay");
 {
        _audio_gain = node->number_child<float> ("AudioGain");
        _audio_delay = node->number_child<int> ("AudioDelay");
-       if (node->optional_string_child ("AudioProcessor")) {
-               _audio_processor = AudioProcessor::from_id (node->string_child ("AudioProcessor"));
-       }
 }
 
 AudioContent::AudioContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
 }
 
 AudioContent::AudioContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
@@ -104,7 +96,6 @@ AudioContent::AudioContent (shared_ptr<const Film> f, vector<shared_ptr<Content>
 
        _audio_gain = ref->audio_gain ();
        _audio_delay = ref->audio_delay ();
 
        _audio_gain = ref->audio_gain ();
        _audio_delay = ref->audio_delay ();
-       _audio_processor = ref->audio_processor ();
 }
 
 void
 }
 
 void
@@ -113,9 +104,6 @@ AudioContent::as_xml (xmlpp::Node* node) const
        boost::mutex::scoped_lock lm (_mutex);
        node->add_child("AudioGain")->add_child_text (raw_convert<string> (_audio_gain));
        node->add_child("AudioDelay")->add_child_text (raw_convert<string> (_audio_delay));
        boost::mutex::scoped_lock lm (_mutex);
        node->add_child("AudioGain")->add_child_text (raw_convert<string> (_audio_gain));
        node->add_child("AudioDelay")->add_child_text (raw_convert<string> (_audio_delay));
-       if (_audio_processor) {
-               node->add_child("AudioProcessor")->add_child_text (_audio_processor->id ());
-       }
 }
 
 
 }
 
 
@@ -141,22 +129,6 @@ AudioContent::set_audio_delay (int d)
        signal_changed (AudioContentProperty::AUDIO_DELAY);
 }
 
        signal_changed (AudioContentProperty::AUDIO_DELAY);
 }
 
-void
-AudioContent::set_audio_processor (AudioProcessor const * p)
-{
-       {
-               boost::mutex::scoped_lock lm (_mutex);
-               _audio_processor = p;
-       }
-
-       /* The channel count might have changed, so reset the mapping */
-       AudioMapping m (processed_audio_channels ());
-       m.make_default ();
-       set_audio_mapping (m);
-
-       signal_changed (AudioContentProperty::AUDIO_PROCESSOR);
-}
-
 boost::signals2::connection
 AudioContent::analyse_audio (boost::function<void()> finished)
 {
 boost::signals2::connection
 AudioContent::analyse_audio (boost::function<void()> finished)
 {
@@ -186,18 +158,57 @@ AudioContent::audio_analysis_path () const
 string
 AudioContent::technical_summary () const
 {
 string
 AudioContent::technical_summary () const
 {
-       return String::compose (
-               "audio: channels %1, content rate %2, resampled rate %3",
-               audio_channels(),
-               audio_frame_rate(),
-               resampled_audio_frame_rate()
-               );
+       string s = "audio :";
+       BOOST_FOREACH (AudioStreamPtr i, audio_streams ()) {
+               s += String::compose ("stream channels %1 rate %2", i->channels(), i->frame_rate());
+       }
+
+       return s;
 }
 
 void
 }
 
 void
-AudioContent::set_audio_mapping (AudioMapping)
+AudioContent::set_audio_mapping (AudioMapping mapping)
+{
+       int c = 0;
+       BOOST_FOREACH (AudioStreamPtr i, audio_streams ()) {
+               AudioMapping stream_mapping (i->channels ());
+               for (int j = 0; j < i->channels(); ++j) {
+                       for (int k = 0; k < MAX_DCP_AUDIO_CHANNELS; ++k) {
+                               stream_mapping.set (j, static_cast<dcp::Channel> (k), mapping.get (c, static_cast<dcp::Channel> (k)));
+                       }
+                       ++c;
+               }
+               i->set_mapping (stream_mapping);
+       }
+               
+       signal_changed (AudioContentProperty::AUDIO_STREAMS);
+}
+
+AudioMapping
+AudioContent::audio_mapping () const
 {
 {
-       signal_changed (AudioContentProperty::AUDIO_MAPPING);
+       int channels = 0;
+       BOOST_FOREACH (AudioStreamPtr i, audio_streams ()) {
+               channels += i->channels ();
+       }
+       
+       AudioMapping merged (channels);
+       
+       int c = 0;
+       int s = 0;
+       BOOST_FOREACH (AudioStreamPtr i, audio_streams ()) {
+               AudioMapping mapping = i->mapping ();
+               for (int j = 0; j < mapping.content_channels(); ++j) {
+                       merged.set_name (c, String::compose ("%1:%2", s + 1, j + 1));
+                       for (int k = 0; k < MAX_DCP_AUDIO_CHANNELS; ++k) {
+                               merged.set (c, static_cast<dcp::Channel> (k), mapping.get (j, static_cast<dcp::Channel> (k)));
+                       }
+                       ++c;
+               }
+               ++s;
+       }
+
+       return merged;
 }
 
 /** @return the frame rate that this content should be resampled to in order
 }
 
 /** @return the frame rate that this content should be resampled to in order
@@ -210,7 +221,7 @@ AudioContent::resampled_audio_frame_rate () const
        DCPOMATIC_ASSERT (film);
        
        /* Resample to a DCI-approved sample rate */
        DCPOMATIC_ASSERT (film);
        
        /* Resample to a DCI-approved sample rate */
-       double t = dcp_audio_frame_rate (audio_frame_rate ());
+       double t = has_rate_above_48k() ? 96000 : 48000;
 
        FrameRateChange frc = film->active_frame_rate_change (position ());
 
 
        FrameRateChange frc = film->active_frame_rate_change (position ());
 
@@ -226,32 +237,66 @@ AudioContent::resampled_audio_frame_rate () const
        return rint (t);
 }
 
        return rint (t);
 }
 
-int
-AudioContent::processed_audio_channels () const
+string
+AudioContent::processing_description () const
 {
 {
-       if (!audio_processor ()) {
-               return audio_channels ();
+       vector<AudioStreamPtr> streams = audio_streams ();
+       if (streams.empty ()) {
+               return "";
+       }
+
+       /* Possible answers are:
+          1. all audio will be resampled from x to y.
+          2. all audio will be resampled to y (from a variety of rates)
+          3. some audio will be resampled to y (from a variety of rates)
+          4. nothing will be resampled.
+       */
+
+       bool not_resampled = false;
+       bool resampled = false;
+       bool same = true;
+
+       optional<int> common_frame_rate;
+       BOOST_FOREACH (AudioStreamPtr i, streams) {
+               if (i->frame_rate() != resampled_audio_frame_rate()) {
+                       resampled = true;
+               } else {
+                       not_resampled = true;
+               }
+
+               if (common_frame_rate && common_frame_rate != i->frame_rate ()) {
+                       same = false;
+               }
+               common_frame_rate = i->frame_rate ();
        }
 
        }
 
-       return audio_processor()->out_channels (audio_channels ());
+       if (not_resampled && !resampled) {
+               return _("Audio will not be resampled");
+       }
+
+       if (not_resampled && resampled) {
+               return String::compose (_("Some audio will be resampled to %1kHz"), resampled_audio_frame_rate ());
+       }
+
+       if (!not_resampled && resampled) {
+               if (same) {
+                       return String::compose (_("Audio will be resampled from %1kHz to %2kHz"), common_frame_rate.get(), resampled_audio_frame_rate ());
+               } else {
+                       return String::compose (_("Audio will be resampled to %1kHz"), resampled_audio_frame_rate ());
+               }
+       }
+
+       return "";
 }
 
 }
 
-string
-AudioContent::processing_description () const
+bool
+AudioContent::has_rate_above_48k () const
 {
 {
-       stringstream d;
-       
-       if (audio_frame_rate() != resampled_audio_frame_rate ()) {
-               stringstream from;
-               from << fixed << setprecision(3) << (audio_frame_rate() / 1000.0);
-               stringstream to;
-               to << fixed << setprecision(3) << (resampled_audio_frame_rate() / 1000.0);
-
-               d << String::compose (_("Audio will be resampled from %1kHz to %2kHz."), from.str(), to.str());
-       } else {
-               d << _("Audio will not be resampled.");
+       BOOST_FOREACH (AudioStreamPtr i, audio_streams ()) {
+               if (i->frame_rate() > 48000) {
+                       return true;
+               }
        }
 
        }
 
-       return d.str ();
+       return false;
 }
 }
-
index 85728922ac98661c90af0496c56915259008c421..79dba9fdaf340716c0ca8745ae5890c8ce2b6c32 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
-    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2015 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
 
     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
 #define DCPOMATIC_AUDIO_CONTENT_H
 
 #include "content.h"
 #define DCPOMATIC_AUDIO_CONTENT_H
 
 #include "content.h"
+#include "audio_stream.h"
 #include "audio_mapping.h"
 
 namespace cxml {
        class Node;
 }
 
 #include "audio_mapping.h"
 
 namespace cxml {
        class Node;
 }
 
-class AudioProcessor;
-
 /** @class AudioContentProperty
  *  @brief Names for properties of AudioContent.
  */
 class AudioContentProperty
 {
 public:
 /** @class AudioContentProperty
  *  @brief Names for properties of AudioContent.
  */
 class AudioContentProperty
 {
 public:
-       static int const AUDIO_CHANNELS;
-       static int const AUDIO_FRAME_RATE;
+       static int const AUDIO_STREAMS;
        static int const AUDIO_GAIN;
        static int const AUDIO_DELAY;
        static int const AUDIO_GAIN;
        static int const AUDIO_DELAY;
-       static int const AUDIO_MAPPING;
-       static int const AUDIO_PROCESSOR;
 };
 
 /** @class AudioContent
 };
 
 /** @class AudioContent
@@ -62,22 +58,18 @@ public:
        void as_xml (xmlpp::Node *) const;
        std::string technical_summary () const;
 
        void as_xml (xmlpp::Node *) const;
        std::string technical_summary () const;
 
-       /** @return number of audio channels in the content */
-       virtual int audio_channels () const = 0;
-       /** @return the frame rate of the content */
-       virtual int audio_frame_rate () const = 0;
-       virtual AudioMapping audio_mapping () const = 0;
-       virtual void set_audio_mapping (AudioMapping);
-       virtual boost::filesystem::path audio_analysis_path () const;
+       virtual std::vector<AudioStreamPtr> audio_streams () const = 0;
 
 
+       AudioMapping audio_mapping () const;
+       void set_audio_mapping (AudioMapping);
+       boost::filesystem::path audio_analysis_path () const;
        int resampled_audio_frame_rate () const;
        int resampled_audio_frame_rate () const;
-       int processed_audio_channels () const;
+       bool has_rate_above_48k () const;
 
        boost::signals2::connection analyse_audio (boost::function<void()>);
 
        void set_audio_gain (double);
        void set_audio_delay (int);
 
        boost::signals2::connection analyse_audio (boost::function<void()>);
 
        void set_audio_gain (double);
        void set_audio_delay (int);
-       void set_audio_processor (AudioProcessor const *);
        
        double audio_gain () const {
                boost::mutex::scoped_lock lm (_mutex);
        
        double audio_gain () const {
                boost::mutex::scoped_lock lm (_mutex);
@@ -89,11 +81,6 @@ public:
                return _audio_delay;
        }
 
                return _audio_delay;
        }
 
-       AudioProcessor const * audio_processor () const {
-               boost::mutex::scoped_lock lm (_mutex);
-               return _audio_processor;
-       }
-
        std::string processing_description () const;
        
 private:
        std::string processing_description () const;
        
 private:
@@ -101,7 +88,6 @@ private:
        double _audio_gain;
        /** Delay to apply to audio (positive moves audio later) in milliseconds */
        int _audio_delay;
        double _audio_gain;
        /** Delay to apply to audio (positive moves audio later) in milliseconds */
        int _audio_delay;
-       AudioProcessor const * _audio_processor;
 };
 
 #endif
 };
 
 #endif
index a310650c526e20a71fa1fbc6823f6de024ad0186..20c8ea1ea812c60b7b33fd62a8d1300fe8c3958d 100644 (file)
 
 #include "audio_decoder.h"
 #include "audio_buffers.h"
 
 #include "audio_decoder.h"
 #include "audio_buffers.h"
-#include "audio_processor.h"
-#include "resampler.h"
-#include "util.h"
+#include "audio_decoder_stream.h"
+#include <boost/foreach.hpp>
 #include <iostream>
 
 #include "i18n.h"
 
 #include <iostream>
 
 #include "i18n.h"
 
-using std::list;
-using std::pair;
 using std::cout;
 using std::cout;
-using std::min;
-using std::max;
-using boost::optional;
+using std::map;
 using boost::shared_ptr;
 
 AudioDecoder::AudioDecoder (shared_ptr<const AudioContent> content)
        : _audio_content (content)
 {
 using boost::shared_ptr;
 
 AudioDecoder::AudioDecoder (shared_ptr<const AudioContent> content)
        : _audio_content (content)
 {
-       if (content->resampled_audio_frame_rate() != content->audio_frame_rate() && content->audio_channels ()) {
-               _resampler.reset (new Resampler (content->audio_frame_rate(), content->resampled_audio_frame_rate(), content->audio_channels ()));
+       BOOST_FOREACH (AudioStreamPtr i, content->audio_streams ()) {
+               _streams[i] = shared_ptr<AudioDecoderStream> (new AudioDecoderStream (_audio_content, i, this));
        }
        }
-
-       if (content->audio_processor ()) {
-               _processor = content->audio_processor()->clone (content->resampled_audio_frame_rate ());
-       }
-
-       reset_decoded_audio ();
-}
-
-void
-AudioDecoder::reset_decoded_audio ()
-{
-       _decoded_audio = ContentAudio (shared_ptr<AudioBuffers> (new AudioBuffers (_audio_content->processed_audio_channels(), 0)), 0);
 }
 
 }
 
-shared_ptr<ContentAudio>
-AudioDecoder::get_audio (Frame frame, Frame length, bool accurate)
+ContentAudio
+AudioDecoder::get_audio (AudioStreamPtr stream, Frame frame, Frame length, bool accurate)
 {
 {
-       shared_ptr<ContentAudio> dec;
-
-       Frame const end = frame + length - 1;
-               
-       if (frame < _decoded_audio.frame || end > (_decoded_audio.frame + length * 4)) {
-               /* Either we have no decoded data, or what we do have is a long way from what we want: seek */
-               seek (ContentTime::from_frames (frame, _audio_content->resampled_audio_frame_rate()), accurate);
-       }
-
-       /* Offset of the data that we want from the start of _decoded_audio.audio
-          (to be set up shortly)
-       */
-       Frame decoded_offset = 0;
-       
-       /* Now enough pass() calls will either:
-        *  (a) give us what we want, or
-        *  (b) hit the end of the decoder.
-        *
-        * If we are being accurate, we want the right frames,
-        * otherwise any frames will do.
-        */
-       if (accurate) {
-               /* Keep stuffing data into _decoded_audio until we have enough data, or the subclass does not want to give us any more */
-               while ((_decoded_audio.frame > frame || (_decoded_audio.frame + _decoded_audio.audio->frames()) < end) && !pass (PASS_REASON_AUDIO)) {}
-               decoded_offset = frame - _decoded_audio.frame;
-       } else {
-               while (_decoded_audio.audio->frames() < length && !pass (PASS_REASON_AUDIO)) {}
-               /* Use decoded_offset of 0, as we don't really care what frames we return */
-       }
-
-       /* The amount of data available in _decoded_audio.audio starting from `frame'.  This could be -ve
-          if pass() returned true before we got enough data.
-       */
-       Frame const available = _decoded_audio.audio->frames() - decoded_offset;
-
-       /* We will return either that, or the requested amount, whichever is smaller */
-       Frame const to_return = max ((Frame) 0, min (available, length));
-
-       /* Copy our data to the output */
-       shared_ptr<AudioBuffers> out (new AudioBuffers (_decoded_audio.audio->channels(), to_return));
-       out->copy_from (_decoded_audio.audio.get(), to_return, decoded_offset, 0);
-
-       Frame const remaining = max ((Frame) 0, available - to_return);
-
-       /* Clean up decoded; first, move the data after what we just returned to the start of the buffer */
-       _decoded_audio.audio->move (decoded_offset + to_return, 0, remaining);
-       /* And set up the number of frames we have left */
-       _decoded_audio.audio->set_frames (remaining);
-       /* Also bump where those frames are in terms of the content */
-       _decoded_audio.frame += decoded_offset + to_return;
-
-       return shared_ptr<ContentAudio> (new ContentAudio (out, frame));
+       return _streams[stream]->get (frame, length, accurate);
 }
 
 }
 
-/** Called by subclasses when audio data is ready.
- *
- *  Audio timestamping is made hard by many factors, but perhaps the most entertaining is resampling.
- *  We have to assume that we are feeding continuous data into the resampler, and so we get continuous
- *  data out.  Hence we do the timestamping here, post-resampler, just by counting samples.
- *
- *  The time is passed in here so that after a seek we can set up our _audio_position.  The
- *  time is ignored once this has been done.
- */
 void
 void
-AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time)
+AudioDecoder::audio (AudioStreamPtr stream, shared_ptr<const AudioBuffers> data, ContentTime time)
 {
 {
-       if (_resampler) {
-               data = _resampler->run (data);
-       }
-
-       if (_processor) {
-               data = _processor->run (data);
-       }
-
-       Frame const frame_rate = _audio_content->resampled_audio_frame_rate ();
-
-       if (_seek_reference) {
-               /* We've had an accurate seek and now we're seeing some data */
-               ContentTime const delta = time - _seek_reference.get ();
-               Frame const delta_frames = delta.frames (frame_rate);
-               if (delta_frames > 0) {
-                       /* This data comes after the seek time.  Pad the data with some silence. */
-                       shared_ptr<AudioBuffers> padded (new AudioBuffers (data->channels(), data->frames() + delta_frames));
-                       padded->make_silent ();
-                       padded->copy_from (data.get(), data->frames(), 0, delta_frames);
-                       data = padded;
-                       time -= delta;
-               } else if (delta_frames < 0) {
-                       /* This data comes before the seek time.  Throw some data away */
-                       Frame const to_discard = min (-delta_frames, static_cast<Frame> (data->frames()));
-                       Frame const to_keep = data->frames() - to_discard;
-                       if (to_keep == 0) {
-                               /* We have to throw all this data away, so keep _seek_reference and
-                                  try again next time some data arrives.
-                               */
-                               return;
-                       }
-                       shared_ptr<AudioBuffers> trimmed (new AudioBuffers (data->channels(), to_keep));
-                       trimmed->copy_from (data.get(), to_keep, to_discard, 0);
-                       data = trimmed;
-                       time += ContentTime::from_frames (to_discard, frame_rate);
-               }
-               _seek_reference = optional<ContentTime> ();
-       }
-
-       if (!_audio_position) {
-               _audio_position = time.frames (frame_rate);
-       }
-
-       DCPOMATIC_ASSERT (_audio_position.get() >= (_decoded_audio.frame + _decoded_audio.audio->frames()));
-
-       add (data);
-}
-
-void
-AudioDecoder::add (shared_ptr<const AudioBuffers> data)
-{
-       if (!_audio_position) {
-               /* This should only happen when there is a seek followed by a flush, but
-                  we need to cope with it.
-               */
-               return;
-       }
-       
-       /* Resize _decoded_audio to fit the new data */
-       int new_size = 0;
-       if (_decoded_audio.audio->frames() == 0) {
-               /* There's nothing in there, so just store the new data */
-               new_size = data->frames ();
-               _decoded_audio.frame = _audio_position.get ();
-       } else {
-               /* Otherwise we need to extend _decoded_audio to include the new stuff */
-               new_size = _audio_position.get() + data->frames() - _decoded_audio.frame;
-       }
-       
-       _decoded_audio.audio->ensure_size (new_size);
-       _decoded_audio.audio->set_frames (new_size);
-
-       /* Copy new data in */
-       _decoded_audio.audio->copy_from (data.get(), data->frames(), 0, _audio_position.get() - _decoded_audio.frame);
-       _audio_position = _audio_position.get() + data->frames ();
-
-       /* Limit the amount of data we keep in case nobody is asking for it */
-       int const max_frames = _audio_content->resampled_audio_frame_rate () * 10;
-       if (_decoded_audio.audio->frames() > max_frames) {
-               int const to_remove = _decoded_audio.audio->frames() - max_frames;
-               _decoded_audio.frame += to_remove;
-               _decoded_audio.audio->move (to_remove, 0, max_frames);
-               _decoded_audio.audio->set_frames (max_frames);
-       }
+       _streams[stream]->audio (data, time);
 }
 
 void
 AudioDecoder::flush ()
 {
 }
 
 void
 AudioDecoder::flush ()
 {
-       if (!_resampler) {
-               return;
-       }
-
-       shared_ptr<const AudioBuffers> b = _resampler->flush ();
-       if (b) {
-               add (b);
+       for (map<AudioStreamPtr, shared_ptr<AudioDecoderStream> >::const_iterator i = _streams.begin(); i != _streams.end(); ++i) {
+               i->second->flush ();
        }
 }
 
 void
 AudioDecoder::seek (ContentTime t, bool accurate)
 {
        }
 }
 
 void
 AudioDecoder::seek (ContentTime t, bool accurate)
 {
-       _audio_position.reset ();
-       reset_decoded_audio ();
-       if (accurate) {
-               _seek_reference = t;
-       }
-       if (_processor) {
-               _processor->flush ();
+       for (map<AudioStreamPtr, shared_ptr<AudioDecoderStream> >::const_iterator i = _streams.begin(); i != _streams.end(); ++i) {
+               i->second->seek (t, accurate);
        }
 }
        }
 }
index 99e9092b30619c02f394f12ad0ab32be47bcb3c0..1b17029b7e4b2889d85e72861802b6e6c9097bac 100644 (file)
 #include "content.h"
 #include "audio_content.h"
 #include "content_audio.h"
 #include "content.h"
 #include "audio_content.h"
 #include "content_audio.h"
+#include <boost/enable_shared_from_this.hpp>
 
 class AudioBuffers;
 
 class AudioBuffers;
-class Resampler;
+class AudioDecoderStream;
 
 /** @class AudioDecoder.
  *  @brief Parent class for audio decoders.
  */
 
 /** @class AudioDecoder.
  *  @brief Parent class for audio decoders.
  */
-class AudioDecoder : public virtual Decoder
+class AudioDecoder : public virtual Decoder, public boost::enable_shared_from_this<AudioDecoder>
 {
 public:
        AudioDecoder (boost::shared_ptr<const AudioContent>);
 {
 public:
        AudioDecoder (boost::shared_ptr<const AudioContent>);
@@ -50,26 +51,17 @@ public:
         *  @param accurate true to try hard to return frames from exactly `frame', false if we don't mind nearby frames.
         *  @return Time-stamped audio data which may or may not be from the location (and of the length) requested.
         */
         *  @param accurate true to try hard to return frames from exactly `frame', false if we don't mind nearby frames.
         *  @return Time-stamped audio data which may or may not be from the location (and of the length) requested.
         */
-       boost::shared_ptr<ContentAudio> get_audio (Frame time, Frame length, bool accurate);
-       
-protected:
+       ContentAudio get_audio (AudioStreamPtr stream, Frame time, Frame length, bool accurate);
 
 
-       void seek (ContentTime time, bool accurate);
-       void audio (boost::shared_ptr<const AudioBuffers>, ContentTime);
+protected:
+       void audio (AudioStreamPtr stream, boost::shared_ptr<const AudioBuffers>, ContentTime);
        void flush ();
        void flush ();
-       void reset_decoded_audio ();
-       void add (boost::shared_ptr<const AudioBuffers>);
-
+       void seek (ContentTime t, bool accurate);
+       
+private:       
        boost::shared_ptr<const AudioContent> _audio_content;
        boost::shared_ptr<const AudioContent> _audio_content;
-       boost::shared_ptr<Resampler> _resampler;
-       boost::shared_ptr<AudioProcessor> _processor;
-       boost::optional<Frame> _audio_position;
-       /** Currently-available decoded audio data */
-       ContentAudio _decoded_audio;
-       /** The time of an accurate seek after which we have not yet received any actual
-           data at the seek time.
-       */
-       boost::optional<ContentTime> _seek_reference;
+       /** An AudioDecoderStream object to manage each stream in _audio_content */
+       std::map<AudioStreamPtr, boost::shared_ptr<AudioDecoderStream> > _streams;
 };
 
 #endif
 };
 
 #endif
diff --git a/src/lib/audio_decoder_stream.cc b/src/lib/audio_decoder_stream.cc
new file mode 100644 (file)
index 0000000..b9a8f26
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+    Copyright (C) 2012-2015 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_decoder_stream.h"
+#include "audio_buffers.h"
+#include "audio_processor.h"
+#include "audio_decoder.h"
+#include "resampler.h"
+#include "util.h"
+#include <iostream>
+
+#include "i18n.h"
+
+using std::list;
+using std::pair;
+using std::cout;
+using std::min;
+using std::max;
+using boost::optional;
+using boost::shared_ptr;
+
+AudioDecoderStream::AudioDecoderStream (shared_ptr<const AudioContent> content, AudioStreamPtr stream, AudioDecoder* decoder)
+       : _content (content)
+       , _stream (stream)
+       , _decoder (decoder)
+{
+       if (content->resampled_audio_frame_rate() != _stream->frame_rate()) {
+               _resampler.reset (new Resampler (_stream->frame_rate(), content->resampled_audio_frame_rate(), _stream->channels ()));
+       }
+
+       reset_decoded ();
+}
+
+void
+AudioDecoderStream::reset_decoded ()
+{
+       _decoded = ContentAudio (shared_ptr<AudioBuffers> (new AudioBuffers (_stream->channels(), 0)), 0);
+}
+
+ContentAudio
+AudioDecoderStream::get (Frame frame, Frame length, bool accurate)
+{
+       shared_ptr<ContentAudio> dec;
+
+       Frame const end = frame + length - 1;
+               
+       if (frame < _decoded.frame || end > (_decoded.frame + length * 4)) {
+               /* Either we have no decoded data, or what we do have is a long way from what we want: seek */
+               seek (ContentTime::from_frames (frame, _content->resampled_audio_frame_rate()), accurate);
+       }
+
+       /* Offset of the data that we want from the start of _decoded.audio
+          (to be set up shortly)
+       */
+       Frame decoded_offset = 0;
+       
+       /* Now enough pass() calls will either:
+        *  (a) give us what we want, or
+        *  (b) hit the end of the decoder.
+        *
+        * If we are being accurate, we want the right frames,
+        * otherwise any frames will do.
+        */
+       if (accurate) {
+               /* Keep stuffing data into _decoded until we have enough data, or the subclass does not want to give us any more */
+               while (
+                       (_decoded.frame > frame || (_decoded.frame + _decoded.audio->frames()) < end) &&
+                       !_decoder->pass (Decoder::PASS_REASON_AUDIO)
+                       )
+               {}
+               
+               decoded_offset = frame - _decoded.frame;
+       } else {
+               while (
+                       _decoded.audio->frames() < length &&
+                       !_decoder->pass (Decoder::PASS_REASON_AUDIO)
+                       )
+               {}
+               
+               /* Use decoded_offset of 0, as we don't really care what frames we return */
+       }
+
+       /* The amount of data available in _decoded.audio starting from `frame'.  This could be -ve
+          if pass() returned true before we got enough data.
+       */
+       Frame const available = _decoded.audio->frames() - decoded_offset;
+
+       /* We will return either that, or the requested amount, whichever is smaller */
+       Frame const to_return = max ((Frame) 0, min (available, length));
+
+       /* Copy our data to the output */
+       shared_ptr<AudioBuffers> out (new AudioBuffers (_decoded.audio->channels(), to_return));
+       out->copy_from (_decoded.audio.get(), to_return, decoded_offset, 0);
+
+       Frame const remaining = max ((Frame) 0, available - to_return);
+
+       /* Clean up decoded; first, move the data after what we just returned to the start of the buffer */
+       _decoded.audio->move (decoded_offset + to_return, 0, remaining);
+       /* And set up the number of frames we have left */
+       _decoded.audio->set_frames (remaining);
+       /* Also bump where those frames are in terms of the content */
+       _decoded.frame += decoded_offset + to_return;
+
+       return ContentAudio (out, frame);
+}
+
+/** Audio timestamping is made hard by many factors, but perhaps the most entertaining is resampling.
+ *  We have to assume that we are feeding continuous data into the resampler, and so we get continuous
+ *  data out.  Hence we do the timestamping here, post-resampler, just by counting samples.
+ *
+ *  The time is passed in here so that after a seek we can set up our _position.  The
+ *  time is ignored once this has been done.
+ */
+void
+AudioDecoderStream::audio (shared_ptr<const AudioBuffers> data, ContentTime time)
+{
+       if (_resampler) {
+               data = _resampler->run (data);
+       }
+
+       Frame const frame_rate = _content->resampled_audio_frame_rate ();
+
+       if (_seek_reference) {
+               /* We've had an accurate seek and now we're seeing some data */
+               ContentTime const delta = time - _seek_reference.get ();
+               Frame const delta_frames = delta.frames (frame_rate);
+               if (delta_frames > 0) {
+                       /* This data comes after the seek time.  Pad the data with some silence. */
+                       shared_ptr<AudioBuffers> padded (new AudioBuffers (data->channels(), data->frames() + delta_frames));
+                       padded->make_silent ();
+                       padded->copy_from (data.get(), data->frames(), 0, delta_frames);
+                       data = padded;
+                       time -= delta;
+               } else if (delta_frames < 0) {
+                       /* This data comes before the seek time.  Throw some data away */
+                       Frame const to_discard = min (-delta_frames, static_cast<Frame> (data->frames()));
+                       Frame const to_keep = data->frames() - to_discard;
+                       if (to_keep == 0) {
+                               /* We have to throw all this data away, so keep _seek_reference and
+                                  try again next time some data arrives.
+                               */
+                               return;
+                       }
+                       shared_ptr<AudioBuffers> trimmed (new AudioBuffers (data->channels(), to_keep));
+                       trimmed->copy_from (data.get(), to_keep, to_discard, 0);
+                       data = trimmed;
+                       time += ContentTime::from_frames (to_discard, frame_rate);
+               }
+               _seek_reference = optional<ContentTime> ();
+       }
+
+       if (!_position) {
+               _position = time.frames (frame_rate);
+       }
+
+       DCPOMATIC_ASSERT (_position.get() >= (_decoded.frame + _decoded.audio->frames()));
+
+       add (data);
+}
+
+void
+AudioDecoderStream::add (shared_ptr<const AudioBuffers> data)
+{
+       if (!_position) {
+               /* This should only happen when there is a seek followed by a flush, but
+                  we need to cope with it.
+               */
+               return;
+       }
+       
+       /* Resize _decoded to fit the new data */
+       int new_size = 0;
+       if (_decoded.audio->frames() == 0) {
+               /* There's nothing in there, so just store the new data */
+               new_size = data->frames ();
+               _decoded.frame = _position.get ();
+       } else {
+               /* Otherwise we need to extend _decoded to include the new stuff */
+               new_size = _position.get() + data->frames() - _decoded.frame;
+       }
+       
+       _decoded.audio->ensure_size (new_size);
+       _decoded.audio->set_frames (new_size);
+
+       /* Copy new data in */
+       _decoded.audio->copy_from (data.get(), data->frames(), 0, _position.get() - _decoded.frame);
+       _position = _position.get() + data->frames ();
+
+       /* Limit the amount of data we keep in case nobody is asking for it */
+       int const max_frames = _content->resampled_audio_frame_rate () * 10;
+       if (_decoded.audio->frames() > max_frames) {
+               int const to_remove = _decoded.audio->frames() - max_frames;
+               _decoded.frame += to_remove;
+               _decoded.audio->move (to_remove, 0, max_frames);
+               _decoded.audio->set_frames (max_frames);
+       }
+}
+
+void
+AudioDecoderStream::flush ()
+{
+       if (!_resampler) {
+               return;
+       }
+
+       shared_ptr<const AudioBuffers> b = _resampler->flush ();
+       if (b) {
+               add (b);
+       }
+}
+
+void
+AudioDecoderStream::seek (ContentTime t, bool accurate)
+{
+       _position.reset ();
+       reset_decoded ();
+       if (accurate) {
+               _seek_reference = t;
+       }
+}
diff --git a/src/lib/audio_decoder_stream.h b/src/lib/audio_decoder_stream.h
new file mode 100644 (file)
index 0000000..24f86c2
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+    Copyright (C) 2012-2015 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.
+
+*/
+
+#ifndef DCPOMATIC_AUDIO_DECODER_STREAM_H
+#define DCPOMATIC_AUDIO_DECODER_STREAM_H
+
+#include "audio_stream.h"
+#include "content_audio.h"
+#include <boost/shared_ptr.hpp>
+
+class AudioContent;
+class AudioDecoder;
+class Resampler;
+
+class AudioDecoderStream
+{
+public:
+       AudioDecoderStream (boost::shared_ptr<const AudioContent>, AudioStreamPtr, AudioDecoder* decoder);
+       
+       ContentAudio get (Frame time, Frame length, bool accurate);
+       void audio (boost::shared_ptr<const AudioBuffers>, ContentTime);
+       void flush ();
+       void seek (ContentTime time, bool accurate);
+       
+private:
+
+       void reset_decoded ();
+       void add (boost::shared_ptr<const AudioBuffers>);
+
+       boost::shared_ptr<const AudioContent> _content;
+       AudioStreamPtr _stream;
+       AudioDecoder* _decoder;
+       boost::shared_ptr<Resampler> _resampler;
+       boost::optional<Frame> _position;
+       /** Currently-available decoded audio data */
+       ContentAudio _decoded;
+       /** The time of an accurate seek after which we have not yet received any actual
+           data at the seek time.
+       */
+       boost::optional<ContentTime> _seek_reference;
+};
+
+#endif
index a5f05598483a26a49d19c91a1db332e5a5c72bcb..c4b915c99852bf8c4d37af5129854654a6c71007 100644 (file)
 
 */
 
 
 */
 
+#ifndef DCPOMATIC_AUDIO_EXAMINER_H
+#define DCPOMATIC_AUDIO_EXAMINER_H
+
 /** @file  src/lib/audio_examiner.h
  *  @brief AudioExaminer class.
  */
 
 /** @file  src/lib/audio_examiner.h
  *  @brief AudioExaminer class.
  */
 
+#include "types.h"
+
 /** @class AudioExaminer
  *  @brief Parent for classes which examine AudioContent for their pertinent details.
  */
 /** @class AudioExaminer
  *  @brief Parent for classes which examine AudioContent for their pertinent details.
  */
@@ -33,3 +38,5 @@ public:
        virtual Frame audio_length () const = 0;
        virtual int audio_frame_rate () const = 0;
 };
        virtual Frame audio_length () const = 0;
        virtual int audio_frame_rate () const = 0;
 };
+
+#endif
index 4e5a8afa20e6b890046e4f489be31545f0171a5b..65eb5fc962c77baa42b7fb4ca5faec9b78a47190 100644 (file)
@@ -57,16 +57,26 @@ AudioMapping::setup (int c)
        for (int i = 0; i < _content_channels; ++i) {
                _gain[i].resize (MAX_DCP_AUDIO_CHANNELS);
        }
        for (int i = 0; i < _content_channels; ++i) {
                _gain[i].resize (MAX_DCP_AUDIO_CHANNELS);
        }
+
+       _name.resize (_content_channels);
+
+       make_zero ();
 }
 
 void
 }
 
 void
-AudioMapping::make_default ()
+AudioMapping::make_zero ()
 {
        for (int i = 0; i < _content_channels; ++i) {
                for (int j = 0; j < MAX_DCP_AUDIO_CHANNELS; ++j) {
                        _gain[i][j] = 0;
                }
        }
 {
        for (int i = 0; i < _content_channels; ++i) {
                for (int j = 0; j < MAX_DCP_AUDIO_CHANNELS; ++j) {
                        _gain[i][j] = 0;
                }
        }
+}
+
+void
+AudioMapping::make_default ()
+{
+       make_zero ();
 
        if (_content_channels == 1) {
                /* Mono -> Centre */
 
        if (_content_channels == 1) {
                /* Mono -> Centre */
@@ -176,3 +186,8 @@ AudioMapping::unmap_all ()
        }
 }
 
        }
 }
 
+void
+AudioMapping::set_name (int channel, string name)
+{
+       _name[channel] = name;
+}
index bac2b10b0a4b77cef32ccfe6c7bf9984d38d3462..e37beaeb26eb77dce33fd0a75a7ab8dee51d1086 100644 (file)
@@ -64,6 +64,11 @@ public:
                return _content_channels;
        }
 
                return _content_channels;
        }
 
+       void set_name (int channel, std::string name);
+       std::string name (int channel) const {
+               return _name[channel];
+       }
+
        std::string digest () const;
 
        std::list<dcp::Channel> mapped_dcp_channels () const;
        std::string digest () const;
 
        std::list<dcp::Channel> mapped_dcp_channels () const;
@@ -71,9 +76,11 @@ public:
        
 private:
        void setup (int);
        
 private:
        void setup (int);
+       void make_zero ();
        
        int _content_channels;
        std::vector<std::vector<float> > _gain;
        
        int _content_channels;
        std::vector<std::vector<float> > _gain;
+       std::vector<std::string> _name;
 };
 
 #endif
 };
 
 #endif
diff --git a/src/lib/audio_stream.cc b/src/lib/audio_stream.cc
new file mode 100644 (file)
index 0000000..a4fa8bd
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+    Copyright (C) 2015 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_stream.h"
+#include "audio_mapping.h"
+
+AudioStream::AudioStream (int frame_rate, int channels)
+       : _frame_rate (frame_rate)
+{
+       _mapping = AudioMapping (channels);
+}
+
+AudioStream::AudioStream (int frame_rate, AudioMapping mapping)
+       : _frame_rate (frame_rate)
+       , _mapping (mapping)
+{
+
+}
+
+void
+AudioStream::set_mapping (AudioMapping mapping)
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       _mapping = mapping;
+}
+
+void
+AudioStream::set_frame_rate (int frame_rate)
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       _frame_rate = frame_rate;
+}
+
+int
+AudioStream::channels () const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       return _mapping.content_channels ();
+}
diff --git a/src/lib/audio_stream.h b/src/lib/audio_stream.h
new file mode 100644 (file)
index 0000000..b3b203b
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+    Copyright (C) 2015 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.
+
+*/
+
+#ifndef DCPOMATIC_AUDIO_STREAM_H
+#define DCPOMATIC_AUDIO_STREAM_H
+
+#include "audio_mapping.h"
+#include <boost/thread/mutex.hpp>
+
+class audio_sampling_rate_test;
+
+class AudioStream
+{
+public:
+       AudioStream (int frame_rate, int channels);
+       AudioStream (int frame_rate, AudioMapping mapping);
+       
+       void set_mapping (AudioMapping mapping);
+       void set_frame_rate (int frame_rate);
+
+       AudioMapping const & mapping () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _mapping;
+       }
+
+       AudioMapping & mapping () {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _mapping;
+       }
+       
+       int frame_rate () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _frame_rate;
+       }
+
+       int channels () const;
+
+protected:
+       mutable boost::mutex _mutex;
+
+private:
+       friend class audio_sampling_rate_test;
+       
+       int _frame_rate;
+       AudioMapping _mapping;
+};
+
+typedef boost::shared_ptr<AudioStream> AudioStreamPtr;
+
+#endif
index b9e8367e1c3025ba0b0ea992c6a5c8830de60efb..9a7cb0f34f68e0837862bbc96bc36aca00ab6d27 100644 (file)
@@ -139,7 +139,7 @@ Content::examine (shared_ptr<Job> job)
        vector<boost::filesystem::path> p = _paths;
        lm.unlock ();
 
        vector<boost::filesystem::path> p = _paths;
        lm.unlock ();
 
-       /* Some content files are very big, so we use a poor's
+       /* Some content files are very big, so we use a poor man's
           digest here: a MD5 of the first and last 1e6 bytes with the
           size of the first file tacked on the end as a string.
        */
           digest here: a MD5 of the first and last 1e6 bytes with the
           size of the first file tacked on the end as a string.
        */
index f704fac58fa691faa92ab69f91bddf9bfd9e3f1b..194e90e3f35919f4562a6709b97cfad47d16fd6f 100644 (file)
 
 */
 
 
 */
 
+#ifndef DCPOMATIC_CONTENT_AUDIO_H
+#define DCPOMATIC_CONTENT_AUDIO_H
+
 /** @file  src/lib/content_audio.h
  *  @brief ContentAudio class.
  */
 
 #include "audio_buffers.h"
 /** @file  src/lib/content_audio.h
  *  @brief ContentAudio class.
  */
 
 #include "audio_buffers.h"
+#include "types.h"
 
 /** @class ContentAudio
  *  @brief A block of audio from a piece of content, with a timestamp as a frame within that content.
 
 /** @class ContentAudio
  *  @brief A block of audio from a piece of content, with a timestamp as a frame within that content.
@@ -42,3 +46,5 @@ public:
        boost::shared_ptr<AudioBuffers> audio;
        Frame frame;
 };
        boost::shared_ptr<AudioBuffers> audio;
        Frame frame;
 };
+
+#endif
index 0c89685313337b69e1584df648240bd0c7c3d71f..b707754a78957bc2e3291659fc01c3e4d84606fe 100644 (file)
@@ -90,11 +90,13 @@ DCPContent::examine (shared_ptr<Job> job)
        take_from_video_examiner (examiner);
        take_from_audio_examiner (examiner);
 
        take_from_video_examiner (examiner);
        take_from_audio_examiner (examiner);
 
-       boost::mutex::scoped_lock lm (_mutex);
-       _name = examiner->name ();
-       _has_subtitles = examiner->has_subtitles ();
-       _encrypted = examiner->encrypted ();
-       _kdm_valid = examiner->kdm_valid ();
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _name = examiner->name ();
+               _has_subtitles = examiner->has_subtitles ();
+               _encrypted = examiner->encrypted ();
+               _kdm_valid = examiner->kdm_valid ();
+       }
 
        if (could_be_played != can_be_played ()) {
                signal_changed (DCPContentProperty::CAN_BE_PLAYED);
 
        if (could_be_played != can_be_played ()) {
                signal_changed (DCPContentProperty::CAN_BE_PLAYED);
@@ -160,6 +162,7 @@ DCPContent::add_kdm (dcp::EncryptedKDM k)
 bool
 DCPContent::can_be_played () const
 {
 bool
 DCPContent::can_be_played () const
 {
+       boost::mutex::scoped_lock lm (_mutex);
        return !_encrypted || _kdm_valid;
 }
 
        return !_encrypted || _kdm_valid;
 }
 
index 3bfbd7720c9e04cf99b951db455e306bd2be3cb9..0411ad94183d8818df84adcc5d23ac5ec1d3b5f8 100644 (file)
@@ -89,7 +89,7 @@ DCPDecoder::pass (PassReason)
                shared_ptr<const dcp::SoundFrame> sf = (*_reel)->main_sound()->mxf()->get_frame (entry_point + frame);
                uint8_t const * from = sf->data ();
 
                shared_ptr<const dcp::SoundFrame> sf = (*_reel)->main_sound()->mxf()->get_frame (entry_point + frame);
                uint8_t const * from = sf->data ();
 
-               int const channels = _dcp_content->audio_channels ();
+               int const channels = _dcp_content->audio_stream()->channels ();
                int const frames = sf->size() / (3 * channels);
                shared_ptr<AudioBuffers> data (new AudioBuffers (channels, frames));
                for (int i = 0; i < frames; ++i) {
                int const frames = sf->size() / (3 * channels);
                shared_ptr<AudioBuffers> data (new AudioBuffers (channels, frames));
                for (int i = 0; i < frames; ++i) {
@@ -99,7 +99,7 @@ DCPDecoder::pass (PassReason)
                        }
                }
 
                        }
                }
 
-               audio (data, _next);
+               audio (_dcp_content->audio_stream(), data, _next);
        }
 
        /* XXX: subtitle */
        }
 
        /* XXX: subtitle */
index b308e82b42baec709200c80cf29ccf2fc8b9dbc7..0b78c5390557f03024bfac43f0b012159d4da68e 100644 (file)
@@ -188,6 +188,10 @@ public:
                return Time<S, O> (1);
        }
 
                return Time<S, O> (1);
        }
 
+       static Time<S, O> min () {
+               return Time<S, O> (-INT64_MAX);
+       }
+       
        static Time<S, O> max () {
                return Time<S, O> (INT64_MAX);
        }
        static Time<S, O> max () {
                return Time<S, O> (INT64_MAX);
        }
index 0703a542686933e3065f3b3cd5a343498ef0cebd..acc77a814920630582c74678aee72c12ef858c13 100644 (file)
@@ -41,7 +41,8 @@ public:
        virtual ~Decoder () {}
 
 protected:     
        virtual ~Decoder () {}
 
 protected:     
-
+       friend class AudioDecoderStream;
+       
        /** Seek so that the next pass() will yield the next thing
         *  (video/sound frame, subtitle etc.) at or after the requested
         *  time.  Pass accurate = true to try harder to ensure that, at worst,
        /** Seek so that the next pass() will yield the next thing
         *  (video/sound frame, subtitle etc.) at or after the requested
         *  time.  Pass accurate = true to try harder to ensure that, at worst,
index 0cbf7e128b4443e43025768163e3e7ef99f6e447..741716b0400acc818c23d965b02eb97071d8278e 100644 (file)
@@ -189,16 +189,6 @@ FFmpeg::video_codec_context () const
        return _format_context->streams[_video_stream]->codec;
 }
 
        return _format_context->streams[_video_stream]->codec;
 }
 
-AVCodecContext *
-FFmpeg::audio_codec_context () const
-{
-       if (!_ffmpeg_content->audio_stream ()) {
-               return 0;
-       }
-       
-       return _ffmpeg_content->audio_stream()->stream(_format_context)->codec;
-}
-
 AVCodecContext *
 FFmpeg::subtitle_codec_context () const
 {
 AVCodecContext *
 FFmpeg::subtitle_codec_context () const
 {
index 6dd9da0dc4efb9f571ec76629ed7c6953ebd2d6d..835136c759d8b6004f7b9a6f6c7eecf414eac2ba 100644 (file)
@@ -55,7 +55,6 @@ public:
 
 protected:
        AVCodecContext* video_codec_context () const;
 
 protected:
        AVCodecContext* video_codec_context () const;
-       AVCodecContext* audio_codec_context () const;
        AVCodecContext* subtitle_codec_context () const;
        
        boost::shared_ptr<const FFmpegContent> _ffmpeg_content;
        AVCodecContext* subtitle_codec_context () const;
        
        boost::shared_ptr<const FFmpegContent> _ffmpeg_content;
index 6300958e094ec75d8ece6e4025f83ae274cd651d..7a9e8c18e651f2dfbbe016f489497ce2fb51165d 100644 (file)
@@ -26,9 +26,7 @@ using std::string;
 
 FFmpegAudioStream::FFmpegAudioStream (cxml::ConstNodePtr node, int version)
        : FFmpegStream (node)
 
 FFmpegAudioStream::FFmpegAudioStream (cxml::ConstNodePtr node, int version)
        : FFmpegStream (node)
-       , _frame_rate (node->number_child<int> ("FrameRate"))
-       , _channels (node->number_child<int64_t> ("Channels"))
-       , _mapping (node->node_child ("Mapping"), version)
+       , AudioStream (node->number_child<int> ("FrameRate"), AudioMapping (node->node_child ("Mapping"), version))
 {
        first_audio = node->optional_number_child<double> ("FirstAudio");
 }
 {
        first_audio = node->optional_number_child<double> ("FirstAudio");
 }
@@ -37,10 +35,9 @@ void
 FFmpegAudioStream::as_xml (xmlpp::Node* root) const
 {
        FFmpegStream::as_xml (root);
 FFmpegAudioStream::as_xml (xmlpp::Node* root) const
 {
        FFmpegStream::as_xml (root);
-       root->add_child("FrameRate")->add_child_text (raw_convert<string> (_frame_rate));
-       root->add_child("Channels")->add_child_text (raw_convert<string> (_channels));
+       root->add_child("FrameRate")->add_child_text (raw_convert<string> (frame_rate ()));
+       mapping().as_xml (root->add_child("Mapping"));
        if (first_audio) {
        if (first_audio) {
-               root->add_child("FirstAudio")->add_child_text (raw_convert<string> (first_audio.get().get()));
+               root->add_child("FirstAudio")->add_child_text (raw_convert<string> (first_audio.get ()));
        }
        }
-       _mapping.as_xml (root->add_child("Mapping"));
 }
 }
index 1587afcae4528a8f979e92aeaedd111b7aa4c08e..7fe3c4fdaedd907df31ecdf5d06360eb8b41f43a 100644 (file)
 
 #include "ffmpeg_stream.h"
 #include "audio_mapping.h"
 
 #include "ffmpeg_stream.h"
 #include "audio_mapping.h"
+#include "audio_stream.h"
 #include "dcpomatic_time.h"
 
 struct ffmpeg_pts_offset_test;
 
 #include "dcpomatic_time.h"
 
 struct ffmpeg_pts_offset_test;
 
-class FFmpegAudioStream : public FFmpegStream
+class FFmpegAudioStream : public FFmpegStream, public AudioStream
 {
 public:
 {
 public:
-       FFmpegAudioStream (std::string n, int i, int f, int c)
-               : FFmpegStream (n, i)
-               , _frame_rate (f)
-               , _channels (c)
-               , _mapping (c)
-       {
-               _mapping.make_default ();
-       }
+       FFmpegAudioStream (std::string name, int id, int frame_rate, int channels)
+               : FFmpegStream (name, id)
+               , AudioStream (frame_rate, channels)
+       {}
 
        FFmpegAudioStream (cxml::ConstNodePtr, int);
 
        void as_xml (xmlpp::Node *) const;
 
 
        FFmpegAudioStream (cxml::ConstNodePtr, int);
 
        void as_xml (xmlpp::Node *) const;
 
-       int frame_rate () const {
-               return _frame_rate;
-       }
-       
-       int channels () const {
-               return _channels;
-       }
-       
-       AudioMapping mapping () const {
-               return _mapping;
-       }
-
-       void set_mapping (AudioMapping m) {
-               _mapping = m;
-       }
+       /* XXX: should probably be locked */
        
        boost::optional<ContentTime> first_audio;
 
        
        boost::optional<ContentTime> first_audio;
 
@@ -63,12 +46,6 @@ private:
        /* Constructor for tests */
        FFmpegAudioStream ()
                : FFmpegStream ("", 0)
        /* Constructor for tests */
        FFmpegAudioStream ()
                : FFmpegStream ("", 0)
-               , _frame_rate (0)
-               , _channels (0)
-               , _mapping (1)
+               , AudioStream (0, 0)
        {}
        {}
-
-       int _frame_rate;
-       int _channels;
-       AudioMapping _mapping;
 };
 };
index 1ca194f092f33f28c96a2ba04401c91da5691dd9..fa2671b3697c2663060d7403863421bf72b1f04c 100644 (file)
@@ -35,6 +35,7 @@
 extern "C" {
 #include <libavformat/avformat.h>
 }
 extern "C" {
 #include <libavformat/avformat.h>
 }
+#include <boost/foreach.hpp>
 
 #include "i18n.h"
 
 
 #include "i18n.h"
 
@@ -51,8 +52,7 @@ using boost::dynamic_pointer_cast;
 int const FFmpegContentProperty::SUBTITLE_STREAMS = 100;
 int const FFmpegContentProperty::SUBTITLE_STREAM = 101;
 int const FFmpegContentProperty::AUDIO_STREAMS = 102;
 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;
+int const FFmpegContentProperty::FILTERS = 103;
 
 FFmpegContent::FFmpegContent (shared_ptr<const Film> f, boost::filesystem::path p)
        : Content (f, p)
 
 FFmpegContent::FFmpegContent (shared_ptr<const Film> f, boost::filesystem::path p)
        : Content (f, p)
@@ -80,9 +80,6 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, cxml::ConstNodePtr node,
        c = node->node_children ("AudioStream");
        for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
                _audio_streams.push_back (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (*i, version)));
        c = node->node_children ("AudioStream");
        for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
                _audio_streams.push_back (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (*i, version)));
-               if ((*i)->optional_number_child<int> ("Selected")) {
-                       _audio_stream = _audio_streams.back ();
-               }
        }
 
        c = node->node_children ("Filter");
        }
 
        c = node->node_children ("Filter");
@@ -112,16 +109,11 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, vector<boost::shared_ptr
                if (fc->use_subtitles() && *(fc->_subtitle_stream.get()) != *(ref->_subtitle_stream.get())) {
                        throw JoinError (_("Content to be joined must use the same subtitle stream."));
                }
                if (fc->use_subtitles() && *(fc->_subtitle_stream.get()) != *(ref->_subtitle_stream.get())) {
                        throw JoinError (_("Content to be joined must use the same subtitle stream."));
                }
-
-               if (*(fc->_audio_stream.get()) != *(ref->_audio_stream.get())) {
-                       throw JoinError (_("Content to be joined must use the same audio stream."));
-               }
        }
 
        _subtitle_streams = ref->subtitle_streams ();
        _subtitle_stream = ref->subtitle_stream ();
        }
 
        _subtitle_streams = ref->subtitle_streams ();
        _subtitle_stream = ref->subtitle_stream ();
-       _audio_streams = ref->audio_streams ();
-       _audio_stream = ref->audio_stream ();
+       _audio_streams = ref->ffmpeg_audio_streams ();
        _first_video = ref->_first_video;
 }
 
        _first_video = ref->_first_video;
 }
 
@@ -145,11 +137,7 @@ FFmpegContent::as_xml (xmlpp::Node* node) const
        }
 
        for (vector<shared_ptr<FFmpegAudioStream> >::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
        }
 
        for (vector<shared_ptr<FFmpegAudioStream> >::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) {
-               xmlpp::Node* t = node->add_child("AudioStream");
-               if (_audio_stream && *i == _audio_stream) {
-                       t->add_child("Selected")->add_child_text("1");
-               }
-               (*i)->as_xml (t);
+               (*i)->as_xml (node->add_child("AudioStream"));
        }
 
        for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
        }
 
        for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
@@ -183,8 +171,9 @@ FFmpegContent::examine (shared_ptr<Job> job)
                }
                
                _audio_streams = examiner->audio_streams ();
                }
                
                _audio_streams = examiner->audio_streams ();
+
                if (!_audio_streams.empty ()) {
                if (!_audio_streams.empty ()) {
-                       _audio_stream = _audio_streams.front ();
+                       _audio_streams.front()->mapping().make_default ();
                }
 
                _first_video = examiner->first_video ();
                }
 
                _first_video = examiner->first_video ();
@@ -193,8 +182,6 @@ FFmpegContent::examine (shared_ptr<Job> job)
        signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
        signal_changed (FFmpegContentProperty::AUDIO_STREAMS);
        signal_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
        signal_changed (FFmpegContentProperty::AUDIO_STREAMS);
-       signal_changed (FFmpegContentProperty::AUDIO_STREAM);
-       signal_changed (AudioContentProperty::AUDIO_CHANNELS);
 }
 
 string
 }
 
 string
@@ -207,9 +194,13 @@ FFmpegContent::summary () const
 string
 FFmpegContent::technical_summary () const
 {
 string
 FFmpegContent::technical_summary () const
 {
-       string as = "none";
-       if (_audio_stream) {
-               as = _audio_stream->technical_summary ();
+       string as = "";
+       BOOST_FOREACH (shared_ptr<FFmpegAudioStream> i, ffmpeg_audio_streams ()) {
+               as += i->technical_summary () + " " ;
+       }
+
+       if (as.empty ()) {
+               as = "none";
        }
 
        string ss = "none";
        }
 
        string ss = "none";
@@ -223,7 +214,7 @@ FFmpegContent::technical_summary () const
                + VideoContent::technical_summary() + " - "
                + AudioContent::technical_summary() + " - "
                + String::compose (
                + VideoContent::technical_summary() + " - "
                + AudioContent::technical_summary() + " - "
                + String::compose (
-                       "ffmpeg: audio %1, subtitle %2, filters %3", as, ss, filt
+                       "ffmpeg: audio %1 subtitle %2 filters %3", as, ss, filt
                        );
 }
 
                        );
 }
 
@@ -238,41 +229,6 @@ FFmpegContent::set_subtitle_stream (shared_ptr<FFmpegSubtitleStream> s)
        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
 }
 
        signal_changed (FFmpegContentProperty::SUBTITLE_STREAM);
 }
 
-void
-FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s)
-{
-       {
-               boost::mutex::scoped_lock lm (_mutex);
-               _audio_stream = s;
-       }
-
-       signal_changed (FFmpegContentProperty::AUDIO_STREAM);
-}
-
-int
-FFmpegContent::audio_channels () const
-{
-       boost::mutex::scoped_lock lm (_mutex);
-       
-       if (!_audio_stream) {
-               return 0;
-       }
-
-       return _audio_stream->channels ();
-}
-
-int
-FFmpegContent::audio_frame_rate () const
-{
-       boost::mutex::scoped_lock lm (_mutex);
-
-       if (!_audio_stream) {
-               return 0;
-       }
-
-       return _audio_stream->frame_rate ();
-}
-
 bool
 operator== (FFmpegStream const & a, FFmpegStream const & b)
 {
 bool
 operator== (FFmpegStream const & a, FFmpegStream const & b)
 {
@@ -294,18 +250,6 @@ FFmpegContent::full_length () const
        return DCPTime::from_frames (rint (video_length_after_3d_combine() * frc.factor()), film->video_frame_rate());
 }
 
        return DCPTime::from_frames (rint (video_length_after_3d_combine() * frc.factor()), film->video_frame_rate());
 }
 
-AudioMapping
-FFmpegContent::audio_mapping () const
-{
-       boost::mutex::scoped_lock lm (_mutex);
-
-       if (!_audio_stream) {
-               return AudioMapping ();
-       }
-
-       return _audio_stream->mapping ();
-}
-
 void
 FFmpegContent::set_filters (vector<Filter const *> const & filters)
 {
 void
 FFmpegContent::set_filters (vector<Filter const *> const & filters)
 {
@@ -317,13 +261,6 @@ FFmpegContent::set_filters (vector<Filter const *> const & filters)
        signal_changed (FFmpegContentProperty::FILTERS);
 }
 
        signal_changed (FFmpegContentProperty::FILTERS);
 }
 
-void
-FFmpegContent::set_audio_mapping (AudioMapping m)
-{
-       audio_stream()->set_mapping (m);
-       AudioContent::set_audio_mapping (m);
-}
-
 string
 FFmpegContent::identifier () const
 {
 string
 FFmpegContent::identifier () const
 {
@@ -344,25 +281,6 @@ FFmpegContent::identifier () const
        return s.str ();
 }
 
        return s.str ();
 }
 
-boost::filesystem::path
-FFmpegContent::audio_analysis_path () const
-{
-       shared_ptr<const Film> film = _film.lock ();
-       if (!film) {
-               return boost::filesystem::path ();
-       }
-
-       /* We need to include the stream ID in this path so that we get different
-          analyses for each stream.
-       */
-
-       boost::filesystem::path p = AudioContent::audio_analysis_path ();
-       if (audio_stream ()) {
-               p = p.string() + "_" + audio_stream()->identifier ();
-       }
-       return p;
-}
-
 list<ContentTimePeriod>
 FFmpegContent::subtitles_during (ContentTimePeriod period, bool starting) const
 {
 list<ContentTimePeriod>
 FFmpegContent::subtitles_during (ContentTimePeriod period, bool starting) const
 {
@@ -394,4 +312,12 @@ FFmpegContent::set_default_colour_conversion ()
        }
 }
 
        }
 }
 
-
+vector<AudioStreamPtr>
+FFmpegContent::audio_streams () const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       
+       vector<AudioStreamPtr> s;
+       copy (_audio_streams.begin(), _audio_streams.end(), back_inserter (s));
+       return s;
+}
index 6d27c66ca6b42cf5773518a3068a0113f6955de4..b9ae4707a3420cfc150927952a5baccdaba82346 100644 (file)
@@ -34,6 +34,7 @@ class Filter;
 class FFmpegSubtitleStream;
 class FFmpegAudioStream;
 struct ffmpeg_pts_offset_test;
 class FFmpegSubtitleStream;
 class FFmpegAudioStream;
 struct ffmpeg_pts_offset_test;
+struct audio_sampling_rate_test;
 
 class FFmpegContentProperty : public VideoContentProperty
 {
 
 class FFmpegContentProperty : public VideoContentProperty
 {
@@ -41,7 +42,6 @@ public:
        static int const SUBTITLE_STREAMS;
        static int const SUBTITLE_STREAM;
        static int const AUDIO_STREAMS;
        static int const SUBTITLE_STREAMS;
        static int const SUBTITLE_STREAM;
        static int const AUDIO_STREAMS;
-       static int const AUDIO_STREAM;
        static int const FILTERS;
 };
 
        static int const FILTERS;
 };
 
@@ -68,11 +68,7 @@ public:
        void set_default_colour_conversion ();
        
        /* AudioContent */
        void set_default_colour_conversion ();
        
        /* AudioContent */
-       int audio_channels () const;
-       int audio_frame_rate () const;
-       AudioMapping audio_mapping () const;
-       void set_audio_mapping (AudioMapping);
-       boost::filesystem::path audio_analysis_path () const;
+       std::vector<AudioStreamPtr> audio_streams () const;
 
        /* SubtitleContent */
        bool has_subtitles () const;
 
        /* SubtitleContent */
        bool has_subtitles () const;
@@ -89,15 +85,10 @@ public:
                return _subtitle_stream;
        }
 
                return _subtitle_stream;
        }
 
-       std::vector<boost::shared_ptr<FFmpegAudioStream> > audio_streams () const {
+       std::vector<boost::shared_ptr<FFmpegAudioStream> > ffmpeg_audio_streams () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _audio_streams;
        }
                boost::mutex::scoped_lock lm (_mutex);
                return _audio_streams;
        }
-       
-       boost::shared_ptr<FFmpegAudioStream> audio_stream () const {
-               boost::mutex::scoped_lock lm (_mutex);
-               return _audio_stream;
-       }
 
        std::vector<Filter const *> filters () const {
                boost::mutex::scoped_lock lm (_mutex);
 
        std::vector<Filter const *> filters () const {
                boost::mutex::scoped_lock lm (_mutex);
@@ -105,7 +96,6 @@ public:
        }
 
        void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
        }
 
        void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
-       void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>);
 
        boost::optional<ContentTime> first_video () const {
                boost::mutex::scoped_lock lm (_mutex);
 
        boost::optional<ContentTime> first_video () const {
                boost::mutex::scoped_lock lm (_mutex);
@@ -116,11 +106,11 @@ public:
 
 private:
        friend struct ffmpeg_pts_offset_test;
 
 private:
        friend struct ffmpeg_pts_offset_test;
+       friend struct audio_sampling_rate_test;
        
        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
        boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
        std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
        
        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
        boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
        std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
-       boost::shared_ptr<FFmpegAudioStream> _audio_stream;
        boost::optional<ContentTime> _first_video;
        /** Video filters that should be used when generating DCPs */
        std::vector<Filter const *> _filters;
        boost::optional<ContentTime> _first_video;
        /** Video filters that should be used when generating DCPs */
        std::vector<Filter const *> _filters;
index 35e15a331c2cd58c0bd7a2926b21ca340af456ab..b7516f6d2d7a7cb005e08dd617e4b0a7f370146f 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
-    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2015 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
 
     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
  *  @brief A decoder using FFmpeg to decode content.
  */
 
  *  @brief A decoder using FFmpeg to decode content.
  */
 
-#include <stdexcept>
-#include <vector>
-#include <iomanip>
-#include <iostream>
-#include <stdint.h>
-#include <sndfile.h>
-extern "C" {
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-}
 #include "filter.h"
 #include "exceptions.h"
 #include "image.h"
 #include "filter.h"
 #include "exceptions.h"
 #include "image.h"
@@ -45,6 +35,17 @@ extern "C" {
 #include "raw_image_proxy.h"
 #include "film.h"
 #include "timer.h"
 #include "raw_image_proxy.h"
 #include "film.h"
 #include "timer.h"
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+}
+#include <boost/foreach.hpp>
+#include <stdexcept>
+#include <vector>
+#include <iomanip>
+#include <iostream>
+#include <stdint.h>
+#include <sndfile.h>
 
 #include "i18n.h"
 
 
 #include "i18n.h"
 
@@ -60,6 +61,7 @@ using std::list;
 using std::min;
 using std::pair;
 using std::make_pair;
 using std::min;
 using std::pair;
 using std::make_pair;
+using std::max;
 using boost::shared_ptr;
 using boost::optional;
 using boost::dynamic_pointer_cast;
 using boost::shared_ptr;
 using boost::optional;
 using boost::dynamic_pointer_cast;
@@ -81,20 +83,25 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log>
           Then we remove big initial gaps in PTS and we allow our
           insertion of black frames to work.
 
           Then we remove big initial gaps in PTS and we allow our
           insertion of black frames to work.
 
-          We will do pts_to_use = pts_from_ffmpeg + pts_offset;
+          We will do:
+            audio_pts_to_use = audio_pts_from_ffmpeg + pts_offset;
+            video_pts_to_use = video_pts_from_ffmpeg + pts_offset;
        */
 
        */
 
-       bool const have_video = c->first_video();
-       bool const have_audio = c->audio_stream () && c->audio_stream()->first_audio;
-
        /* First, make one of them start at 0 */
 
        /* First, make one of them start at 0 */
 
-       if (have_audio && have_video) {
-               _pts_offset = - min (c->first_video().get(), c->audio_stream()->first_audio.get());
-       } else if (have_video) {
-               _pts_offset = - c->first_video().get();
-       } else if (have_audio) {
-               _pts_offset = - c->audio_stream()->first_audio.get();
+       vector<shared_ptr<FFmpegAudioStream> > streams = c->ffmpeg_audio_streams ();
+
+       _pts_offset = ContentTime::min ();
+
+       if (c->first_video ()) {
+               _pts_offset = - c->first_video().get ();
+       }
+
+       BOOST_FOREACH (shared_ptr<FFmpegAudioStream> i, streams) {
+               if (i->first_audio) {
+                       _pts_offset = max (_pts_offset, - i->first_audio.get ());
+               }
        }
 
        /* If _pts_offset is positive we would be pushing things from a -ve PTS to be played.
        }
 
        /* If _pts_offset is positive we would be pushing things from a -ve PTS to be played.
@@ -105,8 +112,8 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log>
                _pts_offset = ContentTime ();
        }
 
                _pts_offset = ContentTime ();
        }
 
-       /* Now adjust both so that the video pts starts on a frame */
-       if (have_video && have_audio) {
+       /* Now adjust so that the video pts starts on a frame */
+       if (c->first_video ()) {
                ContentTime first_video = c->first_video().get() + _pts_offset;
                ContentTime const old_first_video = first_video;
                _pts_offset += first_video.round_up (c->video_frame_rate ()) - old_first_video;
                ContentTime first_video = c->first_video().get() + _pts_offset;
                ContentTime const old_first_video = first_video;
                _pts_offset += first_video.round_up (c->video_frame_rate ()) - old_first_video;
@@ -125,10 +132,8 @@ FFmpegDecoder::flush ()
        
        while (decode_video_packet ()) {}
        
        
        while (decode_video_packet ()) {}
        
-       if (_ffmpeg_content->audio_stream()) {
-               decode_audio_packet ();
-               AudioDecoder::flush ();
-       }
+       decode_audio_packet ();
+       AudioDecoder::flush ();
 }
 
 bool
 }
 
 bool
@@ -157,7 +162,7 @@ FFmpegDecoder::pass (PassReason reason)
 
        if (si == _video_stream && !_ignore_video && reason != PASS_REASON_SUBTITLE) {
                decode_video_packet ();
 
        if (si == _video_stream && !_ignore_video && reason != PASS_REASON_SUBTITLE) {
                decode_video_packet ();
-       } else if (fc->audio_stream() && fc->audio_stream()->uses_index (_format_context, si) && reason != PASS_REASON_SUBTITLE) {
+       } else if (reason != PASS_REASON_SUBTITLE) {
                decode_audio_packet ();
        } else if (fc->subtitle_stream() && fc->subtitle_stream()->uses_index (_format_context, si)) {
                decode_subtitle_packet ();
                decode_audio_packet ();
        } else if (fc->subtitle_stream() && fc->subtitle_stream()->uses_index (_format_context, si)) {
                decode_subtitle_packet ();
@@ -171,21 +176,20 @@ FFmpegDecoder::pass (PassReason reason)
  *  Only the first buffer will be used for non-planar data, otherwise there will be one per channel.
  */
 shared_ptr<AudioBuffers>
  *  Only the first buffer will be used for non-planar data, otherwise there will be one per channel.
  */
 shared_ptr<AudioBuffers>
-FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
+FFmpegDecoder::deinterleave_audio (shared_ptr<FFmpegAudioStream> stream, uint8_t** data, int size)
 {
 {
-       DCPOMATIC_ASSERT (_ffmpeg_content->audio_channels());
-       DCPOMATIC_ASSERT (bytes_per_audio_sample());
+       DCPOMATIC_ASSERT (bytes_per_audio_sample (stream));
 
        /* Deinterleave and convert to float */
 
        /* total_samples and frames will be rounded down here, so if there are stray samples at the end
           of the block that do not form a complete sample or frame they will be dropped.
        */
 
        /* Deinterleave and convert to float */
 
        /* total_samples and frames will be rounded down here, so if there are stray samples at the end
           of the block that do not form a complete sample or frame they will be dropped.
        */
-       int const total_samples = size / bytes_per_audio_sample();
-       int const frames = total_samples / _ffmpeg_content->audio_channels();
-       shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames));
+       int const total_samples = size / bytes_per_audio_sample (stream);
+       int const frames = total_samples / stream->channels();
+       shared_ptr<AudioBuffers> audio (new AudioBuffers (stream->channels(), frames));
 
 
-       switch (audio_sample_format()) {
+       switch (audio_sample_format (stream)) {
        case AV_SAMPLE_FMT_U8:
        {
                uint8_t* p = reinterpret_cast<uint8_t *> (data[0]);
        case AV_SAMPLE_FMT_U8:
        {
                uint8_t* p = reinterpret_cast<uint8_t *> (data[0]);
@@ -195,7 +199,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
                        audio->data(channel)[sample] = float(*p++) / (1 << 23);
 
                        ++channel;
                        audio->data(channel)[sample] = float(*p++) / (1 << 23);
 
                        ++channel;
-                       if (channel == _ffmpeg_content->audio_channels()) {
+                       if (channel == stream->channels()) {
                                channel = 0;
                                ++sample;
                        }
                                channel = 0;
                                ++sample;
                        }
@@ -212,7 +216,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
                        audio->data(channel)[sample] = float(*p++) / (1 << 15);
 
                        ++channel;
                        audio->data(channel)[sample] = float(*p++) / (1 << 15);
 
                        ++channel;
-                       if (channel == _ffmpeg_content->audio_channels()) {
+                       if (channel == stream->channels()) {
                                channel = 0;
                                ++sample;
                        }
                                channel = 0;
                                ++sample;
                        }
@@ -223,7 +227,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
        case AV_SAMPLE_FMT_S16P:
        {
                int16_t** p = reinterpret_cast<int16_t **> (data);
        case AV_SAMPLE_FMT_S16P:
        {
                int16_t** p = reinterpret_cast<int16_t **> (data);
-               for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
+               for (int i = 0; i < stream->channels(); ++i) {
                        for (int j = 0; j < frames; ++j) {
                                audio->data(i)[j] = static_cast<float>(p[i][j]) / (1 << 15);
                        }
                        for (int j = 0; j < frames; ++j) {
                                audio->data(i)[j] = static_cast<float>(p[i][j]) / (1 << 15);
                        }
@@ -240,7 +244,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
                        audio->data(channel)[sample] = static_cast<float>(*p++) / (1 << 31);
 
                        ++channel;
                        audio->data(channel)[sample] = static_cast<float>(*p++) / (1 << 31);
 
                        ++channel;
-                       if (channel == _ffmpeg_content->audio_channels()) {
+                       if (channel == stream->channels()) {
                                channel = 0;
                                ++sample;
                        }
                                channel = 0;
                                ++sample;
                        }
@@ -257,7 +261,7 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
                        audio->data(channel)[sample] = *p++;
 
                        ++channel;
                        audio->data(channel)[sample] = *p++;
 
                        ++channel;
-                       if (channel == _ffmpeg_content->audio_channels()) {
+                       if (channel == stream->channels()) {
                                channel = 0;
                                ++sample;
                        }
                                channel = 0;
                                ++sample;
                        }
@@ -268,33 +272,29 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
        case AV_SAMPLE_FMT_FLTP:
        {
                float** p = reinterpret_cast<float**> (data);
        case AV_SAMPLE_FMT_FLTP:
        {
                float** p = reinterpret_cast<float**> (data);
-               for (int i = 0; i < _ffmpeg_content->audio_channels(); ++i) {
+               for (int i = 0; i < stream->channels(); ++i) {
                        memcpy (audio->data(i), p[i], frames * sizeof(float));
                }
        }
        break;
 
        default:
                        memcpy (audio->data(i), p[i], frames * sizeof(float));
                }
        }
        break;
 
        default:
-               throw DecodeError (String::compose (_("Unrecognised audio sample format (%1)"), static_cast<int> (audio_sample_format())));
+               throw DecodeError (String::compose (_("Unrecognised audio sample format (%1)"), static_cast<int> (audio_sample_format (stream))));
        }
 
        return audio;
 }
 
 AVSampleFormat
        }
 
        return audio;
 }
 
 AVSampleFormat
-FFmpegDecoder::audio_sample_format () const
+FFmpegDecoder::audio_sample_format (shared_ptr<FFmpegAudioStream> stream) const
 {
 {
-       if (!_ffmpeg_content->audio_stream()) {
-               return (AVSampleFormat) 0;
-       }
-       
-       return audio_codec_context()->sample_fmt;
+       return stream->stream (_format_context)->codec->sample_fmt;
 }
 
 int
 }
 
 int
-FFmpegDecoder::bytes_per_audio_sample () const
+FFmpegDecoder::bytes_per_audio_sample (shared_ptr<FFmpegAudioStream> stream) const
 {
 {
-       return av_get_bytes_per_sample (audio_sample_format ());
+       return av_get_bytes_per_sample (audio_sample_format (stream));
 }
 
 void
 }
 
 void
@@ -319,9 +319,9 @@ FFmpegDecoder::seek (ContentTime time, bool accurate)
        av_seek_frame (_format_context, _video_stream, u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base), 0);
 
        avcodec_flush_buffers (video_codec_context());
        av_seek_frame (_format_context, _video_stream, u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base), 0);
 
        avcodec_flush_buffers (video_codec_context());
-       if (audio_codec_context ()) {
-               avcodec_flush_buffers (audio_codec_context ());
-       }
+
+       /* XXX: should be flushing audio buffers? */
+       
        if (subtitle_codec_context ()) {
                avcodec_flush_buffers (subtitle_codec_context ());
        }
        if (subtitle_codec_context ()) {
                avcodec_flush_buffers (subtitle_codec_context ());
        }
@@ -335,11 +335,23 @@ FFmpegDecoder::decode_audio_packet ()
        */
        
        AVPacket copy_packet = _packet;
        */
        
        AVPacket copy_packet = _packet;
+
+       /* XXX: inefficient */
+       vector<shared_ptr<FFmpegAudioStream> > streams = ffmpeg_content()->ffmpeg_audio_streams ();
+       vector<shared_ptr<FFmpegAudioStream> >::const_iterator stream = streams.begin ();
+       while (stream != streams.end () && !(*stream)->uses_index (_format_context, copy_packet.stream_index)) {
+               ++stream;
+       }
+
+       if (stream == streams.end ()) {
+               /* The packet's stream may not be an audio one; just ignore it in this method if so */
+               return;
+       }
        
        while (copy_packet.size > 0) {
 
                int frame_finished;
        
        while (copy_packet.size > 0) {
 
                int frame_finished;
-               int decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
+               int decode_result = avcodec_decode_audio4 ((*stream)->stream (_format_context)->codec, _frame, &frame_finished, &copy_packet);
                if (decode_result < 0) {
                        /* avcodec_decode_audio4 can sometimes return an error even though it has decoded
                           some valid data; for example dca_subframe_footer can return AVERROR_INVALIDDATA
                if (decode_result < 0) {
                        /* avcodec_decode_audio4 can sometimes return an error even though it has decoded
                           some valid data; for example dca_subframe_footer can return AVERROR_INVALIDDATA
@@ -359,14 +371,14 @@ FFmpegDecoder::decode_audio_packet ()
                if (frame_finished) {
                        ContentTime const ct = ContentTime::from_seconds (
                                av_frame_get_best_effort_timestamp (_frame) *
                if (frame_finished) {
                        ContentTime const ct = ContentTime::from_seconds (
                                av_frame_get_best_effort_timestamp (_frame) *
-                               av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base))
+                               av_q2d ((*stream)->stream (_format_context)->time_base))
                                + _pts_offset;
                        
                        int const data_size = av_samples_get_buffer_size (
                                + _pts_offset;
                        
                        int const data_size = av_samples_get_buffer_size (
-                               0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
+                               0, (*stream)->stream(_format_context)->codec->channels, _frame->nb_samples, audio_sample_format (*stream), 1
                                );
 
                                );
 
-                       audio (deinterleave_audio (_frame->data, data_size), ct);
+                       audio (*stream, deinterleave_audio (*stream, _frame->data, data_size), ct);
                }
                        
                copy_packet.data += decode_result;
                }
                        
                copy_packet.data += decode_result;
index 6f027ce1c1ba2687df7d774aa22207883f373a27..ec975f439e31e2ce4f9bf411d31c8392d415ac46 100644 (file)
@@ -40,6 +40,7 @@ extern "C" {
 
 class Log;
 class FilterGraph;
 
 class Log;
 class FilterGraph;
+class FFmpegAudioStream;
 struct ffmpeg_pts_offset_test;
 
 /** @class FFmpegDecoder
 struct ffmpeg_pts_offset_test;
 
 /** @class FFmpegDecoder
@@ -57,8 +58,8 @@ private:
        void seek (ContentTime time, bool);
        void flush ();
 
        void seek (ContentTime time, bool);
        void flush ();
 
-       AVSampleFormat audio_sample_format () const;
-       int bytes_per_audio_sample () const;
+       AVSampleFormat audio_sample_format (boost::shared_ptr<FFmpegAudioStream> stream) const;
+       int bytes_per_audio_sample (boost::shared_ptr<FFmpegAudioStream> stream) const;
 
        bool decode_video_packet ();
        void decode_audio_packet ();
 
        bool decode_video_packet ();
        void decode_audio_packet ();
@@ -67,7 +68,7 @@ private:
        void decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTimePeriod period);
 
        void maybe_add_subtitle ();
        void decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTimePeriod period);
 
        void maybe_add_subtitle ();
-       boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
+       boost::shared_ptr<AudioBuffers> deinterleave_audio (boost::shared_ptr<FFmpegAudioStream> stream, uint8_t** data, int size);
 
        std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod, bool starting) const;
        std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod, bool starting) const;
 
        std::list<ContentTimePeriod> image_subtitles_during (ContentTimePeriod, bool starting) const;
        std::list<ContentTimePeriod> text_subtitles_during (ContentTimePeriod, bool starting) const;
index e5c5d931265a41bf293d2eef940251d7c85811eb..edcb124e57e8edaedb91bb956a9dc8a0e15fcbe0 100644 (file)
@@ -983,9 +983,7 @@ Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
 {
        if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
                set_video_frame_rate (_playlist->best_dcp_frame_rate ());
 {
        if (p == VideoContentProperty::VIDEO_FRAME_RATE) {
                set_video_frame_rate (_playlist->best_dcp_frame_rate ());
-       } else if (
-               p == AudioContentProperty::AUDIO_MAPPING ||
-               p == AudioContentProperty::AUDIO_CHANNELS) {
+       } else if (p == AudioContentProperty::AUDIO_STREAMS) {
                signal_changed (NAME);
        }
 
                signal_changed (NAME);
        }
 
index be00aebc7d7657449a88cf94666693c439a5d3f9..b81eb4d807ae4c94422e3a4e41ea7243223b86a2 100644 (file)
@@ -414,13 +414,6 @@ Player::get_audio (DCPTime time, DCPTime length, bool accurate)
                shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
                DCPOMATIC_ASSERT (decoder);
 
                shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
                DCPOMATIC_ASSERT (decoder);
 
-               if (content->audio_frame_rate() == 0) {
-                       /* This AudioContent has no audio (e.g. if it is an FFmpegContent with no
-                        * audio stream).
-                        */
-                       continue;
-               }
-
                /* The time that we should request from the content */
                DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0);
                Frame request_frames = length_frames;
                /* The time that we should request from the content */
                DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0);
                Frame request_frames = length_frames;
@@ -439,41 +432,44 @@ Player::get_audio (DCPTime time, DCPTime length, bool accurate)
 
                Frame const content_frame = dcp_to_content_audio (*i, request);
 
 
                Frame const content_frame = dcp_to_content_audio (*i, request);
 
-               /* Audio from this piece's decoder (which might be more or less than what we asked for) */
-               shared_ptr<ContentAudio> all = decoder->get_audio (content_frame, request_frames, accurate);
-
-               /* Gain */
-               if (content->audio_gain() != 0) {
-                       shared_ptr<AudioBuffers> gain (new AudioBuffers (all->audio));
-                       gain->apply_gain (content->audio_gain ());
-                       all->audio = gain;
-               }
+               BOOST_FOREACH (AudioStreamPtr j, content->audio_streams ()) {
+                       
+                       /* Audio from this piece's decoder stream (which might be more or less than what we asked for) */
+                       ContentAudio all = decoder->get_audio (j, content_frame, request_frames, accurate);
+
+                       /* Gain */
+                       if (content->audio_gain() != 0) {
+                               shared_ptr<AudioBuffers> gain (new AudioBuffers (all.audio));
+                               gain->apply_gain (content->audio_gain ());
+                               all.audio = gain;
+                       }
 
 
-               /* Remap channels */
-               shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all->audio->frames()));
-               dcp_mapped->make_silent ();
-               AudioMapping map = content->audio_mapping ();
-               for (int i = 0; i < map.content_channels(); ++i) {
-                       for (int j = 0; j < _film->audio_channels(); ++j) {
-                               if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
-                                       dcp_mapped->accumulate_channel (
-                                               all->audio.get(),
-                                               i,
-                                               j,
-                                               map.get (i, static_cast<dcp::Channel> (j))
-                                               );
+                       /* Remap channels */
+                       shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all.audio->frames()));
+                       dcp_mapped->make_silent ();
+                       AudioMapping map = j->mapping ();
+                       for (int i = 0; i < map.content_channels(); ++i) {
+                               for (int j = 0; j < _film->audio_channels(); ++j) {
+                                       if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
+                                               dcp_mapped->accumulate_channel (
+                                                       all.audio.get(),
+                                                       i,
+                                                       j,
+                                                       map.get (i, static_cast<dcp::Channel> (j))
+                                                       );
+                                       }
                                }
                        }
                                }
                        }
-               }
                
                
-               all->audio = dcp_mapped;
+                       all.audio = dcp_mapped;
 
 
-               audio->accumulate_frames (
-                       all->audio.get(),
-                       content_frame - all->frame,
-                       offset.frames (_film->audio_frame_rate()),
-                       min (Frame (all->audio->frames()), request_frames)
-                       );
+                       audio->accumulate_frames (
+                               all.audio.get(),
+                               content_frame - all.frame,
+                               offset.frames (_film->audio_frame_rate()),
+                               min (Frame (all.audio->frames()), request_frames)
+                               );
+               }
        }
 
        return audio;
        }
 
        return audio;
index 76f6e00d96f1b964ecdd7439bfe9b10eda41d81d..3389c0557f6a233a0e9ec1ef8f729a8e384471e8 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
-    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2014-2015 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
 
     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::string;
 using std::cout;
 
 using std::string;
 using std::cout;
+using std::vector;
 using boost::shared_ptr;
 
 SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f)
        : Content (f)
        , AudioContent (f)
 using boost::shared_ptr;
 
 SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f)
        : Content (f)
        , AudioContent (f)
-       , _audio_channels (0)
-       , _audio_length (0)
-       , _audio_frame_rate (0)
 {
 
 }
 {
 
 }
@@ -39,9 +37,6 @@ SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f)
 SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f, boost::filesystem::path p)
        : Content (f, p)
        , AudioContent (f, p)
 SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f, boost::filesystem::path p)
        : Content (f, p)
        , AudioContent (f, p)
-       , _audio_channels (0)
-       , _audio_length (0)
-       , _audio_frame_rate (0)
 {
 
 }
 {
 
 }
@@ -49,33 +44,17 @@ SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f, bo
 SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
        : Content (f, node)
        , AudioContent (f, node)
 SingleStreamAudioContent::SingleStreamAudioContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
        : Content (f, node)
        , AudioContent (f, node)
-       , _audio_mapping (node->node_child ("AudioMapping"), version)
+       , _audio_stream (new AudioStream (node->number_child<int> ("AudioFrameRate"), AudioMapping (node->node_child ("AudioMapping"), version)))
 {
 {
-       _audio_channels = node->number_child<int> ("AudioChannels");
-       _audio_length = node->number_child<Frame> ("AudioLength");
-       _audio_frame_rate = node->number_child<int> ("AudioFrameRate");
-}
-
-void
-SingleStreamAudioContent::set_audio_mapping (AudioMapping m)
-{
-       {
-               boost::mutex::scoped_lock lm (_mutex);
-               _audio_mapping = m;
-       }
 
 
-       AudioContent::set_audio_mapping (m);
 }
 
 }
 
-
 void
 SingleStreamAudioContent::as_xml (xmlpp::Node* node) const
 {
        AudioContent::as_xml (node);
 void
 SingleStreamAudioContent::as_xml (xmlpp::Node* node) const
 {
        AudioContent::as_xml (node);
-       node->add_child("AudioChannels")->add_child_text (raw_convert<string> (audio_channels ()));
-       node->add_child("AudioLength")->add_child_text (raw_convert<string> (audio_length ()));
-       node->add_child("AudioFrameRate")->add_child_text (raw_convert<string> (audio_frame_rate ()));
-       _audio_mapping.as_xml (node->add_child("AudioMapping"));
+       node->add_child("AudioFrameRate")->add_child_text (raw_convert<string> (audio_stream()->frame_rate ()));
+       audio_stream()->mapping().as_xml (node->add_child("AudioMapping"));
 }
 
 void
 }
 
 void
@@ -83,22 +62,17 @@ SingleStreamAudioContent::take_from_audio_examiner (shared_ptr<AudioExaminer> ex
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
-               _audio_channels = examiner->audio_channels ();
-               _audio_length = examiner->audio_length ();
-               _audio_frame_rate = examiner->audio_frame_rate ();
+               _audio_stream.reset (new AudioStream (examiner->audio_frame_rate(), examiner->audio_channels ()));
+               _audio_stream->mapping().make_default ();
        }
 
        }
 
-       signal_changed (AudioContentProperty::AUDIO_CHANNELS);
-       signal_changed (AudioContentProperty::AUDIO_FRAME_RATE);
-
-       int const p = processed_audio_channels ();
+       signal_changed (AudioContentProperty::AUDIO_STREAMS);
+}
 
 
-       {
-               boost::mutex::scoped_lock lm (_mutex);
-               /* XXX: do this in signal_changed...? */
-               _audio_mapping = AudioMapping (p);
-               _audio_mapping.make_default ();
-       }
-       
-       signal_changed (AudioContentProperty::AUDIO_MAPPING);
+vector<AudioStreamPtr>
+SingleStreamAudioContent::audio_streams () const
+{
+       vector<AudioStreamPtr> s;
+       s.push_back (_audio_stream);
+       return s;
 }
 }
index 944d887b0ba4ae9ec293efe9e6b1385697e9372c..d8fcb8df0193237da7d1b687b33bc915269307de 100644 (file)
@@ -40,35 +40,16 @@ public:
 
        void as_xml (xmlpp::Node* node) const;
 
 
        void as_xml (xmlpp::Node* node) const;
 
-       int audio_channels () const {
-               boost::mutex::scoped_lock lm (_mutex);
-               return _audio_channels;
-       }
-
-       Frame audio_length () const {
-               boost::mutex::scoped_lock lm (_mutex);
-               return _audio_length;
-       }
-       
-       int audio_frame_rate () const {
-               boost::mutex::scoped_lock lm (_mutex);
-               return _audio_frame_rate;
-       }
+       std::vector<AudioStreamPtr> audio_streams () const;
 
 
-       AudioMapping audio_mapping () const {
-               boost::mutex::scoped_lock lm (_mutex);
-               return _audio_mapping;
+       AudioStreamPtr audio_stream () const {
+               return _audio_stream;
        }
        }
-
-       void set_audio_mapping (AudioMapping);
-
+               
        void take_from_audio_examiner (boost::shared_ptr<AudioExaminer>);
 
 protected:
        void take_from_audio_examiner (boost::shared_ptr<AudioExaminer>);
 
 protected:
-       int _audio_channels;
-       Frame _audio_length;
-       int _audio_frame_rate;
-       AudioMapping _audio_mapping;
+       boost::shared_ptr<AudioStream> _audio_stream;
 };
 
 #endif
 };
 
 #endif
diff --git a/src/lib/sndfile_base.cc b/src/lib/sndfile_base.cc
new file mode 100644 (file)
index 0000000..b7ac12c
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+    Copyright (C) 2012-2015 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 "sndfile_base.h"
+#include "sndfile_content.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using boost::shared_ptr;
+
+Sndfile::Sndfile (shared_ptr<const SndfileContent> c)
+       : _sndfile_content (c)
+{
+       _info.format = 0;
+
+       /* Here be monsters.  See fopen_boost for similar shenanigans */
+#ifdef DCPOMATIC_WINDOWS
+       _sndfile = sf_wchar_open (_sndfile_content->path(0).c_str(), SFM_READ, &_info);
+#else  
+       _sndfile = sf_open (_sndfile_content->path(0).string().c_str(), SFM_READ, &_info);
+#endif
+       
+       if (!_sndfile) {
+               throw DecodeError (_("could not open audio file for reading"));
+       }
+}
+
+Sndfile::~Sndfile ()
+{
+       sf_close (_sndfile);
+}
diff --git a/src/lib/sndfile_base.h b/src/lib/sndfile_base.h
new file mode 100644 (file)
index 0000000..6c4f021
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+    Copyright (C) 2012-2015 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.
+
+*/
+
+#ifndef DCPOMATIC_SNDFILE_BASE_H
+#define DCPOMATIC_SNDFILE_BASE_H
+
+#include <sndfile.h>
+#include <boost/shared_ptr.hpp>
+
+class SndfileContent;
+
+class Sndfile
+{
+public:
+       Sndfile (boost::shared_ptr<const SndfileContent> content);
+       virtual ~Sndfile ();
+
+protected:     
+       boost::shared_ptr<const SndfileContent> _sndfile_content;
+       SNDFILE* _sndfile;
+       SF_INFO _info;
+};
+
+#endif
index 0eb7efb2f92bd5d96181c9cdf352e7f585cd9760..cfee7bd381b3fc6b615291f8ad590c064e6dc5dd 100644 (file)
 
 */
 
 
 */
 
-#include <libcxml/cxml.h>
 #include "sndfile_content.h"
 #include "sndfile_decoder.h"
 #include "sndfile_content.h"
 #include "sndfile_decoder.h"
+#include "sndfile_examiner.h"
 #include "film.h"
 #include "compose.hpp"
 #include "job.h"
 #include "util.h"
 #include "safe_stringstream.h"
 #include "film.h"
 #include "compose.hpp"
 #include "job.h"
 #include "util.h"
 #include "safe_stringstream.h"
+#include "raw_convert.h"
+#include <libcxml/cxml.h>
 
 #include "i18n.h"
 
 
 #include "i18n.h"
 
@@ -42,8 +44,9 @@ SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::pat
 SndfileContent::SndfileContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
        : Content (f, node)
        , SingleStreamAudioContent (f, node, version)
 SndfileContent::SndfileContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
        : Content (f, node)
        , SingleStreamAudioContent (f, node, version)
+       , _audio_length (node->number_child<int64_t> ("AudioLength"))
 {
 {
-       
+
 }
 
 void
 }
 
 void
@@ -52,6 +55,7 @@ SndfileContent::as_xml (xmlpp::Node* node) const
        node->add_child("Type")->add_child_text ("Sndfile");
        Content::as_xml (node);
        SingleStreamAudioContent::as_xml (node);
        node->add_child("Type")->add_child_text ("Sndfile");
        Content::as_xml (node);
        SingleStreamAudioContent::as_xml (node);
+       node->add_child("AudioLength")->add_child_text (raw_convert<string> (audio_length ()));
 }
 
 
 }
 
 
@@ -84,16 +88,25 @@ SndfileContent::examine (shared_ptr<Job> job)
 {
        job->set_progress_unknown ();
        Content::examine (job);
 {
        job->set_progress_unknown ();
        Content::examine (job);
-       shared_ptr<AudioExaminer> dec (new SndfileDecoder (shared_from_this()));
+       shared_ptr<AudioExaminer> dec (new SndfileExaminer (shared_from_this ()));
        take_from_audio_examiner (dec);
 }
 
        take_from_audio_examiner (dec);
 }
 
+void
+SndfileContent::take_from_audio_examiner (shared_ptr<AudioExaminer> examiner)
+{
+       SingleStreamAudioContent::take_from_audio_examiner (examiner);
+
+       boost::mutex::scoped_lock lm (_mutex);
+       _audio_length = examiner->audio_length ();
+}
+
 DCPTime
 SndfileContent::full_length () const
 {
        shared_ptr<const Film> film = _film.lock ();
        DCPOMATIC_ASSERT (film);
        FrameRateChange const frc = film->active_frame_rate_change (position ());
 DCPTime
 SndfileContent::full_length () const
 {
        shared_ptr<const Film> film = _film.lock ();
        DCPOMATIC_ASSERT (film);
        FrameRateChange const frc = film->active_frame_rate_change (position ());
-       return DCPTime::from_frames (audio_length() / frc.speed_up, audio_frame_rate ());
+       return DCPTime::from_frames (audio_length() / frc.speed_up, audio_stream()->frame_rate ());
 }
 
 }
 
index 1bac51167cad010cf2aceed5f07a088e420cf432..f5a2e38b1310a0fb8f4bac0ea7f3e4ebf4933a3a 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
-    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2015 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
 
     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
@@ -46,8 +46,18 @@ public:
        std::string technical_summary () const;
        std::string information () const;
        void as_xml (xmlpp::Node *) const;
        std::string technical_summary () const;
        std::string information () const;
        void as_xml (xmlpp::Node *) const;
+
+       void take_from_audio_examiner (boost::shared_ptr<AudioExaminer>);
        
        static bool valid_file (boost::filesystem::path);
        
        static bool valid_file (boost::filesystem::path);
+
+private:
+       Frame audio_length () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _audio_length;
+       }
+       
+       Frame _audio_length;
 };
 
 #endif
 };
 
 #endif
index ecd1a7e16d46d74ba49e84c2a22482e24839b38c..2c47fcd9e9ca9ddff3946590dc9008d3ff8d8815 100644 (file)
@@ -37,30 +37,17 @@ using std::cout;
 using boost::shared_ptr;
 
 SndfileDecoder::SndfileDecoder (shared_ptr<const SndfileContent> c)
 using boost::shared_ptr;
 
 SndfileDecoder::SndfileDecoder (shared_ptr<const SndfileContent> c)
-       : AudioDecoder (c)
-       , _sndfile_content (c)
+       : Sndfile (c)
+       , AudioDecoder (c)
+       , _done (0)
+       , _remaining (_info.frames)
        , _deinterleave_buffer (0)
 {
        , _deinterleave_buffer (0)
 {
-       _info.format = 0;
-
-       /* Here be monsters.  See fopen_boost for similar shenanigans */
-#ifdef DCPOMATIC_WINDOWS
-       _sndfile = sf_wchar_open (_sndfile_content->path(0).c_str(), SFM_READ, &_info);
-#else  
-       _sndfile = sf_open (_sndfile_content->path(0).string().c_str(), SFM_READ, &_info);
-#endif
        
        
-       if (!_sndfile) {
-               throw DecodeError (_("could not open audio file for reading"));
-       }
-
-       _done = 0;
-       _remaining = _info.frames;
 }
 
 SndfileDecoder::~SndfileDecoder ()
 {
 }
 
 SndfileDecoder::~SndfileDecoder ()
 {
-       sf_close (_sndfile);
        delete[] _deinterleave_buffer;
 }
 
        delete[] _deinterleave_buffer;
 }
 
@@ -74,14 +61,14 @@ SndfileDecoder::pass (PassReason)
        /* Do things in half second blocks as I think there may be limits
           to what FFmpeg (and in particular the resampler) can cope with.
        */
        /* Do things in half second blocks as I think there may be limits
           to what FFmpeg (and in particular the resampler) can cope with.
        */
-       sf_count_t const block = _sndfile_content->audio_frame_rate() / 2;
+       sf_count_t const block = _sndfile_content->audio_stream()->frame_rate() / 2;
        sf_count_t const this_time = min (block, _remaining);
 
        sf_count_t const this_time = min (block, _remaining);
 
-       int const channels = _sndfile_content->audio_channels ();
+       int const channels = _sndfile_content->audio_stream()->channels ();
        
        shared_ptr<AudioBuffers> data (new AudioBuffers (channels, this_time));
 
        
        shared_ptr<AudioBuffers> data (new AudioBuffers (channels, this_time));
 
-       if (_sndfile_content->audio_channels() == 1) {
+       if (_sndfile_content->audio_stream()->channels() == 1) {
                /* No de-interleaving required */
                sf_read_float (_sndfile, data->data(0), this_time);
        } else {
                /* No de-interleaving required */
                sf_read_float (_sndfile, data->data(0), this_time);
        } else {
@@ -103,36 +90,18 @@ SndfileDecoder::pass (PassReason)
        }
                
        data->set_frames (this_time);
        }
                
        data->set_frames (this_time);
-       audio (data, ContentTime::from_frames (_done, audio_frame_rate ()));
+       audio (_sndfile_content->audio_stream (), data, ContentTime::from_frames (_done, _info.samplerate));
        _done += this_time;
        _remaining -= this_time;
 
        return _remaining == 0;
 }
 
        _done += this_time;
        _remaining -= this_time;
 
        return _remaining == 0;
 }
 
-int
-SndfileDecoder::audio_channels () const
-{
-       return _info.channels;
-}
-
-Frame
-SndfileDecoder::audio_length () const
-{
-       return _info.frames;
-}
-
-int
-SndfileDecoder::audio_frame_rate () const
-{
-       return _info.samplerate;
-}
-
 void
 SndfileDecoder::seek (ContentTime t, bool accurate)
 {
        AudioDecoder::seek (t, accurate);
 
 void
 SndfileDecoder::seek (ContentTime t, bool accurate)
 {
        AudioDecoder::seek (t, accurate);
 
-       _done = t.frames (audio_frame_rate ());
+       _done = t.frames (_info.samplerate);
        _remaining = _info.frames - _done;
 }
        _remaining = _info.frames - _done;
 }
index 932394626407df4d18292344da435bb4ba36fc8a..4474adc9e3b37dba35bf62bd689ec318e973e643 100644 (file)
 #include "decoder.h"
 #include "audio_decoder.h"
 #include "audio_examiner.h"
 #include "decoder.h"
 #include "audio_decoder.h"
 #include "audio_examiner.h"
-#include <sndfile.h>
+#include "sndfile_base.h"
 
 class SndfileContent;
 
 
 class SndfileContent;
 
-class SndfileDecoder : public AudioDecoder, public AudioExaminer
+class SndfileDecoder : public Sndfile, public AudioDecoder
 {
 public:
        SndfileDecoder (boost::shared_ptr<const SndfileContent> c);
        ~SndfileDecoder ();
 
 {
 public:
        SndfileDecoder (boost::shared_ptr<const SndfileContent> c);
        ~SndfileDecoder ();
 
-       int audio_channels () const;
-       Frame audio_length () const;
-       int audio_frame_rate () const;
-
 private:
        bool pass (PassReason);
        void seek (ContentTime, bool);
        
 private:
        bool pass (PassReason);
        void seek (ContentTime, bool);
        
-       boost::shared_ptr<const SndfileContent> _sndfile_content;
-       SNDFILE* _sndfile;
-       SF_INFO _info;
        int64_t _done;
        int64_t _remaining;
        float* _deinterleave_buffer;
        int64_t _done;
        int64_t _remaining;
        float* _deinterleave_buffer;
diff --git a/src/lib/sndfile_examiner.cc b/src/lib/sndfile_examiner.cc
new file mode 100644 (file)
index 0000000..5f2338a
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+    Copyright (C) 2015 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 "sndfile_examiner.h"
+
+using boost::shared_ptr;
+
+SndfileExaminer::SndfileExaminer (shared_ptr<const SndfileContent> content)
+       : Sndfile (content)
+{
+
+}
+
+int
+SndfileExaminer::audio_channels () const
+{
+       return _info.channels;
+}
+
+Frame
+SndfileExaminer::audio_length () const
+{
+       return _info.frames;
+}
+
+int
+SndfileExaminer::audio_frame_rate () const
+{
+       return _info.samplerate;
+}
diff --git a/src/lib/sndfile_examiner.h b/src/lib/sndfile_examiner.h
new file mode 100644 (file)
index 0000000..b3ab3c4
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+    Copyright (C) 2015 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 "sndfile_base.h"
+#include "audio_examiner.h"
+
+class SndfileExaminer : public Sndfile, public AudioExaminer
+{
+public:
+       SndfileExaminer (boost::shared_ptr<const SndfileContent> content);
+       
+       int audio_channels () const;
+       Frame audio_length () const;
+       int audio_frame_rate () const;
+};
index 5956c73d65c4f35ed1ef885f1529f4fefa57d9b0..258ad96a0818fe7eb2850a55dc12db50cfa6d338 100644 (file)
@@ -25,9 +25,11 @@ sources = """
           audio_buffers.cc
           audio_content.cc
           audio_decoder.cc
           audio_buffers.cc
           audio_content.cc
           audio_decoder.cc
+          audio_decoder_stream.cc
           audio_filter.cc
           audio_mapping.cc
           audio_processor.cc
           audio_filter.cc
           audio_mapping.cc
           audio_processor.cc
+          audio_stream.cc
           cinema.cc
           cinema_sound_processor.cc
           colour_conversion.cc
           cinema.cc
           cinema_sound_processor.cc
           colour_conversion.cc
@@ -98,8 +100,10 @@ sources = """
           server.cc
           server_finder.cc
           single_stream_audio_content.cc
           server.cc
           server_finder.cc
           single_stream_audio_content.cc
+          sndfile_base.cc
           sndfile_content.cc
           sndfile_decoder.cc
           sndfile_content.cc
           sndfile_decoder.cc
+          sndfile_examiner.cc
           subrip.cc
           subrip_content.cc
           subrip_decoder.cc
           subrip.cc
           subrip_content.cc
           subrip_decoder.cc
index fcae9c30f9f85835adb0b3833d785cb5ec0cd4f7..11de9586b4d62fc441c1f915375a35a9228db1f7 100644 (file)
@@ -196,7 +196,7 @@ AudioDialog::content_changed (int p)
        if (p == AudioContentProperty::AUDIO_GAIN) {
                _plot->set_gain (_content->audio_gain ());
                setup_peak_time ();
        if (p == AudioContentProperty::AUDIO_GAIN) {
                _plot->set_gain (_content->audio_gain ());
                setup_peak_time ();
-       } else if (p == AudioContentProperty::AUDIO_MAPPING) {
+       } else if (p == AudioContentProperty::AUDIO_STREAMS) {
                try_to_load_analysis ();
        }
 }
                try_to_load_analysis ();
        }
 }
index 4d783ca9da71b9e6f5ec69c2b87fb7afaf8ee6e2..8b2682762c4984fbfcd83e03958481eb07982767 100644 (file)
@@ -19,8 +19,6 @@
 
 #include "lib/config.h"
 #include "lib/ffmpeg_content.h"
 
 #include "lib/config.h"
 #include "lib/ffmpeg_content.h"
-#include "lib/ffmpeg_audio_stream.h"
-#include "lib/audio_processor.h"
 #include "lib/cinema_sound_processor.h"
 #include "audio_dialog.h"
 #include "audio_panel.h"
 #include "lib/cinema_sound_processor.h"
 #include "audio_dialog.h"
 #include "audio_panel.h"
@@ -83,17 +81,6 @@ AudioPanel::AudioPanel (ContentPanel* p)
        add_label_to_grid_bag_sizer (grid, this, _("ms"), false, wxGBPosition (r, 2));
        ++r;
 
        add_label_to_grid_bag_sizer (grid, this, _("ms"), false, wxGBPosition (r, 2));
        ++r;
 
-       add_label_to_grid_bag_sizer (grid, this, _("Stream"), true, wxGBPosition (r, 0));
-       _stream = new wxChoice (this, wxID_ANY);
-       grid->Add (_stream, wxGBPosition (r, 1), wxGBSpan (1, 3), wxEXPAND);
-       ++r;
-
-       add_label_to_grid_bag_sizer (grid, this, _("Process with"), true, wxGBPosition (r, 0));
-       _processor = new wxChoice (this, wxID_ANY);
-       setup_processors ();
-       grid->Add (_processor, wxGBPosition (r, 1), wxGBSpan (1, 3), wxEXPAND);
-       ++r;
-       
        _mapping = new AudioMappingView (this);
        _sizer->Add (_mapping, 1, wxEXPAND | wxALL, 6);
        ++r;
        _mapping = new AudioMappingView (this);
        _sizer->Add (_mapping, 1, wxEXPAND | wxALL, 6);
        ++r;
@@ -111,10 +98,8 @@ AudioPanel::AudioPanel (ContentPanel* p)
        _gain->wrapped()->SetIncrement (0.5);
        _delay->wrapped()->SetRange (-1000, 1000);
 
        _gain->wrapped()->SetIncrement (0.5);
        _delay->wrapped()->SetRange (-1000, 1000);
 
-       _stream->Bind                (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&AudioPanel::stream_changed, this));
        _show->Bind                  (wxEVT_COMMAND_BUTTON_CLICKED,  boost::bind (&AudioPanel::show_clicked, this));
        _gain_calculate_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED,  boost::bind (&AudioPanel::gain_calculate_button_clicked, this));
        _show->Bind                  (wxEVT_COMMAND_BUTTON_CLICKED,  boost::bind (&AudioPanel::show_clicked, this));
        _gain_calculate_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED,  boost::bind (&AudioPanel::gain_calculate_button_clicked, this));
-       _processor->Bind             (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&AudioPanel::processor_changed, this));
 
        _mapping_connection = _mapping->Changed.connect (boost::bind (&AudioPanel::mapping_changed, this, _1));
 }
 
        _mapping_connection = _mapping->Changed.connect (boost::bind (&AudioPanel::mapping_changed, this, _1));
 }
@@ -147,34 +132,10 @@ AudioPanel::film_content_changed (int property)
                fcs = dynamic_pointer_cast<FFmpegContent> (acs);
        }
        
                fcs = dynamic_pointer_cast<FFmpegContent> (acs);
        }
        
-       if (property == AudioContentProperty::AUDIO_MAPPING) {
+       if (property == AudioContentProperty::AUDIO_STREAMS) {
                _mapping->set (acs ? acs->audio_mapping () : AudioMapping ());
                _sizer->Layout ();
                _mapping->set (acs ? acs->audio_mapping () : AudioMapping ());
                _sizer->Layout ();
-       } else if (property == AudioContentProperty::AUDIO_FRAME_RATE) {
                setup_description ();
                setup_description ();
-       } else if (property == FFmpegContentProperty::AUDIO_STREAM) {
-               _mapping->set (acs ? acs->audio_mapping () : AudioMapping ());
-               _sizer->Layout ();
-       } else if (property == FFmpegContentProperty::AUDIO_STREAMS) {
-               if (fcs) {
-                       vector<pair<string, string> > data;
-                       BOOST_FOREACH (shared_ptr<FFmpegAudioStream> i, fcs->audio_streams ()) {
-                               data.push_back (make_pair (i->name, i->identifier ()));
-                       }
-                       checked_set (_stream, data);
-                       
-                       if (fcs->audio_stream()) {
-                               checked_set (_stream, fcs->audio_stream()->identifier ());
-                       }
-               } else {
-                       _stream->Clear ();
-               }
-       } else if (property == AudioContentProperty::AUDIO_PROCESSOR) {
-               if (acs) {
-                       checked_set (_processor, acs->audio_processor() ? acs->audio_processor()->id() : N_("none"));
-               } else {
-                       checked_set (_processor, N_("none"));
-               }
        }
 }
 
        }
 }
 
@@ -222,47 +183,6 @@ AudioPanel::show_clicked ()
        _audio_dialog->set_content (ac.front ());
 }
 
        _audio_dialog->set_content (ac.front ());
 }
 
-void
-AudioPanel::stream_changed ()
-{
-       FFmpegContentList fc = _parent->selected_ffmpeg ();
-       if (fc.size() != 1) {
-               return;
-       }
-
-       shared_ptr<FFmpegContent> fcs = fc.front ();
-       
-       if (_stream->GetSelection() == -1) {
-               return;
-       }
-       
-       vector<shared_ptr<FFmpegAudioStream> > a = fcs->audio_streams ();
-       vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin ();
-       string const s = string_client_data (_stream->GetClientObject (_stream->GetSelection ()));
-       while (i != a.end() && (*i)->identifier () != s) {
-               ++i;
-       }
-
-       if (i != a.end ()) {
-               fcs->set_audio_stream (*i);
-       }
-}
-
-void
-AudioPanel::processor_changed ()
-{
-       string const s = string_client_data (_processor->GetClientObject (_processor->GetSelection ()));
-       AudioProcessor const * p = 0;
-       if (s != wx_to_std (N_("none"))) {
-               p = AudioProcessor::from_id (s);
-       }
-               
-       AudioContentList c = _parent->selected_audio ();
-       for (AudioContentList::const_iterator i = c.begin(); i != c.end(); ++i) {
-               (*i)->set_audio_processor (p);
-       }
-}
-
 void
 AudioPanel::setup_description ()
 {
 void
 AudioPanel::setup_description ()
 {
@@ -298,36 +218,7 @@ AudioPanel::content_selection_changed ()
 
        _gain_calculate_button->Enable (sel.size() == 1);
        _show->Enable (sel.size() == 1);
 
        _gain_calculate_button->Enable (sel.size() == 1);
        _show->Enable (sel.size() == 1);
-       _stream->Enable (sel.size() == 1);
-       _processor->Enable (!sel.empty());
        _mapping->Enable (sel.size() == 1);
 
        _mapping->Enable (sel.size() == 1);
 
-       setup_processors ();
-
-       film_content_changed (AudioContentProperty::AUDIO_MAPPING);
-       film_content_changed (AudioContentProperty::AUDIO_PROCESSOR);
-       film_content_changed (AudioContentProperty::AUDIO_FRAME_RATE);
-       film_content_changed (FFmpegContentProperty::AUDIO_STREAM);
        film_content_changed (FFmpegContentProperty::AUDIO_STREAMS);
 }
        film_content_changed (FFmpegContentProperty::AUDIO_STREAMS);
 }
-
-void
-AudioPanel::setup_processors ()
-{
-       AudioContentList sel = _parent->selected_audio ();
-
-       _processor->Clear ();
-       list<AudioProcessor const *> ap = AudioProcessor::all ();
-       _processor->Append (_("None"), new wxStringClientData (N_("none")));
-       for (list<AudioProcessor const *>::const_iterator i = ap.begin(); i != ap.end(); ++i) {
-
-               AudioContentList::const_iterator j = sel.begin();
-               while (j != sel.end() && (*i)->in_channels().includes ((*j)->audio_channels ())) {
-                       ++j;
-               }
-
-               if (j == sel.end ()) {
-                       _processor->Append (std_to_wx ((*i)->name ()), new wxStringClientData (std_to_wx ((*i)->id ())));
-               }
-       }
-}
index a5bfef4ca918ffc23425d33f52da15901c6f0f99..ad7d6081bf656b82a863ea943ab039712f08c4da 100644 (file)
@@ -40,18 +40,13 @@ public:
 private:
        void gain_calculate_button_clicked ();
        void show_clicked ();
 private:
        void gain_calculate_button_clicked ();
        void show_clicked ();
-       void stream_changed ();
        void mapping_changed (AudioMapping);
        void mapping_changed (AudioMapping);
-       void processor_changed ();
-       void setup_processors ();
        void setup_description ();
 
        ContentSpinCtrlDouble<AudioContent>* _gain;
        wxButton* _gain_calculate_button;
        wxButton* _show;
        ContentSpinCtrl<AudioContent>* _delay;
        void setup_description ();
 
        ContentSpinCtrlDouble<AudioContent>* _gain;
        wxButton* _gain_calculate_button;
        wxButton* _show;
        ContentSpinCtrl<AudioContent>* _delay;
-       wxChoice* _stream;
-       wxChoice* _processor;
        AudioMappingView* _mapping;
        wxStaticText* _description;
        AudioDialog* _audio_dialog;
        AudioMappingView* _mapping;
        wxStaticText* _description;
        AudioDialog* _audio_dialog;
index c212eb50a7665e4ed9d37ef16f4e02b9d067ea13..feb2c6a4631a7b2bac65f0b7a50bbd2e23257166 100644 (file)
@@ -53,19 +53,13 @@ ContentPropertiesDialog::ContentPropertiesDialog (wxWindow* parent, shared_ptr<C
                        );
        }
 
                        );
        }
 
-       shared_ptr<AudioContent> audio = dynamic_pointer_cast<AudioContent> (content);
-       if (audio) {
-               add_property (
-                       _("Audio channels"),
-                       std_to_wx (raw_convert<string> (audio->audio_channels ()))
-                       );
-       }
-
+       /* XXX: this could be better wrt audio streams */
+       
        shared_ptr<SingleStreamAudioContent> single = dynamic_pointer_cast<SingleStreamAudioContent> (content);
        if (single) {
                add_property (
        shared_ptr<SingleStreamAudioContent> single = dynamic_pointer_cast<SingleStreamAudioContent> (content);
        if (single) {
                add_property (
-                       _("Audio length"),
-                       std_to_wx (raw_convert<string> (single->audio_length())) + " " + _("audio frames")
+                       _("Audio channels"),
+                       std_to_wx (raw_convert<string> (single->audio_stream()->channels ()))
                        );
        }
        
                        );
        }
        
index 18ffb8b3b59e31fea6613bcd8638bfaa85332ecd..be6e08341ddc6a49f65bc44e65e78359b071db2b 100644 (file)
@@ -361,7 +361,7 @@ DCPPanel::film_changed (int p)
 void
 DCPPanel::film_content_changed (int property)
 {
 void
 DCPPanel::film_content_changed (int property)
 {
-       if (property == FFmpegContentProperty::AUDIO_STREAM ||
+       if (property == FFmpegContentProperty::AUDIO_STREAMS ||
            property == SubtitleContentProperty::USE_SUBTITLES ||
            property == VideoContentProperty::VIDEO_SCALE) {
                setup_dcp_name ();
            property == SubtitleContentProperty::USE_SUBTITLES ||
            property == VideoContentProperty::VIDEO_SCALE) {
                setup_dcp_name ();
index f1c6e30f342c981f2dc307228e4929fdadc35e2a..c1713bb61262e2bcc27047a60eadb2b9186baf47 100644 (file)
@@ -142,7 +142,7 @@ Timeline::playlist_content_changed (int property)
                        setup_pixels_per_second ();
                }
                Refresh ();
                        setup_pixels_per_second ();
                }
                Refresh ();
-       } else if (property == AudioContentProperty::AUDIO_MAPPING) {
+       } else if (property == AudioContentProperty::AUDIO_STREAMS) {
                recreate_views ();
        }
 }
                recreate_views ();
        }
 }
index 3753ca37992319027b5083f56725e36e35de3820..880e3d6b643e2a689cd25762558999dab3da4d33 100644 (file)
 #include <boost/test/unit_test.hpp>
 #include "test.h"
 #include "lib/audio_decoder.h"
 #include <boost/test/unit_test.hpp>
 #include "test.h"
 #include "lib/audio_decoder.h"
-#include "lib/audio_content.h"
+#include "lib/single_stream_audio_content.h"
 
 using std::string;
 using std::cout;
 using std::min;
 using boost::shared_ptr;
 
 
 using std::string;
 using std::cout;
 using std::min;
 using boost::shared_ptr;
 
-class TestAudioContent : public AudioContent
+class TestAudioContent : public SingleStreamAudioContent
 {
 public:
 {
 public:
-       TestAudioContent (shared_ptr<Film> film)
+       TestAudioContent (shared_ptr<const Film> film)
                : Content (film)
                : Content (film)
-               , AudioContent (film, DCPTime ())
-       {}
-
-       string summary () const {
-               return "";
+               , SingleStreamAudioContent (film)
+       {
+               _audio_stream.reset (new AudioStream (48000, 2));
        }
 
        }
 
-       string information () const {
+       std::string summary () const {
                return "";
        }
 
        DCPTime full_length () const {
                return "";
        }
 
        DCPTime full_length () const {
-               return DCPTime::from_seconds (float (audio_length()) / audio_frame_rate ());
+               return DCPTime::from_seconds (float (audio_length()) / audio_stream()->frame_rate ());
        }
        }
-
-       int audio_channels () const {
-               return 2;
-       }
-
+       
        Frame audio_length () const {
        Frame audio_length () const {
-               return rint (61.2942 * audio_frame_rate ());
-       }
-
-       int audio_frame_rate () const {
-               return 48000;
+               return rint (61.2942 * audio_stream()->frame_rate ());
        }
        }
-
-       AudioMapping audio_mapping () const {
-               return AudioMapping (audio_channels ());
-       }
-
-       void set_audio_mapping (AudioMapping) {}
 };
 
 class TestAudioDecoder : public AudioDecoder
 };
 
 class TestAudioDecoder : public AudioDecoder
@@ -87,14 +71,14 @@ public:
                        _test_audio_content->audio_length() - _position
                        );
 
                        _test_audio_content->audio_length() - _position
                        );
 
-               shared_ptr<AudioBuffers> buffers (new AudioBuffers (_audio_content->audio_channels(), N));
-               for (int i = 0; i < _audio_content->audio_channels(); ++i) {
+               shared_ptr<AudioBuffers> buffers (new AudioBuffers (_test_audio_content->audio_stream()->channels(), N));
+               for (int i = 0; i < _test_audio_content->audio_stream()->channels(); ++i) {
                        for (int j = 0; j < N; ++j) {
                                buffers->data(i)[j] = j + _position;
                        }
                }
 
                        for (int j = 0; j < N; ++j) {
                                buffers->data(i)[j] = j + _position;
                        }
                }
 
-               audio (buffers, ContentTime::from_frames (_position, _audio_content->resampled_audio_frame_rate ()));
+               audio (_test_audio_content->audio_stream(), buffers, ContentTime::from_frames (_position, 48000));
                _position += N;
 
                return N < 2000;
                _position += N;
 
                return N < 2000;
@@ -103,7 +87,7 @@ public:
        void seek (ContentTime t, bool accurate)
        {
                AudioDecoder::seek (t, accurate);
        void seek (ContentTime t, bool accurate)
        {
                AudioDecoder::seek (t, accurate);
-               _position = t.frames (_audio_content->resampled_audio_frame_rate ());
+               _position = t.frames (_test_audio_content->resampled_audio_frame_rate ());
        }
 
 private:
        }
 
 private:
@@ -114,23 +98,22 @@ private:
 shared_ptr<TestAudioContent> content;
 shared_ptr<TestAudioDecoder> decoder;
 
 shared_ptr<TestAudioContent> content;
 shared_ptr<TestAudioDecoder> decoder;
 
-static shared_ptr<ContentAudio>
+static ContentAudio
 get (Frame from, Frame length)
 {
        decoder->seek (ContentTime::from_frames (from, content->resampled_audio_frame_rate ()), true);
 get (Frame from, Frame length)
 {
        decoder->seek (ContentTime::from_frames (from, content->resampled_audio_frame_rate ()), true);
-       shared_ptr<ContentAudio> ca = decoder->get_audio (from, length, true);
-       BOOST_CHECK_EQUAL (ca->frame, from);
+       ContentAudio ca = decoder->get_audio (content->audio_stream(), from, length, true);
+       BOOST_CHECK_EQUAL (ca.frame, from);
        return ca;
 }
 
 static void
 check (Frame from, Frame length)
 {
        return ca;
 }
 
 static void
 check (Frame from, Frame length)
 {
-       shared_ptr<ContentAudio> ca = get (from, length);
-       for (int i = 0; i < content->audio_channels(); ++i) {
+       ContentAudio ca = get (from, length);
+       for (int i = 0; i < content->audio_stream()->channels(); ++i) {
                for (int j = 0; j < length; ++j) {
                for (int j = 0; j < length; ++j) {
-                       BOOST_CHECK_EQUAL (ca->audio->data(i)[j], j + from);
-                       assert (ca->audio->data(i)[j] == j + from);
+                       BOOST_REQUIRE_EQUAL (ca.audio->data(i)[j], j + from);
                }
        }
 }
                }
        }
 }
@@ -152,11 +135,11 @@ BOOST_AUTO_TEST_CASE (audio_decoder_get_audio_test)
 
        Frame const from = content->resampled_audio_frame_rate() * 61;
        Frame const length = content->resampled_audio_frame_rate() * 4;
 
        Frame const from = content->resampled_audio_frame_rate() * 61;
        Frame const length = content->resampled_audio_frame_rate() * 4;
-       shared_ptr<ContentAudio> ca = get (from, length);
+       ContentAudio ca = get (from, length);
        
        
-       for (int i = 0; i < content->audio_channels(); ++i) {
-               for (int j = 0; j < ca->audio->frames(); ++j) {
-                       BOOST_REQUIRE_EQUAL (ca->audio->data(i)[j], j + from);
+       for (int i = 0; i < content->audio_stream()->channels(); ++i) {
+               for (int j = 0; j < ca.audio->frames(); ++j) {
+                       BOOST_REQUIRE_EQUAL (ca.audio->data(i)[j], j + from);
                }
        }
 }
                }
        }
 }
index 68e14ff3ca353f470d9f09f8c14112f6713b34a4..63f363114894a4ee119aaf271b8fb36453bcee46 100644 (file)
@@ -43,6 +43,8 @@ using boost::shared_ptr;
 static
 void test_audio_delay (int delay_in_ms)
 {
 static
 void test_audio_delay (int delay_in_ms)
 {
+       BOOST_TEST_MESSAGE ("Testing delay of " << 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_isdcf_name ("FTR"));
        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_isdcf_name ("FTR"));
@@ -87,7 +89,7 @@ void test_audio_delay (int delay_in_ms)
                                delayed = 0;
                        }
 
                                delayed = 0;
                        }
 
-                       BOOST_CHECK_EQUAL (sample, delayed);
+                       BOOST_REQUIRE_EQUAL (sample, delayed);
                        ++n;
                }
        }
                        ++n;
                }
        }
index 444b0869b7dc52fd2928dec6444a408315265642..1e2bbb377af7f68a18f0a896bd7ed9d735460192 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
-    Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2015 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
 
     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
index f3504ffc591f8e8a799981436eb46c4ee5c6b0b5..c15bf8c98525c72d939dc601a45d70b59dfe3120 100644 (file)
@@ -41,10 +41,10 @@ using boost::shared_ptr;
 using boost::optional;
 
 static void
 using boost::optional;
 
 static void
-check (FFmpegDecoder& decoder, int frame)
+check (shared_ptr<FFmpegDecoder> decoder, int frame)
 {
        list<ContentVideo> v;
 {
        list<ContentVideo> v;
-       v = decoder.get_video (frame, true);
+       v = decoder->get_video (frame, true);
        BOOST_CHECK (v.size() == 1);
        BOOST_CHECK_EQUAL (v.front().frame, frame);
 }
        BOOST_CHECK (v.size() == 1);
        BOOST_CHECK_EQUAL (v.front().frame, frame);
 }
@@ -63,7 +63,7 @@ test (boost::filesystem::path file, vector<int> frames)
        film->examine_and_add_content (content);
        wait_for_jobs ();
        shared_ptr<Log> log (new NullLog);
        film->examine_and_add_content (content);
        wait_for_jobs ();
        shared_ptr<Log> log (new NullLog);
-       FFmpegDecoder decoder (content, log);
+       shared_ptr<FFmpegDecoder> decoder (new FFmpegDecoder (content, log));
 
        for (vector<int>::const_iterator i = frames.begin(); i != frames.end(); ++i) {
                check (decoder, *i);
 
        for (vector<int>::const_iterator i = frames.begin(); i != frames.end(); ++i) {
                check (decoder, *i);
index 6b02efcb50d264a1de413f537275a5d97922f557..b4f37dd0bb6ffa197c0589022b6eae9a74a9e640 100644 (file)
@@ -50,22 +50,22 @@ test (boost::filesystem::path file, float fps, int gaps)
        film->examine_and_add_content (content);
        wait_for_jobs ();
        shared_ptr<Log> log (new NullLog);
        film->examine_and_add_content (content);
        wait_for_jobs ();
        shared_ptr<Log> log (new NullLog);
-       FFmpegDecoder decoder (content, log);
+       shared_ptr<FFmpegDecoder> decoder (new FFmpegDecoder (content, log));
 
 
-       BOOST_CHECK_CLOSE (decoder.video_content()->video_frame_rate(), fps, 0.01);
+       BOOST_CHECK_CLOSE (decoder->video_content()->video_frame_rate(), fps, 0.01);
        
        
-       Frame const N = decoder.video_content()->video_length();
+       Frame const N = decoder->video_content()->video_length();
 #ifdef DCPOMATIC_DEBUG 
 #ifdef DCPOMATIC_DEBUG 
-       decoder.test_gaps = 0;
+       decoder->test_gaps = 0;
 #endif 
        for (Frame i = 0; i < N; ++i) {
                list<ContentVideo> v;
 #endif 
        for (Frame i = 0; i < N; ++i) {
                list<ContentVideo> v;
-               v = decoder.get_video (i, true);
+               v = decoder->get_video (i, true);
                BOOST_CHECK_EQUAL (v.size(), 1);
                BOOST_CHECK_EQUAL (v.front().frame, i);
        }
 #ifdef DCPOMATIC_DEBUG 
                BOOST_CHECK_EQUAL (v.size(), 1);
                BOOST_CHECK_EQUAL (v.front().frame, i);
        }
 #ifdef DCPOMATIC_DEBUG 
-       BOOST_CHECK_EQUAL (decoder.test_gaps, gaps);
+       BOOST_CHECK_EQUAL (decoder->test_gaps, gaps);
 #endif
 }
 
 #endif
 }
 
index 94e7223ab394641a3a64940e2dac24a1d0e65804..0ded564316cf8d990aaf5e8d59875770c4656632 100644 (file)
@@ -34,13 +34,13 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
 {
        shared_ptr<Film> film = new_test_film ("ffmpeg_pts_offset_test");
        shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/test.mp4"));
 {
        shared_ptr<Film> film = new_test_film ("ffmpeg_pts_offset_test");
        shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/test.mp4"));
-       content->_audio_stream.reset (new FFmpegAudioStream);
+       content->_audio_streams.push_back (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream));
        content->_video_frame_rate = 24;
 
        {
                /* Sound == video so no offset required */
                content->_first_video = ContentTime ();
        content->_video_frame_rate = 24;
 
        {
                /* Sound == video so no offset required */
                content->_first_video = ContentTime ();
-               content->_audio_stream->first_audio = ContentTime ();
+               content->_audio_streams.front()->first_audio = ContentTime ();
                FFmpegDecoder decoder (content, film->log());
                BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime ());
        }
                FFmpegDecoder decoder (content, film->log());
                BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime ());
        }
@@ -48,7 +48,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
        {
                /* Common offset should be removed */
                content->_first_video = ContentTime::from_seconds (600);
        {
                /* Common offset should be removed */
                content->_first_video = ContentTime::from_seconds (600);
-               content->_audio_stream->first_audio = ContentTime::from_seconds (600);
+               content->_audio_streams.front()->first_audio = ContentTime::from_seconds (600);
                FFmpegDecoder decoder (content, film->log());
                BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime::from_seconds (-600));
        }
                FFmpegDecoder decoder (content, film->log());
                BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime::from_seconds (-600));
        }
@@ -56,7 +56,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
        {
                /* Video is on a frame boundary */
                content->_first_video = ContentTime::from_frames (1, 24);
        {
                /* Video is on a frame boundary */
                content->_first_video = ContentTime::from_frames (1, 24);
-               content->_audio_stream->first_audio = ContentTime ();
+               content->_audio_streams.front()->first_audio = ContentTime ();
                FFmpegDecoder decoder (content, film->log());
                BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime ());
        }
                FFmpegDecoder decoder (content, film->log());
                BOOST_CHECK_EQUAL (decoder._pts_offset, ContentTime ());
        }
@@ -65,7 +65,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
                /* Video is off a frame boundary */
                double const frame = 1.0 / 24.0;
                content->_first_video = ContentTime::from_seconds (frame + 0.0215);
                /* Video is off a frame boundary */
                double const frame = 1.0 / 24.0;
                content->_first_video = ContentTime::from_seconds (frame + 0.0215);
-               content->_audio_stream->first_audio = ContentTime ();
+               content->_audio_streams.front()->first_audio = ContentTime ();
                FFmpegDecoder decoder (content, film->log());
                BOOST_CHECK_CLOSE (decoder._pts_offset.seconds(), (frame - 0.0215), 0.00001);
        }
                FFmpegDecoder decoder (content, film->log());
                BOOST_CHECK_CLOSE (decoder._pts_offset.seconds(), (frame - 0.0215), 0.00001);
        }
@@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
                /* Video is off a frame boundary and both have a common offset */
                double const frame = 1.0 / 24.0;
                content->_first_video = ContentTime::from_seconds (frame + 0.0215 + 4.1);
                /* Video is off a frame boundary and both have a common offset */
                double const frame = 1.0 / 24.0;
                content->_first_video = ContentTime::from_seconds (frame + 0.0215 + 4.1);
-               content->_audio_stream->first_audio = ContentTime::from_seconds (4.1);
+               content->_audio_streams.front()->first_audio = ContentTime::from_seconds (4.1);
                FFmpegDecoder decoder (content, film->log());
                BOOST_CHECK_CLOSE (decoder._pts_offset.seconds(), (frame - 0.0215) - 4.1, 0.1);
        }
                FFmpegDecoder decoder (content, film->log());
                BOOST_CHECK_CLOSE (decoder._pts_offset.seconds(), (frame - 0.0215) - 4.1, 0.1);
        }
index e8ebcea3b959833ccb1009c561c7b99f4394bbf5..39a64504d70207b60667e908c810be140a47decb 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
-    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2015 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
 
     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
@@ -237,7 +237,6 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_double)
        BOOST_CHECK_EQUAL (film->playlist()->best_dcp_frame_rate(), 25);
 }
 
        BOOST_CHECK_EQUAL (film->playlist()->best_dcp_frame_rate(), 25);
 }
 
-
 BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
 {
        shared_ptr<Film> film = new_test_film ("audio_sampling_rate_test");
 BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
 {
        shared_ptr<Film> film = new_test_film ("audio_sampling_rate_test");
@@ -252,46 +251,47 @@ BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
        afr.push_back (30);
        Config::instance()->set_allowed_dcp_frame_rates (afr);
 
        afr.push_back (30);
        Config::instance()->set_allowed_dcp_frame_rates (afr);
 
+       shared_ptr<FFmpegAudioStream> stream (new FFmpegAudioStream ("foo", 0, 0, 0));
+       content->_audio_streams.push_back (stream);
        content->_video_frame_rate = 24;
        film->set_video_frame_rate (24);
        content->_video_frame_rate = 24;
        film->set_video_frame_rate (24);
-       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+       stream->_frame_rate = 48000;
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 48000);
 
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 48000);
 
-       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
+       stream->_frame_rate = 44100;
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 48000);
 
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 48000);
 
-       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 80000, 0)));
+       stream->_frame_rate = 80000;
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 96000);
 
        content->_video_frame_rate = 23.976;
        film->set_video_frame_rate (24);
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 96000);
 
        content->_video_frame_rate = 23.976;
        film->set_video_frame_rate (24);
-       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+       stream->_frame_rate = 48000;
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 47952);
 
        content->_video_frame_rate = 29.97;
        film->set_video_frame_rate (30);
        BOOST_CHECK_EQUAL (film->video_frame_rate (), 30);
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 47952);
 
        content->_video_frame_rate = 29.97;
        film->set_video_frame_rate (30);
        BOOST_CHECK_EQUAL (film->video_frame_rate (), 30);
-       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+       stream->_frame_rate = 48000;
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 47952);
 
        content->_video_frame_rate = 25;
        film->set_video_frame_rate (24);
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 47952);
 
        content->_video_frame_rate = 25;
        film->set_video_frame_rate (24);
-       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 48000, 0)));
+       stream->_frame_rate = 48000;
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 50000);
 
        content->_video_frame_rate = 25;
        film->set_video_frame_rate (24);
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 50000);
 
        content->_video_frame_rate = 25;
        film->set_video_frame_rate (24);
-       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 44100, 0)));
+       stream->_frame_rate = 44100;
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 50000);
 
        /* Check some out-there conversions (not the best) */
        
        content->_video_frame_rate = 14.99;
        film->set_video_frame_rate (25);
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), 50000);
 
        /* Check some out-there conversions (not the best) */
        
        content->_video_frame_rate = 14.99;
        film->set_video_frame_rate (25);
-       content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
+       stream->_frame_rate = 16000;
        /* The FrameRateChange within resampled_audio_frame_rate should choose to double-up
           the 14.99 fps video to 30 and then run it slow at 25.
        */
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), rint (48000 * 2 * 14.99 / 25));
 }
        /* The FrameRateChange within resampled_audio_frame_rate should choose to double-up
           the 14.99 fps video to 30 and then run it slow at 25.
        */
        BOOST_CHECK_EQUAL (content->resampled_audio_frame_rate(), rint (48000 * 2 * 14.99 / 25));
 }
-
index f00180c89106ea14eef59e291731ffda20e6e65f..3aef113734fbebfd7092d2aa121a28808bf40819 100644 (file)
@@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE (seek_zero_test)
        /* Work out the first video frame index that we will be given, taking into account
         * the difference between first video and first audio.
         */
        /* Work out the first video frame index that we will be given, taking into account
         * the difference between first video and first audio.
         */
-       ContentTime video_delay = content->first_video().get() - content->audio_stream()->first_audio.get();
+       ContentTime video_delay = content->first_video().get() - content->ffmpeg_audio_streams().front()->first_audio.get();
        if (video_delay < ContentTime ()) {
                video_delay = ContentTime ();
        }
        if (video_delay < ContentTime ()) {
                video_delay = ContentTime ();
        }
index 9bcbf3a696504e20da51e8c20499a622c93e320f..5192809ab6308a47ab8a3d29198310b8b8273ae0 100644 (file)
@@ -30,6 +30,8 @@
 
 using boost::shared_ptr;
 
 
 using boost::shared_ptr;
 
+#if 0
+/* XXX: no audio processors in content any more */
 BOOST_AUTO_TEST_CASE (upmixer_a_test)
 {
        shared_ptr<Film> film = new_test_film ("upmixer_a_test");
 BOOST_AUTO_TEST_CASE (upmixer_a_test)
 {
        shared_ptr<Film> film = new_test_film ("upmixer_a_test");
@@ -78,3 +80,4 @@ BOOST_AUTO_TEST_CASE (upmixer_a_test)
        check_audio_file ("test/data/upmixer_a_test/Ls.wav", "build/test/upmixer_a_test/Ls.wav");
        check_audio_file ("test/data/upmixer_a_test/Rs.wav", "build/test/upmixer_a_test/Rs.wav");
 }
        check_audio_file ("test/data/upmixer_a_test/Ls.wav", "build/test/upmixer_a_test/Ls.wav");
        check_audio_file ("test/data/upmixer_a_test/Rs.wav", "build/test/upmixer_a_test/Rs.wav");
 }
+#endif