Commit 4280511a by Oliver Woodman

Seamless splicing for adaptive HLS.

parent 87d0be25
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer.demo.full.player; package com.google.android.exoplayer.demo.full.player;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.demo.DemoUtil; import com.google.android.exoplayer.demo.DemoUtil;
...@@ -35,6 +36,7 @@ import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; ...@@ -35,6 +36,7 @@ import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.UriDataSource; import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import com.google.android.exoplayer.util.MimeTypes;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.net.Uri; import android.net.Uri;
...@@ -92,7 +94,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -92,7 +94,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter); DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest, bandwidthMeter, null, HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest, bandwidthMeter, null,
false); MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264, false).adaptive);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 3); HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 3);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, player.getMainHandler(), player, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, player.getMainHandler(), player, 50);
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer.demo.simple; package com.google.android.exoplayer.demo.simple;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.demo.DemoUtil; import com.google.android.exoplayer.demo.DemoUtil;
...@@ -32,6 +33,7 @@ import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; ...@@ -32,6 +33,7 @@ import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.UriDataSource; import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import com.google.android.exoplayer.util.MimeTypes;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.net.Uri; import android.net.Uri;
...@@ -88,7 +90,7 @@ import java.util.Collections; ...@@ -88,7 +90,7 @@ import java.util.Collections;
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter); DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest, bandwidthMeter, null, HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest, bandwidthMeter, null,
false); MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264, false).adaptive);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 2); HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 2);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(), MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
......
...@@ -249,9 +249,6 @@ public class HlsChunkSource { ...@@ -249,9 +249,6 @@ public class HlsChunkSource {
} else { } else {
extractor = previousTsChunk.extractor; extractor = previousTsChunk.extractor;
} }
if (splicingOut) {
extractor.discardFromNextKeyframes();
}
return new TsChunk(dataSource, dataSpec, extractor, enabledVariants[currentVariantIndex].index, return new TsChunk(dataSource, dataSpec, extractor, enabledVariants[currentVariantIndex].index,
startTimeUs, endTimeUs, nextChunkMediaSequence, splicingOut); startTimeUs, endTimeUs, nextChunkMediaSequence, splicingOut);
......
...@@ -187,6 +187,13 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -187,6 +187,13 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
extractors.removeFirst().clear(); extractors.removeFirst().clear();
extractor = extractors.getFirst(); extractor = extractors.getFirst();
} }
if (extractors.size() > 1) {
// If there's more than one extractor, attempt to configure a seamless splice from the
// current one to the next one.
extractor.configureSpliceTo(extractors.get(1));
}
int extractorIndex = 0; int extractorIndex = 0;
while (extractors.size() > extractorIndex + 1 && !extractor.hasSamples(track)) { while (extractors.size() > extractorIndex + 1 && !extractor.hasSamples(track)) {
// We're finished reading from the extractor for this particular track, so advance to the // We're finished reading from the extractor for this particular track, so advance to the
......
...@@ -59,11 +59,11 @@ public final class TsExtractor { ...@@ -59,11 +59,11 @@ public final class TsExtractor {
/* package */ final long firstSampleTimestamp; /* package */ final long firstSampleTimestamp;
private boolean prepared; private boolean prepared;
private boolean spliceConfigured;
/* package */ boolean pendingFirstSampleTimestampAdjustment; /* package */ boolean pendingFirstSampleTimestampAdjustment;
/* package */ long sampleTimestampOffsetUs; /* package */ long sampleTimestampOffsetUs;
/* package */ long largestParsedTimestampUs; /* package */ long largestParsedTimestampUs;
/* package */ boolean discardFromNextKeyframes;
public TsExtractor(long firstSampleTimestamp, SamplePool samplePool) { public TsExtractor(long firstSampleTimestamp, SamplePool samplePool) {
this.firstSampleTimestamp = firstSampleTimestamp; this.firstSampleTimestamp = firstSampleTimestamp;
...@@ -120,10 +120,33 @@ public final class TsExtractor { ...@@ -120,10 +120,33 @@ public final class TsExtractor {
} }
/** /**
* For each track, discards samples from the next key frame (inclusive). * Attempts to configure a splice from this extractor to the next.
* <p>
* The splice is performed such that for each track the samples read from the next extractor
* start with a keyframe, and continue from where the samples read from this extractor finish.
* A successful splice may discard samples from either or both extractors.
* <p>
* Splice configuration may fail if the next extractor is not yet in a state that allows the
* splice to be performed. Calling this method is a noop if the splice has already been
* configured. Hence this method should be called repeatedly during the window within which a
* splice can be performed.
*
* @param nextExtractor The extractor being spliced to.
*/ */
public void discardFromNextKeyframes() { public void configureSpliceTo(TsExtractor nextExtractor) {
discardFromNextKeyframes = true; Assertions.checkState(prepared);
if (spliceConfigured || !nextExtractor.isPrepared()) {
// The splice is already configured or the next extractor isn't ready to be spliced in.
// Already configured, or too early to splice.
return;
}
boolean spliceConfigured = true;
for (int i = 0; i < sampleQueues.size(); i++) {
spliceConfigured &= sampleQueues.valueAt(i).configureSpliceTo(
nextExtractor.sampleQueues.valueAt(i));
}
this.spliceConfigured = spliceConfigured;
return;
} }
/** /**
...@@ -144,12 +167,13 @@ public final class TsExtractor { ...@@ -144,12 +167,13 @@ public final class TsExtractor {
*/ */
public boolean getSample(int track, SampleHolder out) { public boolean getSample(int track, SampleHolder out) {
Assertions.checkState(prepared); Assertions.checkState(prepared);
Sample sample = sampleQueues.valueAt(track).poll(); SampleQueue sampleQueue = sampleQueues.valueAt(track);
Sample sample = sampleQueue.poll();
if (sample == null) { if (sample == null) {
return false; return false;
} }
convert(sample, out); convert(sample, out);
samplePool.recycle(sample); sampleQueue.recycle(sample);
return true; return true;
} }
...@@ -177,7 +201,7 @@ public final class TsExtractor { ...@@ -177,7 +201,7 @@ public final class TsExtractor {
* for the specified track. False otherwise. * for the specified track. False otherwise.
*/ */
public boolean hasSamples(int track) { public boolean hasSamples(int track) {
return !sampleQueues.valueAt(track).isEmpty(); return sampleQueues.valueAt(track).peek() != null;
} }
private boolean checkPrepared() { private boolean checkPrepared() {
...@@ -252,6 +276,7 @@ public final class TsExtractor { ...@@ -252,6 +276,7 @@ public final class TsExtractor {
return read; return read;
} }
@SuppressLint("InlinedApi")
private void convert(Sample in, SampleHolder out) { private void convert(Sample in, SampleHolder out) {
if (out.data == null || out.data.capacity() < in.size) { if (out.data == null || out.data.capacity() < in.size) {
out.replaceBuffer(in.size); out.replaceBuffer(in.size);
...@@ -260,7 +285,7 @@ public final class TsExtractor { ...@@ -260,7 +285,7 @@ public final class TsExtractor {
out.data.put(in.data, 0, in.size); out.data.put(in.data, 0, in.size);
} }
out.size = in.size; out.size = in.size;
out.flags = in.flags; out.flags = in.isKeyframe ? MediaExtractor.SAMPLE_FLAG_SYNC : 0;
out.timeUs = in.timeUs; out.timeUs = in.timeUs;
} }
...@@ -349,15 +374,15 @@ public final class TsExtractor { ...@@ -349,15 +374,15 @@ public final class TsExtractor {
PesPayloadReader pesPayloadReader = null; PesPayloadReader pesPayloadReader = null;
switch (streamType) { switch (streamType) {
case TS_STREAM_TYPE_AAC: case TS_STREAM_TYPE_AAC:
pesPayloadReader = new AdtsReader(); pesPayloadReader = new AdtsReader(samplePool);
break; break;
case TS_STREAM_TYPE_H264: case TS_STREAM_TYPE_H264:
SeiReader seiReader = new SeiReader(); SeiReader seiReader = new SeiReader(samplePool);
sampleQueues.put(TS_STREAM_TYPE_EIA608, seiReader); sampleQueues.put(TS_STREAM_TYPE_EIA608, seiReader);
pesPayloadReader = new H264Reader(seiReader); pesPayloadReader = new H264Reader(samplePool, seiReader);
break; break;
case TS_STREAM_TYPE_ID3: case TS_STREAM_TYPE_ID3:
pesPayloadReader = new Id3Reader(); pesPayloadReader = new Id3Reader(samplePool);
break; break;
} }
...@@ -471,18 +496,24 @@ public final class TsExtractor { ...@@ -471,18 +496,24 @@ public final class TsExtractor {
} }
/** /**
* A collection of extracted samples. * A queue of extracted samples together with their corresponding {@link MediaFormat}.
*/ */
private abstract class SampleQueue { private abstract class SampleQueue {
private final ConcurrentLinkedQueue<Sample> queue; @SuppressWarnings("hiding")
private final SamplePool samplePool;
private final ConcurrentLinkedQueue<Sample> internalQueue;
private MediaFormat mediaFormat; private MediaFormat mediaFormat;
private boolean foundFirstKeyframe; private long spliceOutTimeUs;
private boolean foundLastKeyframe; private long lastParsedTimestampUs;
private boolean readFirstFrame;
protected SampleQueue() {
this.queue = new ConcurrentLinkedQueue<Sample>(); protected SampleQueue(SamplePool samplePool) {
this.samplePool = samplePool;
internalQueue = new ConcurrentLinkedQueue<Sample>();
spliceOutTimeUs = Long.MIN_VALUE;
lastParsedTimestampUs = Long.MIN_VALUE;
} }
public boolean hasMediaFormat() { public boolean hasMediaFormat() {
...@@ -497,20 +528,113 @@ public final class TsExtractor { ...@@ -497,20 +528,113 @@ public final class TsExtractor {
this.mediaFormat = mediaFormat; this.mediaFormat = mediaFormat;
} }
/**
* Removes and returns the next sample from the queue.
* <p>
* The first sample returned is guaranteed to be a keyframe, since any non-keyframe samples
* queued prior to the first keyframe are discarded.
*
* @return The next sample from the queue, or null if a sample isn't available.
*/
public Sample poll() {
Sample head = peek();
if (head != null) {
internalQueue.remove();
readFirstFrame = true;
}
return head;
}
/**
* Like {@link #poll()}, except the returned sample is not removed from the queue.
*
* @return The next sample from the queue, or null if a sample isn't available.
*/
public Sample peek() {
Sample head = internalQueue.peek();
if (!readFirstFrame) {
// Peeking discard of samples until we find a keyframe or run out of available samples.
while (head != null && !head.isKeyframe) {
recycle(head);
internalQueue.remove();
head = internalQueue.peek();
}
}
if (head == null) {
return null;
}
if (spliceOutTimeUs != Long.MIN_VALUE && head.timeUs >= spliceOutTimeUs) {
// The sample is later than the time this queue is spliced out.
recycle(head);
internalQueue.remove();
return null;
}
return head;
}
/**
* Clears the queue.
*/
public void clear() { public void clear() {
Sample toRecycle = queue.poll(); Sample toRecycle = internalQueue.poll();
while (toRecycle != null) { while (toRecycle != null) {
samplePool.recycle(toRecycle); recycle(toRecycle);
toRecycle = queue.poll(); toRecycle = internalQueue.poll();
} }
} }
public Sample poll() { /**
return queue.poll(); * Recycles a sample.
*
* @param sample The sample to recycle.
*/
public void recycle(Sample sample) {
samplePool.recycle(sample);
} }
public boolean isEmpty() { /**
return queue.isEmpty(); * Attempts to configure a splice from this queue to the next.
*
* @param nextQueue The queue being spliced to.
* @return Whether the splice was configured successfully.
*/
public boolean configureSpliceTo(SampleQueue nextQueue) {
if (spliceOutTimeUs != Long.MIN_VALUE) {
// We've already configured the splice.
return true;
}
long firstPossibleSpliceTime;
Sample nextSample = internalQueue.peek();
if (nextSample != null) {
firstPossibleSpliceTime = nextSample.timeUs;
} else {
firstPossibleSpliceTime = lastParsedTimestampUs + 1;
}
ConcurrentLinkedQueue<Sample> nextInternalQueue = nextQueue.internalQueue;
Sample nextQueueSample = nextInternalQueue.peek();
while (nextQueueSample != null
&& (nextQueueSample.timeUs < firstPossibleSpliceTime || !nextQueueSample.isKeyframe)) {
// Discard samples from the next queue for as long as they are before the earliest possible
// splice time, or not keyframes.
nextQueue.internalQueue.remove();
nextQueueSample = nextQueue.internalQueue.peek();
}
if (nextQueueSample != null) {
// We've found a keyframe in the next queue that can serve as the splice point. Set the
// splice point now.
spliceOutTimeUs = nextQueueSample.timeUs;
return true;
}
return false;
}
/**
* Obtains a Sample object to use.
*
* @return The sample.
*/
protected Sample getSample() {
return samplePool.get();
} }
/** /**
...@@ -519,33 +643,22 @@ public final class TsExtractor { ...@@ -519,33 +643,22 @@ public final class TsExtractor {
* @param buffer The buffer to read sample data. * @param buffer The buffer to read sample data.
* @param sampleSize The size of the sample data. * @param sampleSize The size of the sample data.
* @param sampleTimeUs The sample time stamp. * @param sampleTimeUs The sample time stamp.
* @param isKeyframe True if the sample is a keyframe. False otherwise.
*/ */
protected void addSample(BitArray buffer, int sampleSize, long sampleTimeUs, int flags) { protected void addSample(BitArray buffer, int sampleSize, long sampleTimeUs,
Sample sample = samplePool.get(); boolean isKeyframe) {
Sample sample = getSample();
addToSample(sample, buffer, sampleSize); addToSample(sample, buffer, sampleSize);
sample.flags = flags; sample.isKeyframe = isKeyframe;
sample.timeUs = sampleTimeUs; sample.timeUs = sampleTimeUs;
addSample(sample); addSample(sample);
} }
@SuppressLint("InlinedApi")
protected void addSample(Sample sample) { protected void addSample(Sample sample) {
boolean isKeyframe = (sample.flags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0;
if (isKeyframe) {
if (!foundFirstKeyframe) {
foundFirstKeyframe = true;
}
if (discardFromNextKeyframes) {
foundLastKeyframe = true;
}
}
adjustTimestamp(sample); adjustTimestamp(sample);
if (foundFirstKeyframe && !foundLastKeyframe) { lastParsedTimestampUs = sample.timeUs;
largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sample.timeUs); largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sample.timeUs);
queue.add(sample); internalQueue.add(sample);
} else {
samplePool.recycle(sample);
}
} }
protected void addToSample(Sample sample, BitArray buffer, int size) { protected void addToSample(Sample sample, BitArray buffer, int size) {
...@@ -571,6 +684,10 @@ public final class TsExtractor { ...@@ -571,6 +684,10 @@ public final class TsExtractor {
*/ */
private abstract class PesPayloadReader extends SampleQueue { private abstract class PesPayloadReader extends SampleQueue {
protected PesPayloadReader(SamplePool samplePool) {
super(samplePool);
}
public abstract void read(BitArray pesBuffer, int pesPayloadSize, long pesTimeUs); public abstract void read(BitArray pesBuffer, int pesPayloadSize, long pesTimeUs);
} }
...@@ -589,7 +706,8 @@ public final class TsExtractor { ...@@ -589,7 +706,8 @@ public final class TsExtractor {
// Used to store uncompleted sample data. // Used to store uncompleted sample data.
private Sample currentSample; private Sample currentSample;
public H264Reader(SeiReader seiReader) { public H264Reader(SamplePool samplePool, SeiReader seiReader) {
super(samplePool);
this.seiReader = seiReader; this.seiReader = seiReader;
} }
...@@ -597,7 +715,7 @@ public final class TsExtractor { ...@@ -597,7 +715,7 @@ public final class TsExtractor {
public void clear() { public void clear() {
super.clear(); super.clear();
if (currentSample != null) { if (currentSample != null) {
samplePool.recycle(currentSample); recycle(currentSample);
currentSample = null; currentSample = null;
} }
} }
...@@ -613,13 +731,13 @@ public final class TsExtractor { ...@@ -613,13 +731,13 @@ public final class TsExtractor {
// Single PES packet should contain only one new H.264 frame. // Single PES packet should contain only one new H.264 frame.
if (currentSample != null) { if (currentSample != null) {
if (!hasMediaFormat() && (currentSample.flags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) { if (!hasMediaFormat() && currentSample.isKeyframe) {
parseMediaFormat(currentSample); parseMediaFormat(currentSample);
} }
seiReader.read(currentSample.data, currentSample.size, pesTimeUs); seiReader.read(currentSample.data, currentSample.size, pesTimeUs);
addSample(currentSample); addSample(currentSample);
} }
currentSample = samplePool.get(); currentSample = getSample();
pesPayloadSize -= readOneH264Frame(pesBuffer, false); pesPayloadSize -= readOneH264Frame(pesBuffer, false);
currentSample.timeUs = pesTimeUs; currentSample.timeUs = pesTimeUs;
...@@ -635,7 +753,7 @@ public final class TsExtractor { ...@@ -635,7 +753,7 @@ public final class TsExtractor {
if (currentSample != null) { if (currentSample != null) {
int idrStart = pesBuffer.findNextNalUnit(NAL_UNIT_TYPE_IDR, offset); int idrStart = pesBuffer.findNextNalUnit(NAL_UNIT_TYPE_IDR, offset);
if (idrStart < audStart) { if (idrStart < audStart) {
currentSample.flags = MediaExtractor.SAMPLE_FLAG_SYNC; currentSample.isKeyframe = true;
} }
addToSample(currentSample, pesBuffer, audStart); addToSample(currentSample, pesBuffer, audStart);
} else { } else {
...@@ -790,7 +908,8 @@ public final class TsExtractor { ...@@ -790,7 +908,8 @@ public final class TsExtractor {
private final BitArray seiBuffer; private final BitArray seiBuffer;
public SeiReader() { public SeiReader(SamplePool samplePool) {
super(samplePool);
setMediaFormat(MediaFormat.createEia608Format()); setMediaFormat(MediaFormat.createEia608Format());
seiBuffer = new BitArray(); seiBuffer = new BitArray();
} }
...@@ -806,7 +925,7 @@ public final class TsExtractor { ...@@ -806,7 +925,7 @@ public final class TsExtractor {
seiBuffer.skipBytes(seiStart + 4); seiBuffer.skipBytes(seiStart + 4);
int ccDataSize = Eia608Parser.parseHeader(seiBuffer); int ccDataSize = Eia608Parser.parseHeader(seiBuffer);
if (ccDataSize > 0) { if (ccDataSize > 0) {
addSample(seiBuffer, ccDataSize, pesTimeUs, MediaExtractor.SAMPLE_FLAG_SYNC); addSample(seiBuffer, ccDataSize, pesTimeUs, true);
} }
} }
} }
...@@ -821,7 +940,8 @@ public final class TsExtractor { ...@@ -821,7 +940,8 @@ public final class TsExtractor {
private final BitArray adtsBuffer; private final BitArray adtsBuffer;
private long timeUs; private long timeUs;
public AdtsReader() { public AdtsReader(SamplePool samplePool) {
super(samplePool);
adtsBuffer = new BitArray(); adtsBuffer = new BitArray();
} }
...@@ -904,7 +1024,7 @@ public final class TsExtractor { ...@@ -904,7 +1024,7 @@ public final class TsExtractor {
return false; return false;
} }
addSample(adtsBuffer, frameSize, timeUs, MediaExtractor.SAMPLE_FLAG_SYNC); addSample(adtsBuffer, frameSize, timeUs, true);
return true; return true;
} }
...@@ -921,14 +1041,15 @@ public final class TsExtractor { ...@@ -921,14 +1041,15 @@ public final class TsExtractor {
*/ */
private class Id3Reader extends PesPayloadReader { private class Id3Reader extends PesPayloadReader {
public Id3Reader() { public Id3Reader(SamplePool samplePool) {
super(samplePool);
setMediaFormat(MediaFormat.createId3Format()); setMediaFormat(MediaFormat.createId3Format());
} }
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
@Override @Override
public void read(BitArray pesBuffer, int pesPayloadSize, long pesTimeUs) { public void read(BitArray pesBuffer, int pesPayloadSize, long pesTimeUs) {
addSample(pesBuffer, pesPayloadSize, pesTimeUs, MediaExtractor.SAMPLE_FLAG_SYNC); addSample(pesBuffer, pesPayloadSize, pesTimeUs, true);
} }
} }
...@@ -968,7 +1089,7 @@ public final class TsExtractor { ...@@ -968,7 +1089,7 @@ public final class TsExtractor {
public Sample nextInPool; public Sample nextInPool;
public byte[] data; public byte[] data;
public int flags; public boolean isKeyframe;
public int size; public int size;
public long timeUs; public long timeUs;
...@@ -983,7 +1104,7 @@ public final class TsExtractor { ...@@ -983,7 +1104,7 @@ public final class TsExtractor {
} }
public void reset() { public void reset() {
flags = 0; isKeyframe = false;
size = 0; size = 0;
timeUs = 0; timeUs = 0;
} }
......
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