aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Horizon/Sdk/Codec
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Horizon/Sdk/Codec')
-rw-r--r--src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs16
-rw-r--r--src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs336
-rw-r--r--src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs386
-rw-r--r--src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs11
-rw-r--r--src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs13
-rw-r--r--src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs15
-rw-r--r--src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs17
-rw-r--r--src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs20
-rw-r--r--src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs19
-rw-r--r--src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs11
10 files changed, 844 insertions, 0 deletions
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs b/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs
new file mode 100644
index 00000000..21508b7f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.Horizon.Sdk.Codec
+{
+ static class CodecResult
+ {
+ private const int ModuleId = 111;
+
+ public static Result InvalidLength => new(ModuleId, 3);
+ public static Result OpusBadArg => new(ModuleId, 130);
+ public static Result OpusInvalidPacket => new(ModuleId, 133);
+ public static Result InvalidNumberOfStreams => new(ModuleId, 1000);
+ public static Result InvalidSampleRate => new(ModuleId, 1001);
+ public static Result InvalidChannelCount => new(ModuleId, 1002);
+ }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs
new file mode 100644
index 00000000..5d279858
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs
@@ -0,0 +1,336 @@
+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
+ {
+ [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
+ {
+ int SampleRate { get; }
+ int ChannelsCount { get; }
+
+ int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize);
+ void ResetState();
+ }
+
+ private class Decoder : IDecoder
+ {
+ private readonly OpusDecoder _decoder;
+
+ public int SampleRate => _decoder.SampleRate;
+ public int ChannelsCount => _decoder.NumChannels;
+
+ public Decoder(int sampleRate, int channelsCount)
+ {
+ _decoder = new OpusDecoder(sampleRate, channelsCount);
+ }
+
+ public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
+ {
+ return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize);
+ }
+
+ public void ResetState()
+ {
+ _decoder.ResetState();
+ }
+ }
+
+ private class MultiSampleDecoder : IDecoder
+ {
+ private readonly OpusMSDecoder _decoder;
+
+ public int SampleRate => _decoder.SampleRate;
+ public int ChannelsCount { get; }
+
+ public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
+ {
+ ChannelsCount = channelsCount;
+ _decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
+ }
+
+ public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
+ {
+ return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0);
+ }
+
+ public void ResetState()
+ {
+ _decoder.ResetState();
+ }
+ }
+
+ 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;
+
+ Result result = DecodeInterleaved(_decoder, reset, input, out short[] outPcmData, output.Length, out outConsumed, out outSamples);
+
+ if (withPerf)
+ {
+ // This is the time the DSP took to process the request, TODO: fill this.
+ timeTaken = 0;
+ }
+
+ MemoryMarshal.Cast<short, byte>(outPcmData).CopyTo(output[..(outPcmData.Length * sizeof(short))]);
+
+ return result;
+ }
+
+ private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, byte[] packet)
+ {
+ int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, 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,
+ out short[] outPcmData,
+ int outputSize,
+ out int outConsumed,
+ out int outSamples)
+ {
+ outPcmData = null;
+ 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;
+ }
+
+ byte[] opusData = input.Slice(headerSize, (int)header.Length).ToArray();
+
+ Result result = GetPacketNumSamples(decoder, out int numSamples, opusData);
+
+ if (result.IsSuccess)
+ {
+ if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize)
+ {
+ return CodecResult.InvalidLength;
+ }
+
+ outPcmData = new short[numSamples * decoder.ChannelsCount];
+
+ if (reset)
+ {
+ decoder.ResetState();
+ }
+
+ try
+ {
+ outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount);
+ outConsumed = (int)totalSize;
+ }
+ catch (OpusException)
+ {
+ // TODO: As OpusException doesn't return the exact error code, this is inaccurate in some cases...
+ return CodecResult.InvalidLength;
+ }
+ }
+
+ return Result.Success;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (_workBufferHandle != 0)
+ {
+ HorizonStatic.Syscall.CloseHandle(_workBufferHandle);
+
+ _workBufferHandle = 0;
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs
new file mode 100644
index 00000000..acec66e8
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs
@@ -0,0 +1,386 @@
+using Ryujinx.Common;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+ partial class HardwareOpusDecoderManager : IHardwareOpusDecoderManager
+ {
+ [CmifCommand(0)]
+ public Result OpenHardwareOpusDecoder(
+ out IHardwareOpusDecoder decoder,
+ HardwareOpusDecoderParameterInternal parameter,
+ [CopyHandle] int workBufferHandle,
+ int workBufferSize)
+ {
+ decoder = null;
+
+ if (!IsValidSampleRate(parameter.SampleRate))
+ {
+ HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+ return CodecResult.InvalidSampleRate;
+ }
+
+ if (!IsValidChannelCount(parameter.ChannelsCount))
+ {
+ HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+ return CodecResult.InvalidChannelCount;
+ }
+
+ decoder = new HardwareOpusDecoder(parameter.SampleRate, parameter.ChannelsCount, workBufferHandle);
+
+ return Result.Success;
+ }
+
+ [CmifCommand(1)]
+ public Result GetWorkBufferSize(out int size, HardwareOpusDecoderParameterInternal parameter)
+ {
+ size = 0;
+
+ if (!IsValidChannelCount(parameter.ChannelsCount))
+ {
+ return CodecResult.InvalidChannelCount;
+ }
+
+ if (!IsValidSampleRate(parameter.SampleRate))
+ {
+ return CodecResult.InvalidSampleRate;
+ }
+
+ int opusDecoderSize = GetOpusDecoderSize(parameter.ChannelsCount);
+
+ int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0;
+ int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * 1920 / sampleRateRatio : 0, 64);
+ size = opusDecoderSize + 1536 + frameSize;
+
+ return Result.Success;
+ }
+
+ [CmifCommand(2)] // 3.0.0+
+ public Result OpenHardwareOpusDecoderForMultiStream(
+ out IHardwareOpusDecoder decoder,
+ [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x110)] in HardwareOpusMultiStreamDecoderParameterInternal parameter,
+ [CopyHandle] int workBufferHandle,
+ int workBufferSize)
+ {
+ decoder = null;
+
+ if (!IsValidSampleRate(parameter.SampleRate))
+ {
+ HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+ return CodecResult.InvalidSampleRate;
+ }
+
+ if (!IsValidMultiChannelCount(parameter.ChannelsCount))
+ {
+ HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+ return CodecResult.InvalidChannelCount;
+ }
+
+ if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount))
+ {
+ HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+ return CodecResult.InvalidNumberOfStreams;
+ }
+
+ decoder = new HardwareOpusDecoder(
+ parameter.SampleRate,
+ parameter.ChannelsCount,
+ parameter.NumberOfStreams,
+ parameter.NumberOfStereoStreams,
+ parameter.ChannelMappings.AsSpan().ToArray(),
+ workBufferHandle);
+
+ return Result.Success;
+ }
+
+ [CmifCommand(3)] // 3.0.0+
+ public Result GetWorkBufferSizeForMultiStream(
+ out int size,
+ [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x110)] in HardwareOpusMultiStreamDecoderParameterInternal parameter)
+ {
+ size = 0;
+
+ if (!IsValidMultiChannelCount(parameter.ChannelsCount))
+ {
+ return CodecResult.InvalidChannelCount;
+ }
+
+ if (!IsValidSampleRate(parameter.SampleRate))
+ {
+ return CodecResult.InvalidSampleRate;
+ }
+
+ if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount))
+ {
+ return CodecResult.InvalidSampleRate;
+ }
+
+ int opusDecoderSize = GetOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams);
+
+ int streamSize = BitUtils.AlignUp(parameter.NumberOfStreams * 1500, 64);
+ int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0;
+ int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * 1920 / sampleRateRatio : 0, 64);
+ size = opusDecoderSize + streamSize + frameSize;
+
+ return Result.Success;
+ }
+
+ [CmifCommand(4)] // 12.0.0+
+ public Result OpenHardwareOpusDecoderEx(
+ out IHardwareOpusDecoder decoder,
+ HardwareOpusDecoderParameterInternalEx parameter,
+ [CopyHandle] int workBufferHandle,
+ int workBufferSize)
+ {
+ decoder = null;
+
+ if (!IsValidChannelCount(parameter.ChannelsCount))
+ {
+ HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+ return CodecResult.InvalidChannelCount;
+ }
+
+ if (!IsValidSampleRate(parameter.SampleRate))
+ {
+ HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+ return CodecResult.InvalidSampleRate;
+ }
+
+ decoder = new HardwareOpusDecoder(parameter.SampleRate, parameter.ChannelsCount, workBufferHandle);
+
+ return Result.Success;
+ }
+
+ [CmifCommand(5)] // 12.0.0+
+ public Result GetWorkBufferSizeEx(out int size, HardwareOpusDecoderParameterInternalEx parameter)
+ {
+ return GetWorkBufferSizeExImpl(out size, in parameter, fromDsp: false);
+ }
+
+ [CmifCommand(6)] // 12.0.0+
+ public Result OpenHardwareOpusDecoderForMultiStreamEx(
+ out IHardwareOpusDecoder decoder,
+ [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter,
+ [CopyHandle] int workBufferHandle,
+ int workBufferSize)
+ {
+ decoder = null;
+
+ if (!IsValidSampleRate(parameter.SampleRate))
+ {
+ HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+ return CodecResult.InvalidSampleRate;
+ }
+
+ if (!IsValidMultiChannelCount(parameter.ChannelsCount))
+ {
+ HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+ return CodecResult.InvalidChannelCount;
+ }
+
+ if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount))
+ {
+ HorizonStatic.Syscall.CloseHandle(workBufferHandle);
+
+ return CodecResult.InvalidNumberOfStreams;
+ }
+
+ decoder = new HardwareOpusDecoder(
+ parameter.SampleRate,
+ parameter.ChannelsCount,
+ parameter.NumberOfStreams,
+ parameter.NumberOfStereoStreams,
+ parameter.ChannelMappings.AsSpan().ToArray(),
+ workBufferHandle);
+
+ return Result.Success;
+ }
+
+ [CmifCommand(7)] // 12.0.0+
+ public Result GetWorkBufferSizeForMultiStreamEx(
+ out int size,
+ [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter)
+ {
+ return GetWorkBufferSizeForMultiStreamExImpl(out size, in parameter, fromDsp: false);
+ }
+
+ [CmifCommand(8)] // 16.0.0+
+ public Result GetWorkBufferSizeExEx(out int size, HardwareOpusDecoderParameterInternalEx parameter)
+ {
+ return GetWorkBufferSizeExImpl(out size, in parameter, fromDsp: true);
+ }
+
+ [CmifCommand(9)] // 16.0.0+
+ public Result GetWorkBufferSizeForMultiStreamExEx(
+ out int size,
+ [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter)
+ {
+ return GetWorkBufferSizeForMultiStreamExImpl(out size, in parameter, fromDsp: true);
+ }
+
+ private Result GetWorkBufferSizeExImpl(out int size, in HardwareOpusDecoderParameterInternalEx parameter, bool fromDsp)
+ {
+ size = 0;
+
+ if (!IsValidChannelCount(parameter.ChannelsCount))
+ {
+ return CodecResult.InvalidChannelCount;
+ }
+
+ if (!IsValidSampleRate(parameter.SampleRate))
+ {
+ return CodecResult.InvalidSampleRate;
+ }
+
+ int opusDecoderSize = fromDsp ? GetDspOpusDecoderSize(parameter.ChannelsCount) : GetOpusDecoderSize(parameter.ChannelsCount);
+
+ int frameSizeMono48KHz = parameter.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
+ int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0;
+ int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * frameSizeMono48KHz / sampleRateRatio : 0, 64);
+ size = opusDecoderSize + 1536 + frameSize;
+
+ return Result.Success;
+ }
+
+ private Result GetWorkBufferSizeForMultiStreamExImpl(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, bool fromDsp)
+ {
+ size = 0;
+
+ if (!IsValidMultiChannelCount(parameter.ChannelsCount))
+ {
+ return CodecResult.InvalidChannelCount;
+ }
+
+ if (!IsValidSampleRate(parameter.SampleRate))
+ {
+ return CodecResult.InvalidSampleRate;
+ }
+
+ if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount))
+ {
+ return CodecResult.InvalidSampleRate;
+ }
+
+ int opusDecoderSize = fromDsp
+ ? GetDspOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams)
+ : GetOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams);
+
+ int frameSizeMono48KHz = parameter.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
+ int streamSize = BitUtils.AlignUp(parameter.NumberOfStreams * 1500, 64);
+ int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0;
+ int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * frameSizeMono48KHz / sampleRateRatio : 0, 64);
+ size = opusDecoderSize + streamSize + frameSize;
+
+ return Result.Success;
+ }
+
+ private static int GetDspOpusDecoderSize(int channelsCount)
+ {
+ // TODO: Figure out the size returned here.
+ // Not really important because we don't use the work buffer, and the size being lower is fine.
+
+ return 0;
+ }
+
+ private static int GetDspOpusMultistreamDecoderSize(int streams, int coupledStreams)
+ {
+ // TODO: Figure out the size returned here.
+ // Not really important because we don't use the work buffer, and the size being lower is fine.
+
+ return 0;
+ }
+
+ private static int GetOpusDecoderSize(int channelsCount)
+ {
+ const int SilkDecoderSize = 0x2160;
+
+ if (channelsCount < 1 || channelsCount > 2)
+ {
+ return 0;
+ }
+
+ int celtDecoderSize = GetCeltDecoderSize(channelsCount);
+ int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x50;
+
+ return opusDecoderSize + SilkDecoderSize + celtDecoderSize;
+ }
+
+ private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams)
+ {
+ if (streams < 1 || coupledStreams > streams || coupledStreams < 0)
+ {
+ return 0;
+ }
+
+ int coupledSize = GetOpusDecoderSize(2);
+ int monoSize = GetOpusDecoderSize(1);
+
+ return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) +
+ Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb920;
+ }
+
+ private static int Align4(int value)
+ {
+ return BitUtils.AlignUp(value, 4);
+ }
+
+ private static int GetOpusDecoderAllocSize(int channelsCount)
+ {
+ return channelsCount * 0x800 + 0x4800;
+ }
+
+ private static int GetCeltDecoderSize(int channelsCount)
+ {
+ const int DecodeBufferSize = 0x2030;
+ const int Overlap = 120;
+ const int EBandsCount = 21;
+
+ return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x54;
+ }
+
+ private static bool IsValidChannelCount(int channelsCount)
+ {
+ return channelsCount > 0 && channelsCount <= 2;
+ }
+
+ private static bool IsValidMultiChannelCount(int channelsCount)
+ {
+ return channelsCount > 0 && channelsCount <= 255;
+ }
+
+ private static bool IsValidSampleRate(int sampleRate)
+ {
+ switch (sampleRate)
+ {
+ case 8000:
+ case 12000:
+ case 16000:
+ case 24000:
+ case 48000:
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsValidNumberOfStreams(int numberOfStreams, int numberOfStereoStreams, int channelsCount)
+ {
+ return numberOfStreams > 0 &&
+ numberOfStreams + numberOfStereoStreams <= channelsCount &&
+ numberOfStereoStreams >= 0 &&
+ numberOfStereoStreams <= numberOfStreams;
+ }
+ }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs
new file mode 100644
index 00000000..271a592c
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs
@@ -0,0 +1,11 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)]
+ struct HardwareOpusDecoderParameterInternal
+ {
+ public int SampleRate;
+ public int ChannelsCount;
+ }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs
new file mode 100644
index 00000000..e2b81c77
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs
@@ -0,0 +1,13 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)]
+ struct HardwareOpusDecoderParameterInternalEx
+ {
+ public int SampleRate;
+ public int ChannelsCount;
+ public OpusDecoderFlags Flags;
+ public uint Reserved;
+ }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs
new file mode 100644
index 00000000..98536a4f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x110)]
+ struct HardwareOpusMultiStreamDecoderParameterInternal
+ {
+ public int SampleRate;
+ public int ChannelsCount;
+ public int NumberOfStreams;
+ public int NumberOfStereoStreams;
+ public Array256<byte> ChannelMappings;
+ }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs
new file mode 100644
index 00000000..8f8615df
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs
@@ -0,0 +1,17 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x118)]
+ struct HardwareOpusMultiStreamDecoderParameterInternalEx
+ {
+ public int SampleRate;
+ public int ChannelsCount;
+ public int NumberOfStreams;
+ public int NumberOfStereoStreams;
+ public OpusDecoderFlags Flags;
+ public uint Reserved;
+ public Array256<byte> ChannelMappings;
+ }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs
new file mode 100644
index 00000000..ae09ad15
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+ interface IHardwareOpusDecoder : IServiceObject
+ {
+ Result DecodeInterleavedOld(out int outConsumed, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input);
+ Result SetContext(ReadOnlySpan<byte> context);
+ Result DecodeInterleavedForMultiStreamOld(out int outConsumed, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input);
+ Result SetContextForMultiStream(ReadOnlySpan<byte> context);
+ Result DecodeInterleavedWithPerfOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input);
+ Result DecodeInterleavedForMultiStreamWithPerfOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input);
+ Result DecodeInterleavedWithPerfAndResetOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset);
+ Result DecodeInterleavedForMultiStreamWithPerfAndResetOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset);
+ Result DecodeInterleaved(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset);
+ Result DecodeInterleavedForMultiStream(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset);
+ }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs
new file mode 100644
index 00000000..fb6c787b
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+ interface IHardwareOpusDecoderManager : IServiceObject
+ {
+ Result OpenHardwareOpusDecoder(out IHardwareOpusDecoder decoder, HardwareOpusDecoderParameterInternal parameter, int workBufferHandle, int workBufferSize);
+ Result GetWorkBufferSize(out int size, HardwareOpusDecoderParameterInternal parameter);
+ Result OpenHardwareOpusDecoderForMultiStream(out IHardwareOpusDecoder decoder, in HardwareOpusMultiStreamDecoderParameterInternal parameter, int workBufferHandle, int workBufferSize);
+ Result GetWorkBufferSizeForMultiStream(out int size, in HardwareOpusMultiStreamDecoderParameterInternal parameter);
+ Result OpenHardwareOpusDecoderEx(out IHardwareOpusDecoder decoder, HardwareOpusDecoderParameterInternalEx parameter, int workBufferHandle, int workBufferSize);
+ Result GetWorkBufferSizeEx(out int size, HardwareOpusDecoderParameterInternalEx parameter);
+ Result OpenHardwareOpusDecoderForMultiStreamEx(out IHardwareOpusDecoder decoder, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, int workBufferHandle, int workBufferSize);
+ Result GetWorkBufferSizeForMultiStreamEx(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter);
+ Result GetWorkBufferSizeExEx(out int size, HardwareOpusDecoderParameterInternalEx parameter);
+ Result GetWorkBufferSizeForMultiStreamExEx(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter);
+ }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs
new file mode 100644
index 00000000..d630b10f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Codec.Detail
+{
+ [Flags]
+ enum OpusDecoderFlags : uint
+ {
+ None,
+ LargeFrameSize = 1 << 0,
+ }
+}