Skip to content

Commit eefc0c2

Browse files
committed
Tests: Add a test set for file buffered ranges
1 parent b59df5f commit eefc0c2

8 files changed

Lines changed: 206 additions & 0 deletions

File tree

Tests/LibMedia/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ include(audio)
22

33
set(TEST_SOURCES
44
TestCICP.cpp
5+
TestBufferedRanges.cpp
56
TestDataProducers.cpp
67
TestDecodedAudioProducer.cpp
78
TestFFmpegAudioNormalization.cpp
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/*
2+
* Copyright (c) 2026-present, the Ladybird developers.
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <LibCore/File.h>
8+
#include <LibMedia/FFmpeg/FFmpegDemuxer.h>
9+
#include <LibMedia/IncrementallyPopulatedStream.h>
10+
#include <LibTest/TestCase.h>
11+
12+
struct SplitPoint {
13+
size_t byte;
14+
AK::Duration range_end_before_byte;
15+
AK::Duration range_start_at_byte;
16+
17+
SplitPoint(size_t byte, AK::Duration range_end_before_byte, AK::Duration range_start_at_byte)
18+
: byte(byte)
19+
, range_end_before_byte(range_end_before_byte)
20+
, range_start_at_byte(range_start_at_byte)
21+
{
22+
}
23+
24+
SplitPoint(size_t byte, i64 timestamp_prior, i64 timestamp_delta, u32 timebase)
25+
: SplitPoint(byte, AK::Duration::from_time_units(timestamp_prior, 1, timebase), AK::Duration::from_time_units(timestamp_prior + timestamp_delta, 1, timebase))
26+
{
27+
}
28+
};
29+
30+
struct Fixture {
31+
StringView path;
32+
SplitPoint first_split;
33+
SplitPoint second_split;
34+
};
35+
36+
static ByteBuffer read_fixture(StringView path)
37+
{
38+
auto file = MUST(Core::File::open(path, Core::File::OpenMode::Read));
39+
return MUST(file->read_until_eof());
40+
}
41+
42+
static NonnullRefPtr<Media::FFmpeg::FFmpegDemuxer> create_demuxer(NonnullRefPtr<Media::IncrementallyPopulatedStream> const& stream)
43+
{
44+
return MUST(Media::FFmpeg::FFmpegDemuxer::from_stream(stream));
45+
}
46+
47+
static NonnullRefPtr<Media::IncrementallyPopulatedStream> create_complete_stream(ByteBuffer const& data)
48+
{
49+
return Media::IncrementallyPopulatedStream::create_from_buffer(data);
50+
}
51+
52+
static NonnullRefPtr<Media::IncrementallyPopulatedStream> create_disjoint_stream(ByteBuffer const& data, SplitPoint const& first_split, SplitPoint const& second_split)
53+
{
54+
VERIFY(first_split.byte < second_split.byte);
55+
VERIFY(second_split.byte < data.size());
56+
57+
auto stream = Media::IncrementallyPopulatedStream::create_empty();
58+
stream->add_chunk_at(0, data.bytes().trim(first_split.byte));
59+
stream->add_chunk_at(second_split.byte, data.bytes().slice(second_split.byte));
60+
stream->close();
61+
return stream;
62+
}
63+
64+
static void expect_valid_ranges(Media::TimeRanges const& ranges)
65+
{
66+
EXPECT(!ranges.is_empty());
67+
68+
auto previous_end = AK::Duration::min();
69+
for (auto const& range : ranges) {
70+
EXPECT(range.start >= AK::Duration::zero());
71+
EXPECT(range.end > range.start);
72+
EXPECT(range.start >= previous_end);
73+
previous_end = range.end;
74+
}
75+
}
76+
77+
[[maybe_unused]] static bool nearly_greater_than_or_equal(AK::Duration lhs, AK::Duration rhs)
78+
{
79+
return rhs - lhs <= AK::Duration::from_microseconds(5);
80+
}
81+
82+
static void expect_complete_file_is_fully_buffered(Fixture const& fixture)
83+
{
84+
dbgln("-- '{}' fully buffered --", fixture.path);
85+
86+
auto data = read_fixture(fixture.path);
87+
auto demuxer = create_demuxer(create_complete_stream(data));
88+
auto duration = MUST(demuxer->total_duration());
89+
auto ranges = demuxer->buffered_time_ranges();
90+
91+
expect_valid_ranges(ranges);
92+
EXPECT_EQ(ranges.size(), 1u);
93+
EXPECT_EQ(ranges[0].start, AK::Duration::zero());
94+
EXPECT(ranges[0].end >= duration - AK::Duration::from_microseconds(5));
95+
}
96+
97+
static void expect_disjoint_byte_ranges_produce_disjoint_time_ranges(Fixture const& fixture)
98+
{
99+
dbgln("-- '{}' disjointed --", fixture.path);
100+
101+
auto data = read_fixture(fixture.path);
102+
auto demuxer = create_demuxer(create_disjoint_stream(data, fixture.first_split, fixture.second_split));
103+
auto ranges = demuxer->buffered_time_ranges();
104+
105+
expect_valid_ranges(ranges);
106+
EXPECT_EQ(ranges.size(), 2u);
107+
EXPECT_EQ(ranges[0].start, AK::Duration::zero());
108+
EXPECT_EQ(ranges[0].end, fixture.first_split.range_end_before_byte);
109+
EXPECT_EQ(ranges[1].start, fixture.second_split.range_start_at_byte);
110+
EXPECT(ranges[0].end < ranges[ranges.size() - 1].start);
111+
}
112+
113+
static void expect_removing_inside_of_complete_file_produces_disjoint_time_ranges(Fixture const& fixture)
114+
{
115+
dbgln("-- '{}' removed inside --", fixture.path);
116+
117+
auto data = read_fixture(fixture.path);
118+
auto stream = create_complete_stream(data);
119+
auto demuxer = create_demuxer(stream);
120+
(void)demuxer->buffered_time_ranges();
121+
stream->remove_byte_range(fixture.first_split.byte, fixture.second_split.byte);
122+
auto ranges = demuxer->buffered_time_ranges();
123+
124+
expect_valid_ranges(ranges);
125+
EXPECT_EQ(ranges.size(), 2u);
126+
EXPECT_EQ(ranges[0].start, AK::Duration::zero());
127+
EXPECT_EQ(ranges[0].end, fixture.first_split.range_end_before_byte);
128+
EXPECT_EQ(ranges[1].start, fixture.second_split.range_start_at_byte);
129+
EXPECT(ranges[0].end < ranges[ranges.size() - 1].start);
130+
}
131+
132+
static void expect_removing_ends_of_complete_file_produces_middle_time_range(Fixture const& fixture)
133+
{
134+
dbgln("-- '{}' removed ends --", fixture.path);
135+
136+
auto data = read_fixture(fixture.path);
137+
auto stream = create_complete_stream(data);
138+
auto demuxer = create_demuxer(stream);
139+
(void)demuxer->buffered_time_ranges();
140+
stream->remove_byte_range(fixture.second_split.byte, data.size());
141+
stream->remove_byte_range(0, fixture.first_split.byte);
142+
auto ranges = demuxer->buffered_time_ranges();
143+
144+
expect_valid_ranges(ranges);
145+
EXPECT_EQ(ranges.size(), 1u);
146+
EXPECT_EQ(ranges[0].start, fixture.first_split.range_start_at_byte);
147+
EXPECT_EQ(ranges[0].end, fixture.second_split.range_end_before_byte);
148+
}
149+
150+
static Array fixtures {
151+
Fixture {
152+
"buffered-ranges/tone.wav"sv,
153+
{ 256 * KiB, (256 * KiB) - 78, 0, 192000 },
154+
{ 1024 * KiB, (1024 * KiB) - 78, 0, 192000 },
155+
},
156+
Fixture {
157+
"buffered-ranges/tone.mp3"sv,
158+
{ 32 * KiB, 28449792, 338688, 14112000 },
159+
{ 72 * KiB, 64350720, 338688, 14112000 },
160+
},
161+
Fixture {
162+
"buffered-ranges/tone.flac"sv,
163+
{ 32 * KiB, 87552, 0, 48000 },
164+
{ 68 * KiB, 221184, 0, 48000 },
165+
},
166+
Fixture {
167+
"buffered-ranges/tone_opus.ogg"sv,
168+
{ 96 * KiB, 432000, 48000, 48000 },
169+
{ 160 * KiB, 768000, 48000, 48000 },
170+
},
171+
Fixture {
172+
"buffered-ranges/tone_flac.ogg"sv,
173+
{ 96 * KiB, 322560, 46080, 44100 },
174+
{ 160 * KiB, 552960, 46080, 44100 },
175+
},
176+
Fixture {
177+
"buffered-ranges/tone_vorbis.ogg"sv,
178+
{ 256 * KiB, 3333120, 45056, 44100 },
179+
{ 320 * KiB, 4189184, 45056, 44100 },
180+
},
181+
};
182+
183+
TEST_CASE(complete_files_are_fully_buffered)
184+
{
185+
for (auto const& fixture : fixtures)
186+
expect_complete_file_is_fully_buffered(fixture);
187+
}
188+
189+
TEST_CASE(disjoint_byte_ranges_produce_disjoint_time_ranges)
190+
{
191+
for (auto const& fixture : fixtures)
192+
expect_disjoint_byte_ranges_produce_disjoint_time_ranges(fixture);
193+
}
194+
195+
TEST_CASE(removing_inside_of_complete_file_produces_disjoint_time_ranges)
196+
{
197+
for (auto const& fixture : fixtures)
198+
expect_removing_inside_of_complete_file_produces_disjoint_time_ranges(fixture);
199+
}
200+
201+
TEST_CASE(removing_ends_of_complete_file_produces_middle_time_range)
202+
{
203+
for (auto const& fixture : fixtures)
204+
expect_removing_ends_of_complete_file_produces_middle_time_range(fixture);
205+
}
113 KB
Binary file not shown.
126 KB
Binary file not shown.
1.46 MB
Binary file not shown.
354 KB
Binary file not shown.
299 KB
Binary file not shown.
402 KB
Binary file not shown.

0 commit comments

Comments
 (0)