You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Play attached file, it will show a duration of more than 1 hours and will keep playing without sound for that duration.
The file plays well in other players and the actual duration is properly reported by taglib or ffmpeg or Android.
Expected result
File have correct duration and does not play without sound for 1 hour.
Note that I had a few report of this over the years from different users.
Actual result
The file duration is completely off and it keeps playing with no sound for that duration.
By chance I happened to test this file in the 1.2.1 version of the demo app, and there I see a duration of 7:17 and playback stops at 7:18 (this was after seeking, so the mismatch is likely due to MP3 + Xing header resulting in inherently innacurate seeking).
At 1.3.0 I see the issue you describe, the duration is shown as 1:12:51.
Reverting that on top of 1.3.0 restores the duration to 7:17.
The issue is that this file is variable bit rate (VBR) (as reported by mediainfo), but it has an Info frame, which generally indicates constant bitrate (CBR) (Xing frames are usually used for VBR files) - and the change I linked above started assuming that Info frames imply CBR, in order to improve seeking accuracy: #878
This results in calculating the duration of the stream by assuming all frames are the same size as the first frame after the Info frame (since that's true in a CBR stream):
In this case, the file seems to have a single 104 byte, 32kbps frame immediately after the Info frame (visible with ffprobe -show_packets) - so ExoPlayer derives the duration assuming the rest of the stream is 32kbps. Every subsequent frame is then either 1044 or 1045 bytes, so it's effectively (if not technically) CBR.
Implementing the TODO related to deriving the duration from the Info header resolves the issue: We then initialize the ConstantBitrateSeeker with a bitrate and frame size derived from the Xing/Info frame frameCount, dataSize (or the InputStream.length if known), samplesPerFrame, and sampleRate values. This also ensures that when seeking the playback position ends at 7:17, indicating the seeking is more accurate than is possible from the Info header.
I will work on tidying up this change, and sending it for internal review.
In this case, the file seems to have a single 104 byte, 32kbps frame immediately after the Info frame (visible with ffprobe -show_packets) - so ExoPlayer derives the duration assuming the rest of the stream is 32kbps. Every subsequent frame is then either 1044 or 1045 bytes, so it's effectively (if not technically) CBR.
Correction/clarification - all the 1044/1045 byte frames are 320kbps, so CBR - the one byte difference comes from the padding value in the frame header.
ExoPlayer complains about this file for another, related, reason: the Info frame's 'data size' field evaluates to 17463482 (0x010A78BA), but the file is 17510998 bytes long.
Even if we consider that the Xing.dataSize value is intended to measure from the start of the Xing header (0x5DB9 = 23993), it still doesn't match up:
ExoPlayer warns about this mismatch at 1.2.1 where the Xing frame is used for duration and seeking (after this warning, it uses the Xing frame value instead of the input stream size):
XING data size mismatch: 17510998, 17487439
Digging into the file with ffprobe and a hex viewer it looks like the MP3 'audio' data does end at 17487439 as indicated by the Info header:
$ ffprobe -show_packets 003.mp3
# only showing last packet and eliding irrelevant values
...
[PACKET]
...
size=1045
pos=17486394
...
[/PACKET]
The data after this point seems to be some album artwork, as you can see from an ASCII interpretation of a snippet of the hex dump at this point:
.cH.T..APETAGEX.....[...............
....X......Cover Art (Front).Cover A
rt (Front).jpg.......JFIF...........
The "use Info frame from CBR" logic added in 4061d47 uses the input stream length in preference to the length from the Info frame (L579 in Mp3Extractor code snippet above) - which this file suggests is a poor decision. I will change that too.
For the record those are APEv1 tags.
From Wikipedia:
APEv1
The APEv1 tag was designed for the [Monkey's Audio](https://en.wikipedia.org/wiki/Monkey%27s_Audio) format.[[2]](https://en.wikipedia.org/wiki/APE_tag#cite_note-2) In [MP3](https://en.wikipedia.org/wiki/MP3) files, the APE tag is stored at the very end of the file, with no inline declaration in the body of the file. The software handles the writing and access to the tag and does not interfere with the contents of the MP3.
This should be resolved by the commits linked above.
I considered some other changes and ultimately rejected them:
Assume the bitrate of the Info frame is the same as the rest of the file (instead of the frame after it).
This is true for the file you provided, but not for our existing test assets, so (sample size of 2) seems not to be a reliable heuristic.
Only assume a Info frame represents a CBR file if vbrInfo in the frame is 1
This also isn't true for our existing test file, so doesn't seem reliable either.
Explicitly check for a PCUT frame and skip it
This is only needed for an Info frame that doesn't contain frame count/data size info, which I think is probably very unusual (though technically allowed)
Activity
icbaker commentedon May 16, 2024
By chance I happened to test this file in the 1.2.1 version of the demo app, and there I see a duration of 7:17 and playback stops at 7:18 (this was after seeking, so the mismatch is likely due to MP3 + Xing header resulting in inherently innacurate seeking).
At 1.3.0 I see the issue you describe, the duration is shown as 1:12:51.
The culprit is this change: 4061d476a14314867da2f74ba7049c85568b56eb\
Reverting that on top of 1.3.0 restores the duration to 7:17.
The issue is that this file is variable bit rate (VBR) (as reported by
mediainfo), but it has anInfoframe, which generally indicates constant bitrate (CBR) (Xingframes are usually used for VBR files) - and the change I linked above started assuming thatInfoframes imply CBR, in order to improve seeking accuracy: #878This results in calculating the duration of the stream by assuming all frames are the same size as the first frame after the
Infoframe (since that's true in a CBR stream):media/libraries/extractor/src/main/java/androidx/media3/extractor/mp3/Mp3Extractor.java
Lines 540 to 543 in d13a0f4
media/libraries/extractor/src/main/java/androidx/media3/extractor/mp3/Mp3Extractor.java
Lines 566 to 583 in d13a0f4
In this case, the file seems to have a single 104 byte, 32kbps frame immediately after the
Infoframe (visible withffprobe -show_packets) - so ExoPlayer derives the duration assuming the rest of the stream is 32kbps. Every subsequent frame is then either 1044 or 1045 bytes, so it's effectively (if not technically) CBR.Implementing the TODO related to deriving the duration from the
Infoheader resolves the issue: We then initialize theConstantBitrateSeekerwith a bitrate and frame size derived from theXing/InfoframeframeCount,dataSize(or theInputStream.lengthif known),samplesPerFrame, andsampleRatevalues. This also ensures that when seeking the playback position ends at 7:17, indicating the seeking is more accurate than is possible from theInfoheader.I will work on tidying up this change, and sending it for internal review.
icbaker commentedon May 16, 2024
Correction/clarification - all the 1044/1045 byte frames are 320kbps, so CBR - the one byte difference comes from the padding value in the frame header.
Tolriq commentedon May 16, 2024
Thanks for the details and the probable fix.
icbaker commentedon May 17, 2024
ExoPlayer complains about this file for another, related, reason: the
Infoframe's 'data size' field evaluates to17463482(0x010A78BA), but the file is17510998bytes long.Even if we consider that the
Xing.dataSizevalue is intended to measure from the start of theXingheader (0x5DB9=23993), it still doesn't match up:ExoPlayer warns about this mismatch at 1.2.1 where the Xing frame is used for duration and seeking (after this warning, it uses the
Xingframe value instead of the input stream size):Digging into the file with
ffprobeand a hex viewer it looks like the MP3 'audio' data does end at17487439as indicated by theInfoheader:$ ffprobe -show_packets 003.mp3 # only showing last packet and eliding irrelevant values ... [PACKET] ... size=1045 pos=17486394 ... [/PACKET]The data after this point seems to be some album artwork, as you can see from an ASCII interpretation of a snippet of the hex dump at this point:
The "use
Infoframe from CBR" logic added in 4061d47 uses the input stream length in preference to the length from theInfoframe (L579 inMp3Extractorcode snippet above) - which this file suggests is a poor decision. I will change that too.Tolriq commentedon May 17, 2024
For the record those are APEv1 tags.
From Wikipedia:
In case this impact other places too.
icbaker commentedon May 17, 2024
Ah thanks, that's useful context.
I also did a bit of digging into this one-off 104 byte (32kbps) frame at the start of the stream, and noticed it has a
PCUTidentifier in a hex viewer. It seems this is maybe related to https://code.google.com/archive/p/pcutmp3/ - and there's a post here from 2006 pointing out that it can result in CBR files being considered VBR: https://hydrogenaud.io/index.php/topic,32379.msg357668.html#msg357668Tolriq commentedon May 17, 2024
Seems I found you a nice file for the test collection :)
Thanks for taking the time on something that I suppose is not really that common.
MP3: Add test CBR sample with 'too small' `PCUT` frame
MP3: Derive duration and bitrate from frame count in `Info` header
icbaker commentedon May 22, 2024
This should be resolved by the commits linked above.
I considered some other changes and ultimately rejected them:
Infoframe is the same as the rest of the file (instead of the frame after it).Infoframe represents a CBR file ifvbrInfoin the frame is1PCUTframe and skip itInfoframe that doesn't contain frame count/data size info, which I think is probably very unusual (though technically allowed)