Commit ccac9fad by ojw28

Merge pull request #287 from google/dev

dev -> dev-webm-vp9-opus
parents 876fa41b b0a3c30a
...@@ -25,7 +25,6 @@ import android.media.AudioFormat; ...@@ -25,7 +25,6 @@ import android.media.AudioFormat;
import android.os.Handler; import android.os.Handler;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
/** /**
* Renders encoded AC-3/enhanced AC-3 data to an {@link AudioTrack} for decoding on the playback * Renders encoded AC-3/enhanced AC-3 data to an {@link AudioTrack} for decoding on the playback
...@@ -105,8 +104,8 @@ public final class Ac3PassthroughAudioTrackRenderer extends TrackRenderer { ...@@ -105,8 +104,8 @@ public final class Ac3PassthroughAudioTrackRenderer extends TrackRenderer {
this.source = Assertions.checkNotNull(source); this.source = Assertions.checkNotNull(source);
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
sampleHolder.data = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE); sampleHolder.replaceBuffer(DEFAULT_BUFFER_SIZE);
formatHolder = new MediaFormatHolder(); formatHolder = new MediaFormatHolder();
audioTrack = new AudioTrack(); audioTrack = new AudioTrack();
shouldReadInputBuffer = true; shouldReadInputBuffer = true;
...@@ -199,8 +198,7 @@ public final class Ac3PassthroughAudioTrackRenderer extends TrackRenderer { ...@@ -199,8 +198,7 @@ public final class Ac3PassthroughAudioTrackRenderer extends TrackRenderer {
// Get more data if we have run out. // Get more data if we have run out.
if (shouldReadInputBuffer) { if (shouldReadInputBuffer) {
sampleHolder.data.clear(); sampleHolder.clearData();
int result = int result =
source.readData(trackIndex, currentPositionUs, formatHolder, sampleHolder, false); source.readData(trackIndex, currentPositionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.FORMAT_READ) { if (result == SampleSource.FORMAT_READ) {
......
...@@ -96,4 +96,13 @@ public final class SampleHolder { ...@@ -96,4 +96,13 @@ public final class SampleHolder {
return false; return false;
} }
/**
* Clears {@link #data}. Does nothing if {@link #data} is null.
*/
public void clearData() {
if (data != null) {
data.clear();
}
}
} }
...@@ -86,6 +86,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -86,6 +86,7 @@ public final class FragmentedMp4Extractor implements Extractor {
parsedAtoms.add(Atom.TYPE_moof); parsedAtoms.add(Atom.TYPE_moof);
parsedAtoms.add(Atom.TYPE_moov); parsedAtoms.add(Atom.TYPE_moov);
parsedAtoms.add(Atom.TYPE_mp4a); parsedAtoms.add(Atom.TYPE_mp4a);
parsedAtoms.add(Atom.TYPE_mvhd);
parsedAtoms.add(Atom.TYPE_sidx); parsedAtoms.add(Atom.TYPE_sidx);
parsedAtoms.add(Atom.TYPE_stsd); parsedAtoms.add(Atom.TYPE_stsd);
parsedAtoms.add(Atom.TYPE_tfdt); parsedAtoms.add(Atom.TYPE_tfdt);
...@@ -379,7 +380,8 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -379,7 +380,8 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex); ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
extendsDefaults = parseTrex(mvex.getLeafAtomOfType(Atom.TYPE_trex).data); extendsDefaults = parseTrex(mvex.getLeafAtomOfType(Atom.TYPE_trex).data);
track = CommonMp4AtomParsers.parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak)); track = CommonMp4AtomParsers.parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak),
moov.getLeafAtomOfType(Atom.TYPE_mvhd));
} }
private void onMoofContainerAtomRead(ContainerAtom moof) { private void onMoofContainerAtomRead(ContainerAtom moof) {
......
...@@ -34,7 +34,9 @@ import android.util.SparseArray; ...@@ -34,7 +34,9 @@ import android.util.SparseArray;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
/** /**
...@@ -541,7 +543,6 @@ public final class TsExtractor { ...@@ -541,7 +543,6 @@ public final class TsExtractor {
@SuppressWarnings("hiding") @SuppressWarnings("hiding")
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;
...@@ -553,7 +554,6 @@ public final class TsExtractor { ...@@ -553,7 +554,6 @@ public final class TsExtractor {
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;
...@@ -582,7 +582,7 @@ public final class TsExtractor { ...@@ -582,7 +582,7 @@ public final class TsExtractor {
public Sample poll() { public Sample poll() {
Sample head = peek(); Sample head = peek();
if (head != null) { if (head != null) {
internalQueue.remove(); internalPollSample();
needKeyframe = false; needKeyframe = false;
lastReadTimeUs = head.timeUs; lastReadTimeUs = head.timeUs;
} }
...@@ -595,13 +595,13 @@ public final class TsExtractor { ...@@ -595,13 +595,13 @@ public final class TsExtractor {
* @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 = internalQueue.peek(); Sample head = internalPeekSample();
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);
internalQueue.remove(); internalPollSample();
head = internalQueue.peek(); head = internalPeekSample();
} }
} }
if (head == null) { if (head == null) {
...@@ -610,7 +610,7 @@ public final class TsExtractor { ...@@ -610,7 +610,7 @@ public final class TsExtractor {
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);
internalQueue.remove(); internalPollSample();
return null; return null;
} }
return head; return head;
...@@ -625,8 +625,8 @@ public final class TsExtractor { ...@@ -625,8 +625,8 @@ public final class TsExtractor {
Sample head = peek(); Sample head = peek();
while (head != null && head.timeUs < timeUs) { while (head != null && head.timeUs < timeUs) {
recycle(head); recycle(head);
internalQueue.remove(); internalPollSample();
head = internalQueue.peek(); head = internalPeekSample();
// 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;
...@@ -638,10 +638,10 @@ public final class TsExtractor { ...@@ -638,10 +638,10 @@ public final class TsExtractor {
* Clears the queue. * Clears the queue.
*/ */
public void release() { public void release() {
Sample toRecycle = internalQueue.poll(); Sample toRecycle = internalPollSample();
while (toRecycle != null) { while (toRecycle != null) {
recycle(toRecycle); recycle(toRecycle);
toRecycle = internalQueue.poll(); toRecycle = internalPollSample();
} }
} }
...@@ -666,20 +666,19 @@ public final class TsExtractor { ...@@ -666,20 +666,19 @@ public final class TsExtractor {
return true; return true;
} }
long firstPossibleSpliceTime; long firstPossibleSpliceTime;
Sample nextSample = internalQueue.peek(); Sample nextSample = internalPeekSample();
if (nextSample != null) { if (nextSample != null) {
firstPossibleSpliceTime = nextSample.timeUs; firstPossibleSpliceTime = nextSample.timeUs;
} else { } else {
firstPossibleSpliceTime = lastReadTimeUs + 1; firstPossibleSpliceTime = lastReadTimeUs + 1;
} }
ConcurrentLinkedQueue<Sample> nextInternalQueue = nextQueue.internalQueue; Sample nextQueueSample = nextQueue.internalPeekSample();
Sample nextQueueSample = nextInternalQueue.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.internalQueue.remove(); nextQueue.internalPollSample();
nextQueueSample = nextQueue.internalQueue.peek(); nextQueueSample = nextQueue.internalPeekSample();
} }
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
...@@ -720,7 +719,7 @@ public final class TsExtractor { ...@@ -720,7 +719,7 @@ public final class TsExtractor {
protected void addSample(Sample sample) { protected void addSample(Sample sample) {
largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sample.timeUs); largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sample.timeUs);
internalQueue.add(sample); internalQueueSample(sample);
} }
protected void addToSample(Sample sample, BitArray buffer, int size) { protected void addToSample(Sample sample, BitArray buffer, int size) {
...@@ -731,15 +730,37 @@ public final class TsExtractor { ...@@ -731,15 +730,37 @@ public final class TsExtractor {
sample.size += size; sample.size += size;
} }
protected abstract Sample internalPeekSample();
protected abstract Sample internalPollSample();
protected abstract void internalQueueSample(Sample sample);
} }
/** /**
* Extracts individual samples from continuous byte stream. * Extracts individual samples from continuous byte stream, preserving original order.
*/ */
private abstract class PesPayloadReader extends SampleQueue { private 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);
} }
public abstract void read(BitArray pesBuffer, int pesPayloadSize, long pesTimeUs); public abstract void read(BitArray pesBuffer, int pesPayloadSize, long pesTimeUs);
...@@ -992,18 +1013,23 @@ public final class TsExtractor { ...@@ -992,18 +1013,23 @@ public final class TsExtractor {
/** /**
* 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
* a sample with an earlier timestamp won't be added to it.
*/ */
private class SeiReader extends SampleQueue { private class SeiReader extends SampleQueue implements Comparator<Sample> {
// 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 BitArray seiBuffer; private final BitArray 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 BitArray(); seiBuffer = new BitArray();
internalQueue = new TreeSet<Sample>(this);
} }
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
...@@ -1022,6 +1048,27 @@ public final class TsExtractor { ...@@ -1022,6 +1048,27 @@ public final class TsExtractor {
} }
} }
@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);
}
} }
/** /**
......
...@@ -39,6 +39,7 @@ public abstract class Atom { ...@@ -39,6 +39,7 @@ public abstract class Atom {
public static final int TYPE_trun = getAtomTypeInteger("trun"); public static final int TYPE_trun = getAtomTypeInteger("trun");
public static final int TYPE_sidx = getAtomTypeInteger("sidx"); public static final int TYPE_sidx = getAtomTypeInteger("sidx");
public static final int TYPE_moov = getAtomTypeInteger("moov"); public static final int TYPE_moov = getAtomTypeInteger("moov");
public static final int TYPE_mvhd = getAtomTypeInteger("mvhd");
public static final int TYPE_trak = getAtomTypeInteger("trak"); public static final int TYPE_trak = getAtomTypeInteger("trak");
public static final int TYPE_mdia = getAtomTypeInteger("mdia"); public static final int TYPE_mdia = getAtomTypeInteger("mdia");
public static final int TYPE_minf = getAtomTypeInteger("minf"); public static final int TYPE_minf = getAtomTypeInteger("minf");
...@@ -69,6 +70,7 @@ public abstract class Atom { ...@@ -69,6 +70,7 @@ public abstract class Atom {
public static final int TYPE_mp4v = getAtomTypeInteger("mp4v"); public static final int TYPE_mp4v = getAtomTypeInteger("mp4v");
public static final int TYPE_stts = getAtomTypeInteger("stts"); public static final int TYPE_stts = getAtomTypeInteger("stts");
public static final int TYPE_stss = getAtomTypeInteger("stss"); public static final int TYPE_stss = getAtomTypeInteger("stss");
public static final int TYPE_ctts = getAtomTypeInteger("ctts");
public static final int TYPE_stsc = getAtomTypeInteger("stsc"); public static final int TYPE_stsc = getAtomTypeInteger("stsc");
public static final int TYPE_stsz = getAtomTypeInteger("stsz"); public static final int TYPE_stsz = getAtomTypeInteger("stsz");
public static final int TYPE_stco = getAtomTypeInteger("stco"); public static final int TYPE_stco = getAtomTypeInteger("stco");
......
/*
* 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.mp4;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
import android.media.MediaExtractor;
/** Sample table for a track in an MP4 file. */
public final class Mp4TrackSampleTable {
/** Sample offsets in bytes. */
public final long[] offsets;
/** Sample sizes in bytes. */
public final int[] sizes;
/** Sample timestamps in microseconds. */
public final long[] timestampsUs;
/** Sample flags. */
public final int[] flags;
Mp4TrackSampleTable(
long[] offsets, int[] sizes, long[] timestampsUs, int[] flags) {
Assertions.checkArgument(sizes.length == timestampsUs.length);
Assertions.checkArgument(offsets.length == timestampsUs.length);
Assertions.checkArgument(flags.length == timestampsUs.length);
this.offsets = offsets;
this.sizes = sizes;
this.timestampsUs = timestampsUs;
this.flags = flags;
}
/** Returns the number of samples in the table. */
public int getSampleCount() {
return sizes.length;
}
/**
* Returns the sample index of the closest synchronization sample at or before the given
* timestamp, if one is available.
*
* @param timeUs Timestamp adjacent to which to find a synchronization sample.
* @return Index of the synchronization sample, or {@link Mp4Util#NO_SAMPLE} if none.
*/
public int getIndexOfEarlierOrEqualSynchronizationSample(long timeUs) {
int startIndex = Util.binarySearchFloor(timestampsUs, timeUs, true, false);
for (int i = startIndex; i >= 0; i--) {
if (timestampsUs[i] <= timeUs && (flags[i] & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
return i;
}
}
return Mp4Util.NO_SAMPLE;
}
/**
* Returns the sample index of the closest synchronization sample at or after the given timestamp,
* if one is available.
*
* @param timeUs Timestamp adjacent to which to find a synchronization sample.
* @return index Index of the synchronization sample, or {@link Mp4Util#NO_SAMPLE} if none.
*/
public int getIndexOfLaterOrEqualSynchronizationSample(long timeUs) {
int startIndex = Util.binarySearchCeil(timestampsUs, timeUs, true, false);
for (int i = startIndex; i < timestampsUs.length; i++) {
if (timestampsUs[i] >= timeUs && (flags[i] & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
return i;
}
}
return Mp4Util.NO_SAMPLE;
}
}
...@@ -34,6 +34,15 @@ public final class Mp4Util { ...@@ -34,6 +34,15 @@ public final class Mp4Util {
/** Size of a full atom header, in bytes. */ /** Size of a full atom header, in bytes. */
public static final int FULL_ATOM_HEADER_SIZE = 12; public static final int FULL_ATOM_HEADER_SIZE = 12;
/** Value for the first 32 bits of atomSize when the atom size is actually a long value. */
public static final int LONG_ATOM_SIZE = 1;
/** Sample index when no sample is available. */
public static final int NO_SAMPLE = -1;
/** Track index when no track is selected. */
public static final int NO_TRACK = -1;
/** Four initial bytes that must prefix H.264/AVC NAL units for decoding. */ /** Four initial bytes that must prefix H.264/AVC NAL units for decoding. */
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1}; private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
......
...@@ -91,8 +91,9 @@ public interface SampleExtractor { ...@@ -91,8 +91,9 @@ public interface SampleExtractor {
* {@link SampleSource#END_OF_STREAM} if the last samples in all tracks have been read, or * {@link SampleSource#END_OF_STREAM} if the last samples in all tracks have been read, or
* {@link SampleSource#NOTHING_READ} if the sample cannot be read immediately as it is not * {@link SampleSource#NOTHING_READ} if the sample cannot be read immediately as it is not
* loaded. * loaded.
* @throws IOException Thrown if the source can't be read.
*/ */
int readSample(int track, SampleHolder sampleHolder); int readSample(int track, SampleHolder sampleHolder) throws IOException;
/** Releases resources associated with this extractor. */ /** Releases resources associated with this extractor. */
void release(); void release();
......
...@@ -135,7 +135,6 @@ public class SubtitleParserHelper implements Handler.Callback { ...@@ -135,7 +135,6 @@ public class SubtitleParserHelper implements Handler.Callback {
if (sampleHolder != holder) { if (sampleHolder != holder) {
// A flush has occurred since this holder was posted. Do nothing. // A flush has occurred since this holder was posted. Do nothing.
} else { } else {
holder.data.position(0);
this.result = result; this.result = result;
this.error = error; this.error = error;
this.parsing = false; this.parsing = false;
......
...@@ -177,8 +177,9 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -177,8 +177,9 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
if (!inputStreamEnded && subtitle == null) { if (!inputStreamEnded && subtitle == null) {
try { try {
SampleHolder sampleHolder = parserHelper.getSampleHolder(); SampleHolder sampleHolder = parserHelper.getSampleHolder();
sampleHolder.clearData();
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false); int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) { if (result == SampleSource.SAMPLE_READ && !sampleHolder.decodeOnly) {
parserHelper.startParseOperation(); parserHelper.startParseOperation();
textRendererNeedsUpdate = false; textRendererNeedsUpdate = false;
} else if (result == SampleSource.END_OF_STREAM) { } else if (result == SampleSource.END_OF_STREAM) {
......
...@@ -18,7 +18,7 @@ package com.google.android.exoplayer.text.eia608; ...@@ -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.
*/ */
public final class ClosedCaption implements Comparable<ClosedCaption> { /* package */ abstract class ClosedCaption implements Comparable<ClosedCaption> {
/** /**
* Identifies closed captions with control characters. * Identifies closed captions with control characters.
...@@ -30,23 +30,16 @@ public final class ClosedCaption implements Comparable<ClosedCaption> { ...@@ -30,23 +30,16 @@ public final class ClosedCaption implements Comparable<ClosedCaption> {
public static final int TYPE_TEXT = 1; public static final int TYPE_TEXT = 1;
/** /**
* The type of the closed caption data. If equals to {@link #TYPE_TEXT} the {@link #text} field * The type of the closed caption data.
* has the textual data, if equals to {@link #TYPE_CTRL} the {@link #text} field has two control
* characters (C1, C2).
*/ */
public final int type; public final int type;
/** /**
* Contains text or two control characters.
*/
public final String text;
/**
* Timestamp associated with the closed caption. * Timestamp associated with the closed caption.
*/ */
public final long timeUs; public final long timeUs;
public ClosedCaption(int type, String text, long timeUs) { protected ClosedCaption(int type, long timeUs) {
this.type = type; this.type = type;
this.text = text;
this.timeUs = timeUs; this.timeUs = timeUs;
} }
......
/*
* 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 ClosedCaptionCtrl extends ClosedCaption {
/**
* The receipt of the {@link #RESUME_CAPTION_LOADING} command initiates pop-on style captioning.
* Subsequent data should be loaded into a non-displayed memory and held there until the
* {@link #END_OF_CAPTION} command is received, at which point the non-displayed memory becomes
* the displayed memory (and vice versa).
*/
public static final byte RESUME_CAPTION_LOADING = 0x20;
/**
* The receipt of the {@link #ROLL_UP_CAPTIONS_2_ROWS} command initiates roll-up style
* captioning, with the maximum of 2 rows displayed simultaneously.
*/
public static final byte ROLL_UP_CAPTIONS_2_ROWS = 0x25;
/**
* The receipt of the {@link #ROLL_UP_CAPTIONS_3_ROWS} command initiates roll-up style
* captioning, with the maximum of 3 rows displayed simultaneously.
*/
public static final byte ROLL_UP_CAPTIONS_3_ROWS = 0x26;
/**
* The receipt of the {@link #ROLL_UP_CAPTIONS_4_ROWS} command initiates roll-up style
* captioning, with the maximum of 4 rows displayed simultaneously.
*/
public static final byte ROLL_UP_CAPTIONS_4_ROWS = 0x27;
/**
* The receipt of the {@link #RESUME_DIRECT_CAPTIONING} command initiates paint-on style
* captioning. Subsequent data should be addressed immediately to displayed memory without need
* for the {@link #RESUME_CAPTION_LOADING} command.
*/
public static final byte RESUME_DIRECT_CAPTIONING = 0x29;
/**
* The receipt of the {@link #END_OF_CAPTION} command indicates the end of pop-on style caption,
* at this point already loaded in non-displayed memory caption should become the displayed
* memory (and vice versa). If no {@link #RESUME_CAPTION_LOADING} command has been received,
* {@link #END_OF_CAPTION} command forces the receiver into pop-on style.
*/
public static final byte END_OF_CAPTION = 0x2F;
public static final byte ERASE_DISPLAYED_MEMORY = 0x2C;
public static final byte CARRIAGE_RETURN = 0x2D;
public static final byte ERASE_NON_DISPLAYED_MEMORY = 0x2E;
public static final byte MID_ROW_CHAN_1 = 0x11;
public static final byte MID_ROW_CHAN_2 = 0x19;
public static final byte MISC_CHAN_1 = 0x14;
public static final byte MISC_CHAN_2 = 0x1C;
public static final byte TAB_OFFSET_CHAN_1 = 0x17;
public static final byte TAB_OFFSET_CHAN_2 = 0x1F;
public final byte cc1;
public final byte cc2;
protected ClosedCaptionCtrl(byte cc1, byte cc2, long timeUs) {
super(ClosedCaption.TYPE_CTRL, timeUs);
this.cc1 = cc1;
this.cc2 = cc2;
}
public boolean isMidRowCode() {
return (cc1 == MID_ROW_CHAN_1 || cc1 == MID_ROW_CHAN_2) && (cc2 >= 0x20 && cc2 <= 0x2F);
}
public boolean isMiscCode() {
return (cc1 == MISC_CHAN_1 || cc1 == MISC_CHAN_2) && (cc2 >= 0x20 && cc2 <= 0x2F);
}
public boolean isTabOffsetCode() {
return (cc1 == TAB_OFFSET_CHAN_1 || cc1 == TAB_OFFSET_CHAN_2) && (cc2 >= 0x21 && cc2 <= 0x23);
}
public boolean isPreambleAddressCode() {
return (cc1 >= 0x10 && cc1 <= 0x1F) && (cc2 >= 0x40 && cc2 <= 0x7F);
}
}
/*
* 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 ClosedCaptionText extends ClosedCaption {
public final String text;
public ClosedCaptionText(String text, long timeUs) {
super(ClosedCaption.TYPE_TEXT, timeUs);
this.text = text;
}
}
...@@ -18,8 +18,6 @@ package com.google.android.exoplayer.text.eia608; ...@@ -18,8 +18,6 @@ package com.google.android.exoplayer.text.eia608;
import com.google.android.exoplayer.util.BitArray; import com.google.android.exoplayer.util.BitArray;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
...@@ -82,22 +80,29 @@ public class Eia608Parser { ...@@ -82,22 +80,29 @@ public class Eia608Parser {
0xFB // 3F: 251 'û' "Latin small letter U with circumflex" 0xFB // 3F: 251 'û' "Latin small letter U with circumflex"
}; };
public boolean canParse(String mimeType) { private final BitArray seiBuffer;
private final StringBuilder stringBuilder;
/* package */ Eia608Parser() {
seiBuffer = new BitArray();
stringBuilder = new StringBuilder();
}
/* package */ boolean canParse(String mimeType) {
return mimeType.equals(MimeTypes.APPLICATION_EIA608); return mimeType.equals(MimeTypes.APPLICATION_EIA608);
} }
public List<ClosedCaption> parse(byte[] data, int size, long timeUs) { /* package */ void parse(byte[] data, int size, long timeUs, List<ClosedCaption> out) {
if (size <= 0) { if (size <= 0) {
return null; return;
} }
BitArray seiBuffer = new BitArray(data, size);
stringBuilder.setLength(0);
seiBuffer.reset(data, size);
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.skipBytes(1); seiBuffer.skipBytes(1);
List<ClosedCaption> captions = new ArrayList<ClosedCaption>();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < ccCount; i++) { for (int i = 0; i < ccCount; i++) {
seiBuffer.skipBits(5); // one_bit + reserved seiBuffer.skipBits(5); // one_bit + reserved
boolean ccValid = seiBuffer.readBit(); boolean ccValid = seiBuffer.readBit();
...@@ -129,12 +134,10 @@ public class Eia608Parser { ...@@ -129,12 +134,10 @@ public class Eia608Parser {
// Control character. // Control character.
if (ccData1 < 0x20) { if (ccData1 < 0x20) {
if (stringBuilder.length() > 0) { if (stringBuilder.length() > 0) {
captions.add(new ClosedCaption(ClosedCaption.TYPE_TEXT, stringBuilder.toString(), out.add(new ClosedCaptionText(stringBuilder.toString(), timeUs));
timeUs));
stringBuilder.setLength(0); stringBuilder.setLength(0);
} }
captions.add(new ClosedCaption(ClosedCaption.TYPE_CTRL, out.add(new ClosedCaptionCtrl(ccData1, ccData2, timeUs));
new String(new char[] {(char) ccData1, (char) ccData2}), timeUs));
continue; continue;
} }
...@@ -146,10 +149,8 @@ public class Eia608Parser { ...@@ -146,10 +149,8 @@ public class Eia608Parser {
} }
if (stringBuilder.length() > 0) { if (stringBuilder.length() > 0) {
captions.add(new ClosedCaption(ClosedCaption.TYPE_TEXT, stringBuilder.toString(), timeUs)); out.add(new ClosedCaptionText(stringBuilder.toString(), timeUs));
} }
return Collections.unmodifiableList(captions);
} }
private static char getChar(byte ccData) { private static char getChar(byte ccData) {
......
...@@ -402,6 +402,32 @@ public final class Util { ...@@ -402,6 +402,32 @@ public final class Util {
} }
/** /**
* Applies {@link #scaleLargeTimestamp(long, long, long)} to an array of unscaled timestamps.
*
* @param timestamps The timestamps to scale.
* @param multiplier The multiplier.
* @param divisor The divisor.
*/
public static void scaleLargeTimestampsInPlace(long[] timestamps, long multiplier, long divisor) {
if (divisor >= multiplier && (divisor % multiplier) == 0) {
long divisionFactor = divisor / multiplier;
for (int i = 0; i < timestamps.length; i++) {
timestamps[i] /= divisionFactor;
}
} else if (divisor < multiplier && (multiplier % divisor) == 0) {
long multiplicationFactor = multiplier / divisor;
for (int i = 0; i < timestamps.length; i++) {
timestamps[i] *= multiplicationFactor;
}
} else {
double multiplicationFactor = (double) multiplier / divisor;
for (int i = 0; i < timestamps.length; i++) {
timestamps[i] = (long) (timestamps[i] * multiplicationFactor);
}
}
}
/**
* Converts a list of integers to a primitive array. * Converts a list of integers to a primitive array.
* *
* @param list A list of integers. * @param list A list of integers.
......
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