1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-22 14:15:08 +00:00
serenity/Userland/Libraries/LibAudio/PulseAudioWrappers.h
Zaggy1024 bc4d4f0f95 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.
2023-08-04 13:49:36 -06:00

184 lines
5.5 KiB
C++

/*
* 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);
}