Browse Source

HwOpus service implementation (#201)

* Started to implement the hwopus service

* Write outputs on decode method, some basic error handling

* Fix buffer size read from header and check

* Fix order of values
gdkchan 7 years ago
parent
commit
b833183ef6

+ 1 - 0
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;
     }
 }

+ 91 - 0
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<int, ServiceProcessRequest> m_Commands;
+
+        public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+        private int SampleRate;
+        private int ChannelsCount;
+
+        private OpusDecoder Decoder;
+
+        public IHardwareOpusDecoder(int SampleRate, int ChannelsCount)
+        {
+            m_Commands = new Dictionary<int, ServiceProcessRequest>()
+            {
+                { 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;
+        }
+    }
+}

+ 72 - 0
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<int, ServiceProcessRequest> m_Commands;
+
+        public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+        public IHardwareOpusDecoderManager()
+        {
+            m_Commands = new Dictionary<int, ServiceProcessRequest>()
+            {
+                { 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;
+        }
+    }
+}

+ 3 - 0
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();
 

+ 4 - 0
Ryujinx.HLE/Ryujinx.HLE.csproj

@@ -29,4 +29,8 @@
     <PackageReference Include="LibHac" Version="0.1.3" />
   </ItemGroup>
 
+  <ItemGroup>
+    <PackageReference Include="Concentus" Version="1.1.7" />
+  </ItemGroup>
+
 </Project>