20#include <opus/opus_multistream.h>
47 return XO(
"no error");
49 return XO(
"invalid argument");
50 case OPUS_BUFFER_TOO_SMALL:
51 return XO(
"buffer too small");
52 case OPUS_INTERNAL_ERROR:
53 return XO(
"internal error");
54 case OPUS_INVALID_PACKET:
55 return XO(
"invalid packet");
56 case OPUS_UNIMPLEMENTED:
57 return XO(
"not implemented");
58 case OPUS_INVALID_STATE:
59 return XO(
"invalid state");
61 return XO(
"memory allocation has failed");
63 return XO(
"Unknown error");
82 return XO(
"%d kbps").Format(n);
105const std::initializer_list<PlainExportOptionsEditor::OptionDesc>
OPUSOptions {
147 },
wxT(
"/FileFormats/OPUS/Bitrate")
155 },
wxT(
"/FileFormats/OPUS/Quality")
178 },
wxT(
"/FileFormats/OPUS/FrameDuration")
186 {
XO(
"Off"),
XO(
"On"),
XO(
"Constrained") }
187 },
wxT(
"/FileFormats/OPUS/VbrMode")
192 OPUS_APPLICATION_AUDIO,
194 { OPUS_APPLICATION_VOIP, OPUS_APPLICATION_AUDIO, OPUS_APPLICATION_RESTRICTED_LOWDELAY },
195 {
XO(
"Speech"),
XO(
"Audio"),
XO(
"Low Delay") }
196 },
wxT(
"/FileFormats/OPUS/Application")
205 OPUS_BANDWIDTH_NARROWBAND,
206 OPUS_BANDWIDTH_MEDIUMBAND,
207 OPUS_BANDWIDTH_WIDEBAND,
208 OPUS_BANDWIDTH_SUPERWIDEBAND,
209 OPUS_BANDWIDTH_FULLBAND,
216 XO(
"Super Wideband"),
219 },
wxT(
"/FileFormats/OPUS/Cutoff")
238 using byte_type = std::remove_pointer_t<
decltype(ogg_packet::packet)>;
244 packet.packetno = packetNo;
279 void Write(
const void* data,
const long length)
281 const auto nextPos =
packet.bytes + length;
283 if (nextPos >
buffer.size())
289 XO(
"Buffer overflow in OGG packet"), OPUS_BUFFER_TOO_SMALL);
293 reinterpret_cast<const byte_type*
>(data),
294 reinterpret_cast<const byte_type*
>(data) + length,
300 template<
typename IntType>
303 static_assert(std::is_integral_v<IntType>);
304 if constexpr (
sizeof (IntType) == 1)
312 Write(&value,
sizeof (IntType));
317 Write(&swapped,
sizeof (IntType));
353 struct OpusState final
358 opus_multistream_encoder_destroy(
encoder);
373 struct OggState final
384 std::mt19937 gen(std::time(
nullptr));
385 ogg_stream_init(&
stream, gen());
388 void PacketIn(
const OggPacket& packet)
390 ogg_stream_packetin(&
stream,
392 const_cast<ogg_packet*
>(&packet.packet));
395 void WriteOut(wxFile& outputStream)
399 while (ogg_stream_pageout(&
stream, &page))
400 WritePage(outputStream, page);
403 void Flush(wxFile& outputStream)
407 while (ogg_stream_flush(&
stream, &page))
408 WritePage(outputStream, page);
416 void WritePage(wxFile& outputStream,
const ogg_page& page)
419 outputStream.Write(page.header, page.header_len) !=
423 if (outputStream.Write(page.body, page.body_len) != page.body_len)
436 static const int32_t multipliers[] = {
437 25, 50, 100, 200, 400, 600,
442 for (
auto multiplier : multipliers)
460 double t0,
double t1,
bool selectedOnly,
463 const Tags* tags)
override;
478 std::vector<std::string>
GetMimeTypes(
int)
const override;
480 std::unique_ptr<ExportOptionsEditor>
496 wxT(
"Opus"),
XO(
"Opus Files"), {
wxT(
"opus") }, 255,
true
502 return {
"audio/opus" };
505std::unique_ptr<ExportOptionsEditor>
508 return std::make_unique<PlainExportOptionsEditor>(
516 return std::make_unique<OpusExportProcessor>();
522 const auto headerSize =
537 (
context.opus.channelMapping == 0 ? 0 :
546 OggPacket headerPacket(0, headerSize,
false);
550 headerPacket.
Write(
"OpusHead", 8);
551 headerPacket.
Write<uint8_t>(1);
557 headerPacket.
Write<uint16_t>(0);
560 if (
context.opus.channelMapping > 0)
565 for (
int i = 0; i <
context.numChannels; ++i)
570 assert(headerPacket.
packet.bytes == headerSize);
572 context.ogg.PacketIn(headerPacket);
580 commentsPacket.
Write(
"OpusTags", 8);
582 const std::string_view vendor { opus_get_version_string() };
584 commentsPacket.Write<uint32_t>(vendor.size());
585 commentsPacket.Write(vendor.data(), vendor.size());
587 commentsPacket.Write<uint32_t>(
context.metadata->Count());
589 for (
const auto& pair :
context.metadata->GetRange())
591 const auto key = pair.first ==
TAG_YEAR ? std::string(
"DATE") :
596 commentsPacket.Write<uint32_t>(
key.size() + value.size() + 1);
597 commentsPacket.Write(
key.data(),
key.size());
598 commentsPacket.Write(
"=", 1);
599 commentsPacket.Write(value.data(), value.size());
602 context.ogg.PacketIn(commentsPacket);
615 const Tags* metadata)
631 const auto bitRate = ExportPluginHelpers::GetParameterValue<int>(
633 const auto vbrMode = ExportPluginHelpers::GetParameterValue<int>(
635 const int complexity = ExportPluginHelpers::GetParameterValue<int>(
637 const int frameMultiplier = ExportPluginHelpers::GetParameterValue<int>(
639 const int application = ExportPluginHelpers::GetParameterValue<int>(
641 const int cutoff = ExportPluginHelpers::GetParameterValue<int>(
645 context.opus.frameSize = frameMultiplier *
context.sampleRate / 10000;
647 context.status = selectionOnly ?
XO(
"Exporting selected audio as Opus") :
648 XO(
"Exporting the audio as Opus");
655 context.opus.channelMapping = 0;
661 context.opus.encoder = opus_multistream_encoder_create(
663 context.opus.nbCoupled,
context.opus.streamMap, application, &error);
671 context.opus.encoder = opus_multistream_surround_encoder_create(
674 context.opus.streamMap, application, &error);
682 if (error != OPUS_OK)
685 error = opus_multistream_encoder_ctl(
686 context.opus.encoder, OPUS_SET_BITRATE(bitRate));
688 if (error != OPUS_OK)
691 error = opus_multistream_encoder_ctl(
692 context.opus.encoder, OPUS_SET_COMPLEXITY(complexity));
694 if (error != OPUS_OK)
697 error = opus_multistream_encoder_ctl(
698 context.opus.encoder, OPUS_SET_BANDWIDTH(cutoff));
700 if (error != OPUS_OK)
703 error = opus_multistream_encoder_ctl(
706 if (error != OPUS_OK)
711 error = opus_multistream_encoder_ctl(
712 context.opus.encoder, OPUS_SET_VBR_CONSTRAINT(1));
714 if (error != OPUS_OK)
721 error = opus_multistream_encoder_ctl(
722 context.opus.encoder, OPUS_GET_LOOKAHEAD(&lookahead));
724 if (error != OPUS_OK)
728 const auto calculatedPreskip = lookahead *
context.opus.sampleRateFactor;
730 calculatedPreskip < 0 ||
731 calculatedPreskip >= std::numeric_limits<uint16_t>::max())
732 FailExport(
XO(
"Failed to calculate correct preskip"), OPUS_BAD_ARG);
734 context.opus.preskip = uint16_t(calculatedPreskip);
739 context.ogg.audioStreamPacket.Resize(
753 context.metadata = std::make_unique<Tags>(
771 int64_t granulePos = 0;
773 int32_t latencyLeft =
context.opus.preskip;
777 auto samplesThisRun =
context.mixer->Process();
779 if (samplesThisRun == 0)
782 auto mixedAudioBuffer =
783 reinterpret_cast<const float*
>(
context.mixer->GetBuffer());
788 if (samplesThisRun < bestFrameSize)
794 mixedAudioBuffer, mixedAudioBuffer + samplesThisRun *
context.numChannels,
798 context.encodeBuffer.begin() + samplesThisRun *
context.numChannels,
799 context.encodeBuffer.begin() + bestFrameSize *
context.numChannels,
802 mixedAudioBuffer =
context.encodeBuffer.data();
804 auto zeroesCount = bestFrameSize - int32_t(samplesThisRun);
806 if (zeroesCount < latencyLeft)
807 samplesThisRun += zeroesCount;
809 samplesThisRun += latencyLeft;
813 latencyLeft = std::max(0, latencyLeft - zeroesCount);
816 auto result = opus_multistream_encode_float(
817 context.opus.encoder, mixedAudioBuffer, bestFrameSize,
818 context.ogg.audioStreamPacket.GetBuffer(),
819 context.ogg.audioStreamPacket.GetBufferSize());
822 FailExport(
XO(
"Failed to encode input buffer"), result);
825 granulePos += samplesThisRun *
context.opus.sampleRateFactor;
827 context.ogg.audioStreamPacket.packet.bytes = result;
828 context.ogg.audioStreamPacket.packet.granulepos = granulePos;
830 if (latencyLeft == 0)
831 context.ogg.audioStreamPacket.MarkEOS();
836 context.ogg.audioStreamPacket.packet.packetno++;
844 while (latencyLeft > 0)
856 auto result = opus_multistream_encode_float(
859 context.ogg.audioStreamPacket.GetBufferSize());
862 FailExport(
XO(
"Failed to encode input buffer"), result);
864 granulePos += samplesOut *
context.opus.sampleRateFactor;
866 context.ogg.audioStreamPacket.packet.bytes = result;
867 context.ogg.audioStreamPacket.packet.granulepos = granulePos;
869 if (latencyLeft == samplesOut)
870 context.ogg.audioStreamPacket.MarkEOS();
875 context.ogg.audioStreamPacket.packet.packetno++;
877 latencyLeft -= samplesOut;
890 []{
return std::make_unique< ExportOpus >(); }
Declare functions to perform UTF-8 to std::wstring conversions.
TranslatableString n_kbps(int n)
static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin
constexpr IntType SwapIntBytes(IntType value) noexcept
Swap bytes in an integer.
bool IsLittleEndian() noexcept
Check that machine is little-endian.
declares abstract base class Track, TrackList, and iterators over TrackList
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Listener object that is used to report on option changes.
std::vector< int > SampleRateList
FormatInfo GetFormatInfo(int) const override
Returns FormatInfo structure for given index if it's valid, or a default one. FormatInfo::format isn'...
std::vector< std::string > GetMimeTypes(int) const override
std::unique_ptr< ExportOptionsEditor > CreateOptionsEditor(int, ExportOptionsEditor::Listener *) const override
Creates format-dependent options editor, that is used to create a valid set of parameters to be used ...
std::unique_ptr< ExportProcessor > CreateProcessor(int format) const override
int GetFormatCount() const override
static ExportResult UpdateProgress(ExportProcessorDelegate &delegate, Mixer &mixer, double t0, double t1)
Sends progress update to delegate and retrieves state update from it. Typically used inside each expo...
static std::unique_ptr< Mixer > CreateMixer(const AudacityProject &project, bool selectionOnly, double startTime, double stopTime, unsigned numOutChannels, size_t outBufferSize, bool outInterleaved, double outRate, sampleFormat outFormat, MixerOptions::Downmix *mixerSpec)
virtual void SetStatusString(const TranslatableString &str)=0
std::vector< std::tuple< ExportOptionID, ExportValue > > Parameters
A matrix of booleans, one row per input channel, column per output.
OggPacket audioStreamPacket
std::unique_ptr< Tags > metadata
struct OpusExportProcessor::@180::OggState ogg
bool Initialize(AudacityProject &project, const Parameters ¶meters, const wxFileNameWrapper &filename, double t0, double t1, bool selectedOnly, double sampleRate, unsigned channels, MixerOptions::Downmix *mixerSpec, const Tags *tags) override
Called before start processing.
TranslatableString status
std::unique_ptr< Mixer > mixer
struct OpusExportProcessor::@180::OpusState opus
std::vector< float > encodeBuffer
int32_t GetBestFrameSize(int32_t samplesCount) const noexcept
ExportResult Process(ExportProcessorDelegate &delegate) override
struct OpusExportProcessor::@180 context
Holds a msgid for the translation catalog; may also bind format arguments.
constexpr auto sampleRate
void FailExport(const TranslatableString &title, int errorCode=0)
@ OPUSOptionIDApplication
@ OPUSOptionIDFrameDuration
TranslatableString GetOpusEncErrorString(int error)
const std::initializer_list< PlainExportOptionsEditor::OptionDesc > OPUSOptions
bool IsValidSampleRate(int sampleRate) noexcept
constexpr int supportedSampleRates[]
std::string ToUTF8(const std::wstring &wstr)
void copy(const T *src, T *dst, int32_t n)
@ TypeEnum
List/enum option. values holds items, and names text to be displayed.
@ TypeRange
Range option. values holds [min, max].
OggPacket(int64_t packetNo, bool resizable)
std::remove_pointer_t< decltype(ogg_packet::packet)> byte_type
size_t GetBufferSize() const
void Write(const void *data, const long length)
std::vector< byte_type > buffer
void Write(IntType value)
OggPacket(int64_t packetNo)
OggPacket(int64_t packetNo, long size, bool resizable)