Add an AC3 bitstream extractor
Defined in ATSC Standard: Digital Audio Compression (AC-3, E-AC-3). Link: http://atsc.org/wp-content/uploads/2015/03/A52-201212-17.pdf. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=131294260
This commit is contained in:
parent
af2b8fb7a6
commit
09c58004dc
BIN
library/src/androidTest/assets/ts/sample.ac3
Normal file
BIN
library/src/androidTest/assets/ts/sample.ac3
Normal file
Binary file not shown.
61
library/src/androidTest/assets/ts/sample.ac3.0.dump
Normal file
61
library/src/androidTest/assets/ts/sample.ac3.0.dump
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
seekMap:
|
||||||
|
isSeekable = false
|
||||||
|
duration = UNSET TIME
|
||||||
|
getPosition(0) = 0
|
||||||
|
numberOfTracks = 1
|
||||||
|
track 0:
|
||||||
|
format:
|
||||||
|
bitrate = -1
|
||||||
|
id = null
|
||||||
|
containerMimeType = null
|
||||||
|
sampleMimeType = audio/ac3
|
||||||
|
maxInputSize = -1
|
||||||
|
width = -1
|
||||||
|
height = -1
|
||||||
|
frameRate = -1.0
|
||||||
|
rotationDegrees = -1
|
||||||
|
pixelWidthHeightRatio = -1.0
|
||||||
|
channelCount = 6
|
||||||
|
sampleRate = 48000
|
||||||
|
pcmEncoding = -1
|
||||||
|
encoderDelay = -1
|
||||||
|
encoderPadding = -1
|
||||||
|
subsampleOffsetUs = 9223372036854775807
|
||||||
|
selectionFlags = 0
|
||||||
|
language = null
|
||||||
|
drmInitData = -
|
||||||
|
initializationData:
|
||||||
|
sample count = 8
|
||||||
|
sample 0:
|
||||||
|
time = 0
|
||||||
|
flags = 1
|
||||||
|
data = length 1536, hash 7108D5C2
|
||||||
|
sample 1:
|
||||||
|
time = 32000
|
||||||
|
flags = 1
|
||||||
|
data = length 1536, hash 80BF3B34
|
||||||
|
sample 2:
|
||||||
|
time = 64000
|
||||||
|
flags = 1
|
||||||
|
data = length 1536, hash 5D09685
|
||||||
|
sample 3:
|
||||||
|
time = 96000
|
||||||
|
flags = 1
|
||||||
|
data = length 1536, hash A9A24E44
|
||||||
|
sample 4:
|
||||||
|
time = 128000
|
||||||
|
flags = 1
|
||||||
|
data = length 1536, hash 6F856273
|
||||||
|
sample 5:
|
||||||
|
time = 160000
|
||||||
|
flags = 1
|
||||||
|
data = length 1536, hash B1737D3C
|
||||||
|
sample 6:
|
||||||
|
time = 192000
|
||||||
|
flags = 1
|
||||||
|
data = length 1536, hash 98FDEB9D
|
||||||
|
sample 7:
|
||||||
|
time = 224000
|
||||||
|
flags = 1
|
||||||
|
data = length 1536, hash 99B9B943
|
||||||
|
tracksEnded = true
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import android.test.InstrumentationTestCase;
|
||||||
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for {@link Ac3Extractor}.
|
||||||
|
*/
|
||||||
|
public final class Ac3ExtractorTest extends InstrumentationTestCase {
|
||||||
|
|
||||||
|
public void testSample() throws Exception {
|
||||||
|
TestUtil.assertOutput(new TestUtil.ExtractorFactory() {
|
||||||
|
@Override
|
||||||
|
public Extractor create() {
|
||||||
|
return new Ac3Extractor();
|
||||||
|
}
|
||||||
|
}, "ts/sample.ac3", getInstrumentation());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -87,6 +87,13 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
|||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
// Extractor not found.
|
// Extractor not found.
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
extractorClasses.add(
|
||||||
|
Class.forName("com.google.android.exoplayer2.extractor.ts.Ac3Extractor")
|
||||||
|
.asSubclass(Extractor.class));
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
// Extractor not found.
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
extractorClasses.add(
|
extractorClasses.add(
|
||||||
Class.forName("com.google.android.exoplayer2.extractor.ts.TsExtractor")
|
Class.forName("com.google.android.exoplayer2.extractor.ts.TsExtractor")
|
||||||
|
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.audio.Ac3Util;
|
||||||
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||||
|
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||||
|
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||||
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facilitates the extraction of AC-3 samples from elementary audio files formatted as AC-3
|
||||||
|
* bitstreams.
|
||||||
|
*/
|
||||||
|
public final class Ac3Extractor implements Extractor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for {@link Ac3Extractor} instances.
|
||||||
|
*/
|
||||||
|
public static final ExtractorsFactory FACTORY = new ExtractorsFactory() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Extractor[] createExtractors() {
|
||||||
|
return new Extractor[] {new Ac3Extractor()};
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of bytes to search when sniffing, excluding ID3 information, before giving
|
||||||
|
* up.
|
||||||
|
*/
|
||||||
|
private static final int MAX_SNIFF_BYTES = 8 * 1024;
|
||||||
|
private static final int AC3_SYNC_WORD = 0x0B77;
|
||||||
|
private static final int MAX_SYNC_FRAME_SIZE = 2786;
|
||||||
|
private static final int ID3_TAG = Util.getIntegerCodeForString("ID3");
|
||||||
|
|
||||||
|
private final long firstSampleTimestampUs;
|
||||||
|
private final ParsableByteArray sampleData;
|
||||||
|
|
||||||
|
private Ac3Reader reader;
|
||||||
|
private boolean startedPacket;
|
||||||
|
|
||||||
|
public Ac3Extractor() {
|
||||||
|
this(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Ac3Extractor(long firstSampleTimestampUs) {
|
||||||
|
this.firstSampleTimestampUs = firstSampleTimestampUs;
|
||||||
|
sampleData = new ParsableByteArray(MAX_SYNC_FRAME_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
|
// Skip any ID3 headers.
|
||||||
|
ParsableByteArray scratch = new ParsableByteArray(10);
|
||||||
|
int startPosition = 0;
|
||||||
|
while (true) {
|
||||||
|
input.peekFully(scratch.data, 0, 10);
|
||||||
|
scratch.setPosition(0);
|
||||||
|
if (scratch.readUnsignedInt24() != ID3_TAG) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
scratch.skipBytes(3);
|
||||||
|
int length = scratch.readSynchSafeInt();
|
||||||
|
startPosition += 10 + length;
|
||||||
|
input.advancePeekPosition(length);
|
||||||
|
}
|
||||||
|
input.resetPeekPosition();
|
||||||
|
input.advancePeekPosition(startPosition);
|
||||||
|
|
||||||
|
int headerPosition = startPosition;
|
||||||
|
int validFramesCount = 0;
|
||||||
|
while (true) {
|
||||||
|
input.peekFully(scratch.data, 0, 5);
|
||||||
|
scratch.setPosition(0);
|
||||||
|
int syncBytes = scratch.readUnsignedShort();
|
||||||
|
if (syncBytes != AC3_SYNC_WORD) {
|
||||||
|
validFramesCount = 0;
|
||||||
|
input.resetPeekPosition();
|
||||||
|
if (++headerPosition - startPosition >= MAX_SNIFF_BYTES) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input.advancePeekPosition(headerPosition);
|
||||||
|
} else {
|
||||||
|
if (++validFramesCount >= 4) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int frameSize = Ac3Util.parseAc3SyncframeSize(scratch.data);
|
||||||
|
input.advancePeekPosition(frameSize - 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(ExtractorOutput output) {
|
||||||
|
reader = new Ac3Reader(output.track(0), false); // TODO: Add support for embedded ID3.
|
||||||
|
output.endTracks();
|
||||||
|
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek(long position) {
|
||||||
|
startedPacket = false;
|
||||||
|
reader.seek();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException,
|
||||||
|
InterruptedException {
|
||||||
|
int bytesRead = input.read(sampleData.data, 0, MAX_SYNC_FRAME_SIZE);
|
||||||
|
if (bytesRead == C.RESULT_END_OF_INPUT) {
|
||||||
|
return RESULT_END_OF_INPUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed whatever data we have to the reader, regardless of whether the read finished or not.
|
||||||
|
sampleData.setPosition(0);
|
||||||
|
sampleData.setLimit(bytesRead);
|
||||||
|
|
||||||
|
if (!startedPacket) {
|
||||||
|
// Pass data to the reader as though it's contained within a single infinitely long packet.
|
||||||
|
reader.packetStarted(firstSampleTimestampUs, true);
|
||||||
|
startedPacket = true;
|
||||||
|
}
|
||||||
|
// TODO: Make it possible for the reader to consume the dataSource directly, so that it becomes
|
||||||
|
// unnecessary to copy the data through packetBuffer.
|
||||||
|
reader.consume(sampleData);
|
||||||
|
return RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -51,6 +51,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
// Used when reading the samples.
|
// Used when reading the samples.
|
||||||
private long timeUs;
|
private long timeUs;
|
||||||
|
|
||||||
|
// TODO: Remove the isEac3 parameter by reading the BSID field.
|
||||||
/**
|
/**
|
||||||
* Constructs a new reader for (E-)AC-3 elementary streams.
|
* Constructs a new reader for (E-)AC-3 elementary streams.
|
||||||
*
|
*
|
||||||
|
@ -57,7 +57,7 @@ public final class AdtsExtractor implements Extractor {
|
|||||||
private final ParsableByteArray packetBuffer;
|
private final ParsableByteArray packetBuffer;
|
||||||
|
|
||||||
// Accessed only by the loading thread.
|
// Accessed only by the loading thread.
|
||||||
private AdtsReader adtsReader;
|
private AdtsReader reader;
|
||||||
private boolean startedPacket;
|
private boolean startedPacket;
|
||||||
|
|
||||||
public AdtsExtractor() {
|
public AdtsExtractor() {
|
||||||
@ -81,8 +81,8 @@ public final class AdtsExtractor implements Extractor {
|
|||||||
if (scratch.readUnsignedInt24() != ID3_TAG) {
|
if (scratch.readUnsignedInt24() != ID3_TAG) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
int length = (scratch.data[6] & 0x7F) << 21 | ((scratch.data[7] & 0x7F) << 14)
|
scratch.skipBytes(3);
|
||||||
| ((scratch.data[8] & 0x7F) << 7) | (scratch.data[9] & 0x7F);
|
int length = scratch.readSynchSafeInt();
|
||||||
startPosition += 10 + length;
|
startPosition += 10 + length;
|
||||||
input.advancePeekPosition(length);
|
input.advancePeekPosition(length);
|
||||||
}
|
}
|
||||||
@ -126,7 +126,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), output.track(1));
|
reader = new AdtsReader(output.track(0), output.track(1));
|
||||||
output.endTracks();
|
output.endTracks();
|
||||||
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
|
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
|
||||||
}
|
}
|
||||||
@ -134,7 +134,7 @@ public final class AdtsExtractor implements Extractor {
|
|||||||
@Override
|
@Override
|
||||||
public void seek(long position) {
|
public void seek(long position) {
|
||||||
startedPacket = false;
|
startedPacket = false;
|
||||||
adtsReader.seek();
|
reader.seek();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -154,14 +154,14 @@ public final class AdtsExtractor implements Extractor {
|
|||||||
packetBuffer.setPosition(0);
|
packetBuffer.setPosition(0);
|
||||||
packetBuffer.setLimit(bytesRead);
|
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.
|
|
||||||
if (!startedPacket) {
|
if (!startedPacket) {
|
||||||
// Pass data to the reader as though it's contained within a single infinitely long packet.
|
// Pass data to the reader as though it's contained within a single infinitely long packet.
|
||||||
adtsReader.packetStarted(firstSampleTimestampUs, true);
|
reader.packetStarted(firstSampleTimestampUs, true);
|
||||||
startedPacket = true;
|
startedPacket = true;
|
||||||
}
|
}
|
||||||
adtsReader.consume(packetBuffer);
|
// TODO: Make it possible for reader to consume the dataSource directly, so that it becomes
|
||||||
|
// unnecessary to copy the data through packetBuffer.
|
||||||
|
reader.consume(packetBuffer);
|
||||||
return RESULT_CONTINUE;
|
return RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user