Basic audio support

Implement IAudioOut.
Small corrections on AudIAudioRenderer.
Add glitched audio playback support through OpenAL.
This commit is contained in:
AcK77 2018-02-08 17:52:02 +01:00
parent 64d34f2882
commit 9f2aea4059
5 changed files with 211 additions and 7 deletions

View file

@ -8,3 +8,6 @@ Contributions are always welcome.
To run this emulator, you need the .NET Core 2.0 (or higher) SDK. 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. 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

View file

@ -127,8 +127,20 @@ namespace Ryujinx.OsHle.Ipc
//IAudioRenderer //IAudioRenderer
{ (typeof(AudIAudioRenderer), 4), AudIAudioRenderer.RequestUpdateAudioRenderer }, { (typeof(AudIAudioRenderer), 4), AudIAudioRenderer.RequestUpdateAudioRenderer },
{ (typeof(AudIAudioRenderer), 5), AudIAudioRenderer.StartAudioRenderer }, { (typeof(AudIAudioRenderer), 5), AudIAudioRenderer.StartAudioRenderer },
{ (typeof(AudIAudioRenderer), 6), AudIAudioRenderer.StopAudioRenderer },
{ (typeof(AudIAudioRenderer), 7), AudIAudioRenderer.QuerySystemEvent }, { (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 //IFile
{ (typeof(FspSrvIFile), 0), FspSrvIFile.Read }, { (typeof(FspSrvIFile), 0), FspSrvIFile.Read },
{ (typeof(FspSrvIFile), 1), FspSrvIFile.Write }, { (typeof(FspSrvIFile), 1), FspSrvIFile.Write },

View file

@ -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<long> KeysQueue = new List<long>();
//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;
}
}
}

View file

@ -1,15 +1,20 @@
using Ryujinx.OsHle.Handles;
using Ryujinx.OsHle.Ipc;
namespace Ryujinx.OsHle.Objects namespace Ryujinx.OsHle.Objects
{ {
class AudIAudioRenderer class AudIAudioRenderer
{ {
public static long RequestUpdateAudioRenderer(ServiceCtx Context) 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; long Position = Context.Request.ReceiveBuff[0].Position;
//0x40 bytes header //0x40 bytes header
Context.Memory.WriteInt32(Position + 0x4, 0xb0); //Behavior Out State Size? (note: this is the last section) 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 + 0x8, 0x18e0); //Memory Pool Out State Size?
Context.Memory.WriteInt32(Position + 0xc, 0x600); //Voice 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 + 0x14, 0xe0); //Effect Out State Size?
Context.Memory.WriteInt32(Position + 0x1c, 0x20); //Sink Out State Size? Context.Memory.WriteInt32(Position + 0x1c, 0x20); //Sink Out State Size?
Context.Memory.WriteInt32(Position + 0x20, 0x10); //Performance Out State Size? Context.Memory.WriteInt32(Position + 0x20, 0x10); //Performance Out State Size?
@ -28,8 +33,17 @@ namespace Ryujinx.OsHle.Objects
return 0; return 0;
} }
public static long StopAudioRenderer(ServiceCtx Context)
{
return 0;
}
public static long QuerySystemEvent(ServiceCtx Context) public static long QuerySystemEvent(ServiceCtx Context)
{ {
int Handle = Context.Ns.Os.Handles.GenerateId(new HEvent());
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
return 0; return 0;
} }
} }

View file

@ -21,10 +21,21 @@ namespace Ryujinx.OsHle.Services
public static long AudOutOpenAudioOut(ServiceCtx Context) public static long AudOutOpenAudioOut(ServiceCtx Context)
{ {
Context.ResponseData.Write(48000); MakeObject(Context, new AudIAudioOut());
Context.ResponseData.Write(2);
Context.ResponseData.Write(2); Context.ResponseData.Write(48000); //Sample Rate
Context.ResponseData.Write(0); 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; return 0;
} }