Audacity  3.0.3
ExportFLAC.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 ExportFLAC.cpp
6 
7 Frederik M.J.V
8 
9 This program is distributed under the GNU General Public License, version 2.
10 A copy of this license is included with this source.
11 
12 Based on ExportOGG.cpp by:
13 Joshua Haberman
14 
15 Portions from vorbis-tools, copyright 2000-2002 Michael Smith
16 <[email protected]>; Vorbize, Kenneth Arnold <[email protected]>;
17 and libvorbis examples, Monty <[email protected]>
18 
19 **********************************************************************/
20 
21 
22 
23 #ifdef USE_LIBFLAC
24 
25 #include "Export.h"
26 
27 #include <wx/ffile.h>
28 #include <wx/log.h>
29 
30 #include "FLAC++/encoder.h"
31 
32 #include "../float_cast.h"
33 #include "../ProjectSettings.h"
34 #include "../Mix.h"
35 #include "../Prefs.h"
36 #include "../ShuttleGui.h"
37 
38 #include "../Tags.h"
39 #include "../Track.h"
40 
41 #include "../widgets/AudacityMessageBox.h"
42 #include "../widgets/ProgressDialog.h"
43 #include "../wxFileNameWrapper.h"
44 
45 //----------------------------------------------------------------------------
46 // ExportFLACOptions Class
47 //----------------------------------------------------------------------------
48 
49 class ExportFLACOptions final : public wxPanelWrapper
50 {
51 public:
52 
53  ExportFLACOptions(wxWindow *parent, int format);
54  virtual ~ExportFLACOptions();
55 
57  bool TransferDataToWindow() override;
58  bool TransferDataFromWindow() override;
59 };
60 
63 ExportFLACOptions::ExportFLACOptions(wxWindow *parent, int WXUNUSED(format))
64 : wxPanelWrapper(parent, wxID_ANY)
65 {
68 
70 }
71 
75 {
77 }
78 
80  wxT("/FileFormats/FLACBitDepth"),
81  {
82  ByColumns,
83  { XO("16 bit") , XO("24 bit") , },
84  { wxT("16") , wxT("24") , }
85  },
86  0 // "16",
87 };
88 
90  wxT("/FileFormats/FLACLevel"),
91  {
92  ByColumns,
93  {
94  XO("0 (fastest)") ,
95  XO("1") ,
96  XO("2") ,
97  XO("3") ,
98  XO("4") ,
99  XO("5") ,
100  XO("6") ,
101  XO("7") ,
102  XO("8 (best)") ,
103  },
104  {
105  wxT("0") ,
106  wxT("1") ,
107  wxT("2") ,
108  wxT("3") ,
109  wxT("4") ,
110  wxT("5") ,
111  wxT("6") ,
112  wxT("7") ,
113  wxT("8") ,
114  }
115  },
116  5 //"5"
117 };
118 
122 {
123  S.StartVerticalLay();
124  {
125  S.StartHorizontalLay(wxCENTER);
126  {
127  S.StartMultiColumn(2, wxCENTER);
128  {
129  S.TieChoice( XXO("Level:"), FLACLevel);
130  S.TieChoice( XXO("Bit depth:"), FLACBitDepth);
131  }
132  S.EndMultiColumn();
133  }
134  S.EndHorizontalLay();
135  }
136  S.EndVerticalLay();
137 
138  return;
139 }
140 
144 {
145  return true;
146 }
147 
151 {
152  ShuttleGui S(this, eIsSavingToPrefs);
154 
155  gPrefs->Flush();
156 
157  return true;
158 }
159 
160 //----------------------------------------------------------------------------
161 // ExportFLAC Class
162 //----------------------------------------------------------------------------
163 
164 #define SAMPLES_PER_RUN 8192u
165 
166 /* FLACPP_API_VERSION_CURRENT is 6 for libFLAC++ from flac-1.1.3 (see <FLAC++/export.h>) */
167 #if !defined FLACPP_API_VERSION_CURRENT || FLACPP_API_VERSION_CURRENT < 6
168 #define LEGACY_FLAC
169 #else
170 #undef LEGACY_FLAC
171 #endif
172 
173 static struct
174 {
183  unsigned max_lpc_order;
184 } flacLevels[] = {
185  { false, false, false, false, 0, 2, 2, 0, 0 },
186  { false, false, true, true, 0, 2, 2, 0, 0 },
187  { false, false, true, false, 0, 0, 3, 0, 0 },
188  { false, false, false, false, 0, 3, 3, 0, 6 },
189  { false, false, true, true, 0, 3, 3, 0, 8 },
190  { false, false, true, false, 0, 3, 3, 0, 8 },
191  { false, false, true, false, 0, 0, 4, 0, 8 },
192  { true, false, true, false, 0, 0, 6, 0, 8 },
193  { true, false, true, false, 0, 0, 6, 0, 12 },
194 };
195 
196 //----------------------------------------------------------------------------
197 
199  void operator () (FLAC__StreamMetadata *p) const
200  { if (p) ::FLAC__metadata_object_delete(p); }
201 };
202 using FLAC__StreamMetadataHandle = std::unique_ptr<
203  FLAC__StreamMetadata, FLAC__StreamMetadataDeleter
204 >;
205 
206 class ExportFLAC final : public ExportPlugin
207 {
208 public:
209 
210  ExportFLAC();
211 
212  // Required
213 
214  void OptionsCreate(ShuttleGui &S, int format) override;
216  std::unique_ptr<ProgressDialog> &pDialog,
217  unsigned channels,
218  const wxFileNameWrapper &fName,
219  bool selectedOnly,
220  double t0,
221  double t1,
222  MixerSpec *mixerSpec = NULL,
223  const Tags *metadata = NULL,
224  int subformat = 0) override;
225 
226 private:
227 
228  bool GetMetadata(AudacityProject *project, const Tags *tags);
229 
230  // Should this be a stack variable instead in Export?
232 };
233 
234 //----------------------------------------------------------------------------
235 
237 : ExportPlugin()
238 {
239  AddFormat();
240  SetFormat(wxT("FLAC"),0);
241  AddExtension(wxT("flac"),0);
242  SetMaxChannels(FLAC__MAX_CHANNELS,0);
243  SetCanMetaData(true,0);
244  SetDescription(XO("FLAC Files"),0);
245 }
246 
248  std::unique_ptr<ProgressDialog> &pDialog,
249  unsigned numChannels,
250  const wxFileNameWrapper &fName,
251  bool selectionOnly,
252  double t0,
253  double t1,
254  MixerSpec *mixerSpec,
255  const Tags *metadata,
256  int WXUNUSED(subformat))
257 {
258  const auto &settings = ProjectSettings::Get( *project );
259  double rate = settings.GetRate();
260  const auto &tracks = TrackList::Get( *project );
261 
262  wxLogNull logNo; // temporarily disable wxWidgets error messages
263  auto updateResult = ProgressResult::Success;
264 
265  long levelPref;
266  FLACLevel.Read().ToLong( &levelPref );
267 
268  auto bitDepthPref = FLACBitDepth.Read();
269 
270  FLAC::Encoder::File encoder;
271 
272  bool success = true;
273  success = success &&
274 #ifdef LEGACY_FLAC
275  encoder.set_filename(OSOUTPUT(fName)) &&
276 #endif
277  encoder.set_channels(numChannels) &&
278  encoder.set_sample_rate(lrint(rate));
279 
280  // See note in GetMetadata() about a bug in libflac++ 1.1.2
281  if (success && !GetMetadata(project, metadata)) {
282  // TODO: more precise message
283  ShowExportErrorDialog("FLAC:283");
285  }
286 
287  if (success && mMetadata) {
288  // set_metadata expects an array of pointers to metadata and a size.
289  // The size is 1.
290  FLAC__StreamMetadata *p = mMetadata.get();
291  success = encoder.set_metadata(&p, 1);
292  }
293 
294  auto cleanup1 = finally( [&] {
295  mMetadata.reset(); // need this?
296  } );
297 
299  if (bitDepthPref == wxT("24")) {
301  success = success && encoder.set_bits_per_sample(24);
302  } else { //convert float to 16 bits
304  success = success && encoder.set_bits_per_sample(16);
305  }
306 
307 
308  // Duplicate the flac command line compression levels
309  if (levelPref < 0 || levelPref > 8) {
310  levelPref = 5;
311  }
312  success = success &&
313  encoder.set_do_exhaustive_model_search(flacLevels[levelPref].do_exhaustive_model_search) &&
314  encoder.set_do_escape_coding(flacLevels[levelPref].do_escape_coding);
315 
316  if (numChannels != 2) {
317  success = success &&
318  encoder.set_do_mid_side_stereo(false) &&
319  encoder.set_loose_mid_side_stereo(false);
320  }
321  else {
322  success = success &&
323  encoder.set_do_mid_side_stereo(flacLevels[levelPref].do_mid_side_stereo) &&
324  encoder.set_loose_mid_side_stereo(flacLevels[levelPref].loose_mid_side_stereo);
325  }
326 
327  success = success &&
328  encoder.set_qlp_coeff_precision(flacLevels[levelPref].qlp_coeff_precision) &&
329  encoder.set_min_residual_partition_order(flacLevels[levelPref].min_residual_partition_order) &&
330  encoder.set_max_residual_partition_order(flacLevels[levelPref].max_residual_partition_order) &&
331  encoder.set_rice_parameter_search_dist(flacLevels[levelPref].rice_parameter_search_dist) &&
332  encoder.set_max_lpc_order(flacLevels[levelPref].max_lpc_order);
333 
334  if (!success) {
335  // TODO: more precise message
336  ShowExportErrorDialog("FLAC:336");
338  }
339 
340 #ifdef LEGACY_FLAC
341  encoder.init();
342 #else
343  wxFFile f; // will be closed when it goes out of scope
344  const auto path = fName.GetFullPath();
345  if (!f.Open(path, wxT("w+b"))) {
346  AudacityMessageBox( XO("FLAC export couldn't open %s").Format( path ) );
348  }
349 
350  // Even though there is an init() method that takes a filename, use the one that
351  // takes a file handle because wxWidgets can open a file with a Unicode name and
352  // libflac can't (under Windows).
353  int status = encoder.init(f.fp());
354  if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
356  XO("FLAC encoder failed to initialize\nStatus: %d")
357  .Format( status ) );
359  }
360 #endif
361 
362  mMetadata.reset();
363 
364  auto cleanup2 = finally( [&] {
365  if (!(updateResult == ProgressResult::Success ||
366  updateResult == ProgressResult::Stopped)) {
367 #ifndef LEGACY_FLAC
368  f.Detach(); // libflac closes the file
369 #endif
370  encoder.finish();
371  }
372  } );
373 
374  auto mixer = CreateMixer(tracks, selectionOnly,
375  t0, t1,
376  numChannels, SAMPLES_PER_RUN, false,
377  rate, format, mixerSpec);
378 
379  ArraysOf<FLAC__int32> tmpsmplbuf{ numChannels, SAMPLES_PER_RUN, true };
380 
381  InitProgress( pDialog, fName,
382  selectionOnly
383  ? XO("Exporting the selected audio as FLAC")
384  : XO("Exporting the audio as FLAC") );
385  auto &progress = *pDialog;
386 
387  while (updateResult == ProgressResult::Success) {
388  auto samplesThisRun = mixer->Process(SAMPLES_PER_RUN);
389  if (samplesThisRun == 0) { //stop encoding
390  break;
391  }
392  else {
393  for (size_t i = 0; i < numChannels; i++) {
394  samplePtr mixed = mixer->GetBuffer(i);
395  if (format == int24Sample) {
396  for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
397  tmpsmplbuf[i][j] = ((int *)mixed)[j];
398  }
399  }
400  else {
401  for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
402  tmpsmplbuf[i][j] = ((short *)mixed)[j];
403  }
404  }
405  }
406  if (! encoder.process(
407  reinterpret_cast<FLAC__int32**>( tmpsmplbuf.get() ),
408  samplesThisRun) ) {
409  // TODO: more precise message
411  updateResult = ProgressResult::Cancelled;
412  break;
413  }
414  if (updateResult == ProgressResult::Success)
415  updateResult =
416  progress.Update(mixer->MixGetCurrentTime() - t0, t1 - t0);
417  }
418  }
419 
420  if (updateResult == ProgressResult::Success ||
421  updateResult == ProgressResult::Stopped) {
422 #ifndef LEGACY_FLAC
423  f.Detach(); // libflac closes the file
424 #endif
425  if (!encoder.finish())
426  // Do not reassign updateResult, see cleanup2
427  return ProgressResult::Failed;
428 #ifdef LEGACY_FLAC
429  if (!f.Flush() || !f.Close())
430  return ProgressResult::Failed;
431 #endif
432  }
433 
434  return updateResult;
435 }
436 
438 {
440 }
441 
442 // LL: There's a bug in libflac++ 1.1.2 that prevents us from using
443 // FLAC::Metadata::VorbisComment directly. The set_metadata()
444 // function allocates an array on the stack, but the base library
445 // expects that array to be valid until the stream is initialized.
446 //
447 // This has been fixed in 1.1.4.
448 bool ExportFLAC::GetMetadata(AudacityProject *project, const Tags *tags)
449 {
450  // Retrieve tags if needed
451  if (tags == NULL)
452  tags = &Tags::Get( *project );
453 
454  mMetadata.reset(::FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT));
455 
456  wxString n;
457  for (const auto &pair : tags->GetRange()) {
458  n = pair.first;
459  const auto &v = pair.second;
460  if (n == TAG_YEAR) {
461  n = wxT("DATE");
462  }
463  else if (n == TAG_COMMENTS) {
464  // Some apps like Foobar use COMMENT and some like Windows use DESCRIPTION,
465  // so add both to try and make everyone happy.
466  n = wxT("COMMENT");
467  FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8),
468  v.mb_str(wxConvUTF8));
469  if (! ::FLAC__metadata_object_vorbiscomment_append_comment(mMetadata.get(),
470  entry.get_entry(),
471  true) ) {
472  return false;
473  }
474  n = wxT("DESCRIPTION");
475  }
476  FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8),
477  v.mb_str(wxConvUTF8));
478  if (! ::FLAC__metadata_object_vorbiscomment_append_comment(mMetadata.get(),
479  entry.get_entry(),
480  true) ) {
481  return false;
482  }
483  }
484 
485  return true;
486 }
487 
489  []{ return std::make_unique< ExportFLAC >(); }
490 };
491 
492 #endif // USE_LIBFLAC
493 
flacLevels
static struct @0 flacLevels[]
FLACBitDepth
ChoiceSetting FLACBitDepth
Definition: ExportFLAC.cpp:79
ShuttleGuiBase::StartVerticalLay
void StartVerticalLay(int iProp=1)
Definition: ShuttleGui.cpp:1177
wxFileNameWrapper
Definition: wxFileNameWrapper.h:21
gPrefs
FileConfig * gPrefs
Definition: Prefs.cpp:68
AudacityMessageBox
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: AudacityMessageBox.h:20
ExportPlugin::AddExtension
void AddExtension(const FileExtension &extension, int index)
Definition: Export.cpp:126
ShowExportErrorDialog
void ShowExportErrorDialog(wxString ErrorCode, TranslatableString message, const TranslatableString &caption)
Definition: Export.cpp:1518
wxPanelWrapper
Definition: wxPanelWrapper.h:41
ExportFLACOptions::~ExportFLACOptions
virtual ~ExportFLACOptions()
Definition: ExportFLAC.cpp:74
int24Sample
@ int24Sample
Definition: Types.h:198
FLAC__StreamMetadataDeleter::operator()
void operator()(FLAC__StreamMetadata *p) const
Definition: ExportFLAC.cpp:199
Tags
ID3 Tags (for MP3)
Definition: Tags.h:74
ExportFLACOptions::TransferDataToWindow
bool TransferDataToWindow() override
Definition: ExportFLAC.cpp:143
Format
Abstract base class used in importing a file.
Tags::GetRange
Iterators GetRange() const
Definition: Tags.cpp:480
ExportFLAC::GetMetadata
bool GetMetadata(AudacityProject *project, const Tags *tags)
Definition: ExportFLAC.cpp:448
ExportFLAC
Definition: ExportFLAC.cpp:207
qlp_coeff_precision
unsigned qlp_coeff_precision
Definition: ExportFLAC.cpp:179
XO
#define XO(s)
Definition: Internat.h:31
ProgressResult::Cancelled
@ Cancelled
ProjectSettings::Get
static ProjectSettings & Get(AudacityProject &project)
Definition: ProjectSettings.cpp:40
OSOUTPUT
#define OSOUTPUT(X)
Definition: FileNames.h:257
ShuttleGuiBase::EndMultiColumn
void EndMultiColumn()
Definition: ShuttleGui.cpp:1212
sRegisteredPlugin
static Exporter::RegisteredExportPlugin sRegisteredPlugin
Definition: ExportFLAC.cpp:488
ExportFLAC::OptionsCreate
void OptionsCreate(ShuttleGui &S, int format) override
Definition: ExportFLAC.cpp:437
loose_mid_side_stereo
bool loose_mid_side_stereo
Definition: ExportFLAC.cpp:178
Tags::Get
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:236
FLACLevel
ChoiceSetting FLACLevel
Definition: ExportFLAC.cpp:89
rice_parameter_search_dist
unsigned rice_parameter_search_dist
Definition: ExportFLAC.cpp:182
ChoiceSetting
Definition: Prefs.h:261
samplePtr
char * samplePtr
Definition: Types.h:214
sampleFormat
sampleFormat
Definition: Types.h:194
ShowDiskFullExportErrorDialog
void ShowDiskFullExportErrorDialog(const wxFileNameWrapper &fileName)
Definition: Export.cpp:1529
ProgressResult::Failed
@ Failed
XXO
#define XXO(s)
Definition: Internat.h:44
do_mid_side_stereo
bool do_mid_side_stereo
Definition: ExportFLAC.cpp:177
ShuttleGuiBase::EndHorizontalLay
void EndHorizontalLay()
Definition: ShuttleGui.cpp:1170
ExportPlugin::InitProgress
static void InitProgress(std::unique_ptr< ProgressDialog > &pDialog, const TranslatableString &title, const TranslatableString &message)
Definition: Export.cpp:250
ArraysOf
memory.h template class for making an array of arrays.
Definition: MemoryX.h:80
ProgressResult
ProgressResult
Definition: ProgressDialog.h:33
ShuttleGuiBase::StartHorizontalLay
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
Definition: ShuttleGui.cpp:1160
ProgressResult::Success
@ Success
ShuttleGuiBase::StartMultiColumn
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
Definition: ShuttleGui.cpp:1203
ExportPlugin::SetFormat
void SetFormat(const wxString &format, int index)
Definition: Export.cpp:116
ShuttleGuiBase::EndVerticalLay
void EndVerticalLay()
Definition: ShuttleGui.cpp:1196
ProgressResult::Stopped
@ Stopped
format
int format
Definition: ExportPCM.cpp:54
ShuttleGuiBase::GetParent
wxWindow * GetParent()
Definition: ShuttleGui.h:490
Export.h
TAG_YEAR
#define TAG_YEAR
Definition: Tags.h:64
ExportPlugin::SetDescription
void SetDescription(const TranslatableString &description, int index)
Definition: Export.cpp:121
SAMPLES_PER_RUN
#define SAMPLES_PER_RUN
Definition: ExportFLAC.cpp:164
max_residual_partition_order
unsigned max_residual_partition_order
Definition: ExportFLAC.cpp:181
do_escape_coding
bool do_escape_coding
Definition: ExportFLAC.cpp:176
ExportFLACOptions::ExportFLACOptions
ExportFLACOptions(wxWindow *parent, int format)
Definition: ExportFLAC.cpp:63
FLAC__StreamMetadataDeleter
Definition: ExportFLAC.cpp:198
ExportFLAC::Export
ProgressResult Export(AudacityProject *project, std::unique_ptr< 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:247
ExportFLAC::mMetadata
FLAC__StreamMetadataHandle mMetadata
Definition: ExportFLAC.cpp:231
ExportFLACOptions
Definition: ExportFLAC.cpp:50
ExportPlugin::SetMaxChannels
void SetMaxChannels(unsigned maxchannels, unsigned index)
Definition: Export.cpp:141
ChoiceSetting::Read
wxString Read() const
Definition: Prefs.cpp:236
eIsSavingToPrefs
@ eIsSavingToPrefs
Definition: ShuttleGui.h:47
Exporter::RegisteredExportPlugin
Definition: Export.h:176
ShuttleGuiBase::AddWindow
wxWindow * AddWindow(wxWindow *pWindow)
Definition: ShuttleGui.cpp:292
FileConfig::Flush
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:153
TrackList::Get
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:495
ExportFLAC::ExportFLAC
ExportFLAC()
Definition: ExportFLAC.cpp:236
AudacityProject
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:112
ExportPlugin::CreateMixer
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
ExportPlugin::SetCanMetaData
void SetCanMetaData(bool canmetadata, int index)
Definition: Export.cpp:146
int16Sample
@ int16Sample
Definition: Types.h:197
TAG_COMMENTS
#define TAG_COMMENTS
Definition: Tags.h:66
do_exhaustive_model_search
bool do_exhaustive_model_search
Definition: ExportFLAC.cpp:175
FLAC__StreamMetadataHandle
std::unique_ptr< FLAC__StreamMetadata, FLAC__StreamMetadataDeleter > FLAC__StreamMetadataHandle
Definition: ExportFLAC.cpp:204
ExportPlugin::AddFormat
int AddFormat()
Add a NEW entry to the list of formats this plug-in can export.
Definition: Export.cpp:100
max_lpc_order
unsigned max_lpc_order
Definition: ExportFLAC.cpp:183
ByColumns
ByColumns_t ByColumns
Definition: Prefs.cpp:374
MixerSpec
Class used with Mixer.
Definition: Mix.h:57
eIsCreatingFromPrefs
@ eIsCreatingFromPrefs
Definition: ShuttleGui.h:46
ExportPlugin
Definition: Export.h:65
safenew
#define safenew
Definition: MemoryX.h:10
settings
static Settings & settings()
Definition: TrackInfo.cpp:87
lrint
#define lrint(dbl)
Definition: float_cast.h:148
ShuttleGuiBase::TieChoice
wxChoice * TieChoice(const TranslatableString &Prompt, TranslatableString &Selected, const TranslatableStrings &choices)
Definition: ShuttleGui.cpp:1701
ExportFLACOptions::TransferDataFromWindow
bool TransferDataFromWindow() override
Definition: ExportFLAC.cpp:150
ExportFLACOptions::PopulateOrExchange
void PopulateOrExchange(ShuttleGui &S)
Definition: ExportFLAC.cpp:121
min_residual_partition_order
unsigned min_residual_partition_order
Definition: ExportFLAC.cpp:180
ShuttleGui
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:625