diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudErr.cs b/Ryujinx.HLE/HOS/Services/Aud/AudErr.cs index cecea86057..675ea8c7c9 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/AudErr.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/AudErr.cs @@ -5,5 +5,6 @@ namespace Ryujinx.HLE.HOS.Services.Aud public const int DeviceNotFound = 1; public const int UnsupportedRevision = 2; public const int UnsupportedSampleRate = 3; + public const int OpusInvalidInput = 6; } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Aud/IHardwareOpusDecoder.cs b/Ryujinx.HLE/HOS/Services/Aud/IHardwareOpusDecoder.cs new file mode 100644 index 0000000000..a71b8602ad --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Aud/IHardwareOpusDecoder.cs @@ -0,0 +1,91 @@ +using Concentus.Structs; +using Ryujinx.HLE.HOS.Ipc; +using System.Collections.Generic; + +using static Ryujinx.HLE.HOS.ErrorCode; + +namespace Ryujinx.HLE.HOS.Services.Aud +{ + class IHardwareOpusDecoder : IpcService + { + private const int FixedSampleRate = 48000; + + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + private int SampleRate; + private int ChannelsCount; + + private OpusDecoder Decoder; + + public IHardwareOpusDecoder(int SampleRate, int ChannelsCount) + { + m_Commands = new Dictionary() + { + { 0, DecodeInterleaved }, + { 4, DecodeInterleavedWithPerf } + }; + + this.SampleRate = SampleRate; + this.ChannelsCount = ChannelsCount; + + Decoder = new OpusDecoder(FixedSampleRate, ChannelsCount); + } + + public long DecodeInterleavedWithPerf(ServiceCtx Context) + { + long Result = DecodeInterleaved(Context); + + //TODO: Figure out what this value is. + //According to switchbrew, it is now used. + Context.ResponseData.Write(0L); + + return Result; + } + + public long DecodeInterleaved(ServiceCtx Context) + { + long InPosition = Context.Request.SendBuff[0].Position; + long InSize = Context.Request.SendBuff[0].Size; + + if (InSize < 8) + { + return MakeError(ErrorModule.Audio, AudErr.OpusInvalidInput); + } + + long OutPosition = Context.Request.ReceiveBuff[0].Position; + long OutSize = Context.Request.ReceiveBuff[0].Size; + + byte[] OpusData = Context.Memory.ReadBytes(InPosition, InSize); + + int Processed = ((OpusData[0] << 24) | + (OpusData[1] << 16) | + (OpusData[2] << 8) | + (OpusData[3] << 0)) + 8; + + if ((uint)Processed > (ulong)InSize) + { + return MakeError(ErrorModule.Audio, AudErr.OpusInvalidInput); + } + + short[] Pcm = new short[OutSize / 2]; + + int FrameSize = Pcm.Length / (ChannelsCount * 2); + + int Samples = Decoder.Decode(OpusData, 0, OpusData.Length, Pcm, 0, FrameSize); + + foreach (short Sample in Pcm) + { + Context.Memory.WriteInt16(OutPosition, Sample); + + OutPosition += 2; + } + + Context.ResponseData.Write(Processed); + Context.ResponseData.Write(Samples); + + return 0; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Aud/IHardwareOpusDecoderManager.cs b/Ryujinx.HLE/HOS/Services/Aud/IHardwareOpusDecoderManager.cs new file mode 100644 index 0000000000..875dc74c34 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Aud/IHardwareOpusDecoderManager.cs @@ -0,0 +1,72 @@ +using Ryujinx.HLE.HOS.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Aud +{ + class IHardwareOpusDecoderManager : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IHardwareOpusDecoderManager() + { + m_Commands = new Dictionary() + { + { 0, Initialize }, + { 1, GetWorkBufferSize } + }; + } + + public long Initialize(ServiceCtx Context) + { + int SampleRate = Context.RequestData.ReadInt32(); + int ChannelsCount = Context.RequestData.ReadInt32(); + + MakeObject(Context, new IHardwareOpusDecoder(SampleRate, ChannelsCount)); + + return 0; + } + + public long GetWorkBufferSize(ServiceCtx Context) + { + //Note: The sample rate is ignored because it is fixed to 48KHz. + int SampleRate = Context.RequestData.ReadInt32(); + int ChannelsCount = Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(GetOpusDecoderSize(ChannelsCount)); + + return 0; + } + + private static int GetOpusDecoderSize(int ChannelsCount) + { + const int SilkDecoderSize = 0x2198; + + if (ChannelsCount < 1 || ChannelsCount > 2) + { + return 0; + } + + int CeltDecoderSize = GetCeltDecoderSize(ChannelsCount); + + int OpusDecoderSize = (ChannelsCount * 0x800 + 0x4807) & -0x800 | 0x50; + + return OpusDecoderSize + SilkDecoderSize + CeltDecoderSize; + } + + private static int GetCeltDecoderSize(int ChannelsCount) + { + const int DecodeBufferSize = 0x2030; + const int CeltDecoderSize = 0x58; + const int CeltSigSize = 0x4; + const int Overlap = 120; + const int EBandsCount = 21; + + return (DecodeBufferSize + Overlap * 4) * ChannelsCount + + EBandsCount * 16 + + CeltDecoderSize + + CeltSigSize; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs index 5908e81082..d91583d62b 100644 --- a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs +++ b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs @@ -105,6 +105,9 @@ namespace Ryujinx.HLE.HOS.Services case "ldr:ro": return new IRoInterface(); + case "hwopus": + return new IHardwareOpusDecoderManager(); + case "lm": return new ILogService(); diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index 165a3d9d0b..71a0cf1c47 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -29,4 +29,8 @@ + + + +