blob: 4db023cc9c934f120cd1500157457a110850b619 [file] [log] [blame]
/*
* Copyright (C) 2016 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.exoplayer2.text.cea;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.text.Subtitle;
import com.google.android.exoplayer2.text.SubtitleDecoder;
import com.google.android.exoplayer2.text.SubtitleDecoderException;
import com.google.android.exoplayer2.text.SubtitleInputBuffer;
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayDeque;
import java.util.PriorityQueue;
/** Base class for subtitle parsers for CEA captions. */
/* package */ abstract class CeaDecoder implements SubtitleDecoder {
private static final int NUM_INPUT_BUFFERS = 10;
private static final int NUM_OUTPUT_BUFFERS = 2;
private final ArrayDeque<CeaInputBuffer> availableInputBuffers;
private final ArrayDeque<SubtitleOutputBuffer> availableOutputBuffers;
private final PriorityQueue<CeaInputBuffer> queuedInputBuffers;
@Nullable private CeaInputBuffer dequeuedInputBuffer;
private long playbackPositionUs;
private long queuedInputBufferCount;
@SuppressWarnings("nullness:methodref.receiver.bound")
public CeaDecoder() {
availableInputBuffers = new ArrayDeque<>();
for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
availableInputBuffers.add(new CeaInputBuffer());
}
availableOutputBuffers = new ArrayDeque<>();
for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
availableOutputBuffers.add(new CeaOutputBuffer(this::releaseOutputBuffer));
}
queuedInputBuffers = new PriorityQueue<>();
}
@Override
public abstract String getName();
@Override
public void setPositionUs(long positionUs) {
playbackPositionUs = positionUs;
}
@Override
@Nullable
public SubtitleInputBuffer dequeueInputBuffer() throws SubtitleDecoderException {
Assertions.checkState(dequeuedInputBuffer == null);
if (availableInputBuffers.isEmpty()) {
return null;
}
dequeuedInputBuffer = availableInputBuffers.pollFirst();
return dequeuedInputBuffer;
}
@Override
public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws SubtitleDecoderException {
Assertions.checkArgument(inputBuffer == dequeuedInputBuffer);
CeaInputBuffer ceaInputBuffer = (CeaInputBuffer) inputBuffer;
if (ceaInputBuffer.isDecodeOnly()) {
// We can start decoding anywhere in CEA formats, so discarding on the input side is fine.
releaseInputBuffer(ceaInputBuffer);
} else {
ceaInputBuffer.queuedInputBufferCount = queuedInputBufferCount++;
queuedInputBuffers.add(ceaInputBuffer);
}
dequeuedInputBuffer = null;
}
@Override
@Nullable
public SubtitleOutputBuffer dequeueOutputBuffer() throws SubtitleDecoderException {
if (availableOutputBuffers.isEmpty()) {
return null;
}
// Process input buffers up to the current playback position. Processing of input buffers for
// future content is deferred.
while (!queuedInputBuffers.isEmpty()
&& Util.castNonNull(queuedInputBuffers.peek()).timeUs <= playbackPositionUs) {
CeaInputBuffer inputBuffer = Util.castNonNull(queuedInputBuffers.poll());
if (inputBuffer.isEndOfStream()) {
// availableOutputBuffers.isEmpty() is checked at the top of the method, so this is safe.
SubtitleOutputBuffer outputBuffer = Util.castNonNull(availableOutputBuffers.pollFirst());
outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
releaseInputBuffer(inputBuffer);
return outputBuffer;
}
decode(inputBuffer);
if (isNewSubtitleDataAvailable()) {
Subtitle subtitle = createSubtitle();
// availableOutputBuffers.isEmpty() is checked at the top of the method, so this is safe.
SubtitleOutputBuffer outputBuffer = Util.castNonNull(availableOutputBuffers.pollFirst());
outputBuffer.setContent(inputBuffer.timeUs, subtitle, Format.OFFSET_SAMPLE_RELATIVE);
releaseInputBuffer(inputBuffer);
return outputBuffer;
}
releaseInputBuffer(inputBuffer);
}
return null;
}
private void releaseInputBuffer(CeaInputBuffer inputBuffer) {
inputBuffer.clear();
availableInputBuffers.add(inputBuffer);
}
protected void releaseOutputBuffer(SubtitleOutputBuffer outputBuffer) {
outputBuffer.clear();
availableOutputBuffers.add(outputBuffer);
}
@Override
public void flush() {
queuedInputBufferCount = 0;
playbackPositionUs = 0;
while (!queuedInputBuffers.isEmpty()) {
releaseInputBuffer(Util.castNonNull(queuedInputBuffers.poll()));
}
if (dequeuedInputBuffer != null) {
releaseInputBuffer(dequeuedInputBuffer);
dequeuedInputBuffer = null;
}
}
@Override
public void release() {
// Do nothing.
}
/** Returns whether there is data available to create a new {@link Subtitle}. */
protected abstract boolean isNewSubtitleDataAvailable();
/** Creates a {@link Subtitle} from the available data. */
protected abstract Subtitle createSubtitle();
/**
* Filters and processes the raw data, providing {@link Subtitle}s via {@link #createSubtitle()}
* when sufficient data has been processed.
*/
protected abstract void decode(SubtitleInputBuffer inputBuffer);
@Nullable
protected final SubtitleOutputBuffer getAvailableOutputBuffer() {
return availableOutputBuffers.pollFirst();
}
protected final long getPositionUs() {
return playbackPositionUs;
}
private static final class CeaInputBuffer extends SubtitleInputBuffer
implements Comparable<CeaInputBuffer> {
private long queuedInputBufferCount;
@Override
public int compareTo(CeaInputBuffer other) {
if (isEndOfStream() != other.isEndOfStream()) {
return isEndOfStream() ? 1 : -1;
}
long delta = timeUs - other.timeUs;
if (delta == 0) {
delta = queuedInputBufferCount - other.queuedInputBufferCount;
if (delta == 0) {
return 0;
}
}
return delta > 0 ? 1 : -1;
}
}
private static final class CeaOutputBuffer extends SubtitleOutputBuffer {
private Owner<CeaOutputBuffer> owner;
public CeaOutputBuffer(Owner<CeaOutputBuffer> owner) {
this.owner = owner;
}
@Override
public final void release() {
owner.releaseOutputBuffer(this);
}
}
}