Audacity  2.2.2
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 #include "../Audacity.h"
22 
23 #ifdef USE_LIBFLAC
24 
25 #include "ExportFLAC.h"
26 #include "Export.h"
27 
28 #include <wx/progdlg.h>
29 #include <wx/ffile.h>
30 #include <wx/log.h>
31 
32 #include "FLAC++/encoder.h"
33 
34 #include "../float_cast.h"
35 #include "../Project.h"
36 #include "../Mix.h"
37 #include "../Prefs.h"
38 #include "../ShuttleGui.h"
39 
40 #include "../Internat.h"
41 #include "../Tags.h"
42 
43 #include "../Track.h"
44 #include "../widgets/ErrorDialog.h"
45 
46 //----------------------------------------------------------------------------
47 // ExportFLACOptions Class
48 //----------------------------------------------------------------------------
49 
50 class ExportFLACOptions final : public wxPanelWrapper
51 {
52 public:
53 
54  ExportFLACOptions(wxWindow *parent, int format);
55  virtual ~ExportFLACOptions();
56 
57  void PopulateOrExchange(ShuttleGui & S);
58  bool TransferDataToWindow() override;
59  bool TransferDataFromWindow() override;
60 };
61 
64 ExportFLACOptions::ExportFLACOptions(wxWindow *parent, int WXUNUSED(format))
65 : wxPanelWrapper(parent, wxID_ANY)
66 {
68  PopulateOrExchange(S);
69 
70  TransferDataToWindow();
71 }
72 
75 ExportFLACOptions::~ExportFLACOptions()
76 {
77  TransferDataFromWindow();
78 }
79 
82 void ExportFLACOptions::PopulateOrExchange(ShuttleGui & S)
83 {
84  wxArrayString flacLevelNames, flacLevelLabels;
85  flacLevelLabels.Add(wxT("0")); flacLevelNames.Add(_("0 (fastest)"));
86  flacLevelLabels.Add(wxT("1")); flacLevelNames.Add(_("1"));
87  flacLevelLabels.Add(wxT("2")); flacLevelNames.Add(_("2"));
88  flacLevelLabels.Add(wxT("3")); flacLevelNames.Add(_("3"));
89  flacLevelLabels.Add(wxT("4")); flacLevelNames.Add(_("4"));
90  flacLevelLabels.Add(wxT("5")); flacLevelNames.Add(_("5"));
91  flacLevelLabels.Add(wxT("6")); flacLevelNames.Add(_("6"));
92  flacLevelLabels.Add(wxT("7")); flacLevelNames.Add(_("7"));
93  flacLevelLabels.Add(wxT("8")); flacLevelNames.Add(_("8 (best)"));
94 
95  wxArrayString flacBitDepthNames, flacBitDepthLabels;
96  flacBitDepthLabels.Add(wxT("16")); flacBitDepthNames.Add(_("16 bit"));
97  flacBitDepthLabels.Add(wxT("24")); flacBitDepthNames.Add(_("24 bit"));
98 
99  S.StartVerticalLay();
100  {
101  S.StartHorizontalLay(wxCENTER);
102  {
103  S.StartMultiColumn(2, wxCENTER);
104  {
105  S.TieChoice(_("Level:"), wxT("/FileFormats/FLACLevel"),
106  wxT("5"), flacLevelNames, flacLevelLabels);
107  S.TieChoice(_("Bit depth:"), wxT("/FileFormats/FLACBitDepth"),
108  wxT("16"), flacBitDepthNames, flacBitDepthLabels);
109  }
110  S.EndMultiColumn();
111  }
112  S.EndHorizontalLay();
113  }
114  S.EndVerticalLay();
115 
116  return;
117 }
118 
121 bool ExportFLACOptions::TransferDataToWindow()
122 {
123  return true;
124 }
125 
128 bool ExportFLACOptions::TransferDataFromWindow()
129 {
130  ShuttleGui S(this, eIsSavingToPrefs);
131  PopulateOrExchange(S);
132 
133  gPrefs->Flush();
134 
135  return true;
136 }
137 
138 //----------------------------------------------------------------------------
139 // ExportFLAC Class
140 //----------------------------------------------------------------------------
141 
142 #define SAMPLES_PER_RUN 8192u
143 
144 /* FLACPP_API_VERSION_CURRENT is 6 for libFLAC++ from flac-1.1.3 (see <FLAC++/export.h>) */
145 #if !defined FLACPP_API_VERSION_CURRENT || FLACPP_API_VERSION_CURRENT < 6
146 #define LEGACY_FLAC
147 #else
148 #undef LEGACY_FLAC
149 #endif
150 
151 static struct
152 {
153  bool do_exhaustive_model_search;
154  bool do_escape_coding;
155  bool do_mid_side_stereo;
156  bool loose_mid_side_stereo;
157  unsigned qlp_coeff_precision;
158  unsigned min_residual_partition_order;
159  unsigned max_residual_partition_order;
160  unsigned rice_parameter_search_dist;
161  unsigned max_lpc_order;
162 } flacLevels[] = {
163  { false, false, false, false, 0, 2, 2, 0, 0 },
164  { false, false, true, true, 0, 2, 2, 0, 0 },
165  { false, false, true, false, 0, 0, 3, 0, 0 },
166  { false, false, false, false, 0, 3, 3, 0, 6 },
167  { false, false, true, true, 0, 3, 3, 0, 8 },
168  { false, false, true, false, 0, 3, 3, 0, 8 },
169  { false, false, true, false, 0, 0, 4, 0, 8 },
170  { true, false, true, false, 0, 0, 6, 0, 8 },
171  { true, false, true, false, 0, 0, 6, 0, 12 },
172 };
173 
174 //----------------------------------------------------------------------------
175 
176 struct FLAC__StreamMetadataDeleter {
177  void operator () (FLAC__StreamMetadata *p) const
178  { if (p) ::FLAC__metadata_object_delete(p); }
179 };
180 using FLAC__StreamMetadataHandle = std::unique_ptr<
181  FLAC__StreamMetadata, FLAC__StreamMetadataDeleter
182 >;
183 
184 class ExportFLAC final : public ExportPlugin
185 {
186 public:
187 
188  ExportFLAC();
189 
190  // Required
191 
192  wxWindow *OptionsCreate(wxWindow *parent, int format) override;
194  std::unique_ptr<ProgressDialog> &pDialog,
195  unsigned channels,
196  const wxString &fName,
197  bool selectedOnly,
198  double t0,
199  double t1,
200  MixerSpec *mixerSpec = NULL,
201  const Tags *metadata = NULL,
202  int subformat = 0) override;
203 
204 private:
205 
206  bool GetMetadata(AudacityProject *project, const Tags *tags);
207 
208  // Should this be a stack variable instead in Export?
209  FLAC__StreamMetadataHandle mMetadata;
210 };
211 
212 //----------------------------------------------------------------------------
213 
214 ExportFLAC::ExportFLAC()
215 : ExportPlugin()
216 {
217  AddFormat();
218  SetFormat(wxT("FLAC"),0);
219  AddExtension(wxT("flac"),0);
220  SetMaxChannels(FLAC__MAX_CHANNELS,0);
221  SetCanMetaData(true,0);
222  SetDescription(_("FLAC Files"),0);
223 }
224 
225 ProgressResult ExportFLAC::Export(AudacityProject *project,
226  std::unique_ptr<ProgressDialog> &pDialog,
227  unsigned numChannels,
228  const wxString &fName,
229  bool selectionOnly,
230  double t0,
231  double t1,
232  MixerSpec *mixerSpec,
233  const Tags *metadata,
234  int WXUNUSED(subformat))
235 {
236  double rate = project->GetRate();
237  const TrackList *tracks = project->GetTracks();
238 
239  wxLogNull logNo; // temporarily disable wxWidgets error messages
240  auto updateResult = ProgressResult::Success;
241 
242  int levelPref;
243  gPrefs->Read(wxT("/FileFormats/FLACLevel"), &levelPref, 5);
244 
245  wxString bitDepthPref =
246  gPrefs->Read(wxT("/FileFormats/FLACBitDepth"), wxT("16"));
247 
248  FLAC::Encoder::File encoder;
249 
250  bool success = true;
251  success = success &&
252 #ifdef LEGACY_FLAC
253  encoder.set_filename(OSOUTPUT(fName)) &&
254 #endif
255  encoder.set_channels(numChannels) &&
256  encoder.set_sample_rate(lrint(rate));
257 
258  // See note in GetMetadata() about a bug in libflac++ 1.1.2
259  if (success && !GetMetadata(project, metadata)) {
260  // TODO: more precise message
261  AudacityMessageBox(_("Unable to export"));
263  }
264 
265  if (success && mMetadata) {
266  // set_metadata expects an array of pointers to metadata and a size.
267  // The size is 1.
268  FLAC__StreamMetadata *p = mMetadata.get();
269  success = encoder.set_metadata(&p, 1);
270  }
271 
272  auto cleanup1 = finally( [&] {
273  mMetadata.reset(); // need this?
274  } );
275 
277  if (bitDepthPref == wxT("24")) {
278  format = int24Sample;
279  success = success && encoder.set_bits_per_sample(24);
280  } else { //convert float to 16 bits
281  format = int16Sample;
282  success = success && encoder.set_bits_per_sample(16);
283  }
284 
285 
286  // Duplicate the flac command line compression levels
287  if (levelPref < 0 || levelPref > 8) {
288  levelPref = 5;
289  }
290  success = success &&
291  encoder.set_do_exhaustive_model_search(flacLevels[levelPref].do_exhaustive_model_search) &&
292  encoder.set_do_escape_coding(flacLevels[levelPref].do_escape_coding);
293 
294  if (numChannels != 2) {
295  success = success &&
296  encoder.set_do_mid_side_stereo(false) &&
297  encoder.set_loose_mid_side_stereo(false);
298  }
299  else {
300  success = success &&
301  encoder.set_do_mid_side_stereo(flacLevels[levelPref].do_mid_side_stereo) &&
302  encoder.set_loose_mid_side_stereo(flacLevels[levelPref].loose_mid_side_stereo);
303  }
304 
305  success = success &&
306  encoder.set_qlp_coeff_precision(flacLevels[levelPref].qlp_coeff_precision) &&
307  encoder.set_min_residual_partition_order(flacLevels[levelPref].min_residual_partition_order) &&
308  encoder.set_max_residual_partition_order(flacLevels[levelPref].max_residual_partition_order) &&
309  encoder.set_rice_parameter_search_dist(flacLevels[levelPref].rice_parameter_search_dist) &&
310  encoder.set_max_lpc_order(flacLevels[levelPref].max_lpc_order);
311 
312  if (!success) {
313  // TODO: more precise message
314  AudacityMessageBox(_("Unable to export"));
316  }
317 
318 #ifdef LEGACY_FLAC
319  encoder.init();
320 #else
321  wxFFile f; // will be closed when it goes out of scope
322  if (!f.Open(fName, wxT("w+b"))) {
323  AudacityMessageBox(wxString::Format(_("FLAC export couldn't open %s"), fName));
325  }
326 
327  // Even though there is an init() method that takes a filename, use the one that
328  // takes a file handle because wxWidgets can open a file with a Unicode name and
329  // libflac can't (under Windows).
330  int status = encoder.init(f.fp());
331  if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
332  AudacityMessageBox(wxString::Format(_("FLAC encoder failed to initialize\nStatus: %d"), status));
334  }
335 #endif
336 
337  mMetadata.reset();
338 
339  auto cleanup2 = finally( [&] {
340  if (!(updateResult == ProgressResult::Success ||
341  updateResult == ProgressResult::Stopped)) {
342 #ifndef LEGACY_FLAC
343  f.Detach(); // libflac closes the file
344 #endif
345  encoder.finish();
346  }
347  } );
348 
349  const WaveTrackConstArray waveTracks =
350  tracks->GetWaveTrackConstArray(selectionOnly, false);
351  auto mixer = CreateMixer(waveTracks,
352  tracks->GetTimeTrack(),
353  t0, t1,
354  numChannels, SAMPLES_PER_RUN, false,
355  rate, format, true, mixerSpec);
356 
357  ArraysOf<FLAC__int32> tmpsmplbuf{ numChannels, SAMPLES_PER_RUN, true };
358 
359  InitProgress( pDialog, wxFileName(fName).GetName(),
360  selectionOnly
361  ? _("Exporting the selected audio as FLAC")
362  : _("Exporting the audio as FLAC") );
363  auto &progress = *pDialog;
364 
365  while (updateResult == ProgressResult::Success) {
366  auto samplesThisRun = mixer->Process(SAMPLES_PER_RUN);
367  if (samplesThisRun == 0) { //stop encoding
368  break;
369  }
370  else {
371  for (size_t i = 0; i < numChannels; i++) {
372  samplePtr mixed = mixer->GetBuffer(i);
373  if (format == int24Sample) {
374  for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
375  tmpsmplbuf[i][j] = ((int *)mixed)[j];
376  }
377  }
378  else {
379  for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) {
380  tmpsmplbuf[i][j] = ((short *)mixed)[j];
381  }
382  }
383  }
384  if (! encoder.process(
385  reinterpret_cast<FLAC__int32**>( tmpsmplbuf.get() ),
386  samplesThisRun) ) {
387  // TODO: more precise message
388  AudacityMessageBox(_("Unable to export"));
389  updateResult = ProgressResult::Cancelled;
390  break;
391  }
392  if (updateResult == ProgressResult::Success)
393  updateResult =
394  progress.Update(mixer->MixGetCurrentTime() - t0, t1 - t0);
395  }
396  }
397 
398  if (updateResult == ProgressResult::Success ||
399  updateResult == ProgressResult::Stopped) {
400 #ifndef LEGACY_FLAC
401  f.Detach(); // libflac closes the file
402 #endif
403  if (!encoder.finish())
404  // Do not reassign updateResult, see cleanup2
405  return ProgressResult::Failed;
406 #ifdef LEGACY_FLAC
407  if (!f.Flush() || !f.Close())
408  return ProgressResult::Failed;
409 #endif
410  }
411 
412  return updateResult;
413 }
414 
415 wxWindow *ExportFLAC::OptionsCreate(wxWindow *parent, int format)
416 {
417  wxASSERT(parent); // to justify safenew
418  return safenew ExportFLACOptions(parent, format);
419 }
420 
421 // LL: There's a bug in libflac++ 1.1.2 that prevents us from using
422 // FLAC::Metadata::VorbisComment directly. The set_metadata()
423 // function allocates an array on the stack, but the base library
424 // expects that array to be valid until the stream is initialized.
425 //
426 // This has been fixed in 1.1.4.
427 bool ExportFLAC::GetMetadata(AudacityProject *project, const Tags *tags)
428 {
429  // Retrieve tags if needed
430  if (tags == NULL)
431  tags = project->GetTags();
432 
433  mMetadata.reset(::FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT));
434 
435  wxString n;
436  for (const auto &pair : tags->GetRange()) {
437  n = pair.first;
438  const auto &v = pair.second;
439  if (n == TAG_YEAR) {
440  n = wxT("DATE");
441  }
442  FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8),
443  v.mb_str(wxConvUTF8));
444  if (! ::FLAC__metadata_object_vorbiscomment_append_comment(mMetadata.get(),
445  entry.get_entry(),
446  true) )
447  return false;
448  }
449 
450  return true;
451 }
452 
453 std::unique_ptr<ExportPlugin> New_ExportFLAC()
454 {
455  return std::make_unique<ExportFLAC>();
456 }
457 
458 #endif // USE_LIBFLAC
459 
wxChoice * TieChoice(const wxString &Prompt, WrappedType &WrappedRef, const wxArrayString *pChoices)
#define OSOUTPUT(X)
Definition: Internat.h:175
memory.h template class for making an array of arrays.
Definition: MemoryX.h:137
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
A list of TrackListNode items.
Definition: Track.h:618
ProgressResult
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:409
void EndMultiColumn()
virtual wxWindow * OptionsCreate(wxWindow *parent, int format)=0
Definition: Export.cpp:215
int AudacityMessageBox(const wxString &message, const wxString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: ErrorDialog.h:92
TimeTrack * GetTimeTrack()
Definition: Track.cpp:1244
#define safenew
Definition: Audacity.h:230
void EndHorizontalLay()
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:176
void EndVerticalLay()
int format
Definition: ExportPCM.cpp:56
WaveTrackConstArray GetWaveTrackConstArray(bool selectionOnly, bool includeMuted=true) const
Definition: Track.cpp:1349
std::unique_ptr< ExportPlugin > New_ExportFLAC()
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
std::vector< std::shared_ptr< const WaveTrack > > WaveTrackConstArray
Definition: AudioIO.h:66
sampleFormat
Definition: Types.h:188
#define lrint(dbl)
Definition: float_cast.h:136
char * samplePtr
Definition: Types.h:203
ID3 Tags (for MP3)
Definition: Tags.h:70
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
const Tags * GetTags()
Definition: Project.cpp:1453
virtual ProgressResult Export(AudacityProject *project, std::unique_ptr< ProgressDialog > &pDialog, unsigned channels, const wxString &fName, bool selectedOnly, double t0, double t1, MixerSpec *mixerSpec=NULL, const Tags *metadata=NULL, int subformat=0)=0
called to export audio into a file.
double GetRate() const
Definition: Project.h:199
TrackList * GetTracks()
Definition: Project.h:192
Iterators GetRange() const
Definition: Tags.cpp:444
#define TAG_YEAR
Definition: Tags.h:64
Class used with Mixer.
Definition: Mix.h:58
void StartVerticalLay(int iProp=1)