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<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;
+        }
+    }
+}
\ 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<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;
+            }
+        }
+    }
+}
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<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 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"))),