diff --git a/src/LibRyujinx/Audio/Oboe/OboeAudioBuffer.cs b/src/LibRyujinx/Audio/Oboe/OboeAudioBuffer.cs new file mode 100644 index 000000000..aad74115f --- /dev/null +++ b/src/LibRyujinx/Audio/Oboe/OboeAudioBuffer.cs @@ -0,0 +1,18 @@ +namespace LibRyujinx.Shared.Audio.Oboe +{ + internal class OboeAudioBuffer + { + public readonly ulong DriverIdentifier; + public readonly ulong SampleCount; + public readonly byte[] Data; + public ulong SamplePlayed; + + public OboeAudioBuffer(ulong driverIdentifier, byte[] data, ulong sampleCount) + { + DriverIdentifier = driverIdentifier; + Data = data; + SampleCount = sampleCount; + SamplePlayed = 0; + } + } +} diff --git a/src/LibRyujinx/Audio/Oboe/OboeHardwareDeviceDriver.cs b/src/LibRyujinx/Audio/Oboe/OboeHardwareDeviceDriver.cs new file mode 100644 index 000000000..c56d0fe38 --- /dev/null +++ b/src/LibRyujinx/Audio/Oboe/OboeHardwareDeviceDriver.cs @@ -0,0 +1,108 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace LibRyujinx.Shared.Audio.Oboe +{ + internal class OboeHardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly ManualResetEvent _updateRequiredEvent; + private readonly ManualResetEvent _pauseEvent; + private readonly ConcurrentDictionary _sessions; + + public OboeHardwareDeviceDriver() + { + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + _sessions = new ConcurrentDictionary(); + } + + public static bool IsSupported => true; + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public ManualResetEvent GetPauseEvent() + { + return _pauseEvent; + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) + { + if (channelCount == 0) + { + channelCount = 2; + } + + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + if (direction != Direction.Output) + { + throw new NotImplementedException("Input direction is currently not implemented on Oboe backend!"); + } + + OboeHardwareDeviceSession session = new OboeHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); + + _sessions.TryAdd(session, 0); + + return session; + } + + internal bool Unregister(OboeHardwareDeviceSession session) + { + return _sessions.TryRemove(session, out _); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (OboeHardwareDeviceSession session in _sessions.Keys) + { + session.Dispose(); + } + + _pauseEvent.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return sampleFormat != SampleFormat.Adpcm; + } + + public bool SupportsChannelCount(uint channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6; + } + + public bool SupportsDirection(Direction direction) + { + return direction == Direction.Output; + } + } +} \ No newline at end of file diff --git a/src/LibRyujinx/Audio/Oboe/OboeHardwareDeviceSession.cs b/src/LibRyujinx/Audio/Oboe/OboeHardwareDeviceSession.cs new file mode 100644 index 000000000..b23426d30 --- /dev/null +++ b/src/LibRyujinx/Audio/Oboe/OboeHardwareDeviceSession.cs @@ -0,0 +1,169 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace LibRyujinx.Shared.Audio.Oboe +{ + internal class OboeHardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private OboeHardwareDeviceDriver _driver; + private bool _isWorkerActive; + private Queue _queuedBuffers; + private bool _isActive; + private ulong _playedSampleCount; + private Thread _workerThread; + private ManualResetEvent _updateRequiredEvent; + private IntPtr _session; + private object _queueLock = new object(); + private object _trackLock = new object(); + + public OboeHardwareDeviceSession(OboeHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _driver = driver; + _isActive = false; + _playedSampleCount = 0; + _isWorkerActive = true; + _queuedBuffers = new Queue(); + _updateRequiredEvent = driver.GetUpdateRequiredEvent(); + + _session = OboeInterop.CreateSession((int)requestedSampleFormat, requestedSampleRate, requestedChannelCount); + + _workerThread = new Thread(Update); + _workerThread.Name = $"HardwareDeviceSession.Android.Track"; + _workerThread.Start(); + + SetVolume(requestedVolume); + } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public unsafe void Update(object ignored) + { + while (_isWorkerActive) + { + bool needUpdate = false; + + bool hasBuffer; + + OboeAudioBuffer buffer; + + lock (_queueLock) + { + hasBuffer = _queuedBuffers.TryPeek(out buffer); + } + + while (hasBuffer) + { + StartIfNotPlaying(); + + fixed(byte* ptr = buffer.Data) + OboeInterop.WriteToSession(_session, (ulong)ptr, buffer.SampleCount); + + lock (_queueLock) + { + _playedSampleCount += buffer.SampleCount; + + _queuedBuffers.TryDequeue(out _); + } + + needUpdate = true; + + lock (_queueLock) + { + hasBuffer = _queuedBuffers.TryPeek(out buffer); + } + } + + if (needUpdate) + { + _updateRequiredEvent.Set(); + } + + // No work + Thread.Sleep(5); + } + + } + + public override void Dispose() + { + OboeInterop.CloseSession(_session); + } + public override void PrepareToClose() + { + _isWorkerActive = false; + _workerThread.Join(); + } + + private void StartIfNotPlaying() + { + lock (_trackLock) + { + if (OboeInterop.IsPlaying(_session) == 0) + { + Start(); + } + } + } + + public override void QueueBuffer(AudioBuffer buffer) + { + lock (_queueLock) + { + OboeAudioBuffer driverBuffer = new OboeAudioBuffer(buffer.DataPointer, buffer.Data, GetSampleCount(buffer)); + + _queuedBuffers.Enqueue(driverBuffer); + + if (_isActive) + { + StartIfNotPlaying(); + } + } + } + + public override float GetVolume() + { + return OboeInterop.GetSessionVolume(_session); + } + + public override ulong GetPlayedSampleCount() + { + lock (_queueLock) + { + return _playedSampleCount; + } + } + + public override void SetVolume(float volume) + { + volume = 1; + OboeInterop.SetSessionVolume(_session, volume); + } + + public override void Start() + { + OboeInterop.StartSession(_session); + } + + public override void Stop() + { + OboeInterop.StopSession(_session); + } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + lock (_queueLock) + { + if (!_queuedBuffers.TryPeek(out OboeAudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + } + } +} diff --git a/src/LibRyujinx/Audio/Oboe/OboeInterop.cs b/src/LibRyujinx/Audio/Oboe/OboeInterop.cs new file mode 100644 index 000000000..79d9ca43e --- /dev/null +++ b/src/LibRyujinx/Audio/Oboe/OboeInterop.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace LibRyujinx.Shared.Audio.Oboe +{ + internal static partial class OboeInterop + { + private const string InteropLib = "libryujinxjni"; + + [LibraryImport(InteropLib, EntryPoint = "create_session")] + public static partial IntPtr CreateSession(int sample_format, + uint sample_rate, + uint channel_count); + + + [LibraryImport(InteropLib, EntryPoint = "start_session")] + public static partial void StartSession(IntPtr session); + + [LibraryImport(InteropLib, EntryPoint = "stop_session")] + public static partial void StopSession(IntPtr session); + + [LibraryImport(InteropLib, EntryPoint = "close_session")] + public static partial void CloseSession(IntPtr session); + + [LibraryImport(InteropLib, EntryPoint = "set_session_volume")] + public static partial void SetSessionVolume(IntPtr session, float volume); + + [LibraryImport(InteropLib, EntryPoint = "get_session_volume")] + public static partial float GetSessionVolume(IntPtr session); + + [LibraryImport(InteropLib, EntryPoint = "is_playing")] + public static partial int IsPlaying(IntPtr session); + + [LibraryImport(InteropLib, EntryPoint = "write_to_session")] + public static partial void WriteToSession(IntPtr session, ulong data, ulong samples); + } +} diff --git a/src/LibRyujinx/JniExportedMethods.cs b/src/LibRyujinx/JniExportedMethods.cs index 12597a01a..cb90f6609 100644 --- a/src/LibRyujinx/JniExportedMethods.cs +++ b/src/LibRyujinx/JniExportedMethods.cs @@ -14,6 +14,8 @@ using Ryujinx.Common.Logging; using Ryujinx.Common.Logging.Targets; using Silk.NET.Vulkan; using Silk.NET.Vulkan.Extensions.KHR; +using LibRyujinx.Shared.Audio.Oboe; + namespace LibRyujinx { @@ -34,11 +36,13 @@ namespace LibRyujinx public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef jpath) { var path = GetString(jEnv, jpath); -//"/storage/emulated/0/Android/data/org.ryujinx.k/files"; + Ryujinx.Common.SystemInfo.SystemInfo.IsBionic = true; var init = Initialize(path); + AudioDriver = new OboeHardwareDeviceDriver(); + Logger.AddTarget( new AsyncLogTargetWrapper( new AndroidLogTarget("Ryujinx"), @@ -51,20 +55,6 @@ namespace LibRyujinx private static string GetString(JEnvRef jEnv, JStringLocalRef jString) { - /*JEnvValue value = jEnv.Environment; - ref JNativeInterface jInterface = ref value.Functions; - - IntPtr newStringPtr = jInterface.GetStringUtfCharsPointer; - IntPtr releaseStringPtr = jInterface.ReleaseStringUtfCharsPointer; - var newString = newStringPtr.GetUnsafeDelegate(); - var releaseString = releaseStringPtr.GetUnsafeDelegate(); - - var stringPtr = newString(jEnv, jString, new JBooleanRef(false)); - - var str = stringPtr.AsString(); - - releaseString(jEnv, jString, stringPtr);*/ - var stringPtr = getStringPointer(jEnv, jString); var s = Marshal.PtrToStringAnsi(stringPtr); @@ -128,7 +118,6 @@ namespace LibRyujinx EnableShaderCache = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableShaderCache"), GetCCharSequence("Z"))), EnableMacroHLE = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroHLE"), GetCCharSequence("Z"))), EnableMacroJit = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroJit"), GetCCharSequence("Z"))), - EnableSpirvCompilationOnVulkan = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableSpirvCompilationOnVulkan"), GetCCharSequence("Z"))), EnableTextureRecompression = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableTextureRecompression"), GetCCharSequence("Z"))), Fast2DCopy = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("Fast2DCopy"), GetCCharSequence("Z"))), FastGpuTime = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("FastGpuTime"), GetCCharSequence("Z"))),