Skip to content

Commit db71822

Browse files
committed
LibMedia: Implement buffered range scanning for Ogg
Unfortunately, this necessarily involves parsing codec frames to get their durations. Ogg's granule positions indicate the last sample of the last complete frame of the page, so the navigator has to determine the durations of every packet in the page to offset it back to its start. Vorbis is an especially involved codec to determine that for, since it has initial data defining a mapping of indices to big or small block sizes that has to be held onto, and that mapping is preceded by a bunch of conditionally parsed bits. Also, a frame's block size calculation involves the block size of the previous frame for an overlap add. To avoid bringing that complexity directly into LibMedia, we delegate the parsing/calculation to FFmpeg's av_vorbis_parse functions.
1 parent 488b97e commit db71822

8 files changed

Lines changed: 639 additions & 2 deletions

File tree

Libraries/LibMedia/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ set(SOURCES
44
Audio/AudioDevices.cpp
55
Codecs/FLAC.cpp
66
Codecs/Opus.cpp
7+
Codecs/Vorbis.cpp
78
Containers/ConstantBitrateContainerNavigator.cpp
89
Containers/FLACNavigator.cpp
910
Containers/IndexedContainerNavigator.cpp
1011
Containers/MP3Navigator.cpp
12+
Containers/OggNavigator.cpp
1113
Containers/Matroska/MatroskaDemuxer.cpp
1214
Containers/Matroska/Reader.cpp
1315
Containers/Matroska/SampleIterator.cpp

Libraries/LibMedia/Codecs/Opus.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
namespace Media::Codecs {
1111

12-
DecoderErrorOr<AK::Duration> Opus::parse_frame_duration(MediaStreamCursor& cursor, size_t frame_size)
12+
DecoderErrorOr<u32> Opus::parse_frame_duration_in_samples(MediaStreamCursor& cursor, size_t frame_size)
1313
{
1414
if (frame_size == 0)
1515
return DecoderError::corrupted("Opus frame is too small"sv);
@@ -53,7 +53,12 @@ DecoderErrorOr<AK::Duration> Opus::parse_frame_duration(MediaStreamCursor& curso
5353
}
5454
}());
5555

56-
return AK::Duration::from_microseconds(packet_duration);
56+
return packet_duration * 48 / 1000;
57+
}
58+
59+
DecoderErrorOr<AK::Duration> Opus::parse_frame_duration(MediaStreamCursor& cursor, size_t frame_size)
60+
{
61+
return AK::Duration::from_time_units(TRY(parse_frame_duration_in_samples(cursor, frame_size)), 1, 48000);
5762
}
5863

5964
}

Libraries/LibMedia/Codecs/Opus.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ namespace Media::Codecs {
1515
class Opus {
1616
public:
1717
static DecoderErrorOr<AK::Duration> parse_frame_duration(MediaStreamCursor&, size_t frame_size);
18+
static DecoderErrorOr<u32> parse_frame_duration_in_samples(MediaStreamCursor&, size_t frame_size);
1819
};
1920

2021
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2026-present, the Ladybird developers.
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <AK/Math.h>
8+
#include <LibMedia/Codecs/Vorbis.h>
9+
10+
extern "C" {
11+
#include <libavcodec/vorbis_parser.h>
12+
}
13+
14+
namespace Media::Codecs {
15+
16+
Optional<Vorbis::Parser> Vorbis::Parser::create(ReadonlyBytes codec_initialization_data)
17+
{
18+
if (codec_initialization_data.is_empty())
19+
return {};
20+
21+
auto* parser = av_vorbis_parse_init(codec_initialization_data.data(), AK::clamp_to<int>(codec_initialization_data.size()));
22+
if (!parser)
23+
return {};
24+
return Parser { parser };
25+
}
26+
27+
Vorbis::Parser::Parser(AVVorbisParseContext* context)
28+
: m_context(context)
29+
{
30+
VERIFY(m_context);
31+
}
32+
33+
Vorbis::Parser::Parser(Parser&& other)
34+
: m_context(exchange(other.m_context, nullptr))
35+
{
36+
}
37+
38+
Vorbis::Parser& Vorbis::Parser::operator=(Parser&& other)
39+
{
40+
if (this == &other)
41+
return *this;
42+
43+
if (m_context)
44+
av_vorbis_parse_free(&m_context);
45+
m_context = exchange(other.m_context, nullptr);
46+
return *this;
47+
}
48+
49+
Vorbis::Parser::~Parser()
50+
{
51+
if (m_context)
52+
av_vorbis_parse_free(&m_context);
53+
}
54+
55+
void Vorbis::Parser::reset() const
56+
{
57+
av_vorbis_parse_reset(m_context);
58+
}
59+
60+
Optional<u32> Vorbis::Parser::parse_packet_duration_in_samples(ReadonlyBytes packet) const
61+
{
62+
int flags = 0;
63+
auto duration = av_vorbis_parse_frame_flags(m_context, packet.data(), AK::clamp_to<int>(packet.size()), &flags);
64+
if (duration < 0)
65+
return {};
66+
return duration;
67+
}
68+
69+
Optional<AK::Duration> Vorbis::Parser::parse_packet_duration(ReadonlyBytes packet, u32 sample_rate) const
70+
{
71+
auto duration = parse_packet_duration_in_samples(packet);
72+
if (!duration.has_value())
73+
return {};
74+
return AK::Duration::from_time_units(duration.value(), 1, sample_rate);
75+
}
76+
77+
}

Libraries/LibMedia/Codecs/Vorbis.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) 2026-present, the Ladybird developers.
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
#include <AK/Noncopyable.h>
10+
#include <AK/Optional.h>
11+
#include <AK/Span.h>
12+
#include <AK/Time.h>
13+
14+
struct AVVorbisParseContext;
15+
16+
namespace Media::Codecs {
17+
18+
class Vorbis {
19+
public:
20+
class Parser {
21+
AK_MAKE_NONCOPYABLE(Parser);
22+
23+
public:
24+
static Optional<Parser> create(ReadonlyBytes codec_initialization_data);
25+
26+
Parser(Parser&&);
27+
Parser& operator=(Parser&&);
28+
~Parser();
29+
30+
void reset() const;
31+
Optional<u32> parse_packet_duration_in_samples(ReadonlyBytes packet) const;
32+
Optional<AK::Duration> parse_packet_duration(ReadonlyBytes packet, u32 sample_rate) const;
33+
34+
private:
35+
explicit Parser(AVVorbisParseContext*);
36+
37+
AVVorbisParseContext* m_context { nullptr };
38+
};
39+
};
40+
41+
}

0 commit comments

Comments
 (0)