mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 09:00:22 +00:00
LibAudio: Implement PlaybackStream for Android using Oboe
https://github.com/google/oboe There are many ways to implement audio for Android, however this is the recommended way to do it.
This commit is contained in:
parent
abddd0ac1b
commit
6b88e43b3b
Notes:
github-actions[bot]
2024-07-21 22:10:28 +00:00
Author: https://github.com/Olekoop Commit: https://github.com/LadybirdBrowser/ladybird/commit/6b88e43b3bd Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/597 Reviewed-by: https://github.com/ADKaster Reviewed-by: https://github.com/Zaggy1024
6 changed files with 208 additions and 2 deletions
|
@ -38,6 +38,7 @@ android {
|
|||
cppFlags += "-std=c++2b"
|
||||
arguments += listOf(
|
||||
"-DLagomTools_DIR=$buildDir/lagom-tools-install/share/LagomTools",
|
||||
"-DANDROID_STL=c++_shared",
|
||||
"-DSERENITY_CACHE_DIR=$cacheDir",
|
||||
"-DVCPKG_ROOT=$sourceDir/Toolchain/Tarballs/vcpkg",
|
||||
"-DVCPKG_TARGET_ANDROID=ON"
|
||||
|
@ -76,6 +77,7 @@ android {
|
|||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
prefab = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,4 +91,5 @@ dependencies {
|
|||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.ext:junit-ktx:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
implementation("com.google.oboe:oboe:1.9.0")
|
||||
}
|
||||
|
|
|
@ -56,8 +56,7 @@ ErrorOr<int> service_main(int ipc_socket)
|
|||
Web::Platform::ImageCodecPlugin::install(*new Ladybird::ImageCodecPlugin(move(image_decoder_client)));
|
||||
|
||||
Web::Platform::AudioCodecPlugin::install_creation_hook([](auto loader) {
|
||||
(void)loader;
|
||||
return Error::from_string_literal("Don't know how to initialize audio in this configuration!");
|
||||
return Web::Platform::AudioCodecPluginAgnostic::create(move(loader));
|
||||
});
|
||||
|
||||
auto request_server_client = TRY(bind_request_server_service());
|
||||
|
|
|
@ -36,3 +36,9 @@ if (APPLE AND NOT IOS)
|
|||
find_library(AUDIO_UNIT AudioUnit REQUIRED)
|
||||
target_link_libraries(LibAudio PRIVATE ${AUDIO_UNIT})
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
target_sources(LibAudio PRIVATE PlaybackStreamOboe.cpp)
|
||||
find_package(oboe REQUIRED CONFIG)
|
||||
target_link_libraries(LibAudio PRIVATE log oboe::oboe)
|
||||
endif()
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
# include <LibAudio/PlaybackStreamPulseAudio.h>
|
||||
#elif defined(AK_OS_MACOS)
|
||||
# include <LibAudio/PlaybackStreamAudioUnit.h>
|
||||
#elif defined(AK_OS_ANDROID)
|
||||
# include <LibAudio/PlaybackStreamOboe.h>
|
||||
#endif
|
||||
|
||||
namespace Audio {
|
||||
|
@ -25,6 +27,8 @@ ErrorOr<NonnullRefPtr<PlaybackStream>> PlaybackStream::create(OutputState initia
|
|||
return PlaybackStreamPulseAudio::create(initial_output_state, sample_rate, channels, target_latency_ms, move(data_request_callback));
|
||||
#elif defined(AK_OS_MACOS)
|
||||
return PlaybackStreamAudioUnit::create(initial_output_state, sample_rate, channels, target_latency_ms, move(data_request_callback));
|
||||
#elif defined(AK_OS_ANDROID)
|
||||
return PlaybackStreamOboe::create(initial_output_state, sample_rate, channels, target_latency_ms, move(data_request_callback));
|
||||
#else
|
||||
(void)initial_output_state, (void)sample_rate, (void)channels, (void)target_latency_ms;
|
||||
return Error::from_string_literal("Audio output is not available for this platform");
|
||||
|
|
158
Userland/Libraries/LibAudio/PlaybackStreamOboe.cpp
Normal file
158
Userland/Libraries/LibAudio/PlaybackStreamOboe.cpp
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Olekoop <mlglol360xd@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#define AK_DONT_REPLACE_STD
|
||||
#include <AK/Atomic.h>
|
||||
#include <AK/SourceLocation.h>
|
||||
#include <LibAudio/PlaybackStreamOboe.h>
|
||||
#include <LibCore/SharedCircularQueue.h>
|
||||
#include <LibCore/ThreadedPromise.h>
|
||||
#include <memory>
|
||||
|
||||
#include <oboe/Oboe.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class OboeCallback : public oboe::AudioStreamDataCallback {
|
||||
public:
|
||||
virtual oboe::DataCallbackResult onAudioReady(oboe::AudioStream* oboeStream, void* audioData, int32_t numFrames)
|
||||
{
|
||||
Bytes output_buffer {
|
||||
reinterpret_cast<u8*>(audioData),
|
||||
static_cast<size_t>(numFrames * oboeStream->getChannelCount() * sizeof(float))
|
||||
};
|
||||
auto written_bytes = m_data_request_callback(output_buffer, PcmSampleFormat::Float32, numFrames);
|
||||
if (written_bytes.is_empty())
|
||||
return oboe::DataCallbackResult::Stop;
|
||||
|
||||
auto timestamp = oboeStream->getTimestamp(CLOCK_MONOTONIC);
|
||||
if (timestamp == oboe::Result::OK) {
|
||||
m_number_of_samples_enqueued = timestamp.value().position;
|
||||
} else {
|
||||
// Fallback for OpenSLES
|
||||
m_number_of_samples_enqueued += numFrames;
|
||||
}
|
||||
auto last_sample_time = static_cast<i64>(m_number_of_samples_enqueued / oboeStream->getSampleRate());
|
||||
m_last_sample_time.store(last_sample_time);
|
||||
|
||||
float* output = (float*)audioData;
|
||||
for (int frames = 0; frames < numFrames; frames++) {
|
||||
for (int channels = 0; channels < oboeStream->getChannelCount(); channels++) {
|
||||
*output++ *= m_volume.load();
|
||||
}
|
||||
}
|
||||
return oboe::DataCallbackResult::Continue;
|
||||
}
|
||||
OboeCallback(PlaybackStream::AudioDataRequestCallback data_request_callback)
|
||||
: m_data_request_callback(move(data_request_callback))
|
||||
{
|
||||
}
|
||||
Duration last_sample_time() const
|
||||
{
|
||||
return Duration::from_seconds(m_last_sample_time.load());
|
||||
}
|
||||
void set_volume(float volume)
|
||||
{
|
||||
m_volume.store(volume);
|
||||
}
|
||||
|
||||
private:
|
||||
PlaybackStream::AudioDataRequestCallback m_data_request_callback;
|
||||
Atomic<i64> m_last_sample_time { 0 };
|
||||
size_t m_number_of_samples_enqueued { 0 };
|
||||
Atomic<float> m_volume { 1.0 };
|
||||
};
|
||||
|
||||
class PlaybackStreamOboe::Storage : public RefCounted<PlaybackStreamOboe::Storage> {
|
||||
public:
|
||||
Storage(std::shared_ptr<oboe::AudioStream> stream, std::shared_ptr<OboeCallback> oboe_callback)
|
||||
: m_stream(move(stream))
|
||||
, m_oboe_callback(move(oboe_callback))
|
||||
{
|
||||
}
|
||||
std::shared_ptr<oboe::AudioStream> stream() const { return m_stream; }
|
||||
std::shared_ptr<OboeCallback> oboe_callback() const { return m_oboe_callback; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<oboe::AudioStream> m_stream;
|
||||
std::shared_ptr<OboeCallback> m_oboe_callback;
|
||||
};
|
||||
|
||||
PlaybackStreamOboe::PlaybackStreamOboe(NonnullRefPtr<Storage> storage)
|
||||
: m_storage(move(storage))
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<NonnullRefPtr<PlaybackStream>> PlaybackStreamOboe::create(OutputState initial_output_state, u32 sample_rate, u8 channels, u32, AudioDataRequestCallback&& data_request_callback)
|
||||
{
|
||||
std::shared_ptr<oboe::AudioStream> stream;
|
||||
auto oboe_callback = std::make_shared<OboeCallback>(move(data_request_callback));
|
||||
oboe::AudioStreamBuilder builder;
|
||||
auto result = builder.setSharingMode(oboe::SharingMode::Shared)
|
||||
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
||||
->setFormat(oboe::AudioFormat::Float)
|
||||
->setDataCallback(oboe_callback)
|
||||
->setChannelCount(channels)
|
||||
->setSampleRate(sample_rate)
|
||||
->openStream(stream);
|
||||
|
||||
if (result != oboe::Result::OK)
|
||||
return Error::from_string_literal("Oboe failed to start");
|
||||
|
||||
if (initial_output_state == OutputState::Playing)
|
||||
stream->requestStart();
|
||||
|
||||
auto storage = TRY(adopt_nonnull_ref_or_enomem(new PlaybackStreamOboe::Storage(move(stream), move(oboe_callback))));
|
||||
return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) PlaybackStreamOboe(move(storage))));
|
||||
}
|
||||
|
||||
PlaybackStreamOboe::~PlaybackStreamOboe() = default;
|
||||
|
||||
void PlaybackStreamOboe::set_underrun_callback(Function<void()>)
|
||||
{
|
||||
// FIXME: Implement this.
|
||||
}
|
||||
|
||||
NonnullRefPtr<Core::ThreadedPromise<Duration>> PlaybackStreamOboe::resume()
|
||||
{
|
||||
auto promise = Core::ThreadedPromise<Duration>::create();
|
||||
auto time = MUST(total_time_played());
|
||||
m_storage->stream()->start();
|
||||
promise->resolve(move(time));
|
||||
return promise;
|
||||
}
|
||||
|
||||
NonnullRefPtr<Core::ThreadedPromise<void>> PlaybackStreamOboe::drain_buffer_and_suspend()
|
||||
{
|
||||
auto promise = Core::ThreadedPromise<void>::create();
|
||||
m_storage->stream()->stop();
|
||||
promise->resolve();
|
||||
return promise;
|
||||
}
|
||||
|
||||
NonnullRefPtr<Core::ThreadedPromise<void>> PlaybackStreamOboe::discard_buffer_and_suspend()
|
||||
{
|
||||
auto promise = Core::ThreadedPromise<void>::create();
|
||||
m_storage->stream()->pause();
|
||||
m_storage->stream()->flush();
|
||||
promise->resolve();
|
||||
return promise;
|
||||
}
|
||||
|
||||
ErrorOr<Duration> PlaybackStreamOboe::total_time_played()
|
||||
{
|
||||
return m_storage->oboe_callback()->last_sample_time();
|
||||
}
|
||||
|
||||
NonnullRefPtr<Core::ThreadedPromise<void>> PlaybackStreamOboe::set_volume(double volume)
|
||||
{
|
||||
auto promise = Core::ThreadedPromise<void>::create();
|
||||
m_storage->oboe_callback()->set_volume(volume);
|
||||
promise->resolve();
|
||||
return promise;
|
||||
}
|
||||
|
||||
}
|
36
Userland/Libraries/LibAudio/PlaybackStreamOboe.h
Normal file
36
Userland/Libraries/LibAudio/PlaybackStreamOboe.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Olekoop <mlglol360xd@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Error.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <LibAudio/PlaybackStream.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class PlaybackStreamOboe final : public PlaybackStream {
|
||||
public:
|
||||
static ErrorOr<NonnullRefPtr<PlaybackStream>> create(OutputState initial_output_state, u32 sample_rate, u8 channels, u32 target_latency_ms, AudioDataRequestCallback&& data_request_callback);
|
||||
|
||||
virtual void set_underrun_callback(Function<void()>) override;
|
||||
|
||||
virtual NonnullRefPtr<Core::ThreadedPromise<Duration>> resume() override;
|
||||
virtual NonnullRefPtr<Core::ThreadedPromise<void>> drain_buffer_and_suspend() override;
|
||||
virtual NonnullRefPtr<Core::ThreadedPromise<void>> discard_buffer_and_suspend() override;
|
||||
|
||||
virtual ErrorOr<Duration> total_time_played() override;
|
||||
|
||||
virtual NonnullRefPtr<Core::ThreadedPromise<void>> set_volume(double) override;
|
||||
|
||||
private:
|
||||
class Storage;
|
||||
explicit PlaybackStreamOboe(NonnullRefPtr<Storage>);
|
||||
~PlaybackStreamOboe();
|
||||
RefPtr<Storage> m_storage;
|
||||
};
|
||||
|
||||
}
|
Loading…
Reference in a new issue