From 7239547eada6ad4a24e65957dfab180a51818947 Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Sun, 17 Dec 2023 00:11:20 -0500
Subject: [PATCH 1/5] android: add oboe audio sink

---
 CMakeLists.txt                       |   3 +
 src/audio_core/CMakeLists.txt        |  11 ++
 src/audio_core/sink/oboe_sink.cpp    | 184 +++++++++++++++++++++++++++
 src/audio_core/sink/oboe_sink.h      |  75 +++++++++++
 src/audio_core/sink/sink_details.cpp |  13 ++
 src/common/settings_enums.h          |   7 +-
 vcpkg.json                           |   9 ++
 7 files changed, 298 insertions(+), 4 deletions(-)
 create mode 100644 src/audio_core/sink/oboe_sink.cpp
 create mode 100644 src/audio_core/sink/oboe_sink.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 18b8f7967d..9dfc06ac37 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -142,6 +142,9 @@ if (YUZU_USE_BUNDLED_VCPKG)
     if (ENABLE_WEB_SERVICE)
         list(APPEND VCPKG_MANIFEST_FEATURES "web-service")
     endif()
+    if (ANDROID)
+        list(APPEND VCPKG_MANIFEST_FEATURES "android")
+    endif()
 
     include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
 elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 400988c5fb..e982d03bef 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -253,6 +253,17 @@ if (ENABLE_SDL2)
     target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
 endif()
 
+if (ANDROID)
+    target_sources(audio_core PRIVATE
+        sink/oboe_sink.cpp
+        sink/oboe_sink.h
+    )
+
+    # FIXME: this port seems broken, it cannot be imported with find_package(oboe REQUIRED)
+    target_link_libraries(audio_core PRIVATE "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib/liboboe.a")
+    target_compile_definitions(audio_core PRIVATE HAVE_OBOE)
+endif()
+
 if (YUZU_USE_PRECOMPILED_HEADERS)
     target_precompile_headers(audio_core PRIVATE precompiled_headers.h)
 endif()
diff --git a/src/audio_core/sink/oboe_sink.cpp b/src/audio_core/sink/oboe_sink.cpp
new file mode 100644
index 0000000000..fc62faaeee
--- /dev/null
+++ b/src/audio_core/sink/oboe_sink.cpp
@@ -0,0 +1,184 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <span>
+#include <vector>
+
+#include <oboe/Oboe.h>
+
+#include "audio_core/common/common.h"
+#include "audio_core/sink/oboe_sink.h"
+#include "audio_core/sink/sink_stream.h"
+#include "common/logging/log.h"
+#include "common/scope_exit.h"
+#include "core/core.h"
+
+namespace AudioCore::Sink {
+
+class OboeSinkStream final : public SinkStream,
+                             public oboe::AudioStreamDataCallback,
+                             public oboe::AudioStreamErrorCallback {
+public:
+    explicit OboeSinkStream(Core::System& system_, StreamType type_, const std::string& name_,
+                            u32 device_channels_, u32 system_channels_)
+        : SinkStream(system_, type_) {
+        name = name_;
+        system_channels = system_channels_;
+        device_channels = device_channels_;
+
+        this->OpenStream();
+    }
+
+    ~OboeSinkStream() override {
+        LOG_DEBUG(Audio_Sink, "Destructing Oboe stream {}", name);
+    }
+
+    void Finalize() override {
+        this->Stop();
+        m_stream.reset();
+    }
+
+    void Start(bool resume = false) override {
+        if (!m_stream || !paused) {
+            return;
+        }
+
+        paused = false;
+
+        if (m_stream->start() != oboe::Result::OK) {
+            LOG_CRITICAL(Audio_Sink, "Error starting Oboe stream");
+        }
+    }
+
+    void Stop() override {
+        if (!m_stream || paused) {
+            return;
+        }
+
+        this->SignalPause();
+
+        if (m_stream->stop() != oboe::Result::OK) {
+            LOG_CRITICAL(Audio_Sink, "Error stopping Oboe stream");
+        }
+    }
+
+protected:
+    oboe::DataCallbackResult onAudioReady(oboe::AudioStream*, void* audio_data,
+                                          s32 num_buffer_frames) override {
+        const size_t num_channels = this->GetDeviceChannels();
+        const size_t frame_size = num_channels;
+        const size_t num_frames = static_cast<size_t>(num_buffer_frames);
+
+        if (type == StreamType::In) {
+            std::span<const s16> input_buffer{reinterpret_cast<const s16*>(audio_data),
+                                              num_frames * frame_size};
+            this->ProcessAudioIn(input_buffer, num_frames);
+        } else {
+            std::span<s16> output_buffer{reinterpret_cast<s16*>(audio_data),
+                                         num_frames * frame_size};
+            this->ProcessAudioOutAndRender(output_buffer, num_frames);
+        }
+
+        return oboe::DataCallbackResult::Continue;
+    }
+
+    void onErrorAfterClose(oboe::AudioStream*, oboe::Result) override {
+        LOG_INFO(Audio_Sink, "Audio stream closed, reinitializing");
+
+        if (this->OpenStream()) {
+            m_stream->start();
+        }
+    }
+
+private:
+    bool OpenStream() {
+        const auto direction = [&]() {
+            switch (type) {
+            case StreamType::In:
+                return oboe::Direction::Input;
+            case StreamType::Out:
+            case StreamType::Render:
+                return oboe::Direction::Output;
+            default:
+                ASSERT(false);
+                return oboe::Direction::Output;
+            }
+        }();
+
+        const auto channel_mask = [&]() {
+            switch (device_channels) {
+            case 1:
+                return oboe::ChannelMask::Mono;
+            case 2:
+                return oboe::ChannelMask::Stereo;
+            case 6:
+                return oboe::ChannelMask::CM5Point1;
+            default:
+                ASSERT(false);
+                return oboe::ChannelMask::Unspecified;
+            }
+        }();
+
+        oboe::AudioStreamBuilder builder;
+        const auto result = builder.setDirection(direction)
+                                ->setSampleRate(TargetSampleRate)
+                                ->setChannelCount(device_channels)
+                                ->setChannelMask(channel_mask)
+                                ->setFormat(oboe::AudioFormat::I16)
+                                ->setFormatConversionAllowed(true)
+                                ->setDataCallback(this)
+                                ->setErrorCallback(this)
+                                ->openStream(m_stream);
+
+        ASSERT(result == oboe::Result::OK);
+        return result == oboe::Result::OK;
+    }
+
+    std::shared_ptr<oboe::AudioStream> m_stream{};
+};
+
+OboeSink::OboeSink() {
+    // TODO: how do we get the number of channels, or device list?
+    // This seems to be missing from NDK.
+    device_channels = 2;
+}
+
+OboeSink::~OboeSink() = default;
+
+SinkStream* OboeSink::AcquireSinkStream(Core::System& system, u32 system_channels,
+                                        const std::string& name, StreamType type) {
+    SinkStreamPtr& stream = sink_streams.emplace_back(
+        std::make_unique<OboeSinkStream>(system, type, name, device_channels, system_channels));
+
+    return stream.get();
+}
+
+void OboeSink::CloseStream(SinkStream* to_remove) {
+    sink_streams.remove_if([&](auto& stream) { return stream.get() == to_remove; });
+}
+
+void OboeSink::CloseStreams() {
+    sink_streams.clear();
+}
+
+f32 OboeSink::GetDeviceVolume() const {
+    if (sink_streams.empty()) {
+        return 1.0f;
+    }
+
+    return sink_streams.front()->GetDeviceVolume();
+}
+
+void OboeSink::SetDeviceVolume(f32 volume) {
+    for (auto& stream : sink_streams) {
+        stream->SetDeviceVolume(volume);
+    }
+}
+
+void OboeSink::SetSystemVolume(f32 volume) {
+    for (auto& stream : sink_streams) {
+        stream->SetSystemVolume(volume);
+    }
+}
+
+} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/oboe_sink.h b/src/audio_core/sink/oboe_sink.h
new file mode 100644
index 0000000000..8f6f54ab53
--- /dev/null
+++ b/src/audio_core/sink/oboe_sink.h
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <list>
+#include <string>
+
+#include "audio_core/sink/sink.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore::Sink {
+class SinkStream;
+
+class OboeSink final : public Sink {
+public:
+    explicit OboeSink();
+    ~OboeSink() override;
+
+    /**
+     * Create a new sink stream.
+     *
+     * @param system          - Core system.
+     * @param system_channels - Number of channels the audio system expects.
+     *                          May differ from the device's channel count.
+     * @param name            - Name of this stream.
+     * @param type            - Type of this stream, render/in/out.
+     *
+     * @return A pointer to the created SinkStream
+     */
+    SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
+                                  const std::string& name, StreamType type) override;
+
+    /**
+     * Close a given stream.
+     *
+     * @param stream - The stream to close.
+     */
+    void CloseStream(SinkStream* stream) override;
+
+    /**
+     * Close all streams.
+     */
+    void CloseStreams() override;
+
+    /**
+     * Get the device volume. Set from calls to the IAudioDevice service.
+     *
+     * @return Volume of the device.
+     */
+    f32 GetDeviceVolume() const override;
+
+    /**
+     * Set the device volume. Set from calls to the IAudioDevice service.
+     *
+     * @param volume - New volume of the device.
+     */
+    void SetDeviceVolume(f32 volume) override;
+
+    /**
+     * Set the system volume. Comes from the audio system using this stream.
+     *
+     * @param volume - New volume of the system.
+     */
+    void SetSystemVolume(f32 volume) override;
+
+private:
+    /// List of streams managed by this sink
+    std::list<SinkStreamPtr> sink_streams{};
+};
+
+} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp
index 7c9a4e3acf..449af659d0 100644
--- a/src/audio_core/sink/sink_details.cpp
+++ b/src/audio_core/sink/sink_details.cpp
@@ -7,6 +7,9 @@
 #include <vector>
 
 #include "audio_core/sink/sink_details.h"
+#ifdef HAVE_OBOE
+#include "audio_core/sink/oboe_sink.h"
+#endif
 #ifdef HAVE_CUBEB
 #include "audio_core/sink/cubeb_sink.h"
 #endif
@@ -36,6 +39,16 @@ struct SinkDetails {
 
 // sink_details is ordered in terms of desirability, with the best choice at the top.
 constexpr SinkDetails sink_details[] = {
+#ifdef HAVE_OBOE
+    SinkDetails{
+        Settings::AudioEngine::Oboe,
+        [](std::string_view device_id) -> std::unique_ptr<Sink> {
+            return std::make_unique<OboeSink>();
+        },
+        [](bool capture) { return std::vector<std::string>{"Default"}; },
+        []() { return true; },
+    },
+#endif
 #ifdef HAVE_CUBEB
     SinkDetails{
         Settings::AudioEngine::Cubeb,
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index d6351e57e9..6170365886 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -82,16 +82,15 @@ enum class AudioEngine : u32 {
     Cubeb,
     Sdl2,
     Null,
+    Oboe,
 };
 
 template <>
 inline std::vector<std::pair<std::string, AudioEngine>>
 EnumMetadata<AudioEngine>::Canonicalizations() {
     return {
-        {"auto", AudioEngine::Auto},
-        {"cubeb", AudioEngine::Cubeb},
-        {"sdl2", AudioEngine::Sdl2},
-        {"null", AudioEngine::Null},
+        {"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2},
+        {"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe},
     };
 }
 
diff --git a/vcpkg.json b/vcpkg.json
index 01a4657d41..a9e895153e 100644
--- a/vcpkg.json
+++ b/vcpkg.json
@@ -41,6 +41,15 @@
                     "platform": "windows"
                 }
             ]
+        },
+        "android": {
+            "description": "Enable Android dependencies",
+            "dependencies": [
+                {
+                    "name": "oboe",
+                    "platform": "android"
+                }
+            ]
         }
     },
     "overrides": [

From e01c5351781f7c0a8f5e6469a33563f26f8f0779 Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Sun, 17 Dec 2023 02:01:25 -0500
Subject: [PATCH 2/5] oboe_sink: implement channel count querying

---
 src/audio_core/sink/oboe_sink.cpp | 47 +++++++++++++++++++++++--------
 1 file changed, 35 insertions(+), 12 deletions(-)

diff --git a/src/audio_core/sink/oboe_sink.cpp b/src/audio_core/sink/oboe_sink.cpp
index fc62faaeee..c502205e1d 100644
--- a/src/audio_core/sink/oboe_sink.cpp
+++ b/src/audio_core/sink/oboe_sink.cpp
@@ -20,11 +20,10 @@ class OboeSinkStream final : public SinkStream,
                              public oboe::AudioStreamErrorCallback {
 public:
     explicit OboeSinkStream(Core::System& system_, StreamType type_, const std::string& name_,
-                            u32 device_channels_, u32 system_channels_)
+                            u32 system_channels_)
         : SinkStream(system_, type_) {
         name = name_;
         system_channels = system_channels_;
-        device_channels = device_channels_;
 
         this->OpenStream();
     }
@@ -62,6 +61,21 @@ public:
         }
     }
 
+public:
+    static s32 QueryChannelCount(oboe::Direction direction) {
+        std::shared_ptr<oboe::AudioStream> temp_stream;
+        oboe::AudioStreamBuilder builder;
+
+        const auto result = builder.setDirection(direction)
+                                ->setSampleRate(TargetSampleRate)
+                                ->setFormat(oboe::AudioFormat::I16)
+                                ->setFormatConversionAllowed(true)
+                                ->openStream(temp_stream);
+        ASSERT(result == oboe::Result::OK);
+
+        return temp_stream->getChannelCount() >= 6 ? 6 : 2;
+    }
+
 protected:
     oboe::DataCallbackResult onAudioReady(oboe::AudioStream*, void* audio_data,
                                           s32 num_buffer_frames) override {
@@ -105,8 +119,9 @@ private:
             }
         }();
 
-        const auto channel_mask = [&]() {
-            switch (device_channels) {
+        const auto expected_channels = QueryChannelCount(direction);
+        const auto expected_mask = [&]() {
+            switch (expected_channels) {
             case 1:
                 return oboe::ChannelMask::Mono;
             case 2:
@@ -122,25 +137,33 @@ private:
         oboe::AudioStreamBuilder builder;
         const auto result = builder.setDirection(direction)
                                 ->setSampleRate(TargetSampleRate)
-                                ->setChannelCount(device_channels)
-                                ->setChannelMask(channel_mask)
+                                ->setChannelCount(expected_channels)
+                                ->setChannelMask(expected_mask)
                                 ->setFormat(oboe::AudioFormat::I16)
                                 ->setFormatConversionAllowed(true)
                                 ->setDataCallback(this)
                                 ->setErrorCallback(this)
                                 ->openStream(m_stream);
-
         ASSERT(result == oboe::Result::OK);
-        return result == oboe::Result::OK;
+        return result == oboe::Result::OK && this->SetStreamProperties();
+    }
+
+    bool SetStreamProperties() {
+        ASSERT(m_stream);
+
+        device_channels = m_stream->getChannelCount();
+        LOG_INFO(Audio_Sink, "Opened Oboe stream with {} channels", device_channels);
+
+        return true;
     }
 
     std::shared_ptr<oboe::AudioStream> m_stream{};
 };
 
 OboeSink::OboeSink() {
-    // TODO: how do we get the number of channels, or device list?
-    // This seems to be missing from NDK.
-    device_channels = 2;
+    // TODO: This is not generally knowable
+    // The channel count is distinct based on direction and can change
+    device_channels = OboeSinkStream::QueryChannelCount(oboe::Direction::Output);
 }
 
 OboeSink::~OboeSink() = default;
@@ -148,7 +171,7 @@ OboeSink::~OboeSink() = default;
 SinkStream* OboeSink::AcquireSinkStream(Core::System& system, u32 system_channels,
                                         const std::string& name, StreamType type) {
     SinkStreamPtr& stream = sink_streams.emplace_back(
-        std::make_unique<OboeSinkStream>(system, type, name, device_channels, system_channels));
+        std::make_unique<OboeSinkStream>(system, type, name, system_channels));
 
     return stream.get();
 }

From 6ca530a72164878718a2bded90bf139bf4636873 Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Sun, 17 Dec 2023 11:44:49 -0500
Subject: [PATCH 3/5] android: add oboe to audio configuration

---
 src/android/app/src/main/res/values/arrays.xml  | 2 ++
 src/android/app/src/main/res/values/strings.xml | 1 +
 2 files changed, 3 insertions(+)

diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index ab435dce99..e3915ef4f7 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -256,11 +256,13 @@
 
     <string-array name="outputEngineEntries">
         <item>@string/auto</item>
+        <item>@string/oboe</item>
         <item>@string/cubeb</item>
         <item>@string/string_null</item>
     </string-array>
     <integer-array name="outputEngineValues">
         <item>0</item>
+        <item>4</item>
         <item>1</item>
         <item>3</item>
     </integer-array>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index c86c43df24..0b80b04a49 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -503,6 +503,7 @@
     <string name="theme_mode_dark">Dark</string>
 
     <!-- Audio output engines -->
+    <string name="oboe">oboe</string>
     <string name="cubeb">cubeb</string>
 
     <!-- Black backgrounds theme -->

From 797e8fdbc3b6a9220544596099ceddea0739e31c Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Sun, 17 Dec 2023 21:05:00 -0500
Subject: [PATCH 4/5] oboe_sink: set low latency performance mode

---
 src/audio_core/sink/oboe_sink.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/audio_core/sink/oboe_sink.cpp b/src/audio_core/sink/oboe_sink.cpp
index c502205e1d..8143b0db61 100644
--- a/src/audio_core/sink/oboe_sink.cpp
+++ b/src/audio_core/sink/oboe_sink.cpp
@@ -67,6 +67,7 @@ public:
         oboe::AudioStreamBuilder builder;
 
         const auto result = builder.setDirection(direction)
+                                ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
                                 ->setSampleRate(TargetSampleRate)
                                 ->setFormat(oboe::AudioFormat::I16)
                                 ->setFormatConversionAllowed(true)
@@ -136,6 +137,7 @@ private:
 
         oboe::AudioStreamBuilder builder;
         const auto result = builder.setDirection(direction)
+                                ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
                                 ->setSampleRate(TargetSampleRate)
                                 ->setChannelCount(expected_channels)
                                 ->setChannelMask(expected_mask)

From a7731abb72e80c26d5af771d17233652435a0c7d Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Mon, 18 Dec 2023 09:22:20 -0500
Subject: [PATCH 5/5] oboe_sink: specify additional required parameters

---
 src/audio_core/sink/oboe_sink.cpp | 40 +++++++++++++++++++++----------
 1 file changed, 27 insertions(+), 13 deletions(-)

diff --git a/src/audio_core/sink/oboe_sink.cpp b/src/audio_core/sink/oboe_sink.cpp
index 8143b0db61..e618411722 100644
--- a/src/audio_core/sink/oboe_sink.cpp
+++ b/src/audio_core/sink/oboe_sink.cpp
@@ -29,7 +29,7 @@ public:
     }
 
     ~OboeSinkStream() override {
-        LOG_DEBUG(Audio_Sink, "Destructing Oboe stream {}", name);
+        LOG_INFO(Audio_Sink, "Destroyed Oboe stream");
     }
 
     void Finalize() override {
@@ -66,12 +66,7 @@ public:
         std::shared_ptr<oboe::AudioStream> temp_stream;
         oboe::AudioStreamBuilder builder;
 
-        const auto result = builder.setDirection(direction)
-                                ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
-                                ->setSampleRate(TargetSampleRate)
-                                ->setFormat(oboe::AudioFormat::I16)
-                                ->setFormatConversionAllowed(true)
-                                ->openStream(temp_stream);
+        const auto result = ConfigureBuilder(builder, direction)->openStream(temp_stream);
         ASSERT(result == oboe::Result::OK);
 
         return temp_stream->getChannelCount() >= 6 ? 6 : 2;
@@ -106,6 +101,20 @@ protected:
     }
 
 private:
+    static oboe::AudioStreamBuilder* ConfigureBuilder(oboe::AudioStreamBuilder& builder,
+                                                      oboe::Direction direction) {
+        // TODO: investigate callback delay issues when using AAudio
+        return builder.setPerformanceMode(oboe::PerformanceMode::LowLatency)
+            ->setAudioApi(oboe::AudioApi::OpenSLES)
+            ->setDirection(direction)
+            ->setSampleRate(TargetSampleRate)
+            ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
+            ->setFormat(oboe::AudioFormat::I16)
+            ->setFormatConversionAllowed(true)
+            ->setUsage(oboe::Usage::Game)
+            ->setBufferCapacityInFrames(TargetSampleCount * 2);
+    }
+
     bool OpenStream() {
         const auto direction = [&]() {
             switch (type) {
@@ -136,13 +145,10 @@ private:
         }();
 
         oboe::AudioStreamBuilder builder;
-        const auto result = builder.setDirection(direction)
-                                ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
-                                ->setSampleRate(TargetSampleRate)
+        const auto result = ConfigureBuilder(builder, direction)
                                 ->setChannelCount(expected_channels)
                                 ->setChannelMask(expected_mask)
-                                ->setFormat(oboe::AudioFormat::I16)
-                                ->setFormatConversionAllowed(true)
+                                ->setChannelConversionAllowed(true)
                                 ->setDataCallback(this)
                                 ->setErrorCallback(this)
                                 ->openStream(m_stream);
@@ -153,8 +159,16 @@ private:
     bool SetStreamProperties() {
         ASSERT(m_stream);
 
+        m_stream->setBufferSizeInFrames(TargetSampleCount * 2);
         device_channels = m_stream->getChannelCount();
-        LOG_INFO(Audio_Sink, "Opened Oboe stream with {} channels", device_channels);
+
+        const auto sample_rate = m_stream->getSampleRate();
+        const auto buffer_capacity = m_stream->getBufferCapacityInFrames();
+        const auto stream_backend =
+            m_stream->getAudioApi() == oboe::AudioApi::AAudio ? "AAudio" : "OpenSLES";
+
+        LOG_INFO(Audio_Sink, "Opened Oboe {} stream with {} channels sample rate {} capacity {}",
+                 stream_backend, device_channels, sample_rate, buffer_capacity);
 
         return true;
     }