-
-
Notifications
You must be signed in to change notification settings - Fork 497
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
6 changed files
with
201 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
/* | ||
* 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) | ||
{ | ||
auto last_sample_time = static_cast<i64>(m_number_of_samples_enqueued / oboeStream->getSampleRate()); | ||
m_last_sample_time.store(last_sample_time); | ||
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; | ||
|
||
m_number_of_samples_enqueued += numFrames; | ||
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()->requestStart(); | ||
promise->resolve(move(time)); | ||
return promise; | ||
} | ||
|
||
NonnullRefPtr<Core::ThreadedPromise<void>> PlaybackStreamOboe::drain_buffer_and_suspend() | ||
{ | ||
auto promise = Core::ThreadedPromise<void>::create(); | ||
m_storage->stream()->requestPause(); | ||
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()->requestFlush(); | ||
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; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
}; | ||
|
||
} |