From 9f2aea40594e6a34b9e520ea6ef9b522cb9e2731 Mon Sep 17 00:00:00 2001 From: AcK77 Date: Thu, 8 Feb 2018 17:52:02 +0100 Subject: [PATCH] Basic audio support Implement IAudioOut. Small corrections on AudIAudioRenderer. Add glitched audio playback support through OpenAL. --- README.md | 3 + Ryujinx/OsHle/Ipc/IpcHandler.cs | 12 ++ Ryujinx/OsHle/Objects/AudIAudioOut.cs | 164 +++++++++++++++++++++ Ryujinx/OsHle/Objects/AudIAudioRenderer.cs | 20 ++- Ryujinx/OsHle/Services/ServiceAud.cs | 19 ++- 5 files changed, 211 insertions(+), 7 deletions(-) create mode 100644 Ryujinx/OsHle/Objects/AudIAudioOut.cs diff --git a/README.md b/README.md index 1c8850de..857ba680 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,6 @@ Contributions are always welcome. To run this emulator, you need the .NET Core 2.0 (or higher) SDK. Run `dotnet run -c Release -- game.nro` inside the Ryujinx solution folder. + +Audio is partially supported (glitched) on Windows, you need to install the OpenAL Core SDK : +https://openal.org/downloads/OpenAL11CoreSDK.zip diff --git a/Ryujinx/OsHle/Ipc/IpcHandler.cs b/Ryujinx/OsHle/Ipc/IpcHandler.cs index 50968014..4e57889a 100644 --- a/Ryujinx/OsHle/Ipc/IpcHandler.cs +++ b/Ryujinx/OsHle/Ipc/IpcHandler.cs @@ -127,8 +127,20 @@ namespace Ryujinx.OsHle.Ipc //IAudioRenderer { (typeof(AudIAudioRenderer), 4), AudIAudioRenderer.RequestUpdateAudioRenderer }, { (typeof(AudIAudioRenderer), 5), AudIAudioRenderer.StartAudioRenderer }, + { (typeof(AudIAudioRenderer), 6), AudIAudioRenderer.StopAudioRenderer }, { (typeof(AudIAudioRenderer), 7), AudIAudioRenderer.QuerySystemEvent }, + //IAudioOut + { (typeof(AudIAudioOut), 0), AudIAudioOut.GetAudioOutState }, + { (typeof(AudIAudioOut), 1), AudIAudioOut.StartAudioOut }, + { (typeof(AudIAudioOut), 2), AudIAudioOut.StopAudioOut }, + { (typeof(AudIAudioOut), 3), AudIAudioOut.AppendAudioOutBuffer }, + { (typeof(AudIAudioOut), 4), AudIAudioOut.RegisterBufferEvent }, + { (typeof(AudIAudioOut), 5), AudIAudioOut.GetReleasedAudioOutBuffer }, + { (typeof(AudIAudioOut), 6), AudIAudioOut.ContainsAudioOutBuffer }, + { (typeof(AudIAudioOut), 7), AudIAudioOut.AppendAudioOutBuffer_ex }, + { (typeof(AudIAudioOut), 8), AudIAudioOut.GetReleasedAudioOutBuffer_ex }, + //IFile { (typeof(FspSrvIFile), 0), FspSrvIFile.Read }, { (typeof(FspSrvIFile), 1), FspSrvIFile.Write }, diff --git a/Ryujinx/OsHle/Objects/AudIAudioOut.cs b/Ryujinx/OsHle/Objects/AudIAudioOut.cs new file mode 100644 index 00000000..f9b8d1ca --- /dev/null +++ b/Ryujinx/OsHle/Objects/AudIAudioOut.cs @@ -0,0 +1,164 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; +using System.Collections.Generic; +using System.IO; +using static Ryujinx.OsHle.Objects.ObjHelper; +using OpenTK.Audio; +using OpenTK.Audio.OpenAL; // https://openal.org/downloads/OpenAL11CoreSDK.zip Needed! +using System; + +namespace Ryujinx.OsHle.Objects +{ + class AudIAudioOut + { + enum AudioOutState + { + Started, + Stopped + }; + + //IAudioOut + private static AudioOutState State = AudioOutState.Stopped; + private static List KeysQueue = new List(); + + //OpenAL + private static bool OpenALInstalled = true; + private static AudioContext AudioCtx; + private static int Source; + private static int Buffer; + + //Return State of IAudioOut + public static long GetAudioOutState(ServiceCtx Context) + { + Context.ResponseData.Write((int)State); + + return 0; + } + + public static long StartAudioOut(ServiceCtx Context) + { + if (State == AudioOutState.Stopped) + { + State = AudioOutState.Started; + + try + { + AudioCtx = new AudioContext(); //Create the audio context + } + catch (Exception ex) + { + Console.WriteLine("OpenAL Error! PS: Install OpenAL Core SDK!"); + OpenALInstalled = false; + } + + if(OpenALInstalled) AL.Listener(ALListenerf.Gain, (float)8.0); //Add more gain to it + } + + return 0; + } + + public static long StopAudioOut(ServiceCtx Context) + { + if (State == AudioOutState.Started) + { + if (OpenALInstalled) + { + if (AudioCtx == null) //Needed to call the instance of AudioContext() + return 0; + + AL.SourceStop(Source); + AL.DeleteSource(Source); + } + State = AudioOutState.Stopped; + } + + return 0; + } + + public static long AppendAudioOutBuffer(ServiceCtx Context) + { + long BufferId = Context.RequestData.ReadInt64(); + + KeysQueue.Insert(0, BufferId); + + byte[] AudioOutBuffer = AMemoryHelper.ReadBytes(Context.Memory, Context.Request.SendBuff[0].Position, 0x28); + using (MemoryStream MS = new MemoryStream(AudioOutBuffer)) + { + BinaryReader Reader = new BinaryReader(MS); + long PointerToSampleDataPointer = Reader.ReadInt64(); + long PointerToSampleData = Reader.ReadInt64(); + long CapacitySampleBuffer = Reader.ReadInt64(); + long SizeDataSampleBuffer = Reader.ReadInt64(); + long Unknown = Reader.ReadInt64(); + + byte[] AudioSampleBuffer = AMemoryHelper.ReadBytes(Context.Memory, PointerToSampleData, (int)SizeDataSampleBuffer); + + if (OpenALInstalled) + { + if (AudioCtx == null) //Needed to call the instance of AudioContext() + return 0; + + Buffer = AL.GenBuffer(); + AL.BufferData(Buffer, ALFormat.Stereo16, AudioSampleBuffer, AudioSampleBuffer.Length, 48000); + + Source = AL.GenSource(); + AL.SourceQueueBuffer(Source, Buffer); + } + } + + return 0; + } + + public static long RegisterBufferEvent(ServiceCtx Context) + { + int Handle = Context.Ns.Os.Handles.GenerateId(new HEvent()); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + return 0; + } + + public static long GetReleasedAudioOutBuffer(ServiceCtx Context) + { + long TempKey = 0; + + if(KeysQueue.Count > 0) + { + TempKey = KeysQueue[KeysQueue.Count - 1]; + KeysQueue.Remove(KeysQueue[KeysQueue.Count - 1]); + } + + AMemoryHelper.WriteBytes(Context.Memory, Context.Request.ReceiveBuff[0].Position, System.BitConverter.GetBytes(TempKey)); + + Context.ResponseData.Write((int)TempKey); + + if (OpenALInstalled) + { + if (AudioCtx == null) //Needed to call the instance of AudioContext() + return 0; + + AL.SourcePlay(Source); + int[] FreeBuffers = AL.SourceUnqueueBuffers(Source, 1); + AL.DeleteBuffers(FreeBuffers); + } + + return 0; + } + + public static long ContainsAudioOutBuffer(ServiceCtx Context) + { + return 0; + } + + public static long AppendAudioOutBuffer_ex(ServiceCtx Context) + { + return 0; + } + + public static long GetReleasedAudioOutBuffer_ex(ServiceCtx Context) + { + return 0; + } + } +} diff --git a/Ryujinx/OsHle/Objects/AudIAudioRenderer.cs b/Ryujinx/OsHle/Objects/AudIAudioRenderer.cs index 84ac4e04..35a5b82d 100644 --- a/Ryujinx/OsHle/Objects/AudIAudioRenderer.cs +++ b/Ryujinx/OsHle/Objects/AudIAudioRenderer.cs @@ -1,15 +1,20 @@ +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; + namespace Ryujinx.OsHle.Objects { class AudIAudioRenderer { public static long RequestUpdateAudioRenderer(ServiceCtx Context) { + //buffer < unknown, 5, 0 >) -> (buffer < unknown, 6, 0 >, buffer < unknown, 6, 0 > + long Position = Context.Request.ReceiveBuff[0].Position; //0x40 bytes header - Context.Memory.WriteInt32(Position + 0x4, 0xb0); //Behavior Out State Size? (note: this is the last section) - Context.Memory.WriteInt32(Position + 0x8, 0x18e0); //Memory Pool Out State Size? - Context.Memory.WriteInt32(Position + 0xc, 0x600); //Voice Out State Size? + Context.Memory.WriteInt32(Position + 0x4, 0xb0); //Behavior Out State Size? (note: this is the last section) + Context.Memory.WriteInt32(Position + 0x8, 0x18e0); //Memory Pool Out State Size? + Context.Memory.WriteInt32(Position + 0xc, 0x600); //Voice Out State Size? Context.Memory.WriteInt32(Position + 0x14, 0xe0); //Effect Out State Size? Context.Memory.WriteInt32(Position + 0x1c, 0x20); //Sink Out State Size? Context.Memory.WriteInt32(Position + 0x20, 0x10); //Performance Out State Size? @@ -28,8 +33,17 @@ namespace Ryujinx.OsHle.Objects return 0; } + public static long StopAudioRenderer(ServiceCtx Context) + { + return 0; + } + public static long QuerySystemEvent(ServiceCtx Context) { + int Handle = Context.Ns.Os.Handles.GenerateId(new HEvent()); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + return 0; } } diff --git a/Ryujinx/OsHle/Services/ServiceAud.cs b/Ryujinx/OsHle/Services/ServiceAud.cs index 96e7e548..18401ae9 100644 --- a/Ryujinx/OsHle/Services/ServiceAud.cs +++ b/Ryujinx/OsHle/Services/ServiceAud.cs @@ -21,10 +21,21 @@ namespace Ryujinx.OsHle.Services public static long AudOutOpenAudioOut(ServiceCtx Context) { - Context.ResponseData.Write(48000); - Context.ResponseData.Write(2); - Context.ResponseData.Write(2); - Context.ResponseData.Write(0); + MakeObject(Context, new AudIAudioOut()); + + Context.ResponseData.Write(48000); //Sample Rate + Context.ResponseData.Write(2); //Channel Count + Context.ResponseData.Write(2); //PCM Format + /* + 0 - Invalid + 1 - INT8 + 2 - INT16 + 3 - INT24 + 4 - INT32 + 5 - PCM Float + 6 - ADPCM + */ + Context.ResponseData.Write(0); //Unknown return 0; }