diff --git a/android/app/src/cpp/CMakeLists.txt b/android/app/src/cpp/CMakeLists.txt
index 94f6e71ea..d8408d25c 100644
--- a/android/app/src/cpp/CMakeLists.txt
+++ b/android/app/src/cpp/CMakeLists.txt
@@ -7,3 +7,13 @@ set(SRCS
 
 add_library(duckstation-native SHARED ${SRCS})
 target_link_libraries(duckstation-native PRIVATE android frontend-common core common glad imgui)
+
+find_package(OpenSLES)
+if(OPENSLES_FOUND)
+  message("Enabling OpenSL ES audio stream")
+  target_sources(duckstation-native PRIVATE
+    opensles_audio_stream.cpp
+    opensles_audio_stream.h)
+  target_link_libraries(duckstation-native PRIVATE OpenSLES::OpenSLES)
+  target_compile_definitions(duckstation-native PRIVATE "-DUSE_OPENSLES=1")
+endif()
diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp
index a3ef5f223..c0e41baa1 100644
--- a/android/app/src/cpp/android_host_interface.cpp
+++ b/android/app/src/cpp/android_host_interface.cpp
@@ -20,6 +20,10 @@
 #include <imgui.h>
 Log_SetChannel(AndroidHostInterface);
 
+#ifdef USE_OPENSLES
+#include "opensles_audio_stream.h"
+#endif
+
 static JavaVM* s_jvm;
 static jclass s_AndroidHostInterface_class;
 static jmethodID s_AndroidHostInterface_constructor;
@@ -394,6 +398,16 @@ void AndroidHostInterface::ReleaseHostDisplay()
   m_display.reset();
 }
 
+std::unique_ptr<AudioStream> AndroidHostInterface::CreateAudioStream(AudioBackend backend)
+{
+#ifdef USE_OPENSLES
+  if (backend == AudioBackend::OpenSLES)
+    return OpenSLESAudioStream::Create();
+#endif
+
+  return CommonHostInterface::CreateAudioStream(backend);
+}
+
 void AndroidHostInterface::OnSystemDestroyed()
 {
   CommonHostInterface::OnSystemDestroyed();
diff --git a/android/app/src/cpp/android_host_interface.h b/android/app/src/cpp/android_host_interface.h
index 4c8af0dad..a2ae84012 100644
--- a/android/app/src/cpp/android_host_interface.h
+++ b/android/app/src/cpp/android_host_interface.h
@@ -59,6 +59,7 @@ protected:
 
   bool AcquireHostDisplay() override;
   void ReleaseHostDisplay() override;
+  std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) override;
 
   void OnSystemDestroyed() override;
   void OnRunningGameChanged() override;
diff --git a/android/app/src/cpp/opensles_audio_stream.cpp b/android/app/src/cpp/opensles_audio_stream.cpp
new file mode 100644
index 000000000..92baf69b8
--- /dev/null
+++ b/android/app/src/cpp/opensles_audio_stream.cpp
@@ -0,0 +1,167 @@
+#include "opensles_audio_stream.h"
+#include "common/assert.h"
+#include "common/log.h"
+#include <cmath>
+Log_SetChannel(OpenSLESAudioStream);
+
+// Based off Dolphin's OpenSLESStream class.
+
+OpenSLESAudioStream::OpenSLESAudioStream() = default;
+
+OpenSLESAudioStream::~OpenSLESAudioStream()
+{
+  if (IsOpen())
+    OpenSLESAudioStream::CloseDevice();
+}
+
+std::unique_ptr<AudioStream> OpenSLESAudioStream::Create()
+{
+  return std::make_unique<OpenSLESAudioStream>();
+}
+
+bool OpenSLESAudioStream::OpenDevice()
+{
+  DebugAssert(!IsOpen());
+
+  SLresult res = slCreateEngine(&m_engine, 0, nullptr, 0, nullptr, nullptr);
+  if (res != SL_RESULT_SUCCESS)
+  {
+    Log_ErrorPrintf("slCreateEngine failed: %d", res);
+    return false;
+  }
+
+  res = (*m_engine)->Realize(m_engine, SL_BOOLEAN_FALSE);
+  if (res == SL_RESULT_SUCCESS)
+    res = (*m_engine)->GetInterface(m_engine, SL_IID_ENGINE, &m_engine_engine);
+  if (res == SL_RESULT_SUCCESS)
+    res = (*m_engine_engine)->CreateOutputMix(m_engine_engine, &m_output_mix, 0, 0, 0);
+  if (res == SL_RESULT_SUCCESS)
+    res = (*m_output_mix)->Realize(m_output_mix, SL_BOOLEAN_FALSE);
+  if (res != SL_RESULT_SUCCESS)
+  {
+    Log_ErrorPrintf("Failed to create engine/output mix");
+    CloseDevice();
+    return false;
+  }
+
+  SLDataLocator_AndroidSimpleBufferQueue dloc_bq{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, NUM_BUFFERS};
+  SLDataFormat_PCM format = {SL_DATAFORMAT_PCM,
+                             m_channels,
+                             m_output_sample_rate * 1000u,
+                             SL_PCMSAMPLEFORMAT_FIXED_16,
+                             SL_PCMSAMPLEFORMAT_FIXED_16,
+                             SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
+                             SL_BYTEORDER_LITTLEENDIAN};
+  SLDataSource dsrc{&dloc_bq, &format};
+  SLDataLocator_OutputMix dloc_outputmix{SL_DATALOCATOR_OUTPUTMIX, m_output_mix};
+  SLDataSink dsink{&dloc_outputmix, nullptr};
+
+  const std::array<SLInterfaceID, 2> ap_interfaces = {{SL_IID_BUFFERQUEUE, SL_IID_VOLUME}};
+  const std::array<SLboolean, 2> ap_interfaces_req = {{true, true}};
+  res = (*m_engine_engine)
+          ->CreateAudioPlayer(m_engine_engine, &m_player, &dsrc, &dsink, static_cast<u32>(ap_interfaces.size()),
+                              ap_interfaces.data(), ap_interfaces_req.data());
+  if (res != SL_RESULT_SUCCESS)
+  {
+    Log_ErrorPrintf("Failed to create audio player: %d", res);
+    CloseDevice();
+    return false;
+  }
+
+  res = (*m_player)->Realize(m_player, SL_BOOLEAN_FALSE);
+  if (res == SL_RESULT_SUCCESS)
+    res = (*m_player)->GetInterface(m_player, SL_IID_PLAY, &m_play_interface);
+  if (res == SL_RESULT_SUCCESS)
+    res = (*m_player)->GetInterface(m_player, SL_IID_BUFFERQUEUE, &m_buffer_queue_interface);
+  if (res == SL_RESULT_SUCCESS)
+    res = (*m_player)->GetInterface(m_player, SL_IID_VOLUME, &m_volume_interface);
+  if (res != SL_RESULT_SUCCESS)
+  {
+    Log_ErrorPrintf("Failed to get player interfaces: %d", res);
+    CloseDevice();
+    return false;
+  }
+
+  res = (*m_buffer_queue_interface)->RegisterCallback(m_buffer_queue_interface, BufferCallback, this);
+  if (res != SL_RESULT_SUCCESS)
+  {
+    Log_ErrorPrintf("Failed to register callback: %d", res);
+    CloseDevice();
+    return false;
+  }
+
+  for (u32 i = 0; i < NUM_BUFFERS; i++)
+    m_buffers[i] = std::make_unique<SampleType[]>(m_buffer_size * m_channels);
+
+  return true;
+}
+
+void OpenSLESAudioStream::PauseDevice(bool paused)
+{
+  if (m_paused == paused)
+    return;
+
+  (*m_play_interface)->SetPlayState(m_play_interface, paused ? SL_PLAYSTATE_PAUSED : SL_PLAYSTATE_PLAYING);
+
+  if (!paused && !m_buffer_enqueued)
+  {
+    m_buffer_enqueued = true;
+    EnqueueBuffer();
+  }
+
+  m_paused = paused;
+}
+
+void OpenSLESAudioStream::CloseDevice()
+{
+  m_buffers = {};
+  m_current_buffer = 0;
+  m_paused = true;
+  m_buffer_enqueued = false;
+
+  if (m_player)
+  {
+    (*m_player)->Destroy(m_player);
+    m_volume_interface = {};
+    m_buffer_queue_interface = {};
+    m_play_interface = {};
+    m_player = {};
+  }
+  if (m_output_mix)
+  {
+    (*m_output_mix)->Destroy(m_output_mix);
+    m_output_mix = {};
+  }
+  (*m_engine)->Destroy(m_engine);
+  m_engine_engine = {};
+  m_engine = {};
+}
+
+void OpenSLESAudioStream::SetOutputVolume(u32 volume)
+{
+  const SLmillibel attenuation = (volume == 0) ?
+                                   SL_MILLIBEL_MIN :
+                                   static_cast<SLmillibel>(2000.0f * std::log10(static_cast<float>(volume) / 100.0f));
+  (*m_volume_interface)->SetVolumeLevel(m_volume_interface, attenuation);
+}
+
+void OpenSLESAudioStream::EnqueueBuffer()
+{
+  SampleType* samples = m_buffers[m_current_buffer].get();
+  ReadFrames(samples, m_buffer_size, false);
+
+  SLresult res = (*m_buffer_queue_interface)
+                   ->Enqueue(m_buffer_queue_interface, samples, m_buffer_size * m_channels * sizeof(SampleType));
+  if (res != SL_RESULT_SUCCESS)
+    Log_ErrorPrintf("Enqueue buffer failed: %d", res);
+
+  m_current_buffer = (m_current_buffer + 1) % NUM_BUFFERS;
+}
+
+void OpenSLESAudioStream::BufferCallback(SLAndroidSimpleBufferQueueItf buffer_queue, void* context)
+{
+  OpenSLESAudioStream* const this_ptr = static_cast<OpenSLESAudioStream*>(context);
+  this_ptr->EnqueueBuffer();
+}
+
+void OpenSLESAudioStream::FramesAvailable() {}
diff --git a/android/app/src/cpp/opensles_audio_stream.h b/android/app/src/cpp/opensles_audio_stream.h
new file mode 100644
index 000000000..83c54aa6e
--- /dev/null
+++ b/android/app/src/cpp/opensles_audio_stream.h
@@ -0,0 +1,48 @@
+#pragma once
+#include "common/audio_stream.h"
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+#include <array>
+#include <memory>
+
+class OpenSLESAudioStream final : public AudioStream
+{
+public:
+  OpenSLESAudioStream();
+  ~OpenSLESAudioStream();
+
+  static std::unique_ptr<AudioStream> Create();
+
+  void SetOutputVolume(u32 volume) override;
+
+protected:
+  enum : u32
+  {
+    NUM_BUFFERS = 2
+  };
+
+  ALWAYS_INLINE bool IsOpen() const { return (m_engine != nullptr); }
+
+  bool OpenDevice() override;
+  void PauseDevice(bool paused) override;
+  void CloseDevice() override;
+  void FramesAvailable() override;
+
+  void EnqueueBuffer();
+
+  static void BufferCallback(SLAndroidSimpleBufferQueueItf buffer_queue, void* context);
+
+  SLObjectItf m_engine{};
+  SLEngineItf m_engine_engine{};
+  SLObjectItf m_output_mix{};
+
+  SLObjectItf m_player{};
+  SLPlayItf m_play_interface{};
+  SLAndroidSimpleBufferQueueItf m_buffer_queue_interface{};
+  SLVolumeItf m_volume_interface{};
+
+  std::array<std::unique_ptr<SampleType[]>, NUM_BUFFERS> m_buffers;
+  u32 m_current_buffer = 0;
+  bool m_paused = true;
+  bool m_buffer_enqueued = false;
+};
diff --git a/android/app/src/main/res/values/arrays.xml b/android/app/src/main/res/values/arrays.xml
index e50d31fc8..3fa092f53 100644
--- a/android/app/src/main/res/values/arrays.xml
+++ b/android/app/src/main/res/values/arrays.xml
@@ -178,10 +178,20 @@
         <item>analog_stick</item>
         <item>analog_sticks</item>
     </string-array>
+    <string-array name="settings_audio_backend_entries">
+        <item>Null (No Output)</item>
+        <item>Cubeb</item>
+        <item>OpenSL ES (Recommended)</item>
+    </string-array>
+    <string-array name="settings_audio_backend_values">
+        <item>Null</item>
+        <item>Cubeb</item>
+        <item>OpenSLES</item>
+    </string-array>
     <string-array name="settings_audio_buffer_size_entries">
         <item>1024 Frames (23.22ms)</item>
-        <item>2048 Frames (46.44ms)</item>
-        <item>3072 Frames (69.66ms, Recommended)</item>
+        <item>2048 Frames (46.44ms, Recommended)</item>
+        <item>3072 Frames (69.66ms)</item>
         <item>4096 Frames (92.88ms)</item>
     </string-array>
     <string-array name="settings_audio_buffer_size_values">
diff --git a/android/app/src/main/res/xml/root_preferences.xml b/android/app/src/main/res/xml/root_preferences.xml
index d6b2ffabb..a28be012d 100644
--- a/android/app/src/main/res/xml/root_preferences.xml
+++ b/android/app/src/main/res/xml/root_preferences.xml
@@ -300,12 +300,20 @@
             app:defaultValue="false"
             app:summary="Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable background music in some games."
             app:iconSpaceReserved="false" />
+        <ListPreference
+            app:key="Audio/Backend"
+            app:title="Audio Backend"
+            app:entries="@array/settings_audio_backend_entries"
+            app:entryValues="@array/settings_audio_backend_values"
+            app:defaultValue="OpenSLES"
+            app:useSimpleSummaryProvider="true"
+            app:iconSpaceReserved="false"/>
         <ListPreference
             app:key="Audio/BufferSize"
             app:title="Audio Buffer Size"
             app:entries="@array/settings_audio_buffer_size_entries"
             app:entryValues="@array/settings_audio_buffer_size_values"
-            app:defaultValue="3072"
+            app:defaultValue="2048"
             app:summary="Determines the latency between audio being generated and output to speakers. Smaller values reduce latency, but variations in emulation speed will cause hitches."
             app:useSimpleSummaryProvider="true"
             app:iconSpaceReserved="false" />
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 2e2ca2bfa..af5a1c949 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -592,10 +592,24 @@ float Settings::GetDisplayAspectRatioValue(DisplayAspectRatio ar)
   return s_display_aspect_ratio_values[static_cast<int>(ar)];
 }
 
-static std::array<const char*, 3> s_audio_backend_names = {{"Null", "Cubeb", "SDL"}};
-static std::array<const char*, 3> s_audio_backend_display_names = {{TRANSLATABLE("AudioBackend", "Null (No Output)"),
-                                                                    TRANSLATABLE("AudioBackend", "Cubeb"),
-                                                                    TRANSLATABLE("AudioBackend", "SDL")}};
+static std::array<const char*, 3> s_audio_backend_names = {{
+  "Null",
+  "Cubeb",
+#ifndef ANDROID
+  "SDL",
+#else
+  "OpenSLES",
+#endif
+}};
+static std::array<const char*, 3> s_audio_backend_display_names = {{
+  TRANSLATABLE("AudioBackend", "Null (No Output)"),
+  TRANSLATABLE("AudioBackend", "Cubeb"),
+#ifndef ANDROID
+  TRANSLATABLE("AudioBackend", "SDL"),
+#else
+  TRANSLATABLE("AudioBackend", "OpenSL ES"),
+#endif
+}};
 
 std::optional<AudioBackend> Settings::ParseAudioBackend(const char* str)
 {
diff --git a/src/core/settings.h b/src/core/settings.h
index d5d589c44..d1f3786c2 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -251,8 +251,15 @@ struct Settings
 #endif
   static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest;
   static constexpr ConsoleRegion DEFAULT_CONSOLE_REGION = ConsoleRegion::Auto;
+
   static constexpr CPUExecutionMode DEFAULT_CPU_EXECUTION_MODE = CPUExecutionMode::Recompiler;
+
+#ifndef ANDROID
   static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::Cubeb;
+#else
+  static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::OpenSLES;
+#endif
+
   static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
   static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::R4_3;
   static constexpr ControllerType DEFAULT_CONTROLLER_1_TYPE = ControllerType::DigitalController;
diff --git a/src/core/types.h b/src/core/types.h
index 0fb6bdd33..a8834eef6 100644
--- a/src/core/types.h
+++ b/src/core/types.h
@@ -97,7 +97,11 @@ enum class AudioBackend : u8
 {
   Null,
   Cubeb,
+#ifndef ANDROID
   SDL,
+#else
+  OpenSLES,
+#endif
   Count
 };