From da97e30e3387eb2e564d441e93ff54ac89b3e651 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 18 Sep 2015 18:23:50 +0100 Subject: [PATCH] Support mp3 media segments in HLS. Issue #804 --- .../exoplayer/extractor/mp3/Mp3Extractor.java | 21 +++++++++- .../android/exoplayer/hls/HlsChunkSource.java | 32 +++++++++------- .../exoplayer/hls/HlsSampleSource.java | 38 +++++++++++-------- 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java index 34249c0069..9a6b95c5d8 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 876d706281..f70d1900f6 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index 211ab09e33..e139d5cda0 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -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.