Add support for playing DTS in HLS.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=112774678
This commit is contained in:
andrewlewis 2016-01-22 04:59:17 -08:00 committed by root
parent a02fd14c31
commit a9720457d8
5 changed files with 290 additions and 7 deletions

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer.audio;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.util.Ac3Util;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.DtsUtil;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util;
@ -965,10 +966,7 @@ public final class AudioTrack {
private static int getFramesPerEncodedSample(int encoding, ByteBuffer buffer) {
if (encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD) {
// Calculate the sample size in frames as per ETSI TS 102 114 F.3.2.1.
int nblks = ((buffer.get(buffer.position() + 4) & 0x01) << 6)
| ((buffer.get(buffer.position() + 5) & 0xFC) >> 2);
return (nblks + 1) * 32;
return DtsUtil.parseDtsAudioSampleCount(buffer);
} else if (encoding == C.ENCODING_AC3) {
return Ac3Util.getAc3SyncframeAudioSampleCount();
} else if (encoding == C.ENCODING_E_AC3) {

View File

@ -0,0 +1,166 @@
/*
* 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.DtsUtil;
import com.google.android.exoplayer.util.ParsableByteArray;
/**
* Parses a continuous DTS byte stream and extracts individual samples.
*/
/* package */ final class DtsReader 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 = 15;
private static final int SYNC_VALUE = 0x7FFE8001;
private static final int SYNC_VALUE_SIZE = 4;
private final ParsableByteArray headerScratchBytes;
private int state;
private int bytesRead;
// Used to find the header.
private int syncBytes;
// Used when parsing the header.
private long sampleDurationUs;
private MediaFormat mediaFormat;
private int sampleSize;
// Used when reading the samples.
private long timeUs;
/**
* Constructs a new reader for DTS elementary streams.
*
* @param output Track output for extracted samples.
*/
public DtsReader(TrackOutput output) {
super(output);
headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]);
headerScratchBytes.data[0] = (byte) ((SYNC_VALUE >> 24) & 0xFF);
headerScratchBytes.data[1] = (byte) ((SYNC_VALUE >> 16) & 0xFF);
headerScratchBytes.data[2] = (byte) ((SYNC_VALUE >> 8) & 0xFF);
headerScratchBytes.data[3] = (byte) (SYNC_VALUE & 0xFF);
state = STATE_FINDING_SYNC;
}
@Override
public void seek() {
state = STATE_FINDING_SYNC;
bytesRead = 0;
syncBytes = 0;
}
@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)) {
bytesRead = SYNC_VALUE_SIZE;
state = STATE_READING_HEADER;
}
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 += sampleDurationUs;
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 value in the buffer, advancing the position to the byte that immediately
* follows it. If SYNC was not located, the position is advanced to the limit.
*
* @param pesBuffer The buffer whose position should be advanced.
* @return True if SYNC was found. False otherwise.
*/
private boolean skipToNextSync(ParsableByteArray pesBuffer) {
while (pesBuffer.bytesLeft() > 0) {
syncBytes <<= 8;
syncBytes |= pesBuffer.readUnsignedByte();
if (syncBytes == SYNC_VALUE) {
syncBytes = 0;
return true;
}
}
return false;
}
/**
* Parses the sample header.
*/
private void parseHeader() {
byte[] frameData = headerScratchBytes.data;
if (mediaFormat == null) {
mediaFormat = DtsUtil.parseDtsFormat(frameData, null, C.UNKNOWN_TIME_US, null);
output.format(mediaFormat);
}
sampleSize = DtsUtil.getDtsFrameSize(frameData);
// In this class a sample is an access unit (frame in DTS), but the MediaFormat sample rate
// specifies the number of PCM audio samples per second.
sampleDurationUs = (int) (C.MICROS_PER_SECOND
* DtsUtil.parseDtsAudioSampleCount(frameData) / mediaFormat.sampleRate);
}
}

View File

@ -47,6 +47,7 @@ public final class TsExtractor implements Extractor {
private static final int TS_STREAM_TYPE_AAC = 0x0F;
private static final int TS_STREAM_TYPE_AC3 = 0x81;
private static final int TS_STREAM_TYPE_DTS = 0x8A;
private static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;
private static final int TS_STREAM_TYPE_E_AC3 = 0x87;
private static final int TS_STREAM_TYPE_H262 = 0x02;
private static final int TS_STREAM_TYPE_H264 = 0x1B;
@ -345,6 +346,10 @@ public final class TsExtractor implements Extractor {
case TS_STREAM_TYPE_E_AC3:
pesPayloadReader = new Ac3Reader(output.track(TS_STREAM_TYPE_E_AC3), true);
break;
case TS_STREAM_TYPE_DTS:
case TS_STREAM_TYPE_HDMV_DTS:
pesPayloadReader = new DtsReader(output.track(TS_STREAM_TYPE_DTS));
break;
case TS_STREAM_TYPE_H262:
pesPayloadReader = new H262Reader(output.track(TS_STREAM_TYPE_H262));
break;

View File

@ -235,8 +235,6 @@ public final class Ac3Util {
}
}
private Ac3Util() {
// Prevent instantiation.
}
private Ac3Util() {}
}

View File

@ -0,0 +1,116 @@
/*
* 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.nio.ByteBuffer;
/**
* Utility methods for parsing DTS frames.
*/
public final class DtsUtil {
/**
* Maps AMODE to the number of channels. See ETSI TS 102 114 table 5.4.
*/
private static final int[] CHANNELS_BY_AMODE = new int[] {1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6,
7, 8, 8};
/**
* Maps SFREQ to the sampling frequency in Hz. See ETSI TS 102 144 table 5.5.
*/
private static final int[] SAMPLE_RATE_BY_SFREQ = new int[] {-1, 8000, 16000, 32000, -1, -1,
11025, 22050, 44100, -1, -1, 12000, 24000, 48000, -1, -1};
/**
* Maps RATE to 2 * bitrate in kbit/s. See ETSI TS 102 144 table 5.7.
*/
private static final int[] TWICE_BITRATE_KBPS_BY_RATE = new int[] {64, 112, 128, 192, 224, 256,
384, 448, 512, 640, 768, 896, 1024, 1152, 1280, 1536, 1920, 2048, 2304, 2560, 2688, 2816,
2823, 2944, 3072, 3840, 4096, 6144, 7680};
private static final ParsableBitArray SCRATCH_BITS = new ParsableBitArray();
/**
* Returns the DTS format given {@code data} containing the DTS frame according to ETSI TS 102 114
* subsections 5.3/5.4.
* <p>
* This method may only be called from one thread at a time.
*
* @param frame The DTS frame to parse.
* @param trackId The track identifier to set on the format, or null.
* @param durationUs The duration to set on the format, in microseconds.
* @param language The language to set on the format.
* @return The DTS format parsed from data in the header.
*/
public static MediaFormat parseDtsFormat(byte[] frame, String trackId, long durationUs,
String language) {
ParsableBitArray frameBits = SCRATCH_BITS;
frameBits.reset(frame);
frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE
int amode = frameBits.readBits(6);
int channelCount = CHANNELS_BY_AMODE[amode];
int sfreq = frameBits.readBits(4);
int sampleRate = SAMPLE_RATE_BY_SFREQ[sfreq];
int rate = frameBits.readBits(5);
int bitrate = rate >= TWICE_BITRATE_KBPS_BY_RATE.length ? MediaFormat.NO_VALUE
: TWICE_BITRATE_KBPS_BY_RATE[rate] * 1000 / 2;
frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF
channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_DTS, bitrate,
MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, language);
}
/**
* Returns the number of audio samples represented by the given DTS frame.
*
* @param data The frame to parse.
* @return The number of audio samples represented by the frame.
*/
public static int parseDtsAudioSampleCount(byte[] data) {
// See ETSI TS 102 114 subsection 5.4.1.
int nblks = ((data[4] & 0x01) << 6) | ((data[5] & 0xFC) >> 2);
return (nblks + 1) * 32;
}
/**
* Like {@link #parseDtsAudioSampleCount(byte[])} but reads from a byte buffer. The buffer
* position is not modified.
*/
public static int parseDtsAudioSampleCount(ByteBuffer data) {
// See ETSI TS 102 114 subsection 5.4.1.
int position = data.position();
int nblks = ((data.get(position + 4) & 0x01) << 6)
| ((data.get(position + 5) & 0xFC) >> 2);
return (nblks + 1) * 32;
}
/**
* Returns the size in bytes of the given DTS frame.
*
* @param data The frame to parse.
* @return The frame's size in bytes.
*/
public static int getDtsFrameSize(byte[] data) {
return (((data[5] & 0x02) << 12)
| ((data[6] & 0xFF) << 4)
| ((data[7] & 0xF0) >> 4)) + 1;
}
private DtsUtil() {}
}