mirror of
https://github.com/samsonjs/media.git
synced 2026-04-12 12:25:47 +00:00
commit
654d37fe29
20 changed files with 438 additions and 210 deletions
|
|
@ -3,9 +3,11 @@
|
|||
We'd love to hear your feedback. Please open new issues describing any bugs,
|
||||
feature requests or suggestions that you have.
|
||||
|
||||
We are not actively looking to accept patches to this project at the current
|
||||
time, however in some cases we may do so. For such cases, please see the
|
||||
agreement below.
|
||||
We will also consider high quality pull requests. These should normally merge
|
||||
into the [dev][] branch rather than master. To contribute in this way you must
|
||||
first submit a Contributor License Agreement, as described below.
|
||||
|
||||
[dev]: https://github.com/google/ExoPlayer/tree/dev
|
||||
|
||||
|
||||
## Contributor License Agreement ##
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer.demo"
|
||||
android:versionCode="1200"
|
||||
android:versionName="1.2.00"
|
||||
android:versionCode="1300"
|
||||
android:versionName="1.3.00"
|
||||
android:theme="@style/RootTheme">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
|
|
|||
|
|
@ -117,9 +117,12 @@ import java.util.Locale;
|
|||
new Sample("Apple master playlist advanced",
|
||||
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/"
|
||||
+ "bipbop_16x9_variant.m3u8", DemoUtil.TYPE_HLS),
|
||||
new Sample("Apple single media playlist",
|
||||
new Sample("Apple TS media playlist",
|
||||
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/"
|
||||
+ "prog_index.m3u8", DemoUtil.TYPE_HLS),
|
||||
new Sample("Apple AAC media playlist",
|
||||
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear0/"
|
||||
+ "prog_index.m3u8", DemoUtil.TYPE_HLS),
|
||||
};
|
||||
|
||||
public static final Sample[] MISC = new Sample[] {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public class ExoPlayerLibraryInfo {
|
|||
/**
|
||||
* The version of the library, expressed as a string.
|
||||
*/
|
||||
public static final String VERSION = "1.2.0";
|
||||
public static final String VERSION = "1.3.0";
|
||||
|
||||
/**
|
||||
* The version of the library, expressed as an integer.
|
||||
|
|
@ -34,7 +34,7 @@ public class ExoPlayerLibraryInfo {
|
|||
* Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the
|
||||
* corresponding integer version 001002003.
|
||||
*/
|
||||
public static final int VERSION_INT = 001002000;
|
||||
public static final int VERSION_INT = 001003000;
|
||||
|
||||
/**
|
||||
* Whether the library was compiled with {@link com.google.android.exoplayer.util.Assertions}
|
||||
|
|
|
|||
|
|
@ -417,7 +417,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
|
|||
if (currentLoadable != null && mediaChunk == currentLoadable) {
|
||||
// Linearly interpolate partially-fetched chunk times.
|
||||
long chunkLength = mediaChunk.getLength();
|
||||
if (chunkLength != C.LENGTH_UNBOUNDED) {
|
||||
if (chunkLength != C.LENGTH_UNBOUNDED && chunkLength != 0) {
|
||||
return mediaChunk.startTimeUs + ((mediaChunk.endTimeUs - mediaChunk.startTimeUs) *
|
||||
mediaChunk.bytesLoaded()) / chunkLength;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ package com.google.android.exoplayer.hls;
|
|||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
import com.google.android.exoplayer.hls.parser.AdtsExtractor;
|
||||
import com.google.android.exoplayer.hls.parser.HlsExtractor;
|
||||
import com.google.android.exoplayer.hls.parser.TsExtractor;
|
||||
import com.google.android.exoplayer.upstream.Aes128DataSource;
|
||||
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
||||
|
|
@ -105,6 +107,7 @@ public class HlsChunkSource {
|
|||
public static final long DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS = 20000;
|
||||
|
||||
private static final String TAG = "HlsChunkSource";
|
||||
private static final String AAC_FILE_EXTENSION = ".aac";
|
||||
private static final float BANDWIDTH_FRACTION = 0.8f;
|
||||
|
||||
private final BufferPool bufferPool;
|
||||
|
|
@ -332,9 +335,11 @@ public class HlsChunkSource {
|
|||
boolean isLastChunk = !mediaPlaylist.live && chunkIndex == mediaPlaylist.segments.size() - 1;
|
||||
|
||||
// Configure the extractor that will read the chunk.
|
||||
TsExtractor extractor;
|
||||
HlsExtractor extractor;
|
||||
if (previousTsChunk == null || segment.discontinuity || switchingVariant || liveDiscontinuity) {
|
||||
extractor = new TsExtractor(startTimeUs, switchingVariantSpliced, bufferPool);
|
||||
extractor = chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION)
|
||||
? new AdtsExtractor(switchingVariantSpliced, startTimeUs, bufferPool)
|
||||
: new TsExtractor(switchingVariantSpliced, startTimeUs, bufferPool);
|
||||
} else {
|
||||
extractor = previousTsChunk.extractor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public final class HlsPlaylistParser implements ManifestParser<HlsPlaylist> {
|
|||
private static final Pattern BANDWIDTH_ATTR_REGEX =
|
||||
Pattern.compile(BANDWIDTH_ATTR + "=(\\d+)\\b");
|
||||
private static final Pattern CODECS_ATTR_REGEX =
|
||||
Pattern.compile(CODECS_ATTR + "=\"(.+)\"");
|
||||
Pattern.compile(CODECS_ATTR + "=\"(.+?)\"");
|
||||
private static final Pattern RESOLUTION_ATTR_REGEX =
|
||||
Pattern.compile(RESOLUTION_ATTR + "=(\\d+x\\d+)");
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import com.google.android.exoplayer.SampleHolder;
|
|||
import com.google.android.exoplayer.SampleSource;
|
||||
import com.google.android.exoplayer.TrackInfo;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.hls.parser.TsExtractor;
|
||||
import com.google.android.exoplayer.hls.parser.HlsExtractor;
|
||||
import com.google.android.exoplayer.upstream.Loader;
|
||||
import com.google.android.exoplayer.upstream.Loader.Loadable;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
|
|
@ -44,7 +44,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||
private static final int NO_RESET_PENDING = -1;
|
||||
|
||||
private final HlsChunkSource chunkSource;
|
||||
private final LinkedList<TsExtractor> extractors;
|
||||
private final LinkedList<HlsExtractor> extractors;
|
||||
private final boolean frameAccurateSeeking;
|
||||
private final int minLoadableRetryCount;
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||
this.frameAccurateSeeking = frameAccurateSeeking;
|
||||
this.remainingReleaseCount = downstreamRendererCount;
|
||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||
extractors = new LinkedList<TsExtractor>();
|
||||
extractors = new LinkedList<HlsExtractor>();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -96,7 +96,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||
}
|
||||
continueBufferingInternal();
|
||||
if (!extractors.isEmpty()) {
|
||||
TsExtractor extractor = extractors.getFirst();
|
||||
HlsExtractor extractor = extractors.getFirst();
|
||||
if (extractor.isPrepared()) {
|
||||
trackCount = extractor.getTrackCount();
|
||||
trackEnabledStates = new boolean[trackCount];
|
||||
|
|
@ -195,7 +195,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||
return NOTHING_READ;
|
||||
}
|
||||
|
||||
TsExtractor extractor = getCurrentExtractor();
|
||||
HlsExtractor extractor = getCurrentExtractor();
|
||||
if (extractors.size() > 1) {
|
||||
// If there's more than one extractor, attempt to configure a seamless splice from the
|
||||
// current one to the next one.
|
||||
|
|
@ -328,8 +328,8 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||
*
|
||||
* @return The current extractor from which samples should be read. Guaranteed to be non-null.
|
||||
*/
|
||||
private TsExtractor getCurrentExtractor() {
|
||||
TsExtractor extractor = extractors.getFirst();
|
||||
private HlsExtractor getCurrentExtractor() {
|
||||
HlsExtractor extractor = extractors.getFirst();
|
||||
while (extractors.size() > 1 && !haveSamplesForEnabledTracks(extractor)) {
|
||||
// We're finished reading from the extractor for all tracks, and so can discard it.
|
||||
extractors.removeFirst().release();
|
||||
|
|
@ -338,7 +338,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||
return extractor;
|
||||
}
|
||||
|
||||
private void discardSamplesForDisabledTracks(TsExtractor extractor, long timeUs) {
|
||||
private void discardSamplesForDisabledTracks(HlsExtractor extractor, long timeUs) {
|
||||
if (!extractor.isPrepared()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -349,7 +349,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean haveSamplesForEnabledTracks(TsExtractor extractor) {
|
||||
private boolean haveSamplesForEnabledTracks(HlsExtractor extractor) {
|
||||
if (!extractor.isPrepared()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.hls;
|
||||
|
||||
import com.google.android.exoplayer.hls.parser.TsExtractor;
|
||||
import com.google.android.exoplayer.hls.parser.HlsExtractor;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ public final class TsChunk extends HlsChunk {
|
|||
/**
|
||||
* The extractor into which this chunk is being consumed.
|
||||
*/
|
||||
public final TsExtractor extractor;
|
||||
public final HlsExtractor extractor;
|
||||
|
||||
private int loadPosition;
|
||||
private volatile boolean loadFinished;
|
||||
|
|
@ -60,16 +60,17 @@ public final class TsChunk extends HlsChunk {
|
|||
/**
|
||||
* @param dataSource A {@link DataSource} for loading the data.
|
||||
* @param dataSpec Defines the data to be loaded.
|
||||
* @param extractor An extractor to parse samples from the data.
|
||||
* @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 chunkIndex The index of the chunk.
|
||||
* @param isLastChunk True if this is the last chunk in the media. False otherwise.
|
||||
*/
|
||||
public TsChunk(DataSource dataSource, DataSpec dataSpec, TsExtractor tsExtractor,
|
||||
public TsChunk(DataSource dataSource, DataSpec dataSpec, HlsExtractor extractor,
|
||||
int variantIndex, long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk) {
|
||||
super(dataSource, dataSpec);
|
||||
this.extractor = tsExtractor;
|
||||
this.extractor = extractor;
|
||||
this.variantIndex = variantIndex;
|
||||
this.startTimeUs = startTimeUs;
|
||||
this.endTimeUs = endTimeUs;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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.parser;
|
||||
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
import com.google.android.exoplayer.SampleHolder;
|
||||
import com.google.android.exoplayer.upstream.BufferPool;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS
|
||||
* headers.
|
||||
*/
|
||||
public class AdtsExtractor extends HlsExtractor {
|
||||
|
||||
private static final int MAX_PACKET_SIZE = 200;
|
||||
|
||||
private final long firstSampleTimestamp;
|
||||
private final ParsableByteArray packetBuffer;
|
||||
private final AdtsReader adtsReader;
|
||||
|
||||
// Accessed only by the loading thread.
|
||||
private boolean firstPacket;
|
||||
// Accessed by both the loading and consuming threads.
|
||||
private volatile boolean prepared;
|
||||
|
||||
public AdtsExtractor(boolean shouldSpliceIn, long firstSampleTimestamp, BufferPool bufferPool) {
|
||||
super(shouldSpliceIn);
|
||||
this.firstSampleTimestamp = firstSampleTimestamp;
|
||||
packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE);
|
||||
adtsReader = new AdtsReader(bufferPool);
|
||||
firstPacket = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrackCount() {
|
||||
Assertions.checkState(prepared);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaFormat getFormat(int track) {
|
||||
Assertions.checkState(prepared);
|
||||
return adtsReader.getMediaFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrepared() {
|
||||
return prepared;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
adtsReader.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLargestSampleTimestamp() {
|
||||
return adtsReader.getLargestParsedTimestampUs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getSample(int track, SampleHolder holder) {
|
||||
Assertions.checkState(prepared);
|
||||
Assertions.checkState(track == 0);
|
||||
return adtsReader.getSample(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void discardUntil(int track, long timeUs) {
|
||||
Assertions.checkState(prepared);
|
||||
Assertions.checkState(track == 0);
|
||||
adtsReader.discardUntil(timeUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSamples(int track) {
|
||||
Assertions.checkState(prepared);
|
||||
Assertions.checkState(track == 0);
|
||||
return !adtsReader.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(DataSource dataSource) throws IOException {
|
||||
int bytesRead = dataSource.read(packetBuffer.data, 0, MAX_PACKET_SIZE);
|
||||
if (bytesRead == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
packetBuffer.setPosition(0);
|
||||
packetBuffer.setLimit(bytesRead);
|
||||
|
||||
// TODO: Make it possible for adtsReader to consume the dataSource directly, so that it becomes
|
||||
// unnecessary to copy the data through packetBuffer.
|
||||
adtsReader.consume(packetBuffer, firstSampleTimestamp, firstPacket);
|
||||
firstPacket = false;
|
||||
if (!prepared) {
|
||||
prepared = adtsReader.hasMediaFormat();
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SampleQueue getSampleQueue(int track) {
|
||||
Assertions.checkState(track == 0);
|
||||
return adtsReader;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ import java.util.Collections;
|
|||
/**
|
||||
* Parses a continuous ADTS byte stream and extracts individual frames.
|
||||
*/
|
||||
/* package */ class AdtsReader extends PesPayloadReader {
|
||||
/* package */ class AdtsReader extends ElementaryStreamReader {
|
||||
|
||||
private static final int STATE_FINDING_SYNC = 0;
|
||||
private static final int STATE_READING_HEADER = 1;
|
||||
|
|
@ -137,6 +137,8 @@ import java.util.Collections;
|
|||
if (found) {
|
||||
hasCrc = (adtsData[i] & 0x1) == 0;
|
||||
pesBuffer.setPosition(i + 1);
|
||||
// Reset lastByteWasFF for next time.
|
||||
lastByteWasFF = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ import com.google.android.exoplayer.upstream.BufferPool;
|
|||
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
|
||||
/**
|
||||
* Extracts individual samples from continuous byte stream, preserving original order.
|
||||
* Extracts individual samples from an elementary media stream, preserving original order.
|
||||
*/
|
||||
/* package */ abstract class PesPayloadReader extends SampleQueue {
|
||||
/* package */ abstract class ElementaryStreamReader extends SampleQueue {
|
||||
|
||||
protected PesPayloadReader(BufferPool bufferPool) {
|
||||
protected ElementaryStreamReader(BufferPool bufferPool) {
|
||||
super(bufferPool);
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ import java.util.List;
|
|||
/**
|
||||
* Parses a continuous H264 byte stream and extracts individual frames.
|
||||
*/
|
||||
/* package */ class H264Reader extends PesPayloadReader {
|
||||
/* package */ class H264Reader extends ElementaryStreamReader {
|
||||
|
||||
private static final int NAL_UNIT_TYPE_IDR = 5;
|
||||
private static final int NAL_UNIT_TYPE_SEI = 6;
|
||||
|
|
@ -44,6 +44,8 @@ import java.util.List;
|
|||
private final NalUnitTargetBuffer pps;
|
||||
private final NalUnitTargetBuffer sei;
|
||||
|
||||
private int scratchEscapeCount;
|
||||
private int[] scratchEscapePositions;
|
||||
private boolean isKeyframe;
|
||||
|
||||
public H264Reader(BufferPool bufferPool, SeiReader seiReader) {
|
||||
|
|
@ -53,6 +55,7 @@ import java.util.List;
|
|||
sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128);
|
||||
pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128);
|
||||
sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128);
|
||||
scratchEscapePositions = new int[10];
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -133,7 +136,8 @@ import java.util.List;
|
|||
sps.endNalUnit(discardPadding);
|
||||
pps.endNalUnit(discardPadding);
|
||||
if (sei.endNalUnit(discardPadding)) {
|
||||
seiReader.read(sei.nalData, 0, pesTimeUs);
|
||||
int unescapedLength = unescapeStream(sei.nalData, sei.nalLength);
|
||||
seiReader.read(sei.nalData, 0, unescapedLength, pesTimeUs);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,8 +151,8 @@ import java.util.List;
|
|||
initializationData.add(ppsData);
|
||||
|
||||
// Unescape and then parse the SPS unit.
|
||||
byte[] unescapedSps = unescapeStream(spsData, 0, spsData.length);
|
||||
ParsableBitArray bitArray = new ParsableBitArray(unescapedSps);
|
||||
unescapeStream(sps.nalData, sps.nalLength);
|
||||
ParsableBitArray bitArray = new ParsableBitArray(sps.nalData);
|
||||
bitArray.skipBits(32); // NAL header
|
||||
int profileIdc = bitArray.readBits(8);
|
||||
bitArray.skipBits(16); // constraint bits (6), reserved (2) and level_idc (8)
|
||||
|
|
@ -242,36 +246,45 @@ import java.util.List;
|
|||
}
|
||||
|
||||
/**
|
||||
* Replaces occurrences of [0, 0, 3] with [0, 0].
|
||||
* Unescapes {@code data} up to the specified limit, replacing occurrences of [0, 0, 3] with
|
||||
* [0, 0]. The unescaped data is returned in-place, with the return value indicating its length.
|
||||
* <p>
|
||||
* See ISO/IEC 14496-10:2005(E) page 36 for more information.
|
||||
*
|
||||
* @param data The data to unescape.
|
||||
* @param limit The limit (exclusive) of the data to unescape.
|
||||
* @return The length of the unescaped data.
|
||||
*/
|
||||
private byte[] unescapeStream(byte[] data, int offset, int limit) {
|
||||
int position = offset;
|
||||
List<Integer> escapePositions = new ArrayList<Integer>();
|
||||
private int unescapeStream(byte[] data, int limit) {
|
||||
int position = 0;
|
||||
scratchEscapeCount = 0;
|
||||
while (position < limit) {
|
||||
position = findNextUnescapeIndex(data, position, limit);
|
||||
if (position < limit) {
|
||||
escapePositions.add(position);
|
||||
if (scratchEscapePositions.length <= scratchEscapeCount) {
|
||||
// Grow scratchEscapePositions to hold a larger number of positions.
|
||||
scratchEscapePositions = Arrays.copyOf(scratchEscapePositions,
|
||||
scratchEscapePositions.length * 2);
|
||||
}
|
||||
scratchEscapePositions[scratchEscapeCount++] = position;
|
||||
position += 3;
|
||||
}
|
||||
}
|
||||
|
||||
int escapeCount = escapePositions.size();
|
||||
int escapedPosition = offset; // The position being read from.
|
||||
int unescapedLength = limit - scratchEscapeCount;
|
||||
int escapedPosition = 0; // The position being read from.
|
||||
int unescapedPosition = 0; // The position being written to.
|
||||
byte[] unescapedData = new byte[limit - offset - escapeCount];
|
||||
for (int i = 0; i < escapeCount; i++) {
|
||||
int nextEscapePosition = escapePositions.get(i);
|
||||
for (int i = 0; i < scratchEscapeCount; i++) {
|
||||
int nextEscapePosition = scratchEscapePositions[i];
|
||||
int copyLength = nextEscapePosition - escapedPosition;
|
||||
System.arraycopy(data, escapedPosition, unescapedData, unescapedPosition, copyLength);
|
||||
System.arraycopy(data, escapedPosition, data, unescapedPosition, copyLength);
|
||||
escapedPosition += copyLength + 3;
|
||||
unescapedPosition += copyLength + 2;
|
||||
}
|
||||
|
||||
int remainingLength = unescapedData.length - unescapedPosition;
|
||||
System.arraycopy(data, escapedPosition, unescapedData, unescapedPosition, remainingLength);
|
||||
return unescapedData;
|
||||
int remainingLength = unescapedLength - unescapedPosition;
|
||||
System.arraycopy(data, escapedPosition, data, unescapedPosition, remainingLength);
|
||||
return unescapedLength;
|
||||
}
|
||||
|
||||
private int findNextUnescapeIndex(byte[] bytes, int offset, int limit) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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.parser;
|
||||
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
import com.google.android.exoplayer.SampleHolder;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Facilitates extraction of media samples for HLS playbacks.
|
||||
*/
|
||||
// TODO: Consider consolidating more common logic in this base class.
|
||||
public abstract class HlsExtractor {
|
||||
|
||||
private final boolean shouldSpliceIn;
|
||||
|
||||
// Accessed only by the consuming thread.
|
||||
private boolean spliceConfigured;
|
||||
|
||||
public HlsExtractor(boolean shouldSpliceIn) {
|
||||
this.shouldSpliceIn = shouldSpliceIn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to configure a splice from this extractor to the next.
|
||||
* <p>
|
||||
* The splice is performed such that for each track the samples read from the next extractor
|
||||
* start with a keyframe, and continue from where the samples read from this extractor finish.
|
||||
* A successful splice may discard samples from either or both extractors.
|
||||
* <p>
|
||||
* Splice configuration may fail if the next extractor is not yet in a state that allows the
|
||||
* splice to be performed. Calling this method is a noop if the splice has already been
|
||||
* configured. Hence this method should be called repeatedly during the window within which a
|
||||
* splice can be performed.
|
||||
*
|
||||
* @param nextExtractor The extractor being spliced to.
|
||||
*/
|
||||
public final void configureSpliceTo(HlsExtractor nextExtractor) {
|
||||
if (spliceConfigured || !nextExtractor.shouldSpliceIn || !nextExtractor.isPrepared()) {
|
||||
// The splice is already configured, or the next extractor doesn't want to be spliced in, or
|
||||
// the next extractor isn't ready to be spliced in.
|
||||
return;
|
||||
}
|
||||
boolean spliceConfigured = true;
|
||||
int trackCount = getTrackCount();
|
||||
for (int i = 0; i < trackCount; i++) {
|
||||
spliceConfigured &= getSampleQueue(i).configureSpliceTo(nextExtractor.getSampleQueue(i));
|
||||
}
|
||||
this.spliceConfigured = spliceConfigured;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of available tracks.
|
||||
* <p>
|
||||
* This method should only be called after the extractor has been prepared.
|
||||
*
|
||||
* @return The number of available tracks.
|
||||
*/
|
||||
public abstract int getTrackCount();
|
||||
|
||||
/**
|
||||
* Gets the format of the specified track.
|
||||
* <p>
|
||||
* This method must only be called after the extractor has been prepared.
|
||||
*
|
||||
* @param track The track index.
|
||||
* @return The corresponding format.
|
||||
*/
|
||||
public abstract MediaFormat getFormat(int track);
|
||||
|
||||
/**
|
||||
* Whether the extractor is prepared.
|
||||
*
|
||||
* @return True if the extractor is prepared. False otherwise.
|
||||
*/
|
||||
public abstract boolean isPrepared();
|
||||
|
||||
/**
|
||||
* Releases the extractor, recycling any pending or incomplete samples to the sample pool.
|
||||
* <p>
|
||||
* This method should not be called whilst {@link #read(DataSource)} is also being invoked.
|
||||
*/
|
||||
public abstract void release();
|
||||
|
||||
/**
|
||||
* Gets the largest timestamp of any sample parsed by the extractor.
|
||||
*
|
||||
* @return The largest timestamp, or {@link Long#MIN_VALUE} if no samples have been parsed.
|
||||
*/
|
||||
public abstract long getLargestSampleTimestamp();
|
||||
|
||||
/**
|
||||
* Gets the next sample for the specified track.
|
||||
*
|
||||
* @param track The track from which to read.
|
||||
* @param holder A {@link SampleHolder} into which the sample should be read.
|
||||
* @return True if a sample was read. False otherwise.
|
||||
*/
|
||||
public abstract boolean getSample(int track, SampleHolder holder);
|
||||
|
||||
/**
|
||||
* Discards samples for the specified track up to the specified time.
|
||||
*
|
||||
* @param track The track from which samples should be discarded.
|
||||
* @param timeUs The time up to which samples should be discarded, in microseconds.
|
||||
*/
|
||||
public abstract void discardUntil(int track, long timeUs);
|
||||
|
||||
/**
|
||||
* Whether samples are available for reading from {@link #getSample(int, SampleHolder)} for the
|
||||
* specified track.
|
||||
*
|
||||
* @return True if samples are available for reading from {@link #getSample(int, SampleHolder)}
|
||||
* for the specified track. False otherwise.
|
||||
*/
|
||||
public abstract boolean hasSamples(int track);
|
||||
|
||||
/**
|
||||
* Reads up to a single TS packet.
|
||||
*
|
||||
* @param dataSource The {@link DataSource} from which to read.
|
||||
* @throws IOException If an error occurred reading from the source.
|
||||
* @return The number of bytes read from the source.
|
||||
*/
|
||||
public abstract int read(DataSource dataSource) throws IOException;
|
||||
|
||||
/**
|
||||
* Gets the {@link SampleQueue} for the specified track.
|
||||
*
|
||||
* @param track The track index.
|
||||
* @return The corresponding sample queue.
|
||||
*/
|
||||
protected abstract SampleQueue getSampleQueue(int track);
|
||||
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||
/**
|
||||
* Parses ID3 data and extracts individual text information frames.
|
||||
*/
|
||||
/* package */ class Id3Reader extends PesPayloadReader {
|
||||
/* package */ class Id3Reader extends ElementaryStreamReader {
|
||||
|
||||
public Id3Reader(BufferPool bufferPool) {
|
||||
super(bufferPool);
|
||||
|
|
|
|||
|
|
@ -36,14 +36,33 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||
seiBuffer = new ParsableByteArray();
|
||||
}
|
||||
|
||||
public void read(byte[] data, int position, long pesTimeUs) {
|
||||
seiBuffer.reset(data, data.length);
|
||||
public void read(byte[] data, int position, int limit, long pesTimeUs) {
|
||||
seiBuffer.reset(data, limit);
|
||||
// Skip the NAL prefix and type.
|
||||
seiBuffer.setPosition(position + 4);
|
||||
int ccDataSize = Eia608Parser.parseHeader(seiBuffer);
|
||||
if (ccDataSize > 0) {
|
||||
startSample(pesTimeUs);
|
||||
appendData(seiBuffer, ccDataSize);
|
||||
commitSample(true);
|
||||
|
||||
int b;
|
||||
while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) {
|
||||
// Parse payload type.
|
||||
int payloadType = 0;
|
||||
do {
|
||||
b = seiBuffer.readUnsignedByte();
|
||||
payloadType += b;
|
||||
} while (b == 0xFF);
|
||||
// Parse payload size.
|
||||
int payloadSize = 0;
|
||||
do {
|
||||
b = seiBuffer.readUnsignedByte();
|
||||
payloadSize += b;
|
||||
} while (b == 0xFF);
|
||||
// Process the payload. We only support EIA-608 payloads currently.
|
||||
if (Eia608Parser.inspectSeiMessage(payloadType, payloadSize, seiBuffer)) {
|
||||
startSample(pesTimeUs);
|
||||
appendData(seiBuffer, payloadSize);
|
||||
commitSample(true);
|
||||
} else {
|
||||
seiBuffer.skip(payloadSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import java.io.IOException;
|
|||
/**
|
||||
* Facilitates the extraction of data from the MPEG-2 TS container format.
|
||||
*/
|
||||
public final class TsExtractor {
|
||||
public final class TsExtractor extends HlsExtractor {
|
||||
|
||||
private static final String TAG = "TsExtractor";
|
||||
|
||||
|
|
@ -51,13 +51,9 @@ public final class TsExtractor {
|
|||
private final SparseArray<SampleQueue> sampleQueues; // Indexed by streamType
|
||||
private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
|
||||
private final BufferPool bufferPool;
|
||||
private final boolean shouldSpliceIn;
|
||||
private final long firstSampleTimestamp;
|
||||
private final ParsableBitArray tsScratch;
|
||||
|
||||
// Accessed only by the consuming thread.
|
||||
private boolean spliceConfigured;
|
||||
|
||||
// Accessed only by the loading thread.
|
||||
private int tsPacketBytesRead;
|
||||
private long timestampOffsetUs;
|
||||
|
|
@ -66,9 +62,9 @@ public final class TsExtractor {
|
|||
// Accessed by both the loading and consuming threads.
|
||||
private volatile boolean prepared;
|
||||
|
||||
public TsExtractor(long firstSampleTimestamp, boolean shouldSpliceIn, BufferPool bufferPool) {
|
||||
public TsExtractor(boolean shouldSpliceIn, long firstSampleTimestamp, BufferPool bufferPool) {
|
||||
super(shouldSpliceIn);
|
||||
this.firstSampleTimestamp = firstSampleTimestamp;
|
||||
this.shouldSpliceIn = shouldSpliceIn;
|
||||
this.bufferPool = bufferPool;
|
||||
tsScratch = new ParsableBitArray(new byte[3]);
|
||||
tsPacketBuffer = new ParsableByteArray(TS_PACKET_SIZE);
|
||||
|
|
@ -78,86 +74,31 @@ public final class TsExtractor {
|
|||
lastPts = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of available tracks.
|
||||
* <p>
|
||||
* This method should only be called after the extractor has been prepared.
|
||||
*
|
||||
* @return The number of available tracks.
|
||||
*/
|
||||
@Override
|
||||
public int getTrackCount() {
|
||||
Assertions.checkState(prepared);
|
||||
return sampleQueues.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the format of the specified track.
|
||||
* <p>
|
||||
* This method must only be called after the extractor has been prepared.
|
||||
*
|
||||
* @param track The track index.
|
||||
* @return The corresponding format.
|
||||
*/
|
||||
@Override
|
||||
public MediaFormat getFormat(int track) {
|
||||
Assertions.checkState(prepared);
|
||||
return sampleQueues.valueAt(track).getMediaFormat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the extractor is prepared.
|
||||
*
|
||||
* @return True if the extractor is prepared. False otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean isPrepared() {
|
||||
return prepared;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the extractor, recycling any pending or incomplete samples to the sample pool.
|
||||
* <p>
|
||||
* This method should not be called whilst {@link #read(DataSource)} is also being invoked.
|
||||
*/
|
||||
@Override
|
||||
public void release() {
|
||||
for (int i = 0; i < sampleQueues.size(); i++) {
|
||||
sampleQueues.valueAt(i).release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to configure a splice from this extractor to the next.
|
||||
* <p>
|
||||
* The splice is performed such that for each track the samples read from the next extractor
|
||||
* start with a keyframe, and continue from where the samples read from this extractor finish.
|
||||
* A successful splice may discard samples from either or both extractors.
|
||||
* <p>
|
||||
* Splice configuration may fail if the next extractor is not yet in a state that allows the
|
||||
* splice to be performed. Calling this method is a noop if the splice has already been
|
||||
* configured. Hence this method should be called repeatedly during the window within which a
|
||||
* splice can be performed.
|
||||
*
|
||||
* @param nextExtractor The extractor being spliced to.
|
||||
*/
|
||||
public void configureSpliceTo(TsExtractor nextExtractor) {
|
||||
Assertions.checkState(prepared);
|
||||
if (spliceConfigured || !nextExtractor.shouldSpliceIn || !nextExtractor.isPrepared()) {
|
||||
// The splice is already configured, or the next extractor doesn't want to be spliced in, or
|
||||
// the next extractor isn't ready to be spliced in.
|
||||
return;
|
||||
}
|
||||
boolean spliceConfigured = true;
|
||||
for (int i = 0; i < sampleQueues.size(); i++) {
|
||||
spliceConfigured &= sampleQueues.valueAt(i).configureSpliceTo(
|
||||
nextExtractor.sampleQueues.valueAt(i));
|
||||
}
|
||||
this.spliceConfigured = spliceConfigured;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the largest timestamp of any sample parsed by the extractor.
|
||||
*
|
||||
* @return The largest timestamp, or {@link Long#MIN_VALUE} if no samples have been parsed.
|
||||
*/
|
||||
@Override
|
||||
public long getLargestSampleTimestamp() {
|
||||
long largestParsedTimestampUs = Long.MIN_VALUE;
|
||||
for (int i = 0; i < sampleQueues.size(); i++) {
|
||||
|
|
@ -167,36 +108,19 @@ public final class TsExtractor {
|
|||
return largestParsedTimestampUs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next sample for the specified track.
|
||||
*
|
||||
* @param track The track from which to read.
|
||||
* @param holder A {@link SampleHolder} into which the sample should be read.
|
||||
* @return True if a sample was read. False otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean getSample(int track, SampleHolder holder) {
|
||||
Assertions.checkState(prepared);
|
||||
return sampleQueues.valueAt(track).getSample(holder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards samples for the specified track up to the specified time.
|
||||
*
|
||||
* @param track The track from which samples should be discarded.
|
||||
* @param timeUs The time up to which samples should be discarded, in microseconds.
|
||||
*/
|
||||
@Override
|
||||
public void discardUntil(int track, long timeUs) {
|
||||
Assertions.checkState(prepared);
|
||||
sampleQueues.valueAt(track).discardUntil(timeUs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether samples are available for reading from {@link #getSample(int, SampleHolder)} for the
|
||||
* specified track.
|
||||
*
|
||||
* @return True if samples are available for reading from {@link #getSample(int, SampleHolder)}
|
||||
* for the specified track. False otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean hasSamples(int track) {
|
||||
Assertions.checkState(prepared);
|
||||
return !sampleQueues.valueAt(track).isEmpty();
|
||||
|
|
@ -215,13 +139,7 @@ public final class TsExtractor {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to a single TS packet.
|
||||
*
|
||||
* @param dataSource The {@link DataSource} from which to read.
|
||||
* @throws IOException If an error occurred reading from the source.
|
||||
* @return The number of bytes read from the source.
|
||||
*/
|
||||
@Override
|
||||
public int read(DataSource dataSource) throws IOException {
|
||||
int bytesRead = dataSource.read(tsPacketBuffer.data, tsPacketBytesRead,
|
||||
TS_PACKET_SIZE - tsPacketBytesRead);
|
||||
|
|
@ -276,6 +194,11 @@ public final class TsExtractor {
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SampleQueue getSampleQueue(int track) {
|
||||
return sampleQueues.valueAt(track);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts a PTS value to the corresponding time in microseconds, accounting for PTS wraparound.
|
||||
*
|
||||
|
|
@ -404,7 +327,7 @@ public final class TsExtractor {
|
|||
continue;
|
||||
}
|
||||
|
||||
PesPayloadReader pesPayloadReader = null;
|
||||
ElementaryStreamReader pesPayloadReader = null;
|
||||
switch (streamType) {
|
||||
case TS_STREAM_TYPE_AAC:
|
||||
pesPayloadReader = new AdtsReader(bufferPool);
|
||||
|
|
@ -444,7 +367,7 @@ public final class TsExtractor {
|
|||
private static final int MAX_HEADER_EXTENSION_SIZE = 5;
|
||||
|
||||
private final ParsableBitArray pesScratch;
|
||||
private final PesPayloadReader pesPayloadReader;
|
||||
private final ElementaryStreamReader pesPayloadReader;
|
||||
|
||||
private int state;
|
||||
private int bytesRead;
|
||||
|
|
@ -457,7 +380,7 @@ public final class TsExtractor {
|
|||
|
||||
private long timeUs;
|
||||
|
||||
public PesReader(PesPayloadReader pesPayloadReader) {
|
||||
public PesReader(ElementaryStreamReader pesPayloadReader) {
|
||||
this.pesPayloadReader = pesPayloadReader;
|
||||
pesScratch = new ParsableBitArray(new byte[HEADER_SIZE]);
|
||||
state = STATE_FINDING_HEADER;
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ public final class Mp4Util {
|
|||
}
|
||||
}
|
||||
|
||||
int limit = endOffset - 2;
|
||||
int limit = endOffset - 1;
|
||||
// We're looking for the NAL unit start code prefix 0x000001, followed by a byte that matches
|
||||
// the specified type. The value of i tracks the index of the third byte in the four bytes
|
||||
// being examined.
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public final class DefaultSampleSource implements SampleSource {
|
|||
Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED);
|
||||
trackStates[track] = TRACK_STATE_ENABLED;
|
||||
sampleExtractor.selectTrack(track);
|
||||
seekToUs(positionUs);
|
||||
seekToUsInternal(positionUs, positionUs != 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -131,17 +131,7 @@ public final class DefaultSampleSource implements SampleSource {
|
|||
@Override
|
||||
public void seekToUs(long positionUs) {
|
||||
Assertions.checkState(prepared);
|
||||
if (seekPositionUs != positionUs) {
|
||||
// Avoid duplicate calls to the underlying extractor's seek method in the case that there
|
||||
// have been no interleaving calls to readSample.
|
||||
seekPositionUs = positionUs;
|
||||
sampleExtractor.seekTo(positionUs);
|
||||
for (int i = 0; i < trackStates.length; ++i) {
|
||||
if (trackStates[i] != TRACK_STATE_DISABLED) {
|
||||
pendingDiscontinuities[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
seekToUsInternal(positionUs, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -158,4 +148,18 @@ public final class DefaultSampleSource implements SampleSource {
|
|||
}
|
||||
}
|
||||
|
||||
private void seekToUsInternal(long positionUs, boolean force) {
|
||||
// Unless forced, avoid duplicate calls to the underlying extractor's seek method in the case
|
||||
// that there have been no interleaving calls to readSample.
|
||||
if (force || seekPositionUs != positionUs) {
|
||||
seekPositionUs = positionUs;
|
||||
sampleExtractor.seekTo(positionUs);
|
||||
for (int i = 0; i < trackStates.length; ++i) {
|
||||
if (trackStates[i] != TRACK_STATE_DISABLED) {
|
||||
pendingDiscontinuities[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,14 +97,17 @@ public class Eia608Parser {
|
|||
}
|
||||
|
||||
/* package */ ClosedCaptionList parse(SampleHolder sampleHolder) {
|
||||
if (sampleHolder.size <= 0) {
|
||||
if (sampleHolder.size < 10) {
|
||||
return null;
|
||||
}
|
||||
|
||||
captions.clear();
|
||||
stringBuilder.setLength(0);
|
||||
seiBuffer.reset(sampleHolder.data.array());
|
||||
seiBuffer.skipBits(3); // reserved + process_cc_data_flag + zero_bit
|
||||
|
||||
// country_code (8) + provider_code (16) + user_identifier (32) + user_data_type_code (8) +
|
||||
// reserved (1) + process_cc_data_flag (1) + zero_bit (1)
|
||||
seiBuffer.skipBits(67);
|
||||
int ccCount = seiBuffer.readBits(5);
|
||||
seiBuffer.skipBits(8);
|
||||
|
||||
|
|
@ -177,52 +180,28 @@ public class Eia608Parser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses the beginning of SEI data and returns the size of underlying contains closed captions
|
||||
* data following the header. Returns 0 if the SEI doesn't contain any closed captions data.
|
||||
* Inspects an sei message to determine whether it contains EIA-608.
|
||||
* <p>
|
||||
* The position of {@code payload} is left unchanged.
|
||||
*
|
||||
* @param seiBuffer The buffer to read from.
|
||||
* @return The size of closed captions data.
|
||||
* @param payloadType The payload type of the message.
|
||||
* @param payloadLength The length of the payload.
|
||||
* @param payload A {@link ParsableByteArray} containing the payload.
|
||||
* @return True if the sei message contains EIA-608. False otherwise.
|
||||
*/
|
||||
public static int parseHeader(ParsableByteArray seiBuffer) {
|
||||
int b = 0;
|
||||
int payloadType = 0;
|
||||
|
||||
do {
|
||||
b = seiBuffer.readUnsignedByte();
|
||||
payloadType += b;
|
||||
} while (b == 0xFF);
|
||||
|
||||
if (payloadType != PAYLOAD_TYPE_CC) {
|
||||
return 0;
|
||||
public static boolean inspectSeiMessage(int payloadType, int payloadLength,
|
||||
ParsableByteArray payload) {
|
||||
if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int payloadSize = 0;
|
||||
do {
|
||||
b = seiBuffer.readUnsignedByte();
|
||||
payloadSize += b;
|
||||
} while (b == 0xFF);
|
||||
|
||||
if (payloadSize <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int countryCode = seiBuffer.readUnsignedByte();
|
||||
if (countryCode != COUNTRY_CODE) {
|
||||
return 0;
|
||||
}
|
||||
int providerCode = seiBuffer.readUnsignedShort();
|
||||
if (providerCode != PROVIDER_CODE) {
|
||||
return 0;
|
||||
}
|
||||
int userIdentifier = seiBuffer.readInt();
|
||||
if (userIdentifier != USER_ID) {
|
||||
return 0;
|
||||
}
|
||||
int userDataTypeCode = seiBuffer.readUnsignedByte();
|
||||
if (userDataTypeCode != USER_DATA_TYPE_CODE) {
|
||||
return 0;
|
||||
}
|
||||
return payloadSize;
|
||||
int startPosition = payload.getPosition();
|
||||
int countryCode = payload.readUnsignedByte();
|
||||
int providerCode = payload.readUnsignedShort();
|
||||
int userIdentifier = payload.readInt();
|
||||
int userDataTypeCode = payload.readUnsignedByte();
|
||||
payload.setPosition(startPosition);
|
||||
return countryCode == COUNTRY_CODE && providerCode == PROVIDER_CODE
|
||||
&& userIdentifier == USER_ID && userDataTypeCode == USER_DATA_TYPE_CODE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue