mirror of
https://github.com/RGBCube/serenity
synced 2025-05-22 14:15:08 +00:00

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.
184 lines
5.5 KiB
C++
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);
|
|
|
|
}
|