Audacity 3.2.0
Classes | Public Member Functions | Private Member Functions | Private Attributes | List of all members
OpusExportProcessor Class Referencefinal
Inheritance diagram for OpusExportProcessor:
[legend]
Collaboration diagram for OpusExportProcessor:
[legend]

Classes

struct  OggPacket
 

Public Member Functions

 ~OpusExportProcessor ()
 
bool Initialize (AudacityProject &project, const Parameters &parameters, const wxFileNameWrapper &filename, double t0, double t1, bool selectedOnly, double sampleRate, unsigned channels, MixerOptions::Downmix *mixerSpec, const Tags *tags) override
 Called before start processing. More...
 
ExportResult Process (ExportProcessorDelegate &delegate) override
 
- Public Member Functions inherited from ExportProcessor
 ExportProcessor (const ExportProcessor &)=delete
 
ExportProcessoroperator= (const ExportProcessor &)=delete
 
 ExportProcessor ()=default
 
virtual ~ExportProcessor ()
 
virtual bool Initialize (AudacityProject &project, const Parameters &parameters, const wxFileNameWrapper &filename, double t0, double t1, bool selectedOnly, double rate, unsigned channels, MixerOptions::Downmix *mixerSpec=nullptr, const Tags *tags=nullptr)=0
 Called before start processing. More...
 
virtual ExportResult Process (ExportProcessorDelegate &delegate)=0
 

Private Member Functions

void WriteOpusHeader ()
 
void WriteTags ()
 
int32_t GetBestFrameSize (int32_t samplesCount) const noexcept
 

Private Attributes

struct {
   TranslatableString   status
 
   int32_t   sampleRate {}
 
   double   t0 {}
 
   double   t1 {}
 
   unsigned   numChannels {}
 
   wxFileNameWrapper   fName
 
   wxFile   outFile
 
   std::unique_ptr< Mixer >   mixer
 
   std::unique_ptr< Tags >   metadata
 
   struct OpusState {
      OpusMSEncoder *   encoder {}
 
      int32_t   frameSize {}
 
      int32_t   sampleRateFactor {}
 
      uint16_t   preskip {}
 
      uint8_t   channelMapping {}
 
      uint8_t   nbStreams {}
 
      uint8_t   nbCoupled {}
 
      uint8_t   streamMap [255] {}
 
   }   opus
 
   struct OggState {
      ogg_stream_state   stream
 
      OggPacket   audioStreamPacket
 
   }   ogg
 
   std::vector< float >   encodeBuffer
 
context
 

Additional Inherited Members

- Public Types inherited from ExportProcessor
using Parameters = std::vector< std::tuple< ExportOptionID, ExportValue > >
 

Detailed Description

Definition at line 234 of file ExportOpus.cpp.

Constructor & Destructor Documentation

◆ ~OpusExportProcessor()

OpusExportProcessor::~OpusExportProcessor ( )

Definition at line 606 of file ExportOpus.cpp.

607{
608
609}

Member Function Documentation

◆ GetBestFrameSize()

int32_t OpusExportProcessor::GetBestFrameSize ( int32_t  samplesCount) const
inlineprivatenoexcept

Definition at line 434 of file ExportOpus.cpp.

435 {
436 static const int32_t multipliers[] = {
437 25, 50, 100, 200, 400, 600,
438 };
439
440 const auto sampleRate = context.sampleRate;
441
442 for (auto multiplier : multipliers)
443 {
444 const auto frameSize = multiplier * sampleRate / 10000;
445
446 if (samplesCount <= frameSize)
447 return frameSize;
448 }
449
450 return 60 * sampleRate / 1000;
451 }
struct OpusExportProcessor::@180 context

References context, frameSize, and sampleRate.

Referenced by Process().

Here is the caller graph for this function:

◆ Initialize()

bool OpusExportProcessor::Initialize ( AudacityProject project,
const Parameters parameters,
const wxFileNameWrapper filename,
double  t0,
double  t1,
bool  selectedOnly,
double  rate,
unsigned  channels,
MixerOptions::Downmix mixerSpec,
const Tags tags 
)
overridevirtual

Called before start processing.

Parameters
projectProcessor may access project data, take care to exclude any data race
parametersA format-dependent set of parameters used in exporting
selectedOnlySet to true if all tracks should be mixed, to false if only the selected tracks should be mixed and exported.
tagsA Tags object that will over-ride the one in *project and be used to tag the file that is exported. @retern Implementations may simply return false without any error reporting. This is to temporarily preserve old behavior, which is to be removed in the nearest future.

Implements ExportProcessor.

Definition at line 611 of file ExportOpus.cpp.

616{
617 context.sampleRate = int32_t(sampleRate);
618
619 if (!IsValidSampleRate(context.sampleRate))
620 throw ExportException(XO("Unsupported sample rate").Translation());
621
622 context.t0 = t0;
623 context.t1 = t1;
624 context.numChannels = numChannels;
625 context.fName = fName;
626
627 // Internally the Opus is always in 48k, find out the multiplier for
628 // values, that expect 48k sample rate
629 context.opus.sampleRateFactor = 48000 / context.sampleRate;
630
631 const auto bitRate = ExportPluginHelpers::GetParameterValue<int>(
632 parameters, OPUSOptionIDBitRate, OPUS_AUTO);
633 const auto vbrMode = ExportPluginHelpers::GetParameterValue<int>(
634 parameters, OPUSOptionIDVBRMode, VBRMode::VBR);
635 const int complexity = ExportPluginHelpers::GetParameterValue<int>(
636 parameters, OPUSOptionIDQuality, 10);
637 const int frameMultiplier = ExportPluginHelpers::GetParameterValue<int>(
638 parameters, OPUSOptionIDFrameDuration, 200);
639 const int application = ExportPluginHelpers::GetParameterValue<int>(
640 parameters, OPUSOptionIDApplication, OPUS_APPLICATION_AUDIO);
641 const int cutoff = ExportPluginHelpers::GetParameterValue<int>(
642 parameters, OPUSOptionIDCutoff, OPUS_AUTO);
643
644 // Number of samples per frame per channel
645 context.opus.frameSize = frameMultiplier * context.sampleRate / 10000;
646
647 context.status = selectionOnly ? XO("Exporting selected audio as Opus") :
648 XO("Exporting the audio as Opus");
649
650 // Create opus encoder
651 int error;
652
653 if (numChannels <= 2)
654 {
655 context.opus.channelMapping = 0;
656 context.opus.nbStreams = 1;
657 context.opus.nbCoupled = numChannels - 1;
658 context.opus.streamMap[0] = 0;
659 context.opus.streamMap[1] = 1;
660
661 context.opus.encoder = opus_multistream_encoder_create(
662 sampleRate, numChannels, context.opus.nbStreams,
663 context.opus.nbCoupled, context.opus.streamMap, application, &error);
664 }
665 else
666 {
667 context.opus.channelMapping = numChannels <= 8 ? 1 : 255;
668
669 int nbStreams {}, nbCoupled {};
670
671 context.opus.encoder = opus_multistream_surround_encoder_create(
672 sampleRate, numChannels, context.opus.channelMapping,
674 context.opus.streamMap, application, &error);
675
676 // opus_multistream_surround_encoder_create is expected to fill
677 // stream count with values in [0, 255]
678 context.opus.nbStreams = uint8_t(nbStreams);
679 context.opus.nbCoupled = uint8_t(nbCoupled);
680 }
681
682 if (error != OPUS_OK)
683 FailExport(XO("Unable to create Opus encoder"), error);
684
685 error = opus_multistream_encoder_ctl(
686 context.opus.encoder, OPUS_SET_BITRATE(bitRate));
687
688 if (error != OPUS_OK)
689 FailExport(XO("Unable to set bitrate"), error);
690
691 error = opus_multistream_encoder_ctl(
692 context.opus.encoder, OPUS_SET_COMPLEXITY(complexity));
693
694 if (error != OPUS_OK)
695 FailExport(XO("Unable to set complexity"), error);
696
697 error = opus_multistream_encoder_ctl(
698 context.opus.encoder, OPUS_SET_BANDWIDTH(cutoff));
699
700 if (error != OPUS_OK)
701 FailExport(XO("Unable to set bandwidth"), error);
702
703 error = opus_multistream_encoder_ctl(
704 context.opus.encoder, OPUS_SET_VBR(vbrMode == VBRMode::CBR ? 0 : 1));
705
706 if (error != OPUS_OK)
707 FailExport(XO("Unable to set VBR mode"), error);
708
709 if (vbrMode == VBRMode::CVBR)
710 {
711 error = opus_multistream_encoder_ctl(
712 context.opus.encoder, OPUS_SET_VBR_CONSTRAINT(1));
713
714 if (error != OPUS_OK)
715 FailExport(XO("Unable to set CVBR mode"), error);
716 }
717
718 // Calculate the encoder latency. This value is needed in header
719 // and to flush the encoder
720 int lookahead {};
721 error = opus_multistream_encoder_ctl(
722 context.opus.encoder, OPUS_GET_LOOKAHEAD(&lookahead));
723
724 if (error != OPUS_OK)
725 FailExport(XO("Unable to get lookahead"), error);
726
727 // Latency is always in 48k encoded samples
728 const auto calculatedPreskip = lookahead * context.opus.sampleRateFactor;
729 if (
730 calculatedPreskip < 0 ||
731 calculatedPreskip >= std::numeric_limits<uint16_t>::max())
732 FailExport(XO("Failed to calculate correct preskip"), OPUS_BAD_ARG);
733 // It is safe to cast to uint16_t here
734 context.opus.preskip = uint16_t(calculatedPreskip);
735
736 // Resize the audio packet so it can contain all the raw data.
737 // This is overkill, but should be enough to hold all the data from
738 // the encode float
739 context.ogg.audioStreamPacket.Resize(
740 context.opus.frameSize * sizeof(float) * numChannels);
741
742
743 // Try to open the file for writing
744 if (
745 !context.outFile.Create(fName.GetFullPath(), true) ||
746 !context.outFile.IsOpened())
747 {
748 throw ExportException(_("Unable to open target file for writing"));
749 }
750
752
753 context.metadata = std::make_unique<Tags>(
754 metadata == nullptr ? Tags::Get(project) : *metadata);
755
756 WriteTags();
757
759 project, selectionOnly, t0, t1, numChannels, context.opus.frameSize, true,
760 sampleRate, floatSample, mixerSpec);
761
762 return true;
763}
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
const auto project
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)
std::unique_ptr< Tags > metadata
Definition: ExportOpus.cpp:350
wxFileNameWrapper fName
Definition: ExportOpus.cpp:347
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
void FailExport(const TranslatableString &title, int errorCode=0)
Definition: ExportOpus.cpp:67
bool IsValidSampleRate(int sampleRate) noexcept
Definition: ExportOpus.cpp:225

References _, anonymous_namespace{ExportOpus.cpp}::VBRMode::CBR, context, ExportPluginHelpers::CreateMixer(), anonymous_namespace{ExportOpus.cpp}::VBRMode::CVBR, anonymous_namespace{ExportOpus.cpp}::FailExport(), floatSample, fName, Tags::Get(), anonymous_namespace{ExportOpus.cpp}::IsValidSampleRate(), metadata, nbCoupled, nbStreams, numChannels, anonymous_namespace{ExportOpus.cpp}::OPUSOptionIDApplication, anonymous_namespace{ExportOpus.cpp}::OPUSOptionIDBitRate, anonymous_namespace{ExportOpus.cpp}::OPUSOptionIDCutoff, anonymous_namespace{ExportOpus.cpp}::OPUSOptionIDFrameDuration, anonymous_namespace{ExportOpus.cpp}::OPUSOptionIDQuality, anonymous_namespace{ExportOpus.cpp}::OPUSOptionIDVBRMode, project, sampleRate, t0, t1, anonymous_namespace{ExportOpus.cpp}::VBRMode::VBR, WriteOpusHeader(), WriteTags(), and XO().

Here is the call graph for this function:

◆ Process()

ExportResult OpusExportProcessor::Process ( ExportProcessorDelegate delegate)
overridevirtual

Implements ExportProcessor.

Definition at line 765 of file ExportOpus.cpp.

766{
767 delegate.SetStatusString(context.status);
768
769 auto exportResult = ExportResult::Success;
770
771 int64_t granulePos = 0;
772
773 int32_t latencyLeft = context.opus.preskip;
774
775 while (exportResult == ExportResult::Success)
776 {
777 auto samplesThisRun = context.mixer->Process();
778
779 if (samplesThisRun == 0)
780 break;
781
782 auto mixedAudioBuffer =
783 reinterpret_cast<const float*>(context.mixer->GetBuffer());
784
785 // bestFrameSize <= context.opus.frameSize by design
786 auto bestFrameSize = GetBestFrameSize(samplesThisRun);
787
788 if (samplesThisRun < bestFrameSize)
789 {
790 // Opus expects that the full frame is passed to the encoder, fill missing data with zeroes
791 context.encodeBuffer.resize(bestFrameSize * context.numChannels);
792
793 std::copy(
794 mixedAudioBuffer, mixedAudioBuffer + samplesThisRun * context.numChannels,
795 context.encodeBuffer.begin());
796
797 std::fill(
798 context.encodeBuffer.begin() + samplesThisRun * context.numChannels,
799 context.encodeBuffer.begin() + bestFrameSize * context.numChannels,
800 0);
801
802 mixedAudioBuffer = context.encodeBuffer.data();
803
804 auto zeroesCount = bestFrameSize - int32_t(samplesThisRun);
805
806 if (zeroesCount < latencyLeft)
807 samplesThisRun += zeroesCount;
808 else
809 samplesThisRun += latencyLeft;
810
811 // Reduce the latency by the number of zeroes pushed (potentially
812 // removing the need to flush the encoder)
813 latencyLeft = std::max(0, latencyLeft - zeroesCount);
814 }
815
816 auto result = opus_multistream_encode_float(
817 context.opus.encoder, mixedAudioBuffer, bestFrameSize,
818 context.ogg.audioStreamPacket.GetBuffer(),
819 context.ogg.audioStreamPacket.GetBufferSize());
820
821 if (result < 0)
822 FailExport(XO("Failed to encode input buffer"), result);
823
824 // granulePos is the index of the last real sample in the packet at 48k rate
825 granulePos += samplesThisRun * context.opus.sampleRateFactor;
826
827 context.ogg.audioStreamPacket.packet.bytes = result;
828 context.ogg.audioStreamPacket.packet.granulepos = granulePos;
829
830 if (latencyLeft == 0)
831 context.ogg.audioStreamPacket.MarkEOS();
832
833 context.ogg.PacketIn(context.ogg.audioStreamPacket);
834 context.ogg.WriteOut(context.outFile);
835
836 context.ogg.audioStreamPacket.packet.packetno++;
837
839 delegate, *context.mixer, context.t0, context.t1);
840 }
841
842 // Flush the encoder
843
844 while (latencyLeft > 0)
845 {
846 auto frameSize = GetBestFrameSize(latencyLeft);
847
848 context.encodeBuffer.resize(frameSize * context.numChannels);
849
850 std::fill(
851 context.encodeBuffer.begin(),
852 context.encodeBuffer.begin() + frameSize * context.numChannels, 0);
853
854 auto samplesOut = std::min(latencyLeft, frameSize);
855
856 auto result = opus_multistream_encode_float(
857 context.opus.encoder, context.encodeBuffer.data(),
858 frameSize, context.ogg.audioStreamPacket.GetBuffer(),
859 context.ogg.audioStreamPacket.GetBufferSize());
860
861 if (result < 0)
862 FailExport(XO("Failed to encode input buffer"), result);
863
864 granulePos += samplesOut * context.opus.sampleRateFactor;
865
866 context.ogg.audioStreamPacket.packet.bytes = result;
867 context.ogg.audioStreamPacket.packet.granulepos = granulePos;
868
869 if (latencyLeft == samplesOut)
870 context.ogg.audioStreamPacket.MarkEOS();
871
872 context.ogg.PacketIn(context.ogg.audioStreamPacket);
873 context.ogg.WriteOut(context.outFile);
874
875 context.ogg.audioStreamPacket.packet.packetno++;
876
877 latencyLeft -= samplesOut;
878 }
879
880 context.ogg.Flush(context.outFile);
881
882 if (!context.outFile.Close())
883 return ExportResult::Error;
884
885 return exportResult;
886}
int min(int a, int b)
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...
virtual void SetStatusString(const TranslatableString &str)=0
int32_t GetBestFrameSize(int32_t samplesCount) const noexcept
Definition: ExportOpus.cpp:434
void copy(const T *src, T *dst, int32_t n)
Definition: VectorOps.h:40

References context, staffpad::vo::copy(), Error, anonymous_namespace{ExportOpus.cpp}::FailExport(), frameSize, GetBestFrameSize(), min(), ExportProcessorDelegate::SetStatusString(), Success, ExportPluginHelpers::UpdateProgress(), and XO().

Here is the call graph for this function:

◆ WriteOpusHeader()

void OpusExportProcessor::WriteOpusHeader ( )
private

Definition at line 520 of file ExportOpus.cpp.

521{
522 const auto headerSize =
523 // "OpusHead"
524 8 +
525 // Version number (always 1)
526 1 +
527 // Channels count
528 1 +
529 // Preskip
530 2 +
531 // Input sample rate
532 4 +
533 // Output gain (always 0)
534 2 +
535 // Channel mapping
536 1 +
537 (context.opus.channelMapping == 0 ? 0 :
538 (
539 // Stream count
540 1 +
541 // Two channel stream count
542 1 +
543 // Channel mapping
544 context.numChannels));
545
546 OggPacket headerPacket(0, headerSize, false);
547 // Header must have beginning-of-stream marker
548 headerPacket.MarkBOS();
549
550 headerPacket.Write("OpusHead", 8);
551 headerPacket.Write<uint8_t>(1);
552 headerPacket.Write<uint8_t>(context.numChannels);
553 headerPacket.Write(context.opus.preskip);
554 // Should we put the project sample rate here?
555 headerPacket.Write(context.sampleRate);
556 // Opus docs recommend encoders to use 0 as a gain
557 headerPacket.Write<uint16_t>(0);
558 headerPacket.Write(context.opus.channelMapping);
559
560 if (context.opus.channelMapping > 0)
561 {
562 headerPacket.Write(context.opus.nbStreams);
563 headerPacket.Write(context.opus.nbCoupled);
564
565 for (int i = 0; i < context.numChannels; ++i)
566 headerPacket.Write<uint8_t>(context.opus.streamMap[i]);
567 }
568
569 // This is guaranteed by the way we calculate the header size
570 assert(headerPacket.packet.bytes == headerSize);
571
572 context.ogg.PacketIn(headerPacket);
573 context.ogg.Flush(context.outFile);
574}

References context, OpusExportProcessor::OggPacket::MarkBOS(), OpusExportProcessor::OggPacket::packet, and OpusExportProcessor::OggPacket::Write().

Referenced by Initialize().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ WriteTags()

void OpusExportProcessor::WriteTags ( )
private

Definition at line 576 of file ExportOpus.cpp.

577{
578 OggPacket commentsPacket { 1, true };
579
580 commentsPacket.Write("OpusTags", 8);
581
582 const std::string_view vendor { opus_get_version_string() };
583
584 commentsPacket.Write<uint32_t>(vendor.size());
585 commentsPacket.Write(vendor.data(), vendor.size());
586
587 commentsPacket.Write<uint32_t>(context.metadata->Count());
588
589 for (const auto& pair : context.metadata->GetRange())
590 {
591 const auto key = pair.first == TAG_YEAR ? std::string("DATE") :
592 audacity::ToUTF8(pair.first);
593
594 const auto value = audacity::ToUTF8(pair.second);
595
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());
600 }
601
602 context.ogg.PacketIn(commentsPacket);
603 context.ogg.Flush(context.outFile);
604}
static const AudacityProject::AttachedObjects::RegisteredFactory key
#define TAG_YEAR
Definition: Tags.h:62
std::string ToUTF8(const std::wstring &wstr)

References context, key, TAG_YEAR, audacity::ToUTF8(), and OpusExportProcessor::OggPacket::Write().

Referenced by Initialize().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Data Documentation

◆ audioStreamPacket

OggPacket OpusExportProcessor::audioStreamPacket

Definition at line 413 of file ExportOpus.cpp.

◆ channelMapping

uint8_t OpusExportProcessor::channelMapping {}

Definition at line 366 of file ExportOpus.cpp.

◆ 

struct { ... } OpusExportProcessor::context

◆ encodeBuffer

std::vector<float> OpusExportProcessor::encodeBuffer

Definition at line 428 of file ExportOpus.cpp.

◆ encoder

OpusMSEncoder* OpusExportProcessor::encoder {}

Definition at line 361 of file ExportOpus.cpp.

◆ fName

wxFileNameWrapper OpusExportProcessor::fName

Definition at line 347 of file ExportOpus.cpp.

Referenced by Initialize().

◆ frameSize

int32_t OpusExportProcessor::frameSize {}

Definition at line 363 of file ExportOpus.cpp.

Referenced by GetBestFrameSize(), and Process().

◆ metadata

std::unique_ptr<Tags> OpusExportProcessor::metadata

Definition at line 350 of file ExportOpus.cpp.

Referenced by Initialize().

◆ mixer

std::unique_ptr<Mixer> OpusExportProcessor::mixer

Definition at line 349 of file ExportOpus.cpp.

◆ nbCoupled

uint8_t OpusExportProcessor::nbCoupled {}

Definition at line 368 of file ExportOpus.cpp.

Referenced by Initialize().

◆ nbStreams

uint8_t OpusExportProcessor::nbStreams {}

Definition at line 367 of file ExportOpus.cpp.

Referenced by Initialize().

◆ numChannels

unsigned OpusExportProcessor::numChannels {}

Definition at line 346 of file ExportOpus.cpp.

Referenced by Initialize().

◆ 

struct { ... } ::OggState OpusExportProcessor::ogg

◆ 

struct { ... } ::OpusState OpusExportProcessor::opus

◆ outFile

wxFile OpusExportProcessor::outFile

Definition at line 348 of file ExportOpus.cpp.

◆ preskip

uint16_t OpusExportProcessor::preskip {}

Definition at line 365 of file ExportOpus.cpp.

◆ sampleRate

int32_t OpusExportProcessor::sampleRate {}

Definition at line 342 of file ExportOpus.cpp.

Referenced by GetBestFrameSize(), and Initialize().

◆ sampleRateFactor

int32_t OpusExportProcessor::sampleRateFactor {}

Definition at line 364 of file ExportOpus.cpp.

◆ status

TranslatableString OpusExportProcessor::status

Definition at line 341 of file ExportOpus.cpp.

◆ stream

ogg_stream_state OpusExportProcessor::stream

Definition at line 411 of file ExportOpus.cpp.

◆ streamMap

uint8_t OpusExportProcessor::streamMap[255] {}

Definition at line 369 of file ExportOpus.cpp.

◆ t0

double OpusExportProcessor::t0 {}

Definition at line 344 of file ExportOpus.cpp.

Referenced by Initialize().

◆ t1

double OpusExportProcessor::t1 {}

Definition at line 345 of file ExportOpus.cpp.

Referenced by Initialize().


The documentation for this class was generated from the following file: