From 1545a052f0899ac657c3a5d8dccb280848684793 Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Thu, 26 Feb 2015 19:12:58 +0100 Subject: [PATCH 1/5] Added MPEG audio support to TsExtractor --- .../exoplayer/hls/parser/MpaReader.java | 297 ++++++++++++++++++ .../exoplayer/hls/parser/TsExtractor.java | 6 + .../exoplayer/util/CodecSpecificDataUtil.java | 50 ++- .../android/exoplayer/util/MimeTypes.java | 1 + 4 files changed, 341 insertions(+), 13 deletions(-) create mode 100644 library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java new file mode 100644 index 0000000000..b18266faf4 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java @@ -0,0 +1,297 @@ +/* + * 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.hls.parser; + +import com.google.android.exoplayer.C; +import com.google.android.exoplayer.MediaFormat; +import com.google.android.exoplayer.upstream.BufferPool; +import com.google.android.exoplayer.util.CodecSpecificDataUtil; +import com.google.android.exoplayer.util.MimeTypes; +import com.google.android.exoplayer.util.ParsableBitArray; +import com.google.android.exoplayer.util.ParsableByteArray; + +import android.util.Pair; + +import java.util.Collections; + +/** + * Parses a continuous MPEG Audio byte stream and extracts individual + * frames. + */ +/* package */ public class MpaReader 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 = 4; + private static final int SYNCWORD_SIZE = 2; + private static final int CRC_SIZE = 2; + + private final ParsableBitArray mpaScratch; + + private int state; + private int bytesRead; + + // Used to find the header. + private boolean hasCrc; + + // Parsed from the header. + private long frameDurationUs; + private int sampleSize; + + // Used when reading the samples. + private long timeUs; + + // + /** + * sampling rates in hertz: + * + * @index MPEG Version ID + * @index sampling rate index + */ + + private static final int[][] MPA_SAMPLING_RATES = new int[][] { + {11025, 12000, 8000}, // MPEG 2.5 + { 0, 0, 0}, // reserved + {22050, 24000, 16000}, // MPEG 2 + {44100, 48000, 32000} // MPEG 1 + }; + + /** + * bitrates: + * + * @index LSF + * @index Layer + * @index bitrate index + */ + + private static final int[][][] MPA_BITRATES = new int[][][] { + { // MPEG 1 + // Layer1 + { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}, + // Layer2 + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}, + // Layer3 + { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320} + }, + { // MPEG 2, 2.5 + // Layer1 + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, + // Layer2 + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, + // Layer3 + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160} + } + }; + + /** + * Samples per Frame: + * + * @index LSF + * @index Layer + */ + + private static final int[][] MPA_SAMPLES_PER_FRAME = new int[][] { + { // MPEG 1 + 384, // Layer1 + 1152, // Layer2 + 1152 // Layer3 + }, + { // MPEG 2, 2.5 + 384, // Layer1 + 1152, // Layer2 + 576 // Layer3 + } + }; + + /** + * Coefficients (samples per frame / 8): + * + * @index = LSF + * @index = Layer + */ + + private static final int[][] MPA_COEFFICIENTS = new int[][] { + { // MPEG 1 + 12, // Layer1 + 144, // Layer2 + 144 // Layer3 + }, + { // MPEG 2, 2.5 + 12, // Layer1 + 144, // Layer2 + 72 // Layer3 + } + }; + + /** + * slot size per layer: + * + * @index = Layer + */ + + private static final int[] MPA_SLOT_SIZE = new int[] { + 4, // Layer1 + 1, // Layer2 + 1 // Layer3 + }; + + public MpaReader(BufferPool bufferPool) { + super(bufferPool); + mpaScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]); + state = STATE_FINDING_SYNC; + } + + @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 = 0; + state = STATE_READING_HEADER; + } + break; + case STATE_READING_HEADER: + int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE; + if (continueRead(data, mpaScratch.getData(), targetLength)) { + parseHeader(); + startSample(timeUs); + /** + * Reset buffer after Header data extraction in order to pass full + * mpeg audio frame to the mpeg decoder + */ + data.setPosition(data.getPosition() - targetLength); + bytesRead = 0; + state = STATE_READING_SAMPLE; + } + break; + case STATE_READING_SAMPLE: + int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); + appendData(data, bytesToRead); + bytesRead += bytesToRead; + if (bytesRead == sampleSize) { + commitSample(true); + timeUs += frameDurationUs; + bytesRead = 0; + 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) { + byte[] mpaData = pesBuffer.data; + int startOffset = pesBuffer.getPosition(); + int endOffset = pesBuffer.limit(); + for (int i = startOffset; i < endOffset - 1; i++) { + int syncBits = (mpaData[i] << 8 ) | mpaData[i + 1]; + if ((syncBits & 0xFFF0) == 0xFFF0) { + hasCrc = (mpaData[i + 1] & 0x1) == 0; + pesBuffer.setPosition(i); + return true; + } + } + pesBuffer.setPosition(endOffset); + return false; + } + + /** + * Calculates MPEG Audio frame size + * + * @param layer The MPEG layer + * @param LSF Low Sample rate Format (MPEG 2) + * @param bitrate The bitrate in bits per second + * @param samplesPerSec The sampling rate in hertz + * @param paddingSize + * @return Frame size in bytes + */ + private static int CalcMpaFrameSize (int layer, int LSF, int bitrate, int samplesPerSec, int paddingSize) { + return (int)(Math.floor(MPA_COEFFICIENTS[LSF][layer] * bitrate / samplesPerSec) + paddingSize) * MPA_SLOT_SIZE[layer]; + } + + /** + * Parses the sample header. + */ + private void parseHeader() { + mpaScratch.setPosition(0); + + if (!hasMediaFormat()) { + mpaScratch.skipBits(12); + int isLSF = (!mpaScratch.readBit()) ? 1 : 0; + int layer = mpaScratch.readBits(2) ^ 3; + mpaScratch.skipBits(1); + int audioObjectType = 32 + layer; + int bitRate = MPA_BITRATES[isLSF][layer][mpaScratch.readBits(4)]; + int sampleRate = MPA_SAMPLING_RATES[3 - isLSF][mpaScratch.readBits(2)]; + int sampleRateIndex = 0; + for (; sampleRateIndex < (CodecSpecificDataUtil.AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE).length; sampleRateIndex++) { + if ((CodecSpecificDataUtil.AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE)[sampleRateIndex] == sampleRate) { + break; + } + } + int paddingBit = (mpaScratch.readBit()) ? 1 : 0; + mpaScratch.skipBits(1); + int channelConfig = mpaScratch.readBits(2) == 3 ? 1 : 2; + + byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAudioSpecificConfig( + audioObjectType, sampleRateIndex, channelConfig); + Pair audioParams = CodecSpecificDataUtil.parseAudioSpecificConfig( + audioSpecificConfig); + + // need to investigate how to detect if the mpeg decoder supports Layers other than Layer III + MediaFormat mediaFormat = MediaFormat.createAudioFormat(/*isLSF == 1 ?*/ MimeTypes.AUDIO_MPEG/* : MimeTypes.AUDIO_MP1L2*/, + MediaFormat.NO_VALUE, audioParams.second, audioParams.first, + Collections.singletonList(audioSpecificConfig)); + frameDurationUs = (C.MICROS_PER_SECOND * MPA_SAMPLES_PER_FRAME[isLSF][layer]) / mediaFormat.sampleRate; + setMediaFormat(mediaFormat); + sampleSize = CalcMpaFrameSize(layer, isLSF, bitRate * 1000, sampleRate, paddingBit); + } + } +} diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/TsExtractor.java index 8468254440..f1d117c839 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/TsExtractor.java @@ -40,6 +40,8 @@ public final class TsExtractor extends HlsExtractor { private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. private static final int TS_PAT_PID = 0; + private static final int TS_STREAM_TYPE_MPA = 0x03; + private static final int TS_STREAM_TYPE_MPA_LSF = 0x04; private static final int TS_STREAM_TYPE_AAC = 0x0F; private static final int TS_STREAM_TYPE_H264 = 0x1B; private static final int TS_STREAM_TYPE_ID3 = 0x15; @@ -329,6 +331,10 @@ public final class TsExtractor extends HlsExtractor { ElementaryStreamReader pesPayloadReader = null; switch (streamType) { + case TS_STREAM_TYPE_MPA: + case TS_STREAM_TYPE_MPA_LSF: + pesPayloadReader = new MpaReader(bufferPool); + break; case TS_STREAM_TYPE_AAC: pesPayloadReader = new AdtsReader(bufferPool); break; diff --git a/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java b/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java index 019f7459c0..bb1d937c08 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java @@ -29,11 +29,11 @@ public final class CodecSpecificDataUtil { private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1}; - private static final int[] AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE = new int[] { + public static final int[] AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE = new int[] { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 }; - private static final int[] AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE = new int[] { + public static final int[] AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE = new int[] { 0, 1, 2, 3, 4, 5, 6, 8 }; @@ -49,13 +49,22 @@ public final class CodecSpecificDataUtil { */ public static Pair parseAudioSpecificConfig(byte[] audioSpecificConfig) { int audioObjectType = (audioSpecificConfig[0] >> 3) & 0x1F; - int byteOffset = audioObjectType == 5 || audioObjectType == 29 ? 1 : 0; - int frequencyIndex = (audioSpecificConfig[byteOffset] & 0x7) << 1 - | ((audioSpecificConfig[byteOffset + 1] >> 7) & 0x1); - Assertions.checkState(frequencyIndex < 13); - int sampleRate = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; - int channelCount = (audioSpecificConfig[byteOffset + 1] >> 3) & 0xF; - return Pair.create(sampleRate, channelCount); + if (audioObjectType != 31) { + int byteOffset = audioObjectType == 5 || audioObjectType == 29 ? 1 : 0; + int frequencyIndex = (audioSpecificConfig[byteOffset] & 0x7) << 1 | + ((audioSpecificConfig[byteOffset + 1] >> 7) & 0x1); + Assertions.checkState(frequencyIndex < 13); + int sampleRate = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; + int channelCount = (audioSpecificConfig[byteOffset + 1] >> 3) & 0xF; + return Pair.create(sampleRate, channelCount); + } else { + int frequencyIndex = (audioSpecificConfig[1] & 0x1E) >> 1; + Assertions.checkState(frequencyIndex < 13); + int sampleRate = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; + int channelCount = (audioSpecificConfig[1] & 0x01) << 2 | + ((audioSpecificConfig[2] >> 6) & 0x03); + return Pair.create(sampleRate, channelCount); + } } /** @@ -68,10 +77,25 @@ public final class CodecSpecificDataUtil { */ public static byte[] buildAudioSpecificConfig(int audioObjectType, int sampleRateIndex, int channelConfig) { - byte[] audioSpecificConfig = new byte[2]; - audioSpecificConfig[0] = (byte) ((audioObjectType << 3) & 0xF8 | (sampleRateIndex >> 1) & 0x07); - audioSpecificConfig[1] = (byte) ((sampleRateIndex << 7) & 0x80 | (channelConfig << 3) & 0x78); - return audioSpecificConfig; + if (audioObjectType < 31) { + byte[] audioSpecificConfig = new byte[2]; + audioSpecificConfig[0] = (byte) ((audioObjectType << 3) & 0xF8 | + (sampleRateIndex >> 1) & 0x07); + audioSpecificConfig[1] = (byte) ((sampleRateIndex << 7) & 0x80 | + (channelConfig << 3) & 0x78); + return audioSpecificConfig; + } else { + byte[] audioSpecificConfig = new byte[3]; + int audioObjectTypeExt = audioObjectType - 32; + audioObjectType = 31; + audioSpecificConfig[0] = (byte) ((audioObjectType << 3) & 0xF8 | + (audioObjectTypeExt >> 3) & 0x07); + audioSpecificConfig[1] = (byte) ((audioObjectTypeExt << 5) & 0xE0 | + (sampleRateIndex << 1) & 0x1E | + (channelConfig >> 2) & 0x01); + audioSpecificConfig[2] = (byte) ((channelConfig << 6) & 0xC0); + return audioSpecificConfig; + } } /** diff --git a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java index 11d49103e7..ac6874c216 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java +++ b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java @@ -31,6 +31,7 @@ public class MimeTypes { public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9"; public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es"; + public static final String AUDIO_MPEG = BASE_TYPE_AUDIO + "/mpeg"; public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4"; public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm"; public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3"; From 2ce938d650195ca7c5cb0327673145550ad875a4 Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Fri, 27 Feb 2015 14:29:21 +0100 Subject: [PATCH 2/5] Added MPEG audio support to TsExtractor v.2 --- .../exoplayer/hls/parser/MpaReader.java | 23 ++++++++----------- .../exoplayer/util/CodecSpecificDataUtil.java | 22 +++++++++++++++--- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java index b18266faf4..1ea3489ced 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java @@ -38,7 +38,6 @@ import java.util.Collections; private static final int STATE_READING_SAMPLE = 2; private static final int HEADER_SIZE = 4; - private static final int SYNCWORD_SIZE = 2; private static final int CRC_SIZE = 2; private final ParsableBitArray mpaScratch; @@ -172,13 +171,8 @@ import java.util.Collections; case STATE_READING_HEADER: int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE; if (continueRead(data, mpaScratch.getData(), targetLength)) { - parseHeader(); startSample(timeUs); - /** - * Reset buffer after Header data extraction in order to pass full - * mpeg audio frame to the mpeg decoder - */ - data.setPosition(data.getPosition() - targetLength); + parseHeader(); bytesRead = 0; state = STATE_READING_SAMPLE; } @@ -260,6 +254,7 @@ import java.util.Collections; * Parses the sample header. */ private void parseHeader() { + int headerLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE; mpaScratch.setPosition(0); if (!hasMediaFormat()) { @@ -270,12 +265,7 @@ import java.util.Collections; int audioObjectType = 32 + layer; int bitRate = MPA_BITRATES[isLSF][layer][mpaScratch.readBits(4)]; int sampleRate = MPA_SAMPLING_RATES[3 - isLSF][mpaScratch.readBits(2)]; - int sampleRateIndex = 0; - for (; sampleRateIndex < (CodecSpecificDataUtil.AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE).length; sampleRateIndex++) { - if ((CodecSpecificDataUtil.AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE)[sampleRateIndex] == sampleRate) { - break; - } - } + int sampleRateIndex = CodecSpecificDataUtil.getSampleRateIndex(sampleRate); int paddingBit = (mpaScratch.readBit()) ? 1 : 0; mpaScratch.skipBits(1); int channelConfig = mpaScratch.readBits(2) == 3 ? 1 : 2; @@ -291,7 +281,12 @@ import java.util.Collections; Collections.singletonList(audioSpecificConfig)); frameDurationUs = (C.MICROS_PER_SECOND * MPA_SAMPLES_PER_FRAME[isLSF][layer]) / mediaFormat.sampleRate; setMediaFormat(mediaFormat); - sampleSize = CalcMpaFrameSize(layer, isLSF, bitRate * 1000, sampleRate, paddingBit); + sampleSize = CalcMpaFrameSize(layer, isLSF, bitRate * 1000, sampleRate, paddingBit) - headerLength; } + + mpaScratch.setPosition(0); + + ParsableByteArray header = new ParsableByteArray(mpaScratch.getData(),headerLength); + appendData(header, headerLength); } } diff --git a/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java b/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java index bb1d937c08..771a9957ee 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java @@ -29,11 +29,11 @@ public final class CodecSpecificDataUtil { private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1}; - public static final int[] AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE = new int[] { + private static final int[] AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE = new int[] { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 }; - public static final int[] AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE = new int[] { + private static final int[] AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE = new int[] { 0, 1, 2, 3, 4, 5, 6, 8 }; @@ -41,6 +41,22 @@ public final class CodecSpecificDataUtil { private CodecSpecificDataUtil() {} + /** + * Gets the sample rate index. + * + * @param sampleRate The sample rate in Hz. + * @return The sample rate index. + */ + public static int getSampleRateIndex(int sampleRate) { + int sampleRateIndex = 0; + for (; sampleRateIndex < AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE.length; sampleRateIndex++) { + if (AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[sampleRateIndex] == sampleRate) { + return sampleRateIndex; + } + } + return -1; + } + /** * Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 * @@ -49,7 +65,7 @@ public final class CodecSpecificDataUtil { */ public static Pair parseAudioSpecificConfig(byte[] audioSpecificConfig) { int audioObjectType = (audioSpecificConfig[0] >> 3) & 0x1F; - if (audioObjectType != 31) { + if (audioObjectType < 31) { int byteOffset = audioObjectType == 5 || audioObjectType == 29 ? 1 : 0; int frequencyIndex = (audioSpecificConfig[byteOffset] & 0x7) << 1 | ((audioSpecificConfig[byteOffset + 1] >> 7) & 0x1); From 9208c655d18a492fc908b354c75b8927108f6eeb Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Wed, 4 Mar 2015 20:46:22 +0100 Subject: [PATCH 3/5] fix false positives in sync word detection thanks to @jeoliva --- .../java/com/google/android/exoplayer/hls/parser/MpaReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java index 1ea3489ced..69e9ece08f 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java @@ -225,7 +225,7 @@ import java.util.Collections; int startOffset = pesBuffer.getPosition(); int endOffset = pesBuffer.limit(); for (int i = startOffset; i < endOffset - 1; i++) { - int syncBits = (mpaData[i] << 8 ) | mpaData[i + 1]; + int syncBits = ((mpaData[i] & 0xFF) << 8 ) | (mpaData[i + 1] & 0xFF); if ((syncBits & 0xFFF0) == 0xFFF0) { hasCrc = (mpaData[i + 1] & 0x1) == 0; pesBuffer.setPosition(i); From e5acc5a2c86466c26917f810deff2455f9b4272b Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Mon, 13 Apr 2015 16:54:24 +0200 Subject: [PATCH 4/5] resync with Extractor changes --- .../parser => extractor/ts}/MpaReader.java | 31 ++++++++++--------- .../exoplayer/extractor/ts/TsExtractor.java | 4 ++- .../android/exoplayer/util/MimeTypes.java | 1 - 3 files changed, 19 insertions(+), 17 deletions(-) rename library/src/main/java/com/google/android/exoplayer/{hls/parser => extractor/ts}/MpaReader.java (93%) diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpaReader.java similarity index 93% rename from library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java rename to library/src/main/java/com/google/android/exoplayer/extractor/ts/MpaReader.java index 69e9ece08f..2131f2f7a5 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/MpaReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpaReader.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer.hls.parser; +package com.google.android.exoplayer.extractor.ts; import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.upstream.BufferPool; +import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.util.CodecSpecificDataUtil; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.ParsableBitArray; @@ -48,7 +48,8 @@ import java.util.Collections; // Used to find the header. private boolean hasCrc; - // Parsed from the header. + // Used when parsing the header. + private boolean hasOutputFormat; private long frameDurationUs; private int sampleSize; @@ -149,8 +150,8 @@ import java.util.Collections; 1 // Layer3 }; - public MpaReader(BufferPool bufferPool) { - super(bufferPool); + public MpaReader(TrackOutput output) { + super(output); mpaScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]); state = STATE_FINDING_SYNC; } @@ -171,18 +172,17 @@ import java.util.Collections; case STATE_READING_HEADER: int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE; if (continueRead(data, mpaScratch.getData(), targetLength)) { - startSample(timeUs); parseHeader(); - bytesRead = 0; + bytesRead = targetLength; state = STATE_READING_SAMPLE; } break; case STATE_READING_SAMPLE: int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); - appendData(data, bytesToRead); + output.sampleData(data, bytesToRead); bytesRead += bytesToRead; if (bytesRead == sampleSize) { - commitSample(true); + output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); timeUs += frameDurationUs; bytesRead = 0; state = STATE_FINDING_SYNC; @@ -243,7 +243,7 @@ import java.util.Collections; * @param LSF Low Sample rate Format (MPEG 2) * @param bitrate The bitrate in bits per second * @param samplesPerSec The sampling rate in hertz - * @param paddingSize + * @param -paddingSize * @return Frame size in bytes */ private static int CalcMpaFrameSize (int layer, int LSF, int bitrate, int samplesPerSec, int paddingSize) { @@ -255,9 +255,9 @@ import java.util.Collections; */ private void parseHeader() { int headerLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE; - mpaScratch.setPosition(0); - if (!hasMediaFormat()) { + if (!hasOutputFormat) { + mpaScratch.setPosition(0); mpaScratch.skipBits(12); int isLSF = (!mpaScratch.readBit()) ? 1 : 0; int layer = mpaScratch.readBits(2) ^ 3; @@ -279,14 +279,15 @@ import java.util.Collections; MediaFormat mediaFormat = MediaFormat.createAudioFormat(/*isLSF == 1 ?*/ MimeTypes.AUDIO_MPEG/* : MimeTypes.AUDIO_MP1L2*/, MediaFormat.NO_VALUE, audioParams.second, audioParams.first, Collections.singletonList(audioSpecificConfig)); + output.format(mediaFormat); + hasOutputFormat = true; frameDurationUs = (C.MICROS_PER_SECOND * MPA_SAMPLES_PER_FRAME[isLSF][layer]) / mediaFormat.sampleRate; - setMediaFormat(mediaFormat); - sampleSize = CalcMpaFrameSize(layer, isLSF, bitRate * 1000, sampleRate, paddingBit) - headerLength; + sampleSize = CalcMpaFrameSize(layer, isLSF, bitRate * 1000, sampleRate, paddingBit); } mpaScratch.setPosition(0); ParsableByteArray header = new ParsableByteArray(mpaScratch.getData(),headerLength); - appendData(header, headerLength); + output.sampleData(header, headerLength); } } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java index 5e2fb1041a..4d66527f1b 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java @@ -248,8 +248,10 @@ public final class TsExtractor implements Extractor { ElementaryStreamReader pesPayloadReader = null; switch (streamType) { case TS_STREAM_TYPE_MPA: + pesPayloadReader = new MpaReader(output.track(TS_STREAM_TYPE_MPA)); + break; case TS_STREAM_TYPE_MPA_LSF: - pesPayloadReader = new MpaReader(bufferPool); + pesPayloadReader = new MpaReader(output.track(TS_STREAM_TYPE_MPA_LSF)); break; case TS_STREAM_TYPE_AAC: pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC)); diff --git a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java index 1d66360951..9dcc1638ee 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java +++ b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java @@ -31,7 +31,6 @@ public class MimeTypes { public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9"; public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es"; - public static final String AUDIO_MPEG = BASE_TYPE_AUDIO + "/mpeg"; public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4"; public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm"; public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3"; From dd3a4a91394068349c5423505dbd81fd56d537e6 Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Wed, 17 Jun 2015 23:00:31 +0200 Subject: [PATCH 5/5] fix audio channels CodecSpecificDataUtil bugs * fix channel number encoding using the AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE * allocate the correct number of bits (4) int the CodecSpecificDataUtil struct --- .../exoplayer/util/CodecSpecificDataUtil.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java b/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java index 771a9957ee..048b1e6695 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java @@ -71,14 +71,16 @@ public final class CodecSpecificDataUtil { ((audioSpecificConfig[byteOffset + 1] >> 7) & 0x1); Assertions.checkState(frequencyIndex < 13); int sampleRate = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; - int channelCount = (audioSpecificConfig[byteOffset + 1] >> 3) & 0xF; + int channelCount = AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[ + (audioSpecificConfig[byteOffset + 1] >> 3) & 0xF]; return Pair.create(sampleRate, channelCount); } else { int frequencyIndex = (audioSpecificConfig[1] & 0x1E) >> 1; Assertions.checkState(frequencyIndex < 13); int sampleRate = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; - int channelCount = (audioSpecificConfig[1] & 0x01) << 2 | - ((audioSpecificConfig[2] >> 6) & 0x03); + int channelCount = AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[ + (audioSpecificConfig[1] & 0x01) << 3 | + ((audioSpecificConfig[2] >> 5) & 0x07)]; return Pair.create(sampleRate, channelCount); } } @@ -88,11 +90,17 @@ public final class CodecSpecificDataUtil { * * @param audioObjectType The audio object type. * @param sampleRateIndex The sample rate index. - * @param channelConfig The channel configuration. + * @param numChannels The channel configuration. * @return The AudioSpecificConfig. */ public static byte[] buildAudioSpecificConfig(int audioObjectType, int sampleRateIndex, - int channelConfig) { + int numChannels) { + int channelConfig = -1; + for (int i = 0; i < AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE.length; ++i) { + if (numChannels == AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[i]) { + channelConfig = i; + } + } if (audioObjectType < 31) { byte[] audioSpecificConfig = new byte[2]; audioSpecificConfig[0] = (byte) ((audioObjectType << 3) & 0xF8 | @@ -108,8 +116,8 @@ public final class CodecSpecificDataUtil { (audioObjectTypeExt >> 3) & 0x07); audioSpecificConfig[1] = (byte) ((audioObjectTypeExt << 5) & 0xE0 | (sampleRateIndex << 1) & 0x1E | - (channelConfig >> 2) & 0x01); - audioSpecificConfig[2] = (byte) ((channelConfig << 6) & 0xC0); + (channelConfig >> 3) & 0x01); + audioSpecificConfig[2] = (byte) ((channelConfig << 5) & 0xE0); return audioSpecificConfig; } }