android: add oboe audio sink
This commit is contained in:
		@@ -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 "")
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										184
									
								
								src/audio_core/sink/oboe_sink.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								src/audio_core/sink/oboe_sink.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										75
									
								
								src/audio_core/sink/oboe_sink.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/audio_core/sink/oboe_sink.h
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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},
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,15 @@
 | 
			
		||||
                    "platform": "windows"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "android": {
 | 
			
		||||
            "description": "Enable Android dependencies",
 | 
			
		||||
            "dependencies": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "oboe",
 | 
			
		||||
                    "platform": "android"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "overrides": [
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user