1
0
Fork 0
mirror of https://github.com/Ryujinx/Ryujinx.git synced 2025-03-27 12:34:41 +01:00
Ryujinx/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs
Logan Stromberg c634eb4054
Updating Concentus dependency to speed up Opus decoding ()
* 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 <lostromb@microsoft.com>
2024-05-20 18:38:38 -03:00

375 lines
13 KiB
C#

using Concentus;
using Concentus.Enums;
using Concentus.Structs;
using Ryujinx.Common.Logging;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Codec.Detail
{
partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable
{
static HardwareOpusDecoder()
{
OpusCodecFactory.AttemptToUseNativeLibrary = false;
}
[StructLayout(LayoutKind.Sequential)]
private struct OpusPacketHeader
{
public uint Length;
public uint FinalRange;
public static OpusPacketHeader FromSpan(ReadOnlySpan<byte> data)
{
return new()
{
Length = BinaryPrimitives.ReadUInt32BigEndian(data),
FinalRange = BinaryPrimitives.ReadUInt32BigEndian(data[sizeof(uint)..]),
};
}
}
private interface IDecoder : IDisposable
{
int SampleRate { get; }
int ChannelsCount { get; }
int Decode(ReadOnlySpan<byte> inData, Span<short> outPcm, int frameSize);
void ResetState();
}
private class Decoder : IDecoder
{
private readonly IOpusDecoder _decoder;
public int SampleRate => _decoder.SampleRate;
public int ChannelsCount => _decoder.NumChannels;
public Decoder(int sampleRate, int channelsCount)
{
_decoder = OpusCodecFactory.CreateDecoder(sampleRate, channelsCount);
}
public int Decode(ReadOnlySpan<byte> inData, Span<short> outPcm, int 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 IOpusMultiStreamDecoder _decoder;
public int SampleRate => _decoder.SampleRate;
public int ChannelsCount => _decoder.NumChannels;
public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
{
_decoder = OpusCodecFactory.CreateMultiStreamDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
}
public int Decode(ReadOnlySpan<byte> inData, Span<short> outPcm, int frameSize)
{
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;
private int _workBufferHandle;
private HardwareOpusDecoder(int workBufferHandle)
{
_workBufferHandle = workBufferHandle;
}
public HardwareOpusDecoder(int sampleRate, int channelsCount, int workBufferHandle) : this(workBufferHandle)
{
_decoder = new Decoder(sampleRate, channelsCount);
}
public HardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping, int workBufferHandle) : this(workBufferHandle)
{
_decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
}
[CmifCommand(0)]
public Result DecodeInterleavedOld(
out int outConsumed,
out int outSamples,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
{
return DecodeInterleavedInternal(out outConsumed, out outSamples, out _, output, input, reset: false, withPerf: false);
}
[CmifCommand(1)]
public Result SetContext(ReadOnlySpan<byte> context)
{
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return Result.Success;
}
[CmifCommand(2)] // 3.0.0+
public Result DecodeInterleavedForMultiStreamOld(
out int outConsumed,
out int outSamples,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
{
return DecodeInterleavedInternal(out outConsumed, out outSamples, out _, output, input, reset: false, withPerf: false);
}
[CmifCommand(3)] // 3.0.0+
public Result SetContextForMultiStream(ReadOnlySpan<byte> arg0)
{
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return Result.Success;
}
[CmifCommand(4)] // 4.0.0+
public Result DecodeInterleavedWithPerfOld(
out int outConsumed,
out long timeTaken,
out int outSamples,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
{
return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset: false, withPerf: true);
}
[CmifCommand(5)] // 4.0.0+
public Result DecodeInterleavedForMultiStreamWithPerfOld(
out int outConsumed,
out long timeTaken,
out int outSamples,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
{
return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset: false, withPerf: true);
}
[CmifCommand(6)] // 6.0.0+
public Result DecodeInterleavedWithPerfAndResetOld(
out int outConsumed,
out long timeTaken,
out int outSamples,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input,
bool reset)
{
return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true);
}
[CmifCommand(7)] // 6.0.0+
public Result DecodeInterleavedForMultiStreamWithPerfAndResetOld(
out int outConsumed,
out long timeTaken,
out int outSamples,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input,
bool reset)
{
return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true);
}
[CmifCommand(8)] // 7.0.0+
public Result DecodeInterleaved(
out int outConsumed,
out long timeTaken,
out int outSamples,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] ReadOnlySpan<byte> input,
bool reset)
{
return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true);
}
[CmifCommand(9)] // 7.0.0+
public Result DecodeInterleavedForMultiStream(
out int outConsumed,
out long timeTaken,
out int outSamples,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] ReadOnlySpan<byte> input,
bool reset)
{
return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true);
}
private Result DecodeInterleavedInternal(
out int outConsumed,
out int outSamples,
out long timeTaken,
Span<byte> output,
ReadOnlySpan<byte> input,
bool reset,
bool withPerf)
{
timeTaken = 0;
Span<short> outPcmSpace = MemoryMarshal.Cast<byte, short>(output);
Result result = DecodeInterleaved(_decoder, reset, input, outPcmSpace, output.Length, out outConsumed, out outSamples);
if (withPerf)
{
// This is the time the DSP took to process the request, TODO: fill this.
timeTaken = 0;
}
return result;
}
private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, ReadOnlySpan<byte> packet)
{
int result = OpusPacketInfo.GetNumSamples(packet, decoder.SampleRate);
numSamples = result;
if (result == OpusError.OPUS_INVALID_PACKET)
{
return CodecResult.OpusInvalidPacket;
}
else if (result == OpusError.OPUS_BAD_ARG)
{
return CodecResult.OpusBadArg;
}
return Result.Success;
}
private static Result DecodeInterleaved(
IDecoder decoder,
bool reset,
ReadOnlySpan<byte> input,
Span<short> outPcmData,
int outputSize,
out int outConsumed,
out int outSamples)
{
outConsumed = 0;
outSamples = 0;
int streamSize = input.Length;
if (streamSize < Unsafe.SizeOf<OpusPacketHeader>())
{
return CodecResult.InvalidLength;
}
OpusPacketHeader header = OpusPacketHeader.FromSpan(input);
int headerSize = Unsafe.SizeOf<OpusPacketHeader>();
uint totalSize = header.Length + (uint)headerSize;
if (totalSize > streamSize)
{
return CodecResult.InvalidLength;
}
ReadOnlySpan<byte> opusData = input.Slice(headerSize, (int)header.Length);
Result result = GetPacketNumSamples(decoder, out int numSamples, opusData);
if (result.IsSuccess)
{
if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize)
{
return CodecResult.InvalidLength;
}
if (reset)
{
decoder.ResetState();
}
try
{
outSamples = decoder.Decode(opusData, outPcmData, numSamples);
outConsumed = (int)totalSize;
}
catch (OpusException e)
{
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;
}
}
}
return Result.Success;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_workBufferHandle != 0)
{
HorizonStatic.Syscall.CloseHandle(_workBufferHandle);
_workBufferHandle = 0;
}
_decoder?.Dispose();
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}