mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 00:07:36 +00:00
LibAudio: Create a playback class with a PulseAudio implementation
This adds an abstract `Audio::PlaybackStream` class to allow cross- platform audio playback to be done in an opaque manner by applications in both Serenity and Lagom. Currently, the only supported audio API is PulseAudio, but a Serenity implementation should be added shortly as well.
This commit is contained in:
parent
fe672989a9
commit
bc4d4f0f95
9 changed files with 1041 additions and 0 deletions
184
Userland/Libraries/LibAudio/PulseAudioWrappers.h
Normal file
184
Userland/Libraries/LibAudio/PulseAudioWrappers.h
Normal file
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Gregory Bertilson <zaggy1024@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/AtomicRefCounted.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibAudio/Forward.h>
|
||||
#include <LibAudio/PlaybackStream.h>
|
||||
#include <LibAudio/SampleFormats.h>
|
||||
#include <LibThreading/Thread.h>
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class PulseAudioStream;
|
||||
|
||||
enum class PulseAudioContextState {
|
||||
Unconnected = PA_CONTEXT_UNCONNECTED,
|
||||
Connecting = PA_CONTEXT_CONNECTING,
|
||||
Authorizing = PA_CONTEXT_AUTHORIZING,
|
||||
SettingName = PA_CONTEXT_SETTING_NAME,
|
||||
Ready = PA_CONTEXT_READY,
|
||||
Failed = PA_CONTEXT_FAILED,
|
||||
Terminated = PA_CONTEXT_TERMINATED,
|
||||
};
|
||||
|
||||
enum class PulseAudioErrorCode;
|
||||
|
||||
using PulseAudioDataRequestCallback = Function<ReadonlyBytes(PulseAudioStream&, Bytes buffer, size_t sample_count)>;
|
||||
|
||||
// A wrapper around the PulseAudio main loop and context structs.
|
||||
// Generally, only one instance of this should be needed for a single process.
|
||||
class PulseAudioContext
|
||||
: public AtomicRefCounted<PulseAudioContext>
|
||||
, public Weakable<PulseAudioContext> {
|
||||
public:
|
||||
static ErrorOr<NonnullRefPtr<PulseAudioContext>> instance();
|
||||
|
||||
explicit PulseAudioContext(pa_threaded_mainloop*, pa_mainloop_api*, pa_context*);
|
||||
PulseAudioContext(PulseAudioContext const& other) = delete;
|
||||
~PulseAudioContext();
|
||||
|
||||
bool current_thread_is_main_loop_thread();
|
||||
void lock_main_loop();
|
||||
void unlock_main_loop();
|
||||
[[nodiscard]] auto main_loop_locker()
|
||||
{
|
||||
lock_main_loop();
|
||||
return ScopeGuard([this]() { unlock_main_loop(); });
|
||||
}
|
||||
// Waits for signal_to_wake() to be called.
|
||||
// This must be called with the main loop locked.
|
||||
void wait_for_signal();
|
||||
// Signals to wake all threads from calls to signal_to_wake()
|
||||
void signal_to_wake();
|
||||
|
||||
PulseAudioContextState get_connection_state();
|
||||
bool connection_is_good();
|
||||
PulseAudioErrorCode get_last_error();
|
||||
|
||||
ErrorOr<NonnullRefPtr<PulseAudioStream>> create_stream(OutputState initial_state, u32 sample_rate, u8 channels, u32 target_latency_ms, PulseAudioDataRequestCallback write_callback);
|
||||
|
||||
private:
|
||||
friend class PulseAudioStream;
|
||||
|
||||
pa_threaded_mainloop* m_main_loop { nullptr };
|
||||
pa_mainloop_api* m_api { nullptr };
|
||||
pa_context* m_context;
|
||||
};
|
||||
|
||||
enum class PulseAudioStreamState {
|
||||
Unconnected = PA_STREAM_UNCONNECTED,
|
||||
Creating = PA_STREAM_CREATING,
|
||||
Ready = PA_STREAM_READY,
|
||||
Failed = PA_STREAM_FAILED,
|
||||
Terminated = PA_STREAM_TERMINATED,
|
||||
};
|
||||
|
||||
class PulseAudioStream : public AtomicRefCounted<PulseAudioStream> {
|
||||
public:
|
||||
static constexpr bool start_corked = true;
|
||||
|
||||
~PulseAudioStream();
|
||||
|
||||
PulseAudioStreamState get_connection_state();
|
||||
bool connection_is_good();
|
||||
|
||||
// Sets the callback to be run when the server consumes more of the buffer than
|
||||
// has been written yet.
|
||||
void set_underrun_callback(Function<void()>);
|
||||
|
||||
u32 sample_rate();
|
||||
size_t sample_size();
|
||||
size_t frame_size();
|
||||
u8 channel_count();
|
||||
// Gets a data buffer that can be written to and then passed back to PulseAudio through
|
||||
// the write() function. This avoids a copy vs directly calling write().
|
||||
ErrorOr<Bytes> begin_write(size_t bytes_to_write = NumericLimits<size_t>::max());
|
||||
// Writes a data buffer to the playback stream.
|
||||
ErrorOr<void> write(ReadonlyBytes data);
|
||||
// Cancels the previous begin_write() call.
|
||||
ErrorOr<void> cancel_write();
|
||||
|
||||
bool is_suspended() const;
|
||||
// Plays back all buffered data and corks the stream. Until resume() is called, no data
|
||||
// will be written to the stream.
|
||||
ErrorOr<void> drain_and_suspend();
|
||||
// Drops all buffered data and corks the stream. Until resume() is called, no data will
|
||||
// be written to the stream.
|
||||
ErrorOr<void> flush_and_suspend();
|
||||
// Uncorks the stream and forces data to be written to the buffers to force playback to
|
||||
// resume as soon as possible.
|
||||
ErrorOr<void> resume();
|
||||
ErrorOr<Duration> total_time_played();
|
||||
|
||||
ErrorOr<void> set_volume(double volume);
|
||||
|
||||
PulseAudioContext& context() { return *m_context; }
|
||||
|
||||
private:
|
||||
friend class PulseAudioContext;
|
||||
|
||||
explicit PulseAudioStream(NonnullRefPtr<PulseAudioContext>&& context, pa_stream* stream)
|
||||
: m_context(context)
|
||||
, m_stream(stream)
|
||||
{
|
||||
}
|
||||
PulseAudioStream(PulseAudioStream const& other) = delete;
|
||||
|
||||
ErrorOr<void> wait_for_operation(pa_operation*, StringView error_message);
|
||||
|
||||
void on_write_requested(size_t bytes_to_write);
|
||||
|
||||
NonnullRefPtr<PulseAudioContext> m_context;
|
||||
pa_stream* m_stream { nullptr };
|
||||
bool m_started_playback { false };
|
||||
PulseAudioDataRequestCallback m_write_callback { nullptr };
|
||||
// Determines whether we will allow the write callback to run. This should only be true
|
||||
// if the stream is becoming or is already corked.
|
||||
bool m_suspended { false };
|
||||
|
||||
Function<void()> m_underrun_callback;
|
||||
};
|
||||
|
||||
enum class PulseAudioErrorCode {
|
||||
OK = 0,
|
||||
AccessFailure,
|
||||
UnknownCommand,
|
||||
InvalidArgument,
|
||||
EntityExists,
|
||||
NoSuchEntity,
|
||||
ConnectionRefused,
|
||||
ProtocolError,
|
||||
Timeout,
|
||||
NoAuthenticationKey,
|
||||
InternalError,
|
||||
ConnectionTerminated,
|
||||
EntityKilled,
|
||||
InvalidServer,
|
||||
NoduleInitFailed,
|
||||
BadState,
|
||||
NoData,
|
||||
IncompatibleProtocolVersion,
|
||||
DataTooLarge,
|
||||
NotSupported,
|
||||
Unknown,
|
||||
NoExtension,
|
||||
Obsolete,
|
||||
NotImplemented,
|
||||
CalledFromFork,
|
||||
IOError,
|
||||
Busy,
|
||||
Sentinel
|
||||
};
|
||||
|
||||
StringView pulse_audio_error_to_string(PulseAudioErrorCode code);
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue