Plumb MP3 average bitrate from metadata frames into Format
Issue: androidx/media#1081 #minor-release PiperOrigin-RevId: 619185083
This commit is contained in:
parent
6e0f8e3b0d
commit
d00ca1e343
@ -47,6 +47,8 @@
|
||||
resolution but a very small number of frames
|
||||
([#1051](https://github.com/androidx/media/issues/1051)).
|
||||
* Extractors:
|
||||
* MP3: Populate `Format.averageBitrate` from metadata frames such as
|
||||
`XING` and `VBRI`.
|
||||
* Audio:
|
||||
* Allow renderer recovery by disabling offload if audio track fails to
|
||||
initialize in offload mode.
|
||||
|
@ -24,6 +24,8 @@ import androidx.media3.extractor.MpegAudioUtil;
|
||||
*/
|
||||
/* package */ final class ConstantBitrateSeeker extends ConstantBitrateSeekMap implements Seeker {
|
||||
|
||||
private final int bitrate;
|
||||
|
||||
/**
|
||||
* @param inputLength The length of the stream in bytes, or {@link C#LENGTH_UNSET} if unknown.
|
||||
* @param firstFramePosition The position of the first frame in the stream.
|
||||
@ -45,6 +47,7 @@ import androidx.media3.extractor.MpegAudioUtil;
|
||||
mpegAudioHeader.bitrate,
|
||||
mpegAudioHeader.frameSize,
|
||||
allowSeeksIfLengthUnknown);
|
||||
bitrate = mpegAudioHeader.bitrate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -56,4 +59,9 @@ import androidx.media3.extractor.MpegAudioUtil;
|
||||
public long getDataEndPosition() {
|
||||
return C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageBitrate() {
|
||||
return bitrate;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.LongArray;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.extractor.SeekPoint;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
/** MP3 seeker that builds a time-to-byte mapping as the stream is read. */
|
||||
/* package */ final class IndexSeeker implements Seeker {
|
||||
@ -30,6 +31,7 @@ import androidx.media3.extractor.SeekPoint;
|
||||
private final long dataEndPosition;
|
||||
private final LongArray timesUs;
|
||||
private final LongArray positions;
|
||||
private final int averageBitrate;
|
||||
|
||||
private long durationUs;
|
||||
|
||||
@ -40,6 +42,15 @@ import androidx.media3.extractor.SeekPoint;
|
||||
positions = new LongArray();
|
||||
timesUs.add(0L);
|
||||
positions.add(dataStartPosition);
|
||||
if (durationUs != C.TIME_UNSET) {
|
||||
long bitrate =
|
||||
Util.scaleLargeValue(
|
||||
dataStartPosition - dataEndPosition, 8, durationUs, RoundingMode.HALF_UP);
|
||||
this.averageBitrate =
|
||||
bitrate > 0 && bitrate <= Integer.MAX_VALUE ? (int) bitrate : C.RATE_UNSET_INT;
|
||||
} else {
|
||||
this.averageBitrate = C.RATE_UNSET_INT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -79,6 +90,11 @@ import androidx.media3.extractor.SeekPoint;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageBitrate() {
|
||||
return averageBitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a seek point to the index if it is sufficiently distant from the other points.
|
||||
*
|
||||
|
@ -127,4 +127,9 @@ import androidx.media3.extractor.metadata.id3.MlltFrame;
|
||||
public long getDataEndPosition() {
|
||||
return C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageBitrate() {
|
||||
return C.RATE_UNSET_INT;
|
||||
}
|
||||
}
|
||||
|
@ -280,7 +280,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
if (seeker == null) {
|
||||
seeker = computeSeeker(input);
|
||||
extractorOutput.seekMap(seeker);
|
||||
currentTrackOutput.format(
|
||||
Format.Builder format =
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(synchronizedHeader.mimeType)
|
||||
.setMaxInputSize(MpegAudioUtil.MAX_FRAME_SIZE_BYTES)
|
||||
@ -288,8 +288,11 @@ public final class Mp3Extractor implements Extractor {
|
||||
.setSampleRate(synchronizedHeader.sampleRate)
|
||||
.setEncoderDelay(gaplessInfoHolder.encoderDelay)
|
||||
.setEncoderPadding(gaplessInfoHolder.encoderPadding)
|
||||
.setMetadata((flags & FLAG_DISABLE_ID3_METADATA) != 0 ? null : metadata)
|
||||
.build());
|
||||
.setMetadata((flags & FLAG_DISABLE_ID3_METADATA) != 0 ? null : metadata);
|
||||
if (seeker.getAverageBitrate() != C.RATE_UNSET_INT) {
|
||||
format.setAverageBitrate(seeker.getAverageBitrate());
|
||||
}
|
||||
currentTrackOutput.format(format.build());
|
||||
firstSamplePosition = input.getPosition();
|
||||
} else if (firstSamplePosition != 0) {
|
||||
long inputPosition = input.getPosition();
|
||||
|
@ -39,6 +39,12 @@ import androidx.media3.extractor.SeekMap;
|
||||
*/
|
||||
long getDataEndPosition();
|
||||
|
||||
/**
|
||||
* Returns the average bitrate (usually derived from the duration and length of the file), or
|
||||
* {@link C#RATE_UNSET_INT} if not known.
|
||||
*/
|
||||
int getAverageBitrate();
|
||||
|
||||
/** A {@link Seeker} that does not support seeking through audio data. */
|
||||
/* package */ class UnseekableSeeker extends SeekMap.Unseekable implements Seeker {
|
||||
|
||||
@ -56,5 +62,10 @@ import androidx.media3.extractor.SeekMap;
|
||||
// Position unset as we do not know the data end position. Note that returning 0 doesn't work.
|
||||
return C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageBitrate() {
|
||||
return C.RATE_UNSET_INT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,19 +94,23 @@ import androidx.media3.extractor.SeekPoint;
|
||||
if (inputLength != C.LENGTH_UNSET && inputLength != position) {
|
||||
Log.w(TAG, "VBRI data size mismatch: " + inputLength + ", " + position);
|
||||
}
|
||||
return new VbriSeeker(timesUs, positions, durationUs, /* dataEndPosition= */ position);
|
||||
return new VbriSeeker(
|
||||
timesUs, positions, durationUs, /* dataEndPosition= */ position, mpegAudioHeader.bitrate);
|
||||
}
|
||||
|
||||
private final long[] timesUs;
|
||||
private final long[] positions;
|
||||
private final long durationUs;
|
||||
private final long dataEndPosition;
|
||||
private final int bitrate;
|
||||
|
||||
private VbriSeeker(long[] timesUs, long[] positions, long durationUs, long dataEndPosition) {
|
||||
private VbriSeeker(
|
||||
long[] timesUs, long[] positions, long durationUs, long dataEndPosition, int bitrate) {
|
||||
this.timesUs = timesUs;
|
||||
this.positions = positions;
|
||||
this.durationUs = durationUs;
|
||||
this.dataEndPosition = dataEndPosition;
|
||||
this.bitrate = bitrate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -140,4 +144,9 @@ import androidx.media3.extractor.SeekPoint;
|
||||
public long getDataEndPosition() {
|
||||
return dataEndPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageBitrate() {
|
||||
return bitrate;
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,8 @@ import androidx.media3.extractor.SeekPoint;
|
||||
xingFrame.header.sampleRate);
|
||||
if (xingFrame.dataSize == C.LENGTH_UNSET || xingFrame.tableOfContents == null) {
|
||||
// If the size in bytes or table of contents is missing, the stream is not seekable.
|
||||
return new XingSeeker(position, xingFrame.header.frameSize, durationUs);
|
||||
return new XingSeeker(
|
||||
position, xingFrame.header.frameSize, durationUs, xingFrame.header.bitrate);
|
||||
}
|
||||
|
||||
if (inputLength != C.LENGTH_UNSET && inputLength != position + xingFrame.dataSize) {
|
||||
@ -66,6 +67,7 @@ import androidx.media3.extractor.SeekPoint;
|
||||
position,
|
||||
xingFrame.header.frameSize,
|
||||
durationUs,
|
||||
xingFrame.header.bitrate,
|
||||
xingFrame.dataSize,
|
||||
xingFrame.tableOfContents);
|
||||
}
|
||||
@ -73,6 +75,7 @@ import androidx.media3.extractor.SeekPoint;
|
||||
private final long dataStartPosition;
|
||||
private final int xingFrameSize;
|
||||
private final long durationUs;
|
||||
private final int bitrate;
|
||||
|
||||
/** Data size, including the XING frame. */
|
||||
private final long dataSize;
|
||||
@ -85,11 +88,12 @@ import androidx.media3.extractor.SeekPoint;
|
||||
*/
|
||||
@Nullable private final long[] tableOfContents;
|
||||
|
||||
private XingSeeker(long dataStartPosition, int xingFrameSize, long durationUs) {
|
||||
private XingSeeker(long dataStartPosition, int xingFrameSize, long durationUs, int bitrate) {
|
||||
this(
|
||||
dataStartPosition,
|
||||
xingFrameSize,
|
||||
durationUs,
|
||||
bitrate,
|
||||
/* dataSize= */ C.LENGTH_UNSET,
|
||||
/* tableOfContents= */ null);
|
||||
}
|
||||
@ -98,13 +102,15 @@ import androidx.media3.extractor.SeekPoint;
|
||||
long dataStartPosition,
|
||||
int xingFrameSize,
|
||||
long durationUs,
|
||||
int bitrate,
|
||||
long dataSize,
|
||||
@Nullable long[] tableOfContents) {
|
||||
this.dataStartPosition = dataStartPosition;
|
||||
this.xingFrameSize = xingFrameSize;
|
||||
this.durationUs = durationUs;
|
||||
this.tableOfContents = tableOfContents;
|
||||
this.bitrate = bitrate;
|
||||
this.dataSize = dataSize;
|
||||
this.tableOfContents = tableOfContents;
|
||||
dataEndPosition = dataSize == C.LENGTH_UNSET ? C.INDEX_UNSET : dataStartPosition + dataSize;
|
||||
}
|
||||
|
||||
@ -172,6 +178,11 @@ import androidx.media3.extractor.SeekPoint;
|
||||
return dataEndPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageBitrate() {
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time in microseconds for a given table index.
|
||||
*
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 45139
|
||||
sample count = 108
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 30093
|
||||
sample count = 72
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 15046
|
||||
sample count = 36
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 0
|
||||
sample count = 0
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -7,6 +7,7 @@ track 0:
|
||||
total output bytes = 45139
|
||||
sample count = 108
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 45139
|
||||
sample count = 108
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 30093
|
||||
sample count = 72
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 15046
|
||||
sample count = 36
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 0
|
||||
sample count = 0
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -8,6 +8,7 @@ track 0:
|
||||
total output bytes = 45139
|
||||
sample count = 108
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 38160
|
||||
sample count = 117
|
||||
format 0:
|
||||
averageBitrate = 64000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 24384
|
||||
sample count = 77
|
||||
format 0:
|
||||
averageBitrate = 64000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 11328
|
||||
sample count = 38
|
||||
format 0:
|
||||
averageBitrate = 64000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 0
|
||||
sample count = 0
|
||||
format 0:
|
||||
averageBitrate = 64000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 38160
|
||||
sample count = 117
|
||||
format 0:
|
||||
averageBitrate = 64000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 418
|
||||
sample count = 1
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 418
|
||||
sample count = 1
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 418
|
||||
sample count = 1
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 418
|
||||
sample count = 1
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -7,6 +7,7 @@ track 0:
|
||||
total output bytes = 418
|
||||
sample count = 1
|
||||
format 0:
|
||||
averageBitrate = 128000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 2
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 8359
|
||||
sample count = 40
|
||||
format 0:
|
||||
averageBitrate = 64000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 1
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 5643
|
||||
sample count = 27
|
||||
format 0:
|
||||
averageBitrate = 64000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 1
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 2926
|
||||
sample count = 14
|
||||
format 0:
|
||||
averageBitrate = 64000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 1
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 0
|
||||
sample count = 0
|
||||
format 0:
|
||||
averageBitrate = 64000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 1
|
||||
|
@ -10,6 +10,7 @@ track 0:
|
||||
total output bytes = 8359
|
||||
sample count = 40
|
||||
format 0:
|
||||
averageBitrate = 64000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 1
|
||||
|
@ -1,4 +1,5 @@
|
||||
format audio:
|
||||
averageBitrate = 64000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 1
|
||||
|
@ -1,4 +1,5 @@
|
||||
format audio:
|
||||
averageBitrate = 64000
|
||||
sampleMimeType = audio/mpeg
|
||||
maxInputSize = 4096
|
||||
channelCount = 1
|
||||
|
Loading…
x
Reference in New Issue
Block a user