One defect family, two tickets, two files. The FFprobe codec vocabulary is written out in at
least four places and none of them had a test; two of the four had already stopped agreeing.
What was wrong
CodecNames.videoFromName resolved x264, hev1, x265 and vp09; AndroidDeviceCodecs.mimeForCodecName returned null for all four. So the app identified the
codec for the source card and for routing, then ran the device capability check blind on the
same string and took the "unknown, assume the platform copes" fallthrough — a wasted hardware
attempt on exactly the inputs it had already recognised. mpeg4 ran the other way.
One level down, describeVideo answered "Unrecognised" for InputProbe.UNPARSEABLE and describeAudio had no such arm. The sentinel opens with a NUL, so an unparseable audio codec
would have put U+0000 into a Text on the source-info card (#74).
The shape
Both tables are maps rather than when expressions, and that is the load-bearing change: a when cannot be enumerated, so no test could ever ask one table what the other one knows. The
new CodecVocabularyTest walks both key sets and both meanings, so a name added to — or removed
from — one side alone fails the build. describeVideo/describeAudio now share one body, so
the next arm cannot be added to one side only.
The single legitimate asymmetry is listed rather than implied: DECODE_ONLY_NAMES = {"mpeg4"},
decodable input with no VideoCodec to name it. That list is itself checked in both directions,
because otherwise it is an escape hatch for the next divergence.
No fifth table. MediaProbe.kt and Media3Engine.kt are untouched — #84 and #85 own those, and
the join point for them is CodecVocabularyTest iterating CodecNames.VIDEO_ALIASES.keys.
Behaviour change, stated plainly
x264, hev1, x265, vp09 now resolve in the device capability check. Null there means
"unknown to us: assume the platform can handle it and let a failed export trigger the FFmpeg
fallback" — the right policy for a name nobody recognises, the wrong one for a name recognised
one file over. A device without the matching decoder now routes those inputs to FFmpeg up
front instead of spending a doomed hardware attempt. No input loses hardware it could have used:
each alias resolves to the MIME its canonical spelling already resolved to, so a device that has
the decoder still answers true. ConversionRouterTest passes, and that is not evidence either
way — every canDecode in it is a hand-written stub that never reaches this table.
Mutations, each on the full 386-test suite
#87's acceptance bite — add "avc3" to CodecNames.VIDEO_ALIASES only. 386 tests, 2 failed; CodecNamesTest stayed green at 8 tests, 0 failures, which is exactly the ticket's point that
per-table arm tests would encode the disagreement:
CodecVocabularyTest > no video codec name resolves for display without also resolving for the device check FAILED
java.lang.AssertionError: resolve in CodecNames but return null from mimeForCodecName, so the device check runs blind expected:<[]> but was:<[avc3]>
CodecVocabularyTest > the two tables agree on what each name means, not merely that they know it FAILED
java.lang.AssertionError: avc3 is H264 in CodecNames expected:<video/avc> but was:<null>
#74 — delete the UNPARSEABLE arm again. 386 tests, 3 failed. The ? in the messages below
is the report renderer, not the value: the JUnit XML holds zero NUL bytes and substitutes ?,
because XML 1.0 cannot encode U+0000. The NUL is genuinely there at runtime, and the third
failure is the proof — it is a contains('\u0000') assertion on the returned string:
CodecNamesTest > audio descriptions degrade exactly the way video ones do FAILED
org.junit.ComparisonFailure: expected:<[Unrecognised]> but was:<[?unparseable]>
CodecNamesTest > descriptions stay readable for unknown and missing codecs FAILED
org.junit.ComparisonFailure: expected:<[Unrecognised]> but was:<[?unparseable]>
CodecNamesTest > no description can put a control character on the card FAILED
java.lang.AssertionError: ?unparseable leaks the sentinel
The escape hatch — add "x265" to DECODE_ONLY_NAMES. 386 tests, 1 failed:
CodecVocabularyTest > the decode-only names are genuinely decode-only FAILED
java.lang.AssertionError: x265 is listed as decode-only, but CodecNames does resolve it — that is a divergence being waved through rather than a documented exception expected null, but was:<H265>
The pre-fix state — delete "vp09" from the MIME map only. 3 failed, including the two
membership and meaning checks and the by-name pin of the five names #87 measured.
Two corrections to #74, from the file rather than the ticket
It quotes audioFromName as opening with null, InputProbe.UNPARSEABLE -> null. It did not —
it opened with null -> null and the sentinel reached else. Naming the sentinel in the
shared lookup changes no answer; it is documentation, not the fix.
It says describeVideo's arm has no test of its own. It did: descriptions stay readable for unknown and missing codecs asserts it, which is why deleting the shared arm reddens three
tests rather than one.
Not covered
Audio has no cross-check, and that is a gap rather than a decision: the device capability check
is video-only, so this module holds no second audio table to compare AUDIO_ALIASES against. Media3Engine.audioMimeTypeFor is the other half and belongs to #85.
Closes #87. Closes #74.
One defect family, two tickets, two files. The FFprobe codec vocabulary is written out in at
least four places and none of them had a test; two of the four had already stopped agreeing.
## What was wrong
`CodecNames.videoFromName` resolved `x264`, `hev1`, `x265` and `vp09`;
`AndroidDeviceCodecs.mimeForCodecName` returned null for all four. So the app identified the
codec for the source card and for routing, then ran the device capability check blind on the
same string and took the "unknown, assume the platform copes" fallthrough — a wasted hardware
attempt on exactly the inputs it had already recognised. `mpeg4` ran the other way.
One level down, `describeVideo` answered `"Unrecognised"` for `InputProbe.UNPARSEABLE` and
`describeAudio` had no such arm. The sentinel opens with a NUL, so an unparseable audio codec
would have put U+0000 into a `Text` on the source-info card (#74).
## The shape
Both tables are **maps rather than `when` expressions**, and that is the load-bearing change: a
`when` cannot be enumerated, so no test could ever ask one table what the other one knows. The
new `CodecVocabularyTest` walks both key sets and both meanings, so a name added to — or removed
from — one side alone fails the build. `describeVideo`/`describeAudio` now share one body, so
the next arm cannot be added to one side only.
The single legitimate asymmetry is listed rather than implied: `DECODE_ONLY_NAMES = {"mpeg4"}`,
decodable input with no `VideoCodec` to name it. That list is itself checked in both directions,
because otherwise it is an escape hatch for the next divergence.
No fifth table. `MediaProbe.kt` and `Media3Engine.kt` are untouched — #84 and #85 own those, and
the join point for them is `CodecVocabularyTest` iterating `CodecNames.VIDEO_ALIASES.keys`.
## Behaviour change, stated plainly
`x264`, `hev1`, `x265`, `vp09` now resolve in the device capability check. Null there means
"unknown to us: assume the platform can handle it and let a failed export trigger the FFmpeg
fallback" — the right policy for a name nobody recognises, the wrong one for a name recognised
one file over. A device **without** the matching decoder now routes those inputs to FFmpeg up
front instead of spending a doomed hardware attempt. No input loses hardware it could have used:
each alias resolves to the MIME its canonical spelling already resolved to, so a device that has
the decoder still answers true. `ConversionRouterTest` passes, and that is not evidence either
way — every `canDecode` in it is a hand-written stub that never reaches this table.
## Mutations, each on the full 386-test suite
**#87's acceptance bite — add `"avc3"` to `CodecNames.VIDEO_ALIASES` only.** 386 tests, 2 failed;
`CodecNamesTest` stayed green at 8 tests, 0 failures, which is exactly the ticket's point that
per-table arm tests would encode the disagreement:
```
CodecVocabularyTest > no video codec name resolves for display without also resolving for the device check FAILED
java.lang.AssertionError: resolve in CodecNames but return null from mimeForCodecName, so the device check runs blind expected:<[]> but was:<[avc3]>
CodecVocabularyTest > the two tables agree on what each name means, not merely that they know it FAILED
java.lang.AssertionError: avc3 is H264 in CodecNames expected:<video/avc> but was:<null>
```
**#74 — delete the `UNPARSEABLE` arm again.** 386 tests, 3 failed. The `?` in the messages below
is the report renderer, not the value: the JUnit XML holds zero NUL bytes and substitutes `?`,
because XML 1.0 cannot encode U+0000. The NUL is genuinely there at runtime, and the third
failure is the proof — it is a `contains('\u0000')` assertion on the returned string:
```
CodecNamesTest > audio descriptions degrade exactly the way video ones do FAILED
org.junit.ComparisonFailure: expected:<[Unrecognised]> but was:<[?unparseable]>
CodecNamesTest > descriptions stay readable for unknown and missing codecs FAILED
org.junit.ComparisonFailure: expected:<[Unrecognised]> but was:<[?unparseable]>
CodecNamesTest > no description can put a control character on the card FAILED
java.lang.AssertionError: ?unparseable leaks the sentinel
```
**The escape hatch — add `"x265"` to `DECODE_ONLY_NAMES`.** 386 tests, 1 failed:
```
CodecVocabularyTest > the decode-only names are genuinely decode-only FAILED
java.lang.AssertionError: x265 is listed as decode-only, but CodecNames does resolve it — that is a divergence being waved through rather than a documented exception expected null, but was:<H265>
```
**The pre-fix state — delete `"vp09"` from the MIME map only.** 3 failed, including the two
membership and meaning checks and the by-name pin of the five names #87 measured.
## Two corrections to #74, from the file rather than the ticket
- It quotes `audioFromName` as opening with `null, InputProbe.UNPARSEABLE -> null`. It did not —
it opened with `null -> null` and the sentinel reached `else`. Naming the sentinel in the
shared lookup changes no answer; it is documentation, not the fix.
- It says `describeVideo`'s arm has no test of its own. It did: `descriptions stay readable for
unknown and missing codecs` asserts it, which is why deleting the shared arm reddens three
tests rather than one.
## Not covered
Audio has no cross-check, and that is a gap rather than a decision: the device capability check
is video-only, so this module holds no second audio table to compare `AUDIO_ALIASES` against.
`Media3Engine.audioMimeTypeFor` is the other half and belongs to #85.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #87. Closes #74.
One defect family, two tickets, two files. The FFprobe codec vocabulary is written out in at
least four places and none of them had a test; two of the four had already stopped agreeing.
What was wrong
CodecNames.videoFromNameresolvedx264,hev1,x265andvp09;AndroidDeviceCodecs.mimeForCodecNamereturned null for all four. So the app identified thecodec for the source card and for routing, then ran the device capability check blind on the
same string and took the "unknown, assume the platform copes" fallthrough — a wasted hardware
attempt on exactly the inputs it had already recognised.
mpeg4ran the other way.One level down,
describeVideoanswered"Unrecognised"forInputProbe.UNPARSEABLEanddescribeAudiohad no such arm. The sentinel opens with a NUL, so an unparseable audio codecwould have put U+0000 into a
Texton the source-info card (#74).The shape
Both tables are maps rather than
whenexpressions, and that is the load-bearing change: awhencannot be enumerated, so no test could ever ask one table what the other one knows. Thenew
CodecVocabularyTestwalks both key sets and both meanings, so a name added to — or removedfrom — one side alone fails the build.
describeVideo/describeAudionow share one body, sothe next arm cannot be added to one side only.
The single legitimate asymmetry is listed rather than implied:
DECODE_ONLY_NAMES = {"mpeg4"},decodable input with no
VideoCodecto name it. That list is itself checked in both directions,because otherwise it is an escape hatch for the next divergence.
No fifth table.
MediaProbe.ktandMedia3Engine.ktare untouched — #84 and #85 own those, andthe join point for them is
CodecVocabularyTestiteratingCodecNames.VIDEO_ALIASES.keys.Behaviour change, stated plainly
x264,hev1,x265,vp09now resolve in the device capability check. Null there means"unknown to us: assume the platform can handle it and let a failed export trigger the FFmpeg
fallback" — the right policy for a name nobody recognises, the wrong one for a name recognised
one file over. A device without the matching decoder now routes those inputs to FFmpeg up
front instead of spending a doomed hardware attempt. No input loses hardware it could have used:
each alias resolves to the MIME its canonical spelling already resolved to, so a device that has
the decoder still answers true.
ConversionRouterTestpasses, and that is not evidence eitherway — every
canDecodein it is a hand-written stub that never reaches this table.Mutations, each on the full 386-test suite
#87's acceptance bite — add
"avc3"toCodecNames.VIDEO_ALIASESonly. 386 tests, 2 failed;CodecNamesTeststayed green at 8 tests, 0 failures, which is exactly the ticket's point thatper-table arm tests would encode the disagreement:
#74 — delete the
UNPARSEABLEarm again. 386 tests, 3 failed. The?in the messages belowis the report renderer, not the value: the JUnit XML holds zero NUL bytes and substitutes
?,because XML 1.0 cannot encode U+0000. The NUL is genuinely there at runtime, and the third
failure is the proof — it is a
contains('\u0000')assertion on the returned string:The escape hatch — add
"x265"toDECODE_ONLY_NAMES. 386 tests, 1 failed:The pre-fix state — delete
"vp09"from the MIME map only. 3 failed, including the twomembership and meaning checks and the by-name pin of the five names #87 measured.
Two corrections to #74, from the file rather than the ticket
audioFromNameas opening withnull, InputProbe.UNPARSEABLE -> null. It did not —it opened with
null -> nulland the sentinel reachedelse. Naming the sentinel in theshared lookup changes no answer; it is documentation, not the fix.
describeVideo's arm has no test of its own. It did:descriptions stay readable for unknown and missing codecsasserts it, which is why deleting the shared arm reddens threetests rather than one.
Not covered
Audio has no cross-check, and that is a gap rather than a decision: the device capability check
is video-only, so this module holds no second audio table to compare
AUDIO_ALIASESagainst.Media3Engine.audioMimeTypeForis the other half and belongs to #85.🤖 Generated with Claude Code