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
18
19#ifdef USE_LIBFLAC
20
21#include "Export.h"
22
23#include <wx/ffile.h>
24#include <wx/log.h>
25
26#include "FLAC++/encoder.h"
27
28#include "float_cast.h"
29#include "ProjectRate.h"
30#include "Mix.h"
31#include "Prefs.h"
32#include "ShuttleGui.h"
33
34#include "Tags.h"
35#include "Track.h"
36
37#include "AudacityMessageBox.h"
38#include "ProgressDialog.h"
39#include "wxFileNameWrapper.h"
40
41//----------------------------------------------------------------------------
42// ExportFLACOptions Class
43//----------------------------------------------------------------------------
44
46{
47public:
48
49 ExportFLACOptions(wxWindow *parent, int format);
50 virtual ~ExportFLACOptions();
51
53 bool TransferDataToWindow() override;
54 bool TransferDataFromWindow() override;
55};
56
59ExportFLACOptions::ExportFLACOptions(wxWindow *parent, int WXUNUSED(format))
60: wxPanelWrapper(parent, wxID_ANY)
61{
64
66}
67
71{
73}
74
76 wxT("/FileFormats/FLACBitDepth"),
77 {
79 { XO("16 bit") , XO("24 bit") , },
80 { wxT("16") , wxT("24") , }
81 },
82 0 // "16",
83};
84
86 wxT("/FileFormats/FLACLevel"),
87 {
89 {
90 XO("0 (fastest)") ,
91 XO("1") ,
92 XO("2") ,
93 XO("3") ,
94 XO("4") ,
95 XO("5") ,
96 XO("6") ,
97 XO("7") ,
98 XO("8 (best)") ,
99 },
100 {
101 wxT("0") ,
102 wxT("1") ,
103 wxT("2") ,
104 wxT("3") ,
105 wxT("4") ,
106 wxT("5") ,
107 wxT("6") ,
108 wxT("7") ,
109 wxT("8") ,
110 }
111 },
112 5 //"5"
113};
114
118{
119 S.StartVerticalLay();
120 {
121 S.StartHorizontalLay(wxCENTER);
122 {
123 S.StartMultiColumn(2, wxCENTER);
124 {
125 S.TieChoice( XXO("Level:"), FLACLevel);
126 S.TieChoice( XXO("Bit depth:"), FLACBitDepth);
127 }
128 S.EndMultiColumn();
129 }
130 S.EndHorizontalLay();
131 }
132 S.EndVerticalLay();
133
134 return;
135}
136
140{
141 return true;
142}
143
147{
150
151 gPrefs->Flush();
152
153 return true;
154}
155
156//----------------------------------------------------------------------------
157// ExportFLAC Class
158//----------------------------------------------------------------------------
159
160#define SAMPLES_PER_RUN 8192u
161
162/* FLACPP_API_VERSION_CURRENT is 6 for libFLAC++ from flac-1.1.3 (see <FLAC++/export.h>) */
163#if !defined FLACPP_API_VERSION_CURRENT || FLACPP_API_VERSION_CURRENT < 6
164#define LEGACY_FLAC
165#else
166#undef LEGACY_FLAC
167#endif
168
169static struct
170{
180} flacLevels[] = {
181 { false, false, false, false, 0, 2, 2, 0, 0 },
182 { false, false, true, true, 0, 2, 2, 0, 0 },
183 { false, false, true, false, 0, 0, 3, 0, 0 },
184 { false, false, false, false, 0, 3, 3, 0, 6 },
185 { false, false, true, true, 0, 3, 3, 0, 8 },
186 { false, false, true, false, 0, 3, 3, 0, 8 },
187 { false, false, true, false, 0, 0, 4, 0, 8 },
188 { true, false, true, false, 0, 0, 6, 0, 8 },
189 { true, false, true, false, 0, 0, 6, 0, 12 },
191
192//----------------------------------------------------------------------------
193
195 void operator () (FLAC__StreamMetadata *p) const
196 { if (p) ::FLAC__metadata_object_delete(p); }
197};
198using FLAC__StreamMetadataHandle = std::unique_ptr<
199 FLAC__StreamMetadata, FLAC__StreamMetadataDeleter
200>;
201
202class ExportFLAC final : public ExportPlugin
203{
204public:
205
206 ExportFLAC();
207
208 // Required
209
210 void OptionsCreate(ShuttleGui &S, int format) override;
212 std::unique_ptr<BasicUI::ProgressDialog> &pDialog,
213 unsigned channels,
214 const wxFileNameWrapper &fName,
215 bool selectedOnly,
216 double t0,
217 double t1,
218 MixerSpec *mixerSpec = NULL,
219 const Tags *metadata = NULL,
220 int subformat = 0) override;
221
222private:
223
224 bool GetMetadata(AudacityProject *project, const Tags *tags);
225
226 // Should this be a stack variable instead in Export?
228};
229
230//----------------------------------------------------------------------------
231
233: ExportPlugin()
234{
235 AddFormat();
236 SetFormat(wxT("FLAC"),0);
237 AddExtension(wxT("flac"),0);
238 SetMaxChannels(FLAC__MAX_CHANNELS,0);
239 SetCanMetaData(true,0);
240 SetDescription(XO("FLAC Files"),0);
241}
242
244 std::unique_ptr<BasicUI::ProgressDialog> &pDialog,
245 unsigned numChannels,
246 const wxFileNameWrapper &fName,
247 bool selectionOnly,
248 double t0,
249 double t1,
250 MixerSpec *mixerSpec,
251 const Tags *metadata,
252 int WXUNUSED(subformat))
253{
254 double rate = ProjectRate::Get(*project).GetRate();
255 const auto &tracks = TrackList::Get( *project );
256
257 wxLogNull logNo; // temporarily disable wxWidgets error messages
258 auto updateResult = ProgressResult::Success;
259
260 long levelPref;
261 FLACLevel.Read().ToLong( &levelPref );
262
263 auto bitDepthPref = FLACBitDepth.Read();
264
265 FLAC::Encoder::File encoder;
266
267 bool success = true;
268 success = success &&
269#ifdef LEGACY_FLAC
270 encoder.set_filename(OSOUTPUT(fName)) &&
271#endif
272 encoder.set_channels(numChannels) &&
273 encoder.set_sample_rate(lrint(rate));
274
275 // See note in GetMetadata() about a bug in libflac++ 1.1.2
276 if (success && !GetMetadata(project, metadata)) {
277 // TODO: more precise message
278 ShowExportErrorDialog("FLAC:283");
280 }
281
282 if (success && mMetadata) {
283 // set_metadata expects an array of pointers to metadata and a size.
284 // The size is 1.
285 FLAC__StreamMetadata *p = mMetadata.get();
286 success = encoder.set_metadata(&p, 1);
287 }
288
289 auto cleanup1 = finally( [&] {
290 mMetadata.reset(); // need this?
291 } );
292
294 if (bitDepthPref == wxT("24")) {
296 success = success && encoder.set_bits_per_sample(24);
297 } else { //convert float to 16 bits
299 success = success && encoder.set_bits_per_sample(16);
300 }
301
302
303 // Duplicate the flac command line compression levels
304 if (levelPref < 0 || levelPref > 8) {
305 levelPref = 5;
306 }
307 success = success &&
308 encoder.set_do_exhaustive_model_search(flacLevels[levelPref].do_exhaustive_model_search) &&
309 encoder.set_do_escape_coding(flacLevels[levelPref].do_escape_coding);
310
311 if (numChannels != 2) {
312 success = success &&
313 encoder.set_do_mid_side_stereo(false) &&
314 encoder.set_loose_mid_side_stereo(false);
315 }
316 else {
317 success = success &&
318 encoder.set_do_mid_side_stereo(flacLevels[levelPref].do_mid_side_stereo) &&
319 encoder.set_loose_mid_side_stereo(flacLevels[levelPref].loose_mid_side_stereo);
320 }
321
322 success = success &&
323 encoder.set_qlp_coeff_precision(flacLevels[levelPref].qlp_coeff_precision) &&
324 encoder.set_min_residual_partition_order(flacLevels[levelPref].min_residual_partition_order) &&
325 encoder.set_max_residual_partition_order(flacLevels[levelPref].max_residual_partition_order) &&
326 encoder.set_rice_parameter_search_dist(flacLevels[levelPref].rice_parameter_search_dist) &&
327 encoder.set_max_lpc_order(flacLevels[levelPref].max_lpc_order);
328
329 if (!success) {
330 // TODO: more precise message
331 ShowExportErrorDialog("FLAC:336");
333 }
334
335#ifdef LEGACY_FLAC
336 encoder.init();
337#else
338 wxFFile f; // will be closed when it goes out of scope
339 const auto path = fName.GetFullPath();
340 if (!f.Open(path, wxT("w+b"))) {
341 AudacityMessageBox( XO("FLAC export couldn't open %s").Format( path ) );
343 }
344
345 // Even though there is an init() method that takes a filename, use the one that
346 // takes a file handle because wxWidgets can open a file with a Unicode name and
347 // libflac can't (under Windows).
348 int status = encoder.init(f.fp());
349 if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
351 XO("FLAC encoder failed to initialize\nStatus: %d")
352 .Format( status ) );
354 }
355#endif
356
357 mMetadata.reset();
358
359 auto cleanup2 = finally( [&] {
360 if (!(updateResult == ProgressResult::Success ||
361 updateResult == ProgressResult::Stopped)) {
362#ifndef LEGACY_FLAC
363 f.Detach(); // libflac closes the file
364#endif
365 encoder.finish();
366 }
367 } );
368
369 auto mixer = CreateMixer(tracks, selectionOnly,
370 t0, t1,
371 numChannels, SAMPLES_PER_RUN, false,
372 rate, format, mixerSpec);
373
374 ArraysOf<FLAC__int32> tmpsmplbuf{ numChannels, SAMPLES_PER_RUN, true };
375
376 InitProgress( pDialog, fName,
377 selectionOnly
378 ? XO("Exporting the selected audio as FLAC")
379 : XO("Exporting the audio as FLAC") );
380 auto &progress = *pDialog;
381
382 while (updateResult == ProgressResult::Success) {
383 auto samplesThisRun = mixer->Process();
384 if (samplesThisRun == 0) //stop encoding
385 break;
386 else {
387 for (size_t i = 0; i < numChannels; i++) {
388 auto mixed = mixer->GetBuffer(i);
389 if (format == int24Sample) {
390 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
391 tmpsmplbuf[i][j] = ((const int *)mixed)[j];
392 }
393 }
394 else {
395 for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
396 tmpsmplbuf[i][j] = ((const short *)mixed)[j];
397 }
398 }
399 }
400 if (! encoder.process(
401 reinterpret_cast<FLAC__int32**>( tmpsmplbuf.get() ),
402 samplesThisRun) ) {
403 // TODO: more precise message
405 updateResult = ProgressResult::Cancelled;
406 break;
407 }
408 if (updateResult == ProgressResult::Success)
409 updateResult =
410 progress.Poll(mixer->MixGetCurrentTime() - t0, t1 - t0);
411 }
412 }
413
414 if (updateResult == ProgressResult::Success ||
415 updateResult == ProgressResult::Stopped) {
416#ifndef LEGACY_FLAC
417 f.Detach(); // libflac closes the file
418#endif
419 if (!encoder.finish())
420 // Do not reassign updateResult, see cleanup2
421 return ProgressResult::Failed;
422#ifdef LEGACY_FLAC
423 if (!f.Flush() || !f.Close())
424 return ProgressResult::Failed;
425#endif
426 }
427
428 return updateResult;
429}
430
432{
433 S.AddWindow( safenew ExportFLACOptions{ S.GetParent(), format } );
434}
435
436// LL: There's a bug in libflac++ 1.1.2 that prevents us from using
437// FLAC::Metadata::VorbisComment directly. The set_metadata()
438// function allocates an array on the stack, but the base library
439// expects that array to be valid until the stream is initialized.
440//
441// This has been fixed in 1.1.4.
443{
444 // Retrieve tags if needed
445 if (tags == NULL)
446 tags = &Tags::Get( *project );
447
448 mMetadata.reset(::FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT));
449
450 wxString n;
451 for (const auto &pair : tags->GetRange()) {
452 n = pair.first;
453 const auto &v = pair.second;
454 if (n == TAG_YEAR) {
455 n = wxT("DATE");
456 }
457 else if (n == TAG_COMMENTS) {
458 // Some apps like Foobar use COMMENT and some like Windows use DESCRIPTION,
459 // so add both to try and make everyone happy.
460 n = wxT("COMMENT");
461 FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8),
462 v.mb_str(wxConvUTF8));
463 if (! ::FLAC__metadata_object_vorbiscomment_append_comment(mMetadata.get(),
464 entry.get_entry(),
465 true) ) {
466 return false;
467 }
468 n = wxT("DESCRIPTION");
469 }
470 FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8),
471 v.mb_str(wxConvUTF8));
472 if (! ::FLAC__metadata_object_vorbiscomment_append_comment(mMetadata.get(),
473 entry.get_entry(),
474 true) ) {
475 return false;
476 }
477 }
478
479 return true;
480}
481
483 []{ return std::make_unique< ExportFLAC >(); }
484};
485
486#ifdef HAS_CLOUD_UPLOAD
487#include "CloudExporterPlugin.h"
489
490class FlacCloudHelper : public cloud::CloudExporterPlugin
491{
492public:
493 wxString GetExporterID() const override
494 {
495 return "FLAC";
496 }
497
498 FileExtension GetFileExtension() const override
499 {
500 return "flac";
501 }
502
503 void OnBeforeExport() override
504 {
505 FLACBitDepth.Write("24");
506 FLACLevel.Write("5");
507 }
508
509}; // WavPackCloudHelper
510
511static bool cloudExporterRegisterd = cloud::RegisterCloudExporter(
512 "audio/x-flac",
513 [](const AudacityProject&) { return std::make_unique<FlacCloudHelper>(); });
514#endif
515
516#endif // USE_LIBFLAC
517
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
void ShowExportErrorDialog(wxString ErrorCode, TranslatableString message, const TranslatableString &caption, bool allowReporting)
Definition: Export.cpp:1503
void ShowDiskFullExportErrorDialog(const wxFileNameWrapper &fileName)
Definition: Export.cpp:1516
bool do_escape_coding
Definition: ExportFLAC.cpp:172
unsigned max_lpc_order
Definition: ExportFLAC.cpp:179
#define SAMPLES_PER_RUN
Definition: ExportFLAC.cpp:160
unsigned min_residual_partition_order
Definition: ExportFLAC.cpp:176
unsigned rice_parameter_search_dist
Definition: ExportFLAC.cpp:178
bool do_exhaustive_model_search
Definition: ExportFLAC.cpp:171
static struct @40 flacLevels[]
bool do_mid_side_stereo
Definition: ExportFLAC.cpp:173
static Exporter::RegisteredExportPlugin sRegisteredPlugin
Definition: ExportFLAC.cpp:482
unsigned max_residual_partition_order
Definition: ExportFLAC.cpp:177
bool loose_mid_side_stereo
Definition: ExportFLAC.cpp:174
ChoiceSetting FLACLevel
Definition: ExportFLAC.cpp:85
std::unique_ptr< FLAC__StreamMetadata, FLAC__StreamMetadataDeleter > FLAC__StreamMetadataHandle
Definition: ExportFLAC.cpp:200
unsigned qlp_coeff_precision
Definition: ExportFLAC.cpp:175
ChoiceSetting FLACBitDepth
Definition: ExportFLAC.cpp:75
int format
Definition: ExportPCM.cpp:53
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
wxString FileExtension
File extension, not including any leading dot.
Definition: Identifier.h:224
#define safenew
Definition: MemoryX.h:10
FileConfig * gPrefs
Definition: Prefs.cpp:70
ByColumns_t ByColumns
Definition: Prefs.cpp:474
an object holding per-project preferred sample rate
static ProjectFileIORegistry::AttributeWriterEntry entry
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
@ eIsCreatingFromPrefs
Definition: ShuttleGui.h:46
@ eIsSavingToPrefs
Definition: ShuttleGui.h:47
#define TAG_COMMENTS
Definition: Tags.h:64
#define TAG_YEAR
Definition: Tags.h:62
#define S(N)
Definition: ToChars.cpp:64
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
bool Write(const wxString &value)
Definition: Prefs.cpp:390
wxString Read() const
Definition: Prefs.cpp:354
ProgressResult Export(AudacityProject *project, std::unique_ptr< BasicUI::ProgressDialog > &pDialog, unsigned channels, const wxFileNameWrapper &fName, bool selectedOnly, double t0, double t1, MixerSpec *mixerSpec=NULL, const Tags *metadata=NULL, int subformat=0) override
called to export audio into a file.
Definition: ExportFLAC.cpp:243
FLAC__StreamMetadataHandle mMetadata
Definition: ExportFLAC.cpp:227
void OptionsCreate(ShuttleGui &S, int format) override
Definition: ExportFLAC.cpp:431
bool GetMetadata(AudacityProject *project, const Tags *tags)
Definition: ExportFLAC.cpp:442
void PopulateOrExchange(ShuttleGui &S)
Definition: ExportFLAC.cpp:117
bool TransferDataFromWindow() override
Definition: ExportFLAC.cpp:146
ExportFLACOptions(wxWindow *parent, int format)
Definition: ExportFLAC.cpp:59
bool TransferDataToWindow() override
Definition: ExportFLAC.cpp:139
virtual ~ExportFLACOptions()
Definition: ExportFLAC.cpp:70
void AddExtension(const FileExtension &extension, int index)
Definition: Export.cpp:126
int AddFormat()
Add a NEW entry to the list of formats this plug-in can export.
Definition: Export.cpp:100
static void InitProgress(std::unique_ptr< BasicUI::ProgressDialog > &pDialog, const TranslatableString &title, const TranslatableString &message)
Definition: Export.cpp:250
void SetFormat(const wxString &format, int index)
Definition: Export.cpp:116
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, MixerSpec *mixerSpec)
Definition: Export.cpp:222
void SetDescription(const TranslatableString &description, int index)
Definition: Export.cpp:121
void SetCanMetaData(bool canmetadata, int index)
Definition: Export.cpp:146
void SetMaxChannels(unsigned maxchannels, unsigned index)
Definition: Export.cpp:141
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:143
Abstract base class used in importing a file.
A matrix of booleans, one row per input channel, column per output.
Definition: MixerOptions.h:32
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
double GetRate() const
Definition: ProjectRate.cpp:53
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:625
ID3 Tags (for MP3)
Definition: Tags.h:73
Iterators GetRange() const
Definition: Tags.cpp:436
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:487
Helper interface, that allows to setup the desired export format on the ExportPlugin.
virtual wxString GetExporterID() const =0
Identifier of the ExportPlugin to be used.
virtual FileExtension GetFileExtension() const =0
File extension that is expected with this plugin.
virtual void OnBeforeExport()=0
Setup the preferred format for the export.
#define lrint(dbl)
Definition: float_cast.h:169
ProgressResult
Definition: BasicUI.h:147
bool RegisterCloudExporter(MimeType mimeType, CloudExporterPluginFactory factory)
Registers a factory for a specific mime type.
void operator()(FLAC__StreamMetadata *p) const
Definition: ExportFLAC.cpp:195