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::@181 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
758 const auto& tracks = TrackList::Get(project);
759
761 tracks, selectionOnly, t0, t1, numChannels, context.opus.frameSize, true,
762 sampleRate, floatSample, mixerSpec);
763
764 return true;
765}
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
const auto tracks
const auto project
static std::unique_ptr< Mixer > CreateMixer(const TrackList &tracks, 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
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
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(), TrackList::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, tracks, 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 767 of file ExportOpus.cpp.

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