ladybird/Libraries/LibMedia/AudioBlock.h
Zaggy1024 0d29a3bb3b LibMedia: Shift mixing off the PlaybackStream data callback thread
While mixing doesn't cost much time at the moment, moving audio
processing off the callback thread means that the callback itself can
block for a minimal amount of time regardless of the amount of time
needed to actually process the audio data.

This matters a lot for AudioUnit, where the entire daemon can start to
glitch if delays occur.

The goal of this change is to split mixing off into a separate class,
and process audio on both ends of the mixer synchronously. This should
allow a more declarative approach to processing audio, where inserting
a time stretching processor, for example, becomes much simpler.

In this commit, the thread spin-waits for new upstream data. Making it
wait on a condition variable here would overcomplicate this commit, so
it's deferred until a later commit establishes that forward wakeup.
2026-05-13 02:05:35 -05:00

87 lines
2.7 KiB
C++

/*
* Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FixedArray.h>
#include <AK/Math.h>
#include <AK/SaturatingMath.h>
#include <AK/Time.h>
#include <LibMedia/Audio/SampleSpecification.h>
namespace Media {
class AudioBlock {
public:
using Data = FixedArray<float>;
Audio::SampleSpecification const& sample_specification() const { return m_sample_specification; }
AK::Duration timestamp() const { return m_timestamp; }
i64 timestamp_in_samples() const { return m_timestamp_in_samples; }
i64 end_timestamp_in_samples() const { return saturating_add(m_timestamp_in_samples, AK::clamp_to<i64>(sample_count())); }
AK::Duration end_timestamp() const { return AK::Duration::from_time_units(end_timestamp_in_samples(), 1, sample_rate()); }
Data& data() { return m_data; }
Data const& data() const { return m_data; }
void clear()
{
m_sample_specification = {};
m_timestamp_in_samples = 0;
m_data = Data();
}
template<typename Callback>
void emplace(Audio::SampleSpecification sample_specification, AK::Duration timestamp, Callback data_callback)
{
VERIFY(sample_specification.is_valid());
m_sample_specification = sample_specification;
m_timestamp = timestamp;
m_timestamp_in_samples = timestamp.to_time_units(1, sample_rate());
data_callback(m_data);
}
template<typename Callback>
void emplace(Audio::SampleSpecification sample_specification, i64 timestamp_in_samples, Callback data_callback)
{
VERIFY(sample_specification.is_valid());
m_sample_specification = sample_specification;
m_timestamp_in_samples = timestamp_in_samples;
m_timestamp = AK::Duration::from_time_units(timestamp_in_samples, 1, sample_rate());
data_callback(m_data);
}
u32 sample_rate() const
{
return sample_specification().sample_rate();
}
void set_timestamp_in_samples(i64 timestamp_in_samples)
{
VERIFY(!is_empty());
m_timestamp_in_samples = timestamp_in_samples;
m_timestamp = AK::Duration::from_time_units(timestamp_in_samples, 1, sample_rate());
}
bool is_empty() const
{
return !sample_specification().is_valid();
}
size_t data_count() const
{
return data().size();
}
u8 channel_count() const
{
return sample_specification().channel_map().channel_count();
}
size_t sample_count() const
{
return data_count() / channel_count();
}
private:
Audio::SampleSpecification m_sample_specification;
AK::Duration m_timestamp;
i64 m_timestamp_in_samples { 0 };
Data m_data;
};
}