Plumb MP3 average bitrate from metadata frames into Format

Issue: androidx/media#1081

#minor-release

PiperOrigin-RevId: 619185083
This commit is contained in:
ibaker 2024-03-26 07:36:46 -07:00 committed by Copybara-Service
parent 6e0f8e3b0d
commit d00ca1e343
35 changed files with 100 additions and 8 deletions

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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.
*

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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.
*

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 45139
sample count = 108
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 30093
sample count = 72
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 15046
sample count = 36
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 0
sample count = 0
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -7,6 +7,7 @@ track 0:
total output bytes = 45139
sample count = 108
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 45139
sample count = 108
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 30093
sample count = 72
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 15046
sample count = 36
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 0
sample count = 0
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -8,6 +8,7 @@ track 0:
total output bytes = 45139
sample count = 108
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 38160
sample count = 117
format 0:
averageBitrate = 64000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 24384
sample count = 77
format 0:
averageBitrate = 64000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 11328
sample count = 38
format 0:
averageBitrate = 64000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 0
sample count = 0
format 0:
averageBitrate = 64000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 38160
sample count = 117
format 0:
averageBitrate = 64000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 418
sample count = 1
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 418
sample count = 1
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 418
sample count = 1
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 418
sample count = 1
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -7,6 +7,7 @@ track 0:
total output bytes = 418
sample count = 1
format 0:
averageBitrate = 128000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 2

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 8359
sample count = 40
format 0:
averageBitrate = 64000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 1

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 5643
sample count = 27
format 0:
averageBitrate = 64000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 1

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 2926
sample count = 14
format 0:
averageBitrate = 64000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 1

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 0
sample count = 0
format 0:
averageBitrate = 64000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 1

View File

@ -10,6 +10,7 @@ track 0:
total output bytes = 8359
sample count = 40
format 0:
averageBitrate = 64000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 1

View File

@ -1,4 +1,5 @@
format audio:
averageBitrate = 64000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 1

View File

@ -1,4 +1,5 @@
format audio:
averageBitrate = 64000
sampleMimeType = audio/mpeg
maxInputSize = 4096
channelCount = 1