Commit graph

68 commits

Author SHA1 Message Date
Zaggy1024
e42d9efcb6 LibMedia: Remove reserved values from CICP enums
These served no purpose, instead we should determine validity with a
full switch statement.
2026-04-18 01:25:00 -05:00
Zaggy1024
2a75c0db62 LibMedia: Add a TimeRanges class to be used for buffered attributes
This will be used by PlaybackManager and Demuxer implementations to
communicate the buffered time up to HTMLMediaElement and SourceBuffer.
2026-04-01 02:54:22 -05:00
Zaggy1024
b5eddc4ff0 LibMedia: Stop using av_find_best_stream() to select preferred tracks
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).
2026-04-01 02:54:22 -05:00
Zaggy1024
d6f821f22d LibMedia: Parse Opus frame durations in Matroska::Reader
Most WebM files don't have their default duration defined, so we need
to parse the Opus frame header to determine the duration. This is
needed for buffered range calculation.
2026-04-01 02:54:22 -05:00
Zaggy1024
6b45a11716 LibMedia: Calculate Matroska block timestamps for their actual tracks
Instead of using a single track entry for all blocks in the file, use a
lookup to get the info needed to calculate the timestamp for the
specific track a block belongs to. No change in behavior for
SampleIterator, since that only returns blocks from the track that was
passed. This will be useful for MSE, since it demuxes all tracks at
once.
2026-04-01 02:54:22 -05:00
Zaggy1024
fbcfc06151 Tests: Add tests for parsing of basic EBML/Matroska building blocks 2026-04-01 02:54:22 -05:00
Zaggy1024
d189be12ba Tests: Increase the TestPlaybackStream disconnection timeout
Apparently the queued task often takes longer than 100ms to finish,
and the stream is kept alive for the duration of its execution.
2026-03-30 20:57:04 -05:00
Zaggy1024
c77eae4a55 Tests: Only end TestPlaybackStream after the promise is resolved
Otherwise, the promise handlers may never be invoked, and we won't know
whether we're matching expectations.

Prior to 39d865b, this test did not actually check if the PulseAudio
stream was able to connect before passing the test. After that commit,
it would instead try to post the rejection to the main thread, which
would either lead to a crash preventing a use-after-free on the main
event loop, or in the more common case, the event loop would be freed
and never invoke the rejection callback.
2026-03-30 20:57:04 -05:00
Zaggy1024
39d865b403 LibMedia: Provide new PlaybackStreams through promises
This allows us to avoid returning a PlaybackStream in cases where the
async initialization fails.

This is a step towards more graceful fallbacks when audio fails in
AudioMixingSink.
2026-03-21 23:11:47 -05:00
Jonathan Gamble
410e17ab29 LibMedia: Fix signed PCM to float sample normalization 2026-02-25 01:50:00 -06:00
Zaggy1024
1453add361 Tests: Use the proper stream close() method in TestFFmpegDemuxer
The test was outdated and needed to be updated before being merged. :^(
2026-02-24 17:51:03 -06:00
Zaggy1024
3a7f1f0b75 Tests: Add a test to read FFmpegDemuxer to EOF after aborting a read 2026-02-24 16:55:40 -06:00
Zaggy1024
af45418fbf Everywhere: Rename IncrementallyPopulatedStream::reached_end_of_body
This needs to be called even if we haven't reached the end of the body,
so let's call it close() instead.
2026-02-18 13:13:32 -06:00
Jonathan Gamble
0dea87110e LibMedia: Fix multi-channel decode for dumb containers (like WAV)
For web audio, I reckon an occasional misjudged channel layout is
better than more frequent exceptions.

Signed PCM is normalized with unsigned max divided by 2, not
signed max. If you divide by the signed max (32767), you get headroom
that can exceed the threshold below -1.0. It's not audible, this mostly
matters for tests that assume correct normalization. But it turns out
there's no shortage of "golden ears" jackholes out there who swear they
can hear the difference.
2026-02-13 17:57:19 -06:00
Zaggy1024
0f69eac657 Tests: Add tests for data providers exiting the suspended state
These tests would crash before the prior commit.
2026-02-09 09:04:21 +01:00
Zaggy1024
ed10073c74 Tests: Add a test for seeking MatroskaDemuxer to EOS 2026-02-06 13:28:09 +01:00
Zaggy1024
972438c4d7 LibMedia: Abstract the interface of IncrementallyPopulatedStream
The way that other classes interact with IncrementallyPopulatedStream
is now through a virtual interface MediaStream and MediaStreamCursor.
This way, we can have simpler implementations of reading media data
that will not require an RB tree and synchronization.
2026-01-30 10:02:00 -06:00
Zaggy1024
75231e63b1 LibMedia: Only pass Demuxer to the data providers
...and abstract away the stream/cursor blocking/aborting functionality
so that demuxers can implement or ignore those methods as they see fit.

This is a step towards implementing a wrapper demuxer for MSE streams.
2026-01-30 10:02:00 -06:00
Zaggy1024
798a945e3c Tests: Add tests for Media::IncrementallyPopulatedStream 2026-01-29 18:06:02 -06:00
Zaggy1024
d50ab3015e Tests: Test seeking in Matroska files with and without cues 2026-01-28 16:12:22 -06:00
Zaggy1024
f455901d37 Tests: Add a test for Matroska EBML lacing 2026-01-28 16:12:22 -06:00
Zaggy1024
ee95de40d6 LibMedia: Validate fixed-size Matroska frames
We were allowing Matroska blocks with fixed-size lacing to contain
frames with non-divisible sizes. This should not be possible, as it
inherently means that trailing bytes will be discarded.

We now have a valid and invalid testcase for fixed-size lacing to
ensure our handling remains correct.
2026-01-28 16:12:22 -06:00
Zaggy1024
bc67764606 Tests: Change some VERIFY(x == y) to EXPECT_EQ in TestParseMatroska 2026-01-28 16:12:22 -06:00
Zaggy1024
f0d7d1d5f5 LibMedia: Track Matroska master element ends with position()
We don't actually need a Vector stack of bytes read for each element
we're reading out of a Matroska file, we already have the C++ stack
in which we can store the start and end of the master elements we're
reading.

This fixes an issue where seeks while parsing master elements would not
increment m_octets_read, so the master element could continue reading
further than intended.

This could cause a BlockGroup followed by a SimpleBlock to read as if
the BlockGroup contained the SimpleBlock, meaning that SampleIterator
would skip the SimpleBlock.

A test is added to ensure this doesn't regress again.
2026-01-28 14:48:03 -06:00
Tim Ledbetter
5d5188a679 Tests/LibMedia: Add test for reserved and unspecified CICP handling 2026-01-27 11:34:24 -06:00
Zaggy1024
e6dbcccb99 LibGfx+LibMedia: Send video frames to Skia as subsampled YUV
This saves us from having our own color conversion code, which was
taking up a fair amount of time in VideoDataProvider. With this change,
we should be able to play high resolution videos without interruptions
on machines where the CPU can keep up with decoding.

In order to make this change, ImmutableBitmap is now able to be
constructed with YUV data instead of an RBG bitmap. It holds onto a
YUVData instance that stores the buffers of image data, since Skia
itself doesn't take ownership of them.

In order to support greater than 8 bits of color depth, we normalize
the 10- or 12-bit color values into a 16-bit range.
2026-01-22 19:44:36 +01:00
Undefine
6280ca0275 Tests: Explicitly link TestPlaybackStream against PulseAudio
This fixes a compile issue on FreeBSD where this would not compile as
the `pulse/pulseaudio.h` header is not in the default search path,
instead it is in `/usr/local/include'. This is a problem because this
test manually includes `PulseAudioWrappers.h`.
2026-01-12 20:58:21 +01:00
Zaggy1024
9a421ffe9f LibMedia: Make demuxers thread-safe and remove MutexedDemuxer 2026-01-07 00:13:32 +01:00
Zaggy1024
b77980b4cc LibMedia: Move Matroska's get_frames method to SampleIterator
This ensures that we're using the reader for the particular thread that
the block was read from, avoiding any race conditions between seeks and
reads across threads.
2026-01-07 00:13:32 +01:00
Zaggy1024
5aa5beed26 LibMedia: Avoid copying Matroska block data when seeking
We only need to get the frames from a block when requested by the
demuxer, so factor that out into a function that it can call when it is
outputting frames.
2026-01-05 17:53:24 -06:00
Gingeh
451177f1f4 LibMedia: Propagate errors from create_context_for_track 2026-01-02 16:19:44 +01:00
Zaggy1024
744abc0c18 Tests: Use some tasks when testing PlaybackStream
This doesn't help to reproduce the potential UAF in the previous
commit, but it doesn't hurt to include this nonetheless.
2025-12-29 19:58:22 -06:00
R-Goc
1f3e20cebf LibMedia: Add a WASAPI playback stream for Windows
Implement PlaybackStream using WASAPI. The design is similar to
PlaybackStreamAudioUnit in that it uses a task queue. A high priority
thread is used to render the stream. All the stream controls save for
the exit being requested which happens on destruction of the stream are
managed by the render thread.

Due to the design of the windows audio mixer the audio we receive must
be resampled to match the sample rate of the mixer. We use a float based
interleaved PCM stream which matches both our existing code and the
audio mixer which internally usues floats.

Having to use a mutex around a queue for the task queue is suboptimal,
in a future PR a MPSC queue could be added to AK and used instead.
2025-12-29 18:02:02 -06:00
Aliaksandr Kalenik
c5d8cb5c47 LibMedia: Change demuxers to use IncrementallyPopulatedStream as input
Refactor the FFmpeg and Matroska demuxers to consume data through
`IncrementallyPopulatedStream::Cursor` instead of a pointer to fully
buffered.

This change establishes a new rule: each track must be initialized with
its own cursor. Data providers now explicitly create a per-track context
via `Demuxer::create_context_for_track(track, cursor)`, and own pointer
to that cursor. In the upcoming changes, holding the cursor in the
provider would allow to signal "cancel blocking reads" so an
in-flight seek can fail immediately when a newer seek request arrives.
2025-12-16 02:42:48 -06:00
Zaggy1024
07b5ac5db0 Tests: Update TestWav to use TestMediaCommon and AudioDataProvider 2025-12-15 18:03:03 -06:00
ayeteadoe
ca5a321728 Tests/LibMedia: Avoid assertions that assume a valid PlaybackStream
Windows currently doesn't have a LADYBIRD_AUDIO_BACKEND set, this means
Audio::PlaybackStream::create() always returns an Error. We should not
perform assertions in TestPlaybackStream that assume an implementation
always exists.
2025-12-13 16:45:29 -06:00
Zaggy1024
c75284591f LibMedia: Make PlaybackStream use the output's sample specification
Instead of specifying the sample rate, channel count/map, etc. to the
PlaybackStream, we'll now use the output device's sample specification
whenever possible. If necessary, the stream will fall back to sane
default.

This hugely simplifies AudioMixingSink, since it no longer has to take
care of reinitializing the stream with a new sample specification when
it encounters a track with a higher sample rate or more channels. We
wouldn't be likely to benefit from this anyway, since it turns out that
at least Windows's virtual surround doesn't work through WASAPI at all,
and WASAPI likely wouldn't support downmixing.

This commit breaks playback of audio files that don't match the system
default audio device's sample rate and channel count. The next commit
introduces a converter into the pipeline to allow mixing of any sample
specification.
2025-12-13 08:58:26 +01:00
Zaggy1024
b2ab53fc84 Tests: Don't write any audio data in TestPlaybackStream
Also, ensure that the total time played reflects that.
2025-12-13 08:58:26 +01:00
Zaggy1024
5c9a34d84a LibMedia: Only use floats for audio data callbacks
We were already assuming that our streams were using floats, we may as
well hardcode this. If we ever encounter a platform API that doesn't
support or convert from float, we can always bring this back.

Also, since we don't support big-endian systems, remove that check in
PulseAudioWrappers.
2025-12-13 08:58:26 +01:00
Zaggy1024
81a473314d LibMedia: Store the duration of VideoFrames 2025-12-10 16:02:40 -06:00
Zaggy1024
b48c5b4b27 LibMedia+Tests: Avoid deferred-invoking on dead event loops
Posting callbacks to the main thread is now predicated on whether the
event loop reference is alive, preventing a stack-use-after-return.

The data providers will also check if they've been requested to exit
before calling deferred_invoke, though this is not going to be the case
unless the media element gets GCed while the media is playing.
2025-12-10 16:02:40 -06:00
Aliaksandr Kalenik
d754a569e0 LibMedia: Delete from_file() and from_mapped_file() in MatroskaDemuxer
These calls were only used in tests where we could read the whole file
into memory and use `from_data()`.
2025-12-10 11:38:13 -06:00
Aliaksandr Kalenik
9f60828a57 LibMedia+LibWeb: Create demuxer and extract tracks on a separate thread
Demuxer creation and track+duration extraction are moved to a separate
thread so that the media data byte buffer is no longer accessed from the
main thread. This will be important once the buffer is populated
incrementally, as having the main thread both populate and read from the
same buffer could easily lead to deadlocks. Aside from that, moving
demuxer creation off the main thread helps to be more responsive.

`VideoDataProvider` and `AudioDataProvider` now accept the main thread
event loop pointer as they are constructed from the thread responsible
for demuxer creation.
2025-12-09 17:36:18 -06:00
Zaggy1024
2bd541c70c LibMedia: Keep data providers suspended until they are active
This allows us to avoid waiting on the condition variable for the error
handler to be set when an error occurs.
2025-12-03 12:20:49 -06:00
Zaggy1024
6790a695da Tests: Wait for PulseAudio context destruction in TestPlaybackStream
The stream was being kept alive until the moment before we check if the
context is still alive. The stream's control thread holds a reference
to the PulseAudioContext, so that should almost never be destroyed
before the VERIFY in the test. Instead, wait at most 100ms for it to be
destroyed.
2025-11-25 13:13:32 -06:00
Zaggy1024
13cf0e72d8 LibMedia: Stop using threading-unsafe Weakable for PulseAudioContext
We can't control whether the instantiation mutex is held when
~Weakable() is called, so we need to implement this via a static raw
pointer instead to ensure that all operations on it are effectively
atomic.
2025-11-24 15:09:34 -06:00
Zaggy1024
0664e9fbf5 Tests: Ensure that AudioDataProvider never outputs overlapping blocks 2025-11-17 16:51:18 +01:00
Zaggy1024
0f6f5cd353 LibMedia+Tests: Clear the current block when seeking in MatroskaDemuxer
Otherwise, if the sample iterator resides in a block with multiple
frames before the seek, the demuxer will output all the remaining
frames from that block before moving on to the block at the seeked
position.
2025-11-13 13:48:17 +01:00
Zaggy1024
8fa081cb1d Tests: Add a test for Matroska blocks with Xiph lacing 2025-11-11 00:50:42 +01:00
Zaggy1024
d17e7fd921 LibMedia+Tests: Adapt TestVorbisDecode to use AudioDataProvider 2025-10-27 17:28:49 -07:00