mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add support for reading AC-3 audio in HLS.
This commit is contained in:
parent
9b6c30525e
commit
aed45bb2ca
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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.extractor.ts;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||
import com.google.android.exoplayer.util.Ac3Util;
|
||||
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
|
||||
/**
|
||||
* Parses a continuous AC-3 byte stream and extracts individual samples.
|
||||
*/
|
||||
/* package */ final class Ac3Reader extends ElementaryStreamReader {
|
||||
|
||||
private static final int STATE_FINDING_SYNC = 0;
|
||||
private static final int STATE_READING_HEADER = 1;
|
||||
private static final int STATE_READING_SAMPLE = 2;
|
||||
|
||||
private static final int HEADER_SIZE = 8;
|
||||
|
||||
private final ParsableBitArray headerScratchBits;
|
||||
private final ParsableByteArray headerScratchBytes;
|
||||
|
||||
private int state;
|
||||
private int bytesRead;
|
||||
|
||||
// Used to find the header.
|
||||
private boolean lastByteWas0B;
|
||||
|
||||
// Used when parsing the header.
|
||||
private long frameDurationUs;
|
||||
private MediaFormat mediaFormat;
|
||||
private int sampleSize;
|
||||
|
||||
// Used when reading the samples.
|
||||
private long timeUs;
|
||||
|
||||
public Ac3Reader(TrackOutput output) {
|
||||
super(output);
|
||||
headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]);
|
||||
headerScratchBytes = new ParsableByteArray(headerScratchBits.getData());
|
||||
state = STATE_FINDING_SYNC;
|
||||
}
|
||||
|
||||
public void seek() {
|
||||
state = STATE_FINDING_SYNC;
|
||||
bytesRead = 0;
|
||||
lastByteWas0B = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
||||
if (startOfPacket) {
|
||||
timeUs = pesTimeUs;
|
||||
}
|
||||
while (data.bytesLeft() > 0) {
|
||||
switch (state) {
|
||||
case STATE_FINDING_SYNC:
|
||||
if (skipToNextSync(data)) {
|
||||
state = STATE_READING_HEADER;
|
||||
headerScratchBytes.data[0] = 0x0B;
|
||||
headerScratchBytes.data[1] = 0x77;
|
||||
bytesRead = 2;
|
||||
}
|
||||
break;
|
||||
case STATE_READING_HEADER:
|
||||
if (continueRead(data, headerScratchBytes.data, HEADER_SIZE)) {
|
||||
parseHeader();
|
||||
headerScratchBytes.setPosition(0);
|
||||
output.sampleData(headerScratchBytes, HEADER_SIZE);
|
||||
state = STATE_READING_SAMPLE;
|
||||
}
|
||||
break;
|
||||
case STATE_READING_SAMPLE:
|
||||
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
|
||||
output.sampleData(data, bytesToRead);
|
||||
bytesRead += bytesToRead;
|
||||
if (bytesRead == sampleSize) {
|
||||
output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
|
||||
timeUs += frameDurationUs;
|
||||
state = STATE_FINDING_SYNC;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetFinished() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Continues a read from the provided {@code source} into a given {@code target}. It's assumed
|
||||
* that the data should be written into {@code target} starting from an offset of zero.
|
||||
*
|
||||
* @param source The source from which to read.
|
||||
* @param target The target into which data is to be read.
|
||||
* @param targetLength The target length of the read.
|
||||
* @return Whether the target length was reached.
|
||||
*/
|
||||
private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) {
|
||||
int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead);
|
||||
source.readBytes(target, bytesRead, bytesToRead);
|
||||
bytesRead += bytesToRead;
|
||||
return bytesRead == targetLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the next sync word, advancing the position to the byte that immediately follows it.
|
||||
* If a sync word was not located, the position is advanced to the limit.
|
||||
*
|
||||
* @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) {
|
||||
while (pesBuffer.bytesLeft() > 0) {
|
||||
if (!lastByteWas0B) {
|
||||
lastByteWas0B = pesBuffer.readUnsignedByte() == 0x0B;
|
||||
continue;
|
||||
}
|
||||
int secondByte = pesBuffer.readUnsignedByte();
|
||||
if (secondByte == 0x77) {
|
||||
lastByteWas0B = false;
|
||||
return true;
|
||||
} else {
|
||||
lastByteWas0B = secondByte == 0x0B;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the sample header.
|
||||
*/
|
||||
private void parseHeader() {
|
||||
if (mediaFormat == null) {
|
||||
headerScratchBits.setPosition(0);
|
||||
mediaFormat = Ac3Util.parseFrameAc3Format(headerScratchBits);
|
||||
output.format(mediaFormat);
|
||||
}
|
||||
headerScratchBits.setPosition(0);
|
||||
sampleSize = Ac3Util.parseFrameSize(headerScratchBits);
|
||||
frameDurationUs = (int) (1000000L * 8 * sampleSize / mediaFormat.bitrate);
|
||||
}
|
||||
|
||||
}
|
@ -40,6 +40,8 @@ public final class TsExtractor implements Extractor {
|
||||
private static final int TS_PAT_PID = 0;
|
||||
|
||||
private static final int TS_STREAM_TYPE_AAC = 0x0F;
|
||||
private static final int TS_STREAM_TYPE_ATSC_AC3 = 0x81;
|
||||
private static final int TS_STREAM_TYPE_DVB_AC3 = 0x06;
|
||||
private static final int TS_STREAM_TYPE_H264 = 0x1B;
|
||||
private static final int TS_STREAM_TYPE_ID3 = 0x15;
|
||||
private static final int TS_STREAM_TYPE_EIA608 = 0x100; // 0xFF + 1
|
||||
@ -262,11 +264,14 @@ public final class TsExtractor implements Extractor {
|
||||
case TS_STREAM_TYPE_AAC:
|
||||
pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC));
|
||||
break;
|
||||
case TS_STREAM_TYPE_ATSC_AC3:
|
||||
case TS_STREAM_TYPE_DVB_AC3:
|
||||
pesPayloadReader = new Ac3Reader(output.track(streamType));
|
||||
break;
|
||||
case TS_STREAM_TYPE_H264:
|
||||
SeiReader seiReader = new SeiReader(output.track(TS_STREAM_TYPE_EIA608));
|
||||
streamReaders.put(TS_STREAM_TYPE_EIA608, seiReader);
|
||||
pesPayloadReader = new H264Reader(output.track(TS_STREAM_TYPE_H264),
|
||||
seiReader);
|
||||
pesPayloadReader = new H264Reader(output.track(TS_STREAM_TYPE_H264), seiReader);
|
||||
break;
|
||||
case TS_STREAM_TYPE_ID3:
|
||||
pesPayloadReader = id3Reader;
|
||||
|
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Utility methods for parsing AC-3 headers.
|
||||
*/
|
||||
public final class Ac3Util {
|
||||
|
||||
/** Sample rates, indexed by fscod. */
|
||||
private static final int[] SAMPLE_RATES = new int[] {48000, 44100, 32000};
|
||||
/** Channel counts, indexed by acmod. */
|
||||
private static final int[] CHANNEL_COUNTS = new int[] {2, 1, 2, 3, 3, 4, 4, 5};
|
||||
/** Nominal bitrates in kbps, indexed by bit_rate_code. */
|
||||
private static final int[] BITRATES = new int[] {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192,
|
||||
224, 256, 320, 384, 448, 512, 576, 640};
|
||||
/** 16-bit words per sync frame, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.) */
|
||||
private static final int[] FRMSIZECOD_TO_FRAME_SIZE_44_1 = new int[] {69, 87, 104, 121, 139, 174,
|
||||
208, 243, 278, 348, 417, 487, 557, 696, 835, 975, 1114, 1253, 1393};
|
||||
|
||||
/**
|
||||
* Returns the AC-3 format given {@code data} containing the AC3SpecificBox according to
|
||||
* ETSI TS 102 366 Annex F.
|
||||
*/
|
||||
public static MediaFormat parseAnnexFAc3Format(ParsableByteArray data) {
|
||||
// fscod (sample rate code)
|
||||
int fscod = (data.readUnsignedByte() & 0xC0) >> 6;
|
||||
int sampleRate = SAMPLE_RATES[fscod];
|
||||
int nextByte = data.readUnsignedByte();
|
||||
// Map acmod (audio coding mode) onto a channel count.
|
||||
int channelCount = CHANNEL_COUNTS[(nextByte & 0x38) >> 3];
|
||||
// lfeon (low frequency effects on)
|
||||
if ((nextByte & 0x04) != 0) {
|
||||
channelCount++;
|
||||
}
|
||||
// Map bit_rate_code onto a bitrate in bit/s.
|
||||
int bitrate = BITRATES[((nextByte & 0x03) << 3) + (data.readUnsignedByte() >> 5)] * 1000;
|
||||
return MediaFormat.createAudioFormat(MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE,
|
||||
MediaFormat.NO_VALUE, channelCount, sampleRate, bitrate, Collections.<byte[]>emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AC-3 format given {@code data} containing the EC3SpecificBox according to
|
||||
* ETSI TS 102 366 Annex F.
|
||||
*/
|
||||
public static MediaFormat parseAnnexFEAc3Format(ParsableByteArray data) {
|
||||
data.skipBytes(2); // Skip data_rate and num_ind_sub.
|
||||
|
||||
// Read only the first substream.
|
||||
// TODO: Read later substreams?
|
||||
// fscod (sample rate code)
|
||||
int fscod = (data.readUnsignedByte() & 0xC0) >> 6;
|
||||
int sampleRate = SAMPLE_RATES[fscod];
|
||||
int nextByte = data.readUnsignedByte();
|
||||
// Map acmod (audio coding mode) onto a channel count.
|
||||
int channelCount = CHANNEL_COUNTS[(nextByte & 0x0E) >> 1];
|
||||
// lfeon (low frequency effects on)
|
||||
if ((nextByte & 0x01) != 0) {
|
||||
channelCount++;
|
||||
}
|
||||
return MediaFormat.createAudioFormat(MimeTypes.AUDIO_EC3, MediaFormat.NO_VALUE,
|
||||
channelCount, sampleRate, Collections.<byte[]>emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AC-3 format given {@code data} containing the frame header starting from the sync
|
||||
* word.
|
||||
*
|
||||
* @param data Data to parse, positioned at the start of the syncword.
|
||||
* @return AC-3 format parsed from data in the header.
|
||||
*/
|
||||
public static MediaFormat parseFrameAc3Format(ParsableBitArray data) {
|
||||
// Skip syncword and crc1.
|
||||
data.skipBits(4 * 8);
|
||||
|
||||
int fscod = data.readBits(2);
|
||||
int frmsizecod = data.readBits(6);
|
||||
data.skipBits(8); // bsid (5 bits) + bsmod (3 bits)
|
||||
int acmod = data.readBits(3);
|
||||
if ((acmod & 0x01) != 0 && acmod != 1) {
|
||||
data.skipBits(2); // cmixlev
|
||||
}
|
||||
if ((acmod & 0x04) != 0) {
|
||||
data.skipBits(2); // surmixlev
|
||||
}
|
||||
if (acmod == 0x02) {
|
||||
data.skipBits(2); // dsurmod
|
||||
}
|
||||
boolean lfeon = data.readBit();
|
||||
return MediaFormat.createAudioFormat(MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE,
|
||||
MediaFormat.NO_VALUE, CHANNEL_COUNTS[acmod] + (lfeon ? 1 : 0), SAMPLE_RATES[fscod],
|
||||
BITRATES[frmsizecod / 2] * 1000, Collections.<byte[]>emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AC-3 frame size in bytes given {@code data} containing the frame header starting
|
||||
* from the sync word.
|
||||
*
|
||||
* @param data Data to parse, positioned at the start of the syncword.
|
||||
* @return The frame size parsed from data in the header.
|
||||
*/
|
||||
public static int parseFrameSize(ParsableBitArray data) {
|
||||
// Skip syncword and crc1.
|
||||
data.skipBits(4 * 8);
|
||||
|
||||
int fscod = data.readBits(2);
|
||||
int frmsizecod = data.readBits(6);
|
||||
int sampleRate = SAMPLE_RATES[fscod];
|
||||
int bitrate = BITRATES[frmsizecod / 2];
|
||||
if (sampleRate == 32000) {
|
||||
return 6 * bitrate;
|
||||
} else if (sampleRate == 44100) {
|
||||
return 2 * (FRMSIZECOD_TO_FRAME_SIZE_44_1[frmsizecod / 2] + (frmsizecod % 2));
|
||||
} else { // sampleRate == 48000
|
||||
return 4 * bitrate;
|
||||
}
|
||||
}
|
||||
|
||||
private Ac3Util() {
|
||||
// Prevent instantiation.
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user