Commit graph

78 commits

Author SHA1 Message Date
Zaggy1024
9ca1db3bb8 LibMedia: Rename timing getters in AudioBlock
This new naming will make timescale modifications more comprehensible.
2026-06-06 19:58:17 -05:00
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
Zaggy1024
c88b1ca8f7 LibMedia: Reduce the amount of WAV data loaded before playback starts
Apparently FFmpeg probes WAV files for 32 packets before returning the
stream info. With the default of 19200 bytes per packet, this could end
up waiting for up to 5 seconds of data to be downloaded.

Setting the max_size option only affects WAV and W64 in our build of
libavformat. No other formats we care about will be affected.
2026-05-28 10:30:20 -05:00
Zaggy1024
c901896972 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.
2026-05-28 10:30:20 -05:00
Zaggy1024
16f5a3405f LibMedia: Implement buffered ranges for MP3
A new MP3Navigator class is added, which determines timestamps for byte
positions by resyncing to a frame and then interpolating between known
points on either side. The known points start out as the first frame's
position in the file at timestamp 0, and EOF at the timestamp for
FFmpeg's file duration estimate. New buffered ranges are interpolated
between those two points, but also between the end of a prior range and
the start of the next.

Since MP3 can have variable bitrate without declaring it in the file
header, we have to allow buffered ranges to shift forward as new data
arrives to make room for underestimated durations. This is done for all
ranges following the first that has been appended to, keeping the start
of the current range consistent, so that subsequent seeks within that
range remain consistent.

Seeking is also implemented within the navigator to ensure that the
byte<->timestamp mapping is consistent and the buffered ranges begin
exactly where the seek landed.
2026-05-28 10:30:20 -05:00
Zaggy1024
9f788cb80f LibMedia: Implement CBR seeking through ContainerNavigator
This mainly acts as a proof of concept for manual seeking
implementations, which may prove useful for other formats than WAV.
2026-05-28 10:30:20 -05:00
Zaggy1024
454830c7cc LibMedia: Always clear EOF/error flags in AVIOContext when seeking
We could end up seeked within AVIOContext's internal buffer without it
checking whether new data could be buffered without hitting EOF again,
so we could get an unexpected read error.
2026-05-28 10:30:20 -05:00
Zaggy1024
5bb108dd5c LibMedia: Calculate accurate buffered ranges for FLAC
Add a new FLACNavigator class that can scan for frames both forward and
backward from a byte offset to get the exact buffered ranges. The reads
are done in chunks, which keeps each call pretty cheap.

The ranges themselves are resolved by its ScanningContainerNavigator
base class, which reconciles cached ranges against the incoming ones to
avoid repeating work unnecessarily. With those combined optimizations,
the calls normally take under 1 microsecond.
2026-05-28 10:30:20 -05:00
Zaggy1024
3098c0fb62 LibMedia: Implement buffered ranges for FFmpegDemuxer
The default mode is to grab the index and use it to determine the start
and end of the buffered ranges. This works well for MP4.

For WAV, the index is incomplete, so instead use a much simpler method,
just determining the ranges based on the constant bitrate of the file.

This is implemented through a new virtual ContainerNavigator class.
2026-05-28 10:30:20 -05:00
Zaggy1024
3b7c1810e6 LibMedia+Tests: Refer to raw audio frames/samples consistently
Previously, we weren't too consistent about the definition of frame and
sample when it relates to raw audio data. This brings all the usages in
the context of raw data in line (hopefully), with samples referring to
a single PCM value, and frames referring to the multiple samples that
make up an instant's audio across all channels.
2026-05-13 02:05:35 -05:00
Zaggy1024
a4c2c6c0cb LibMedia+LibWeb: Speculatively find keyframes near fast seek targets
This is the new way of handling fast seeks. Instead of delegating the
logic all the way down the pipeline to the decoder thread's seek
handler, we can just determine the timestamp we want to seek to ahead
of time.
2026-05-13 02:05:35 -05:00
Zaggy1024
a60db31902 LibMedia: Use Vector<float> for AudioBlock storage
This will allow reuse of the allocations, instead of reallocating a
FixedArray for every block that changes size. Generally, it will be
possible to reuse AudioBlock memory throughout most of the pipeline
at least while in a single process.
2026-05-13 02:05:35 -05:00
antoniospg
428fa59167 LibMedia+LibWeb: Implement HTMLMediaElement.getStartDate() 2026-05-05 14:45:00 -05:00
Aliaksandr Kalenik
febd85f417 LibMedia+LibWeb: Store decoded YUV data in video frames
Decoded video frames should own their planar YUV data and color space
directly. Keeping that storage behind ImmutableBitmap gave a
still-image abstraction media-specific behavior and made calls like
bitmap() potentially allocate and convert a whole video frame.

Move YUV ownership into Media::VideoFrame, where the lifetime naturally
follows media playback, and remove the YUV-backed mode from
ImmutableBitmap. This commit intentionally keeps the visible Web paint
path on ExternalContentSource by converting the current frame back to
an ImmutableBitmap where Web still expects one.

Callers that need pixels now ask the frame to convert explicitly. That
preserves behavior for canvas and bitmap consumers while making the
expensive YUV-to-pixel path visible at the call site instead of
hiding it behind ImmutableBitmap::bitmap().
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
a659c55504 LibMedia: Make video frames ref-counted
Video frames are about to be shared between the decoder, the data
provider, the display sink, and Web painting code. Passing them by
value keeps ownership tied to the old bitmap-shaped pipeline and makes
later lifetime changes harder to reason about.

Make VideoFrame ref-counted and return NonnullRefPtr from the decoder
and media queues. This changes ownership only: a VideoFrame still wraps
an ImmutableBitmap at this point, so playback behavior remains
unchanged while later commits can move storage and painting
independently.
2026-05-05 14:39:17 -05:00
Zaggy1024
102ce69c8d LibGfx/LibMedia: Override transfer characteristics to sRGB in LibGfx
Doing this in FFmpegVideoDecoder meant that the fallback to BT.709 with
unspecified transfer characteristics was taking precedence. Instead of
overriding unspecified to sRGB there, change the switch statement in
ColorSpace::from_cicp() so that the unspecified fallback and override
both live in the same place.

This should fix the contrast on a lot of YouTube videos.

Note that this now also affects images, which is consistent with
Chromium.
2026-04-18 01:25:00 -05:00
Zaggy1024
c47971e563 LibGfx: Stop storing SkYUVAPixmaps in YUVData
Storing these was pointless, since they're only used briefly to provide
the info needed to upload the buffers to the GPU.

For BT.2020 coefficients, we can just say that the bit depth is 16 bits
since we're always bit replicating to that for Skia's shaders.
2026-04-18 01:25:00 -05:00
Zaggy1024
8208d6f3c5 LibMedia: Ignore unknown track types in FFmpegDemuxer
Fixes #8804.
2026-04-08 13:03:39 +02:00
Zaggy1024
9d26a274e4 LibMedia: Allow demuxers to report buffered times to PlaybackManager
PlaybackManager then intersects all enabled tracks' buffered time
ranges. This will be used by the media element for the buffered
attribute and to update the ready state.
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
a3309fe5f6 LibMedia: Set track kinds in FFmpegDemuxer according to spec
For tracks that aren't ISOBMFF (and therefore have no specified kind),
just always set "main".
2026-04-01 02:54:22 -05:00
Zaggy1024
b4db8f11c5 LibMedia+LibWeb: Align Media::Track more to the web spec
...giving tracks a kind attribute, and renaming name to label.

Demuxers will need to determine the kind attribute, since the spec for
sourcing tracks requires us to select based on info we don't expose.
2026-04-01 02:54:22 -05:00
Jonathan Gamble
410e17ab29 LibMedia: Fix signed PCM to float sample normalization 2026-02-25 01:50:00 -06:00
Zaggy1024
4778c6a53b LibMedia: Deal with aborts in FFmpegDemuxer instead of the IO context
libavcodec apparently holds onto any error that is not AVERROR_EOF when
a read fails. This means that reading until EOF after an aborted read
results in us receiving an AVERROR_EXIT in FFmpegDemuxer instead of
AVERROR_EOF, which causes the playback system to enter an error state
without decoding all frames in the file.

Instead, just always return AVERROR_EOF, and check if the read was
aborted in FFmpegDemuxer instead to return the correct error category
from there.
2026-02-24 16:55:40 -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
27d413c193 LibMedia: Dispose FFmpegDemuxer's tracks' format contexts
This fixes a leak that was oddly enough only just detected in CI.
2026-01-30 16:40:21 -06: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
31cc0c0a45 LibMedia: Save info from FFmpeg's AVFormatContext and destroy it
It's not necessary to keep around an instance of AVFormatContext in
FFmpegDemuxer, so instead just copy out the info we need for our
implementation and then destroy it so that our stream cursor is freed.
2026-01-30 10:02:00 -06:00
Tim Ledbetter
1aa68689ff LibMedia: Remove default_code_points_if_unspecified()
This function is no longer needed as CICP values are now defaulted
downstream.
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
Gingeh
451177f1f4 LibMedia: Propagate errors from create_context_for_track 2026-01-02 16:19:44 +01:00
Aliaksandr Kalenik
21e8ece013 LibMedia: Abort blocking reads for ongoing seek if it's replaced
When a seek is requested while a previous seek is still blocked waiting
for not yet available bytes, we want to abandon the old request
immediately and start processing the new one.
2025-12-16 02:42:48 -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
269f232e52 LibMedia: Correct the formula for converting u8 samples to float 2025-12-15 18:03:03 -06:00
Zaggy1024
7c101ab910 LibMedia: Add WAV PCM codecs to CodecID
This is a subset of FFmpeg's PCM codec IDs, limited to only the ones
that are supported by its WAV encoder.
2025-12-15 18:03:03 -06:00
Zaggy1024
fa3cda8e4a LibMedia: Set the AVCodecContext's sample rate and channel layout
This is required in order for our audio block output function to create
a sample specification for WAV files.
2025-12-15 18:03:03 -06:00
Zaggy1024
b4ca17fd29 LibMedia: Store a SampleSpecification in Track
This allows FFmpegDemuxer to communicate the data necessary to convert
WAV PCM to whatever our output format is.
2025-12-15 18:03:03 -06:00
Zaggy1024
65c0be66e4 LibMedia: Implement audio conversion in AudioDataProvider 2025-12-13 08:58:26 +01:00
Zaggy1024
19ccec2c10 LibMedia: Create a channel map for audio blocks from decoders
Audio blocks now contain a sample specification with the sample rate
and channel map for the audio data they contain. This will facilitate
conversion from one sample specification to another in order to allow
playback on devices with more or less speakers than the audio data
contains.
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
42b19bdebf LibMedia: Store a duration in CodedFrame 2025-12-10 16:02:40 -06:00
Zaggy1024
7e325d64f5 LibMedia: Use [from/to]_time_units in FFmpegDemuxer and AudioMixingSink 2025-11-17 16:51:18 +01:00
Zaggy1024
0e9c746b48 LibMedia: Allow signalling of EOF to decoders
This is used by FFmpeg's H.264 decoder to determine when to output any
buffered frames after data runs out.
2025-11-05 18:40:02 +01:00
Zaggy1024
634e5ff491 LibMedia: Move CICP values from CodedFrame to Track
All our current demuxers have these constant for an entire track, so we
don't need to get them for every frame we output.
2025-11-05 18:40:02 +01:00
Zaggy1024
6bd7e338fd LibMedia: Remove redundant parameter names in Demuxer 2025-11-05 18:40:02 +01:00
Zaggy1024
4cf0a77d38 LibMedia: Export FFmpegDemuxer to allow direct use of data providers
We can use forward declarations of the FFmpeg types within the header
to allow it to be exported without causing errors in using code.
2025-10-27 17:28:49 -07:00
Zaggy1024
fcde0f66c8 LibMedia: Allow FFmpegDemuxer to grab samples from multiple streams
In order to do so, we create a AVFormatContext for each stream we want
to demux.
2025-10-27 17:28:49 -07:00
Zaggy1024
9c960c3307 LibMedia: Use integer math to calculate the FFmpeg seek target pts 2025-10-27 17:28:49 -07:00
Zaggy1024
b1c9a872bc LibMedia: Return whether a demuxer seek moved the stream position
We no longer need to return a timestamp from the seek function, which
makes it much easier to implement backend-agnostically.
2025-10-27 17:28:49 -07:00