ladybird/Libraries/LibMedia/Providers/AudioDataProvider.cpp
Zaggy1024 0ff330c906 LibMedia: Play audio through PlaybackManager using Providers/Sinks
This commit implements the functionality to play back audio through
PlaybackManager.

To decode the audio data, AudioDataProviders are created for each track
in the provided media data. These providers will fill their audio block
queue, then sit idle until their corresponding tracks are enabled.

In order to output the audio, one AudioMixingSink is created which
manages a PlaybackStream which requests audio blocks from multiple
AudioDataProviders and mixes them into one buffer with sample-perfect
precision.
2025-10-27 17:28:49 -07:00

171 lines
5.2 KiB
C++

/*
* Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <LibCore/EventLoop.h>
#include <LibMedia/FFmpeg/FFmpegAudioDecoder.h>
#include <LibMedia/MutexedDemuxer.h>
#include <LibMedia/Sinks/AudioSink.h>
#include <LibThreading/Mutex.h>
#include <LibThreading/Thread.h>
#include "AudioDataProvider.h"
namespace Media {
DecoderErrorOr<NonnullRefPtr<AudioDataProvider>> AudioDataProvider::try_create(NonnullRefPtr<MutexedDemuxer> const& demuxer, Track const& track)
{
auto codec_id = TRY(demuxer->get_codec_id_for_track(track));
auto codec_initialization_data = TRY(demuxer->get_codec_initialization_data_for_track(track));
auto decoder = DECODER_TRY_ALLOC(FFmpeg::FFmpegAudioDecoder::try_create(codec_id, codec_initialization_data));
auto thread_data = DECODER_TRY_ALLOC(try_make_ref_counted<AudioDataProvider::ThreadData>(demuxer, track, move(decoder)));
auto provider = DECODER_TRY_ALLOC(try_make_ref_counted<AudioDataProvider>(thread_data));
auto thread = DECODER_TRY_ALLOC(Threading::Thread::try_create([thread_data]() -> int {
while (!thread_data->should_thread_exit())
thread_data->push_data_and_decode_a_block();
return 0;
}));
thread->start();
thread->detach();
return provider;
}
AudioDataProvider::AudioDataProvider(NonnullRefPtr<ThreadData> const& thread_data)
: m_thread_data(thread_data)
{
}
AudioDataProvider::~AudioDataProvider()
{
m_thread_data->exit();
}
void AudioDataProvider::set_error_handler(ErrorHandler&& handler)
{
m_thread_data->set_error_handler(move(handler));
}
void AudioDataProvider::seek(AK::Duration timestamp)
{
m_thread_data->seek(timestamp);
}
AudioDataProvider::ThreadData::ThreadData(NonnullRefPtr<MutexedDemuxer> const& demuxer, Track const& track, NonnullOwnPtr<AudioDecoder>&& decoder)
: m_main_thread_event_loop(Core::EventLoop::current())
, m_demuxer(demuxer)
, m_track(track)
, m_decoder(move(decoder))
{
}
AudioDataProvider::ThreadData::~ThreadData() = default;
void AudioDataProvider::ThreadData::set_error_handler(ErrorHandler&& handler)
{
auto locker = take_lock();
m_error_handler = move(handler);
m_wait_condition.broadcast();
}
AudioBlock AudioDataProvider::retrieve_block()
{
auto locker = m_thread_data->take_lock();
if (m_thread_data->queue().is_empty())
return AudioBlock();
auto result = m_thread_data->queue().dequeue();
m_thread_data->wake();
return result;
}
void AudioDataProvider::ThreadData::exit()
{
m_exit = true;
m_wait_condition.broadcast();
}
void AudioDataProvider::ThreadData::seek(AK::Duration timestamp)
{
auto demuxer_result = m_demuxer->seek_to_most_recent_keyframe(m_track, timestamp);
if (demuxer_result.is_error()) {
m_error_handler(demuxer_result.release_error());
} else {
auto locker = take_lock();
m_is_in_error_state = false;
m_wait_condition.broadcast();
}
}
bool AudioDataProvider::ThreadData::should_thread_exit() const
{
return m_exit;
}
void AudioDataProvider::ThreadData::push_data_and_decode_a_block()
{
#if PLAYBACK_MANAGER_DEBUG
auto start_time = MonotonicTime::now();
#endif
auto set_error_and_wait_for_seek = [this](DecoderError&& error) {
auto locker = take_lock();
m_is_in_error_state = true;
while (!m_error_handler)
m_wait_condition.wait();
m_main_thread_event_loop.deferred_invoke([this, error = move(error)] mutable {
m_error_handler(move(error));
});
dbgln_if(PLAYBACK_MANAGER_DEBUG, "Audio Data Provider: Encountered an error, waiting for a seek to start decoding again...");
while (m_is_in_error_state)
m_wait_condition.wait();
};
auto sample_result = m_demuxer->get_next_sample_for_track(m_track);
if (sample_result.is_error()) {
if (sample_result.error().category() == DecoderErrorCategory::NeedsMoreInput) {
return;
}
// FIXME: Handle the end of the stream.
set_error_and_wait_for_seek(sample_result.release_error());
return;
}
auto sample = sample_result.release_value();
auto decode_result = m_decoder->receive_coded_data(sample.timestamp(), sample.data());
if (decode_result.is_error()) {
set_error_and_wait_for_seek(decode_result.release_error());
return;
}
while (true) {
auto locker = take_lock();
while (m_queue.size() >= m_queue_max_size) {
m_wait_condition.wait();
if (should_thread_exit())
return;
}
auto block = AudioBlock();
auto timestamp_result = m_decoder->write_next_block(block);
if (timestamp_result.is_error()) {
if (timestamp_result.error().category() == DecoderErrorCategory::NeedsMoreInput)
break;
set_error_and_wait_for_seek(timestamp_result.release_error());
break;
}
// FIXME: Specify trailing samples in the demuxer, and drop them here or in the audio decoder implementation.
VERIFY(!block.is_empty());
m_queue.enqueue(move(block));
VERIFY(!m_queue.tail().is_empty());
}
}
}