Basic audio support
Implement IAudioOut. Small corrections on AudIAudioRenderer. Add glitched audio playback support through OpenAL.
This commit is contained in:
parent
64d34f2882
commit
9f2aea4059
5 changed files with 211 additions and 7 deletions
|
@ -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
|
||||||
|
|
|
@ -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 },
|
||||||
|
|
164
Ryujinx/OsHle/Objects/AudIAudioOut.cs
Normal file
164
Ryujinx/OsHle/Objects/AudIAudioOut.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,14 @@
|
||||||
|
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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue