X-Git-Url: https://main.carlh.net/gitweb/?p=ardour.git;a=blobdiff_plain;f=libs%2Fardour%2Fsession_export.cc;h=463f50441121fc1e5d090c9b6ac6092f69341cde;hp=6902a426c3c0fb98e1ba757ff5e0a05c912caee3;hb=c8c6bca6587450ff64303dbc994a4cd28d6ce7aa;hpb=fedf3d34f32264ac57c6a222b678dc90f2bb1a88 diff --git a/libs/ardour/session_export.cc b/libs/ardour/session_export.cc index 6902a426c3..463f504411 100644 --- a/libs/ardour/session_export.cc +++ b/libs/ardour/session_export.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 1999-2002 Paul Davis + Copyright (C) 1999-2008 Paul Davis 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 @@ -15,633 +15,318 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id$ */ -/* see gdither.cc for why we have to do this */ - -#define _ISOC9X_SOURCE 1 -#define _ISOC99_SOURCE 1 -#include -#undef _ISOC99_SOURCE -#undef _ISOC9X_SOURCE -#undef __USE_SVID -#define __USE_SVID 1 -#include -#undef __USE_SVID - -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "i18n.h" -using namespace std; -using namespace ARDOUR; -using namespace PBD; +#include "pbd/error.h" +#include -static int -convert_spec_to_info (AudioExportSpecification& spec, SF_INFO& sfinfo) -{ - if (spec.path.length() == 0) { - error << _("Export: no output file specified") << endmsg; - return -1; - } +#include - /* XXX add checks that the directory path exists, and also - check if we are overwriting an existing file... - */ +#include "ardour/audioengine.h" +#include "ardour/butler.h" +#include "ardour/export_handler.h" +#include "ardour/export_status.h" +#include "ardour/process_thread.h" +#include "ardour/session.h" +#include "ardour/track.h" - sfinfo.format = spec.format; - sfinfo.samplerate = spec.sample_rate; - sfinfo.frames = spec.end_frame - spec.start_frame + 1; - sfinfo.channels = min (spec.channels, 2U); +#include "pbd/i18n.h" - return 0; -} - -AudioExportSpecification::AudioExportSpecification () -{ - init (); -} +using namespace std; +using namespace ARDOUR; +using namespace PBD; -AudioExportSpecification::~AudioExportSpecification () +boost::shared_ptr +Session::get_export_handler () { - clear (); -} + if (!export_handler) { + export_handler.reset (new ExportHandler (*this)); + } -void -AudioExportSpecification::init () -{ - src_state = 0; - pos = 0; - total_frames = 0; - out = 0; - channels = 0; - running = false; - stop = false; - progress = 0.0; - status = 0; - dither = 0; - start_frame = 0; - end_frame = 0; - dataF = 0; - dataF2 = 0; - leftoverF = 0; - max_leftover_frames = 0; - leftover_frames = 0; - output_data = 0; - out_samples_max = 0; - data_width = 0; - do_freewheel = false; + return export_handler; } -void -AudioExportSpecification::clear () +boost::shared_ptr +Session::get_export_status () { - if (out) { - sf_close (out); - out = 0; - } - - if (src_state) { - src_delete (src_state); - src_state = 0; + if (!export_status) { + export_status.reset (new ExportStatus ()); } - if (dither) { - gdither_free (dither); - dither = 0; - } - - if (output_data) { - free (output_data); - output_data = 0; - } - if (dataF) { - delete [] dataF; - dataF = 0; - } - if (dataF2) { - delete [] dataF2; - dataF2 = 0; - } - if (leftoverF) { - delete [] leftoverF; - leftoverF = 0; - } - - freewheel_connection.disconnect (); - - init (); + return export_status; } + int -AudioExportSpecification::prepare (nframes_t blocksize, nframes_t frate) +Session::pre_export () { - char errbuf[256]; - GDitherSize dither_size; - - frame_rate = frate; + get_export_status (); // Init export_status - if (channels == 0) { - error << _("illegal frame range in export specification") << endmsg; - return -1; - } + /* take everyone out of awrite to avoid disasters */ - if (start_frame >= end_frame) { - error << _("illegal frame range in export specification") << endmsg; - return -1; - } + { + boost::shared_ptr r = routes.reader (); - if ((data_width = sndfile_data_width(format)) == 0) { - error << _("Bad data width size. Report me!") << endmsg; - return -1; + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + (*i)->protect_automation (); + } } - switch (data_width) { - case 8: - dither_size = GDither8bit; - break; - - case 16: - dither_size = GDither16bit; - break; + /* prepare transport */ - case 24: - dither_size = GDither32bit; - break; + realtime_stop (true, true); - default: - dither_size = GDitherFloat; - break; - } - - if (convert_spec_to_info (*this, sfinfo)) { - return -1; - } - - /* XXX make sure we have enough disk space for the output */ - - if ((out = sf_open (path.c_str(), SFM_WRITE, &sfinfo)) == 0) { - sf_error_str (0, errbuf, sizeof (errbuf) - 1); - error << string_compose(_("Export: cannot open output file \"%1\" (%2)"), path, errbuf) << endmsg; - return -1; + if (get_record_enabled()) { + disable_record (false); } - dataF = new float[blocksize * channels]; + unset_play_loop (); - if (sample_rate != frame_rate) { - int err; + /* no slaving */ - if ((src_state = src_new (src_quality, channels, &err)) == 0) { - error << string_compose (_("cannot initialize sample rate conversion: %1"), src_strerror (err)) << endmsg; - return -1; - } - - src_data.src_ratio = sample_rate / (double) frame_rate; - out_samples_max = (nframes_t) ceil (blocksize * src_data.src_ratio * channels); - dataF2 = new float[out_samples_max]; + post_export_sync = config.get_external_sync (); + post_export_position = _transport_frame; - max_leftover_frames = 4 * blocksize; - leftoverF = new float[max_leftover_frames * channels]; - leftover_frames = 0; + config.set_external_sync (false); - } else { - out_samples_max = blocksize * channels; - } + _exporting = true; + export_status->set_running (true); + export_status->Finished.connect_same_thread (*this, boost::bind (&Session::finalize_audio_export, this)); - dither = gdither_new (dither_type, channels, dither_size, data_width); + /* disable MMC output early */ - /* allocate buffers where dithering and output will occur */ + _pre_export_mmc_enabled = _mmc->send_enabled (); + _mmc->enable_send (false); - switch (data_width) { - case 8: - sample_bytes = 1; - break; + return 0; +} - case 16: - sample_bytes = 2; - break; +/** Called for each range that is being exported */ +int +Session::start_audio_export (framepos_t position, bool realtime) +{ + if (!_exporting) { + pre_export (); + } - case 24: - case 32: - sample_bytes = 4; - break; + _realtime_export = realtime; - default: - sample_bytes = 0; // float format - break; + if (realtime) { + _export_preroll = nominal_frame_rate (); + } else { + _export_preroll = Config->get_export_preroll() * nominal_frame_rate (); } - if (sample_bytes) { - output_data = (void*) malloc (sample_bytes * out_samples_max); + if (_export_preroll == 0) { + // must be > 0 so that transport is started in sync. + _export_preroll = 1; } - return 0; -} + /* "worst_track_latency" is the correct value for stem-exports + * see to Route::add_export_point(), + * + * for master-bus export, we'd need to add the master's latency. + * or actually longest-total-session-latency. + * + * We can't use worst_playback_latency because that includes + * includes external latencies and would overcompensate. + */ + _export_latency = worst_track_latency (); + + /* We're about to call Track::seek, so the butler must have finished everything + up otherwise it could be doing do_refill in its thread while we are doing + it here. + */ -int -AudioExportSpecification::process (nframes_t nframes) -{ - float* float_buffer = 0; - uint32_t chn; - uint32_t x; - uint32_t i; - sf_count_t written; - char errbuf[256]; - nframes_t to_write = 0; - int cnt = 0; - - do { - - /* now do sample rate conversion */ - - if (sample_rate != frame_rate) { - - int err; - - src_data.output_frames = out_samples_max / channels; - src_data.end_of_input = ((pos + nframes) >= end_frame); - src_data.data_out = dataF2; - - if (leftover_frames > 0) { - - /* input data will be in leftoverF rather than dataF */ - - src_data.data_in = leftoverF; - - if (cnt == 0) { - - /* first time, append new data from dataF into the leftoverF buffer */ - - memcpy (leftoverF + (leftover_frames * channels), dataF, nframes * channels * sizeof(float)); - src_data.input_frames = nframes + leftover_frames; - } else { - - /* otherwise, just use whatever is still left in leftoverF; the contents - were adjusted using memmove() right after the last SRC call (see - below) - */ - - src_data.input_frames = leftover_frames; - } - - } else { - - src_data.data_in = dataF; - src_data.input_frames = nframes; + _butler->wait_until_finished (); - } + /* get everyone to the right position */ - ++cnt; + { + boost::shared_ptr rl = routes.reader(); - if ((err = src_process (src_state, &src_data)) != 0) { - error << string_compose (_("an error occured during sample rate conversion: %1"), - src_strerror (err)) + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && tr->seek (position, true)) { + error << string_compose (_("%1: cannot seek to %2 for export"), + (*i)->name(), position) << endmsg; return -1; } - - to_write = src_data.output_frames_gen; - leftover_frames = src_data.input_frames - src_data.input_frames_used; - - if (leftover_frames > 0) { - if (leftover_frames > max_leftover_frames) { - error << _("warning, leftover frames overflowed, glitches might occur in output") << endmsg; - leftover_frames = max_leftover_frames; - } - memmove (leftoverF, (char *) (src_data.data_in + (src_data.input_frames_used * channels)), - leftover_frames * channels * sizeof(float)); - } - - float_buffer = dataF2; - - } else { - - /* no SRC, keep it simple */ - - to_write = nframes; - leftover_frames = 0; - float_buffer = dataF; - } - - if (output_data) { - memset (output_data, 0, sample_bytes * to_write * channels); - } - - switch (data_width) { - case 8: - case 16: - case 24: - for (chn = 0; chn < channels; ++chn) { - gdither_runf (dither, chn, to_write, float_buffer, output_data); - } - break; - - case 32: - for (chn = 0; chn < channels; ++chn) { - - int *ob = (int *) output_data; - const double int_max = (float) INT_MAX; - const double int_min = (float) INT_MIN; - - for (x = 0; x < to_write; ++x) { - i = chn + (x * channels); - - if (float_buffer[i] > 1.0f) { - ob[i] = INT_MAX; - } else if (float_buffer[i] < -1.0f) { - ob[i] = INT_MIN; - } else { - if (float_buffer[i] >= 0.0f) { - ob[i] = lrintf (int_max * float_buffer[i]); - } else { - ob[i] = - lrintf (int_min * float_buffer[i]); - } - } - } - } - break; - - default: - for (x = 0; x < to_write * channels; ++x) { - if (float_buffer[x] > 1.0f) { - float_buffer[x] = 1.0f; - } else if (float_buffer[x] < -1.0f) { - float_buffer[x] = -1.0f; - } - } - break; - } - - /* and export to disk */ - - switch (data_width) { - case 8: - /* XXXX no way to deliver 8 bit audio to libsndfile */ - written = to_write; - break; - - case 16: - written = sf_writef_short (out, (short*) output_data, to_write); - break; - - case 24: - case 32: - written = sf_writef_int (out, (int*) output_data, to_write); - break; - - default: - written = sf_writef_float (out, float_buffer, to_write); - break; - } - - if ((nframes_t) written != to_write) { - sf_error_str (out, errbuf, sizeof (errbuf) - 1); - error << string_compose(_("Export: could not write data to output file (%1)"), errbuf) << endmsg; - return -1; } + } + /* we just did the core part of a locate() call above, but + for the sake of any GUI, put the _transport_frame in + the right place too. + */ - } while (leftover_frames >= nframes); + _transport_frame = position; + export_status->stop = false; - return 0; -} + /* get transport ready. note how this is calling butler functions + from a non-butler thread. we waited for the butler to stop + what it was doing earlier in Session::pre_export() and nothing + since then has re-awakened it. + */ -int -Session::start_audio_export (AudioExportSpecification& spec) -{ - int ret; + /* we are ready to go ... */ - if (spec.prepare (current_block_size, frame_rate())) { + if (!_engine.connected()) { return -1; } - spec.pos = spec.start_frame; - spec.end_frame = spec.end_frame; - spec.total_frames = spec.end_frame - spec.start_frame; - - spec.freewheel_connection = _engine.Freewheel.connect (sigc::bind (mem_fun (*this, &Session::process_export), &spec)); + _engine.Freewheel.connect_same_thread (export_freewheel_connection, boost::bind (&Session::process_export_fw, this, _1)); - if ((ret = _engine.freewheel (true)) == 0) { - spec.running = true; - spec.do_freewheel = false; + if (_realtime_export) { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + _export_rolling = true; + process_function = &Session::process_export_fw; + return 0; + } else { + _export_rolling = true; + return _engine.freewheel (true); } - - return ret; } -int -Session::stop_audio_export (AudioExportSpecification& spec) +void +Session::process_export (pframes_t nframes) { - /* can't use stop_transport() here because we need - an immediate halt and don't require all the declick - stuff that stop_transport() implements. - */ + if (_export_rolling && export_status->stop) { + stop_audio_export (); + } - realtime_stop (true); - schedule_butler_transport_work (); + if (_export_rolling) { + if (!_realtime_export) { + /* make sure we've caught up with disk i/o, since + * we're running faster than realtime c/o JACK. + */ + _butler->wait_until_finished (); + } - /* restart slaving */ + /* do the usual stuff */ - if (post_export_slave != None) { - Config->set_slave_source (post_export_slave); - } else { - locate (post_export_position, false, false, false); + process_without_events (nframes); + } else if (_realtime_export) { + fail_roll (nframes); // somehow we need to silence _ALL_ output buffers } - spec.clear (); - _exporting = false; + try { + /* handle export - XXX what about error handling? */ - spec.running = false; + ProcessExport (nframes); - return 0; + } catch (std::exception & e) { + error << string_compose (_("Export ended unexpectedly: %1"), e.what()) << endmsg; + export_status->abort (true); + } } -int -Session::prepare_to_export (AudioExportSpecification& spec) +void +Session::process_export_fw (pframes_t nframes) { - int ret = -1; - - wait_till_butler_finished (); + const bool need_buffers = _engine.freewheeling (); + if (_export_preroll > 0) { - /* take everyone out of awrite to avoid disasters */ - - { - boost::shared_ptr r = routes.reader (); - - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - (*i)->protect_automation (); + if (need_buffers) { + _engine.main_thread()->get_buffers (); + } + fail_roll (nframes); + if (need_buffers) { + _engine.main_thread()->drop_buffers (); } - } - /* get everyone to the right position */ + _export_preroll -= std::min ((framepos_t)nframes, _export_preroll); - { - boost::shared_ptr dsl = diskstreams.reader(); - - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - if ((*i)-> seek (spec.start_frame, true)) { - error << string_compose (_("%1: cannot seek to %2 for export"), - (*i)->name(), spec.start_frame) - << endmsg; - goto out; - } + if (_export_preroll > 0) { + // clear out buffers (reverb tails etc). + return; } - } - /* make sure we are actually rolling */ + set_transport_speed (1.0, 0, false); + butler_transport_work (); + g_atomic_int_set (&_butler->should_do_transport_work, 0); + post_transport (); - if (get_record_enabled()) { - disable_record (false); + return; } - _exporting = true; - - /* no slaving */ - - post_export_slave = Config->get_slave_source (); - post_export_position = _transport_frame; - - Config->set_slave_source (None); + if (_export_latency > 0) { + framepos_t remain = std::min ((framepos_t)nframes, _export_latency); - /* get transport ready */ + if (need_buffers) { + _engine.main_thread()->get_buffers (); + } + process_without_events (remain); + if (need_buffers) { + _engine.main_thread()->drop_buffers (); + } - set_transport_speed (1.0, false); - butler_transport_work (); - g_atomic_int_set (&butler_should_do_transport_work, 0); - post_transport (); + _export_latency -= remain; + nframes -= remain; - /* we are ready to go ... */ + if (nframes == 0) { + return; + } + } - ret = 0; + if (need_buffers) { + _engine.main_thread()->get_buffers (); + } + process_export (nframes); + if (need_buffers) { + _engine.main_thread()->drop_buffers (); + } - out: - return ret; + return; } int -Session::process_export (nframes_t nframes, AudioExportSpecification* spec) +Session::stop_audio_export () { - uint32_t chn; - uint32_t x; - int ret = -1; - nframes_t this_nframes; + /* can't use stop_transport() here because we need + an immediate halt and don't require all the declick + stuff that stop_transport() implements. + */ - /* This is not required to be RT-safe because we are running while freewheeling */ + realtime_stop (true, true); + _export_rolling = false; + _butler->schedule_transport_work (); - if (spec->do_freewheel == false) { - - /* first time in export function: get set up */ + return 0; +} - if (prepare_to_export (*spec)) { - spec->running = false; - spec->status = -1; - return -1; - } - - spec->do_freewheel = true; - } +void +Session::finalize_audio_export () +{ + _exporting = false; - if (!_exporting) { - /* finished, but still freewheeling */ - process_without_events (nframes); - return 0; - } - - if (!spec->running || spec->stop || (this_nframes = min ((spec->end_frame - spec->pos), nframes)) == 0) { - process_without_events (nframes); - return stop_audio_export (*spec); + if (_export_rolling) { + stop_audio_export (); } - /* make sure we've caught up with disk i/o, since - we're running faster than realtime c/o JACK. - */ - - wait_till_butler_finished (); - - /* do the usual stuff */ - - process_without_events (nframes); + /* Clean up */ - /* and now export the results */ + if (_realtime_export) { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + process_function = &Session::process_with_events; + } + _engine.freewheel (false); + export_freewheel_connection.disconnect(); - nframes = this_nframes; + _mmc->enable_send (_pre_export_mmc_enabled); - memset (spec->dataF, 0, sizeof (spec->dataF[0]) * nframes * spec->channels); + /* maybe write CUE/TOC */ - /* foreach output channel ... */ - - for (chn = 0; chn < spec->channels; ++chn) { - - AudioExportPortMap::iterator mi = spec->port_map.find (chn); - - if (mi == spec->port_map.end()) { - /* no ports exported to this channel */ - continue; - } - - vector& mapped_ports ((*mi).second); - - for (vector::iterator t = mapped_ports.begin(); t != mapped_ports.end(); ++t) { - - /* OK, this port's output is supposed to appear on this channel - */ + export_handler.reset(); + export_status.reset(); - AudioPort* const port = dynamic_cast((*t).first); - if (port == 0) { - cerr << "FIXME: Non-audio export" << endl; - continue; - } - Sample* port_buffer = port->get_audio_buffer().data(nframes); - - /* now interleave the data from the channel into the float buffer */ - - for (x = 0; x < nframes; ++x) { - spec->dataF[chn+(x*spec->channels)] += (float) port_buffer[x]; - } - } - } + /* restart slaving */ - if (spec->process (nframes)) { - goto out; - } - - spec->pos += nframes; - spec->progress = 1.0 - (((float) spec->end_frame - spec->pos) / spec->total_frames); - - /* and we're good to go */ - - ret = 0; - - out: - if (ret) { - sf_close (spec->out); - spec->out = 0; - unlink (spec->path.c_str()); - spec->running = false; - spec->status = ret; - _exporting = false; + if (post_export_sync) { + config.set_external_sync (true); + } else { + locate (post_export_position, false, false, false, false, false); } - - return ret; } -