Merge Media3's MP4-only correction into the remux branch
CI on the parent branch proved that four of the five containers the router claimed for Media3 cannot be written by Transformer at all: WebmMuxer, OggMuxer, WavMuxer and AacMuxer each throw UnsupportedOperationException from addMetadataEntry, which MuxerWrapper calls for every metadata entry on the track format. Consequences here beyond the merge itself: - MEDIA3_MUXABLE_VIDEO and MEDIA3_MUXABLE_AUDIO drop to a single MP4 entry. Every other container is already on its way to FFmpeg before those maps are consulted. - Reason.WEBM_CODEC_UNSUPPORTED is removed. WebM now fails the container check first, so nothing could ever produce that reason, and a routing reason no code path can reach is worse than no reason at all. - Media3Muxers gains null branches for the six containers this branch adds. MOV is among them despite being MP4's own family: Mp4Muxer exposes no QuickTime file format. - The README no longer claims Media3 writes five containers. The remux behaviour this branch exists for is unaffected: MKV -> MP4 was always the hardware direction, because Media3 reads Matroska but has never been able to write it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,9 +19,10 @@ import org.libremediaconverter.model.VideoCodec
|
||||
/**
|
||||
* Guards the agreement between the router's container set and the muxers that back it.
|
||||
*
|
||||
* These two drifted once already, and the failure was invisible: [ConversionRouter] correctly
|
||||
* claimed WebM, Ogg, WAV and AAC for Media3 while [Media3Engine] had no way to write any of them
|
||||
* and produced MP4 regardless. Nothing failed — the file was simply the wrong container.
|
||||
* These two drifted once already, and the failure was invisible in both directions: the router
|
||||
* claimed WebM, Ogg, WAV and AAC-ADTS for Media3 — none of which Transformer can actually write —
|
||||
* while [Media3Engine] ignored the container and produced MP4 for all of them. Two bugs that
|
||||
* cancelled out, so nothing failed and the file was simply the wrong container.
|
||||
*
|
||||
* A JVM test rather than an instrumented one: constructing a factory touches no Android APIs, only
|
||||
* `create()` does, so the mapping is checkable without a device.
|
||||
@@ -51,54 +52,18 @@ class Media3MuxersTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WebM advertises only the codecs its muxer accepts`() {
|
||||
val factory = requireNotNull(Media3Muxers.factoryFor(Container.WEBM))
|
||||
|
||||
assertEquals(
|
||||
listOf(MimeTypes.VIDEO_VP8, MimeTypes.VIDEO_VP9),
|
||||
factory.getSupportedSampleMimeTypes(C.TRACK_TYPE_VIDEO),
|
||||
)
|
||||
assertEquals(
|
||||
listOf(MimeTypes.AUDIO_OPUS, MimeTypes.AUDIO_VORBIS),
|
||||
factory.getSupportedSampleMimeTypes(C.TRACK_TYPE_AUDIO),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The audio-only containers must not claim a video track.
|
||||
* MP4 is the whole of it.
|
||||
*
|
||||
* Transformer reads these lists to decide what it may hand the muxer. Claiming video for a
|
||||
* container that cannot hold it turns a routing mistake into a corrupt file rather than a
|
||||
* clean failure.
|
||||
* Pinned as a value rather than left implicit, because `media3-muxer` shipping a `WebmMuxer`,
|
||||
* `OggMuxer`, `WavMuxer` and `AacMuxer` makes it look like there should be five. Those four
|
||||
* throw `UnsupportedOperationException` from `addMetadataEntry`, which `MuxerWrapper` calls
|
||||
* unconditionally — see [Media3Muxers]. If a future Media3 release fixes that, this test is
|
||||
* where the decision to widen the set gets made deliberately.
|
||||
*/
|
||||
@Test
|
||||
fun `audio-only containers advertise no video codecs`() {
|
||||
listOf(Container.OGG, Container.WAV, Container.AAC_ADTS).forEach { container ->
|
||||
val factory = requireNotNull(Media3Muxers.factoryFor(container))
|
||||
assertTrue(
|
||||
"$container claims video support",
|
||||
factory.getSupportedSampleMimeTypes(C.TRACK_TYPE_VIDEO).isEmpty(),
|
||||
)
|
||||
assertTrue(
|
||||
"$container claims no audio support",
|
||||
factory.getSupportedSampleMimeTypes(C.TRACK_TYPE_AUDIO).isNotEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WAV carries PCM and AAC-ADTS carries AAC`() {
|
||||
assertEquals(
|
||||
listOf(MimeTypes.AUDIO_RAW),
|
||||
requireNotNull(Media3Muxers.factoryFor(Container.WAV))
|
||||
.getSupportedSampleMimeTypes(C.TRACK_TYPE_AUDIO),
|
||||
)
|
||||
assertEquals(
|
||||
listOf(MimeTypes.AUDIO_AAC),
|
||||
requireNotNull(Media3Muxers.factoryFor(Container.AAC_ADTS))
|
||||
.getSupportedSampleMimeTypes(C.TRACK_TYPE_AUDIO),
|
||||
)
|
||||
fun `Media3 can write MP4 and nothing else`() {
|
||||
assertEquals(setOf(Container.MP4), ConversionRouter.MEDIA3_CONTAINERS)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,13 +50,16 @@ class ConversionRouterTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `webm vp9 routes to ffmpeg because media3 cannot encode vp9`() {
|
||||
// Transformer.setVideoMimeType accepts only H.263/H.264/H.265/MP4V. The WebM
|
||||
// muxer exists, but there is no VP9 *encoder* behind it, so producing WebM
|
||||
// video is FFmpeg's job even though the container is nominally supported.
|
||||
fun `webm vp9 routes to ffmpeg`() {
|
||||
// Two independent reasons, either of which is sufficient: Transformer.setVideoMimeType
|
||||
// accepts only H.263/H.264/H.265/MP4V, so there is no VP9 encoder — and Media3 cannot mux
|
||||
// WebM at all, which it was previously credited with. The container check runs first and
|
||||
// is the more fundamental of the two: even given a VP9 encoder, the file could not be
|
||||
// written. This asserted NO_PLATFORM_ENCODER while the container was wrongly believed to
|
||||
// be supported.
|
||||
val d = route(OutputFormat.WEBM_VP9)
|
||||
assertEquals(Engine.FFMPEG, d.engine)
|
||||
assertEquals(Reason.NO_PLATFORM_ENCODER, d.reason)
|
||||
assertEquals(Reason.CONTAINER_UNSUPPORTED, d.reason)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -304,6 +307,31 @@ class ConversionRouterTest {
|
||||
}
|
||||
}
|
||||
|
||||
// --- containers Media3 cannot write -------------------------------------
|
||||
|
||||
/**
|
||||
* WAV, Opus and raw AAC used to be claimed for Media3 and are not any more.
|
||||
*
|
||||
* `media3-muxer` ships a muxer for each, which is why they were listed — but none can be driven
|
||||
* by Transformer, so the export dies at the muxer. They were never actually produced on the
|
||||
* hardware path: the engine ignored the container and wrote MP4 into a file named `.wav`.
|
||||
* FFmpeg produces all three, and `FFmpegEngineTest` asserts the produced files.
|
||||
*/
|
||||
@Test
|
||||
fun `audio containers Media3 cannot mux route to FFmpeg`() {
|
||||
listOf(OutputFormat.WAV, OutputFormat.OPUS).forEach { format ->
|
||||
val d = route(format)
|
||||
assertEquals("$format should need FFmpeg", Engine.FFMPEG, d.engine)
|
||||
assertEquals(Reason.CONTAINER_UNSUPPORTED, d.reason)
|
||||
}
|
||||
}
|
||||
|
||||
/** M4A is the audio format that does stay on hardware, because its container is MP4. */
|
||||
@Test
|
||||
fun `m4a stays on hardware because MP4 is a container Media3 can write`() {
|
||||
assertEquals(Engine.MEDIA3, route(OutputFormat.M4A_AAC).engine)
|
||||
}
|
||||
|
||||
// --- exhaustiveness ----------------------------------------------------
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user