ladybird/Tests/LibMedia/TestFFmpegAudioNormalization.cpp
Zaggy1024 c8c64e4819 LibMedia: Store raw audio data as planar
This avoids strided loads for vectorized audio data processing loops.
2026-06-06 19:58:17 -05:00

143 lines
4.8 KiB
C++

/*
* Copyright (c) 2026, The Ladybird developers
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteBuffer.h>
#include <AK/NumericLimits.h>
#include <AK/StringView.h>
#include <AK/Time.h>
#include <LibCore/EventLoop.h>
#include <LibMedia/FFmpeg/FFmpegDemuxer.h>
#include <LibMedia/IncrementallyPopulatedStream.h>
#include <LibMedia/PipelineStatus.h>
#include <LibMedia/Producers/DecodedAudioProducer.h>
#include <LibTest/TestCase.h>
template<typename Integer>
requires(IsIntegral<Integer>)
static void append_little_endian(ByteBuffer& buffer, Integer value)
{
using Unsigned = MakeUnsigned<Integer>;
Unsigned unsigned_value = static_cast<Unsigned>(value);
auto bytes = buffer.must_get_bytes_for_writing(sizeof(Integer));
for (size_t byte_index = 0; byte_index < sizeof(Integer); ++byte_index)
bytes[byte_index] = static_cast<u8>(unsigned_value >> (byte_index * 8));
}
template<typename Sample>
requires(IsSigned<Sample>)
static ByteBuffer make_square_wav(size_t sample_count)
{
constexpr u16 channel_count = 1;
constexpr u32 sample_rate = 8'000;
constexpr u16 bits_per_sample = sizeof(Sample) * 8;
constexpr u16 block_align = channel_count * sizeof(Sample);
constexpr u32 byte_rate = sample_rate * block_align;
u32 data_size = static_cast<u32>(sample_count * sizeof(Sample));
u32 riff_chunk_size = 36 + data_size;
ByteBuffer buffer;
buffer.ensure_capacity(44 + data_size);
buffer.append("RIFF"sv.bytes());
append_little_endian(buffer, riff_chunk_size);
buffer.append("WAVE"sv.bytes());
buffer.append("fmt "sv.bytes());
append_little_endian(buffer, 16u);
append_little_endian(buffer, static_cast<u16>(1));
append_little_endian(buffer, channel_count);
append_little_endian(buffer, sample_rate);
append_little_endian(buffer, byte_rate);
append_little_endian(buffer, block_align);
append_little_endian(buffer, bits_per_sample);
buffer.append("data"sv.bytes());
append_little_endian(buffer, data_size);
for (size_t sample_index = 0; sample_index < sample_count; ++sample_index) {
Sample sample = (sample_index % 2 == 0) ? NumericLimits<Sample>::min() : NumericLimits<Sample>::max();
append_little_endian(buffer, sample);
}
return buffer;
}
template<typename Sample>
requires(IsSigned<Sample>)
static void decode_and_expect()
{
Core::EventLoop loop;
constexpr size_t sample_count = 2048;
auto wav_data = make_square_wav<Sample>(sample_count);
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(wav_data);
auto demuxer = MUST(Media::FFmpeg::FFmpegDemuxer::from_stream(stream));
auto tracks = TRY_OR_FAIL(demuxer->get_tracks_for_type(Media::TrackType::Audio));
VERIFY(!tracks.is_empty());
auto producer = TRY_OR_FAIL(Media::DecodedAudioProducer::try_create(loop, demuxer, tracks[0]));
producer->set_error_handler([&](Media::DecoderError&&) {
FAIL("An error occurred while decoding generated WAV data.");
});
producer->start();
bool saw_negative_full_scale_sample = false;
bool saw_positive_peak_sample = false;
size_t decoded_frame_count = 0;
bool reached_end_of_stream = false;
MonotonicTime deadline = MonotonicTime::now_coarse() + AK::Duration::from_seconds(1);
while (MonotonicTime::now_coarse() < deadline) {
Media::AudioBlock block;
auto status = producer->status();
if (status == Media::PipelineStatus::HaveData)
producer->pull(block);
if (status == Media::PipelineStatus::HaveData) {
EXPECT(!block.is_empty());
for (size_t channel = 0; channel < block.channel_count(); ++channel) {
for (float sample : block.channel_data(channel)) {
EXPECT(sample >= -1.0f);
if constexpr (sizeof(Sample) >= sizeof(i32))
EXPECT(sample <= 1.0f);
else
EXPECT(sample < 1.0f);
if (sample == -1.0f)
saw_negative_full_scale_sample = true;
if (sample > 0.0f)
saw_positive_peak_sample = true;
}
}
decoded_frame_count += block.frame_count();
} else if (status == Media::PipelineStatus::EndOfStream) {
reached_end_of_stream = true;
break;
}
loop.pump(Core::EventLoop::WaitMode::PollForEvents);
}
EXPECT(reached_end_of_stream);
EXPECT_EQ(decoded_frame_count, sample_count);
EXPECT(saw_negative_full_scale_sample);
EXPECT(saw_positive_peak_sample);
}
TEST_CASE(signed_16bit_pcm_to_float)
{
decode_and_expect<i16>();
}
TEST_CASE(signed_32bit_pcm_to_float)
{
decode_and_expect<i32>();
}
TEST_CASE(signed_64bit_pcm_to_float)
{
decode_and_expect<i64>();
}