From 17bf47ed24dede6b864b88e99ffc343ea78d5132 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 21 May 2024 05:28:34 -0700 Subject: [PATCH] MP3: Add test CBR sample with 'too small' `PCUT` frame This shows ExoPlayer currently wrongly reports the duration of this sample, because it assumes every frame is 32kbps (104 bytes) due to the `PCUT` frame immediately after the `Info` frame. A follow-up change will modify `Info` frame handling to resolve this issue. This sample was crafted using a hex editor to insert the additional `PCUT` frame (the pattern of `null` and `x` is taken from the sample file in Issue: androidx/media#1376, the header is modified to set the channel count to 1 to match the rest of the file), and then update the frame count and data size of the `Info` header to match. Issue: androidx/media#1376 PiperOrigin-RevId: 635772837 --- .../extractor/mp3/Mp3ExtractorTest.java | 7 + ...test-cbr-info-header-pcut-frame.mp3.0.dump | 185 ++++++++++++++++++ ...test-cbr-info-header-pcut-frame.mp3.1.dump | 129 ++++++++++++ ...test-cbr-info-header-pcut-frame.mp3.2.dump | 73 +++++++ ...test-cbr-info-header-pcut-frame.mp3.3.dump | 21 ++ ...-header-pcut-frame.mp3.unknown_length.dump | 185 ++++++++++++++++++ .../mp3/test-cbr-info-header-pcut-frame.mp3 | Bin 0 -> 8690 bytes 7 files changed, 600 insertions(+) create mode 100644 libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.0.dump create mode 100644 libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.1.dump create mode 100644 libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.2.dump create mode 100644 libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.3.dump create mode 100644 libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.unknown_length.dump create mode 100644 libraries/test_data/src/test/assets/media/mp3/test-cbr-info-header-pcut-frame.mp3 diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/mp3/Mp3ExtractorTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/mp3/Mp3ExtractorTest.java index cacb094742..8900629912 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/mp3/Mp3ExtractorTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/mp3/Mp3ExtractorTest.java @@ -47,6 +47,13 @@ public final class Mp3ExtractorTest { Mp3Extractor::new, "media/mp3/test-cbr-info-header.mp3", simulationConfig); } + // https://github.com/androidx/media/issues/1376#issuecomment-2117393653 + @Test + public void mp3SampleWithInfoHeaderAndPcutFrame() throws Exception { + ExtractorAsserts.assertBehavior( + Mp3Extractor::new, "media/mp3/test-cbr-info-header-pcut-frame.mp3", simulationConfig); + } + @Test public void mp3SampleWithCbrSeeker() throws Exception { ExtractorAsserts.assertBehavior( diff --git a/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.0.dump new file mode 100644 index 0000000000..67c6563098 --- /dev/null +++ b/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.0.dump @@ -0,0 +1,185 @@ +seekMap: + isSeekable = true + duration = 2115750 + getPosition(0) = [[timeUs=0, position=227]] + getPosition(1) = [[timeUs=0, position=227], [timeUs=26000, position=331]] + getPosition(1057875) = [[timeUs=1040000, position=4387], [timeUs=1066000, position=4491]] + getPosition(2115750) = [[timeUs=2089750, position=8586]] +numberOfTracks = 1 +track 0: + total output bytes = 8463 + sample count = 41 + format 0: + averageBitrate = 32000 + sampleMimeType = audio/mpeg + maxInputSize = 4096 + channelCount = 1 + sampleRate = 44100 + encoderDelay = 576 + encoderPadding = 1404 + metadata = entries=[TSSE: description=null: values=[Lavf58.45.100]] + sample 0: + time = 0 + flags = 1 + data = length 104, hash BF3B0C83 + sample 1: + time = 26122 + flags = 1 + data = length 208, hash 5E56C6DF + sample 2: + time = 52244 + flags = 1 + data = length 209, hash 4FA4483 + sample 3: + time = 78367 + flags = 1 + data = length 209, hash E1201788 + sample 4: + time = 104489 + flags = 1 + data = length 209, hash FDB8052B + sample 5: + time = 130612 + flags = 1 + data = length 209, hash A8FF2C9 + sample 6: + time = 156734 + flags = 1 + data = length 209, hash FD4AE506 + sample 7: + time = 182857 + flags = 1 + data = length 209, hash EA0AC2FE + sample 8: + time = 208979 + flags = 1 + data = length 209, hash 84B9DDE0 + sample 9: + time = 235102 + flags = 1 + data = length 209, hash 796A4F65 + sample 10: + time = 261224 + flags = 1 + data = length 209, hash 4DE92719 + sample 11: + time = 287346 + flags = 1 + data = length 209, hash 458A4711 + sample 12: + time = 313469 + flags = 1 + data = length 209, hash 27954F2B + sample 13: + time = 339591 + flags = 1 + data = length 209, hash 1A5042B0 + sample 14: + time = 365714 + flags = 1 + data = length 209, hash 6CCBBB3B + sample 15: + time = 391836 + flags = 1 + data = length 209, hash 34191E1 + sample 16: + time = 417959 + flags = 1 + data = length 209, hash 57323ED7 + sample 17: + time = 444081 + flags = 1 + data = length 209, hash 75618CF3 + sample 18: + time = 470204 + flags = 1 + data = length 209, hash 784C973B + sample 19: + time = 496326 + flags = 1 + data = length 209, hash 49106390 + sample 20: + time = 522448 + flags = 1 + data = length 209, hash 70F6A563 + sample 21: + time = 548571 + flags = 1 + data = length 209, hash 721882B0 + sample 22: + time = 574693 + flags = 1 + data = length 209, hash 81C62AEE + sample 23: + time = 600816 + flags = 1 + data = length 209, hash 16D22463 + sample 24: + time = 626938 + flags = 1 + data = length 209, hash 47033534 + sample 25: + time = 653061 + flags = 1 + data = length 209, hash CECB37A6 + sample 26: + time = 679183 + flags = 1 + data = length 209, hash 6C9C307B + sample 27: + time = 705306 + flags = 1 + data = length 209, hash 3EB1A364 + sample 28: + time = 731428 + flags = 1 + data = length 209, hash 30962500 + sample 29: + time = 757551 + flags = 1 + data = length 209, hash 2C5CCBB7 + sample 30: + time = 783673 + flags = 1 + data = length 209, hash F9CB9E37 + sample 31: + time = 809795 + flags = 1 + data = length 209, hash F75BC8C0 + sample 32: + time = 835918 + flags = 1 + data = length 209, hash D00ED607 + sample 33: + time = 862040 + flags = 1 + data = length 209, hash B4338395 + sample 34: + time = 888163 + flags = 1 + data = length 209, hash E3E838A0 + sample 35: + time = 914285 + flags = 1 + data = length 209, hash 2B0CF78 + sample 36: + time = 940408 + flags = 1 + data = length 209, hash 31906FA9 + sample 37: + time = 966530 + flags = 1 + data = length 209, hash C92FC08F + sample 38: + time = 992653 + flags = 1 + data = length 209, hash 7C89994 + sample 39: + time = 1018775 + flags = 1 + data = length 209, hash EC37743B + sample 40: + time = 1044897 + flags = 1 + data = length 209, hash C974F6FB +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.1.dump new file mode 100644 index 0000000000..980313e82a --- /dev/null +++ b/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.1.dump @@ -0,0 +1,129 @@ +seekMap: + isSeekable = true + duration = 2115750 + getPosition(0) = [[timeUs=0, position=227]] + getPosition(1) = [[timeUs=0, position=227], [timeUs=26000, position=331]] + getPosition(1057875) = [[timeUs=1040000, position=4387], [timeUs=1066000, position=4491]] + getPosition(2115750) = [[timeUs=2089750, position=8586]] +numberOfTracks = 1 +track 0: + total output bytes = 5643 + sample count = 27 + format 0: + averageBitrate = 32000 + sampleMimeType = audio/mpeg + maxInputSize = 4096 + channelCount = 1 + sampleRate = 44100 + encoderDelay = 576 + encoderPadding = 1404 + metadata = entries=[TSSE: description=null: values=[Lavf58.45.100]] + sample 0: + time = 705000 + flags = 1 + data = length 209, hash 6CCBBB3B + sample 1: + time = 731122 + flags = 1 + data = length 209, hash 34191E1 + sample 2: + time = 757244 + flags = 1 + data = length 209, hash 57323ED7 + sample 3: + time = 783367 + flags = 1 + data = length 209, hash 75618CF3 + sample 4: + time = 809489 + flags = 1 + data = length 209, hash 784C973B + sample 5: + time = 835612 + flags = 1 + data = length 209, hash 49106390 + sample 6: + time = 861734 + flags = 1 + data = length 209, hash 70F6A563 + sample 7: + time = 887857 + flags = 1 + data = length 209, hash 721882B0 + sample 8: + time = 913979 + flags = 1 + data = length 209, hash 81C62AEE + sample 9: + time = 940102 + flags = 1 + data = length 209, hash 16D22463 + sample 10: + time = 966224 + flags = 1 + data = length 209, hash 47033534 + sample 11: + time = 992346 + flags = 1 + data = length 209, hash CECB37A6 + sample 12: + time = 1018469 + flags = 1 + data = length 209, hash 6C9C307B + sample 13: + time = 1044591 + flags = 1 + data = length 209, hash 3EB1A364 + sample 14: + time = 1070714 + flags = 1 + data = length 209, hash 30962500 + sample 15: + time = 1096836 + flags = 1 + data = length 209, hash 2C5CCBB7 + sample 16: + time = 1122959 + flags = 1 + data = length 209, hash F9CB9E37 + sample 17: + time = 1149081 + flags = 1 + data = length 209, hash F75BC8C0 + sample 18: + time = 1175204 + flags = 1 + data = length 209, hash D00ED607 + sample 19: + time = 1201326 + flags = 1 + data = length 209, hash B4338395 + sample 20: + time = 1227448 + flags = 1 + data = length 209, hash E3E838A0 + sample 21: + time = 1253571 + flags = 1 + data = length 209, hash 2B0CF78 + sample 22: + time = 1279693 + flags = 1 + data = length 209, hash 31906FA9 + sample 23: + time = 1305816 + flags = 1 + data = length 209, hash C92FC08F + sample 24: + time = 1331938 + flags = 1 + data = length 209, hash 7C89994 + sample 25: + time = 1358061 + flags = 1 + data = length 209, hash EC37743B + sample 26: + time = 1384183 + flags = 1 + data = length 209, hash C974F6FB +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.2.dump new file mode 100644 index 0000000000..27ea66dd9d --- /dev/null +++ b/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.2.dump @@ -0,0 +1,73 @@ +seekMap: + isSeekable = true + duration = 2115750 + getPosition(0) = [[timeUs=0, position=227]] + getPosition(1) = [[timeUs=0, position=227], [timeUs=26000, position=331]] + getPosition(1057875) = [[timeUs=1040000, position=4387], [timeUs=1066000, position=4491]] + getPosition(2115750) = [[timeUs=2089750, position=8586]] +numberOfTracks = 1 +track 0: + total output bytes = 2717 + sample count = 13 + format 0: + averageBitrate = 32000 + sampleMimeType = audio/mpeg + maxInputSize = 4096 + channelCount = 1 + sampleRate = 44100 + encoderDelay = 576 + encoderPadding = 1404 + metadata = entries=[TSSE: description=null: values=[Lavf58.45.100]] + sample 0: + time = 1436500 + flags = 1 + data = length 209, hash 30962500 + sample 1: + time = 1462622 + flags = 1 + data = length 209, hash 2C5CCBB7 + sample 2: + time = 1488744 + flags = 1 + data = length 209, hash F9CB9E37 + sample 3: + time = 1514867 + flags = 1 + data = length 209, hash F75BC8C0 + sample 4: + time = 1540989 + flags = 1 + data = length 209, hash D00ED607 + sample 5: + time = 1567112 + flags = 1 + data = length 209, hash B4338395 + sample 6: + time = 1593234 + flags = 1 + data = length 209, hash E3E838A0 + sample 7: + time = 1619357 + flags = 1 + data = length 209, hash 2B0CF78 + sample 8: + time = 1645479 + flags = 1 + data = length 209, hash 31906FA9 + sample 9: + time = 1671602 + flags = 1 + data = length 209, hash C92FC08F + sample 10: + time = 1697724 + flags = 1 + data = length 209, hash 7C89994 + sample 11: + time = 1723846 + flags = 1 + data = length 209, hash EC37743B + sample 12: + time = 1749969 + flags = 1 + data = length 209, hash C974F6FB +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.3.dump b/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.3.dump new file mode 100644 index 0000000000..c8ea78c118 --- /dev/null +++ b/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.3.dump @@ -0,0 +1,21 @@ +seekMap: + isSeekable = true + duration = 2115750 + getPosition(0) = [[timeUs=0, position=227]] + getPosition(1) = [[timeUs=0, position=227], [timeUs=26000, position=331]] + getPosition(1057875) = [[timeUs=1040000, position=4387], [timeUs=1066000, position=4491]] + getPosition(2115750) = [[timeUs=2089750, position=8586]] +numberOfTracks = 1 +track 0: + total output bytes = 0 + sample count = 0 + format 0: + averageBitrate = 32000 + sampleMimeType = audio/mpeg + maxInputSize = 4096 + channelCount = 1 + sampleRate = 44100 + encoderDelay = 576 + encoderPadding = 1404 + metadata = entries=[TSSE: description=null: values=[Lavf58.45.100]] +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.unknown_length.dump new file mode 100644 index 0000000000..67c6563098 --- /dev/null +++ b/libraries/test_data/src/test/assets/extractordumps/mp3/test-cbr-info-header-pcut-frame.mp3.unknown_length.dump @@ -0,0 +1,185 @@ +seekMap: + isSeekable = true + duration = 2115750 + getPosition(0) = [[timeUs=0, position=227]] + getPosition(1) = [[timeUs=0, position=227], [timeUs=26000, position=331]] + getPosition(1057875) = [[timeUs=1040000, position=4387], [timeUs=1066000, position=4491]] + getPosition(2115750) = [[timeUs=2089750, position=8586]] +numberOfTracks = 1 +track 0: + total output bytes = 8463 + sample count = 41 + format 0: + averageBitrate = 32000 + sampleMimeType = audio/mpeg + maxInputSize = 4096 + channelCount = 1 + sampleRate = 44100 + encoderDelay = 576 + encoderPadding = 1404 + metadata = entries=[TSSE: description=null: values=[Lavf58.45.100]] + sample 0: + time = 0 + flags = 1 + data = length 104, hash BF3B0C83 + sample 1: + time = 26122 + flags = 1 + data = length 208, hash 5E56C6DF + sample 2: + time = 52244 + flags = 1 + data = length 209, hash 4FA4483 + sample 3: + time = 78367 + flags = 1 + data = length 209, hash E1201788 + sample 4: + time = 104489 + flags = 1 + data = length 209, hash FDB8052B + sample 5: + time = 130612 + flags = 1 + data = length 209, hash A8FF2C9 + sample 6: + time = 156734 + flags = 1 + data = length 209, hash FD4AE506 + sample 7: + time = 182857 + flags = 1 + data = length 209, hash EA0AC2FE + sample 8: + time = 208979 + flags = 1 + data = length 209, hash 84B9DDE0 + sample 9: + time = 235102 + flags = 1 + data = length 209, hash 796A4F65 + sample 10: + time = 261224 + flags = 1 + data = length 209, hash 4DE92719 + sample 11: + time = 287346 + flags = 1 + data = length 209, hash 458A4711 + sample 12: + time = 313469 + flags = 1 + data = length 209, hash 27954F2B + sample 13: + time = 339591 + flags = 1 + data = length 209, hash 1A5042B0 + sample 14: + time = 365714 + flags = 1 + data = length 209, hash 6CCBBB3B + sample 15: + time = 391836 + flags = 1 + data = length 209, hash 34191E1 + sample 16: + time = 417959 + flags = 1 + data = length 209, hash 57323ED7 + sample 17: + time = 444081 + flags = 1 + data = length 209, hash 75618CF3 + sample 18: + time = 470204 + flags = 1 + data = length 209, hash 784C973B + sample 19: + time = 496326 + flags = 1 + data = length 209, hash 49106390 + sample 20: + time = 522448 + flags = 1 + data = length 209, hash 70F6A563 + sample 21: + time = 548571 + flags = 1 + data = length 209, hash 721882B0 + sample 22: + time = 574693 + flags = 1 + data = length 209, hash 81C62AEE + sample 23: + time = 600816 + flags = 1 + data = length 209, hash 16D22463 + sample 24: + time = 626938 + flags = 1 + data = length 209, hash 47033534 + sample 25: + time = 653061 + flags = 1 + data = length 209, hash CECB37A6 + sample 26: + time = 679183 + flags = 1 + data = length 209, hash 6C9C307B + sample 27: + time = 705306 + flags = 1 + data = length 209, hash 3EB1A364 + sample 28: + time = 731428 + flags = 1 + data = length 209, hash 30962500 + sample 29: + time = 757551 + flags = 1 + data = length 209, hash 2C5CCBB7 + sample 30: + time = 783673 + flags = 1 + data = length 209, hash F9CB9E37 + sample 31: + time = 809795 + flags = 1 + data = length 209, hash F75BC8C0 + sample 32: + time = 835918 + flags = 1 + data = length 209, hash D00ED607 + sample 33: + time = 862040 + flags = 1 + data = length 209, hash B4338395 + sample 34: + time = 888163 + flags = 1 + data = length 209, hash E3E838A0 + sample 35: + time = 914285 + flags = 1 + data = length 209, hash 2B0CF78 + sample 36: + time = 940408 + flags = 1 + data = length 209, hash 31906FA9 + sample 37: + time = 966530 + flags = 1 + data = length 209, hash C92FC08F + sample 38: + time = 992653 + flags = 1 + data = length 209, hash 7C89994 + sample 39: + time = 1018775 + flags = 1 + data = length 209, hash EC37743B + sample 40: + time = 1044897 + flags = 1 + data = length 209, hash C974F6FB +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/media/mp3/test-cbr-info-header-pcut-frame.mp3 b/libraries/test_data/src/test/assets/media/mp3/test-cbr-info-header-pcut-frame.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2fee1312e0fee0363c4fb0f85b6852779ad7f2dd GIT binary patch literal 8690 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