Move MpegAudioHeader to MpegUtil
This makes MPEG audio utilities similar to utilities we have for WAV, AC-3 etc., and moves them out of the extractor package so that an extractor module can be split out without needing to have a class in the extractor package in the common library. PiperOrigin-RevId: 290595089
This commit is contained in:
parent
5e2b4117bd
commit
ea69a3dbb6
@ -28,7 +28,6 @@ import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.audio.AudioProcessor.UnhandledAudioFormatException;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
@ -1159,7 +1158,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffer buffer) {
|
||||
switch (encoding) {
|
||||
case C.ENCODING_MP3:
|
||||
return MpegAudioHeader.getFrameSampleCount(buffer.get(buffer.position()));
|
||||
return MpegAudioUtil.parseMpegAudioFrameSampleCount(buffer.get(buffer.position()));
|
||||
case C.ENCODING_DTS:
|
||||
case C.ENCODING_DTS_HD:
|
||||
return DtsUtil.parseDtsAudioSampleCount(buffer);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -13,22 +13,199 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor;
|
||||
package com.google.android.exoplayer2.audio;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
|
||||
/**
|
||||
* An MPEG audio frame header.
|
||||
*/
|
||||
public final class MpegAudioHeader {
|
||||
/** Utility methods for handling MPEG audio streams. */
|
||||
public final class MpegAudioUtil {
|
||||
|
||||
/** Stores the metadata for an MPEG audio frame. */
|
||||
public static final class Header {
|
||||
|
||||
/** MPEG audio header version. */
|
||||
public int version;
|
||||
/** The mime type. */
|
||||
@Nullable public String mimeType;
|
||||
/** Size of the frame associated with this header, in bytes. */
|
||||
public int frameSize;
|
||||
/** Sample rate in samples per second. */
|
||||
public int sampleRate;
|
||||
/** Number of audio channels in the frame. */
|
||||
public int channels;
|
||||
/** Bitrate of the frame in bit/s. */
|
||||
public int bitrate;
|
||||
/** Number of samples stored in the frame. */
|
||||
public int samplesPerFrame;
|
||||
|
||||
/**
|
||||
* Populates the fields in this instance to reflect the the MPEG audio header in {@code
|
||||
* headerData}, returning whether the header was valid. If false, the values of the fields in
|
||||
* this instance will not be updated.
|
||||
*
|
||||
* @param headerData Header data to parse.
|
||||
* @return True if the fields were populated. False otherwise, indicating that {@code
|
||||
* headerData} is not a valid MPEG audio header.
|
||||
*/
|
||||
public boolean setForHeaderData(int headerData) {
|
||||
if (!isMagicPresent(headerData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int version = (headerData >>> 19) & 3;
|
||||
if (version == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int layer = (headerData >>> 17) & 3;
|
||||
if (layer == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int bitrateIndex = (headerData >>> 12) & 15;
|
||||
if (bitrateIndex == 0 || bitrateIndex == 0xF) {
|
||||
// Disallow "free" bitrate.
|
||||
return false;
|
||||
}
|
||||
|
||||
int samplingRateIndex = (headerData >>> 10) & 3;
|
||||
if (samplingRateIndex == 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.version = version;
|
||||
mimeType = MIME_TYPE_BY_LAYER[3 - layer];
|
||||
sampleRate = SAMPLING_RATE_V1[samplingRateIndex];
|
||||
if (version == 2) {
|
||||
// Version 2
|
||||
sampleRate /= 2;
|
||||
} else if (version == 0) {
|
||||
// Version 2.5
|
||||
sampleRate /= 4;
|
||||
}
|
||||
int padding = (headerData >>> 9) & 1;
|
||||
samplesPerFrame = getFrameSizeInSamples(version, layer);
|
||||
if (layer == 3) {
|
||||
// Layer I (layer == 3)
|
||||
bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1];
|
||||
frameSize = (12 * bitrate / sampleRate + padding) * 4;
|
||||
} else {
|
||||
// Layer II (layer == 2) or III (layer == 1)
|
||||
if (version == 3) {
|
||||
// Version 1
|
||||
bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1];
|
||||
frameSize = 144 * bitrate / sampleRate + padding;
|
||||
} else {
|
||||
// Version 2 or 2.5.
|
||||
bitrate = BITRATE_V2[bitrateIndex - 1];
|
||||
frameSize = (layer == 1 ? 72 : 144) * bitrate / sampleRate + padding;
|
||||
}
|
||||
}
|
||||
channels = ((headerData >> 6) & 3) == 3 ? 1 : 2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the frame associated with {@code header}, or {@link C#LENGTH_UNSET} if it
|
||||
* is invalid.
|
||||
*/
|
||||
public static int getFrameSize(int headerData) {
|
||||
if (!isMagicPresent(headerData)) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int version = (headerData >>> 19) & 3;
|
||||
if (version == 1) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int layer = (headerData >>> 17) & 3;
|
||||
if (layer == 0) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int bitrateIndex = (headerData >>> 12) & 15;
|
||||
if (bitrateIndex == 0 || bitrateIndex == 0xF) {
|
||||
// Disallow "free" bitrate.
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int samplingRateIndex = (headerData >>> 10) & 3;
|
||||
if (samplingRateIndex == 3) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int samplingRate = SAMPLING_RATE_V1[samplingRateIndex];
|
||||
if (version == 2) {
|
||||
// Version 2
|
||||
samplingRate /= 2;
|
||||
} else if (version == 0) {
|
||||
// Version 2.5
|
||||
samplingRate /= 4;
|
||||
}
|
||||
|
||||
int bitrate;
|
||||
int padding = (headerData >>> 9) & 1;
|
||||
if (layer == 3) {
|
||||
// Layer I (layer == 3)
|
||||
bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1];
|
||||
return (12 * bitrate / samplingRate + padding) * 4;
|
||||
} else {
|
||||
// Layer II (layer == 2) or III (layer == 1)
|
||||
if (version == 3) {
|
||||
bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1];
|
||||
} else {
|
||||
// Version 2 or 2.5.
|
||||
bitrate = BITRATE_V2[bitrateIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (version == 3) {
|
||||
// Version 1
|
||||
return 144 * bitrate / samplingRate + padding;
|
||||
} else {
|
||||
// Version 2 or 2.5
|
||||
return (layer == 1 ? 72 : 144) * bitrate / samplingRate + padding;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of samples per frame associated with {@code headerData}, or {@link
|
||||
* C#LENGTH_UNSET} if it is invalid.
|
||||
*/
|
||||
public static int parseMpegAudioFrameSampleCount(int headerData) {
|
||||
if (!isMagicPresent(headerData)) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int version = (headerData >>> 19) & 3;
|
||||
if (version == 1) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int layer = (headerData >>> 17) & 3;
|
||||
if (layer == 0) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
// Those header values are not used but are checked for consistency with the other methods
|
||||
int bitrateIndex = (headerData >>> 12) & 15;
|
||||
int samplingRateIndex = (headerData >>> 10) & 3;
|
||||
if (bitrateIndex == 0 || bitrateIndex == 0xF || samplingRateIndex == 3) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
return getFrameSizeInSamples(version, layer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Theoretical maximum frame size for an MPEG audio stream, which occurs when playing a Layer 2
|
||||
* MPEG 2.5 audio stream at 16 kb/s (with padding). The size is 1152 sample/frame *
|
||||
* 160000 bit/s / (8000 sample/s * 8 bit/byte) + 1 padding byte/frame = 2881 byte/frame.
|
||||
* The next power of two size is 4 KiB.
|
||||
* MPEG 2.5 audio stream at 16 kb/s (with padding). The size is 1152 sample/frame * 160000 bit/s /
|
||||
* (8000 sample/s * 8 bit/byte) + 1 padding byte/frame = 2881 byte/frame. The next power of two
|
||||
* size is 4 KiB.
|
||||
*/
|
||||
public static final int MAX_FRAME_SIZE_BYTES = 4096;
|
||||
|
||||
@ -61,172 +238,10 @@ public final class MpegAudioHeader {
|
||||
private static final int SAMPLES_PER_FRAME_L3_V1 = 1152;
|
||||
private static final int SAMPLES_PER_FRAME_L3_V2 = 576;
|
||||
|
||||
/**
|
||||
* Returns the size of the frame associated with {@code header}, or {@link C#LENGTH_UNSET} if it
|
||||
* is invalid.
|
||||
*/
|
||||
public static int getFrameSize(int header) {
|
||||
if (!isMagicPresent(header)) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
private MpegAudioUtil() {}
|
||||
|
||||
int version = (header >>> 19) & 3;
|
||||
if (version == 1) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int layer = (header >>> 17) & 3;
|
||||
if (layer == 0) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int bitrateIndex = (header >>> 12) & 15;
|
||||
if (bitrateIndex == 0 || bitrateIndex == 0xF) {
|
||||
// Disallow "free" bitrate.
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int samplingRateIndex = (header >>> 10) & 3;
|
||||
if (samplingRateIndex == 3) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int samplingRate = SAMPLING_RATE_V1[samplingRateIndex];
|
||||
if (version == 2) {
|
||||
// Version 2
|
||||
samplingRate /= 2;
|
||||
} else if (version == 0) {
|
||||
// Version 2.5
|
||||
samplingRate /= 4;
|
||||
}
|
||||
|
||||
int bitrate;
|
||||
int padding = (header >>> 9) & 1;
|
||||
if (layer == 3) {
|
||||
// Layer I (layer == 3)
|
||||
bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1];
|
||||
return (12 * bitrate / samplingRate + padding) * 4;
|
||||
} else {
|
||||
// Layer II (layer == 2) or III (layer == 1)
|
||||
if (version == 3) {
|
||||
bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1];
|
||||
} else {
|
||||
// Version 2 or 2.5.
|
||||
bitrate = BITRATE_V2[bitrateIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (version == 3) {
|
||||
// Version 1
|
||||
return 144 * bitrate / samplingRate + padding;
|
||||
} else {
|
||||
// Version 2 or 2.5
|
||||
return (layer == 1 ? 72 : 144) * bitrate / samplingRate + padding;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of samples per frame associated with {@code header}, or {@link
|
||||
* C#LENGTH_UNSET} if it is invalid.
|
||||
*/
|
||||
public static int getFrameSampleCount(int header) {
|
||||
|
||||
if (!isMagicPresent(header)) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int version = (header >>> 19) & 3;
|
||||
if (version == 1) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
int layer = (header >>> 17) & 3;
|
||||
if (layer == 0) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
// Those header values are not used but are checked for consistency with the other methods
|
||||
int bitrateIndex = (header >>> 12) & 15;
|
||||
int samplingRateIndex = (header >>> 10) & 3;
|
||||
if (bitrateIndex == 0 || bitrateIndex == 0xF || samplingRateIndex == 3) {
|
||||
return C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
return getFrameSizeInSamples(version, layer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses {@code headerData}, populating {@code header} with the parsed data.
|
||||
*
|
||||
* @param headerData Header data to parse.
|
||||
* @param header Header to populate with data from {@code headerData}.
|
||||
* @return True if the header was populated. False otherwise, indicating that {@code headerData}
|
||||
* is not a valid MPEG audio header.
|
||||
*/
|
||||
public static boolean populateHeader(int headerData, MpegAudioHeader header) {
|
||||
if (!isMagicPresent(headerData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int version = (headerData >>> 19) & 3;
|
||||
if (version == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int layer = (headerData >>> 17) & 3;
|
||||
if (layer == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int bitrateIndex = (headerData >>> 12) & 15;
|
||||
if (bitrateIndex == 0 || bitrateIndex == 0xF) {
|
||||
// Disallow "free" bitrate.
|
||||
return false;
|
||||
}
|
||||
|
||||
int samplingRateIndex = (headerData >>> 10) & 3;
|
||||
if (samplingRateIndex == 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int sampleRate = SAMPLING_RATE_V1[samplingRateIndex];
|
||||
if (version == 2) {
|
||||
// Version 2
|
||||
sampleRate /= 2;
|
||||
} else if (version == 0) {
|
||||
// Version 2.5
|
||||
sampleRate /= 4;
|
||||
}
|
||||
|
||||
int padding = (headerData >>> 9) & 1;
|
||||
int bitrate;
|
||||
int frameSize;
|
||||
int samplesPerFrame = getFrameSizeInSamples(version, layer);
|
||||
if (layer == 3) {
|
||||
// Layer I (layer == 3)
|
||||
bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1];
|
||||
frameSize = (12 * bitrate / sampleRate + padding) * 4;
|
||||
} else {
|
||||
// Layer II (layer == 2) or III (layer == 1)
|
||||
if (version == 3) {
|
||||
// Version 1
|
||||
bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1];
|
||||
frameSize = 144 * bitrate / sampleRate + padding;
|
||||
} else {
|
||||
// Version 2 or 2.5.
|
||||
bitrate = BITRATE_V2[bitrateIndex - 1];
|
||||
frameSize = (layer == 1 ? 72 : 144) * bitrate / sampleRate + padding;
|
||||
}
|
||||
}
|
||||
|
||||
String mimeType = MIME_TYPE_BY_LAYER[3 - layer];
|
||||
int channels = ((headerData >> 6) & 3) == 3 ? 1 : 2;
|
||||
header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate, samplesPerFrame);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isMagicPresent(int header) {
|
||||
return (header & 0xFFE00000) == 0xFFE00000;
|
||||
private static boolean isMagicPresent(int headerData) {
|
||||
return (headerData & 0xFFE00000) == 0xFFE00000;
|
||||
}
|
||||
|
||||
private static int getFrameSizeInSamples(int version, int layer) {
|
||||
@ -237,39 +252,8 @@ public final class MpegAudioHeader {
|
||||
return SAMPLES_PER_FRAME_L2; // Layer II
|
||||
case 3:
|
||||
return SAMPLES_PER_FRAME_L1; // Layer I
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
/** MPEG audio header version. */
|
||||
public int version;
|
||||
/** The mime type. */
|
||||
@Nullable public String mimeType;
|
||||
/** Size of the frame associated with this header, in bytes. */
|
||||
public int frameSize;
|
||||
/** Sample rate in samples per second. */
|
||||
public int sampleRate;
|
||||
/** Number of audio channels in the frame. */
|
||||
public int channels;
|
||||
/** Bitrate of the frame in bit/s. */
|
||||
public int bitrate;
|
||||
/** Number of samples stored in the frame. */
|
||||
public int samplesPerFrame;
|
||||
|
||||
private void setValues(
|
||||
int version,
|
||||
String mimeType,
|
||||
int frameSize,
|
||||
int sampleRate,
|
||||
int channels,
|
||||
int bitrate,
|
||||
int samplesPerFrame) {
|
||||
this.version = version;
|
||||
this.mimeType = mimeType;
|
||||
this.frameSize = frameSize;
|
||||
this.sampleRate = sampleRate;
|
||||
this.channels = channels;
|
||||
this.bitrate = bitrate;
|
||||
this.samplesPerFrame = samplesPerFrame;
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.audio.Ac3Util;
|
||||
import com.google.android.exoplayer2.audio.MpegAudioUtil;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
||||
import com.google.android.exoplayer2.extractor.ChunkIndex;
|
||||
@ -31,7 +32,6 @@ import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||
@ -1989,11 +1989,11 @@ public class MatroskaExtractor implements Extractor {
|
||||
break;
|
||||
case CODEC_ID_MP2:
|
||||
mimeType = MimeTypes.AUDIO_MPEG_L2;
|
||||
maxInputSize = MpegAudioHeader.MAX_FRAME_SIZE_BYTES;
|
||||
maxInputSize = MpegAudioUtil.MAX_FRAME_SIZE_BYTES;
|
||||
break;
|
||||
case CODEC_ID_MP3:
|
||||
mimeType = MimeTypes.AUDIO_MPEG;
|
||||
maxInputSize = MpegAudioHeader.MAX_FRAME_SIZE_BYTES;
|
||||
maxInputSize = MpegAudioUtil.MAX_FRAME_SIZE_BYTES;
|
||||
break;
|
||||
case CODEC_ID_AC3:
|
||||
mimeType = MimeTypes.AUDIO_AC3;
|
||||
|
@ -16,8 +16,8 @@
|
||||
package com.google.android.exoplayer2.extractor.mp3;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.audio.MpegAudioUtil;
|
||||
import com.google.android.exoplayer2.extractor.ConstantBitrateSeekMap;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
|
||||
/**
|
||||
* MP3 seeker that doesn't rely on metadata and seeks assuming the source has a constant bitrate.
|
||||
@ -30,7 +30,7 @@ import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
* @param mpegAudioHeader The MPEG audio header associated with the first frame.
|
||||
*/
|
||||
public ConstantBitrateSeeker(
|
||||
long inputLength, long firstFramePosition, MpegAudioHeader mpegAudioHeader) {
|
||||
long inputLength, long firstFramePosition, MpegAudioUtil.Header mpegAudioHeader) {
|
||||
super(inputLength, firstFramePosition, mpegAudioHeader.bitrate, mpegAudioHeader.frameSize);
|
||||
}
|
||||
|
||||
|
@ -20,13 +20,13 @@ import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.audio.MpegAudioUtil;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.GaplessInfoHolder;
|
||||
import com.google.android.exoplayer2.extractor.Id3Peeker;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||
import com.google.android.exoplayer2.extractor.mp3.Seeker.UnseekableSeeker;
|
||||
@ -107,7 +107,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
@Flags private final int flags;
|
||||
private final long forcedFirstSampleTimestampUs;
|
||||
private final ParsableByteArray scratch;
|
||||
private final MpegAudioHeader synchronizedHeader;
|
||||
private final MpegAudioUtil.Header synchronizedHeader;
|
||||
private final GaplessInfoHolder gaplessInfoHolder;
|
||||
private final Id3Peeker id3Peeker;
|
||||
|
||||
@ -145,7 +145,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
this.flags = flags;
|
||||
this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs;
|
||||
scratch = new ParsableByteArray(SCRATCH_LENGTH);
|
||||
synchronizedHeader = new MpegAudioHeader();
|
||||
synchronizedHeader = new MpegAudioUtil.Header();
|
||||
gaplessInfoHolder = new GaplessInfoHolder();
|
||||
basisTimeUs = C.TIME_UNSET;
|
||||
id3Peeker = new Id3Peeker();
|
||||
@ -215,7 +215,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
synchronizedHeader.mimeType,
|
||||
/* codecs= */ null,
|
||||
/* bitrate= */ Format.NO_VALUE,
|
||||
MpegAudioHeader.MAX_FRAME_SIZE_BYTES,
|
||||
MpegAudioUtil.MAX_FRAME_SIZE_BYTES,
|
||||
synchronizedHeader.channels,
|
||||
synchronizedHeader.sampleRate,
|
||||
/* pcmEncoding= */ Format.NO_VALUE,
|
||||
@ -258,13 +258,13 @@ public final class Mp3Extractor implements Extractor {
|
||||
scratch.setPosition(0);
|
||||
int sampleHeaderData = scratch.readInt();
|
||||
if (!headersMatch(sampleHeaderData, synchronizedHeaderData)
|
||||
|| MpegAudioHeader.getFrameSize(sampleHeaderData) == C.LENGTH_UNSET) {
|
||||
|| MpegAudioUtil.getFrameSize(sampleHeaderData) == C.LENGTH_UNSET) {
|
||||
// We have lost synchronization, so attempt to resynchronize starting at the next byte.
|
||||
extractorInput.skipFully(1);
|
||||
synchronizedHeaderData = 0;
|
||||
return RESULT_CONTINUE;
|
||||
}
|
||||
MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader);
|
||||
synchronizedHeader.setForHeaderData(sampleHeaderData);
|
||||
if (basisTimeUs == C.TIME_UNSET) {
|
||||
basisTimeUs = seeker.getTimeUs(extractorInput.getPosition());
|
||||
if (forcedFirstSampleTimestampUs != C.TIME_UNSET) {
|
||||
@ -325,8 +325,8 @@ public final class Mp3Extractor implements Extractor {
|
||||
int headerData = scratch.readInt();
|
||||
int frameSize;
|
||||
if ((candidateSynchronizedHeaderData != 0
|
||||
&& !headersMatch(headerData, candidateSynchronizedHeaderData))
|
||||
|| (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) {
|
||||
&& !headersMatch(headerData, candidateSynchronizedHeaderData))
|
||||
|| (frameSize = MpegAudioUtil.getFrameSize(headerData)) == C.LENGTH_UNSET) {
|
||||
// The header doesn't match the candidate header or is invalid. Try the next byte offset.
|
||||
if (searchedBytes++ == searchLimitBytes) {
|
||||
if (!sniffing) {
|
||||
@ -346,7 +346,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
// The header matches the candidate header and/or is valid.
|
||||
validFrameCount++;
|
||||
if (validFrameCount == 1) {
|
||||
MpegAudioHeader.populateHeader(headerData, synchronizedHeader);
|
||||
synchronizedHeader.setForHeaderData(headerData);
|
||||
candidateSynchronizedHeaderData = headerData;
|
||||
} else if (validFrameCount == 4) {
|
||||
break;
|
||||
@ -439,7 +439,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
throws IOException, InterruptedException {
|
||||
input.peekFully(scratch.data, 0, 4);
|
||||
scratch.setPosition(0);
|
||||
MpegAudioHeader.populateHeader(scratch.readInt(), synchronizedHeader);
|
||||
synchronizedHeader.setForHeaderData(scratch.readInt());
|
||||
return new ConstantBitrateSeeker(input.getLength(), input.getPosition(), synchronizedHeader);
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ package com.google.android.exoplayer2.extractor.mp3;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.audio.MpegAudioUtil;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
@ -43,7 +43,10 @@ import com.google.android.exoplayer2.util.Util;
|
||||
*/
|
||||
@Nullable
|
||||
public static VbriSeeker create(
|
||||
long inputLength, long position, MpegAudioHeader mpegAudioHeader, ParsableByteArray frame) {
|
||||
long inputLength,
|
||||
long position,
|
||||
MpegAudioUtil.Header mpegAudioHeader,
|
||||
ParsableByteArray frame) {
|
||||
frame.skipBytes(10);
|
||||
int numFrames = frame.readInt();
|
||||
if (numFrames <= 0) {
|
||||
|
@ -17,7 +17,7 @@ package com.google.android.exoplayer2.extractor.mp3;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.audio.MpegAudioUtil;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
@ -44,7 +44,10 @@ import com.google.android.exoplayer2.util.Util;
|
||||
*/
|
||||
@Nullable
|
||||
public static XingSeeker create(
|
||||
long inputLength, long position, MpegAudioHeader mpegAudioHeader, ParsableByteArray frame) {
|
||||
long inputLength,
|
||||
long position,
|
||||
MpegAudioUtil.Header mpegAudioHeader,
|
||||
ParsableByteArray frame) {
|
||||
int samplesPerFrame = mpegAudioHeader.samplesPerFrame;
|
||||
int sampleRate = mpegAudioHeader.sampleRate;
|
||||
|
||||
|
@ -17,8 +17,8 @@ package com.google.android.exoplayer2.extractor.ts;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.audio.MpegAudioUtil;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
@ -39,7 +39,7 @@ public final class MpegAudioReader implements ElementaryStreamReader {
|
||||
private static final int HEADER_SIZE = 4;
|
||||
|
||||
private final ParsableByteArray headerScratch;
|
||||
private final MpegAudioHeader header;
|
||||
private final MpegAudioUtil.Header header;
|
||||
@Nullable private final String language;
|
||||
|
||||
private @MonotonicNonNull TrackOutput output;
|
||||
@ -68,7 +68,7 @@ public final class MpegAudioReader implements ElementaryStreamReader {
|
||||
// The first byte of an MPEG Audio frame header is always 0xFF.
|
||||
headerScratch = new ParsableByteArray(4);
|
||||
headerScratch.data[0] = (byte) 0xFF;
|
||||
header = new MpegAudioHeader();
|
||||
header = new MpegAudioUtil.Header();
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ public final class MpegAudioReader implements ElementaryStreamReader {
|
||||
}
|
||||
|
||||
headerScratch.setPosition(0);
|
||||
boolean parsedHeader = MpegAudioHeader.populateHeader(headerScratch.readInt(), header);
|
||||
boolean parsedHeader = header.setForHeaderData(headerScratch.readInt());
|
||||
if (!parsedHeader) {
|
||||
// We thought we'd located a frame header, but we hadn't.
|
||||
frameBytesRead = 0;
|
||||
@ -187,9 +187,19 @@ public final class MpegAudioReader implements ElementaryStreamReader {
|
||||
frameSize = header.frameSize;
|
||||
if (!hasOutputFormat) {
|
||||
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
|
||||
Format format = Format.createAudioSampleFormat(formatId, header.mimeType, null,
|
||||
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate,
|
||||
null, null, 0, language);
|
||||
Format format =
|
||||
Format.createAudioSampleFormat(
|
||||
formatId,
|
||||
header.mimeType,
|
||||
null,
|
||||
Format.NO_VALUE,
|
||||
MpegAudioUtil.MAX_FRAME_SIZE_BYTES,
|
||||
header.channels,
|
||||
header.sampleRate,
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
language);
|
||||
output.format(format);
|
||||
hasOutputFormat = true;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.audio.MpegAudioUtil;
|
||||
import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
@ -59,8 +59,8 @@ public final class XingSeekerTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MpegAudioHeader xingFrameHeader = new MpegAudioHeader();
|
||||
MpegAudioHeader.populateHeader(XING_FRAME_HEADER_DATA, xingFrameHeader);
|
||||
MpegAudioUtil.Header xingFrameHeader = new MpegAudioUtil.Header();
|
||||
xingFrameHeader.setForHeaderData(XING_FRAME_HEADER_DATA);
|
||||
seeker = XingSeeker.create(C.LENGTH_UNSET, XING_FRAME_POSITION, xingFrameHeader,
|
||||
new ParsableByteArray(XING_FRAME_PAYLOAD));
|
||||
seekerWithInputLength = XingSeeker.create(STREAM_LENGTH,
|
||||
|
Loading…
x
Reference in New Issue
Block a user