Commit fd519016 by Oliver Woodman

Big HLS update. Add start of adaptive support, but leave disabled for now.

parent 6c6ba900
Showing with 283 additions and 235 deletions
......@@ -15,8 +15,6 @@
*/
package com.google.android.exoplayer.demo.full.player;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer;
......@@ -25,13 +23,13 @@ import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.hls.HlsChunkSource;
import com.google.android.exoplayer.hls.HlsMasterPlaylist;
import com.google.android.exoplayer.hls.HlsMasterPlaylist.Variant;
import com.google.android.exoplayer.hls.HlsMasterPlaylistParser;
import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.hls.Variant;
import com.google.android.exoplayer.metadata.Id3Parser;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
......@@ -47,9 +45,6 @@ import java.util.Collections;
*/
public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<HlsMasterPlaylist> {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int VIDEO_BUFFER_SEGMENTS = 200;
private final String userAgent;
private final String url;
private final String contentId;
......@@ -89,12 +84,12 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
@Override
public void onManifest(String contentId, HlsMasterPlaylist manifest) {
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource dataSource = new UriDataSource(userAgent, null);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, 3);
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest, bandwidthMeter, null,
false);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 3);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, player.getMainHandler(), player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
......@@ -111,7 +106,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
private HlsMasterPlaylist newSimpleMasterPlaylist(String mediaPlaylistUrl) {
return new HlsMasterPlaylist(Uri.parse(""),
Collections.singletonList(new Variant(mediaPlaylistUrl, 0, null, -1, -1)));
Collections.singletonList(new Variant(0, mediaPlaylistUrl, 0, null, -1, -1)));
}
}
......@@ -15,8 +15,6 @@
*/
package com.google.android.exoplayer.demo.simple;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer;
......@@ -26,11 +24,11 @@ import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBui
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
import com.google.android.exoplayer.hls.HlsChunkSource;
import com.google.android.exoplayer.hls.HlsMasterPlaylist;
import com.google.android.exoplayer.hls.HlsMasterPlaylist.Variant;
import com.google.android.exoplayer.hls.HlsMasterPlaylistParser;
import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.hls.Variant;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
......@@ -47,9 +45,6 @@ import java.util.Collections;
/* package */ class HlsRendererBuilder implements RendererBuilder,
ManifestCallback<HlsMasterPlaylist> {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int VIDEO_BUFFER_SEGMENTS = 200;
private final SimplePlayerActivity playerActivity;
private final String userAgent;
private final String url;
......@@ -90,12 +85,11 @@ import java.util.Collections;
@Override
public void onManifest(String contentId, HlsMasterPlaylist manifest) {
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
DataSource dataSource = new UriDataSource(userAgent, null);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, 2);
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest, bandwidthMeter, null,
false);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 2);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
playerActivity, 50);
......@@ -109,7 +103,7 @@ import java.util.Collections;
private HlsMasterPlaylist newSimpleMasterPlaylist(String mediaPlaylistUrl) {
return new HlsMasterPlaylist(Uri.parse(""),
Collections.singletonList(new Variant(mediaPlaylistUrl, 0, null, -1, -1)));
Collections.singletonList(new Variant(0, mediaPlaylistUrl, 0, null, -1, -1)));
}
}
/*
* 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.hls;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.util.BitArray;
import java.io.IOException;
/**
* An abstract base class for {@link HlsChunk} implementations where the data should be loaded into
* a {@link BitArray} and subsequently consumed.
*/
public abstract class BitArrayChunk extends HlsChunk {
private static final int READ_GRANULARITY = 16 * 1024;
private final BitArray bitArray;
private volatile boolean loadFinished;
private volatile boolean loadCanceled;
/**
* @param dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}.
* @param bitArray The {@link BitArray} into which the data should be loaded.
*/
public BitArrayChunk(DataSource dataSource, DataSpec dataSpec, BitArray bitArray) {
super(dataSource, dataSpec);
this.bitArray = bitArray;
}
@Override
public void consume() throws IOException {
consume(bitArray);
}
/**
* Invoked by {@link #consume()}. Implementations should override this method to consume the
* loaded data.
*
* @param bitArray The {@link BitArray} containing the loaded data.
* @throws IOException If an error occurs consuming the loaded data.
*/
protected abstract void consume(BitArray bitArray) throws IOException;
/**
* Whether the whole of the chunk has been loaded.
*
* @return True if the whole of the chunk has been loaded. False otherwise.
*/
@Override
public boolean isLoadFinished() {
return loadFinished;
}
// Loadable implementation
@Override
public final void cancelLoad() {
loadCanceled = true;
}
@Override
public final boolean isLoadCanceled() {
return loadCanceled;
}
@Override
public final void load() throws IOException, InterruptedException {
try {
bitArray.reset();
dataSource.open(dataSpec);
int bytesRead = 0;
while (bytesRead != -1 && !loadCanceled) {
bytesRead = bitArray.append(dataSource, READ_GRANULARITY);
}
loadFinished = !loadCanceled;
} finally {
dataSource.close();
}
}
}
......@@ -15,38 +15,21 @@
*/
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.upstream.Allocation;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceStream;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import com.google.android.exoplayer.util.Assertions;
import java.io.IOException;
/**
* An abstract base class for {@link Loadable} implementations that load chunks of data required
* for the playback of streams.
* <p>
* TODO: Figure out whether this should merge with the chunk package, or whether the hls
* implementation is going to naturally diverge.
* for the playback of HLS streams.
*/
public abstract class HlsChunk implements Loadable {
/**
* The reason for a {@link HlsChunkSource} having generated this chunk. For reporting only.
* Possible values for this variable are defined by the specific {@link HlsChunkSource}
* implementations.
*/
public final int trigger;
private final DataSource dataSource;
private final DataSpec dataSpec;
private DataSourceStream dataSourceStream;
protected final DataSource dataSource;
protected final DataSpec dataSpec;
/**
* @param dataSource The source from which the data should be loaded.
......@@ -54,123 +37,15 @@ public abstract class HlsChunk implements Loadable {
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}.
* @param trigger See {@link #trigger}.
*/
public HlsChunk(DataSource dataSource, DataSpec dataSpec, int trigger) {
public HlsChunk(DataSource dataSource, DataSpec dataSpec) {
Assertions.checkState(dataSpec.length <= Integer.MAX_VALUE);
this.dataSource = Assertions.checkNotNull(dataSource);
this.dataSpec = Assertions.checkNotNull(dataSpec);
this.trigger = trigger;
}
/**
* Initializes the {@link HlsChunk}.
*
* @param allocator An {@link Allocator} from which the {@link Allocation} needed to contain the
* data can be obtained.
*/
public final void init(Allocator allocator) {
Assertions.checkState(dataSourceStream == null);
dataSourceStream = new DataSourceStream(dataSource, dataSpec, allocator);
}
/**
* Releases the {@link HlsChunk}, releasing any backing {@link Allocation}s.
*/
public final void release() {
if (dataSourceStream != null) {
dataSourceStream.close();
dataSourceStream = null;
}
}
/**
* Gets the length of the chunk in bytes.
*
* @return The length of the chunk in bytes, or {@link C#LENGTH_UNBOUNDED} if the length has yet
* to be determined.
*/
public final long getLength() {
return dataSourceStream.getLength();
}
/**
* Whether the whole of the data has been consumed.
*
* @return True if the whole of the data has been consumed. False otherwise.
*/
public final boolean isReadFinished() {
return dataSourceStream.isEndOfStream();
}
/**
* Whether the whole of the chunk has been loaded.
*
* @return True if the whole of the chunk has been loaded. False otherwise.
*/
public final boolean isLoadFinished() {
return dataSourceStream.isLoadFinished();
}
public abstract void consume() throws IOException;
/**
* Gets the number of bytes that have been loaded.
*
* @return The number of bytes that have been loaded.
*/
public final long bytesLoaded() {
return dataSourceStream.getLoadPosition();
}
/**
* Causes loaded data to be consumed.
*
* @throws IOException If an error occurs consuming the loaded data.
*/
public final void consume() throws IOException {
Assertions.checkState(dataSourceStream != null);
consumeStream(dataSourceStream);
}
/**
* Invoked by {@link #consume()}. Implementations may override this method if they wish to
* consume the loaded data at this point.
* <p>
* The default implementation is a no-op.
*
* @param stream The stream of loaded data.
* @throws IOException If an error occurs consuming the loaded data.
*/
protected void consumeStream(NonBlockingInputStream stream) throws IOException {
// Do nothing.
}
protected final NonBlockingInputStream getNonBlockingInputStream() {
return dataSourceStream;
}
protected final void resetReadPosition() {
if (dataSourceStream != null) {
dataSourceStream.resetReadPosition();
} else {
// We haven't been initialized yet, so the read position must already be 0.
}
}
// Loadable implementation
@Override
public final void cancelLoad() {
dataSourceStream.cancelLoad();
}
@Override
public final boolean isLoadCanceled() {
return dataSourceStream.isLoadCanceled();
}
@Override
public final void load() throws IOException, InterruptedException {
dataSourceStream.load();
}
public abstract boolean isLoadFinished();
}
......@@ -24,25 +24,6 @@ import java.util.List;
*/
public final class HlsMasterPlaylist {
/**
* Variant stream reference.
*/
public static final class Variant {
public final int bandwidth;
public final String url;
public final String[] codecs;
public final int width;
public final int height;
public Variant(String url, int bandwidth, String[] codecs, int width, int height) {
this.bandwidth = bandwidth;
this.url = url;
this.codecs = codecs;
this.width = width;
this.height = height;
}
}
public final Uri baseUri;
public final List<Variant> variants;
......
......@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.hls.HlsMasterPlaylist.Variant;
import com.google.android.exoplayer.util.ManifestParser;
import android.net.Uri;
......@@ -61,6 +60,7 @@ public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPl
String[] codecs = null;
int width = -1;
int height = -1;
int variantIndex = 0;
String line;
while ((line = reader.readLine()) != null) {
......@@ -70,15 +70,14 @@ public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPl
}
if (line.startsWith(STREAM_INF_TAG)) {
bandwidth = HlsParserUtil.parseIntAttr(line, BANDWIDTH_ATTR_REGEX, BANDWIDTH_ATTR);
String codecsString = HlsParserUtil.parseOptionalStringAttr(line, CODECS_ATTR_REGEX,
CODECS_ATTR);
String codecsString = HlsParserUtil.parseOptionalStringAttr(line, CODECS_ATTR_REGEX);
if (codecsString != null) {
codecs = codecsString.split("(\\s*,\\s*)|(\\s*$)");
} else {
codecs = null;
}
String resolutionString = HlsParserUtil.parseOptionalStringAttr(line, RESOLUTION_ATTR_REGEX,
RESOLUTION_ATTR);
String resolutionString = HlsParserUtil.parseOptionalStringAttr(line,
RESOLUTION_ATTR_REGEX);
if (resolutionString != null) {
String[] widthAndHeight = resolutionString.split("x");
width = Integer.parseInt(widthAndHeight[0]);
......@@ -88,7 +87,7 @@ public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPl
height = -1;
}
} else if (!line.startsWith("#")) {
variants.add(new Variant(line, bandwidth, codecs, width, height));
variants.add(new Variant(variantIndex++, line, bandwidth, codecs, width, height));
bandwidth = 0;
codecs = null;
width = -1;
......
......@@ -114,8 +114,7 @@ public final class HlsMediaPlaylistParser implements ManifestParser<HlsMediaPlay
} else {
segmentEncryptionKeyUri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX,
URI_ATTR);
segmentEncryptionIV = HlsParserUtil.parseOptionalStringAttr(line, IV_ATTR_REGEX,
IV_ATTR);
segmentEncryptionIV = HlsParserUtil.parseOptionalStringAttr(line, IV_ATTR_REGEX);
if (segmentEncryptionIV == null) {
segmentEncryptionIV = Integer.toHexString(segmentMediaSequence);
}
......
......@@ -36,7 +36,7 @@ import java.util.regex.Pattern;
throw new ParserException(String.format("Couldn't match %s tag in %s", tag, line));
}
public static String parseOptionalStringAttr(String line, Pattern pattern, String tag) {
public static String parseOptionalStringAttr(String line, Pattern pattern) {
Matcher matcher = pattern.matcher(line);
if (matcher.find() && matcher.groupCount() == 1) {
return matcher.group(1);
......
......@@ -15,9 +15,12 @@
*/
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import java.io.IOException;
/**
* A MPEG2TS chunk.
*/
......@@ -40,40 +43,87 @@ public final class TsChunk extends HlsChunk {
*/
public final int nextChunkIndex;
/**
* The encoding discontinuity indicator.
* True if this is the final chunk being loaded for the current variant, as we splice to another
* one. False otherwise.
*/
public final boolean discontinuity;
public final boolean splicingOut;
/**
* For each track, whether samples from the first keyframe (inclusive) should be discarded.
* The extractor into which this chunk is being consumed.
*/
public final boolean discardFromFirstKeyframes;
public final TsExtractor extractor;
private volatile int loadPosition;
private volatile boolean loadFinished;
private volatile boolean loadCanceled;
/**
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSpec Defines the data to be loaded.
* @param trigger The reason for this chunk being selected.
* @param variantIndex The index of the variant in the master playlist.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk.
* @param discontinuity The encoding discontinuity indicator.
* @param discardFromFirstKeyframes For each contained media stream, whether samples from the
* first keyframe (inclusive) should be discarded.
* @param splicingOut True if this is the final chunk being loaded for the current variant, as we
* splice to another one. False otherwise.
*/
public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, int variantIndex,
long startTimeUs, long endTimeUs, int nextChunkIndex, boolean discontinuity,
boolean discardFromFirstKeyframes) {
super(dataSource, dataSpec, trigger);
public TsChunk(DataSource dataSource, DataSpec dataSpec, TsExtractor tsExtractor,
int variantIndex, long startTimeUs, long endTimeUs, int nextChunkIndex, boolean splicingOut) {
super(dataSource, dataSpec);
this.extractor = tsExtractor;
this.variantIndex = variantIndex;
this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs;
this.nextChunkIndex = nextChunkIndex;
this.discontinuity = discontinuity;
this.discardFromFirstKeyframes = discardFromFirstKeyframes;
this.splicingOut = splicingOut;
}
@Override
public void consume() throws IOException {
// Do nothing.
}
public boolean isLastChunk() {
return nextChunkIndex == -1;
}
@Override
public boolean isLoadFinished() {
return loadFinished;
}
// Loadable implementation
@Override
public void cancelLoad() {
loadCanceled = true;
}
@Override
public boolean isLoadCanceled() {
return loadCanceled;
}
@Override
public void load() throws IOException, InterruptedException {
DataSpec loadDataSpec;
if (loadPosition == 0) {
loadDataSpec = dataSpec;
} else {
long remainingLength = dataSpec.length != C.LENGTH_UNBOUNDED
? dataSpec.length - loadPosition : C.LENGTH_UNBOUNDED;
loadDataSpec = new DataSpec(dataSpec.uri, dataSpec.position + loadPosition,
remainingLength, dataSpec.key);
}
try {
dataSource.open(loadDataSpec);
int bytesRead = 0;
while (bytesRead != -1 && !loadCanceled) {
bytesRead = extractor.read(dataSource);
}
loadFinished = !loadCanceled;
} finally {
dataSource.close();
}
}
}
......@@ -15,23 +15,42 @@
*/
package com.google.android.exoplayer.hls;
import java.util.Comparator;
/**
* Holds a hls chunk operation, which consists of a {@link HlsChunk} to load together with the
* number of {@link TsChunk}s that should be retained on the queue.
* <p>
* TODO: Figure out whether this should merge with the chunk package, or whether the hls
* implementation is going to naturally diverge.
* Variant stream reference.
*/
public final class HlsChunkOperationHolder {
public final class Variant {
/**
* The number of {@link TsChunk}s to retain in a queue.
* Sorts {@link Variant} objects in order of decreasing bandwidth.
* <p>
* When two {@link Variant}s have the same bandwidth, the one with the lowest index comes first.
*/
public int queueSize;
public static final class DecreasingBandwidthComparator implements Comparator<Variant> {
/**
* The chunk.
*/
public HlsChunk chunk;
@Override
public int compare(Variant a, Variant b) {
int bandwidthDifference = b.bandwidth - a.bandwidth;
return bandwidthDifference != 0 ? bandwidthDifference : a.index - b.index;
}
}
public final int index;
public final int bandwidth;
public final String url;
public final String[] codecs;
public final int width;
public final int height;
public Variant(int index, String url, int bandwidth, String[] codecs, int width, int height) {
this.index = index;
this.bandwidth = bandwidth;
this.url = url;
this.codecs = codecs;
this.width = width;
this.height = height;
}
}
......@@ -16,7 +16,7 @@
package com.google.android.exoplayer.metadata;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.parser.ts.BitsArray;
import com.google.android.exoplayer.util.BitArray;
import com.google.android.exoplayer.util.MimeTypes;
import java.io.UnsupportedEncodingException;
......@@ -37,7 +37,7 @@ public class Id3Parser implements MetadataParser {
@Override
public Map<String, Object> parse(byte[] data, int size)
throws UnsupportedEncodingException, ParserException {
BitsArray id3Buffer = new BitsArray(data, size);
BitArray id3Buffer = new BitArray(data, size);
int id3Size = parseId3Header(id3Buffer);
Map<String, Object> metadata = new HashMap<String, Object>();
......@@ -102,11 +102,11 @@ public class Id3Parser implements MetadataParser {
/**
* Parses ID3 header.
* @param id3Buffer A {@link BitsArray} with raw ID3 data.
* @param id3Buffer A {@link BitArray} with raw ID3 data.
* @return The size of data that contains ID3 frames without header and footer.
* @throws ParserException If ID3 file identifier != "ID3".
*/
private static int parseId3Header(BitsArray id3Buffer) throws ParserException {
private static int parseId3Header(BitArray id3Buffer) throws ParserException {
int id1 = id3Buffer.readUnsignedByte();
int id2 = id3Buffer.readUnsignedByte();
int id3 = id3Buffer.readUnsignedByte();
......
......@@ -19,7 +19,7 @@ import java.io.IOException;
import java.util.Map;
/**
* Parses {@link Metadata}s from binary data.
* Parses metadata objects from binary data.
*/
public interface MetadataParser {
......
......@@ -13,15 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.parser.ts;
package com.google.android.exoplayer.util;
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.upstream.DataSource;
import java.io.IOException;
/**
* Wraps a byte array, providing methods that allow it to be read as a bitstream.
*/
public final class BitsArray {
public final class BitArray {
private byte[] data;
......@@ -33,16 +34,16 @@ public final class BitsArray {
private int byteOffset;
private int bitOffset;
public BitsArray() {
public BitArray() {
}
public BitsArray(byte[] data, int limit) {
public BitArray(byte[] data, int limit) {
this.data = data;
this.limit = limit;
}
/**
* Resets the state.
* Clears all data, setting the offset and limit to zero.
*/
public void reset() {
byteOffset = 0;
......@@ -51,6 +52,28 @@ public final class BitsArray {
}
/**
* Resets to wrap the specified data, setting the offset to zero.
*
* @param data The data to wrap.
* @param limit The limit to set.
*/
public void reset(byte[] data, int limit) {
this.data = data;
this.limit = limit;
byteOffset = 0;
bitOffset = 0;
}
/**
* Gets the backing byte array.
*
* @return The backing byte array.
*/
public byte[] getData() {
return data;
}
/**
* Gets the current byte offset.
*
* @return The current byte offset.
......@@ -69,16 +92,16 @@ public final class BitsArray {
}
/**
* Appends data from a {@link NonBlockingInputStream}.
* Appends data from a {@link DataSource}.
*
* @param inputStream The {@link NonBlockingInputStream} whose data should be appended.
* @param dataSource The {@link DataSource} from which to read.
* @param length The maximum number of bytes to read and append.
* @return The number of bytes that were read and appended. May be 0 if no data was available
* from the stream. -1 is returned if the end of the stream has been reached.
* @return The number of bytes that were read and appended, or -1 if no more data is available.
* @throws IOException If an error occurs reading from the source.
*/
public int append(NonBlockingInputStream inputStream, int length) {
public int append(DataSource dataSource, int length) throws IOException {
expand(length);
int bytesRead = inputStream.read(data, limit, length);
int bytesRead = dataSource.read(data, limit, length);
if (bytesRead == -1) {
return -1;
}
......@@ -87,12 +110,12 @@ public final class BitsArray {
}
/**
* Appends data from another {@link BitsArray}.
* Appends data from another {@link BitArray}.
*
* @param bitsArray The {@link BitsArray} whose data should be appended.
* @param bitsArray The {@link BitArray} whose data should be appended.
* @param length The number of bytes to read and append.
*/
public void append(BitsArray bitsArray, int length) {
public void append(BitArray bitsArray, int length) {
expand(length);
bitsArray.readBytes(data, limit, length);
limit += length;
......@@ -257,6 +280,19 @@ public final class BitsArray {
}
/**
* Reads an Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public int readExpGolombCodedInt() {
int leadingZeros = 0;
while (!readBit()) {
leadingZeros++;
}
return (1 << leadingZeros) - 1 + (leadingZeros > 0 ? readBits(leadingZeros) : 0);
}
/**
* Reads a Synchsafe integer.
* Synchsafe integers are integers that keep the highest bit of every byte zeroed.
* A 32 bit synchsafe integer can store 28 bits of information.
......@@ -293,7 +329,7 @@ public final class BitsArray {
/**
* Finds the next NAL unit.
*
* @param nalUnitType The type of the NAL unit to search for.
* @param nalUnitType The type of the NAL unit to search for, or -1 for any NAL unit.
* @param offset The additional offset in the data to start the search from.
* @return The offset from the current position to the start of the NAL unit. If a NAL unit is
* not found, then the offset to the end of the data is returned.
......@@ -302,7 +338,7 @@ public final class BitsArray {
for (int i = byteOffset + offset; i < limit - 3; i++) {
// Check for NAL unit start code prefix == 0x000001.
if ((data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1)
&& (nalUnitType == (data[i + 3] & 0x1F))) {
&& (nalUnitType == -1 || (nalUnitType == (data[i + 3] & 0x1F)))) {
return i - byteOffset;
}
}
......
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