Media3 can only write MP4, so stop claiming otherwise

CI proved the WAV and Ogg exports this branch added cannot work, and the reason
generalises further than those two.

media3-muxer 1.11.0 ships WebmMuxer, OggMuxer, WavMuxer and AacMuxer, which is
why MEDIA3_CONTAINERS listed the matching containers. But all four throw
UnsupportedOperationException from addMetadataEntry, and
MuxerWrapper.addTrackFormat calls it for every metadata entry on the track
format. Any real recording carries at least a creation timestamp, so the export
dies partway through:

    Caused by: java.lang.UnsupportedOperationException
        at androidx.media3.muxer.OggMuxer.addMetadataEntry(OggMuxer.java:123)
        at androidx.media3.transformer.MuxerWrapper.addTrackFormat(MuxerWrapper.java:488)

They are standalone muxers, not Transformer-compatible ones. WAV fails a second
way before even reaching that: DefaultEncoderFactory has no PCM encoder, so
Transformer reports "No MIME type is supported by both encoder and muxer"
instead of passing raw samples through.

Both observed on an API 35 emulator in CI, not inferred. The tests that found
them were written on the assumption these containers worked.

So MEDIA3_CONTAINERS becomes {MP4}. That the set was wrong went unnoticed
because the engine ignored the container and wrote MP4 regardless — the set
being wrong and the engine being wrong cancelled out. WAV, Opus and raw AAC move
to FFmpeg, which already produces all three with instrumented coverage asserting
the produced files.

WEBM_VP9's routing reason changes from NO_PLATFORM_ENCODER to
CONTAINER_UNSUPPORTED. Both were always true; the container is the more
fundamental, since even given a VP9 encoder the file could not be written.

The audio-only regression guard this branch exists for passed on API 35: M4A
output now carries exactly one AAC track and no video.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 07:17:11 -05:00
co-authored by Claude Opus 5
parent 00c422f317
commit 92b7395ba9
5 changed files with 86 additions and 207 deletions
@@ -108,48 +108,6 @@ class Media3EngineTest {
}
}
/**
* WAV output, which reaches Media3 for the same reason M4A does.
*
* `Container.WAV` is in the router's Media3 set, but until the engine passed a muxer factory
* the only muxer Transformer ever used was the MP4 one — so asking for WAV produced an MP4.
* Asserting the RIFF header proves the container, not merely that a file appeared; this
* follows what `FFmpegEngineTest` already does for its formats.
*/
@Test
fun wavExportWritesARiffHeader(): Unit = runBlocking {
val wav = File(context.cacheDir, "out_audio.wav")
wav.delete()
try {
engine.transcode(Uri.fromFile(input), wav, OutputFormat.WAV)
assertTrue("export produced no file", wav.exists() && wav.length() > 0)
assertEquals("RIFF", String(wav.readBytes().copyOfRange(0, 4), Charsets.US_ASCII))
} finally {
wav.delete()
}
}
/**
* Ogg/Opus output, the third container the router claims for Media3.
*
* Asserted by magic bytes rather than `MediaExtractor`: platform extractor support for raw
* Ogg is inconsistent across the API levels in the CI matrix, and "OggS" is unambiguous.
*/
@Test
fun opusExportWritesAnOggHeader(): Unit = runBlocking {
val ogg = File(context.cacheDir, "out_audio.opus")
ogg.delete()
try {
engine.transcode(Uri.fromFile(input), ogg, OutputFormat.OPUS)
assertTrue("export produced no file", ogg.exists() && ogg.length() > 0)
assertEquals("OggS", String(ogg.readBytes().copyOfRange(0, 4), Charsets.US_ASCII))
} finally {
ogg.delete()
}
}
/**
* Regression guard for the Transformer threading trap.
*