Audacity 3.2.0
ExportFLAC.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5ExportFLAC.cpp
6
7Frederik M.J.V
8
9This program is distributed under the GNU General Public License, version 2.
10A copy of this license is included with this source.
11
12Based on ExportOGG.cpp by:
13Joshua Haberman
14
15**********************************************************************/
16
17#include <rapidjson/document.h>
18
19#include "Export.h"
20
21#include <wx/ffile.h>
22#include <wx/log.h>
23
24#include "FLAC++/encoder.h"
25
26#include "float_cast.h"
27#include "Mix.h"
28#include "Prefs.h"
29
30#include "Tags.h"
31#include "Track.h"
32
33#include "wxFileNameWrapper.h"
34
35#include "ExportPluginHelpers.h"
38
39//----------------------------------------------------------------------------
40// ExportFLACOptions Class
41//----------------------------------------------------------------------------
42
43namespace
44{
45enum : int {
48};
49
50const std::initializer_list<PlainExportOptionsEditor::OptionDesc> FlacOptions {
51 {
52 {
53 FlacOptionIDBitDepth, XO("Bit Depth"),
54 std::string("16"),
56 { std::string("16"), std::string("24") },
57 { XO("16 bit") , XO("24 bit") }
58 }, wxT("/FileFormats/FLACBitDepth")
59 },
60 {
61 {
62 FlacOptionIDLevel, XO("Level"),
63 std::string("5"),
65 {
66 std::string("0"),
67 std::string("1"),
68 std::string("2"),
69 std::string("3"),
70 std::string("4"),
71 std::string("5"),
72 std::string("6"),
73 std::string("7"),
74 std::string("8"),
75 },
76 {
77 XO("0 (fastest)") ,
78 XO("1") ,
79 XO("2") ,
80 XO("3") ,
81 XO("4") ,
82 XO("5") ,
83 XO("6") ,
84 XO("7") ,
85 XO("8 (best)") ,
86 }
87 }, wxT("/FileFormats/FLACLevel")
88 }
89};
90
91}
92
94 wxT("/FileFormats/FLACBitDepth"),
95 {
97 { XO("16 bit") , XO("24 bit") , },
98 { wxT("16") , wxT("24") , }
99 },
100 0 // "16",
101};
102
104 wxT("/FileFormats/FLACLevel"),
105 {
106 ByColumns,
107 {
108 XO("0 (fastest)") ,
109 XO("1") ,
110 XO("2") ,
111 XO("3") ,
112 XO("4") ,
113 XO("5") ,
114 XO("6") ,
115 XO("7") ,
116 XO("8 (best)") ,
117 },
118 {
119 wxT("0") ,
120 wxT("1") ,
121 wxT("2") ,
122 wxT("3") ,
123 wxT("4") ,
124 wxT("5") ,
125 wxT("6") ,
126 wxT("7") ,
127 wxT("8") ,
128 }
129 },
130 5 //"5"
131};
132
133#define SAMPLES_PER_RUN 8192u
134
135/* FLACPP_API_VERSION_CURRENT is 6 for libFLAC++ from flac-1.1.3 (see <FLAC++/export.h>) */
136#if !defined FLACPP_API_VERSION_CURRENT || FLACPP_API_VERSION_CURRENT < 6
137#define LEGACY_FLAC
138#else
139#undef LEGACY_FLAC
140#endif
141
142static struct
143{
153} flacLevels[] = {
154 { false, false, false, false, 0, 2, 2, 0, 0 },
155 { false, false, true, true, 0, 2, 2, 0, 0 },
156 { false, false, true, false, 0, 0, 3, 0, 0 },
157 { false, false, false, false, 0, 3, 3, 0, 6 },
158 { false, false, true, true, 0, 3, 3, 0, 8 },
159 { false, false, true, false, 0, 3, 3, 0, 8 },
160 { false, false, true, false, 0, 0, 4, 0, 8 },
161 { true, false, true, false, 0, 0, 6, 0, 8 },
162 { true, false, true, false, 0, 0, 6, 0, 12 },
164
165//----------------------------------------------------------------------------
166
168 void operator () (FLAC__StreamMetadata *p) const
169 { if (p) ::FLAC__metadata_object_delete(p); }
170};
171using FLAC__StreamMetadataHandle = std::unique_ptr<
172 FLAC__StreamMetadata, FLAC__StreamMetadataDeleter
173>;
174
176{
177 struct
178 {
180 double t0;
181 double t1;
182 unsigned numChannels;
185 FLAC::Encoder::File encoder;
186 wxFFile f;
187 std::unique_ptr<Mixer> mixer;
189
190public:
191
193 const Parameters& parameters,
194 const wxFileNameWrapper& filename,
195 double t0, double t1, bool selectedOnly,
196 double sampleFormat, unsigned channels,
197 MixerOptions::Downmix* mixerSpec,
198 const Tags* tags) override;
199
200 ExportResult Process(ExportProcessorDelegate& delegate) override;
201
202private:
203
205};
206
207class ExportFLAC final : public ExportPlugin
208{
209public:
210
212
213 int GetFormatCount() const override;
214 FormatInfo GetFormatInfo(int) const override;
215
216 bool ParseConfig(int, const rapidjson::Value& config, ExportProcessor::Parameters& parameters) const override;
217
218 std::vector<std::string> GetMimeTypes(int) const override;
219
220 // Required
221
222 std::unique_ptr<ExportOptionsEditor>
223 CreateOptionsEditor(int, ExportOptionsEditor::Listener* listener) const override;
224
225 std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
226};
227
228//----------------------------------------------------------------------------
229
230ExportFLAC::ExportFLAC() = default;
231
233{
234 return 1;
235}
236
238{
239 return {
240 wxT("FLAC"), XO("FLAC Files"), { wxT("flac") }, FLAC__MAX_CHANNELS, true
241 };
242}
243
244bool ExportFLAC::ParseConfig(int, const rapidjson::Value& config, ExportProcessor::Parameters& parameters) const
245{
246 if(!config.IsObject() ||
247 !config.HasMember("level") || !config["level"].IsNumber() ||
248 !config.HasMember("bit_depth") || !config["bit_depth"].IsNumber())
249 return false;
250
251 const auto level = ExportValue(std::to_string(config["level"].GetInt()));
252 const auto bitDepth = ExportValue(std::to_string(config["bit_depth"].GetInt()));
253
254 for(const auto& desc : FlacOptions)
255 {
256 const auto& option = desc.option;
257 if((option.id == FlacOptionIDLevel &&
258 std::find(option.values.begin(),
259 option.values.end(),
260 level) == option.values.end())
261 ||
262 (desc.option.id == FlacOptionIDBitDepth &&
263 std::find(option.values.begin(),
264 option.values.end(),
265 bitDepth) == option.values.end()))
266 return false;
267 }
269 { FlacOptionIDLevel, level },
270 { FlacOptionIDBitDepth, bitDepth }
271 };
272 std::swap(parameters, result);
273 return true;
274}
275
276std::vector<std::string> ExportFLAC::GetMimeTypes(int) const
277{
278 return { "audio/x-flac" };
279}
280
281std::unique_ptr<ExportOptionsEditor>
283{
284 return std::make_unique<PlainExportOptionsEditor>(FlacOptions, listener);
285}
286
287std::unique_ptr<ExportProcessor> ExportFLAC::CreateProcessor(int) const
288{
289 return std::make_unique<FLACExportProcessor>();
290}
291
292
294 const Parameters& parameters,
295 const wxFileNameWrapper& fName,
296 double t0, double t1, bool selectionOnly,
297 double sampleRate, unsigned numChannels,
298 MixerOptions::Downmix* mixerSpec,
299 const Tags* tags)
300{
301 context.t0 = t0;
302 context.t1 = t1;
303 context.numChannels = numChannels;
304 context.fName = fName;
305
306 const auto &tracks = TrackList::Get( project );
307
308 wxLogNull logNo; // temporarily disable wxWidgets error messages
309
310 long levelPref = std::stol(ExportPluginHelpers::GetParameterValue<std::string>(parameters, FlacOptionIDLevel));
311 auto bitDepthPref = ExportPluginHelpers::GetParameterValue<std::string>(parameters, FlacOptionIDBitDepth);
312
313
314 auto& encoder = context.encoder;
315
316 bool success = true;
317 success = success &&
318#ifdef LEGACY_FLAC
319 encoder.set_filename(OSOUTPUT(fName)) &&
320#endif
321 encoder.set_channels(numChannels) &&
322 encoder.set_sample_rate(lrint(sampleRate));
323
324 // See note in MakeMetadata() about a bug in libflac++ 1.1.2
326 if (success)
327 metadata = MakeMetadata(&project, tags);
328
329 if (success && !metadata) {
330 // TODO: more precise message
331 throw ExportErrorException("FLAC:283");
332 }
333
334 if (success && metadata) {
335 // set_metadata expects an array of pointers to metadata and a size.
336 // The size is 1.
337 FLAC__StreamMetadata *p = metadata.get();
338 success = encoder.set_metadata(&p, 1);
339 }
340
341
342 if (bitDepthPref == "24") {
343 context.format = int24Sample;
344 success = success && encoder.set_bits_per_sample(24);
345 } else { //convert float to 16 bits
346 context.format = int16Sample;
347 success = success && encoder.set_bits_per_sample(16);
348 }
349
350
351 // Duplicate the flac command line compression levels
352 if (levelPref < 0 || levelPref > 8) {
353 levelPref = 5;
354 }
355 success = success &&
356 encoder.set_do_exhaustive_model_search(flacLevels[levelPref].do_exhaustive_model_search) &&
357 encoder.set_do_escape_coding(flacLevels[levelPref].do_escape_coding);
358
359 if (numChannels != 2) {
360 success = success &&
361 encoder.set_do_mid_side_stereo(false) &&
362 encoder.set_loose_mid_side_stereo(false);
363 }
364 else {
365 success = success &&
366 encoder.set_do_mid_side_stereo(flacLevels[levelPref].do_mid_side_stereo) &&
367 encoder.set_loose_mid_side_stereo(flacLevels[levelPref].loose_mid_side_stereo);
368 }
369
370 success = success &&
371 encoder.set_qlp_coeff_precision(flacLevels[levelPref].qlp_coeff_precision) &&
372 encoder.set_min_residual_partition_order(flacLevels[levelPref].min_residual_partition_order) &&
373 encoder.set_max_residual_partition_order(flacLevels[levelPref].max_residual_partition_order) &&
374 encoder.set_rice_parameter_search_dist(flacLevels[levelPref].rice_parameter_search_dist) &&
375 encoder.set_max_lpc_order(flacLevels[levelPref].max_lpc_order);
376
377 if (!success) {
378 // TODO: more precise message
379 throw ExportErrorException("FLAC:336");
380 }
381
382#ifdef LEGACY_FLAC
383 encoder.init();
384#else
385 const auto path = fName.GetFullPath();
386 if (!context.f.Open(path, wxT("w+b"))) {
387 throw ExportException(XO("FLAC export couldn't open %s")
388 .Format( path )
389 .Translation());
390 }
391
392 // Even though there is an init() method that takes a filename, use the one that
393 // takes a file handle because wxWidgets can open a file with a Unicode name and
394 // libflac can't (under Windows).
395 int status = encoder.init(context.f.fp());
396 if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
397 throw ExportException(XO("FLAC encoder failed to initialize\nStatus: %d")
398 .Format( status )
399 .Translation());
400 }
401#endif
402
403 metadata.reset();
404
405 context.mixer = ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
406 t0, t1,
408 sampleRate, context.format, mixerSpec);
409
410 context.status = selectionOnly
411 ? XO("Exporting the selected audio as FLAC")
412 : XO("Exporting the audio as FLAC");
413
414 return true;
415}
416
418{
419 delegate.SetStatusString(context.status);
420
421 auto exportResult = ExportResult::Success;
422
423 auto cleanup2 = finally( [&] {
424 if (exportResult == ExportResult::Cancelled || exportResult == ExportResult::Error) {
425#ifndef LEGACY_FLAC
426 context.f.Detach(); // libflac closes the file
427#endif
428 context.encoder.finish();
429 }
430 } );
431
432 ArraysOf<FLAC__int32> tmpsmplbuf{ context.numChannels, SAMPLES_PER_RUN, true };
433
434 while (exportResult == ExportResult::Success) {
435 auto samplesThisRun = context.mixer->Process();
436 if (samplesThisRun == 0) //stop encoding
437 break;
438
439 for (size_t i = 0; i < context.numChannels; i++) {
440 auto mixed = context.mixer->GetBuffer(i);
441 if (context.format == int24Sample) {
442 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
443 tmpsmplbuf[i][j] = ((const int *)mixed)[j];
444 }
445 }
446 else {
447 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
448 tmpsmplbuf[i][j] = ((const short *)mixed)[j];
449 }
450 }
451 }
452 if (! context.encoder.process(
453 reinterpret_cast<FLAC__int32**>( tmpsmplbuf.get() ),
454 samplesThisRun) ) {
455 // TODO: more precise message
456 throw ExportDiskFullError(context.fName);
457 }
459 delegate, *context.mixer, context.t0, context.t1);
460 }
461
462 if (exportResult != ExportResult::Cancelled && exportResult != ExportResult::Error) {
463#ifndef LEGACY_FLAC
464 context.f.Detach(); // libflac closes the file
465#endif
466 if (!context.encoder.finish())
467 {
468 return ExportResult::Error;
469 }
470#ifdef LEGACY_FLAC
471 if (!context.f.Flush() || !context.f.Close())
472 {
473 return ExportResult::Error;
474 }
475#endif
476 }
477 return exportResult;
478}
479
480// LL: There's a bug in libflac++ 1.1.2 that prevents us from using
481// FLAC::Metadata::VorbisComment directly. The set_metadata()
482// function allocates an array on the stack, but the base library
483// expects that array to be valid until the stream is initialized.
484//
485// This has been fixed in 1.1.4.
487{
488 // Retrieve tags if needed
489 if (tags == NULL)
490 tags = &Tags::Get( *project );
491
492 auto metadata = FLAC__StreamMetadataHandle(
493 ::FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)
494 );
495
496 wxString n;
497 for (const auto &pair : tags->GetRange()) {
498 n = pair.first;
499 const auto &v = pair.second;
500 if (n == TAG_YEAR) {
501 n = wxT("DATE");
502 }
503 else if (n == TAG_COMMENTS) {
504 // Some apps like Foobar use COMMENT and some like Windows use DESCRIPTION,
505 // so add both to try and make everyone happy.
506 n = wxT("COMMENT");
507 FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8),
508 v.mb_str(wxConvUTF8));
509 if (! ::FLAC__metadata_object_vorbiscomment_append_comment(metadata.get(),
510 entry.get_entry(),
511 true) ) {
512 return {};
513 }
514 n = wxT("DESCRIPTION");
515 }
516 FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8),
517 v.mb_str(wxConvUTF8));
518 if (! ::FLAC__metadata_object_vorbiscomment_append_comment(metadata.get(),
519 entry.get_entry(),
520 true) ) {
521 return {};
522 }
523 }
524
525 return metadata;
526}
527
529 []{ return std::make_unique< ExportFLAC >(); }
530};
wxT("CloseDown"))
bool do_escape_coding
Definition: ExportFLAC.cpp:145
static struct @159 flacLevels[]
unsigned max_lpc_order
Definition: ExportFLAC.cpp:152
#define SAMPLES_PER_RUN
Definition: ExportFLAC.cpp:133
unsigned min_residual_partition_order
Definition: ExportFLAC.cpp:149
unsigned rice_parameter_search_dist
Definition: ExportFLAC.cpp:151
bool do_exhaustive_model_search
Definition: ExportFLAC.cpp:144
bool do_mid_side_stereo
Definition: ExportFLAC.cpp:146
unsigned max_residual_partition_order
Definition: ExportFLAC.cpp:150
static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin
Definition: ExportFLAC.cpp:528
bool loose_mid_side_stereo
Definition: ExportFLAC.cpp:147
ChoiceSetting FLACLevel
Definition: ExportFLAC.cpp:103
std::unique_ptr< FLAC__StreamMetadata, FLAC__StreamMetadataDeleter > FLAC__StreamMetadataHandle
Definition: ExportFLAC.cpp:173
unsigned qlp_coeff_precision
Definition: ExportFLAC.cpp:148
ChoiceSetting FLACBitDepth
Definition: ExportFLAC.cpp:93
std::variant< bool, int, double, std::string > ExportValue
A type of option values (parameters) used by exporting plugins.
Definition: ExportTypes.h:38
ExportResult
Definition: ExportTypes.h:24
XO("Cut/Copy/Paste")
static ProjectFileIORegistry::AttributeWriterEntry entry
ByColumns_t ByColumns
Definition: Prefs.cpp:515
sampleFormat
The ordering of these values with operator < agrees with the order of increasing bit width.
Definition: SampleFormat.h:30
#define OSOUTPUT(X)
Definition: SelectFile.h:48
#define TAG_COMMENTS
Definition: Tags.h:64
#define TAG_YEAR
Definition: Tags.h:62
const auto tracks
const auto project
declares abstract base class Track, TrackList, and iterators over TrackList
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
std::unique_ptr< ExportOptionsEditor > CreateOptionsEditor(int, ExportOptionsEditor::Listener *listener) const override
Creates format-dependent options editor, that is used to create a valid set of parameters to be used ...
Definition: ExportFLAC.cpp:282
bool ParseConfig(int, const rapidjson::Value &config, ExportProcessor::Parameters &parameters) const override
Attempt to parse configuration JSON object and produce a suitable set of parameters....
Definition: ExportFLAC.cpp:244
std::unique_ptr< ExportProcessor > CreateProcessor(int format) const override
Definition: ExportFLAC.cpp:287
int GetFormatCount() const override
Definition: ExportFLAC.cpp:232
FormatInfo GetFormatInfo(int) const override
Returns FormatInfo structure for given index if it's valid, or a default one. FormatInfo::format isn'...
Definition: ExportFLAC.cpp:237
std::vector< std::string > GetMimeTypes(int) const override
Definition: ExportFLAC.cpp:276
Listener object that is used to report on option changes.
static ExportResult UpdateProgress(ExportProcessorDelegate &delegate, Mixer &mixer, double t0, double t1)
Sends progress update to delegate and retrieves state update from it. Typically used inside each expo...
static std::unique_ptr< Mixer > CreateMixer(const TrackList &tracks, bool selectionOnly, double startTime, double stopTime, unsigned numOutChannels, size_t outBufferSize, bool outInterleaved, double outRate, sampleFormat outFormat, MixerOptions::Downmix *mixerSpec)
virtual void SetStatusString(const TranslatableString &str)=0
std::vector< std::tuple< ExportOptionID, ExportValue > > Parameters
Definition: ExportPlugin.h:93
wxFileNameWrapper fName
Definition: ExportFLAC.cpp:183
sampleFormat format
Definition: ExportFLAC.cpp:184
bool Initialize(AudacityProject &project, const Parameters &parameters, const wxFileNameWrapper &filename, double t0, double t1, bool selectedOnly, double sampleFormat, unsigned channels, MixerOptions::Downmix *mixerSpec, const Tags *tags) override
Called before start processing.
Definition: ExportFLAC.cpp:293
std::unique_ptr< Mixer > mixer
Definition: ExportFLAC.cpp:187
ExportResult Process(ExportProcessorDelegate &delegate) override
Definition: ExportFLAC.cpp:417
struct FLACExportProcessor::@161 context
TranslatableString status
Definition: ExportFLAC.cpp:179
FLAC__StreamMetadataHandle MakeMetadata(AudacityProject *project, const Tags *tags) const
Definition: ExportFLAC.cpp:486
FLAC::Encoder::File encoder
Definition: ExportFLAC.cpp:185
Abstract base class used in importing a file.
A matrix of booleans, one row per input channel, column per output.
Definition: MixerOptions.h:32
ID3 Tags (for MP3)
Definition: Tags.h:73
Iterators GetRange() const
Definition: Tags.cpp:426
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
Holds a msgid for the translation catalog; may also bind format arguments.
#define lrint(dbl)
Definition: float_cast.h:169
const std::initializer_list< PlainExportOptionsEditor::OptionDesc > FlacOptions
Definition: ExportFLAC.cpp:50
const TranslatableString desc
Definition: ExportPCM.cpp:51
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:628
@ TypeEnum
List/enum option. values holds items, and names text to be displayed.
Definition: ExportTypes.h:48
void operator()(FLAC__StreamMetadata *p) const
Definition: ExportFLAC.cpp:168