forked from MeloNX/MeloNX
add audio support for android
This commit is contained in:
parent
a57cad5d5e
commit
ba8028d1d0
18
src/LibRyujinx/Audio/Oboe/OboeAudioBuffer.cs
Normal file
18
src/LibRyujinx/Audio/Oboe/OboeAudioBuffer.cs
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
108
src/LibRyujinx/Audio/Oboe/OboeHardwareDeviceDriver.cs
Normal file
108
src/LibRyujinx/Audio/Oboe/OboeHardwareDeviceDriver.cs
Normal file
@ -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<OboeHardwareDeviceSession, byte> _sessions;
|
||||||
|
|
||||||
|
public OboeHardwareDeviceDriver()
|
||||||
|
{
|
||||||
|
_updateRequiredEvent = new ManualResetEvent(false);
|
||||||
|
_pauseEvent = new ManualResetEvent(true);
|
||||||
|
_sessions = new ConcurrentDictionary<OboeHardwareDeviceSession, byte>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
169
src/LibRyujinx/Audio/Oboe/OboeHardwareDeviceSession.cs
Normal file
169
src/LibRyujinx/Audio/Oboe/OboeHardwareDeviceSession.cs
Normal file
@ -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<OboeAudioBuffer> _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<OboeAudioBuffer>();
|
||||||
|
_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
src/LibRyujinx/Audio/Oboe/OboeInterop.cs
Normal file
41
src/LibRyujinx/Audio/Oboe/OboeInterop.cs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,8 @@ using Ryujinx.Common.Logging;
|
|||||||
using Ryujinx.Common.Logging.Targets;
|
using Ryujinx.Common.Logging.Targets;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using Silk.NET.Vulkan.Extensions.KHR;
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
|
using LibRyujinx.Shared.Audio.Oboe;
|
||||||
|
|
||||||
|
|
||||||
namespace LibRyujinx
|
namespace LibRyujinx
|
||||||
{
|
{
|
||||||
@ -34,11 +36,13 @@ namespace LibRyujinx
|
|||||||
public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef jpath)
|
public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef jpath)
|
||||||
{
|
{
|
||||||
var path = GetString(jEnv, jpath);
|
var path = GetString(jEnv, jpath);
|
||||||
//"/storage/emulated/0/Android/data/org.ryujinx.k/files";
|
|
||||||
Ryujinx.Common.SystemInfo.SystemInfo.IsBionic = true;
|
Ryujinx.Common.SystemInfo.SystemInfo.IsBionic = true;
|
||||||
|
|
||||||
var init = Initialize(path);
|
var init = Initialize(path);
|
||||||
|
|
||||||
|
AudioDriver = new OboeHardwareDeviceDriver();
|
||||||
|
|
||||||
Logger.AddTarget(
|
Logger.AddTarget(
|
||||||
new AsyncLogTargetWrapper(
|
new AsyncLogTargetWrapper(
|
||||||
new AndroidLogTarget("Ryujinx"),
|
new AndroidLogTarget("Ryujinx"),
|
||||||
@ -51,20 +55,6 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
private static string GetString(JEnvRef jEnv, JStringLocalRef jString)
|
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<GetStringUtfCharsDelegate>();
|
|
||||||
var releaseString = releaseStringPtr.GetUnsafeDelegate<ReleaseStringUtfCharsDelegate>();
|
|
||||||
|
|
||||||
var stringPtr = newString(jEnv, jString, new JBooleanRef(false));
|
|
||||||
|
|
||||||
var str = stringPtr.AsString();
|
|
||||||
|
|
||||||
releaseString(jEnv, jString, stringPtr);*/
|
|
||||||
|
|
||||||
var stringPtr = getStringPointer(jEnv, jString);
|
var stringPtr = getStringPointer(jEnv, jString);
|
||||||
|
|
||||||
var s = Marshal.PtrToStringAnsi(stringPtr);
|
var s = Marshal.PtrToStringAnsi(stringPtr);
|
||||||
@ -128,7 +118,6 @@ namespace LibRyujinx
|
|||||||
EnableShaderCache = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableShaderCache"), GetCCharSequence("Z"))),
|
EnableShaderCache = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableShaderCache"), GetCCharSequence("Z"))),
|
||||||
EnableMacroHLE = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroHLE"), GetCCharSequence("Z"))),
|
EnableMacroHLE = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroHLE"), GetCCharSequence("Z"))),
|
||||||
EnableMacroJit = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroJit"), 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"))),
|
EnableTextureRecompression = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableTextureRecompression"), GetCCharSequence("Z"))),
|
||||||
Fast2DCopy = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("Fast2DCopy"), GetCCharSequence("Z"))),
|
Fast2DCopy = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("Fast2DCopy"), GetCCharSequence("Z"))),
|
||||||
FastGpuTime = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("FastGpuTime"), GetCCharSequence("Z"))),
|
FastGpuTime = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("FastGpuTime"), GetCCharSequence("Z"))),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user