From c634eb4054c2e7f530307198d7cc6a20b3666d7d Mon Sep 17 00:00:00 2001 From: Logan Stromberg Date: Mon, 20 May 2024 14:38:38 -0700 Subject: [PATCH] Updating Concentus dependency to speed up Opus decoding (#6757) * Implementing new features in the latest Concentus library - span-in, span-out Opus decoding (so we don't have to make temporary buffer copies), returning a more precise error code from the decoder, and automatically linking the native opus library with P/invoke if supported on the current system * Remove stub log messages and commit package upgrade to 2.1.0 * use more correct disposal pattern * Bump to Concentus 2.1.1 * Bump to Concentus 2.1.2 * Don't bother pulling in native opus binaries from Concentus package (using ExcludeAssets). * Fix opus MS channel count. Explicitly disable native lib probe in OpusCodecFactory. * Bump to package 2.2.0 which has split out the native libs, as suggested. --------- Co-authored-by: Logan Stromberg --- Directory.Packages.props | 4 +- src/Ryujinx.Horizon/Ryujinx.Horizon.csproj | 8 +- .../Sdk/Codec/Detail/HardwareOpusDecoder.cs | 91 +++++++++++++------ 3 files changed, 68 insertions(+), 35 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index d04e237e0..739e66bd0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,7 @@ - + @@ -49,4 +49,4 @@ - + \ No newline at end of file diff --git a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj index d1f572d5c..bf34ddd17 100644 --- a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj +++ b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -16,10 +16,4 @@ - - - - NU1605 - - diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs index 5d2798582..2146362df 100644 --- a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs @@ -14,6 +14,11 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail { partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable { + static HardwareOpusDecoder() + { + OpusCodecFactory.AttemptToUseNativeLibrary = false; + } + [StructLayout(LayoutKind.Sequential)] private struct OpusPacketHeader { @@ -30,60 +35,87 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail } } - private interface IDecoder + private interface IDecoder : IDisposable { int SampleRate { get; } int ChannelsCount { get; } - int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize); + int Decode(ReadOnlySpan inData, Span outPcm, int frameSize); void ResetState(); } private class Decoder : IDecoder { - private readonly OpusDecoder _decoder; + private readonly IOpusDecoder _decoder; public int SampleRate => _decoder.SampleRate; public int ChannelsCount => _decoder.NumChannels; public Decoder(int sampleRate, int channelsCount) { - _decoder = new OpusDecoder(sampleRate, channelsCount); + _decoder = OpusCodecFactory.CreateDecoder(sampleRate, channelsCount); } - public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize) + public int Decode(ReadOnlySpan inData, Span outPcm, int frameSize) { - return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize); + return _decoder.Decode(inData, outPcm, frameSize); } public void ResetState() { _decoder.ResetState(); } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _decoder?.Dispose(); + } + } } private class MultiSampleDecoder : IDecoder { - private readonly OpusMSDecoder _decoder; + private readonly IOpusMultiStreamDecoder _decoder; public int SampleRate => _decoder.SampleRate; - public int ChannelsCount { get; } + public int ChannelsCount => _decoder.NumChannels; public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping) { - ChannelsCount = channelsCount; - _decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); + _decoder = OpusCodecFactory.CreateMultiStreamDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); } - public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize) + public int Decode(ReadOnlySpan inData, Span outPcm, int frameSize) { - return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0); + return _decoder.DecodeMultistream(inData, outPcm, frameSize, false); } public void ResetState() { _decoder.ResetState(); } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _decoder?.Dispose(); + } + } } private readonly IDecoder _decoder; @@ -221,7 +253,8 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail { timeTaken = 0; - Result result = DecodeInterleaved(_decoder, reset, input, out short[] outPcmData, output.Length, out outConsumed, out outSamples); + Span outPcmSpace = MemoryMarshal.Cast(output); + Result result = DecodeInterleaved(_decoder, reset, input, outPcmSpace, output.Length, out outConsumed, out outSamples); if (withPerf) { @@ -229,14 +262,12 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail timeTaken = 0; } - MemoryMarshal.Cast(outPcmData).CopyTo(output[..(outPcmData.Length * sizeof(short))]); - return result; } - private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, byte[] packet) + private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, ReadOnlySpan packet) { - int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate); + int result = OpusPacketInfo.GetNumSamples(packet, decoder.SampleRate); numSamples = result; @@ -256,12 +287,11 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail IDecoder decoder, bool reset, ReadOnlySpan input, - out short[] outPcmData, + Span outPcmData, int outputSize, out int outConsumed, out int outSamples) { - outPcmData = null; outConsumed = 0; outSamples = 0; @@ -281,7 +311,7 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail return CodecResult.InvalidLength; } - byte[] opusData = input.Slice(headerSize, (int)header.Length).ToArray(); + ReadOnlySpan opusData = input.Slice(headerSize, (int)header.Length); Result result = GetPacketNumSamples(decoder, out int numSamples, opusData); @@ -292,8 +322,6 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail return CodecResult.InvalidLength; } - outPcmData = new short[numSamples * decoder.ChannelsCount]; - if (reset) { decoder.ResetState(); @@ -301,13 +329,22 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail try { - outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount); + outSamples = decoder.Decode(opusData, outPcmData, numSamples); outConsumed = (int)totalSize; } - catch (OpusException) + catch (OpusException e) { - // TODO: As OpusException doesn't return the exact error code, this is inaccurate in some cases... - return CodecResult.InvalidLength; + switch (e.OpusErrorCode) + { + case OpusError.OPUS_BUFFER_TOO_SMALL: + return CodecResult.InvalidLength; + case OpusError.OPUS_BAD_ARG: + return CodecResult.OpusBadArg; + case OpusError.OPUS_INVALID_PACKET: + return CodecResult.OpusInvalidPacket; + default: + return CodecResult.InvalidLength; + } } } @@ -324,6 +361,8 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail _workBufferHandle = 0; } + + _decoder?.Dispose(); } }