mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Implement ID3 Metadata support for audio only HLS.
Issue: #862 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=111403855
This commit is contained in:
parent
1e4f2f6a1f
commit
69a42b60f8
7 changed files with 383 additions and 51 deletions
|
|
@ -0,0 +1,175 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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.extractor.ts;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.C;
|
||||||
|
import com.google.android.exoplayer.testutil.FakeTrackOutput;
|
||||||
|
import com.google.android.exoplayer.testutil.TestUtil;
|
||||||
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link AdtsReader}.
|
||||||
|
*/
|
||||||
|
public class AdtsReaderTest extends TestCase {
|
||||||
|
|
||||||
|
public static final byte[] ID3_DATA_1 = TestUtil.createByteArray(
|
||||||
|
0x49, 0x44, 0x33, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x54, 0x58,
|
||||||
|
0x58, 0x58, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x03, 0x00, 0x20, 0x2a,
|
||||||
|
0x2a, 0x2a, 0x20, 0x54, 0x48, 0x49, 0x53, 0x20, 0x49, 0x53, 0x20, 0x54,
|
||||||
|
0x69, 0x6d, 0x65, 0x64, 0x20, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74,
|
||||||
|
0x61, 0x20, 0x40, 0x20, 0x2d, 0x2d, 0x20, 0x30, 0x30, 0x3a, 0x30, 0x30,
|
||||||
|
0x3a, 0x30, 0x30, 0x2e, 0x30, 0x20, 0x2a, 0x2a, 0x2a, 0x20, 0x00);
|
||||||
|
|
||||||
|
public static final byte[] ID3_DATA_2 = TestUtil.createByteArray(
|
||||||
|
0x49,
|
||||||
|
0x44, 0x33, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x50, 0x52, 0x49,
|
||||||
|
0x56, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61,
|
||||||
|
0x70, 0x70, 0x6c, 0x65, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69,
|
||||||
|
0x6e, 0x67, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
|
||||||
|
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||||
|
0x61, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0xbb, 0xa0);
|
||||||
|
|
||||||
|
public static final byte[] ADTS_HEADER = TestUtil.createByteArray(
|
||||||
|
0xff, 0xf1, 0x50, 0x80, 0x01, 0xdf, 0xfc);
|
||||||
|
|
||||||
|
public static final byte[] ADTS_CONTENT = TestUtil.createByteArray(
|
||||||
|
0x20, 0x00, 0x20, 0x00, 0x00, 0x80, 0x0e);
|
||||||
|
|
||||||
|
private static final byte TEST_DATA[] = TestUtil.joinByteArrays(
|
||||||
|
ID3_DATA_1,
|
||||||
|
ID3_DATA_2,
|
||||||
|
ADTS_HEADER,
|
||||||
|
ADTS_CONTENT);
|
||||||
|
|
||||||
|
private static final long ADTS_SAMPLE_DURATION = 23219L;
|
||||||
|
|
||||||
|
private ParsableByteArray data;
|
||||||
|
|
||||||
|
private AdtsReader adtsReader;
|
||||||
|
private FakeTrackOutput adtsOutput;
|
||||||
|
private FakeTrackOutput id3Output;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
adtsOutput = new FakeTrackOutput();
|
||||||
|
id3Output = new FakeTrackOutput();
|
||||||
|
adtsReader = new AdtsReader(adtsOutput, id3Output);
|
||||||
|
data = new ParsableByteArray(TEST_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSkipToNextSample() throws Exception {
|
||||||
|
for (int i = 1; i <= ID3_DATA_1.length + ID3_DATA_2.length; i++) {
|
||||||
|
data.setPosition(i);
|
||||||
|
feed();
|
||||||
|
// Once the data position set to ID3_DATA_1.length, no more id3 samples are read
|
||||||
|
int id3SampleCount = Math.min(i, ID3_DATA_1.length);
|
||||||
|
assertSampleCounts(id3SampleCount, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSkipToNextSampleResetsState() throws Exception {
|
||||||
|
data = new ParsableByteArray(TestUtil.joinByteArrays(
|
||||||
|
ADTS_HEADER,
|
||||||
|
ADTS_CONTENT,
|
||||||
|
// Adts sample missing the first sync byte
|
||||||
|
Arrays.copyOfRange(ADTS_HEADER, 1, ADTS_HEADER.length),
|
||||||
|
ADTS_CONTENT));
|
||||||
|
feed();
|
||||||
|
assertSampleCounts(0, 1);
|
||||||
|
adtsOutput.assertSample(0, ADTS_CONTENT, 0, C.SAMPLE_FLAG_SYNC, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNoData() throws Exception {
|
||||||
|
feedLimited(0);
|
||||||
|
assertSampleCounts(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNotEnoughDataForIdentifier() throws Exception {
|
||||||
|
feedLimited(3 - 1);
|
||||||
|
assertSampleCounts(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNotEnoughDataForHeader() throws Exception {
|
||||||
|
feedLimited(10 - 1);
|
||||||
|
assertSampleCounts(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNotEnoughDataForWholeId3Packet() throws Exception {
|
||||||
|
feedLimited(ID3_DATA_1.length - 1);
|
||||||
|
assertSampleCounts(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConsumeWholeId3Packet() throws Exception {
|
||||||
|
feedLimited(ID3_DATA_1.length);
|
||||||
|
assertSampleCounts(1, 0);
|
||||||
|
id3Output.assertSample(0, ID3_DATA_1, 0, C.SAMPLE_FLAG_SYNC, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testMultiId3Packet() throws Exception {
|
||||||
|
feedLimited(ID3_DATA_1.length + ID3_DATA_2.length - 1);
|
||||||
|
assertSampleCounts(1, 0);
|
||||||
|
id3Output.assertSample(0, ID3_DATA_1, 0, C.SAMPLE_FLAG_SYNC, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testMultiId3PacketConsumed() throws Exception {
|
||||||
|
feedLimited(ID3_DATA_1.length + ID3_DATA_2.length);
|
||||||
|
assertSampleCounts(2, 0);
|
||||||
|
id3Output.assertSample(0, ID3_DATA_1, 0, C.SAMPLE_FLAG_SYNC, null);
|
||||||
|
id3Output.assertSample(1, ID3_DATA_2, 0, C.SAMPLE_FLAG_SYNC, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testMultiPacketConsumed() throws Exception {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
data.setPosition(0);
|
||||||
|
adtsReader.consume(data, 0, i == 0);
|
||||||
|
|
||||||
|
long timeUs = ADTS_SAMPLE_DURATION * i;
|
||||||
|
int j = i * 2;
|
||||||
|
assertSampleCounts(j + 2, i + 1);
|
||||||
|
|
||||||
|
id3Output.assertSample(j, ID3_DATA_1, timeUs, C.SAMPLE_FLAG_SYNC, null);
|
||||||
|
id3Output.assertSample(j + 1, ID3_DATA_2, timeUs, C.SAMPLE_FLAG_SYNC, null);
|
||||||
|
adtsOutput.assertSample(i, ADTS_CONTENT, timeUs, C.SAMPLE_FLAG_SYNC, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAdtsDataOnly() throws Exception {
|
||||||
|
data.setPosition(ID3_DATA_1.length + ID3_DATA_2.length);
|
||||||
|
feed();
|
||||||
|
assertSampleCounts(0, 1);
|
||||||
|
adtsOutput.assertSample(0, ADTS_CONTENT, 0, C.SAMPLE_FLAG_SYNC, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void feedLimited(int limit) {
|
||||||
|
data.setLimit(limit);
|
||||||
|
feed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void feed() {
|
||||||
|
adtsReader.consume(data, 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertSampleCounts(int id3SampleCount, int adtsSampleCount) {
|
||||||
|
id3Output.assertSampleCount(id3SampleCount);
|
||||||
|
adtsOutput.assertSampleCount(adtsSampleCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
package com.google.android.exoplayer;
|
package com.google.android.exoplayer;
|
||||||
|
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
|
@ -180,6 +181,11 @@ public final class MediaFormat {
|
||||||
NO_VALUE);
|
NO_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MediaFormat createId3Format() {
|
||||||
|
return createFormatForMimeType(null, MimeTypes.APPLICATION_ID3, MediaFormat.NO_VALUE,
|
||||||
|
C.UNKNOWN_TIME_US);
|
||||||
|
}
|
||||||
|
|
||||||
/* package */ MediaFormat(String trackId, String mimeType, int bitrate, int maxInputSize,
|
/* package */ MediaFormat(String trackId, String mimeType, int bitrate, int maxInputSize,
|
||||||
long durationUs, int width, int height, int rotationDegrees, float pixelWidthHeightRatio,
|
long durationUs, int width, int height, int rotationDegrees, float pixelWidthHeightRatio,
|
||||||
int channelCount, int sampleRate, String language, long subsampleOffsetUs,
|
int channelCount, int sampleRate, String language, long subsampleOffsetUs,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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.extractor;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dummy {@link TrackOutput} implementation.
|
||||||
|
*/
|
||||||
|
public class DummyTrackOutput implements TrackOutput {
|
||||||
|
@Override
|
||||||
|
public void format(MediaFormat format) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
return input.skip(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sampleData(ParsableByteArray data, int length) {
|
||||||
|
data.skipBytes(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -110,7 +110,7 @@ public final class AdtsExtractor implements Extractor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput output) {
|
public void init(ExtractorOutput output) {
|
||||||
adtsReader = new AdtsReader(output.track(0));
|
adtsReader = new AdtsReader(output.track(0), output.track(1));
|
||||||
output.endTracks();
|
output.endTracks();
|
||||||
output.seekMap(SeekMap.UNSEEKABLE);
|
output.seekMap(SeekMap.UNSEEKABLE);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -32,20 +33,34 @@ import java.util.Collections;
|
||||||
*/
|
*/
|
||||||
/* package */ final class AdtsReader extends ElementaryStreamReader {
|
/* package */ final class AdtsReader extends ElementaryStreamReader {
|
||||||
|
|
||||||
private static final int STATE_FINDING_SYNC = 0;
|
private static final int STATE_FINDING_SAMPLE = 0;
|
||||||
private static final int STATE_READING_HEADER = 1;
|
private static final int STATE_READING_ID3_HEADER = 1;
|
||||||
private static final int STATE_READING_SAMPLE = 2;
|
private static final int STATE_READING_ADTS_HEADER = 2;
|
||||||
|
private static final int STATE_READING_SAMPLE = 3;
|
||||||
|
|
||||||
private static final int HEADER_SIZE = 5;
|
private static final int HEADER_SIZE = 5;
|
||||||
private static final int CRC_SIZE = 2;
|
private static final int CRC_SIZE = 2;
|
||||||
|
|
||||||
|
// Match states used while looking for the next sample
|
||||||
|
private static final int MATCH_STATE_VALUE_SHIFT = 8;
|
||||||
|
private static final int MATCH_STATE_START = 1 << MATCH_STATE_VALUE_SHIFT;
|
||||||
|
private static final int MATCH_STATE_FF = 2 << MATCH_STATE_VALUE_SHIFT;
|
||||||
|
private static final int MATCH_STATE_I = 3 << MATCH_STATE_VALUE_SHIFT;
|
||||||
|
private static final int MATCH_STATE_ID = 4 << MATCH_STATE_VALUE_SHIFT;
|
||||||
|
|
||||||
|
private static final int ID3_HEADER_SIZE = 10;
|
||||||
|
private static final int ID3_SIZE_OFFSET = 6;
|
||||||
|
private static final byte[] ID3_IDENTIFIER = {'I', 'D', '3'};
|
||||||
|
|
||||||
private final ParsableBitArray adtsScratch;
|
private final ParsableBitArray adtsScratch;
|
||||||
|
private final ParsableByteArray id3HeaderBuffer;
|
||||||
|
private final TrackOutput id3Output;
|
||||||
|
|
||||||
private int state;
|
private int state;
|
||||||
private int bytesRead;
|
private int bytesRead;
|
||||||
|
|
||||||
// Used to find the header.
|
private int matchState;
|
||||||
private boolean lastByteWasFF;
|
|
||||||
private boolean hasCrc;
|
private boolean hasCrc;
|
||||||
|
|
||||||
// Used when parsing the header.
|
// Used when parsing the header.
|
||||||
|
|
@ -56,17 +71,25 @@ import java.util.Collections;
|
||||||
// Used when reading the samples.
|
// Used when reading the samples.
|
||||||
private long timeUs;
|
private long timeUs;
|
||||||
|
|
||||||
public AdtsReader(TrackOutput output) {
|
private TrackOutput currentOutput;
|
||||||
|
private long currentSampleDuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param output A {@link TrackOutput} to which AAC samples should be written.
|
||||||
|
* @param id3Output A {@link TrackOutput} to which ID3 samples should be written.
|
||||||
|
*/
|
||||||
|
public AdtsReader(TrackOutput output, TrackOutput id3Output) {
|
||||||
super(output);
|
super(output);
|
||||||
|
this.id3Output = id3Output;
|
||||||
|
id3Output.format(MediaFormat.createId3Format());
|
||||||
adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]);
|
adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]);
|
||||||
state = STATE_FINDING_SYNC;
|
id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE));
|
||||||
|
setFindingSampleState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek() {
|
public void seek() {
|
||||||
state = STATE_FINDING_SYNC;
|
setFindingSampleState();
|
||||||
bytesRead = 0;
|
|
||||||
lastByteWasFF = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -76,30 +99,22 @@ import java.util.Collections;
|
||||||
}
|
}
|
||||||
while (data.bytesLeft() > 0) {
|
while (data.bytesLeft() > 0) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_FINDING_SYNC:
|
case STATE_FINDING_SAMPLE:
|
||||||
if (skipToNextSync(data)) {
|
findNextSample(data);
|
||||||
bytesRead = 0;
|
break;
|
||||||
state = STATE_READING_HEADER;
|
case STATE_READING_ID3_HEADER:
|
||||||
|
if (continueRead(data, id3HeaderBuffer.data, ID3_HEADER_SIZE)) {
|
||||||
|
parseId3Header();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STATE_READING_HEADER:
|
case STATE_READING_ADTS_HEADER:
|
||||||
int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE;
|
int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE;
|
||||||
if (continueRead(data, adtsScratch.data, targetLength)) {
|
if (continueRead(data, adtsScratch.data, targetLength)) {
|
||||||
parseHeader();
|
parseAdtsHeader();
|
||||||
bytesRead = 0;
|
|
||||||
state = STATE_READING_SAMPLE;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STATE_READING_SAMPLE:
|
case STATE_READING_SAMPLE:
|
||||||
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
|
readSample(data);
|
||||||
output.sampleData(data, bytesToRead);
|
|
||||||
bytesRead += bytesToRead;
|
|
||||||
if (bytesRead == sampleSize) {
|
|
||||||
output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
|
|
||||||
timeUs += sampleDurationUs;
|
|
||||||
bytesRead = 0;
|
|
||||||
state = STATE_FINDING_SYNC;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -127,36 +142,109 @@ import java.util.Collections;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Locates the next sync word, advancing the position to the byte that immediately follows it.
|
* Sets the state to STATE_FINDING_SAMPLE.
|
||||||
* If a sync word was not located, the position is advanced to the limit.
|
*/
|
||||||
|
private void setFindingSampleState() {
|
||||||
|
state = STATE_FINDING_SAMPLE;
|
||||||
|
bytesRead = 0;
|
||||||
|
matchState = MATCH_STATE_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state to STATE_READING_ID3_HEADER and resets the fields required for
|
||||||
|
* {@link #parseId3Header()}.
|
||||||
|
*/
|
||||||
|
private void setReadingId3HeaderState() {
|
||||||
|
state = STATE_READING_ID3_HEADER;
|
||||||
|
bytesRead = ID3_IDENTIFIER.length;
|
||||||
|
sampleSize = 0;
|
||||||
|
id3HeaderBuffer.setPosition(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state to STATE_READING_SAMPLE.
|
||||||
|
*
|
||||||
|
* @param outputToUse TrackOutput object to write the sample to
|
||||||
|
* @param currentSampleDuration Duration of the sample to be read
|
||||||
|
* @param priorReadBytes Size of prior read bytes
|
||||||
|
* @param sampleSize Size of the sample
|
||||||
|
*/
|
||||||
|
private void setReadingSampleState(TrackOutput outputToUse, long currentSampleDuration,
|
||||||
|
int priorReadBytes, int sampleSize) {
|
||||||
|
state = STATE_READING_SAMPLE;
|
||||||
|
bytesRead = priorReadBytes;
|
||||||
|
this.currentOutput = outputToUse;
|
||||||
|
this.currentSampleDuration = currentSampleDuration;
|
||||||
|
this.sampleSize = sampleSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state to STATE_READING_ADTS_HEADER.
|
||||||
|
*/
|
||||||
|
private void setReadingAdtsHeaderState() {
|
||||||
|
state = STATE_READING_ADTS_HEADER;
|
||||||
|
bytesRead = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locates the next sample start, advancing the position to the byte that immediately follows
|
||||||
|
* identifier. If a sample was not located, the position is advanced to the limit.
|
||||||
*
|
*
|
||||||
* @param pesBuffer The buffer whose position should be advanced.
|
* @param pesBuffer The buffer whose position should be advanced.
|
||||||
* @return True if a sync word position was found. False otherwise.
|
|
||||||
*/
|
*/
|
||||||
private boolean skipToNextSync(ParsableByteArray pesBuffer) {
|
private void findNextSample(ParsableByteArray pesBuffer) {
|
||||||
byte[] adtsData = pesBuffer.data;
|
byte[] adtsData = pesBuffer.data;
|
||||||
int startOffset = pesBuffer.getPosition();
|
int position = pesBuffer.getPosition();
|
||||||
int endOffset = pesBuffer.limit();
|
int endOffset = pesBuffer.limit();
|
||||||
for (int i = startOffset; i < endOffset; i++) {
|
while (position < endOffset) {
|
||||||
boolean byteIsFF = (adtsData[i] & 0xFF) == 0xFF;
|
int data = adtsData[position++] & 0xFF;
|
||||||
boolean found = lastByteWasFF && !byteIsFF && (adtsData[i] & 0xF0) == 0xF0;
|
if (matchState == MATCH_STATE_FF && data >= 0xF0 && data != 0xFF) {
|
||||||
lastByteWasFF = byteIsFF;
|
hasCrc = (data & 0x1) == 0;
|
||||||
if (found) {
|
setReadingAdtsHeaderState();
|
||||||
hasCrc = (adtsData[i] & 0x1) == 0;
|
pesBuffer.setPosition(position);
|
||||||
pesBuffer.setPosition(i + 1);
|
return;
|
||||||
// Reset lastByteWasFF for next time.
|
}
|
||||||
lastByteWasFF = false;
|
switch (matchState | data) {
|
||||||
return true;
|
case MATCH_STATE_START | 0xFF:
|
||||||
|
matchState = MATCH_STATE_FF;
|
||||||
|
break;
|
||||||
|
case MATCH_STATE_START | 'I':
|
||||||
|
matchState = MATCH_STATE_I;
|
||||||
|
break;
|
||||||
|
case MATCH_STATE_I | 'D':
|
||||||
|
matchState = MATCH_STATE_ID;
|
||||||
|
break;
|
||||||
|
case MATCH_STATE_ID | '3':
|
||||||
|
setReadingId3HeaderState();
|
||||||
|
pesBuffer.setPosition(position);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
if (matchState != MATCH_STATE_START) {
|
||||||
|
// If matching fails in a later state, revert to MATCH_STATE_START and
|
||||||
|
// check this byte again
|
||||||
|
matchState = MATCH_STATE_START;
|
||||||
|
position--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pesBuffer.setPosition(endOffset);
|
pesBuffer.setPosition(position);
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the Id3 header.
|
||||||
|
*/
|
||||||
|
private void parseId3Header() {
|
||||||
|
id3Output.sampleData(id3HeaderBuffer, ID3_HEADER_SIZE);
|
||||||
|
id3HeaderBuffer.setPosition(ID3_SIZE_OFFSET);
|
||||||
|
setReadingSampleState(id3Output, 0, ID3_HEADER_SIZE,
|
||||||
|
id3HeaderBuffer.readSynchSafeInt() + ID3_HEADER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the sample header.
|
* Parses the sample header.
|
||||||
*/
|
*/
|
||||||
private void parseHeader() {
|
private void parseAdtsHeader() {
|
||||||
adtsScratch.setPosition(0);
|
adtsScratch.setPosition(0);
|
||||||
|
|
||||||
if (!hasOutputFormat) {
|
if (!hasOutputFormat) {
|
||||||
|
|
@ -183,10 +271,26 @@ import java.util.Collections;
|
||||||
}
|
}
|
||||||
|
|
||||||
adtsScratch.skipBits(4);
|
adtsScratch.skipBits(4);
|
||||||
sampleSize = adtsScratch.readBits(13) - 2 /* the sync word */ - HEADER_SIZE;
|
int sampleSize = adtsScratch.readBits(13) - 2 /* the sync word */ - HEADER_SIZE;
|
||||||
if (hasCrc) {
|
if (hasCrc) {
|
||||||
sampleSize -= CRC_SIZE;
|
sampleSize -= CRC_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setReadingSampleState(output, sampleDurationUs, 0, sampleSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the rest of the sample
|
||||||
|
*/
|
||||||
|
private void readSample(ParsableByteArray data) {
|
||||||
|
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
|
||||||
|
currentOutput.sampleData(data, bytesToRead);
|
||||||
|
bytesRead += bytesToRead;
|
||||||
|
if (bytesRead == sampleSize) {
|
||||||
|
currentOutput.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
|
||||||
|
timeUs += currentSampleDuration;
|
||||||
|
setFindingSampleState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package com.google.android.exoplayer.extractor.ts;
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
import com.google.android.exoplayer.extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -35,8 +34,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
public Id3Reader(TrackOutput output) {
|
public Id3Reader(TrackOutput output) {
|
||||||
super(output);
|
super(output);
|
||||||
output.format(MediaFormat.createFormatForMimeType(null, MimeTypes.APPLICATION_ID3,
|
output.format(MediaFormat.createId3Format());
|
||||||
MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.extractor.DummyTrackOutput;
|
||||||
import com.google.android.exoplayer.extractor.Extractor;
|
import com.google.android.exoplayer.extractor.Extractor;
|
||||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||||
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
||||||
|
|
@ -334,7 +335,8 @@ public final class TsExtractor implements Extractor {
|
||||||
pesPayloadReader = new MpegAudioReader(output.track(TS_STREAM_TYPE_MPA_LSF));
|
pesPayloadReader = new MpegAudioReader(output.track(TS_STREAM_TYPE_MPA_LSF));
|
||||||
break;
|
break;
|
||||||
case TS_STREAM_TYPE_AAC:
|
case TS_STREAM_TYPE_AAC:
|
||||||
pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC));
|
pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC),
|
||||||
|
new DummyTrackOutput());
|
||||||
break;
|
break;
|
||||||
case TS_STREAM_TYPE_AC3:
|
case TS_STREAM_TYPE_AC3:
|
||||||
pesPayloadReader = new Ac3Reader(output.track(TS_STREAM_TYPE_AC3), false);
|
pesPayloadReader = new Ac3Reader(output.track(TS_STREAM_TYPE_AC3), false);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue