Commit da97e30e by Oliver Woodman

Support mp3 media segments in HLS.

Issue #804
parent c960636d
...@@ -48,6 +48,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -48,6 +48,7 @@ public final class Mp3Extractor implements Extractor {
private static final int INFO_HEADER = Util.getIntegerCodeForString("Info"); private static final int INFO_HEADER = Util.getIntegerCodeForString("Info");
private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI"); private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI");
private final long forcedFirstSampleTimestampUs;
private final BufferingInput inputBuffer; private final BufferingInput inputBuffer;
private final ParsableByteArray scratch; private final ParsableByteArray scratch;
private final MpegAudioHeader synchronizedHeader; private final MpegAudioHeader synchronizedHeader;
...@@ -63,11 +64,25 @@ public final class Mp3Extractor implements Extractor { ...@@ -63,11 +64,25 @@ public final class Mp3Extractor implements Extractor {
private int samplesRead; private int samplesRead;
private int sampleBytesRemaining; private int sampleBytesRemaining;
/** Constructs a new {@link Mp3Extractor}. */ /**
* Constructs a new {@link Mp3Extractor}.
*/
public 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); inputBuffer = new BufferingInput(MpegAudioHeader.MAX_FRAME_SIZE_BYTES * 3);
scratch = new ParsableByteArray(4); scratch = new ParsableByteArray(4);
synchronizedHeader = new MpegAudioHeader(); synchronizedHeader = new MpegAudioHeader();
basisTimeUs = -1;
} }
@Override @Override
...@@ -164,6 +179,10 @@ public final class Mp3Extractor implements Extractor { ...@@ -164,6 +179,10 @@ public final class Mp3Extractor implements Extractor {
} }
if (basisTimeUs == -1) { if (basisTimeUs == -1) {
basisTimeUs = seeker.getTimeUs(getPosition(extractorInput, inputBuffer)); basisTimeUs = seeker.getTimeUs(getPosition(extractorInput, inputBuffer));
if (forcedFirstSampleTimestampUs != -1) {
long embeddedFirstSampleTimestampUs = seeker.getTimeUs(0);
basisTimeUs += forcedFirstSampleTimestampUs - embeddedFirstSampleTimestampUs;
}
} }
sampleBytesRemaining = synchronizedHeader.frameSize; sampleBytesRemaining = synchronizedHeader.frameSize;
} }
......
...@@ -23,6 +23,7 @@ import com.google.android.exoplayer.chunk.ChunkOperationHolder; ...@@ -23,6 +23,7 @@ import com.google.android.exoplayer.chunk.ChunkOperationHolder;
import com.google.android.exoplayer.chunk.DataChunk; import com.google.android.exoplayer.chunk.DataChunk;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.extractor.Extractor; 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.AdtsExtractor;
import com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster; import com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster;
import com.google.android.exoplayer.extractor.ts.TsExtractor; import com.google.android.exoplayer.extractor.ts.TsExtractor;
...@@ -114,6 +115,7 @@ public class HlsChunkSource { ...@@ -114,6 +115,7 @@ public class HlsChunkSource {
private static final String TAG = "HlsChunkSource"; private static final String TAG = "HlsChunkSource";
private static final String AAC_FILE_EXTENSION = ".aac"; 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 static final float BANDWIDTH_FRACTION = 0.8f;
private final DataSource dataSource; private final DataSource dataSource;
...@@ -354,24 +356,28 @@ public class HlsChunkSource { ...@@ -354,24 +356,28 @@ public class HlsChunkSource {
// Configure the extractor that will read the chunk. // Configure the extractor that will read the chunk.
HlsExtractorWrapper extractorWrapper; HlsExtractorWrapper extractorWrapper;
if (previousTsChunk == null || segment.discontinuity || liveDiscontinuity
|| !format.equals(previousTsChunk.format)) {
Extractor extractor;
if (chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION)) { if (chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION)) {
extractor = new AdtsExtractor(startTimeUs); Extractor extractor = new AdtsExtractor(startTimeUs);
} else { 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)) {
// MPEG-2 TS segments, but we need a new extractor.
if (previousTsChunk == null || segment.discontinuity || liveDiscontinuity if (previousTsChunk == null || segment.discontinuity || liveDiscontinuity
|| ptsTimestampAdjuster == null) { || ptsTimestampAdjuster == null) {
// TODO: Use this for AAC as well, along with the ID3 PRIV priv tag values with owner // TODO: Use this for AAC as well, along with the ID3 PRIV priv tag values with owner
// identifier com.apple.streaming.transportStreamTimestamp. // identifier com.apple.streaming.transportStreamTimestamp.
ptsTimestampAdjuster = new PtsTimestampAdjuster(startTimeUs); ptsTimestampAdjuster = new PtsTimestampAdjuster(startTimeUs);
} }
extractor = new TsExtractor(ptsTimestampAdjuster); Extractor extractor = new TsExtractor(ptsTimestampAdjuster);
}
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor, extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight); switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight);
} else { } else {
// MPEG-2 TS segments, and we need to continue using the same extractor.
extractorWrapper = previousTsChunk.extractorWrapper; extractorWrapper = previousTsChunk.extractorWrapper;
} }
......
...@@ -131,8 +131,9 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader, ...@@ -131,8 +131,9 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
return true; return true;
} }
if (!extractors.isEmpty()) { if (!extractors.isEmpty()) {
while (true) {
// We're not prepared, but we might have loaded what we need. // We're not prepared, but we might have loaded what we need.
HlsExtractorWrapper extractor = getCurrentExtractor(); HlsExtractorWrapper extractor = extractors.getFirst();
if (extractor.isPrepared()) { if (extractor.isPrepared()) {
trackCount = extractor.getTrackCount(); trackCount = extractor.getTrackCount();
trackEnabledStates = new boolean[trackCount]; trackEnabledStates = new boolean[trackCount];
...@@ -149,6 +150,11 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader, ...@@ -149,6 +150,11 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
} }
prepared = true; prepared = true;
return true; return true;
} else if (extractors.size() > 1) {
extractors.removeFirst().clear();
} else {
break;
}
} }
} }
// We're not prepared and we haven't loaded what we need. // We're not prepared and we haven't loaded what we need.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment