Skip to content

Mark the final Ogg page as the end of the stream - #922

Merged
chenxiaolong merged 2 commits into
chenxiaolong:masterfrom
django23:fix/ogg-eos-page
Aug 7, 2026
Merged

Mark the final Ogg page as the end of the stream#922
chenxiaolong merged 2 commits into
chenxiaolong:masterfrom
django23:fix/ogg-eos-page

Conversation

@django23

@django23 django23 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

TL;DR: BCR's .oga recordings are missing the marker that says "this is where the file ends". Most players do not care, but stricter ones refuse to play the file at all, and in Firefox none of them work. This adds the marker when a recording finishes. Nothing about the audio itself changes, and existing recordings are unaffected.

Technical version

The OGG/Opus files BCR writes never terminate the logical bitstream: no page in the output has the end-of-stream bit set in its header type flags. The Ogg spec requires the last page of a stream to carry that bit, so every .oga recording BCR has produced is malformed.

The cause is in AOSP's OggWriter. It sets each packet's flag from its own member inside the write loop:

op.e_o_s = mReachedEOS ? 1 : 0;

but mReachedEOS only becomes true at the end of threadFunc(), after the loop has finished. It is false for every packet actually written, and neither stop() nor reset() writes a terminating page afterwards.

OggContainer wraps MediaMuxer and, once it has stopped, sets the bit in the last page's header and recomputes that page's CRC. Audio data is untouched. Like FlacContainer, this is a finalization step guarded on encoder EOF, so aborted recordings behave exactly as they do today.

The practical benefit is that demuxers which seek to the end of the file to find the last granule position will accept the recordings. Firefox is one, and currently refuses to play them with NS_ERROR_DOM_MEDIA_METADATA_ERR.

The page and CRC logic lives in OggPage, free of Android dependencies so it can be unit tested on the JVM. OggPageTest covers the backwards page search, the OggS magic appearing inside page data, the flag and CRC update, and that nothing outside the header type byte and CRC field is touched. The CRC is checked against a bit-by-bit reference implementation that shares no code with the table driven one.

assembleDebug and the unit tests pass.

AOSP's OggWriter copies its mReachedEOS field into every packet's e_o_s
field, but only sets that field to true after the write loop has already
exited. No page in the output file ever has the end-of-stream bit set in
its header type flags, so the logical bitstream is never terminated.

Demuxers that seek to the end of the file to find the last granule
position reject the result. Firefox is one, and refuses to play BCR's
recordings with NS_ERROR_DOM_MEDIA_METADATA_ERR.

Add an OggContainer that wraps MediaMuxer and, once the muxer has
finished writing, sets the end-of-stream bit in the last page's header
and recomputes that page's CRC. As with FlacContainer, this only runs
after the encoder signals EOF, so aborted recordings are untouched.

The page structure and CRC logic is split into OggPage, which has no
Android dependencies and is covered by JVM unit tests.

Signed-off-by: Django Eijgensteijn <djangoboy@gmail.com>
@chenxiaolong

Copy link
Copy Markdown
Owner

Thanks for the PR! That's quite a dumb bug on the AOSP side. I'm able to reproduce this in libstagefright with the latest AOSP android-17.0.0_r1 release.

I will review this after work today.

MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG is not present in earlier SDK
versions.

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>

@chenxiaolong chenxiaolong left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This looks good to me. I only added 7f7e7e3 to annotate OggContainer with @RequiresApi(Build.VERSION_CODES.Q).

@chenxiaolong chenxiaolong self-assigned this Aug 7, 2026
chenxiaolong added a commit that referenced this pull request Aug 7, 2026
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
@chenxiaolong
chenxiaolong merged commit 7f7e7e3 into chenxiaolong:master Aug 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants