1 /*
2 * Copyright (C) 2009-2013 Sakari Bergen <sakari.bergen@beatwaves.net>
3 * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
4 * Copyright (C) 2010-2017 Paul Davis <paul@linuxaudiosystems.com>
5 * Copyright (C) 2011-2014 David Robillard <d@drobilla.net>
6 * Copyright (C) 2012-2016 Tim Mayberry <mojofunk@gmail.com>
7 * Copyright (C) 2013-2016 John Emmas <john@creativepost.co.uk>
8 * Copyright (C) 2015-2019 Robin Gareus <robin@gareus.org>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 */
24
25 #include <vector>
26
27 #include <glibmm/miscutils.h>
28 #include <glibmm/timer.h>
29
30 #include "pbd/uuid.h"
31 #include "pbd/file_utils.h"
32 #include "pbd/cpus.h"
33
34 #include "audiographer/process_context.h"
35 #include "audiographer/general/chunker.h"
36 #include "audiographer/general/cmdpipe_writer.h"
37 #include "audiographer/general/demo_noise.h"
38 #include "audiographer/general/interleaver.h"
39 #include "audiographer/general/limiter.h"
40 #include "audiographer/general/normalizer.h"
41 #include "audiographer/general/analyser.h"
42 #include "audiographer/general/peak_reader.h"
43 #include "audiographer/general/loudness_reader.h"
44 #include "audiographer/general/sample_format_converter.h"
45 #include "audiographer/general/sr_converter.h"
46 #include "audiographer/general/silence_trimmer.h"
47 #include "audiographer/general/threader.h"
48 #include "audiographer/sndfile/tmp_file.h"
49 #include "audiographer/sndfile/tmp_file_rt.h"
50 #include "audiographer/sndfile/tmp_file_sync.h"
51 #include "audiographer/sndfile/sndfile_writer.h"
52
53 #include "ardour/audioengine.h"
54 #include "ardour/export_channel_configuration.h"
55 #include "ardour/export_failed.h"
56 #include "ardour/export_filename.h"
57 #include "ardour/export_format_specification.h"
58 #include "ardour/export_graph_builder.h"
59 #include "ardour/export_timespan.h"
60 #include "ardour/filesystem_paths.h"
61 #include "ardour/session_directory.h"
62 #include "ardour/session_metadata.h"
63 #include "ardour/sndfile_helpers.h"
64 #include "ardour/system_exec.h"
65
66 using namespace AudioGrapher;
67 using std::string;
68
69 namespace ARDOUR {
70
ExportGraphBuilder(Session const & session)71 ExportGraphBuilder::ExportGraphBuilder (Session const & session)
72 : session (session)
73 , thread_pool (hardware_concurrency())
74 {
75 process_buffer_samples = session.engine().samples_per_cycle();
76 }
77
~ExportGraphBuilder()78 ExportGraphBuilder::~ExportGraphBuilder ()
79 {
80 }
81
82 samplecnt_t
process(samplecnt_t samples,bool last_cycle)83 ExportGraphBuilder::process (samplecnt_t samples, bool last_cycle)
84 {
85 assert(samples <= process_buffer_samples);
86
87 sampleoffset_t off = 0;
88 for (ChannelMap::iterator it = channels.begin(); it != channels.end(); ++it) {
89 Sample const * process_buffer = 0;
90 it->first->read (process_buffer, samples);
91
92 if (session.remaining_latency_preroll () >= _master_align + samples) {
93 /* Skip processing during pre-roll, only read/write export ringbuffers */
94 return 0;
95 }
96
97 off = 0;
98 if (session.remaining_latency_preroll () > _master_align) {
99 off = session.remaining_latency_preroll () - _master_align;
100 assert (off < samples);
101 }
102
103 ConstProcessContext<Sample> context(&process_buffer[off], samples - off, 1);
104 if (last_cycle) { context().set_flag (ProcessContext<Sample>::EndOfInput); }
105 it->second->process (context);
106 }
107
108 return samples - off;
109 }
110
111 bool
post_process()112 ExportGraphBuilder::post_process ()
113 {
114 for (std::list<Intermediate *>::iterator it = intermediates.begin(); it != intermediates.end(); /* ++ in loop */) {
115 if ((*it)->process()) {
116 it = intermediates.erase (it);
117 } else {
118 ++it;
119 }
120 }
121
122 return intermediates.empty();
123 }
124
125 unsigned
get_postprocessing_cycle_count() const126 ExportGraphBuilder::get_postprocessing_cycle_count() const
127 {
128 unsigned max = 0;
129 for (std::list<Intermediate *>::const_iterator it = intermediates.begin(); it != intermediates.end(); ++it) {
130 max = std::max(max, (*it)->get_postprocessing_cycle_count());
131 }
132 return max;
133 }
134
135 void
reset()136 ExportGraphBuilder::reset ()
137 {
138 timespan.reset();
139 channel_configs.clear ();
140 channels.clear ();
141 intermediates.clear ();
142 analysis_map.clear();
143 _realtime = false;
144 _master_align = 0;
145 }
146
147 void
cleanup(bool remove_out_files)148 ExportGraphBuilder::cleanup (bool remove_out_files/*=false*/)
149 {
150 ChannelConfigList::iterator iter = channel_configs.begin();
151
152 while (iter != channel_configs.end() ) {
153 iter->remove_children(remove_out_files);
154 iter = channel_configs.erase(iter);
155 }
156 }
157
158 void
set_current_timespan(boost::shared_ptr<ExportTimespan> span)159 ExportGraphBuilder::set_current_timespan (boost::shared_ptr<ExportTimespan> span)
160 {
161 timespan = span;
162 }
163
164 void
add_config(FileSpec const & config,bool rt)165 ExportGraphBuilder::add_config (FileSpec const & config, bool rt)
166 {
167 /* calculate common latency, shave off master-bus hardware playback latency (if any) */
168 _master_align = session.master_out() ? session.master_out()->output()->connected_latency (true) : 0;
169
170 ExportChannelConfiguration::ChannelList const & channels = config.channel_config->get_channels();
171
172 for(ExportChannelConfiguration::ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it) {
173 _master_align = std::min (_master_align, (*it)->common_port_playback_latency ());
174 }
175
176 /* now set-up port-data sniffing and delay-ringbuffers */
177 for(ExportChannelConfiguration::ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it) {
178 (*it)->prepare_export (process_buffer_samples, _master_align);
179 }
180
181 _realtime = rt;
182
183 /* If the sample rate is "session rate", change it to the real value.
184 * However, we need to copy it to not change the config which is saved...
185 */
186 FileSpec new_config (config);
187 new_config.format.reset(new ExportFormatSpecification(*new_config.format, false));
188 if(new_config.format->sample_rate() == ExportFormatBase::SR_Session) {
189 samplecnt_t session_rate = session.nominal_sample_rate();
190 new_config.format->set_sample_rate(ExportFormatBase::nearest_sample_rate(session_rate));
191 }
192
193 if (!new_config.channel_config->get_split ()) {
194 add_split_config (new_config);
195 return;
196 }
197
198 /* Split channel configurations are split into several channel configurations,
199 * each corresponding to a file, at this stage
200 */
201 typedef std::list<boost::shared_ptr<ExportChannelConfiguration> > ConfigList;
202 ConfigList file_configs;
203 new_config.channel_config->configurations_for_files (file_configs);
204
205 unsigned chan = 1;
206 for (ConfigList::iterator it = file_configs.begin(); it != file_configs.end(); ++it, ++chan) {
207 FileSpec copy = new_config;
208 copy.channel_config = *it;
209
210 copy.filename.reset (new ExportFilename (*copy.filename));
211 copy.filename->include_channel = true;
212 copy.filename->set_channel (chan);
213
214 add_split_config (copy);
215 }
216 }
217
218 void
get_analysis_results(AnalysisResults & results)219 ExportGraphBuilder::get_analysis_results (AnalysisResults& results) {
220 for (AnalysisMap::iterator i = analysis_map.begin(); i != analysis_map.end(); ++i) {
221 ExportAnalysisPtr p = i->second->result ();
222 if (p) {
223 results.insert (std::make_pair (i->first, p));
224 }
225 }
226 }
227
228 void
add_split_config(FileSpec const & config)229 ExportGraphBuilder::add_split_config (FileSpec const & config)
230 {
231 for (ChannelConfigList::iterator it = channel_configs.begin(); it != channel_configs.end(); ++it) {
232 if (*it == config) {
233 it->add_child (config);
234 return;
235 }
236 }
237
238 // No duplicate channel config found, create new one
239 channel_configs.push_back (new ChannelConfig (*this, config, channels));
240 }
241
242 /* Encoder */
243
244 template <>
245 boost::shared_ptr<AudioGrapher::Sink<Sample> >
init(FileSpec const & new_config)246 ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
247 {
248 config = new_config;
249 if (config.format->format_id() == ExportFormatBase::F_FFMPEG) {
250 init_writer (pipe_writer);
251 return pipe_writer;
252 } else {
253 init_writer (float_writer);
254 return float_writer;
255 }
256 }
257
258 template <>
259 boost::shared_ptr<AudioGrapher::Sink<int> >
init(FileSpec const & new_config)260 ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
261 {
262 config = new_config;
263 init_writer (int_writer);
264 return int_writer;
265 }
266
267 template <>
268 boost::shared_ptr<AudioGrapher::Sink<short> >
init(FileSpec const & new_config)269 ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
270 {
271 config = new_config;
272 init_writer (short_writer);
273 return short_writer;
274 }
275
276 void
add_child(FileSpec const & new_config)277 ExportGraphBuilder::Encoder::add_child (FileSpec const & new_config)
278 {
279 filenames.push_back (new_config.filename);
280 }
281
282 void
destroy_writer(bool delete_out_file)283 ExportGraphBuilder::Encoder::destroy_writer (bool delete_out_file)
284 {
285 if (delete_out_file ) {
286
287 if (float_writer) {
288 float_writer->close ();
289 }
290
291 if (int_writer) {
292 int_writer->close ();
293 }
294
295 if (short_writer) {
296 short_writer->close ();
297 }
298
299 if (pipe_writer) {
300 pipe_writer->close ();
301 }
302
303 if (std::remove(writer_filename.c_str() ) != 0) {
304 std::cout << "Encoder::destroy_writer () : Error removing file: " << strerror(errno) << std::endl;
305 }
306 }
307
308 float_writer.reset ();
309 int_writer.reset ();
310 short_writer.reset ();
311 pipe_writer.reset ();
312 }
313
314 bool
operator ==(FileSpec const & other_config) const315 ExportGraphBuilder::Encoder::operator== (FileSpec const & other_config) const
316 {
317 return get_real_format (config) == get_real_format (other_config);
318 }
319
320 int
get_real_format(FileSpec const & config)321 ExportGraphBuilder::Encoder::get_real_format (FileSpec const & config)
322 {
323 ExportFormatSpecification & format = *config.format;
324 return format.format_id() | format.sample_format() | format.endianness();
325 }
326
327 template<typename T>
328 void
init_writer(boost::shared_ptr<AudioGrapher::SndfileWriter<T>> & writer)329 ExportGraphBuilder::Encoder::init_writer (boost::shared_ptr<AudioGrapher::SndfileWriter<T> > & writer)
330 {
331 unsigned channels = config.channel_config->get_n_chans();
332 int format = get_real_format (config);
333 config.filename->set_channel_config(config.channel_config);
334 writer_filename = config.filename->get_path (config.format);
335
336 writer.reset (new AudioGrapher::SndfileWriter<T> (writer_filename, format, channels, config.format->sample_rate(), config.broadcast_info));
337 writer->FileWritten.connect_same_thread (copy_files_connection, boost::bind (&ExportGraphBuilder::Encoder::copy_files, this, _1));
338 if (format & ExportFormatBase::SF_Vorbis) {
339 /* libsndfile uses range 0..1 (worst.. best) for
340 * SFC_SET_VBR_ENCODING_QUALITY and maps
341 * SFC_SET_COMPRESSION_LEVEL = 1.0 - VBR_ENCODING_QUALITY
342 */
343 double vorbis_quality = config.format->codec_quality () / 100.f;
344 if (vorbis_quality >= 0 && vorbis_quality <= 1.0) {
345 writer->command (SFC_SET_VBR_ENCODING_QUALITY, &vorbis_quality, sizeof (double));
346 }
347 }
348 }
349
350 template<typename T>
351 void
init_writer(boost::shared_ptr<AudioGrapher::CmdPipeWriter<T>> & writer)352 ExportGraphBuilder::Encoder::init_writer (boost::shared_ptr<AudioGrapher::CmdPipeWriter<T> > & writer)
353 {
354 unsigned channels = config.channel_config->get_n_chans();
355 config.filename->set_channel_config(config.channel_config);
356 writer_filename = config.filename->get_path (config.format);
357
358 std::string ffmpeg_exe;
359 std::string unused;
360
361 if (!ArdourVideoToolPaths::transcoder_exe (ffmpeg_exe, unused)) {
362 throw ExportFailed ("External encoder (ffmpeg) is not available.");
363 }
364
365 int quality = config.format->codec_quality ();
366
367 int a=0;
368 char **argp = (char**) calloc (100, sizeof(char*));
369 char tmp[64];
370 argp[a++] = strdup(ffmpeg_exe.c_str());
371 argp[a++] = strdup ("-f");
372 argp[a++] = strdup ("f32le");
373 argp[a++] = strdup ("-acodec");
374 argp[a++] = strdup ("pcm_f32le");
375 argp[a++] = strdup ("-ac");
376 snprintf (tmp, sizeof(tmp), "%d", channels);
377 argp[a++] = strdup (tmp);
378 argp[a++] = strdup ("-ar");
379 snprintf (tmp, sizeof(tmp), "%d", config.format->sample_rate());
380 argp[a++] = strdup (tmp);
381 argp[a++] = strdup ("-i");
382 argp[a++] = strdup ("pipe:0");
383
384 argp[a++] = strdup ("-y");
385 if (quality <= 0) {
386 /* variable rate, lower is better */
387 snprintf (tmp, sizeof(tmp), "%d", -quality);
388 argp[a++] = strdup ("-q:a"); argp[a++] = strdup (tmp);
389 } else {
390 /* fixed bitrate, higher is better */
391 snprintf (tmp, sizeof(tmp), "%dk", quality); // eg. "192k"
392 argp[a++] = strdup ("-b:a"); argp[a++] = strdup (tmp);
393 }
394
395 SessionMetadata::MetaDataMap meta;
396 meta["comment"] = "Created with " PROGRAM_NAME;
397
398 if (config.format->tag()) {
399 ARDOUR::SessionMetadata* session_data = ARDOUR::SessionMetadata::Metadata();
400 session_data->av_export_tag (meta);
401 }
402
403 for(SessionMetadata::MetaDataMap::const_iterator it = meta.begin(); it != meta.end(); ++it) {
404 argp[a++] = strdup("-metadata");
405 argp[a++] = SystemExec::format_key_value_parameter (it->first.c_str(), it->second.c_str());
406 }
407
408 argp[a++] = strdup (writer_filename.c_str());
409 argp[a] = (char *)0;
410
411 /* argp is free()d in ~SystemExec,
412 * SystemExec is deleted when writer is destroyed */
413 ARDOUR::SystemExec* exec = new ARDOUR::SystemExec (ffmpeg_exe, argp);
414 PBD::info << "Encode command: { " << exec->to_s () << "}" << endmsg;
415 if (exec->start (SystemExec::MergeWithStdin)) {
416 throw ExportFailed ("External encoder (ffmpeg) cannot be started.");
417 }
418 writer.reset (new AudioGrapher::CmdPipeWriter<T> (exec, writer_filename));
419 writer->FileWritten.connect_same_thread (copy_files_connection, boost::bind (&ExportGraphBuilder::Encoder::copy_files, this, _1));
420 }
421
422 void
copy_files(std::string orig_path)423 ExportGraphBuilder::Encoder::copy_files (std::string orig_path)
424 {
425 while (filenames.size()) {
426 ExportFilenamePtr & filename = filenames.front();
427 PBD::copy_file (orig_path, filename->get_path (config.format).c_str());
428 filenames.pop_front();
429 }
430 }
431
432 /* SFC */
433
SFC(ExportGraphBuilder & parent,FileSpec const & new_config,samplecnt_t max_samples)434 ExportGraphBuilder::SFC::SFC (ExportGraphBuilder &parent, FileSpec const & new_config, samplecnt_t max_samples)
435 : data_width(0)
436 {
437 config = new_config;
438 data_width = sndfile_data_width (Encoder::get_real_format (config));
439 unsigned channels = new_config.channel_config->get_n_chans();
440 _analyse = config.format->analyse();
441
442 float ntarget = (config.format->normalize_loudness () || !config.format->normalize()) ? 0.0 : config.format->normalize_dbfs();
443 normalizer.reset (new AudioGrapher::Normalizer (ntarget, max_samples));
444 limiter.reset (new AudioGrapher::Limiter (config.format->sample_rate(), channels, max_samples));
445
446 normalizer->add_output (limiter);
447
448 boost::shared_ptr<AudioGrapher::ListedSource<float> > intermediate = limiter;
449
450 if (_analyse) {
451 samplecnt_t sample_rate = parent.session.nominal_sample_rate();
452 samplecnt_t sb = config.format->silence_beginning_at (parent.timespan->get_start(), sample_rate);
453 samplecnt_t se = config.format->silence_end_at (parent.timespan->get_end(), sample_rate);
454 samplecnt_t duration = parent.timespan->get_length () + sb + se;
455
456 max_samples = std::min ((samplecnt_t) 8192 * channels, std::max ((samplecnt_t) 4096 * channels, max_samples));
457 chunker.reset (new Chunker<Sample> (max_samples));
458 analyser.reset (new Analyser (config.format->sample_rate(), channels, max_samples,
459 (samplecnt_t) ceil (duration * config.format->sample_rate () / (double) sample_rate)));
460
461 config.filename->set_channel_config (config.channel_config);
462 parent.add_analyser (config.filename->get_path (config.format), analyser);
463 limiter->set_result (analyser->result (true));
464
465 chunker->add_output (analyser);
466 intermediate->add_output (chunker);
467 intermediate = analyser;
468 }
469
470 if (config.format->format_id() == ExportFormatBase::F_None) {
471 /* do not encode result, stop after chunker/analyzer */
472 assert (_analyse);
473 return;
474 }
475
476 if (config.format->demo_noise_duration () > 0 && config.format->demo_noise_interval () > 0) {
477 samplecnt_t sample_rate = parent.session.nominal_sample_rate();
478 demo_noise_adder.reset (new DemoNoiseAdder (channels));
479 demo_noise_adder->init (max_samples,
480 sample_rate * config.format->demo_noise_interval () / 1000,
481 sample_rate * config.format->demo_noise_duration () / 1000,
482 config.format->demo_noise_level ());
483
484 intermediate->add_output (demo_noise_adder);
485 intermediate = demo_noise_adder;
486 }
487
488 if (data_width == 8 || data_width == 16) {
489 short_converter = ShortConverterPtr (new SampleFormatConverter<short> (channels));
490 short_converter->init (max_samples, config.format->dither_type(), data_width);
491 add_child (config);
492 intermediate->add_output (short_converter);
493 } else if (data_width == 24 || data_width == 32) {
494 int_converter = IntConverterPtr (new SampleFormatConverter<int> (channels));
495 int_converter->init (max_samples, config.format->dither_type(), data_width);
496 add_child (config);
497 intermediate->add_output (int_converter);
498 } else {
499 int actual_data_width = 8 * sizeof(Sample);
500 float_converter = FloatConverterPtr (new SampleFormatConverter<Sample> (channels));
501 float_converter->init (max_samples, config.format->dither_type(), actual_data_width);
502 add_child (config);
503 intermediate->add_output (float_converter);
504 }
505 }
506
507 void
set_duration(samplecnt_t n_samples)508 ExportGraphBuilder::SFC::set_duration (samplecnt_t n_samples)
509 {
510 /* update after silence trim */
511 if (analyser) {
512 analyser->set_duration (n_samples);
513 }
514 if (limiter) {
515 limiter->set_duration (n_samples);
516 }
517 }
518
519 void
set_peak_dbfs(float peak,bool force)520 ExportGraphBuilder::SFC::set_peak_dbfs (float peak, bool force)
521 {
522 if (!config.format->normalize () && !force) {
523 return;
524 }
525 float gain = normalizer->set_peak (peak);
526 if (_analyse) {
527 analyser->set_normalization_gain (gain);
528 }
529 }
530
531 void
set_peak_lufs(AudioGrapher::LoudnessReader const & lr)532 ExportGraphBuilder::SFC::set_peak_lufs (AudioGrapher::LoudnessReader const& lr)
533 {
534 if (!config.format->normalize_loudness ()) {
535 return;
536 }
537 float LUFSi, LUFSs;
538 if (!config.format->use_tp_limiter ()) {
539 float peak = lr.calc_peak (config.format->normalize_lufs (), config.format->normalize_dbtp ());
540 set_peak_dbfs (peak, true);
541 } else if (lr.get_loudness (&LUFSi, &LUFSs) && (LUFSi > -180 || LUFSs > -180)) {
542 float lufs = LUFSi > -180 ? LUFSi : LUFSs;
543 float peak = powf (10.f, .05 * (lufs - config.format->normalize_lufs () - 0.05));
544 limiter->set_threshold (config.format->normalize_dbtp ());
545 set_peak_dbfs (peak, true);
546 }
547 }
548
549 ExportGraphBuilder::FloatSinkPtr
sink()550 ExportGraphBuilder::SFC::sink ()
551 {
552 return normalizer;
553 }
554
555 void
add_child(FileSpec const & new_config)556 ExportGraphBuilder::SFC::add_child (FileSpec const & new_config)
557 {
558 for (boost::ptr_list<Encoder>::iterator it = children.begin(); it != children.end(); ++it) {
559 if (*it == new_config) {
560 it->add_child (new_config);
561 return;
562 }
563 }
564
565 children.push_back (new Encoder());
566 Encoder & encoder = children.back();
567
568 if (data_width == 8 || data_width == 16) {
569 short_converter->add_output (encoder.init<short> (new_config));
570 } else if (data_width == 24 || data_width == 32) {
571 int_converter->add_output (encoder.init<int> (new_config));
572 } else {
573 float_converter->add_output (encoder.init<Sample> (new_config));
574 }
575 }
576
577 void
remove_children(bool remove_out_files)578 ExportGraphBuilder::SFC::remove_children (bool remove_out_files)
579 {
580 boost::ptr_list<Encoder>::iterator iter = children.begin ();
581
582 while (iter != children.end() ) {
583
584 if (remove_out_files) {
585 iter->destroy_writer(remove_out_files);
586 }
587 iter = children.erase (iter);
588 }
589 }
590
591 bool
operator ==(FileSpec const & other_config) const592 ExportGraphBuilder::SFC::operator== (FileSpec const& other_config) const
593 {
594 ExportFormatSpecification const& a = *config.format;
595 ExportFormatSpecification const& b = *other_config.format;
596
597 bool id = a.sample_format() == b.sample_format();
598
599 if (a.normalize_loudness () == b.normalize_loudness ()) {
600 id &= a.normalize_lufs () == b.normalize_lufs ();
601 id &= a.normalize_dbtp () == b.normalize_dbtp ();
602 } else {
603 return false;
604 }
605 if (a.normalize () == b.normalize ()) {
606 id &= a.normalize_dbfs () == b.normalize_dbfs ();
607 } else {
608 return false;
609 }
610
611 id &= a.demo_noise_duration () == b.demo_noise_duration ();
612 id &= a.demo_noise_interval () == b.demo_noise_interval ();
613
614 return id;
615 }
616
617 /* Intermediate (Normalizer, TmpFile) */
618
Intermediate(ExportGraphBuilder & parent,FileSpec const & new_config,samplecnt_t max_samples)619 ExportGraphBuilder::Intermediate::Intermediate (ExportGraphBuilder & parent, FileSpec const & new_config, samplecnt_t max_samples)
620 : parent (parent)
621 , use_loudness (false)
622 , use_peak (false)
623 {
624 std::string tmpfile_path = parent.session.session_directory().export_path();
625 tmpfile_path = Glib::build_filename(tmpfile_path, "XXXXXX");
626 std::vector<char> tmpfile_path_buf(tmpfile_path.size() + 1);
627 std::copy(tmpfile_path.begin(), tmpfile_path.end(), tmpfile_path_buf.begin());
628 tmpfile_path_buf[tmpfile_path.size()] = '\0';
629
630 config = new_config;
631 uint32_t const channels = config.channel_config->get_n_chans();
632 max_samples_out = 4086 - (4086 % channels); // TODO good chunk size
633
634 buffer.reset (new AllocatingProcessContext<Sample> (max_samples_out, channels));
635
636 peak_reader.reset (new PeakReader ());
637 loudness_reader.reset (new LoudnessReader (config.format->sample_rate(), channels, max_samples));
638 threader.reset (new Threader<Sample> (parent.thread_pool));
639
640 int format = ExportFormatBase::F_RAW | ExportFormatBase::SF_Float;
641
642 if (parent._realtime) {
643 tmp_file.reset (new TmpFileRt<float> (&tmpfile_path_buf[0], format, channels, config.format->sample_rate()));
644 } else {
645 tmp_file.reset (new TmpFileSync<float> (&tmpfile_path_buf[0], format, channels, config.format->sample_rate()));
646 }
647
648 tmp_file->FileWritten.connect_same_thread (post_processing_connection,
649 boost::bind (&Intermediate::prepare_post_processing, this));
650 tmp_file->FileFlushed.connect_same_thread (post_processing_connection,
651 boost::bind (&Intermediate::start_post_processing, this));
652
653 add_child (new_config);
654
655 peak_reader->add_output (loudness_reader);
656 loudness_reader->add_output (tmp_file);
657 }
658
659 ExportGraphBuilder::FloatSinkPtr
sink()660 ExportGraphBuilder::Intermediate::sink ()
661 {
662 if (use_peak) {
663 return peak_reader;
664 } else if (use_loudness) {
665 return loudness_reader;
666 } else {
667 return tmp_file;
668 }
669 }
670
671 void
add_child(FileSpec const & new_config)672 ExportGraphBuilder::Intermediate::add_child (FileSpec const & new_config)
673 {
674 use_peak |= new_config.format->normalize ();
675 use_loudness |= new_config.format->normalize_loudness ();
676
677 for (boost::ptr_list<SFC>::iterator it = children.begin(); it != children.end(); ++it) {
678 if (*it == new_config) {
679 it->add_child (new_config);
680 return;
681 }
682 }
683
684 children.push_back (new SFC (parent, new_config, max_samples_out));
685 threader->add_output (children.back().sink());
686 }
687
688 void
remove_children(bool remove_out_files)689 ExportGraphBuilder::Intermediate::remove_children (bool remove_out_files)
690 {
691 boost::ptr_list<SFC>::iterator iter = children.begin ();
692
693 while (iter != children.end() ) {
694 iter->remove_children (remove_out_files);
695 iter = children.erase (iter);
696 }
697 }
698
699 bool
operator ==(FileSpec const & other_config) const700 ExportGraphBuilder::Intermediate::operator== (FileSpec const & other_config) const
701 {
702 return true;
703 }
704
705 unsigned
get_postprocessing_cycle_count() const706 ExportGraphBuilder::Intermediate::get_postprocessing_cycle_count() const
707 {
708 return static_cast<unsigned>(std::ceil(static_cast<float>(tmp_file->get_samples_written()) /
709 max_samples_out));
710 }
711
712 bool
process()713 ExportGraphBuilder::Intermediate::process()
714 {
715 samplecnt_t samples_read = tmp_file->read (*buffer);
716 return samples_read != buffer->samples();
717 }
718
719 void
prepare_post_processing()720 ExportGraphBuilder::Intermediate::prepare_post_processing()
721 {
722 for (boost::ptr_list<SFC>::iterator i = children.begin(); i != children.end(); ++i) {
723 if (use_peak) {
724 (*i).set_peak_dbfs (peak_reader->get_peak());
725 }
726 if (use_loudness) {
727 (*i).set_peak_lufs (*loudness_reader);
728 }
729 }
730
731 tmp_file->add_output (threader);
732 parent.intermediates.push_back (this);
733 }
734
735 void
start_post_processing()736 ExportGraphBuilder::Intermediate::start_post_processing()
737 {
738 for (boost::ptr_list<SFC>::iterator i = children.begin(); i != children.end(); ++i) {
739 (*i).set_duration (tmp_file->get_samples_written() / config.channel_config->get_n_chans());
740 }
741
742 tmp_file->seek (0, SEEK_SET);
743
744 /* called in disk-thread when exporting in realtime,
745 * to enable freewheeling for post-proc.
746 *
747 * It may also be called to normalize from the
748 * freewheeling rt-callback, in which case this
749 * will be a no-op.
750 *
751 * RT Stem export has multiple TmpFileRt threads,
752 * prevent concurrent calls to enable freewheel ()
753 */
754 Glib::Threads::Mutex::Lock lm (parent.engine_request_lock);
755 if (!AudioEngine::instance()->freewheeling ()) {
756 AudioEngine::instance()->freewheel (true);
757 while (!AudioEngine::instance()->freewheeling ()) {
758 Glib::usleep (AudioEngine::instance()->usecs_per_cycle ());
759 }
760 }
761 }
762
763 /* SRC */
764
SRC(ExportGraphBuilder & parent,FileSpec const & new_config,samplecnt_t max_samples)765 ExportGraphBuilder::SRC::SRC (ExportGraphBuilder & parent, FileSpec const & new_config, samplecnt_t max_samples)
766 : parent (parent)
767 {
768 config = new_config;
769 converter.reset (new SampleRateConverter (new_config.channel_config->get_n_chans()));
770 ExportFormatSpecification & format = *new_config.format;
771 converter->init (parent.session.nominal_sample_rate(), format.sample_rate(), format.src_quality());
772 max_samples_out = converter->allocate_buffers (max_samples);
773
774 add_child (new_config);
775 }
776
777 ExportGraphBuilder::FloatSinkPtr
sink()778 ExportGraphBuilder::SRC::sink ()
779 {
780 return converter;
781 }
782
783 void
add_child(FileSpec const & new_config)784 ExportGraphBuilder::SRC::add_child (FileSpec const & new_config)
785 {
786 if (new_config.format->normalize() || parent._realtime) {
787 add_child_to_list (new_config, intermediate_children);
788 } else {
789 add_child_to_list (new_config, children);
790 }
791 }
792
793 void
remove_children(bool remove_out_files)794 ExportGraphBuilder::SRC::remove_children (bool remove_out_files)
795 {
796 boost::ptr_list<SFC>::iterator sfc_iter = children.begin();
797
798 while (sfc_iter != children.end() ) {
799 converter->remove_output (sfc_iter->sink() );
800 sfc_iter->remove_children (remove_out_files);
801 sfc_iter = children.erase (sfc_iter);
802 }
803
804 boost::ptr_list<Intermediate>::iterator norm_iter = intermediate_children.begin();
805
806 while (norm_iter != intermediate_children.end() ) {
807 converter->remove_output (norm_iter->sink() );
808 norm_iter->remove_children (remove_out_files);
809 norm_iter = intermediate_children.erase (norm_iter);
810 }
811
812 }
813
814 template<typename T>
815 void
add_child_to_list(FileSpec const & new_config,boost::ptr_list<T> & list)816 ExportGraphBuilder::SRC::add_child_to_list (FileSpec const & new_config, boost::ptr_list<T> & list)
817 {
818 for (typename boost::ptr_list<T>::iterator it = list.begin(); it != list.end(); ++it) {
819 if (*it == new_config) {
820 it->add_child (new_config);
821 return;
822 }
823 }
824
825 list.push_back (new T (parent, new_config, max_samples_out));
826 converter->add_output (list.back().sink ());
827 }
828
829 bool
operator ==(FileSpec const & other_config) const830 ExportGraphBuilder::SRC::operator== (FileSpec const & other_config) const
831 {
832 return config.format->sample_rate() == other_config.format->sample_rate();
833 }
834
835 /* SilenceHandler */
SilenceHandler(ExportGraphBuilder & parent,FileSpec const & new_config,samplecnt_t max_samples)836 ExportGraphBuilder::SilenceHandler::SilenceHandler (ExportGraphBuilder & parent, FileSpec const & new_config, samplecnt_t max_samples)
837 : parent (parent)
838 {
839 config = new_config;
840 max_samples_in = max_samples;
841 samplecnt_t sample_rate = parent.session.nominal_sample_rate();
842
843 /* work around partsing "-inf" config to "0" -- 7b1f97b
844 * silence trim 0dBFS makes no sense, anyway.
845 */
846 float est = Config->get_export_silence_threshold ();
847 if (est >= 0.f) est = -INFINITY;
848 #ifdef MIXBUS
849 // Mixbus channelstrip always dithers the signal, cut above dither level
850 silence_trimmer.reset (new SilenceTrimmer<Sample>(max_samples_in, std::max (-90.f, est)));
851 #else
852 // TODO silence-threshold should be per export-preset, with Config->get_silence_threshold being the default
853 silence_trimmer.reset (new SilenceTrimmer<Sample>(max_samples_in, est));
854 #endif
855 silence_trimmer->set_trim_beginning (config.format->trim_beginning());
856 silence_trimmer->set_trim_end (config.format->trim_end());
857
858 samplecnt_t sb = config.format->silence_beginning_at (parent.timespan->get_start(), sample_rate);
859 samplecnt_t se = config.format->silence_end_at (parent.timespan->get_end(), sample_rate);
860
861 silence_trimmer->add_silence_to_beginning (sb);
862 silence_trimmer->add_silence_to_end (se);
863
864 add_child (new_config);
865 }
866
867 ExportGraphBuilder::FloatSinkPtr
sink()868 ExportGraphBuilder::SilenceHandler::sink ()
869 {
870 return silence_trimmer;
871 }
872
873 void
add_child(FileSpec const & new_config)874 ExportGraphBuilder::SilenceHandler::add_child (FileSpec const & new_config)
875 {
876 for (boost::ptr_list<SRC>::iterator it = children.begin(); it != children.end(); ++it) {
877 if (*it == new_config) {
878 it->add_child (new_config);
879 return;
880 }
881 }
882
883 children.push_back (new SRC (parent, new_config, max_samples_in));
884 silence_trimmer->add_output (children.back().sink());
885 }
886
887 void
remove_children(bool remove_out_files)888 ExportGraphBuilder::SilenceHandler::remove_children (bool remove_out_files)
889 {
890 boost::ptr_list<SRC>::iterator iter = children.begin();
891
892 while (iter != children.end() ) {
893 silence_trimmer->remove_output (iter->sink() );
894 iter->remove_children (remove_out_files);
895 iter = children.erase (iter);
896 }
897 }
898
899 bool
operator ==(FileSpec const & other_config) const900 ExportGraphBuilder::SilenceHandler::operator== (FileSpec const & other_config) const
901 {
902 ExportFormatSpecification & format = *config.format;
903 ExportFormatSpecification & other_format = *other_config.format;
904 return (format.trim_beginning() == other_format.trim_beginning()) &&
905 (format.trim_end() == other_format.trim_end()) &&
906 (format.silence_beginning_time() == other_format.silence_beginning_time()) &&
907 (format.silence_end_time() == other_format.silence_end_time());
908 }
909
910 /* ChannelConfig */
911
ChannelConfig(ExportGraphBuilder & parent,FileSpec const & new_config,ChannelMap & channel_map)912 ExportGraphBuilder::ChannelConfig::ChannelConfig (ExportGraphBuilder & parent, FileSpec const & new_config, ChannelMap & channel_map)
913 : parent (parent)
914 {
915 typedef ExportChannelConfiguration::ChannelList ChannelList;
916
917 config = new_config;
918
919 samplecnt_t max_samples = parent.session.engine().samples_per_cycle();
920 interleaver.reset (new Interleaver<Sample> ());
921 interleaver->init (new_config.channel_config->get_n_chans(), max_samples);
922
923 // Make the chunk size divisible by the channel count
924 int chan_count = new_config.channel_config->get_n_chans();
925 max_samples_out = 8192;
926 if (chan_count > 0) {
927 max_samples_out -= max_samples_out % chan_count;
928 }
929 chunker.reset (new Chunker<Sample> (max_samples_out));
930 interleaver->add_output(chunker);
931
932 ChannelList const & channel_list = config.channel_config->get_channels();
933 unsigned chan = 0;
934 for (ChannelList::const_iterator it = channel_list.begin(); it != channel_list.end(); ++it, ++chan) {
935 ChannelMap::iterator map_it = channel_map.find (*it);
936 if (map_it == channel_map.end()) {
937 std::pair<ChannelMap::iterator, bool> result_pair =
938 channel_map.insert (std::make_pair (*it, IdentityVertexPtr (new IdentityVertex<Sample> ())));
939 assert (result_pair.second);
940 map_it = result_pair.first;
941 }
942 map_it->second->add_output (interleaver->input (chan));
943 }
944
945 add_child (new_config);
946 }
947
948 void
add_child(FileSpec const & new_config)949 ExportGraphBuilder::ChannelConfig::add_child (FileSpec const & new_config)
950 {
951 assert (*this == new_config);
952
953 for (boost::ptr_list<SilenceHandler>::iterator it = children.begin(); it != children.end(); ++it) {
954 if (*it == new_config) {
955 it->add_child (new_config);
956 return;
957 }
958 }
959
960 children.push_back (new SilenceHandler (parent, new_config, max_samples_out));
961 chunker->add_output (children.back().sink ());
962 }
963
964 void
remove_children(bool remove_out_files)965 ExportGraphBuilder::ChannelConfig::remove_children (bool remove_out_files)
966 {
967 boost::ptr_list<SilenceHandler>::iterator iter = children.begin();
968
969 while(iter != children.end() ) {
970
971 chunker->remove_output (iter->sink ());
972 iter->remove_children (remove_out_files);
973 iter = children.erase(iter);
974 }
975 }
976
977 bool
operator ==(FileSpec const & other_config) const978 ExportGraphBuilder::ChannelConfig::operator== (FileSpec const & other_config) const
979 {
980 return config.channel_config == other_config.channel_config;
981 }
982
983 } // namespace ARDOUR
984