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 java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/**
|
||||
* Extracts individual samples from continuous byte stream, preserving original order.
|
||||
*/
|
||||
/* package */ abstract class PesPayloadReader extends SampleQueue {
|
||||
|
||||
private final ConcurrentLinkedQueue<Sample> internalQueue;
|
||||
|
||||
protected PesPayloadReader(SamplePool 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.util.ParsableByteArray;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/* package */ abstract class SampleQueue {
|
||||
|
||||
private final SamplePool samplePool;
|
||||
private final ConcurrentLinkedQueue<Sample> internalQueue;
|
||||
|
||||
// Accessed only by the consuming thread.
|
||||
private boolean needKeyframe;
|
||||
@ -33,6 +36,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
|
||||
protected SampleQueue(SamplePool samplePool) {
|
||||
this.samplePool = samplePool;
|
||||
internalQueue = new ConcurrentLinkedQueue<Sample>();
|
||||
needKeyframe = true;
|
||||
lastReadTimeUs = Long.MIN_VALUE;
|
||||
spliceOutTimeUs = Long.MIN_VALUE;
|
||||
@ -66,7 +70,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
public Sample poll() {
|
||||
Sample head = peek();
|
||||
if (head != null) {
|
||||
internalPollSample();
|
||||
internalQueue.poll();
|
||||
needKeyframe = false;
|
||||
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.
|
||||
*/
|
||||
public Sample peek() {
|
||||
Sample head = internalPeekSample();
|
||||
Sample head = internalQueue.peek();
|
||||
if (needKeyframe) {
|
||||
// Peeking discard of samples until we find a keyframe or run out of available samples.
|
||||
while (head != null && !head.isKeyframe) {
|
||||
recycle(head);
|
||||
internalPollSample();
|
||||
head = internalPeekSample();
|
||||
internalQueue.poll();
|
||||
head = internalQueue.peek();
|
||||
}
|
||||
}
|
||||
if (head == null) {
|
||||
@ -94,7 +98,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
if (spliceOutTimeUs != Long.MIN_VALUE && head.timeUs >= spliceOutTimeUs) {
|
||||
// The sample is later than the time this queue is spliced out.
|
||||
recycle(head);
|
||||
internalPollSample();
|
||||
internalQueue.poll();
|
||||
return null;
|
||||
}
|
||||
return head;
|
||||
@ -109,8 +113,8 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
Sample head = peek();
|
||||
while (head != null && head.timeUs < timeUs) {
|
||||
recycle(head);
|
||||
internalPollSample();
|
||||
head = internalPeekSample();
|
||||
internalQueue.poll();
|
||||
head = internalQueue.peek();
|
||||
// We're discarding at least one sample, so any subsequent read will need to start at
|
||||
// a keyframe.
|
||||
needKeyframe = true;
|
||||
@ -122,10 +126,10 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
* Clears the queue.
|
||||
*/
|
||||
public void release() {
|
||||
Sample toRecycle = internalPollSample();
|
||||
Sample toRecycle = internalQueue.poll();
|
||||
while (toRecycle != null) {
|
||||
recycle(toRecycle);
|
||||
toRecycle = internalPollSample();
|
||||
toRecycle = internalQueue.poll();
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,19 +154,19 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
return true;
|
||||
}
|
||||
long firstPossibleSpliceTime;
|
||||
Sample nextSample = internalPeekSample();
|
||||
Sample nextSample = internalQueue.peek();
|
||||
if (nextSample != null) {
|
||||
firstPossibleSpliceTime = nextSample.timeUs;
|
||||
} else {
|
||||
firstPossibleSpliceTime = lastReadTimeUs + 1;
|
||||
}
|
||||
Sample nextQueueSample = nextQueue.internalPeekSample();
|
||||
Sample nextQueueSample = nextQueue.internalQueue.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.internalPollSample();
|
||||
nextQueueSample = nextQueue.internalPeekSample();
|
||||
nextQueue.internalQueue.poll();
|
||||
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
|
||||
@ -203,7 +207,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
|
||||
protected void addSample(Sample sample) {
|
||||
largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sample.timeUs);
|
||||
internalQueueSample(sample);
|
||||
internalQueue.add(sample);
|
||||
}
|
||||
|
||||
protected void addToSample(Sample sample, ParsableByteArray buffer, int size) {
|
||||
@ -214,8 +218,4 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
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 java.util.Comparator;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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.
|
||||
private static final int NAL_UNIT_TYPE_SEI = 6;
|
||||
|
||||
private final ParsableByteArray seiBuffer;
|
||||
private final TreeSet<Sample> internalQueue;
|
||||
|
||||
public SeiReader(SamplePool samplePool) {
|
||||
super(samplePool);
|
||||
setMediaFormat(MediaFormat.createEia608Format());
|
||||
seiBuffer = new ParsableByteArray();
|
||||
internalQueue = new TreeSet<Sample>(this);
|
||||
}
|
||||
|
||||
@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.
|
||||
*/
|
||||
/* package */ abstract class ClosedCaption implements Comparable<ClosedCaption> {
|
||||
/* package */ abstract class ClosedCaption {
|
||||
|
||||
/**
|
||||
* Identifies closed captions with control characters.
|
||||
@ -33,23 +33,9 @@ package com.google.android.exoplayer.text.eia608;
|
||||
* The type of the closed caption data.
|
||||
*/
|
||||
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.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 cc2;
|
||||
|
||||
protected ClosedCaptionCtrl(byte cc1, byte cc2, long timeUs) {
|
||||
super(ClosedCaption.TYPE_CTRL, timeUs);
|
||||
protected ClosedCaptionCtrl(byte cc1, byte cc2) {
|
||||
super(ClosedCaption.TYPE_CTRL);
|
||||
this.cc1 = cc1;
|
||||
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 ClosedCaptionText(String text, long timeUs) {
|
||||
super(ClosedCaption.TYPE_TEXT, timeUs);
|
||||
public ClosedCaptionText(String text) {
|
||||
super(ClosedCaption.TYPE_TEXT);
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
|
@ -15,11 +15,12 @@
|
||||
*/
|
||||
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.ParsableBitArray;
|
||||
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")
|
||||
@ -83,23 +84,26 @@ public class Eia608Parser {
|
||||
|
||||
private final ParsableBitArray seiBuffer;
|
||||
private final StringBuilder stringBuilder;
|
||||
private final ArrayList<ClosedCaption> captions;
|
||||
|
||||
/* package */ Eia608Parser() {
|
||||
seiBuffer = new ParsableBitArray();
|
||||
stringBuilder = new StringBuilder();
|
||||
captions = new ArrayList<ClosedCaption>();
|
||||
}
|
||||
|
||||
/* package */ boolean canParse(String mimeType) {
|
||||
return mimeType.equals(MimeTypes.APPLICATION_EIA608);
|
||||
}
|
||||
|
||||
/* package */ void parse(byte[] data, int size, long timeUs, List<ClosedCaption> out) {
|
||||
if (size <= 0) {
|
||||
return;
|
||||
/* package */ ClosedCaptionList parse(SampleHolder sampleHolder) {
|
||||
if (sampleHolder.size <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
captions.clear();
|
||||
stringBuilder.setLength(0);
|
||||
seiBuffer.reset(data);
|
||||
seiBuffer.reset(sampleHolder.data.array());
|
||||
seiBuffer.skipBits(3); // reserved + process_cc_data_flag + zero_bit
|
||||
int ccCount = seiBuffer.readBits(5);
|
||||
seiBuffer.skipBits(8);
|
||||
@ -135,10 +139,10 @@ public class Eia608Parser {
|
||||
// Control character.
|
||||
if (ccData1 < 0x20) {
|
||||
if (stringBuilder.length() > 0) {
|
||||
out.add(new ClosedCaptionText(stringBuilder.toString(), timeUs));
|
||||
captions.add(new ClosedCaptionText(stringBuilder.toString()));
|
||||
stringBuilder.setLength(0);
|
||||
}
|
||||
out.add(new ClosedCaptionCtrl(ccData1, ccData2, timeUs));
|
||||
captions.add(new ClosedCaptionCtrl(ccData1, ccData2));
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -150,8 +154,16 @@ public class Eia608Parser {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -31,8 +31,7 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
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 Eia608Parser eia608Parser;
|
||||
@ -56,7 +57,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
||||
private final MediaFormatHolder formatHolder;
|
||||
private final SampleHolder sampleHolder;
|
||||
private final StringBuilder captionStringBuilder;
|
||||
private final List<ClosedCaption> captionBuffer;
|
||||
private final TreeSet<ClosedCaptionList> pendingCaptionLists;
|
||||
|
||||
private int trackIndex;
|
||||
private long currentPositionUs;
|
||||
@ -85,7 +86,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
||||
formatHolder = new MediaFormatHolder();
|
||||
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
|
||||
captionStringBuilder = new StringBuilder();
|
||||
captionBuffer = new ArrayList<ClosedCaption>();
|
||||
pendingCaptionLists = new TreeSet<ClosedCaptionList>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -122,6 +123,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
||||
private void seekToInternal(long positionUs) {
|
||||
currentPositionUs = positionUs;
|
||||
inputStreamEnded = false;
|
||||
pendingCaptionLists.clear();
|
||||
clearPendingSample();
|
||||
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
|
||||
setCaptionMode(CC_MODE_UNKNOWN);
|
||||
@ -138,10 +140,17 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
||||
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 {
|
||||
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
|
||||
if (result == SampleSource.END_OF_STREAM) {
|
||||
result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
|
||||
if (result == SampleSource.SAMPLE_READ) {
|
||||
maybeParsePendingSample();
|
||||
} else if (result == SampleSource.END_OF_STREAM) {
|
||||
inputStreamEnded = true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@ -149,17 +158,18 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
||||
}
|
||||
}
|
||||
|
||||
if (isSamplePending() && sampleHolder.timeUs <= currentPositionUs) {
|
||||
// Parse the pending sample.
|
||||
eia608Parser.parse(sampleHolder.data.array(), sampleHolder.size, sampleHolder.timeUs,
|
||||
captionBuffer);
|
||||
// Consume parsed captions.
|
||||
consumeCaptionBuffer();
|
||||
// Update the renderer, unless the sample was marked for decoding only.
|
||||
if (!sampleHolder.decodeOnly) {
|
||||
while (!pendingCaptionLists.isEmpty()) {
|
||||
if (pendingCaptionLists.first().timeUs > currentPositionUs) {
|
||||
// We're too early to render any of the pending caption lists.
|
||||
return;
|
||||
}
|
||||
// Remove and consume the next caption list.
|
||||
ClosedCaptionList nextCaptionList = pendingCaptionLists.pollFirst();
|
||||
consumeCaptionList(nextCaptionList);
|
||||
// Update the renderer, unless the caption list was marked for decoding only.
|
||||
if (!nextCaptionList.decodeOnly) {
|
||||
invokeRenderer(caption);
|
||||
}
|
||||
clearPendingSample();
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,14 +231,26 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
||||
textRenderer.onText(text);
|
||||
}
|
||||
|
||||
private void consumeCaptionBuffer() {
|
||||
int captionBufferSize = captionBuffer.size();
|
||||
private void maybeParsePendingSample() {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < captionBufferSize; i++) {
|
||||
ClosedCaption caption = captionBuffer.get(i);
|
||||
ClosedCaption caption = captionList.captions[i];
|
||||
if (caption.type == ClosedCaption.TYPE_CTRL) {
|
||||
ClosedCaptionCtrl captionCtrl = (ClosedCaptionCtrl) caption;
|
||||
if (captionCtrl.isMiscCode()) {
|
||||
@ -240,7 +262,6 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
||||
handleText((ClosedCaptionText) caption);
|
||||
}
|
||||
}
|
||||
captionBuffer.clear();
|
||||
|
||||
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
|
||||
caption = getDisplayCaption();
|
||||
|
Loading…
x
Reference in New Issue
Block a user