Move EIA reordering back to the renderer (sorry for churn).
Reordering in the extractor isn't going to work well with the optimizations I'm making there. This change moves sorting back to the renderer, although keeps all of the renderer simplifications. It's basically just moving where the sort happens from one place to another.
This commit is contained in:
parent
f7fb4d4c35
commit
784431f3e0
@ -17,33 +17,13 @@ package com.google.android.exoplayer.hls.parser;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts individual samples from continuous byte stream, preserving original order.
|
* Extracts individual samples from continuous byte stream, preserving original order.
|
||||||
*/
|
*/
|
||||||
/* package */ abstract class PesPayloadReader extends SampleQueue {
|
/* package */ abstract class PesPayloadReader extends SampleQueue {
|
||||||
|
|
||||||
private final ConcurrentLinkedQueue<Sample> internalQueue;
|
|
||||||
|
|
||||||
protected PesPayloadReader(SamplePool samplePool) {
|
protected PesPayloadReader(SamplePool samplePool) {
|
||||||
super(samplePool);
|
super(samplePool);
|
||||||
internalQueue = new ConcurrentLinkedQueue<Sample>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final Sample internalPeekSample() {
|
|
||||||
return internalQueue.peek();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final Sample internalPollSample() {
|
|
||||||
return internalQueue.poll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final void internalQueueSample(Sample sample) {
|
|
||||||
internalQueue.add(sample);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,9 +18,12 @@ package com.google.android.exoplayer.hls.parser;
|
|||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
/* package */ abstract class SampleQueue {
|
/* package */ abstract class SampleQueue {
|
||||||
|
|
||||||
private final SamplePool samplePool;
|
private final SamplePool samplePool;
|
||||||
|
private final ConcurrentLinkedQueue<Sample> internalQueue;
|
||||||
|
|
||||||
// Accessed only by the consuming thread.
|
// Accessed only by the consuming thread.
|
||||||
private boolean needKeyframe;
|
private boolean needKeyframe;
|
||||||
@ -33,6 +36,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
|
|
||||||
protected SampleQueue(SamplePool samplePool) {
|
protected SampleQueue(SamplePool samplePool) {
|
||||||
this.samplePool = samplePool;
|
this.samplePool = samplePool;
|
||||||
|
internalQueue = new ConcurrentLinkedQueue<Sample>();
|
||||||
needKeyframe = true;
|
needKeyframe = true;
|
||||||
lastReadTimeUs = Long.MIN_VALUE;
|
lastReadTimeUs = Long.MIN_VALUE;
|
||||||
spliceOutTimeUs = Long.MIN_VALUE;
|
spliceOutTimeUs = Long.MIN_VALUE;
|
||||||
@ -66,7 +70,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
public Sample poll() {
|
public Sample poll() {
|
||||||
Sample head = peek();
|
Sample head = peek();
|
||||||
if (head != null) {
|
if (head != null) {
|
||||||
internalPollSample();
|
internalQueue.poll();
|
||||||
needKeyframe = false;
|
needKeyframe = false;
|
||||||
lastReadTimeUs = head.timeUs;
|
lastReadTimeUs = head.timeUs;
|
||||||
}
|
}
|
||||||
@ -79,13 +83,13 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
* @return The next sample from the queue, or null if a sample isn't available.
|
* @return The next sample from the queue, or null if a sample isn't available.
|
||||||
*/
|
*/
|
||||||
public Sample peek() {
|
public Sample peek() {
|
||||||
Sample head = internalPeekSample();
|
Sample head = internalQueue.peek();
|
||||||
if (needKeyframe) {
|
if (needKeyframe) {
|
||||||
// Peeking discard of samples until we find a keyframe or run out of available samples.
|
// Peeking discard of samples until we find a keyframe or run out of available samples.
|
||||||
while (head != null && !head.isKeyframe) {
|
while (head != null && !head.isKeyframe) {
|
||||||
recycle(head);
|
recycle(head);
|
||||||
internalPollSample();
|
internalQueue.poll();
|
||||||
head = internalPeekSample();
|
head = internalQueue.peek();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (head == null) {
|
if (head == null) {
|
||||||
@ -94,7 +98,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
if (spliceOutTimeUs != Long.MIN_VALUE && head.timeUs >= spliceOutTimeUs) {
|
if (spliceOutTimeUs != Long.MIN_VALUE && head.timeUs >= spliceOutTimeUs) {
|
||||||
// The sample is later than the time this queue is spliced out.
|
// The sample is later than the time this queue is spliced out.
|
||||||
recycle(head);
|
recycle(head);
|
||||||
internalPollSample();
|
internalQueue.poll();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return head;
|
return head;
|
||||||
@ -109,8 +113,8 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
Sample head = peek();
|
Sample head = peek();
|
||||||
while (head != null && head.timeUs < timeUs) {
|
while (head != null && head.timeUs < timeUs) {
|
||||||
recycle(head);
|
recycle(head);
|
||||||
internalPollSample();
|
internalQueue.poll();
|
||||||
head = internalPeekSample();
|
head = internalQueue.peek();
|
||||||
// We're discarding at least one sample, so any subsequent read will need to start at
|
// We're discarding at least one sample, so any subsequent read will need to start at
|
||||||
// a keyframe.
|
// a keyframe.
|
||||||
needKeyframe = true;
|
needKeyframe = true;
|
||||||
@ -122,10 +126,10 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
* Clears the queue.
|
* Clears the queue.
|
||||||
*/
|
*/
|
||||||
public void release() {
|
public void release() {
|
||||||
Sample toRecycle = internalPollSample();
|
Sample toRecycle = internalQueue.poll();
|
||||||
while (toRecycle != null) {
|
while (toRecycle != null) {
|
||||||
recycle(toRecycle);
|
recycle(toRecycle);
|
||||||
toRecycle = internalPollSample();
|
toRecycle = internalQueue.poll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,19 +154,19 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
long firstPossibleSpliceTime;
|
long firstPossibleSpliceTime;
|
||||||
Sample nextSample = internalPeekSample();
|
Sample nextSample = internalQueue.peek();
|
||||||
if (nextSample != null) {
|
if (nextSample != null) {
|
||||||
firstPossibleSpliceTime = nextSample.timeUs;
|
firstPossibleSpliceTime = nextSample.timeUs;
|
||||||
} else {
|
} else {
|
||||||
firstPossibleSpliceTime = lastReadTimeUs + 1;
|
firstPossibleSpliceTime = lastReadTimeUs + 1;
|
||||||
}
|
}
|
||||||
Sample nextQueueSample = nextQueue.internalPeekSample();
|
Sample nextQueueSample = nextQueue.internalQueue.peek();
|
||||||
while (nextQueueSample != null
|
while (nextQueueSample != null
|
||||||
&& (nextQueueSample.timeUs < firstPossibleSpliceTime || !nextQueueSample.isKeyframe)) {
|
&& (nextQueueSample.timeUs < firstPossibleSpliceTime || !nextQueueSample.isKeyframe)) {
|
||||||
// Discard samples from the next queue for as long as they are before the earliest possible
|
// Discard samples from the next queue for as long as they are before the earliest possible
|
||||||
// splice time, or not keyframes.
|
// splice time, or not keyframes.
|
||||||
nextQueue.internalPollSample();
|
nextQueue.internalQueue.poll();
|
||||||
nextQueueSample = nextQueue.internalPeekSample();
|
nextQueueSample = nextQueue.internalQueue.peek();
|
||||||
}
|
}
|
||||||
if (nextQueueSample != null) {
|
if (nextQueueSample != null) {
|
||||||
// We've found a keyframe in the next queue that can serve as the splice point. Set the
|
// We've found a keyframe in the next queue that can serve as the splice point. Set the
|
||||||
@ -203,7 +207,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
|
|
||||||
protected void addSample(Sample sample) {
|
protected void addSample(Sample sample) {
|
||||||
largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sample.timeUs);
|
largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sample.timeUs);
|
||||||
internalQueueSample(sample);
|
internalQueue.add(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addToSample(Sample sample, ParsableByteArray buffer, int size) {
|
protected void addToSample(Sample sample, ParsableByteArray buffer, int size) {
|
||||||
@ -214,8 +218,4 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
sample.size += size;
|
sample.size += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Sample internalPeekSample();
|
|
||||||
protected abstract Sample internalPollSample();
|
|
||||||
protected abstract void internalQueueSample(Sample sample);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,28 +22,23 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a SEI data from H.264 frames and extracts samples with closed captions data.
|
* Parses a SEI data from H.264 frames and extracts samples with closed captions data.
|
||||||
*
|
*
|
||||||
* TODO: Technically, we shouldn't allow a sample to be read from the queue until we're sure that
|
* TODO: Technically, we shouldn't allow a sample to be read from the queue until we're sure that
|
||||||
* a sample with an earlier timestamp won't be added to it.
|
* a sample with an earlier timestamp won't be added to it.
|
||||||
*/
|
*/
|
||||||
/* package */ class SeiReader extends SampleQueue implements Comparator<Sample> {
|
/* package */ class SeiReader extends SampleQueue {
|
||||||
|
|
||||||
// SEI data, used for Closed Captions.
|
// SEI data, used for Closed Captions.
|
||||||
private static final int NAL_UNIT_TYPE_SEI = 6;
|
private static final int NAL_UNIT_TYPE_SEI = 6;
|
||||||
|
|
||||||
private final ParsableByteArray seiBuffer;
|
private final ParsableByteArray seiBuffer;
|
||||||
private final TreeSet<Sample> internalQueue;
|
|
||||||
|
|
||||||
public SeiReader(SamplePool samplePool) {
|
public SeiReader(SamplePool samplePool) {
|
||||||
super(samplePool);
|
super(samplePool);
|
||||||
setMediaFormat(MediaFormat.createEia608Format());
|
setMediaFormat(MediaFormat.createEia608Format());
|
||||||
seiBuffer = new ParsableByteArray();
|
seiBuffer = new ParsableByteArray();
|
||||||
internalQueue = new TreeSet<Sample>(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
@ -63,25 +58,4 @@ import java.util.TreeSet;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(Sample first, Sample second) {
|
|
||||||
// Note - We don't expect samples to have identical timestamps.
|
|
||||||
return first.timeUs <= second.timeUs ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected synchronized Sample internalPeekSample() {
|
|
||||||
return internalQueue.isEmpty() ? null : internalQueue.first();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected synchronized Sample internalPollSample() {
|
|
||||||
return internalQueue.pollFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected synchronized void internalQueueSample(Sample sample) {
|
|
||||||
internalQueue.add(sample);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ package com.google.android.exoplayer.text.eia608;
|
|||||||
/**
|
/**
|
||||||
* A Closed Caption that contains textual data associated with time indices.
|
* A Closed Caption that contains textual data associated with time indices.
|
||||||
*/
|
*/
|
||||||
/* package */ abstract class ClosedCaption implements Comparable<ClosedCaption> {
|
/* package */ abstract class ClosedCaption {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies closed captions with control characters.
|
* Identifies closed captions with control characters.
|
||||||
@ -33,23 +33,9 @@ package com.google.android.exoplayer.text.eia608;
|
|||||||
* The type of the closed caption data.
|
* The type of the closed caption data.
|
||||||
*/
|
*/
|
||||||
public final int type;
|
public final int type;
|
||||||
/**
|
|
||||||
* Timestamp associated with the closed caption.
|
|
||||||
*/
|
|
||||||
public final long timeUs;
|
|
||||||
|
|
||||||
protected ClosedCaption(int type, long timeUs) {
|
protected ClosedCaption(int type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.timeUs = timeUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(ClosedCaption another) {
|
|
||||||
long delta = this.timeUs - another.timeUs;
|
|
||||||
if (delta == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return delta > 0 ? 1 : -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -70,8 +70,8 @@ package com.google.android.exoplayer.text.eia608;
|
|||||||
public final byte cc1;
|
public final byte cc1;
|
||||||
public final byte cc2;
|
public final byte cc2;
|
||||||
|
|
||||||
protected ClosedCaptionCtrl(byte cc1, byte cc2, long timeUs) {
|
protected ClosedCaptionCtrl(byte cc1, byte cc2) {
|
||||||
super(ClosedCaption.TYPE_CTRL, timeUs);
|
super(ClosedCaption.TYPE_CTRL);
|
||||||
this.cc1 = cc1;
|
this.cc1 = cc1;
|
||||||
this.cc2 = cc2;
|
this.cc2 = cc2;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.text.eia608;
|
||||||
|
|
||||||
|
/* package */ final class ClosedCaptionList implements Comparable<ClosedCaptionList> {
|
||||||
|
|
||||||
|
public final long timeUs;
|
||||||
|
public final boolean decodeOnly;
|
||||||
|
public final ClosedCaption[] captions;
|
||||||
|
|
||||||
|
public ClosedCaptionList(long timeUs, boolean decodeOnly, ClosedCaption[] captions) {
|
||||||
|
this.timeUs = timeUs;
|
||||||
|
this.decodeOnly = decodeOnly;
|
||||||
|
this.captions = captions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(ClosedCaptionList other) {
|
||||||
|
long delta = timeUs - other.timeUs;
|
||||||
|
if (delta == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return delta > 0 ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,8 +19,8 @@ package com.google.android.exoplayer.text.eia608;
|
|||||||
|
|
||||||
public final String text;
|
public final String text;
|
||||||
|
|
||||||
public ClosedCaptionText(String text, long timeUs) {
|
public ClosedCaptionText(String text) {
|
||||||
super(ClosedCaption.TYPE_TEXT, timeUs);
|
super(ClosedCaption.TYPE_TEXT);
|
||||||
this.text = text;
|
this.text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,11 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.text.eia608;
|
package com.google.android.exoplayer.text.eia608;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.ParsableBitArray;
|
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Facilitates the extraction and parsing of EIA-608 (a.k.a. "line 21 captions" and "CEA-608")
|
* Facilitates the extraction and parsing of EIA-608 (a.k.a. "line 21 captions" and "CEA-608")
|
||||||
@ -83,23 +84,26 @@ public class Eia608Parser {
|
|||||||
|
|
||||||
private final ParsableBitArray seiBuffer;
|
private final ParsableBitArray seiBuffer;
|
||||||
private final StringBuilder stringBuilder;
|
private final StringBuilder stringBuilder;
|
||||||
|
private final ArrayList<ClosedCaption> captions;
|
||||||
|
|
||||||
/* package */ Eia608Parser() {
|
/* package */ Eia608Parser() {
|
||||||
seiBuffer = new ParsableBitArray();
|
seiBuffer = new ParsableBitArray();
|
||||||
stringBuilder = new StringBuilder();
|
stringBuilder = new StringBuilder();
|
||||||
|
captions = new ArrayList<ClosedCaption>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ boolean canParse(String mimeType) {
|
/* package */ boolean canParse(String mimeType) {
|
||||||
return mimeType.equals(MimeTypes.APPLICATION_EIA608);
|
return mimeType.equals(MimeTypes.APPLICATION_EIA608);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ void parse(byte[] data, int size, long timeUs, List<ClosedCaption> out) {
|
/* package */ ClosedCaptionList parse(SampleHolder sampleHolder) {
|
||||||
if (size <= 0) {
|
if (sampleHolder.size <= 0) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
captions.clear();
|
||||||
stringBuilder.setLength(0);
|
stringBuilder.setLength(0);
|
||||||
seiBuffer.reset(data);
|
seiBuffer.reset(sampleHolder.data.array());
|
||||||
seiBuffer.skipBits(3); // reserved + process_cc_data_flag + zero_bit
|
seiBuffer.skipBits(3); // reserved + process_cc_data_flag + zero_bit
|
||||||
int ccCount = seiBuffer.readBits(5);
|
int ccCount = seiBuffer.readBits(5);
|
||||||
seiBuffer.skipBits(8);
|
seiBuffer.skipBits(8);
|
||||||
@ -135,10 +139,10 @@ public class Eia608Parser {
|
|||||||
// Control character.
|
// Control character.
|
||||||
if (ccData1 < 0x20) {
|
if (ccData1 < 0x20) {
|
||||||
if (stringBuilder.length() > 0) {
|
if (stringBuilder.length() > 0) {
|
||||||
out.add(new ClosedCaptionText(stringBuilder.toString(), timeUs));
|
captions.add(new ClosedCaptionText(stringBuilder.toString()));
|
||||||
stringBuilder.setLength(0);
|
stringBuilder.setLength(0);
|
||||||
}
|
}
|
||||||
out.add(new ClosedCaptionCtrl(ccData1, ccData2, timeUs));
|
captions.add(new ClosedCaptionCtrl(ccData1, ccData2));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,8 +154,16 @@ public class Eia608Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stringBuilder.length() > 0) {
|
if (stringBuilder.length() > 0) {
|
||||||
out.add(new ClosedCaptionText(stringBuilder.toString(), timeUs));
|
captions.add(new ClosedCaptionText(stringBuilder.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (captions.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClosedCaption[] captionArray = new ClosedCaption[captions.size()];
|
||||||
|
captions.toArray(captionArray);
|
||||||
|
return new ClosedCaptionList(sampleHolder.timeUs, sampleHolder.decodeOnly, captionArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static char getChar(byte ccData) {
|
private static char getChar(byte ccData) {
|
||||||
|
@ -31,8 +31,7 @@ import android.os.Looper;
|
|||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.TreeSet;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link TrackRenderer} for EIA-608 closed captions in a media stream.
|
* A {@link TrackRenderer} for EIA-608 closed captions in a media stream.
|
||||||
@ -48,6 +47,8 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
|||||||
|
|
||||||
// The default number of rows to display in roll-up captions mode.
|
// The default number of rows to display in roll-up captions mode.
|
||||||
private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4;
|
private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4;
|
||||||
|
// The maximum duration that captions are parsed ahead of the current position.
|
||||||
|
private static final int MAX_SAMPLE_READAHEAD_US = 5000000;
|
||||||
|
|
||||||
private final SampleSource source;
|
private final SampleSource source;
|
||||||
private final Eia608Parser eia608Parser;
|
private final Eia608Parser eia608Parser;
|
||||||
@ -56,7 +57,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
|||||||
private final MediaFormatHolder formatHolder;
|
private final MediaFormatHolder formatHolder;
|
||||||
private final SampleHolder sampleHolder;
|
private final SampleHolder sampleHolder;
|
||||||
private final StringBuilder captionStringBuilder;
|
private final StringBuilder captionStringBuilder;
|
||||||
private final List<ClosedCaption> captionBuffer;
|
private final TreeSet<ClosedCaptionList> pendingCaptionLists;
|
||||||
|
|
||||||
private int trackIndex;
|
private int trackIndex;
|
||||||
private long currentPositionUs;
|
private long currentPositionUs;
|
||||||
@ -85,7 +86,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
|||||||
formatHolder = new MediaFormatHolder();
|
formatHolder = new MediaFormatHolder();
|
||||||
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
|
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
|
||||||
captionStringBuilder = new StringBuilder();
|
captionStringBuilder = new StringBuilder();
|
||||||
captionBuffer = new ArrayList<ClosedCaption>();
|
pendingCaptionLists = new TreeSet<ClosedCaptionList>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -122,6 +123,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
|||||||
private void seekToInternal(long positionUs) {
|
private void seekToInternal(long positionUs) {
|
||||||
currentPositionUs = positionUs;
|
currentPositionUs = positionUs;
|
||||||
inputStreamEnded = false;
|
inputStreamEnded = false;
|
||||||
|
pendingCaptionLists.clear();
|
||||||
clearPendingSample();
|
clearPendingSample();
|
||||||
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
|
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
|
||||||
setCaptionMode(CC_MODE_UNKNOWN);
|
setCaptionMode(CC_MODE_UNKNOWN);
|
||||||
@ -138,10 +140,17 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
|||||||
throw new ExoPlaybackException(e);
|
throw new ExoPlaybackException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inputStreamEnded && !isSamplePending()) {
|
if (isSamplePending()) {
|
||||||
|
maybeParsePendingSample();
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = inputStreamEnded ? SampleSource.END_OF_STREAM : SampleSource.SAMPLE_READ;
|
||||||
|
while (!isSamplePending() && result == SampleSource.SAMPLE_READ) {
|
||||||
try {
|
try {
|
||||||
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
|
result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
|
||||||
if (result == SampleSource.END_OF_STREAM) {
|
if (result == SampleSource.SAMPLE_READ) {
|
||||||
|
maybeParsePendingSample();
|
||||||
|
} else if (result == SampleSource.END_OF_STREAM) {
|
||||||
inputStreamEnded = true;
|
inputStreamEnded = true;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -149,17 +158,18 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSamplePending() && sampleHolder.timeUs <= currentPositionUs) {
|
while (!pendingCaptionLists.isEmpty()) {
|
||||||
// Parse the pending sample.
|
if (pendingCaptionLists.first().timeUs > currentPositionUs) {
|
||||||
eia608Parser.parse(sampleHolder.data.array(), sampleHolder.size, sampleHolder.timeUs,
|
// We're too early to render any of the pending caption lists.
|
||||||
captionBuffer);
|
return;
|
||||||
// Consume parsed captions.
|
}
|
||||||
consumeCaptionBuffer();
|
// Remove and consume the next caption list.
|
||||||
// Update the renderer, unless the sample was marked for decoding only.
|
ClosedCaptionList nextCaptionList = pendingCaptionLists.pollFirst();
|
||||||
if (!sampleHolder.decodeOnly) {
|
consumeCaptionList(nextCaptionList);
|
||||||
|
// Update the renderer, unless the caption list was marked for decoding only.
|
||||||
|
if (!nextCaptionList.decodeOnly) {
|
||||||
invokeRenderer(caption);
|
invokeRenderer(caption);
|
||||||
}
|
}
|
||||||
clearPendingSample();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,14 +231,26 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
|||||||
textRenderer.onText(text);
|
textRenderer.onText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void consumeCaptionBuffer() {
|
private void maybeParsePendingSample() {
|
||||||
int captionBufferSize = captionBuffer.size();
|
if (sampleHolder.timeUs > currentPositionUs + MAX_SAMPLE_READAHEAD_US) {
|
||||||
|
// We're too early to parse the sample.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ClosedCaptionList holder = eia608Parser.parse(sampleHolder);
|
||||||
|
clearPendingSample();
|
||||||
|
if (holder != null) {
|
||||||
|
pendingCaptionLists.add(holder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consumeCaptionList(ClosedCaptionList captionList) {
|
||||||
|
int captionBufferSize = captionList.captions.length;
|
||||||
if (captionBufferSize == 0) {
|
if (captionBufferSize == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < captionBufferSize; i++) {
|
for (int i = 0; i < captionBufferSize; i++) {
|
||||||
ClosedCaption caption = captionBuffer.get(i);
|
ClosedCaption caption = captionList.captions[i];
|
||||||
if (caption.type == ClosedCaption.TYPE_CTRL) {
|
if (caption.type == ClosedCaption.TYPE_CTRL) {
|
||||||
ClosedCaptionCtrl captionCtrl = (ClosedCaptionCtrl) caption;
|
ClosedCaptionCtrl captionCtrl = (ClosedCaptionCtrl) caption;
|
||||||
if (captionCtrl.isMiscCode()) {
|
if (captionCtrl.isMiscCode()) {
|
||||||
@ -240,7 +262,6 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
|||||||
handleText((ClosedCaptionText) caption);
|
handleText((ClosedCaptionText) caption);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
captionBuffer.clear();
|
|
||||||
|
|
||||||
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
|
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
|
||||||
caption = getDisplayCaption();
|
caption = getDisplayCaption();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user