From d920cf87a68e3b029667f241bda149cef2a2a9db Mon Sep 17 00:00:00 2001 From: rohks Date: Wed, 19 Mar 2025 11:15:19 -0700 Subject: [PATCH] Fix parsing of language code in `mdhd` box Language codes in the `mdhd` box are stored as three 5-bit values, each representing a letter ('a' to 'z') using an offset of `0x60`. If the decoded characters are not in this range, the language should be treated as undefined. PiperOrigin-RevId: 738470544 (cherry picked from commit 2a4cbc3be47b54a59aef384b288ea802e34fb2f0) --- .../androidx/media3/extractor/Ac3Util.java | 14 +++++--- .../androidx/media3/extractor/Ac4Util.java | 7 ++-- .../media3/extractor/mp4/BoxParser.java | 36 ++++++++++++------- .../mp4/sample_opus_fragmented.mp4.0.dump | 1 - ...ding_within_gop_sample_dependencies.0.dump | 1 - ...op_sample_dependencies.unknown_length.dump | 1 - ...le_opus_fragmented.mp4.unknown_length.dump | 1 - .../bbb_1ch_16kHz_q10_vorbis.ogg.dump | 1 - .../assets/muxerdumps/bbb_2ch_44kHz.wav.dump | 1 - .../muxerdumps/bbb_6ch_8kHz_opus.ogg.dump | 1 - .../h265_with_metadata_track.mp4.dump | 1 - .../assets/muxerdumps/hdr10-720p.mp4.dump | 1 - .../muxerdumps/hdr10-720p.mp4_fragmented.dump | 1 - .../muxerdumps/partial_hdr10-720p.mp4.dump | 1 - ...amable_output_disabled_hdr10-720p.mp4.dump | 1 - ...mple_batching_disabled_hdr10-720p.mp4.dump | 1 - .../transmuxed_with_inappmuxer.dump | 1 - .../transmuxed_with_inappmuxer.dump | 1 - .../transmuxed_with_inappmuxer.dump | 1 - 19 files changed, 39 insertions(+), 34 deletions(-) diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac3Util.java b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac3Util.java index 677bae0cc3..406cec3f02 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac3Util.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac3Util.java @@ -169,12 +169,15 @@ public final class Ac3Util { * * @param data The AC3SpecificBox to parse. * @param trackId The track identifier to set on the format. - * @param language The language to set on the format. + * @param language The language to set on the format, or {@code null} if unset. * @param drmInitData {@link DrmInitData} to be included in the format. * @return The AC-3 format parsed from data in the header. */ public static Format parseAc3AnnexFFormat( - ParsableByteArray data, String trackId, String language, @Nullable DrmInitData drmInitData) { + ParsableByteArray data, + String trackId, + @Nullable String language, + @Nullable DrmInitData drmInitData) { ParsableBitArray dataBitArray = new ParsableBitArray(); dataBitArray.reset(data); @@ -208,12 +211,15 @@ public final class Ac3Util { * * @param data The EC3SpecificBox to parse. * @param trackId The track identifier to set on the format. - * @param language The language to set on the format. + * @param language The language to set on the format, or {@code null} if unset. * @param drmInitData {@link DrmInitData} to be included in the format. * @return The E-AC-3 format parsed from data in the header. */ public static Format parseEAc3AnnexFFormat( - ParsableByteArray data, String trackId, String language, @Nullable DrmInitData drmInitData) { + ParsableByteArray data, + String trackId, + @Nullable String language, + @Nullable DrmInitData drmInitData) { ParsableBitArray dataBitArray = new ParsableBitArray(); dataBitArray.reset(data); diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java index 6b51946590..e3eb174c33 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java @@ -163,14 +163,17 @@ public final class Ac4Util { * * @param data The AC4SpecificBox to parse. * @param trackId The track identifier to set on the format. - * @param language The language to set on the format. + * @param language The language to set on the format, or {@code null} if unset. * @param drmInitData {@link DrmInitData} to be included in the format. * @return The AC-4 format parsed from data in the header. * @throws ParserException If an unsupported container feature is encountered while parsing AC-4 * Annex E. */ public static Format parseAc4AnnexEFormat( - ParsableByteArray data, String trackId, String language, @Nullable DrmInitData drmInitData) + ParsableByteArray data, + String trackId, + @Nullable String language, + @Nullable DrmInitData drmInitData) throws ParserException { ParsableBitArray dataBitArray = new ParsableBitArray(); dataBitArray.reset(data); diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java index 3bc998907a..6b4198b391 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java @@ -997,22 +997,34 @@ public final class BoxParser { mediaDurationUs = Util.scaleLargeTimestamp(mediaDuration, C.MICROS_PER_SECOND, timescale); } } - int languageCode = mdhd.readUnsignedShort(); - String language = - "" - + (char) (((languageCode >> 10) & 0x1F) + 0x60) - + (char) (((languageCode >> 5) & 0x1F) + 0x60) - + (char) ((languageCode & 0x1F) + 0x60); + + String language = getLanguageFromCode(/* languageCode= */ mdhd.readUnsignedShort()); return new MdhdData(timescale, mediaDurationUs, language); } + @Nullable + private static String getLanguageFromCode(int languageCode) { + char[] chars = { + (char) (((languageCode >> 10) & 0x1F) + 0x60), + (char) (((languageCode >> 5) & 0x1F) + 0x60), + (char) ((languageCode & 0x1F) + 0x60) + }; + + for (char c : chars) { + if (c < 'a' || c > 'z') { + return null; + } + } + return new String(chars); + } + /** * Parses a stsd atom (defined in ISO/IEC 14496-12). * * @param stsd The stsd atom to decode. * @param trackId The track's identifier in its container. * @param rotationDegrees The rotation of the track in degrees. - * @param language The language of the track. + * @param language The language of the track, or {@code null} if unset. * @param drmInitData {@link DrmInitData} to be included in the format, or {@code null}. * @param isQuickTime True for QuickTime media. False otherwise. * @return An object containing the parsed data. @@ -1021,7 +1033,7 @@ public final class BoxParser { ParsableByteArray stsd, int trackId, int rotationDegrees, - String language, + @Nullable String language, @Nullable DrmInitData drmInitData, boolean isQuickTime) throws ParserException { @@ -1125,7 +1137,7 @@ public final class BoxParser { int position, int atomSize, int trackId, - String language, + @Nullable String language, StsdData out) { parent.setPosition(position + Mp4Box.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); @@ -1820,7 +1832,7 @@ public final class BoxParser { int position, int size, int trackId, - String language, + @Nullable String language, boolean isQuickTime, @Nullable DrmInitData drmInitData, StsdData out, @@ -2593,9 +2605,9 @@ public final class BoxParser { private static final class MdhdData { private final long timescale; private final long mediaDurationUs; - private final String language; + @Nullable private final String language; - public MdhdData(long timescale, long mediaDurationUs, String language) { + public MdhdData(long timescale, long mediaDurationUs, @Nullable String language) { this.timescale = timescale; this.mediaDurationUs = mediaDurationUs; this.language = language; diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.0.dump index bb71b35529..c26cdc1f2a 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.0.dump @@ -12,7 +12,6 @@ track 0: sampleMimeType = audio/opus channelCount = 2 sampleRate = 16000 - language =  initializationData: data = length 19, hash 4034F23B data = length 8, hash 94446F01 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.reading_within_gop_sample_dependencies.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.reading_within_gop_sample_dependencies.0.dump index bb71b35529..c26cdc1f2a 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.reading_within_gop_sample_dependencies.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.reading_within_gop_sample_dependencies.0.dump @@ -12,7 +12,6 @@ track 0: sampleMimeType = audio/opus channelCount = 2 sampleRate = 16000 - language =  initializationData: data = length 19, hash 4034F23B data = length 8, hash 94446F01 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.reading_within_gop_sample_dependencies.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.reading_within_gop_sample_dependencies.unknown_length.dump index bb71b35529..c26cdc1f2a 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.reading_within_gop_sample_dependencies.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.reading_within_gop_sample_dependencies.unknown_length.dump @@ -12,7 +12,6 @@ track 0: sampleMimeType = audio/opus channelCount = 2 sampleRate = 16000 - language =  initializationData: data = length 19, hash 4034F23B data = length 8, hash 94446F01 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.unknown_length.dump index bb71b35529..c26cdc1f2a 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus_fragmented.mp4.unknown_length.dump @@ -12,7 +12,6 @@ track 0: sampleMimeType = audio/opus channelCount = 2 sampleRate = 16000 - language =  initializationData: data = length 19, hash 4034F23B data = length 8, hash 94446F01 diff --git a/libraries/test_data/src/test/assets/muxerdumps/bbb_1ch_16kHz_q10_vorbis.ogg.dump b/libraries/test_data/src/test/assets/muxerdumps/bbb_1ch_16kHz_q10_vorbis.ogg.dump index 05d30352ae..1c46c1f415 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/bbb_1ch_16kHz_q10_vorbis.ogg.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/bbb_1ch_16kHz_q10_vorbis.ogg.dump @@ -18,7 +18,6 @@ track 0: maxInputSize = 295 channelCount = 1 sampleRate = 16000 - language = ``` metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] initializationData: data = length 30, hash C22462B1 diff --git a/libraries/test_data/src/test/assets/muxerdumps/bbb_2ch_44kHz.wav.dump b/libraries/test_data/src/test/assets/muxerdumps/bbb_2ch_44kHz.wav.dump index 3b71cf704c..7c44e32ced 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/bbb_2ch_44kHz.wav.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/bbb_2ch_44kHz.wav.dump @@ -18,7 +18,6 @@ track 0: channelCount = 2 sampleRate = 44100 pcmEncoding = 2 - language = ``` metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] sample 0: time = 0 diff --git a/libraries/test_data/src/test/assets/muxerdumps/bbb_6ch_8kHz_opus.ogg.dump b/libraries/test_data/src/test/assets/muxerdumps/bbb_6ch_8kHz_opus.ogg.dump index f0c8456245..ac490cbee0 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/bbb_6ch_8kHz_opus.ogg.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/bbb_6ch_8kHz_opus.ogg.dump @@ -17,7 +17,6 @@ track 0: maxInputSize = 1185 channelCount = 6 sampleRate = 48000 - language = ``` metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] initializationData: data = length 27, hash 9EE6F879 diff --git a/libraries/test_data/src/test/assets/muxerdumps/h265_with_metadata_track.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/h265_with_metadata_track.mp4.dump index cd13d47e49..d9213a63f3 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/h265_with_metadata_track.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/h265_with_metadata_track.mp4.dump @@ -42,7 +42,6 @@ track 1: maxInputSize = 627 channelCount = 2 sampleRate = 48000 - language = ``` metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] initializationData: data = length 2, hash 560 diff --git a/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4.dump index c5ea5ef204..32c2eb7596 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4.dump @@ -552,7 +552,6 @@ track 1: maxInputSize = 877 channelCount = 2 sampleRate = 48000 - language = ``` metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] initializationData: data = length 2, hash 560 diff --git a/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4_fragmented.dump b/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4_fragmented.dump index 0ebdd2682c..ee77c50859 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4_fragmented.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4_fragmented.dump @@ -543,7 +543,6 @@ track 1: codecs = mp4a.40.2 channelCount = 2 sampleRate = 48000 - language = ``` initializationData: data = length 2, hash 560 sample 0: diff --git a/libraries/test_data/src/test/assets/muxerdumps/partial_hdr10-720p.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/partial_hdr10-720p.mp4.dump index f7e8a260d3..141c44dd68 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/partial_hdr10-720p.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/partial_hdr10-720p.mp4.dump @@ -416,7 +416,6 @@ track 1: maxInputSize = 877 channelCount = 2 sampleRate = 48000 - language = ``` metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] initializationData: data = length 2, hash 560 diff --git a/libraries/test_data/src/test/assets/muxerdumps/sample_batching_and_attempt_streamable_output_disabled_hdr10-720p.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/sample_batching_and_attempt_streamable_output_disabled_hdr10-720p.mp4.dump index 5b38fcaf98..b60163ac36 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/sample_batching_and_attempt_streamable_output_disabled_hdr10-720p.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/sample_batching_and_attempt_streamable_output_disabled_hdr10-720p.mp4.dump @@ -552,7 +552,6 @@ track 1: maxInputSize = 877 channelCount = 2 sampleRate = 48000 - language = ``` metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] initializationData: data = length 2, hash 560 diff --git a/libraries/test_data/src/test/assets/muxerdumps/sample_batching_disabled_hdr10-720p.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/sample_batching_disabled_hdr10-720p.mp4.dump index c5ea5ef204..32c2eb7596 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/sample_batching_disabled_hdr10-720p.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/sample_batching_disabled_hdr10-720p.mp4.dump @@ -552,7 +552,6 @@ track 1: maxInputSize = 877 channelCount = 2 sampleRate = 48000 - language = ``` metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] initializationData: data = length 2, hash 560 diff --git a/libraries/test_data/src/test/assets/transformerdumps/mp4/bbb_1ch_16kHz_q10_vorbis.ogg/transmuxed_with_inappmuxer.dump b/libraries/test_data/src/test/assets/transformerdumps/mp4/bbb_1ch_16kHz_q10_vorbis.ogg/transmuxed_with_inappmuxer.dump index 3c4f0a8400..ea2626d4be 100644 --- a/libraries/test_data/src/test/assets/transformerdumps/mp4/bbb_1ch_16kHz_q10_vorbis.ogg/transmuxed_with_inappmuxer.dump +++ b/libraries/test_data/src/test/assets/transformerdumps/mp4/bbb_1ch_16kHz_q10_vorbis.ogg/transmuxed_with_inappmuxer.dump @@ -18,7 +18,6 @@ track 0: maxInputSize = 295 channelCount = 1 sampleRate = 16000 - language = ``` metadata = entries=[Mp4Timestamp: creation time=3000000000, modification time=4000000000, timescale=10000] initializationData: data = length 30, hash C22462B1 diff --git a/libraries/test_data/src/test/assets/transformerdumps/mp4/bbb_6ch_8kHz_opus.ogg/transmuxed_with_inappmuxer.dump b/libraries/test_data/src/test/assets/transformerdumps/mp4/bbb_6ch_8kHz_opus.ogg/transmuxed_with_inappmuxer.dump index 2818dceb29..a82a152e3b 100644 --- a/libraries/test_data/src/test/assets/transformerdumps/mp4/bbb_6ch_8kHz_opus.ogg/transmuxed_with_inappmuxer.dump +++ b/libraries/test_data/src/test/assets/transformerdumps/mp4/bbb_6ch_8kHz_opus.ogg/transmuxed_with_inappmuxer.dump @@ -17,7 +17,6 @@ track 0: maxInputSize = 1185 channelCount = 6 sampleRate = 48000 - language = ``` metadata = entries=[Mp4Timestamp: creation time=3000000000, modification time=4000000000, timescale=10000] initializationData: data = length 27, hash 9EE6F879 diff --git a/libraries/test_data/src/test/assets/transformerdumps/mp4/h265_with_metadata_track.mp4/transmuxed_with_inappmuxer.dump b/libraries/test_data/src/test/assets/transformerdumps/mp4/h265_with_metadata_track.mp4/transmuxed_with_inappmuxer.dump index 0964c3ae1a..5e664e9d96 100644 --- a/libraries/test_data/src/test/assets/transformerdumps/mp4/h265_with_metadata_track.mp4/transmuxed_with_inappmuxer.dump +++ b/libraries/test_data/src/test/assets/transformerdumps/mp4/h265_with_metadata_track.mp4/transmuxed_with_inappmuxer.dump @@ -57,7 +57,6 @@ track 1: maxInputSize = 627 channelCount = 2 sampleRate = 48000 - language = ``` metadata = entries=[xyz: latitude=51.5932, longitude=-0.2431, Mp4Timestamp: creation time=3000000000, modification time=4000000000, timescale=10000] initializationData: data = length 2, hash 560