From e55ef88ee9c95cb7e24077e78f459dbbbf615202 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Thu, 14 Jul 2016 04:34:18 +0200 Subject: refactoring to prepare for real-time export * add a threaded TmpFile Writer * update API calls to that process_export_fw() can be used as process_function The idea is to re-use export infrastructure from normalization: export to a tmp-file and then encode target formats after that. --- libs/ardour/ardour/audioengine.h | 2 +- libs/ardour/ardour/export_graph_builder.h | 2 + libs/ardour/ardour/session.h | 7 +- libs/ardour/export_graph_builder.cc | 1 + libs/ardour/session.cc | 1 + libs/ardour/session_export.cc | 74 ++++++--- libs/audiographer/audiographer/sndfile/tmp_file.h | 2 +- .../audiographer/sndfile/tmp_file_rt.h | 171 +++++++++++++++++++++ 8 files changed, 231 insertions(+), 29 deletions(-) create mode 100644 libs/audiographer/audiographer/sndfile/tmp_file_rt.h diff --git a/libs/ardour/ardour/audioengine.h b/libs/ardour/ardour/audioengine.h index 7bb7aa3a4d..cb5b82d058 100644 --- a/libs/ardour/ardour/audioengine.h +++ b/libs/ardour/ardour/audioengine.h @@ -161,7 +161,7 @@ class LIBARDOUR_API AudioEngine : public PortManager, public SessionHandlePtr (the regular process() call to session->process() is not made) */ - PBD::Signal1 Freewheel; + PBD::Signal1 Freewheel; PBD::Signal0 Xrun; diff --git a/libs/ardour/ardour/export_graph_builder.h b/libs/ardour/ardour/export_graph_builder.h index df02c9eb57..68eb927513 100644 --- a/libs/ardour/ardour/export_graph_builder.h +++ b/libs/ardour/ardour/export_graph_builder.h @@ -41,6 +41,7 @@ namespace AudioGrapher { template class SndfileWriter; template class SilenceTrimmer; template class TmpFile; + template class TmpFileRt; template class Threader; template class AllocatingProcessContext; } @@ -164,6 +165,7 @@ class LIBARDOUR_API ExportGraphBuilder typedef boost::shared_ptr LoudnessReaderPtr; typedef boost::shared_ptr NormalizerPtr; typedef boost::shared_ptr > TmpFilePtr; + typedef boost::shared_ptr > TmpFileRtPtr; typedef boost::shared_ptr > ThreaderPtr; typedef boost::shared_ptr > BufferPtr; diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index c6f32bbe07..db80ddcae3 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -704,7 +704,7 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop boost::shared_ptr get_export_handler (); boost::shared_ptr get_export_status (); - int start_audio_export (framepos_t position); + int start_audio_export (framepos_t position, bool realtime = false); PBD::Signal1 ProcessExport; static PBD::Signal2 Exported; @@ -1230,8 +1230,8 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop void process_without_events (pframes_t); void process_with_events (pframes_t); void process_audition (pframes_t); - int process_export (pframes_t); - int process_export_fw (pframes_t); + void process_export (pframes_t); + void process_export_fw (pframes_t); void block_processing() { g_atomic_int_set (&processing_prohibited, 1); } void unblock_processing() { g_atomic_int_set (&processing_prohibited, 0); } @@ -1268,6 +1268,7 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop bool _exporting; bool _export_rolling; + bool _realtime_export; framepos_t _export_preroll; framepos_t _export_latency; diff --git a/libs/ardour/export_graph_builder.cc b/libs/ardour/export_graph_builder.cc index 27b2f6948e..49b40a6736 100644 --- a/libs/ardour/export_graph_builder.cc +++ b/libs/ardour/export_graph_builder.cc @@ -36,6 +36,7 @@ #include "audiographer/general/silence_trimmer.h" #include "audiographer/general/threader.h" #include "audiographer/sndfile/tmp_file.h" +#include "audiographer/sndfile/tmp_file_rt.h" #include "audiographer/sndfile/sndfile_writer.h" #include "ardour/audioengine.h" diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 67f43dc470..9ab82c7106 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -213,6 +213,7 @@ Session::Session (AudioEngine &eng, , post_export_position (0) , _exporting (false) , _export_rolling (false) + , _realtime_export (false) , _export_preroll (0) , _export_latency (0) , _pre_export_mmc_enabled (false) diff --git a/libs/ardour/session_export.cc b/libs/ardour/session_export.cc index 1dee1f8d3c..84342fa3d9 100644 --- a/libs/ardour/session_export.cc +++ b/libs/ardour/session_export.cc @@ -104,13 +104,20 @@ Session::pre_export () /** Called for each range that is being exported */ int -Session::start_audio_export (framepos_t position) +Session::start_audio_export (framepos_t position, bool realtime) { if (!_exporting) { pre_export (); } - _export_preroll = Config->get_export_preroll() * nominal_frame_rate (); + _realtime_export = realtime; + + if (realtime) { + _export_preroll = nominal_frame_rate (); + } else { + _export_preroll = Config->get_export_preroll() * nominal_frame_rate (); + } + if (_export_preroll == 0) { // must be > 0 so that transport is started in sync. _export_preroll = 1; @@ -170,12 +177,19 @@ Session::start_audio_export (framepos_t position) return -1; } - _engine.Freewheel.connect_same_thread (export_freewheel_connection, boost::bind (&Session::process_export_fw, this, _1)); - _export_rolling = true; - return _engine.freewheel (true); + if (_realtime_export) { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + _export_rolling = true; + process_function = &Session::process_export_fw; + return 0; + } else { + _engine.Freewheel.connect_same_thread (export_freewheel_connection, boost::bind (&Session::process_export_fw, this, _1)); + _export_rolling = true; + return _engine.freewheel (true); + } } -int +void Session::process_export (pframes_t nframes) { if (_export_rolling && export_status->stop) { @@ -201,26 +215,27 @@ Session::process_export (pframes_t nframes) } catch (std::exception & e) { error << string_compose (_("Export ended unexpectedly: %1"), e.what()) << endmsg; export_status->abort (true); - return -1; } - - return 0; } -int +void Session::process_export_fw (pframes_t nframes) { if (_export_preroll > 0) { - _engine.main_thread()->get_buffers (); + if (!_realtime_export) { + _engine.main_thread()->get_buffers (); + } fail_roll (nframes); - _engine.main_thread()->drop_buffers (); + if (!_realtime_export) { + _engine.main_thread()->drop_buffers (); + } _export_preroll -= std::min ((framepos_t)nframes, _export_preroll); if (_export_preroll > 0) { // clear out buffers (reverb tails etc). - return 0; + return; } set_transport_speed (1.0, 0, false); @@ -228,29 +243,36 @@ Session::process_export_fw (pframes_t nframes) g_atomic_int_set (&_butler->should_do_transport_work, 0); post_transport (); - return 0; + return; } if (_export_latency > 0) { framepos_t remain = std::min ((framepos_t)nframes, _export_latency); - _engine.main_thread()->get_buffers (); + if (!_realtime_export) { + _engine.main_thread()->get_buffers (); + } process_without_events (remain); - _engine.main_thread()->drop_buffers (); + if (!_realtime_export) { + _engine.main_thread()->drop_buffers (); + } _export_latency -= remain; nframes -= remain; if (nframes == 0) { - return 0; + return; } } - - _engine.main_thread()->get_buffers (); + if (!_realtime_export) { + _engine.main_thread()->get_buffers (); + } process_export (nframes); - _engine.main_thread()->drop_buffers (); + if (!_realtime_export) { + _engine.main_thread()->drop_buffers (); + } - return 0; + return; } int @@ -279,9 +301,13 @@ Session::finalize_audio_export () /* Clean up */ - _engine.freewheel (false); - - export_freewheel_connection.disconnect(); + if (_realtime_export) { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + process_function = &Session::process_with_events; + } else { + _engine.freewheel (false); + export_freewheel_connection.disconnect(); + } _mmc->enable_send (_pre_export_mmc_enabled); diff --git a/libs/audiographer/audiographer/sndfile/tmp_file.h b/libs/audiographer/audiographer/sndfile/tmp_file.h index 296fcdf057..c53557beda 100644 --- a/libs/audiographer/audiographer/sndfile/tmp_file.h +++ b/libs/audiographer/audiographer/sndfile/tmp_file.h @@ -35,7 +35,7 @@ class TmpFile : public SndfileWriter, public SndfileReader ~TmpFile() { /* explicitly close first, some OS (yes I'm looking at you windows) - * cannot delet files that are still open + * cannot delete files that are still open */ if (!filename.empty()) { SndfileBase::close(); diff --git a/libs/audiographer/audiographer/sndfile/tmp_file_rt.h b/libs/audiographer/audiographer/sndfile/tmp_file_rt.h new file mode 100644 index 0000000000..ec1f85c773 --- /dev/null +++ b/libs/audiographer/audiographer/sndfile/tmp_file_rt.h @@ -0,0 +1,171 @@ +#ifndef AUDIOGRAPHER_TMP_FILE_RT_H +#define AUDIOGRAPHER_TMP_FILE_RT_H + +#include +#include + +#include +#include "pbd/gstdio_compat.h" +#include "pbd/ringbuffer.h" + +#include "audiographer/flag_debuggable.h" +#include "audiographer/sink.h" +#include "sndfile_reader.h" + +namespace AudioGrapher +{ + +/// A temporary file deleted after this class is destructed +template +class TmpFileRt + : public virtual SndfileReader + , public virtual SndfileBase + , public Sink + , public FlagDebuggable<> +{ + public: + + /// \a filename_template must match the requirements for mkstemp, i.e. end in "XXXXXX" + TmpFileRt (char * filename_template, int format, ChannelCount channels, framecnt_t samplerate) + : SndfileHandle (g_mkstemp(filename_template), true, SndfileBase::ReadWrite, format, channels, samplerate) + , filename (filename_template) + , _rb (samplerate * channels) + { + init (); + } + + using SndfileHandle::operator=; + + ~TmpFileRt() + { + end_write (); + /* explicitly close first, some OS (yes I'm looking at you windows) + * cannot delete files that are still open + */ + if (!filename.empty()) { + SndfileBase::close(); + std::remove(filename.c_str()); + } + pthread_mutex_destroy (&_disk_thread_lock); + pthread_cond_destroy (&_data_ready); + } + + framecnt_t get_frames_written() const { return frames_written; } + void reset_frames_written_count() { frames_written = 0; } + + /// Writes data to file + void process (ProcessContext const & c) + { + check_flags (*this, c); + + if (SndfileReader::throw_level (ThrowStrict) && c.channels() != channels()) { + throw Exception (*this, boost::str (boost::format + ("Wrong number of channels given to process(), %1% instead of %2%") + % c.channels() % channels())); + } + + if (SndfileReader::throw_level (ThrowProcess) && _rb.write_space() < c.frames()) { + throw Exception (*this, boost::str (boost::format + ("Could not write data to ringbuffer/output file (%1%)") + % strError())); + } + + _rb.write (c.data(), c.frames()); + + if (pthread_mutex_trylock (&_disk_thread_lock) == 0) { + pthread_cond_signal (&_data_ready); + pthread_mutex_unlock (&_disk_thread_lock); + } + + if (c.has_flag(ProcessContext::EndOfInput)) { + end_write (); // XXX not rt-safe -- TODO add API call to flush + FileWritten (filename); + } + } + + using Sink::process; + + PBD::Signal1 FileWritten; + + void disk_thread () + { + const size_t chunksize = 8192; // samples + T *framebuf = (T*) malloc (chunksize * sizeof (T)); + + pthread_mutex_lock (&_disk_thread_lock); + + while (1) { + if (!_capture) { + break; + } + if (_rb.read_space () >= chunksize) { + _rb.read (framebuf, chunksize); + framecnt_t const written = write (framebuf, chunksize); + assert (written == chunksize); + frames_written += written; + } + pthread_cond_wait (&_data_ready, &_disk_thread_lock); + } + + // flush ringbuffer + while (_rb.read_space () > 0) { + size_t remain = std::min ((size_t)_rb.read_space (), chunksize); + _rb.read (framebuf, remain); + framecnt_t const written = write (framebuf, remain); + frames_written += written; + } + writeSync(); + + pthread_mutex_unlock (&_disk_thread_lock); + free (framebuf); + } + + protected: + std::string filename; + framecnt_t frames_written; + + bool _capture; + RingBuffer _rb; + + pthread_mutex_t _disk_thread_lock; + pthread_cond_t _data_ready; + pthread_t _thread_id; + + static void * _disk_thread (void *arg) + { + TmpFileRt *d = static_cast(arg); + d->disk_thread (); + pthread_exit (0); + return 0; + } + + void end_write () { + pthread_mutex_lock (&_disk_thread_lock); + if (!_capture) { + pthread_mutex_unlock (&_disk_thread_lock); + return; + } + _capture = false; + pthread_cond_signal (&_data_ready); + pthread_mutex_unlock (&_disk_thread_lock); + pthread_join (_thread_id, NULL); + } + + void init() + { + frames_written = 0; + _capture = true; + add_supported_flag (ProcessContext::EndOfInput); + pthread_mutex_init (&_disk_thread_lock, 0); + pthread_cond_init (&_data_ready, 0); + + pthread_create (&_thread_id, NULL, _disk_thread, this); + } + + private: + TmpFileRt (TmpFileRt const & other) : SndfileHandle (other) {} +}; + +} // namespace + +#endif // AUDIOGRAPHER_TMP_FILE_RT_H -- cgit v1.2.3