diff options
author | Sakari Bergen <sakari.bergen@beatwaves.net> | 2009-12-27 22:09:40 +0000 |
---|---|---|
committer | Sakari Bergen <sakari.bergen@beatwaves.net> | 2009-12-27 22:09:40 +0000 |
commit | 8da27200d18fe4c471a759dde8e10d85ff29d277 (patch) | |
tree | 8a68123da7cb8539a9818704363e3fd98da4d385 /libs | |
parent | dde0848a984e06cbc1d4117d9cffa75c191f3b39 (diff) |
- Fix process callbakc handling during export
- Fix filename handling when exporting multiple files
- Some updates to audiographer
git-svn-id: svn://localhost/ardour2/branches/3.0@6402 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs')
22 files changed, 373 insertions, 128 deletions
diff --git a/libs/ardour/ardour/export_graph_builder.h b/libs/ardour/ardour/export_graph_builder.h index 1244afd647..2f57aaf583 100644 --- a/libs/ardour/ardour/export_graph_builder.h +++ b/libs/ardour/ardour/export_graph_builder.h @@ -62,6 +62,7 @@ class ExportGraphBuilder ~ExportGraphBuilder (); int process (nframes_t frames, bool last_cycle); + bool process_normalize (); // returns true when finished void reset (); void add_config (FileSpec const & config); @@ -124,6 +125,9 @@ class ExportGraphBuilder void add_child (FileSpec const & new_config); bool operator== (FileSpec const & other_config) const; + /// Returns true when finished + bool process (); + private: typedef boost::shared_ptr<AudioGrapher::PeakReader> PeakReaderPtr; typedef boost::shared_ptr<AudioGrapher::Normalizer> NormalizerPtr; @@ -132,7 +136,6 @@ class ExportGraphBuilder typedef boost::shared_ptr<AudioGrapher::AllocatingProcessContext<Sample> > BufferPtr; void start_post_processing(); - void do_post_processing(); ExportGraphBuilder & parent; @@ -217,6 +220,8 @@ class ExportGraphBuilder Sample * process_buffer; nframes_t process_buffer_frames; + std::list<Normalizer *> normalizers; + Glib::ThreadPool thread_pool; }; diff --git a/libs/ardour/ardour/export_handler.h b/libs/ardour/ardour/export_handler.h index f7f3b863b9..971c0c4940 100644 --- a/libs/ardour/ardour/export_handler.h +++ b/libs/ardour/ardour/export_handler.h @@ -112,17 +112,21 @@ class ExportHandler : public ExportElementFactory private: + int process (nframes_t frames); + Session & session; GraphBuilderPtr graph_builder; StatusPtr export_status; ConfigMap config_map; bool realtime; + bool normalizing; /* Timespan management */ void start_timespan (); int process_timespan (nframes_t frames); + int process_normalize (); void finish_timespan (); typedef std::pair<ConfigMap::iterator, ConfigMap::iterator> TimespanBounds; @@ -131,8 +135,6 @@ class ExportHandler : public ExportElementFactory PBD::ScopedConnection process_connection; sframes_t process_position; - - PBD::ScopedConnection export_read_finished_connection; /* CD Marker stuff */ diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index ae9c1f58a5..b72822bd1c 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -530,7 +530,6 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi int start_audio_export (nframes_t position, bool realtime); PBD::Signal1<int,nframes_t> ProcessExport; - PBD::Signal0<void> ExportReadFinished; static PBD::Signal2<void,std::string, std::string> Exported; void add_source (boost::shared_ptr<Source>); @@ -934,7 +933,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi nframes_t post_export_position; bool _exporting; - bool _exporting_realtime; + bool _export_rolling; boost::shared_ptr<ExportHandler> export_handler; boost::shared_ptr<ExportStatus> export_status; diff --git a/libs/ardour/export_graph_builder.cc b/libs/ardour/export_graph_builder.cc index 5b5ec16b36..3b1d1e3838 100644 --- a/libs/ardour/export_graph_builder.cc +++ b/libs/ardour/export_graph_builder.cc @@ -56,11 +56,26 @@ ExportGraphBuilder::process (nframes_t frames, bool last_cycle) return 0; } +bool +ExportGraphBuilder::process_normalize () +{ + for (std::list<Normalizer *>::iterator it = normalizers.begin(); it != normalizers.end(); /* ++ in loop */) { + if ((*it)->process()) { + it = normalizers.erase (it); + } else { + ++it; + } + } + + return normalizers.empty(); +} + void ExportGraphBuilder::reset () { channel_configs.clear (); channels.clear (); + normalizers.clear (); } void @@ -252,20 +267,21 @@ ExportGraphBuilder::Normalizer::operator== (FileSpec const & other_config) const config.format->normalize_target() == other_config.format->normalize_target(); } -void -ExportGraphBuilder::Normalizer::start_post_processing() +bool +ExportGraphBuilder::Normalizer::process() { - normalizer->set_peak (peak_reader->get_peak()); - tmp_file->seek (0, SndfileReader<Sample>::SeekBeginning); - parent.thread_pool.push (sigc::mem_fun (*this, &Normalizer::do_post_processing)); + ProcessContext<Sample> buffer_copy (*buffer); + tmp_file->read (buffer_copy); + normalizer->process (buffer_copy); + return buffer_copy.frames() != buffer->frames(); } void -ExportGraphBuilder::Normalizer::do_post_processing() +ExportGraphBuilder::Normalizer::start_post_processing() { - while (tmp_file->read (*buffer) == buffer->frames()) { - normalizer->process (*buffer); - } + normalizer->set_peak (peak_reader->get_peak()); + tmp_file->seek (0, SndfileReader<Sample>::SeekBeginning); + parent.normalizers.push_back (this); } /* SRC */ diff --git a/libs/ardour/export_handler.cc b/libs/ardour/export_handler.cc index 3ce07cf44d..a9335add37 100644 --- a/libs/ardour/export_handler.cc +++ b/libs/ardour/export_handler.cc @@ -119,7 +119,7 @@ ExportHandler::add_export_config (TimespanPtr timespan, ChannelConfigPtr channel FileSpec spec (channel_config, format, filename); ConfigPair pair (timespan, spec); config_map.insert (pair); - + return true; } @@ -138,8 +138,6 @@ ExportHandler::do_export (bool rt) /* Start export */ realtime = rt; - - session.ExportReadFinished.connect_same_thread (export_read_finished_connection, boost::bind (&ExportHandler::finish_timespan, this)); start_timespan (); } @@ -149,6 +147,7 @@ ExportHandler::start_timespan () export_status->timespan++; if (config_map.empty()) { + // freewheeling has to be stopped from outside the process cycle export_status->running = false; return; } @@ -160,17 +159,33 @@ ExportHandler::start_timespan () timespan_bounds = config_map.equal_range (current_timespan); graph_builder->reset (); for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) { - graph_builder->add_config (it->second); + // Filenames can be shared across timespans + FileSpec & spec = it->second; + spec.filename->set_timespan (it->first); + graph_builder->add_config (spec); } /* start export */ - session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process_timespan, this, _1)); + normalizing = false; + session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1)); process_position = current_timespan->get_start(); session.start_audio_export (process_position, realtime); } int +ExportHandler::process (nframes_t frames) +{ + if (!export_status->running) { + return 0; + } else if (normalizing) { + return process_normalize (); + } else { + return process_timespan (frames); + } +} + +int ExportHandler::process_timespan (nframes_t frames) { /* update position */ @@ -184,6 +199,7 @@ ExportHandler::process_timespan (nframes_t frames) if (last_cycle) { frames_to_read = end - process_position; export_status->stop = true; + normalizing = true; } else { frames_to_read = frames; } @@ -196,11 +212,19 @@ ExportHandler::process_timespan (nframes_t frames) return graph_builder->process (frames_to_read, last_cycle); } +int +ExportHandler::process_normalize () +{ + if (graph_builder->process_normalize ()) { + finish_timespan (); + } + + return 0; +} + void ExportHandler::finish_timespan () { - process_connection.disconnect (); - while (config_map.begin() != timespan_bounds.second) { config_map.erase (config_map.begin()); } diff --git a/libs/ardour/session_export.cc b/libs/ardour/session_export.cc index 86a6250ffd..9a277f5655 100644 --- a/libs/ardour/session_export.cc +++ b/libs/ardour/session_export.cc @@ -123,8 +123,6 @@ Session::start_audio_export (nframes_t position, bool realtime) */ _transport_frame = position; - - _exporting_realtime = realtime; export_status->stop = false; /* get transport ready. note how this is calling butler functions @@ -144,39 +142,30 @@ Session::start_audio_export (nframes_t position, bool realtime) return -1; } - if (realtime) { - last_process_function = process_function; - process_function = &Session::process_export; - } else { - _engine.Freewheel.connect_same_thread (export_freewheel_connection, boost::bind (&Session::process_export_fw, this, _1)); - return _engine.freewheel (true); - } - - return 0; + _engine.Freewheel.connect_same_thread (export_freewheel_connection, boost::bind (&Session::process_export_fw, this, _1)); + _export_rolling = true; + return _engine.freewheel (true); } void Session::process_export (nframes_t nframes) { - try { - - if (export_status->stop) { - stop_audio_export (); - return; - } - - if (!_exporting_realtime) { - /* make sure we've caught up with disk i/o, since - we're running faster than realtime c/o JACK. - */ + if (_export_rolling && export_status->stop) { + stop_audio_export (); + } - _butler->wait_until_finished (); - } + if (_export_rolling) { + /* make sure we've caught up with disk i/o, since + we're running faster than realtime c/o JACK. + */ + _butler->wait_until_finished (); /* do the usual stuff */ process_without_events (nframes); - + } + + try { /* handle export - XXX what about error handling? */ ProcessExport (nframes); @@ -197,23 +186,16 @@ Session::process_export_fw (nframes_t nframes) int Session::stop_audio_export () { - if (_exporting_realtime) { - process_function = last_process_function; - } else { - export_freewheel_connection.disconnect(); - } - /* can't use stop_transport() here because we need an immediate halt and don't require all the declick stuff that stop_transport() implements. */ realtime_stop (true, true); + _export_rolling = false; _butler->schedule_transport_work (); - if (!export_status->aborted()) { - ExportReadFinished (); - } else { + if (export_status->aborted()) { finalize_audio_export (); } @@ -225,20 +207,11 @@ void Session::finalize_audio_export () { _exporting = false; - export_status->running = false; - - if (!_exporting_realtime) { - _engine.freewheel (false); - _exporting_realtime = false; - } + _export_rolling = false; /* Clean up */ - /* BOOST SIGNAL are these necessary? - ProcessExport.clear(); - ExportReadFinished.clear(); - */ - + _engine.freewheel (false); export_freewheel_connection.disconnect(); export_handler.reset(); export_status.reset(); diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 13b9112866..9394cd7bec 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -209,7 +209,6 @@ Session::first_stage_init (string fullpath, string snapshot_name) g_atomic_int_set (&_capture_load_min, 100); _play_range = false; _exporting = false; - _exporting_realtime = false; _gain_automation_buffer = 0; _pan_automation_buffer = 0; _npan_buffers = 0; diff --git a/libs/audiographer/audiographer/debug_utils.h b/libs/audiographer/audiographer/debug_utils.h new file mode 100644 index 0000000000..8b45f0d65c --- /dev/null +++ b/libs/audiographer/audiographer/debug_utils.h @@ -0,0 +1,34 @@ +#ifndef AUDIOGRAPHER_DEBUG_UTILS_H +#define AUDIOGRAPHER_DEBUG_UTILS_H + +#include <string> + +#ifdef __GNUC__ +#include <cxxabi.h> +#endif + +namespace AudioGrapher +{ + +struct DebugUtils +{ + template<typename T> + static std::string demangled_name (T const & obj) + { +#ifdef __GNUC__ + int status; + char * res = abi::__cxa_demangle (typeid(obj).name(), 0, 0, &status); + if (status == 0) { + std::string s(res); + free (res); + return s; + } +#endif + return typeid(obj).name(); + } + +}; + +} // namespace + +#endif // AUDIOGRAPHER_DEBUG_UTILS_H diff --git a/libs/audiographer/audiographer/debuggable.h b/libs/audiographer/audiographer/debuggable.h new file mode 100644 index 0000000000..4126327b86 --- /dev/null +++ b/libs/audiographer/audiographer/debuggable.h @@ -0,0 +1,46 @@ +#ifndef AUDIOGRAPHER_DEBUGGABLE_H +#define AUDIOGRAPHER_DEBUGGABLE_H + +#ifndef DEFAULT_DEBUG_LEVEL +#define DEFAULT_DEBUG_LEVEL DebugNone +#endif + +#include <iostream> + +namespace AudioGrapher +{ + +enum DebugLevel +{ + DebugNone, //< Disabled + DebugObject, //< Object level stuff, ctors, initalizers etc. + DebugProcess, //< Process cycle level stuff + DebugVerbose, //< Lots of output, not on sample level + DebugSample //< Sample level stuff +}; + +/// Class that allows optimizing out debugging code during compile time +template<DebugLevel L = DEFAULT_DEBUG_LEVEL> +class Debuggable +{ + protected: + Debuggable(std::ostream & debug_stream = std::cerr) + : stream (debug_stream) {} + + bool debug_level (DebugLevel level) { + #ifdef NDEBUG + return false; + #else + return L >= level; + #endif + } + std::ostream & debug_stream() { return stream; } + + private: + std::ostream & stream; +}; + + +} // namespace + +#endif // AUDIOGRAPHER_DEBUGGABLE_H diff --git a/libs/audiographer/audiographer/exception.h b/libs/audiographer/audiographer/exception.h index a179b30f91..43891db619 100644 --- a/libs/audiographer/audiographer/exception.h +++ b/libs/audiographer/audiographer/exception.h @@ -3,10 +3,11 @@ #include <exception> #include <string> -#include <cxxabi.h> #include <boost/format.hpp> +#include "audiographer/debug_utils.h" + namespace AudioGrapher { @@ -15,8 +16,9 @@ class Exception : public std::exception public: template<typename T> Exception (T const & thrower, std::string const & reason) - : reason (boost::str (boost::format ( - "Exception thrown by %1%: %2%") % name (thrower) % reason)) + : reason (boost::str (boost::format + ("Exception thrown by %1%: %2%") + % DebugUtils::demangled_name (thrower) % reason)) {} virtual ~Exception () throw() { } @@ -25,22 +27,6 @@ class Exception : public std::exception { return reason.c_str(); } - - protected: - template<typename T> - std::string name (T const & obj) - { -#ifdef __GNUC__ - int status; - char * res = abi::__cxa_demangle (typeid(obj).name(), 0, 0, &status); - if (status == 0) { - std::string s(res); - free (res); - return s; - } -#endif - return typeid(obj).name(); - } private: std::string const reason; diff --git a/libs/audiographer/audiographer/process_context.h b/libs/audiographer/audiographer/process_context.h index 080e492944..654da2565a 100644 --- a/libs/audiographer/audiographer/process_context.h +++ b/libs/audiographer/audiographer/process_context.h @@ -1,9 +1,11 @@ #ifndef AUDIOGRAPHER_PROCESS_CONTEXT_H #define AUDIOGRAPHER_PROCESS_CONTEXT_H -#include "types.h" +#include <boost/static_assert.hpp> +#include <boost/type_traits.hpp> -#include <cstring> +#include "types.h" +#include "type_utils.h" namespace AudioGrapher { @@ -14,7 +16,9 @@ namespace AudioGrapher template <typename T> class ProcessContext { - + + BOOST_STATIC_ASSERT (boost::has_trivial_destructor<T>::value); + public: typedef FlagField::Flag Flag; diff --git a/libs/audiographer/audiographer/sndfile_base.h b/libs/audiographer/audiographer/sndfile_base.h index fd6c5f3552..7f8da752e8 100644 --- a/libs/audiographer/audiographer/sndfile_base.h +++ b/libs/audiographer/audiographer/sndfile_base.h @@ -6,11 +6,12 @@ #include <sigc++/signal.h> #include "types.h" +#include "debuggable.h" namespace AudioGrapher { /// Common interface for templated libsndfile readers/writers -class SndfileBase +class SndfileBase : public Debuggable<> { public: diff --git a/libs/audiographer/audiographer/sr_converter.h b/libs/audiographer/audiographer/sr_converter.h index 073fdc8247..a9bfc7c4c3 100644 --- a/libs/audiographer/audiographer/sr_converter.h +++ b/libs/audiographer/audiographer/sr_converter.h @@ -3,14 +3,20 @@ #include <samplerate.h> -#include "types.h" +#include "debuggable.h" #include "listed_source.h" #include "sink.h" +#include "throwing.h" +#include "types.h" namespace AudioGrapher { -class SampleRateConverter : public ListedSource<float>, public Sink<float> +class SampleRateConverter + : public ListedSource<float> + , public Sink<float> + , public Debuggable<> + , public Throwing<> { public: SampleRateConverter (uint32_t channels); diff --git a/libs/audiographer/audiographer/threader.h b/libs/audiographer/audiographer/threader.h index e6c3aa97bf..ad6a542126 100644 --- a/libs/audiographer/audiographer/threader.h +++ b/libs/audiographer/audiographer/threader.h @@ -23,7 +23,8 @@ class ThreaderException : public Exception ThreaderException (T const & thrower, std::exception const & e) : Exception (thrower, boost::str ( boost::format - ("\n\t- Dynamic type: %1%\n\t- what(): %2%") % name (e) % e.what() )) + ("\n\t- Dynamic type: %1%\n\t- what(): %2%") + % DebugUtils::demangled_name (e) % e.what() )) { } }; diff --git a/libs/audiographer/audiographer/throwing.h b/libs/audiographer/audiographer/throwing.h new file mode 100644 index 0000000000..05a056d5e9 --- /dev/null +++ b/libs/audiographer/audiographer/throwing.h @@ -0,0 +1,32 @@ +#ifndef AUDIOGRAPHER_THROWING_H +#define AUDIOGRAPHER_THROWING_H + +#ifndef DEFAULT_THROW_LEVEL +#define DEFAULT_THROW_LEVEL ThrowStrict +#endif + +namespace AudioGrapher +{ + +enum ThrowLevel +{ + ThrowNone, //< Not allowed to throw + ThrowObject, //< Object level stuff, ctors, initalizers etc. + ThrowProcess, //< Process cycle level stuff + ThrowStrict, //< Stricter checks than ThrowProcess, less than ThrowSample + ThrowSample //< Sample level stuff +}; + +/// Class that allows optimizing out error checking during compile time +template<ThrowLevel L = DEFAULT_THROW_LEVEL> +class Throwing +{ + protected: + Throwing() {} + bool throw_level (ThrowLevel level) { return L >= level; } +}; + + +} // namespace + +#endif // AUDIOGRAPHER_THROWING_H diff --git a/libs/audiographer/audiographer/type_utils.h b/libs/audiographer/audiographer/type_utils.h new file mode 100644 index 0000000000..84a28f3d07 --- /dev/null +++ b/libs/audiographer/audiographer/type_utils.h @@ -0,0 +1,59 @@ +#ifndef AUDIOGRAPHER_TYPE_UTILS_H +#define AUDIOGRAPHER_TYPE_UTILS_H + +#include "types.h" +#include <boost/static_assert.hpp> +#include <boost/type_traits.hpp> +#include <memory> +#include <algorithm> + +namespace AudioGrapher +{ + +class TypeUtilsBase +{ + protected: + + template<typename T, bool b> + static void do_fill(T * buffer, nframes_t frames, const boost::integral_constant<bool, b>&) + { std::uninitialized_fill_n (buffer, frames, T()); } + + template<typename T> + static void do_fill(T * buffer, nframes_t frames, const boost::true_type&) + { memset (buffer, frames * sizeof(T), 0); } + + private: +}; + +template<typename T> +class TypeUtils : private TypeUtilsBase +{ + BOOST_STATIC_ASSERT (boost::has_trivial_destructor<T>::value); + + typedef boost::integral_constant<bool, + boost::is_floating_point<T>::value || + boost::is_signed<T>::value> zero_fillable; + public: + inline static void zero_fill (T * buffer, nframes_t frames) + { do_zero_fill(buffer, frames, zero_fillable()); } + + inline static void copy (T* source, T* destination, nframes_t frames) + { std::uninitialized_copy (source, &source[frames], destination); } + + inline static void move (T* source, T* destination, nframes_t frames) + { + if (destination < source) { + std::copy (source, &source[frames], destination); + } else { + std::copy_backward (source, &source[frames], destination); + } + } + + private: + +}; + + +} // namespace + +#endif // AUDIOGRAPHER_TYPE_UTILS_H diff --git a/libs/audiographer/audiographer/types.h b/libs/audiographer/audiographer/types.h index f48f8f807a..5d31899748 100644 --- a/libs/audiographer/audiographer/types.h +++ b/libs/audiographer/audiographer/types.h @@ -18,10 +18,15 @@ class FlagField { FlagField() : _flags (0) {} FlagField(FlagField const & other) : _flags (other._flags) {} - inline bool has (Flag flag) const { return _flags & (1 << flag); } - inline void set (Flag flag) { _flags |= (1 << flag); } - inline void remove (Flag flag) { _flags &= ~(1 << flag); } - inline storage_type flags () const { return _flags; } + inline bool has (Flag flag) const { return _flags & (1 << flag); } + inline storage_type flags () const { return _flags; } + inline operator bool() const { return _flags; } + inline void set (Flag flag) { _flags |= (1 << flag); } + inline void remove (Flag flag) { _flags &= ~(1 << flag); } + inline void reset () { _flags = 0; } + + inline FlagField & operator+= (FlagField const & other) { _flags |= other._flags; return *this; } + inline bool operator== (FlagField const & other) const { return _flags == other._flags; } private: storage_type _flags; diff --git a/libs/audiographer/src/sndfile_reader.cc b/libs/audiographer/src/sndfile_reader.cc index 0508110314..8297844721 100644 --- a/libs/audiographer/src/sndfile_reader.cc +++ b/libs/audiographer/src/sndfile_reader.cc @@ -33,6 +33,7 @@ SndfileReader<T>::read (ProcessContext<T> & context) nframes_t frames_read = (*read_func) (sndfile, context.data(), context.frames()); if (frames_read < context.frames()) { + context.frames() = frames_read; context.set_flag (ProcessContext<T>::EndOfInput); } output (context); diff --git a/libs/audiographer/src/sndfile_writer.cc b/libs/audiographer/src/sndfile_writer.cc index d12d6b943b..f8c9ea63b5 100644 --- a/libs/audiographer/src/sndfile_writer.cc +++ b/libs/audiographer/src/sndfile_writer.cc @@ -60,9 +60,10 @@ SndfileWriter<T>::process (ProcessContext<T> const & c) if (c.has_flag(ProcessContext<T>::EndOfInput)) { sf_write_sync (sndfile); - //#ifdef HAVE_SIGCPP FileWritten (path); - //#endif + if (debug_level (DebugProcess)) { + debug_stream() << str ( format("Finished writing file %1%") % path) << std::endl; + } } } diff --git a/libs/audiographer/src/sr_converter.cc b/libs/audiographer/src/sr_converter.cc index c61b3d0728..c62fd719e8 100644 --- a/libs/audiographer/src/sr_converter.cc +++ b/libs/audiographer/src/sr_converter.cc @@ -1,19 +1,10 @@ #include "audiographer/sr_converter.h" #include "audiographer/exception.h" +#include "audiographer/type_utils.h" #include <cmath> -#include <cstring> #include <boost/format.hpp> -#define ENABLE_DEBUG 0 - -#if ENABLE_DEBUG - #include <iostream> - #define DEBUG(str) std::cout << str << std::endl; -#else - #define DEBUG(str) -#endif - namespace AudioGrapher { using boost::format; @@ -44,8 +35,11 @@ SampleRateConverter::init (nframes_t in_rate, nframes_t out_rate, int quality) active = true; int err; - if ((src_state = src_new (quality, channels, &err)) == 0) { - throw Exception (*this, str (format ("Cannot initialize sample rate converter: %1%") % src_strerror (err))); + src_state = src_new (quality, channels, &err); + if (throw_level (ThrowObject) && !src_state) { + throw Exception (*this, str (format + ("Cannot initialize sample rate converter: %1%") + % src_strerror (err))); } src_data.src_ratio = (double) out_rate / (double) in_rate; @@ -70,7 +64,7 @@ SampleRateConverter::allocate_buffers (nframes_t max_frames) max_leftover_frames = 4 * max_frames; leftover_data = (float *) realloc (leftover_data, max_leftover_frames * sizeof (float)); - if (!leftover_data) { + if (throw_level (ThrowObject) && !leftover_data) { throw Exception (*this, "A memory allocation error occured"); } @@ -92,13 +86,13 @@ SampleRateConverter::process (ProcessContext<float> const & c) nframes_t frames = c.frames(); float * in = const_cast<float *> (c.data()); // TODO check if this is safe! - if (frames > max_frames_in) { + if (throw_level (ThrowStrict) && frames > max_frames_in) { throw Exception (*this, str (format ( "process() called with too many frames, %1% instead of %2%") % frames % max_frames_in)); } - if (frames % channels != 0) { + if (throw_level (ThrowStrict) && frames % channels != 0) { throw Exception (*this, boost::str (boost::format ( "Number of frames given to process() was not a multiple of channels: %1% frames with %2% channels") % frames % channels)); @@ -121,7 +115,7 @@ SampleRateConverter::process (ProcessContext<float> const & c) /* first time, append new data from data_in into the leftover_data buffer */ - memcpy (&leftover_data [leftover_frames * channels], in, frames * sizeof(float)); + TypeUtils<float>::copy (&leftover_data [leftover_frames * channels], in, frames); src_data.input_frames = frames + leftover_frames; } else { @@ -140,23 +134,28 @@ SampleRateConverter::process (ProcessContext<float> const & c) first_time = false; - DEBUG ("data_in: " << src_data.data_in); - DEBUG ("input_frames: " << src_data.input_frames); - DEBUG ("data_out: " << src_data.data_out); - DEBUG ("output_frames: " << src_data.output_frames); + if (debug_level (DebugVerbose)) { + debug_stream() << "data_in: " << src_data.data_in << + ", input_frames: " << src_data.input_frames << + ", data_out: " << src_data.data_out << + ", output_frames: " << src_data.output_frames << std::endl; + } - if ((err = src_process (src_state, &src_data)) != 0) { - throw Exception (*this, str (format ("An error occured during sample rate conversion: %1%") % src_strerror (err))); + err = src_process (src_state, &src_data); + if (throw_level (ThrowProcess) && err) { + throw Exception (*this, str (format + ("An error occured during sample rate conversion: %1%") + % src_strerror (err))); } leftover_frames = src_data.input_frames - src_data.input_frames_used; if (leftover_frames > 0) { - if (leftover_frames > max_leftover_frames) { + if (throw_level (ThrowProcess) && leftover_frames > max_leftover_frames) { throw Exception(*this, "leftover frames overflowed"); } - memmove (leftover_data, (char *) &src_data.data_in[src_data.input_frames_used * channels], - leftover_frames * channels * sizeof(float)); + TypeUtils<float>::move (&src_data.data_in[src_data.input_frames_used * channels], + leftover_data, leftover_frames * channels); } ProcessContext<float> c_out (c, data_out, src_data.output_frames_gen * channels); @@ -165,11 +164,17 @@ SampleRateConverter::process (ProcessContext<float> const & c) } output (c_out); - DEBUG ("src_data.output_frames_gen: " << src_data.output_frames_gen << ", leftover_frames: " << leftover_frames); + if (debug_level (DebugProcess)) { + debug_stream() << + "src_data.output_frames_gen: " << src_data.output_frames_gen << + ", leftover_frames: " << leftover_frames << std::endl; + } - if (src_data.output_frames_gen == 0 && leftover_frames) { throw Exception (*this, boost::str (boost::format ( - "No output frames genereated with %1% leftover frames") - % leftover_frames)); } + if (throw_level (ThrowProcess) && src_data.output_frames_gen == 0 && leftover_frames) { + throw Exception (*this, boost::str (boost::format + ("No output frames genereated with %1% leftover frames") + % leftover_frames)); + } } while (leftover_frames > frames); @@ -189,7 +194,9 @@ void SampleRateConverter::set_end_of_input (ProcessContext<float> const & c) /* No idea why this has to be done twice for all data to be written, * but that just seems to be the way it is... */ + dummy.remove_flag (ProcessContext<float>::EndOfInput); process (dummy); + dummy.set_flag (ProcessContext<float>::EndOfInput); process (dummy); } diff --git a/libs/audiographer/tests/sr_converter_test.cc b/libs/audiographer/tests/sr_converter_test.cc index 59c05806c6..e7b49a1b71 100644 --- a/libs/audiographer/tests/sr_converter_test.cc +++ b/libs/audiographer/tests/sr_converter_test.cc @@ -9,6 +9,7 @@ class SampleRateConverterTest : public CppUnit::TestFixture CPPUNIT_TEST (testNoConversion); CPPUNIT_TEST (testUpsampleLength); CPPUNIT_TEST (testDownsampleLength); + CPPUNIT_TEST (testRespectsEndOfInput); CPPUNIT_TEST_SUITE_END (); public: @@ -17,6 +18,7 @@ class SampleRateConverterTest : public CppUnit::TestFixture frames = 128; random_data = TestUtils::init_random_data(frames); sink.reset (new AppendingVectorSink<float>()); + grabber.reset (new ProcessContextGrabber<float>()); converter.reset (new SampleRateConverter (1)); } @@ -88,10 +90,36 @@ class SampleRateConverterTest : public CppUnit::TestFixture CPPUNIT_ASSERT (half_frames - tolerance < frames_output && frames_output < half_frames + tolerance); } + void testRespectsEndOfInput() + { + assert (frames % 2 == 0); + nframes_t const half_frames = frames / 2; + + converter->init (44100, 48000); + converter->allocate_buffers (half_frames); + converter->add_output (grabber); + + ProcessContext<float> c (random_data, half_frames, 1); + converter->process (c); + ProcessContext<float> c2 (&random_data[half_frames], half_frames / 2, 1); + c2.set_flag (ProcessContext<float>::EndOfInput); + converter->process (c2); + + for (std::list<ProcessContext<float> >::iterator it = grabber->contexts.begin(); it != grabber->contexts.end(); ++it) { + std::list<ProcessContext<float> >::iterator next = it; ++next; + if (next == grabber->contexts.end()) { + CPPUNIT_ASSERT (it->has_flag (ProcessContext<float>::EndOfInput)); + } else { + CPPUNIT_ASSERT (!it->has_flag (ProcessContext<float>::EndOfInput)); + } + } + } + private: boost::shared_ptr<SampleRateConverter > converter; boost::shared_ptr<AppendingVectorSink<float> > sink; + boost::shared_ptr<ProcessContextGrabber<float> > grabber; float * random_data; nframes_t frames; diff --git a/libs/audiographer/tests/utils.h b/libs/audiographer/tests/utils.h index 6b6a1fea0f..b13a7b7f07 100644 --- a/libs/audiographer/tests/utils.h +++ b/libs/audiographer/tests/utils.h @@ -12,6 +12,7 @@ #include "audiographer/exception.h" #include <vector> +#include <list> #include <cstring> #include <cstdlib> #include <ctime> @@ -116,4 +117,19 @@ class ThrowingSink : public AudioGrapher::Sink<T> using AudioGrapher::Sink<T>::process; }; +template<typename T> +class ProcessContextGrabber : public AudioGrapher::Sink<T> +{ + public: + void process (AudioGrapher::ProcessContext<T> const & c) + { + contexts.push_back (c); + } + using AudioGrapher::Sink<T>::process; + + typedef std::list<AudioGrapher::ProcessContext<T> > ContextList; + ContextList contexts; + +}; + #endif // AUDIOGRAPHER_TESTS_UTILS_H |