/********************************************************************** Audacity: A Digital Audio Editor ExportFLAC.cpp Frederik M.J.V This program is distributed under the GNU General Public License, version 2. A copy of this license is included with this source. Based on ExportOGG.cpp by: Joshua Haberman Portions from vorbis-tools, copyright 2000-2002 Michael Smith ; Vorbize, Kenneth Arnold ; and libvorbis examples, Monty **********************************************************************/ #include "../Audacity.h" // for USE_* macros #ifdef USE_LIBFLAC #include "ExportFLAC.h" #include "Export.h" #include #include #include #include "FLAC++/encoder.h" #include "../float_cast.h" #include "../ProjectSettings.h" #include "../Mix.h" #include "../Prefs.h" #include "../ShuttleGui.h" #include "../Tags.h" #include "../Track.h" #include "../widgets/AudacityMessageBox.h" #include "../widgets/ProgressDialog.h" //---------------------------------------------------------------------------- // ExportFLACOptions Class //---------------------------------------------------------------------------- class ExportFLACOptions final : public wxPanelWrapper { public: ExportFLACOptions(wxWindow *parent, int format); virtual ~ExportFLACOptions(); void PopulateOrExchange(ShuttleGui & S); bool TransferDataToWindow() override; bool TransferDataFromWindow() override; }; /// /// ExportFLACOptions::ExportFLACOptions(wxWindow *parent, int WXUNUSED(format)) : wxPanelWrapper(parent, wxID_ANY) { ShuttleGui S(this, eIsCreatingFromPrefs); PopulateOrExchange(S); TransferDataToWindow(); } /// /// ExportFLACOptions::~ExportFLACOptions() { TransferDataFromWindow(); } /// /// void ExportFLACOptions::PopulateOrExchange(ShuttleGui & S) { wxArrayStringEx flacLevelLabels{ wxT("0") , wxT("1") , wxT("2") , wxT("3") , wxT("4") , wxT("5") , wxT("6") , wxT("7") , wxT("8") , }; wxArrayStringEx flacLevelNames{ _("0 (fastest)") , _("1") , _("2") , _("3") , _("4") , _("5") , _("6") , _("7") , _("8 (best)") , }; wxArrayStringEx flacBitDepthLabels{ wxT("16") , wxT("24") , }; wxArrayStringEx flacBitDepthNames{ _("16 bit") , _("24 bit") , }; S.StartVerticalLay(); { S.StartHorizontalLay(wxCENTER); { S.StartMultiColumn(2, wxCENTER); { S.TieChoice(_("Level:"), wxT("/FileFormats/FLACLevel"), wxT("5"), flacLevelNames, flacLevelLabels); S.TieChoice(_("Bit depth:"), wxT("/FileFormats/FLACBitDepth"), wxT("16"), flacBitDepthNames, flacBitDepthLabels); } S.EndMultiColumn(); } S.EndHorizontalLay(); } S.EndVerticalLay(); return; } /// /// bool ExportFLACOptions::TransferDataToWindow() { return true; } /// /// bool ExportFLACOptions::TransferDataFromWindow() { ShuttleGui S(this, eIsSavingToPrefs); PopulateOrExchange(S); gPrefs->Flush(); return true; } //---------------------------------------------------------------------------- // ExportFLAC Class //---------------------------------------------------------------------------- #define SAMPLES_PER_RUN 8192u /* FLACPP_API_VERSION_CURRENT is 6 for libFLAC++ from flac-1.1.3 (see ) */ #if !defined FLACPP_API_VERSION_CURRENT || FLACPP_API_VERSION_CURRENT < 6 #define LEGACY_FLAC #else #undef LEGACY_FLAC #endif static struct { bool do_exhaustive_model_search; bool do_escape_coding; bool do_mid_side_stereo; bool loose_mid_side_stereo; unsigned qlp_coeff_precision; unsigned min_residual_partition_order; unsigned max_residual_partition_order; unsigned rice_parameter_search_dist; unsigned max_lpc_order; } flacLevels[] = { { false, false, false, false, 0, 2, 2, 0, 0 }, { false, false, true, true, 0, 2, 2, 0, 0 }, { false, false, true, false, 0, 0, 3, 0, 0 }, { false, false, false, false, 0, 3, 3, 0, 6 }, { false, false, true, true, 0, 3, 3, 0, 8 }, { false, false, true, false, 0, 3, 3, 0, 8 }, { false, false, true, false, 0, 0, 4, 0, 8 }, { true, false, true, false, 0, 0, 6, 0, 8 }, { true, false, true, false, 0, 0, 6, 0, 12 }, }; //---------------------------------------------------------------------------- struct FLAC__StreamMetadataDeleter { void operator () (FLAC__StreamMetadata *p) const { if (p) ::FLAC__metadata_object_delete(p); } }; using FLAC__StreamMetadataHandle = std::unique_ptr< FLAC__StreamMetadata, FLAC__StreamMetadataDeleter >; class ExportFLAC final : public ExportPlugin { public: ExportFLAC(); // Required wxWindow *OptionsCreate(wxWindow *parent, int format) override; ProgressResult Export(AudacityProject *project, std::unique_ptr &pDialog, unsigned channels, const wxString &fName, bool selectedOnly, double t0, double t1, MixerSpec *mixerSpec = NULL, const Tags *metadata = NULL, int subformat = 0) override; private: bool GetMetadata(AudacityProject *project, const Tags *tags); // Should this be a stack variable instead in Export? FLAC__StreamMetadataHandle mMetadata; }; //---------------------------------------------------------------------------- ExportFLAC::ExportFLAC() : ExportPlugin() { AddFormat(); SetFormat(wxT("FLAC"),0); AddExtension(wxT("flac"),0); SetMaxChannels(FLAC__MAX_CHANNELS,0); SetCanMetaData(true,0); SetDescription(_("FLAC Files"),0); } ProgressResult ExportFLAC::Export(AudacityProject *project, std::unique_ptr &pDialog, unsigned numChannels, const wxString &fName, bool selectionOnly, double t0, double t1, MixerSpec *mixerSpec, const Tags *metadata, int WXUNUSED(subformat)) { const auto &settings = ProjectSettings::Get( *project ); double rate = settings.GetRate(); const auto &tracks = TrackList::Get( *project ); wxLogNull logNo; // temporarily disable wxWidgets error messages auto updateResult = ProgressResult::Success; int levelPref; gPrefs->Read(wxT("/FileFormats/FLACLevel"), &levelPref, 5); wxString bitDepthPref = gPrefs->Read(wxT("/FileFormats/FLACBitDepth"), wxT("16")); FLAC::Encoder::File encoder; bool success = true; success = success && #ifdef LEGACY_FLAC encoder.set_filename(OSOUTPUT(fName)) && #endif encoder.set_channels(numChannels) && encoder.set_sample_rate(lrint(rate)); // See note in GetMetadata() about a bug in libflac++ 1.1.2 if (success && !GetMetadata(project, metadata)) { // TODO: more precise message AudacityMessageBox(_("Unable to export")); return ProgressResult::Cancelled; } if (success && mMetadata) { // set_metadata expects an array of pointers to metadata and a size. // The size is 1. FLAC__StreamMetadata *p = mMetadata.get(); success = encoder.set_metadata(&p, 1); } auto cleanup1 = finally( [&] { mMetadata.reset(); // need this? } ); sampleFormat format; if (bitDepthPref == wxT("24")) { format = int24Sample; success = success && encoder.set_bits_per_sample(24); } else { //convert float to 16 bits format = int16Sample; success = success && encoder.set_bits_per_sample(16); } // Duplicate the flac command line compression levels if (levelPref < 0 || levelPref > 8) { levelPref = 5; } success = success && encoder.set_do_exhaustive_model_search(flacLevels[levelPref].do_exhaustive_model_search) && encoder.set_do_escape_coding(flacLevels[levelPref].do_escape_coding); if (numChannels != 2) { success = success && encoder.set_do_mid_side_stereo(false) && encoder.set_loose_mid_side_stereo(false); } else { success = success && encoder.set_do_mid_side_stereo(flacLevels[levelPref].do_mid_side_stereo) && encoder.set_loose_mid_side_stereo(flacLevels[levelPref].loose_mid_side_stereo); } success = success && encoder.set_qlp_coeff_precision(flacLevels[levelPref].qlp_coeff_precision) && encoder.set_min_residual_partition_order(flacLevels[levelPref].min_residual_partition_order) && encoder.set_max_residual_partition_order(flacLevels[levelPref].max_residual_partition_order) && encoder.set_rice_parameter_search_dist(flacLevels[levelPref].rice_parameter_search_dist) && encoder.set_max_lpc_order(flacLevels[levelPref].max_lpc_order); if (!success) { // TODO: more precise message AudacityMessageBox(_("Unable to export")); return ProgressResult::Cancelled; } #ifdef LEGACY_FLAC encoder.init(); #else wxFFile f; // will be closed when it goes out of scope if (!f.Open(fName, wxT("w+b"))) { AudacityMessageBox(wxString::Format(_("FLAC export couldn't open %s"), fName)); return ProgressResult::Cancelled; } // Even though there is an init() method that takes a filename, use the one that // takes a file handle because wxWidgets can open a file with a Unicode name and // libflac can't (under Windows). int status = encoder.init(f.fp()); if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { AudacityMessageBox(wxString::Format(_("FLAC encoder failed to initialize\nStatus: %d"), status)); return ProgressResult::Cancelled; } #endif mMetadata.reset(); auto cleanup2 = finally( [&] { if (!(updateResult == ProgressResult::Success || updateResult == ProgressResult::Stopped)) { #ifndef LEGACY_FLAC f.Detach(); // libflac closes the file #endif encoder.finish(); } } ); const WaveTrackConstArray waveTracks = tracks.GetWaveTrackConstArray(selectionOnly, false); auto mixer = CreateMixer(waveTracks, tracks.GetTimeTrack(), t0, t1, numChannels, SAMPLES_PER_RUN, false, rate, format, true, mixerSpec); ArraysOf tmpsmplbuf{ numChannels, SAMPLES_PER_RUN, true }; InitProgress( pDialog, wxFileName(fName).GetName(), selectionOnly ? _("Exporting the selected audio as FLAC") : _("Exporting the audio as FLAC") ); auto &progress = *pDialog; while (updateResult == ProgressResult::Success) { auto samplesThisRun = mixer->Process(SAMPLES_PER_RUN); if (samplesThisRun == 0) { //stop encoding break; } else { for (size_t i = 0; i < numChannels; i++) { samplePtr mixed = mixer->GetBuffer(i); if (format == int24Sample) { for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) { tmpsmplbuf[i][j] = ((int *)mixed)[j]; } } else { for (decltype(samplesThisRun) j = 0; j < samplesThisRun; j++) { tmpsmplbuf[i][j] = ((short *)mixed)[j]; } } } if (! encoder.process( reinterpret_cast( tmpsmplbuf.get() ), samplesThisRun) ) { // TODO: more precise message AudacityMessageBox(_("Unable to export")); updateResult = ProgressResult::Cancelled; break; } if (updateResult == ProgressResult::Success) updateResult = progress.Update(mixer->MixGetCurrentTime() - t0, t1 - t0); } } if (updateResult == ProgressResult::Success || updateResult == ProgressResult::Stopped) { #ifndef LEGACY_FLAC f.Detach(); // libflac closes the file #endif if (!encoder.finish()) // Do not reassign updateResult, see cleanup2 return ProgressResult::Failed; #ifdef LEGACY_FLAC if (!f.Flush() || !f.Close()) return ProgressResult::Failed; #endif } return updateResult; } wxWindow *ExportFLAC::OptionsCreate(wxWindow *parent, int format) { wxASSERT(parent); // to justify safenew return safenew ExportFLACOptions(parent, format); } // LL: There's a bug in libflac++ 1.1.2 that prevents us from using // FLAC::Metadata::VorbisComment directly. The set_metadata() // function allocates an array on the stack, but the base library // expects that array to be valid until the stream is initialized. // // This has been fixed in 1.1.4. bool ExportFLAC::GetMetadata(AudacityProject *project, const Tags *tags) { // Retrieve tags if needed if (tags == NULL) tags = &Tags::Get( *project ); mMetadata.reset(::FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)); wxString n; for (const auto &pair : tags->GetRange()) { n = pair.first; const auto &v = pair.second; if (n == TAG_YEAR) { n = wxT("DATE"); } FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8), v.mb_str(wxConvUTF8)); if (! ::FLAC__metadata_object_vorbiscomment_append_comment(mMetadata.get(), entry.get_entry(), true) ) return false; } return true; } std::unique_ptr New_ExportFLAC() { return std::make_unique(); } #endif // USE_LIBFLAC