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:
aquilescanta 2016-08-25 08:57:50 -07:00 committed by Oliver Woodman
parent af2b8fb7a6
commit 09c58004dc
7 changed files with 270 additions and 9 deletions

Binary file not shown.

View 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

View File

@ -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());
}
}

View File

@ -87,6 +87,13 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
} catch (ClassNotFoundException e) {
// 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 {
extractorClasses.add(
Class.forName("com.google.android.exoplayer2.extractor.ts.TsExtractor")

View File

@ -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;
}
}

View File

@ -51,6 +51,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
// Used when reading the samples.
private long timeUs;
// TODO: Remove the isEac3 parameter by reading the BSID field.
/**
* Constructs a new reader for (E-)AC-3 elementary streams.
*

View File

@ -57,7 +57,7 @@ public final class AdtsExtractor implements Extractor {
private final ParsableByteArray packetBuffer;
// Accessed only by the loading thread.
private AdtsReader adtsReader;
private AdtsReader reader;
private boolean startedPacket;
public AdtsExtractor() {
@ -81,8 +81,8 @@ public final class AdtsExtractor implements Extractor {
if (scratch.readUnsignedInt24() != ID3_TAG) {
break;
}
int length = (scratch.data[6] & 0x7F) << 21 | ((scratch.data[7] & 0x7F) << 14)
| ((scratch.data[8] & 0x7F) << 7) | (scratch.data[9] & 0x7F);
scratch.skipBytes(3);
int length = scratch.readSynchSafeInt();
startPosition += 10 + length;
input.advancePeekPosition(length);
}
@ -126,7 +126,7 @@ public final class AdtsExtractor implements Extractor {
@Override
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.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
}
@ -134,7 +134,7 @@ public final class AdtsExtractor implements Extractor {
@Override
public void seek(long position) {
startedPacket = false;
adtsReader.seek();
reader.seek();
}
@Override
@ -154,14 +154,14 @@ public final class AdtsExtractor implements Extractor {
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.
if (!startedPacket) {
// 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;
}
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;
}