123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- /*
- * 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 AK::WeakPtr<PulseAudioContext> weak_instance();
- 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);
- }
|