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

This commit is contained in:
Logan Stromberg 2024-04-30 10:29:32 -07:00
commit 9d808e4cb4

View file

@ -30,41 +30,47 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
} }
} }
private interface IDecoder private interface IDecoder : IDisposable
{ {
int SampleRate { get; } int SampleRate { get; }
int ChannelsCount { get; } int ChannelsCount { get; }
int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize); int Decode(ReadOnlySpan<byte> inData, Span<short> outPcm, int frameSize);
void ResetState(); void ResetState();
} }
private class Decoder : IDecoder private class Decoder : IDecoder
{ {
private readonly OpusDecoder _decoder; private readonly IOpusDecoder _decoder;
public int SampleRate => _decoder.SampleRate; public int SampleRate => _decoder.SampleRate;
public int ChannelsCount => _decoder.NumChannels; public int ChannelsCount => _decoder.NumChannels;
public Decoder(int sampleRate, int channelsCount) public Decoder(int sampleRate, int channelsCount)
{ {
_decoder = new OpusDecoder(sampleRate, channelsCount); Logger.Warning?.Print(LogClass.Audio, "Creating an Opus decoder!");
_decoder = OpusCodecFactory.CreateDecoder(sampleRate, channelsCount, Console.Out);
} }
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize) public int Decode(ReadOnlySpan<byte> inData, Span<short> outPcm, int frameSize)
{ {
return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize); return _decoder.Decode(inData, outPcm, frameSize);
} }
public void ResetState() public void ResetState()
{ {
_decoder.ResetState(); _decoder.ResetState();
} }
public void Dispose()
{
_decoder?.Dispose();
}
} }
private class MultiSampleDecoder : IDecoder private class MultiSampleDecoder : IDecoder
{ {
private readonly OpusMSDecoder _decoder; private readonly IOpusMultiStreamDecoder _decoder;
public int SampleRate => _decoder.SampleRate; public int SampleRate => _decoder.SampleRate;
public int ChannelsCount { get; } public int ChannelsCount { get; }
@ -72,18 +78,24 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping) public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
{ {
ChannelsCount = channelsCount; ChannelsCount = channelsCount;
_decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); Logger.Warning?.Print(LogClass.Audio, "Creating an Opus multistream decoder!");
_decoder = OpusCodecFactory.CreateMultiStreamDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping, Console.Out);
} }
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize) public int Decode(ReadOnlySpan<byte> inData, Span<short> outPcm, int frameSize)
{ {
return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0); return _decoder.DecodeMultistream(inData, outPcm, frameSize, false);
} }
public void ResetState() public void ResetState()
{ {
_decoder.ResetState(); _decoder.ResetState();
} }
public void Dispose()
{
_decoder?.Dispose();
}
} }
private readonly IDecoder _decoder; private readonly IDecoder _decoder;
@ -221,7 +233,8 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
{ {
timeTaken = 0; timeTaken = 0;
Result result = DecodeInterleaved(_decoder, reset, input, out short[] outPcmData, output.Length, out outConsumed, out outSamples); Span<short> outPcmSpace = MemoryMarshal.Cast<byte, short>(output);
Result result = DecodeInterleaved(_decoder, reset, input, outPcmSpace, output.Length, out outConsumed, out outSamples);
if (withPerf) if (withPerf)
{ {
@ -229,14 +242,12 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
timeTaken = 0; timeTaken = 0;
} }
MemoryMarshal.Cast<short, byte>(outPcmData).CopyTo(output[..(outPcmData.Length * sizeof(short))]);
return result; return result;
} }
private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, byte[] packet) private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, ReadOnlySpan<byte> packet)
{ {
int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate); int result = OpusPacketInfo.GetNumSamples(packet, decoder.SampleRate);
numSamples = result; numSamples = result;
@ -256,12 +267,11 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
IDecoder decoder, IDecoder decoder,
bool reset, bool reset,
ReadOnlySpan<byte> input, ReadOnlySpan<byte> input,
out short[] outPcmData, Span<short> outPcmData,
int outputSize, int outputSize,
out int outConsumed, out int outConsumed,
out int outSamples) out int outSamples)
{ {
outPcmData = null;
outConsumed = 0; outConsumed = 0;
outSamples = 0; outSamples = 0;
@ -281,7 +291,7 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
return CodecResult.InvalidLength; return CodecResult.InvalidLength;
} }
byte[] opusData = input.Slice(headerSize, (int)header.Length).ToArray(); ReadOnlySpan<byte> opusData = input.Slice(headerSize, (int)header.Length);
Result result = GetPacketNumSamples(decoder, out int numSamples, opusData); Result result = GetPacketNumSamples(decoder, out int numSamples, opusData);
@ -292,8 +302,6 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
return CodecResult.InvalidLength; return CodecResult.InvalidLength;
} }
outPcmData = new short[numSamples * decoder.ChannelsCount];
if (reset) if (reset)
{ {
decoder.ResetState(); decoder.ResetState();
@ -301,13 +309,23 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
try try
{ {
outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount); Logger.Debug?.Print(LogClass.Audio, "Decoding Opus audio!");
outSamples = decoder.Decode(opusData, outPcmData, numSamples);
outConsumed = (int)totalSize; outConsumed = (int)totalSize;
} }
catch (OpusException) catch (OpusException e)
{ {
// TODO: As OpusException doesn't return the exact error code, this is inaccurate in some cases... switch (e.OpusErrorCode)
{
case OpusError.OPUS_BUFFER_TOO_SMALL:
return CodecResult.InvalidLength; 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 +342,8 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
_workBufferHandle = 0; _workBufferHandle = 0;
} }
_decoder?.Dispose();
} }
} }