Support mp3 media segments in HLS.

Issue #804
This commit is contained in:
Oliver Woodman 2015-09-18 18:23:50 +01:00
parent c960636d28
commit da97e30e33
3 changed files with 61 additions and 30 deletions

View file

@ -48,6 +48,7 @@ public final class Mp3Extractor implements Extractor {
private static final int INFO_HEADER = Util.getIntegerCodeForString("Info");
private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI");
private final long forcedFirstSampleTimestampUs;
private final BufferingInput inputBuffer;
private final ParsableByteArray scratch;
private final MpegAudioHeader synchronizedHeader;
@ -63,11 +64,25 @@ public final class Mp3Extractor implements Extractor {
private int samplesRead;
private int sampleBytesRemaining;
/** Constructs a new {@link Mp3Extractor}. */
/**
* Constructs a new {@link Mp3Extractor}.
*/
public Mp3Extractor() {
this(-1);
}
/**
* Constructs a new {@link Mp3Extractor}.
*
* @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or -1 if forcing
* is not required.
*/
public Mp3Extractor(long forcedFirstSampleTimestampUs) {
this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs;
inputBuffer = new BufferingInput(MpegAudioHeader.MAX_FRAME_SIZE_BYTES * 3);
scratch = new ParsableByteArray(4);
synchronizedHeader = new MpegAudioHeader();
basisTimeUs = -1;
}
@Override
@ -164,6 +179,10 @@ public final class Mp3Extractor implements Extractor {
}
if (basisTimeUs == -1) {
basisTimeUs = seeker.getTimeUs(getPosition(extractorInput, inputBuffer));
if (forcedFirstSampleTimestampUs != -1) {
long embeddedFirstSampleTimestampUs = seeker.getTimeUs(0);
basisTimeUs += forcedFirstSampleTimestampUs - embeddedFirstSampleTimestampUs;
}
}
sampleBytesRemaining = synchronizedHeader.frameSize;
}

View file

@ -23,6 +23,7 @@ import com.google.android.exoplayer.chunk.ChunkOperationHolder;
import com.google.android.exoplayer.chunk.DataChunk;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster;
import com.google.android.exoplayer.extractor.ts.TsExtractor;
@ -114,6 +115,7 @@ public class HlsChunkSource {
private static final String TAG = "HlsChunkSource";
private static final String AAC_FILE_EXTENSION = ".aac";
private static final String MP3_FILE_EXTENSION = ".mp3";
private static final float BANDWIDTH_FRACTION = 0.8f;
private final DataSource dataSource;
@ -354,24 +356,28 @@ public class HlsChunkSource {
// Configure the extractor that will read the chunk.
HlsExtractorWrapper extractorWrapper;
if (previousTsChunk == null || segment.discontinuity || liveDiscontinuity
if (chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION)) {
Extractor extractor = new AdtsExtractor(startTimeUs);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight);
} else if (chunkUri.getLastPathSegment().endsWith(MP3_FILE_EXTENSION)) {
Extractor extractor = new Mp3Extractor(startTimeUs);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight);
} else if (previousTsChunk == null || segment.discontinuity || liveDiscontinuity
|| !format.equals(previousTsChunk.format)) {
Extractor extractor;
if (chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION)) {
extractor = new AdtsExtractor(startTimeUs);
} else {
if (previousTsChunk == null || segment.discontinuity || liveDiscontinuity
|| ptsTimestampAdjuster == null) {
// TODO: Use this for AAC as well, along with the ID3 PRIV priv tag values with owner
// identifier com.apple.streaming.transportStreamTimestamp.
ptsTimestampAdjuster = new PtsTimestampAdjuster(startTimeUs);
}
extractor = new TsExtractor(ptsTimestampAdjuster);
// MPEG-2 TS segments, but we need a new extractor.
if (previousTsChunk == null || segment.discontinuity || liveDiscontinuity
|| ptsTimestampAdjuster == null) {
// TODO: Use this for AAC as well, along with the ID3 PRIV priv tag values with owner
// identifier com.apple.streaming.transportStreamTimestamp.
ptsTimestampAdjuster = new PtsTimestampAdjuster(startTimeUs);
}
Extractor extractor = new TsExtractor(ptsTimestampAdjuster);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight);
} else {
// MPEG-2 TS segments, and we need to continue using the same extractor.
extractorWrapper = previousTsChunk.extractorWrapper;
}

View file

@ -131,24 +131,30 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
return true;
}
if (!extractors.isEmpty()) {
// We're not prepared, but we might have loaded what we need.
HlsExtractorWrapper extractor = getCurrentExtractor();
if (extractor.isPrepared()) {
trackCount = extractor.getTrackCount();
trackEnabledStates = new boolean[trackCount];
pendingDiscontinuities = new boolean[trackCount];
downstreamMediaFormats = new MediaFormat[trackCount];
trackFormat = new MediaFormat[trackCount];
long durationUs = chunkSource.getDurationUs();
for (int i = 0; i < trackCount; i++) {
MediaFormat format = extractor.getMediaFormat(i).copyWithDurationUs(durationUs);
if (MimeTypes.isVideo(format.mimeType)) {
format = format.copyAsAdaptive();
while (true) {
// We're not prepared, but we might have loaded what we need.
HlsExtractorWrapper extractor = extractors.getFirst();
if (extractor.isPrepared()) {
trackCount = extractor.getTrackCount();
trackEnabledStates = new boolean[trackCount];
pendingDiscontinuities = new boolean[trackCount];
downstreamMediaFormats = new MediaFormat[trackCount];
trackFormat = new MediaFormat[trackCount];
long durationUs = chunkSource.getDurationUs();
for (int i = 0; i < trackCount; i++) {
MediaFormat format = extractor.getMediaFormat(i).copyWithDurationUs(durationUs);
if (MimeTypes.isVideo(format.mimeType)) {
format = format.copyAsAdaptive();
}
trackFormat[i] = format;
}
trackFormat[i] = format;
prepared = true;
return true;
} else if (extractors.size() > 1) {
extractors.removeFirst().clear();
} else {
break;
}
prepared = true;
return true;
}
}
// We're not prepared and we haven't loaded what we need.