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 wxLogNull logNo; // temporarily disable wxWidgets error messages
307
308 long levelPref = std::stol(ExportPluginHelpers::GetParameterValue<std::string>(parameters, FlacOptionIDLevel));
309 auto bitDepthPref = ExportPluginHelpers::GetParameterValue<std::string>(parameters, FlacOptionIDBitDepth);
310
311
312 auto& encoder = context.encoder;
313
314 bool success = true;
315 success = success &&
316#ifdef LEGACY_FLAC
317 encoder.set_filename(OSOUTPUT(fName)) &&
318#endif
319 encoder.set_channels(numChannels) &&
320 encoder.set_sample_rate(lrint(sampleRate));
321
322 // See note in MakeMetadata() about a bug in libflac++ 1.1.2
324 if (success)
325 metadata = MakeMetadata(&project, tags);
326
327 if (success && !metadata) {
328 // TODO: more precise message
329 throw ExportErrorException("FLAC:283");
330 }
331
332 if (success && metadata) {
333 // set_metadata expects an array of pointers to metadata and a size.
334 // The size is 1.
335 FLAC__StreamMetadata *p = metadata.get();
336 success = encoder.set_metadata(&p, 1);
337 }
338
339
340 if (bitDepthPref == "24") {
341 context.format = int24Sample;
342 success = success && encoder.set_bits_per_sample(24);
343 } else { //convert float to 16 bits
344 context.format = int16Sample;
345 success = success && encoder.set_bits_per_sample(16);
346 }
347
348
349 // Duplicate the flac command line compression levels
350 if (levelPref < 0 || levelPref > 8) {
351 levelPref = 5;
352 }
353 success = success &&
354 encoder.set_do_exhaustive_model_search(flacLevels[levelPref].do_exhaustive_model_search) &&
355 encoder.set_do_escape_coding(flacLevels[levelPref].do_escape_coding);
356
357 if (numChannels != 2) {
358 success = success &&
359 encoder.set_do_mid_side_stereo(false) &&
360 encoder.set_loose_mid_side_stereo(false);
361 }
362 else {
363 success = success &&
364 encoder.set_do_mid_side_stereo(flacLevels[levelPref].do_mid_side_stereo) &&
365 encoder.set_loose_mid_side_stereo(flacLevels[levelPref].loose_mid_side_stereo);
366 }
367
368 success = success &&
369 encoder.set_qlp_coeff_precision(flacLevels[levelPref].qlp_coeff_precision) &&
370 encoder.set_min_residual_partition_order(flacLevels[levelPref].min_residual_partition_order) &&
371 encoder.set_max_residual_partition_order(flacLevels[levelPref].max_residual_partition_order) &&
372 encoder.set_rice_parameter_search_dist(flacLevels[levelPref].rice_parameter_search_dist) &&
373 encoder.set_max_lpc_order(flacLevels[levelPref].max_lpc_order);
374
375 if (!success) {
376 // TODO: more precise message
377 throw ExportErrorException("FLAC:336");
378 }
379
380#ifdef LEGACY_FLAC
381 encoder.init();
382#else
383 const auto path = fName.GetFullPath();
384 if (!context.f.Open(path, wxT("w+b"))) {
385 throw ExportException(XO("FLAC export couldn't open %s")
386 .Format( path )
387 .Translation());
388 }
389
390 // Even though there is an init() method that takes a filename, use the one that
391 // takes a file handle because wxWidgets can open a file with a Unicode name and
392 // libflac can't (under Windows).
393 int status = encoder.init(context.f.fp());
394 if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
395 throw ExportException(XO("FLAC encoder failed to initialize\nStatus: %d")
396 .Format( status )
397 .Translation());
398 }
399#endif
400
401 metadata.reset();
402
404 project, selectionOnly, t0, t1, numChannels, SAMPLES_PER_RUN, false,
405 sampleRate, context.format, mixerSpec);
406
407 context.status = selectionOnly
408 ? XO("Exporting the selected audio as FLAC")
409 : XO("Exporting the audio as FLAC");
410
411 return true;
412}
413
415{
416 delegate.SetStatusString(context.status);
417
418 auto exportResult = ExportResult::Success;
419
420 auto cleanup2 = finally( [&] {
421 if (exportResult == ExportResult::Cancelled || exportResult == ExportResult::Error) {
422#ifndef LEGACY_FLAC
423 context.f.Detach(); // libflac closes the file
424#endif
425 context.encoder.finish();
426 }
427 } );
428
429 ArraysOf<FLAC__int32> tmpsmplbuf{ context.numChannels, SAMPLES_PER_RUN, true };
430
431 while (exportResult == ExportResult::Success) {
432 auto samplesThisRun = context.mixer->Process();
433 if (samplesThisRun == 0) //stop encoding
434 break;
435
436 for (size_t i = 0; i < context.numChannels; i++) {
437 auto mixed = context.mixer->GetBuffer(i);
438 if (context.format == int24Sample) {
439 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
440 tmpsmplbuf[i][j] = ((const int *)mixed)[j];
441 }
442 }
443 else {
444 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
445 tmpsmplbuf[i][j] = ((const short *)mixed)[j];
446 }
447 }
448 }
449 if (! context.encoder.process(
450 reinterpret_cast<FLAC__int32**>( tmpsmplbuf.get() ),
451 samplesThisRun) ) {
452 // TODO: more precise message
453 throw ExportDiskFullError(context.fName);
454 }
456 delegate, *context.mixer, context.t0, context.t1);
457 }
458
459 if (exportResult != ExportResult::Cancelled && exportResult != ExportResult::Error) {
460#ifndef LEGACY_FLAC
461 context.f.Detach(); // libflac closes the file
462#endif
463 if (!context.encoder.finish())
464 {
465 return ExportResult::Error;
466 }
467#ifdef LEGACY_FLAC
468 if (!context.f.Flush() || !context.f.Close())
469 {
470 return ExportResult::Error;
471 }
472#endif
473 }
474 return exportResult;
475}
476
477// LL: There's a bug in libflac++ 1.1.2 that prevents us from using
478// FLAC::Metadata::VorbisComment directly. The set_metadata()
479// function allocates an array on the stack, but the base library
480// expects that array to be valid until the stream is initialized.
481//
482// This has been fixed in 1.1.4.
484{
485 // Retrieve tags if needed
486 if (tags == NULL)
487 tags = &Tags::Get( *project );
488
489 auto metadata = FLAC__StreamMetadataHandle(
490 ::FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)
491 );
492
493 wxString n;
494 for (const auto &pair : tags->GetRange()) {
495 n = pair.first;
496 const auto &v = pair.second;
497 if (n == TAG_YEAR) {
498 n = wxT("DATE");
499 }
500 else if (n == TAG_COMMENTS) {
501 // Some apps like Foobar use COMMENT and some like Windows use DESCRIPTION,
502 // so add both to try and make everyone happy.
503 n = wxT("COMMENT");
504 FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8),
505 v.mb_str(wxConvUTF8));
506 if (! ::FLAC__metadata_object_vorbiscomment_append_comment(metadata.get(),
507 entry.get_entry(),
508 true) ) {
509 return {};
510 }
511 n = wxT("DESCRIPTION");
512 }
513 FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8),
514 v.mb_str(wxConvUTF8));
515 if (! ::FLAC__metadata_object_vorbiscomment_append_comment(metadata.get(),
516 entry.get_entry(),
517 true) ) {
518 return {};
519 }
520 }
521
522 return metadata;
523}
524
526 []{ return std::make_unique< ExportFLAC >(); }
527};
wxT("CloseDown"))
bool do_escape_coding
Definition: ExportFLAC.cpp:145
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
static struct @169 flacLevels[]
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:525
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 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 AudacityProject &project, 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:414
TranslatableString status
Definition: ExportFLAC.cpp:179
FLAC__StreamMetadataHandle MakeMetadata(AudacityProject *project, const Tags *tags) const
Definition: ExportFLAC.cpp:483
FLAC::Encoder::File encoder
Definition: ExportFLAC.cpp:185
struct FLACExportProcessor::@171 context
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
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:634
@ 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