mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 01:35:08 +02:00
Apparently this function uses a bitrate heuristic to determine which track is best. We don't want or need that, so just select the first track with default disposition (e.g. FlagDefault=1 in Matroska).
141 lines
4.5 KiB
C++
141 lines
4.5 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/Providers/AudioDataProvider.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 provider = TRY_OR_FAIL(Media::AudioDataProvider::try_create(Core::EventLoop::current_weak(), demuxer, tracks[0]));
|
|
|
|
bool reached_end_of_stream = false;
|
|
provider->set_error_handler([&](Media::DecoderError&& error) {
|
|
if (error.category() == Media::DecoderErrorCategory::EndOfStream) {
|
|
reached_end_of_stream = true;
|
|
return;
|
|
}
|
|
FAIL("An error occurred while decoding generated WAV data.");
|
|
});
|
|
provider->start();
|
|
|
|
bool saw_negative_full_scale_sample = false;
|
|
bool saw_positive_peak_sample = false;
|
|
size_t decoded_sample_count = 0;
|
|
|
|
MonotonicTime deadline = MonotonicTime::now_coarse() + AK::Duration::from_seconds(1);
|
|
while (MonotonicTime::now_coarse() < deadline) {
|
|
auto block = provider->retrieve_block();
|
|
if (block.is_empty()) {
|
|
if (reached_end_of_stream)
|
|
break;
|
|
} else {
|
|
for (float sample : block.data()) {
|
|
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_sample_count += block.sample_count();
|
|
}
|
|
|
|
loop.pump(Core::EventLoop::WaitMode::PollForEvents);
|
|
}
|
|
|
|
EXPECT(reached_end_of_stream);
|
|
EXPECT_EQ(decoded_sample_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>();
|
|
}
|