ladybird/Libraries/LibMedia/Audio/PlaybackStream.h
Zaggy1024 a3177c5f71 LibMedia: Give PlaybackStream a way to wake up after underrun
This gives us a cheap way to wake up all PlaybackStream implementations
when they are sleeping due to an underrun. The new function is meant to
be very cheap so that it can be unconditionally called when new data is
ready to be written.

This will allow the audio sink to rely on streams to track the time in
written audio frames instead of writing silence when data isn't ready.

The WASAPI implementation was actually polling for new data every 10ms
after hitting an underrun, so now it drains the buffers and stops when
entering the underrun-paused state.
2026-05-13 02:05:35 -05:00

82 lines
3.9 KiB
C++

/*
* Copyright (c) 2023-2025, Gregory Bertilson <gregory@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/AtomicRefCounted.h>
#include <AK/Function.h>
#include <AK/Time.h>
#include <LibCore/Forward.h>
#include <LibCore/Promise.h>
#include <LibCore/ThreadedPromise.h>
#include <LibMedia/Audio/SampleSpecification.h>
#include <LibMedia/Export.h>
namespace Audio {
enum class OutputState {
Playing,
Suspended,
};
// This class implements high-level audio playback behavior. It is primarily intended as an abstract cross-platform
// interface to be used by Ladybird (and its dependent libraries) for playback.
//
// The interface is designed to be simple and robust. All control functions can be called safely from any thread.
// Timing information provided by the class should allow audio timestamps to be tracked with the best accuracy possible.
class MEDIA_API PlaybackStream : public AtomicRefCounted<PlaybackStream> {
public:
using CreatePromise = Core::Promise<NonnullRefPtr<PlaybackStream>>;
using AudioDataRequestCallback = Function<ReadonlySpan<float>(Span<float> buffer)>;
// Begins creating a new audio output and returns a promise that is resolved when it is ready.
//
// The initial_output_state parameter determines whether it will begin playback immediately.
//
// The returned promise will be resolved with the PlaybackStream if the audio output was successfully initialized,
// or rejected with an error if not.
//
// The AudioDataRequestCallback will be called when the output needs more audio data to fill its buffers and
// continue playback. This callback will only be allowed to run on one thread at a time, to prevent any data
// race on the resource used by the callback.
static NonnullRefPtr<CreatePromise> create(OutputState initial_output_state, u32 target_latency_ms, AudioDataRequestCallback&&);
virtual SampleSpecification sample_specification() const = 0;
virtual ~PlaybackStream() = default;
// Sets the callback function that will be fired whenever the server consumes more data than is made available
// by the data request callback. It will fire when either the data request runs too long, or the data request
// returns no data. If all the input data has been exhausted and this event fires, that means that playback
// has ended.
virtual void set_underrun_callback(Function<void()>) = 0;
// Resume playback from the suspended state, requesting new data for audio buffers as soon as possible.
//
// The value provided to the promise resolution will match the `total_time_played()` at the exact moment that
// the stream was resumed.
virtual NonnullRefPtr<Core::ThreadedPromise<AK::Duration>> resume() = 0;
// Completes playback of any buffered audio data and then suspends playback and buffering.
virtual NonnullRefPtr<Core::ThreadedPromise<void>> drain_buffer_and_suspend() = 0;
// Drops any buffered audio data and then suspends playback and buffering. This can used be to stop playback
// as soon as possible instead of waiting for remaining audio to play.
virtual NonnullRefPtr<Core::ThreadedPromise<void>> discard_buffer_and_suspend() = 0;
// Notifies the stream that the data request callback may be able to provide data now. This is used to
// wake playback streams that have stopped requesting data due to an underrun.
virtual void notify_data_available() = 0;
// Returns a accurate monotonically-increasing time duration that is based on the number of samples that have
// been played by the output device. The value is interpolated and takes into account latency to the speakers
// whenever possible.
//
// This function should be able to run from any thread safely.
virtual AK::Duration total_time_played() const = 0;
virtual NonnullRefPtr<Core::ThreadedPromise<void>> set_volume(double volume) = 0;
};
}