From ddadd8d4297f3feaa7f8f69693bbe71920ab63b0 Mon Sep 17 00:00:00 2001 From: Leland Lucius Date: Mon, 30 Mar 2020 12:02:03 -0500 Subject: [PATCH] Bug 2166 - Opus export fails - cannot open codec --- src/FFmpeg.cpp | 1 + src/FFmpeg.h | 7 + src/export/ExportFFmpeg.cpp | 31 +++- src/export/ExportFFmpegDialogs.cpp | 276 ++++++++++++++++++++++++++++- src/export/ExportFFmpegDialogs.h | 35 ++++ 5 files changed, 341 insertions(+), 9 deletions(-) diff --git a/src/FFmpeg.cpp b/src/FFmpeg.cpp index 4611bf2f6..3d4e2c943 100644 --- a/src/FFmpeg.cpp +++ b/src/FFmpeg.cpp @@ -967,6 +967,7 @@ bool FFmpegLibs::InitLibs(const wxString &libpath_format, bool WXUNUSED(showerr) FFMPEG_INITALT(avutil, av_frame_free, avcodec, avcodec_free_frame); FFMPEG_INITDYN(avutil, av_samples_get_buffer_size); FFMPEG_INITDYN(avutil, av_get_default_channel_layout); + FFMPEG_INITDYN(avutil, av_strerror); wxLogMessage(wxT("All symbols loaded successfully. Initializing the library.")); #endif diff --git a/src/FFmpeg.h b/src/FFmpeg.h index 561e7c5c5..540337792 100644 --- a/src/FFmpeg.h +++ b/src/FFmpeg.h @@ -52,6 +52,7 @@ extern "C" { #include #include + #include #include #include @@ -527,6 +528,12 @@ extern "C" { (int nb_channels), (nb_channels) ); + FFMPEG_FUNCTION_WITH_RETURN( + int, + av_strerror, + (int errnum, char *errbuf, size_t errbuf_size), + (errnum, errbuf, errbuf_size) + ); // // libavcodec diff --git a/src/export/ExportFFmpeg.cpp b/src/export/ExportFFmpeg.cpp index 9de4a4169..34b9c7742 100644 --- a/src/export/ExportFFmpeg.cpp +++ b/src/export/ExportFFmpeg.cpp @@ -445,6 +445,20 @@ bool ExportFFmpeg::InitCodecs(AudacityProject *project) mSampleRate = 8000; mEncAudioCodecCtx->bit_rate = gPrefs->Read(wxT("/FileFormats/AMRNBBitRate"), 12200); break; + case FMT_OPUS: + av_dict_set(&options, "b", gPrefs->Read(wxT("/FileFormats/OPUSBitRate"), wxT("128000")).ToUTF8(), 0); + av_dict_set(&options, "vbr", gPrefs->Read(wxT("/FileFormats/OPUSVbrMode"), wxT("on")).ToUTF8(), 0); + av_dict_set(&options, "compression_level", gPrefs->Read(wxT("/FileFormats/OPUSCompression"), wxT("10")).ToUTF8(), 0); + av_dict_set(&options, "frame_duration", gPrefs->Read(wxT("/FileFormats/OPUSFrameDuration"), wxT("20")).ToUTF8(), 0); + av_dict_set(&options, "application", gPrefs->Read(wxT("/FileFormats/OPUSApplication"), wxT("audio")).ToUTF8(), 0); + av_dict_set(&options, "cutoff", gPrefs->Read(wxT("/FileFormats/OPUSCutoff"), wxT("0")).ToUTF8(), 0); + av_dict_set(&options, "mapping_family", mChannels <= 2 ? "0" : "255", 0); + if (!CheckSampleRate(mSampleRate, ExportFFmpegOPUSOptions::iOPUSSampleRates[4], ExportFFmpegOPUSOptions::iOPUSSampleRates[0], &ExportFFmpegOPUSOptions::iOPUSSampleRates[0])) + { + int bitrate = gPrefs->Read(wxT("/FileFormats/OPUSBitRate"), 128000); + mSampleRate = AskResample(bitrate, mSampleRate, ExportFFmpegOPUSOptions::iOPUSSampleRates[4], ExportFFmpegOPUSOptions::iOPUSSampleRates[0], &ExportFFmpegOPUSOptions::iOPUSSampleRates[0]); + } + break; case FMT_WMA2: mEncAudioCodecCtx->bit_rate = gPrefs->Read(wxT("/FileFormats/WMABitRate"), 198000); if (!CheckSampleRate(mSampleRate,ExportFFmpegWMAOptions::iWMASampleRates[0], ExportFFmpegWMAOptions::iWMASampleRates[4], &ExportFFmpegWMAOptions::iWMASampleRates[0])) @@ -552,12 +566,15 @@ bool ExportFFmpeg::InitCodecs(AudacityProject *project) } // Open the codec. - if (avcodec_open2(mEncAudioCodecCtx.get(), codec, &options) < 0) + int rc = avcodec_open2(mEncAudioCodecCtx.get(), codec, &options); + if (rc < 0) { + char buf[AV_ERROR_MAX_STRING_SIZE]; + av_strerror(rc, buf, sizeof(buf)); AudacityMessageBox( /* i18n-hint: "codec" is short for a "coder-decoder" algorithm */ - XO("FFmpeg : ERROR - Can't open audio codec 0x%x.") - .Format( mEncAudioCodecCtx->codec_id ), + XO("FFmpeg : ERROR - Can't open audio codec 0x%x\n\n%s") + .Format(mEncAudioCodecCtx->codec_id, wxString(buf)), XO("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION); return false; @@ -1054,7 +1071,7 @@ int ExportFFmpeg::AskResample(int bitrate, int rate, int lowrate, int highrate, .Format( rate ) : XO( "The project sample rate (%d) and bit rate (%d kbps) combination is not\nsupported by the current output file format. ") - .Format( rate, bitrate/1024)) + .Format( rate, bitrate/1000)) + XO("You may resample to one of the rates below.") ); } @@ -1123,6 +1140,12 @@ void ExportFFmpeg::OptionsCreate(ShuttleGui &S, int format) safenew ExportFFmpegAMRNBOptions{ S.GetParent(), format } ); return; } + else if (mSubFormat == FMT_OPUS) + { + S.AddWindow( + safenew ExportFFmpegOPUSOptions{ S.GetParent(), format }); + return; + } else if (mSubFormat == FMT_WMA2) { S.AddWindow( diff --git a/src/export/ExportFFmpegDialogs.cpp b/src/export/ExportFFmpegDialogs.cpp index cc8b28e39..15de8b44d 100644 --- a/src/export/ExportFFmpegDialogs.cpp +++ b/src/export/ExportFFmpegDialogs.cpp @@ -26,6 +26,11 @@ *//***************************************************************//** +\class ExportFFmpegOPUSOptions +\brief Options dialog for FFmpeg exporting of OPUS format. + +*//***************************************************************//** + \class ExportFFmpegWMAOptions \brief Options dialog for FFmpeg exporting of WMA format. @@ -400,6 +405,266 @@ bool ExportFFmpegAMRNBOptions::TransferDataFromWindow() return true; } +//---------------------------------------------------------------------------- +// ExportFFmpegOPUSOptions Class +//---------------------------------------------------------------------------- + +const int ExportFFmpegOPUSOptions::iOPUSSampleRates[] = +{ 48000, 24000, 16000, 12000, 8000, 0 }; + +namespace { + + /// Bit Rates supported by OPUS encoder. Setting bit rate to other values will not result in different file size. + ChoiceSetting OPUSBitrate + { + wxT("/FileFormats/OPUSBitrate"), + { + ByColumns, + { + // i18n-hint kbps abbreviates "thousands of bits per second" + XO("24 kbps"), + XO("32 kbps"), + XO("40 kbps"), + XO("48 kbps"), + XO("64 kbps"), + XO("80 kbps"), + XO("96 kbps"), + XO("128 kbps"), + XO("160 kbps"), + XO("192 kbps"), + XO("256 kbps"), + XO("320 kbps"), + XO("510 kbps"), + }, + { + wxT("24000"), + wxT("32000"), + wxT("40000"), + wxT("48000"), + wxT("64000"), + wxT("80000"), + wxT("96000"), + wxT("128000"), + wxT("160000"), + wxT("192000"), + wxT("256000"), + wxT("320000"), + wxT("510000"), + } + }, + 7 // "128 kbps" + }; + + ChoiceSetting OPUSCompression + { + wxT("/FileFormats/OPUSCompression"), + { + ByColumns, + { + XO("0"), + XO("1"), + XO("2"), + XO("3"), + XO("4"), + XO("5"), + XO("6"), + XO("7"), + XO("8"), + XO("9"), + XO("10"), + }, + { + wxT("0"), + wxT("1"), + wxT("2"), + wxT("3"), + wxT("4"), + wxT("5"), + wxT("67"), + wxT("7"), + wxT("8"), + wxT("9"), + wxT("10"), + } + }, + 10 // "10" + }; + + + ChoiceSetting OPUSVbrMode + { + wxT("/FileFormats/OPUSVbrMode"), + { + ByColumns, + { + XO("Off"), + XO("On"), + XO("Constrained"), + }, + { + wxT("off"), + wxT("on"), + wxT("constrained"), + } + }, + 1 // "On" + }; + + ChoiceSetting OPUSApplication + { + wxT("/FileFormats/OPUSApplication"), + { + ByColumns, + { + XO("VOIP"), + XO("Audio"), + XO("Low Delay"), + }, + { + wxT("voip"), + wxT("audio"), + wxT("lowdelay"), + } + }, + 1 // "Audio" + }; + + ChoiceSetting OPUSFrameDuration + { + wxT("/FileFormats/OPUSFrameDuration"), + { + ByColumns, + { + XO("2.5 ms"), + XO("5 ms"), + XO("10 ms"), + XO("20 ms"), + XO("40 ms"), + XO("60 ms"), + }, + { + wxT("2.5"), + wxT("5"), + wxT("10"), + wxT("20"), + wxT("40"), + wxT("60"), + } + }, + 3 // "20" + }; + + ChoiceSetting OPUSCutoff + { + wxT("/FileFormats/OPUSCutoff"), + { + ByColumns, + { + XO("Disabled"), + XO("Narrowband"), + XO("Mediumband"), + XO("Wideband"), + XO("Super Wideband"), + XO("Fullband"), + }, + { + wxT("0"), + wxT("4000"), + wxT("6000"), + wxT("8000"), + wxT("12000"), + wxT("20000"), + } + }, + 0 // "Disabled" + }; +} + +ExportFFmpegOPUSOptions::ExportFFmpegOPUSOptions(wxWindow *parent, int WXUNUSED(format)) +: wxPanelWrapper(parent, wxID_ANY) +{ + ShuttleGui S(this, eIsCreatingFromPrefs); + PopulateOrExchange(S); + + TransferDataToWindow(); +} + +ExportFFmpegOPUSOptions::~ExportFFmpegOPUSOptions() +{ + TransferDataFromWindow(); +} + +/// +/// +void ExportFFmpegOPUSOptions::PopulateOrExchange(ShuttleGui & S) +{ + S.SetSizerProportion(1); + S.SetBorder(4); + S.StartVerticalLay(); + { + S.StartHorizontalLay(wxCENTER); + { + S.StartMultiColumn(2, wxCENTER); + { + S.StartMultiColumn(2, wxCENTER); + { + S.TieChoice( + XO("Bit Rate:"), + OPUSBitrate); + + S.TieChoice( + XO("Compression"), + OPUSCompression); + + S.TieChoice( + XO("Frame Duration:"), + OPUSFrameDuration); + } + S.EndMultiColumn(); + + S.StartMultiColumn(2, wxCENTER); + { + S.TieChoice( + XO("Vbr Mode:"), + OPUSVbrMode); + + S.TieChoice( + XO("Application:"), + OPUSApplication); + + S.TieChoice( + XO("Cutoff:"), + OPUSCutoff); + + } + S.EndMultiColumn(); + } + S.EndMultiColumn(); + } + S.EndHorizontalLay(); + } + S.EndVerticalLay(); +} + +/// +/// +bool ExportFFmpegOPUSOptions::TransferDataToWindow() +{ + return true; +} + +/// +/// +bool ExportFFmpegOPUSOptions::TransferDataFromWindow() +{ + ShuttleGui S(this, eIsSavingToPrefs); + PopulateOrExchange(S); + + gPrefs->Flush(); + + return true; +} + //---------------------------------------------------------------------------- // ExportFFmpegWMAOptions Class //---------------------------------------------------------------------------- @@ -1340,11 +1605,12 @@ ChoiceSetting AACProfiles { wxT("/FileFormats/FFmpegAACProfile"), /// List of export types ExposedFormat ExportFFmpegOptions::fmts[] = { - {FMT_M4A, wxT("M4A"), wxT("m4a"), wxT("ipod"), 48, AV_CANMETA, true, XO("M4A (AAC) Files (FFmpeg)"), AV_CODEC_ID_AAC, true}, - {FMT_AC3, wxT("AC3"), wxT("ac3"), wxT("ac3"), 7, AV_VERSION_INT(0,0,0), false, XO("AC3 Files (FFmpeg)"), AV_CODEC_ID_AC3, true}, - {FMT_AMRNB, wxT("AMRNB"), wxT("amr"), wxT("amr"), 1, AV_VERSION_INT(0,0,0), false, XO("AMR (narrow band) Files (FFmpeg)"), AV_CODEC_ID_AMR_NB, true}, - {FMT_WMA2, wxT("WMA"), wxT("wma"), wxT("asf"), 2, AV_VERSION_INT(52,53,0), false, XO("WMA (version 2) Files (FFmpeg)"), AV_CODEC_ID_WMAV2, true}, - {FMT_OTHER, wxT("FFMPEG"), wxT(""), wxT(""), 255, AV_CANMETA, true, XO("Custom FFmpeg Export"), AV_CODEC_ID_NONE, true} + {FMT_M4A, wxT("M4A"), wxT("m4a"), wxT("ipod"), 48, AV_CANMETA, true, XO("M4A (AAC) Files (FFmpeg)"), AV_CODEC_ID_AAC, true}, + {FMT_AC3, wxT("AC3"), wxT("ac3"), wxT("ac3"), 7, AV_VERSION_INT(0,0,0), false, XO("AC3 Files (FFmpeg)"), AV_CODEC_ID_AC3, true}, + {FMT_AMRNB, wxT("AMRNB"), wxT("amr"), wxT("amr"), 1, AV_VERSION_INT(0,0,0), false, XO("AMR (narrow band) Files (FFmpeg)"), AV_CODEC_ID_AMR_NB, true}, + {FMT_OPUS, wxT("OPUS"), wxT("opus"), wxT("opus"), 255, AV_CANMETA, true, XO("OPUS (OggOpus) Files (FFmpeg)"), AV_CODEC_ID_OPUS, true}, + {FMT_WMA2, wxT("WMA"), wxT("wma"), wxT("asf"), 2, AV_VERSION_INT(52,53,0), false, XO("WMA (version 2) Files (FFmpeg)"), AV_CODEC_ID_WMAV2, true}, + {FMT_OTHER, wxT("FFMPEG"), wxT(""), wxT(""), 255, AV_CANMETA, true, XO("Custom FFmpeg Export"), AV_CODEC_ID_NONE, true} }; /// Sample rates supported by AAC encoder (must end with zero-element) diff --git a/src/export/ExportFFmpegDialogs.h b/src/export/ExportFFmpegDialogs.h index b5cbbb189..956f2f5a7 100644 --- a/src/export/ExportFFmpegDialogs.h +++ b/src/export/ExportFFmpegDialogs.h @@ -36,6 +36,7 @@ enum FFmpegExposedFormat FMT_M4A, FMT_AC3, FMT_AMRNB, + FMT_OPUS, FMT_WMA2, FMT_OTHER, FMT_LAST @@ -122,6 +123,40 @@ private: int mBitRateFromChoice; }; +class ExportFFmpegOPUSOptions final : public wxPanelWrapper +{ +public: + + ExportFFmpegOPUSOptions(wxWindow *parent, int format); + ~ExportFFmpegOPUSOptions(); + + void PopulateOrExchange(ShuttleGui & S); + bool TransferDataToWindow() override; + bool TransferDataFromWindow() override; + + static const int iOPUSSampleRates[]; + +private: + + wxSlider *mBitRateSlider; + int mBitRateFromSlider; + + wxChoice *mVbrChoice; + int mVbrFromChoice; + + wxSlider *mComplexitySlider; + int mComplexityFromSlider; + + wxChoice *mFramesizeChoice; + int mFramesizeFromChoice; + + wxChoice *mApplicationChoice; + int mApplicationFromChoice; + + wxChoice *mCuttoffChoice; + int mCutoffFromChoice; +}; + class ExportFFmpegWMAOptions final : public wxPanelWrapper { public: