From ae40208d92b19535d12d81ebb38f95fe90ba155c Mon Sep 17 00:00:00 2001 From: ybai001 Date: Mon, 24 Dec 2018 09:38:52 +0800 Subject: [PATCH 1/6] Add AC-4 format support * Add AC-4 MIME type definition * Add AC-4 format support in Mp4Extractor and TsExtractor * Add AC-4 Extractor * Add AC-4 playback support in MPEG-4, MPEG-DASH, TS and HLS --- .../java/com/google/android/exoplayer2/C.java | 9 +- .../android/exoplayer2/audio/Ac4Util.java | 269 ++++++++++++++++++ .../exoplayer2/audio/DefaultAudioSink.java | 4 + .../audio/MediaCodecAudioRenderer.java | 5 + .../extractor/DefaultExtractorsFactory.java | 6 +- .../exoplayer2/extractor/mp4/Atom.java | 2 + .../exoplayer2/extractor/mp4/AtomParsers.java | 8 + .../extractor/mp4/FragmentedMp4Extractor.java | 8 + .../extractor/mp4/Mp4Extractor.java | 9 + .../exoplayer2/extractor/ts/Ac4Extractor.java | 162 +++++++++++ .../exoplayer2/extractor/ts/Ac4Reader.java | 215 ++++++++++++++ .../ts/DefaultTsPayloadReaderFactory.java | 2 + .../exoplayer2/extractor/ts/TsExtractor.java | 14 + .../android/exoplayer2/util/MimeTypes.java | 7 + .../hls/DefaultHlsExtractorFactory.java | 14 + 15 files changed, 729 insertions(+), 5 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/audio/Ac4Util.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Extractor.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index fac9818d9ec..315551fa62a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -143,8 +143,8 @@ private C() {} * {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link * #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link * #ENCODING_PCM_MU_LAW}, {@link #ENCODING_PCM_A_LAW}, {@link #ENCODING_AC3}, {@link - * #ENCODING_E_AC3}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or {@link - * #ENCODING_DOLBY_TRUEHD}. + * #ENCODING_E_AC3}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD}, {@link + * #ENCODING_DOLBY_TRUEHD} or {@link #ENCODING_AC4}. */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -162,7 +162,8 @@ private C() {} ENCODING_E_AC3, ENCODING_DTS, ENCODING_DTS_HD, - ENCODING_DOLBY_TRUEHD + ENCODING_DOLBY_TRUEHD, + ENCODING_AC4 }) public @interface Encoding {} @@ -212,6 +213,8 @@ private C() {} public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD; /** @see AudioFormat#ENCODING_DOLBY_TRUEHD */ public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD; + /** @see AudioFormat#ENCODING_AC4 */ + public static final int ENCODING_AC4 = AudioFormat.ENCODING_AC4; /** * Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac4Util.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac4Util.java new file mode 100644 index 00000000000..9b845a14d43 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac4Util.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2018 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.audio; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.drm.DrmInitData; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.ParsableBitArray; +import com.google.android.exoplayer2.util.ParsableByteArray; +import java.nio.ByteBuffer; + +/** + * Utility methods for parsing AC-4 frames, which are access units in AC-4 bitstreams. + */ +public final class Ac4Util { + + /** + * Holds sample format information as presented by a syncframe header. + */ + public static final class SyncFrameInfo { + + /** + * The sample mime type of the bitstream is {@link MimeTypes#AUDIO_AC4}. + */ + public final String mimeType; + /** + * The bitstream version. + */ + public final int bitstreamVersion; + /** + * The audio sampling rate in Hz. + */ + public final int sampleRate; + /** + * The number of audio channels + */ + public final int channelCount; + /** + * The size of the frame. + */ + public final int frameSize; + /** + * Number of audio samples in the frame. + */ + public final int sampleCount; + + private SyncFrameInfo( + String mimeType, + int bitstreamVersion, + int channelCount, + int sampleRate, + int frameSize, + int sampleCount) { + this.mimeType = mimeType; + this.bitstreamVersion = bitstreamVersion; + this.channelCount = channelCount; + this.sampleRate = sampleRate; + this.frameSize = frameSize; + this.sampleCount = sampleCount; + } + } + + /** + * The channel count of AC-4 stream. + */ + // TODO: Parse AC-4 stream channel count. + public static final int CHANNEL_COUNT_2 = 2; + /** + * The header size for AC-4 parser. Only needs to be as big as we need to read, not the full + * header size. + */ + public static final int HEADER_SIZE_FOR_PARSER = 16; + /** + * Number of audio samples in the frame. Defined in IEC61937-14:2017 table 5 and 6. This table + * provides the number of samples per frame at the playback sampling frequency of 48kHz. For + * 44.1kHz, only frame_rate_index(13) is valid and corresponding sample count is 2048. + */ + private static final int[] SAMPLE_COUNT = new int[] { + /* [ 0] 23.976 fps */ 2002, + /* [ 1] 24 fps */ 2000, + /* [ 2] 25 fps */ 1920, + /* [ 3] 29.97 fps */ 1601, // 1601 | 1602 | 1601 | 1602 | 1602 + /* [ 4] 30 fps */ 1600, + /* [ 5] 47.95 fps */ 1001, + /* [ 6] 48 fps */ 1000, + /* [ 7] 50 fps */ 960, + /* [ 8] 59.94 fps */ 800, // 800 | 801 | 801 | 801 | 801 + /* [ 9] 60 fps */ 800, + /* [10] 100 fps */ 480, + /* [11] 119.88 fps */ 400, // 400 | 400 | 401 | 400 | 401 + /* [12] 120 fps */ 400, + /* [13] 23.438 fps */ 2048 + }; + + /** + * Returns the AC-4 format given {@code data} containing the AC4SpecificBox according to ETSI TS + * 103 190-1 Annex E. The reading position of {@code data} will be modified. + * + * @param data The AC4SpecificBox to parse. + * @param trackId The track identifier to set on the format. + * @param language The language to set on the format. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @return The AC-4 format parsed from data in the header. + */ + public static Format parseAc4AnnexEFormat( + ParsableByteArray data, String trackId, String language, DrmInitData drmInitData) { + data.skipBytes(1); + int sampleRate = ((data.readUnsignedByte() & 0x20) >> 5 == 1) ? 48000 : 44100; + return Format.createAudioSampleFormat( + trackId, + MimeTypes.AUDIO_AC4, + null, + Format.NO_VALUE, + Format.NO_VALUE, + CHANNEL_COUNT_2, + sampleRate, + null, + drmInitData, + 0, + language); + } + + private static int readVariableBits(ParsableBitArray data, int nbits) { + int value = 0; + while (true) { + int moreBits; + value += data.readBits(nbits); + moreBits = data.readBits(1); + if (moreBits == 0) + break; + value++; + value <<= nbits; + } + return value; + } + + /** + * Returns AC-4 format information given {@code data} containing a syncframe. The reading + * position of {@code data} will be modified. + * + * @param data The data to parse, positioned at the start of the syncframe. + * @return The AC-4 format data parsed from the header. + */ + public static SyncFrameInfo parseAc4SyncframeInfo(ParsableBitArray data) { + int headerSize = 0; + int syncWord = data.readBits(16); + headerSize += 2; + int frameSize = data.readBits(16); + headerSize += 2; + if (frameSize == 0xFFFF) { + frameSize = data.readBits(24); + headerSize += 3; + } + frameSize += headerSize; + if (syncWord == 0xAC41) { + frameSize += 2; + } + int bitstreamVersion = data.readBits(2); + if (bitstreamVersion == 3){ + bitstreamVersion += readVariableBits(data, 2); + } + int sequenceCounter = data.readBits(10); + if (data.readBits(1) == 1) { + if (data.readBits(3) > 0){ + data.skipBits(2); + } + } + int sampleRate = (data.readBits(1) == 1) ? 48000 : 44100; + int frameRateIndex = data.readBits(4); + int sampleCount = 0; + if (sampleRate == 44100 && frameRateIndex == 13) { + sampleCount = SAMPLE_COUNT[frameRateIndex]; + } else if (sampleRate == 48000 && frameRateIndex < SAMPLE_COUNT.length) { + sampleCount = SAMPLE_COUNT[frameRateIndex]; + switch (sequenceCounter % 5) { + case 1: + case 3: + if (frameRateIndex == 3 || frameRateIndex == 8) { + sampleCount++; + } + break; + case 2: + if (frameRateIndex == 8 || frameRateIndex == 11) { + sampleCount++; + } + break; + case 4: + if (frameRateIndex == 3 || frameRateIndex == 8 || frameRateIndex == 11) { + sampleCount++; + } + break; + default: + break; + } + } + return new SyncFrameInfo( + MimeTypes.AUDIO_AC4, bitstreamVersion, CHANNEL_COUNT_2, sampleRate, frameSize, sampleCount); + } + + /** + * Returns the size in bytes of the given AC-4 syncframe. + * + * @param data The syncframe to parse. + * @return The syncframe size in bytes. {@link C#LENGTH_UNSET} if the input is invalid. + */ + public static int parseAc4SyncframeSize(byte[] data, int syncBytes) { + if (data.length < 7) { + return C.LENGTH_UNSET; + } + int headerSize = 2; // syncword + int frameSize = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF); + headerSize += 2; + if (frameSize == 0xFFFF) { + frameSize = ((data[4] & 0xFF) << 16) | ((data[5] & 0xFF) << 8) | (data[6] & 0xFF); + headerSize += 3; + } + if (syncBytes == 0xAC41) { + headerSize += 2; + } + frameSize += headerSize; + return frameSize; + } + + /** + * Reads the number of audio samples represented by the given AC-4 syncframe. The buffer's + * position is not modified. + * + * @param buffer The {@link ByteBuffer} from which to read the syncframe. + * @return The number of audio samples represented by the syncframe. + */ + public static int parseAc4SyncframeAudioSampleCount(ByteBuffer buffer) { + byte[] bufferBytes = new byte[HEADER_SIZE_FOR_PARSER]; + buffer.get(bufferBytes); + ParsableBitArray data = new ParsableBitArray(bufferBytes); + SyncFrameInfo ac4SyncframeInfo = parseAc4SyncframeInfo(data); + return ac4SyncframeInfo.sampleCount; + } + + /** + * Create AC-4 sample header, which includes AC-4 syncword and frame size. + * + * @param sampleSize The size of AC-4 sample. + * @return The AC-4 sample header byte array. + */ + public static ParsableByteArray getAc4SampleHeader(int sampleSize) { + // Add AC-4 Syncword 0xAC40 and frame size to create AC-4 syncframe + // ETSI TS 103 190-1 V1.3.1, Annex G + byte[] ac4SampleHeader = new byte[] {(byte)0xAC, 0x40, (byte)0xFF, (byte)0xFF, + (byte)(sampleSize >> 16 & 0xFF), (byte)(sampleSize >> 8 & 0xFF), (byte)(sampleSize & 0xFF)}; + return new ParsableByteArray(ac4SampleHeader); + } + + private Ac4Util() {} + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index fc515dbdb34..686e2365fd0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -1202,6 +1202,8 @@ private static int getMaximumEncodedRateBytesPerSecond(@C.Encoding int encoding) return 18000 * 1000 / 8; case C.ENCODING_DOLBY_TRUEHD: return 24500 * 1000 / 8; + case C.ENCODING_AC4: + return 2688 * 1000 / 8; case C.ENCODING_INVALID: case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_24BIT: @@ -1229,6 +1231,8 @@ private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffe ? 0 : (Ac3Util.parseTrueHdSyncframeAudioSampleCount(buffer, syncframeOffset) * Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT); + } else if (encoding == C.ENCODING_AC4) { + return Ac4Util.parseAc4SyncframeAudioSampleCount(buffer); } else { throw new IllegalStateException("Unexpected audio encoding: " + encoding); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 49c391c4cc8..f9749450c3c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -787,6 +787,11 @@ protected MediaFormat getMediaFormat( mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate); } } + if (MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) { + // Some devices handle AC-4 raw frame by default. + // Need to notify the codec to handle AC-4 sync frame. + mediaFormat.setInteger("ac4-is-sync", 1); + } return mediaFormat; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java index 54bb617c58c..14db0b2ae1b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; import com.google.android.exoplayer2.extractor.ogg.OggExtractor; import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; +import com.google.android.exoplayer2.extractor.ts.Ac4Extractor; import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; import com.google.android.exoplayer2.extractor.ts.PsExtractor; @@ -206,7 +207,7 @@ public synchronized DefaultExtractorsFactory setTsExtractorFlags( @Override public synchronized Extractor[] createExtractors() { - Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 12 : 13]; + Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 13 : 14]; extractors[0] = new MatroskaExtractor(matroskaFlags); extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags); extractors[2] = new Mp4Extractor(mp4Flags); @@ -235,9 +236,10 @@ public synchronized Extractor[] createExtractors() { | (constantBitrateSeekingEnabled ? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING : 0)); + extractors[12] = new Ac4Extractor(); if (FLAC_EXTRACTOR_CONSTRUCTOR != null) { try { - extractors[12] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance(); + extractors[13] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance(); } catch (Exception e) { // Should never happen. throw new IllegalStateException("Unexpected error creating FLAC extractor", e); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java index 440e577c7d8..5f1cf7ecb87 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java @@ -67,6 +67,8 @@ public static final int TYPE_dac3 = Util.getIntegerCodeForString("dac3"); public static final int TYPE_ec_3 = Util.getIntegerCodeForString("ec-3"); public static final int TYPE_dec3 = Util.getIntegerCodeForString("dec3"); + public static final int TYPE_ac_4 = Util.getIntegerCodeForString("ac-4"); + public static final int TYPE_dac4 = Util.getIntegerCodeForString("dac4"); public static final int TYPE_dtsc = Util.getIntegerCodeForString("dtsc"); public static final int TYPE_dtsh = Util.getIntegerCodeForString("dtsh"); public static final int TYPE_dtsl = Util.getIntegerCodeForString("dtsl"); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index d085156f2bf..24b24d50117 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.audio.Ac3Util; +import com.google.android.exoplayer2.audio.Ac4Util; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.extractor.GaplessInfoHolder; import com.google.android.exoplayer2.metadata.Metadata; @@ -684,6 +685,7 @@ private static StsdData parseStsd(ParsableByteArray stsd, int trackId, int rotat || childAtomType == Atom.TYPE_enca || childAtomType == Atom.TYPE_ac_3 || childAtomType == Atom.TYPE_ec_3 + || childAtomType == Atom.TYPE_ac_4 || childAtomType == Atom.TYPE_dtsc || childAtomType == Atom.TYPE_dtse || childAtomType == Atom.TYPE_dtsh @@ -974,6 +976,8 @@ private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType mimeType = MimeTypes.AUDIO_AC3; } else if (atomType == Atom.TYPE_ec_3) { mimeType = MimeTypes.AUDIO_E_AC3; + } else if (atomType == Atom.TYPE_ac_4) { + mimeType = MimeTypes.AUDIO_AC4; } else if (atomType == Atom.TYPE_dtsc) { mimeType = MimeTypes.AUDIO_DTS; } else if (atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl) { @@ -1031,6 +1035,10 @@ private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType parent.setPosition(Atom.HEADER_SIZE + childPosition); out.format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language, drmInitData); + } else if (childAtomType == Atom.TYPE_dac4) { + parent.setPosition(Atom.HEADER_SIZE + childPosition); + out.format = Ac4Util.parseAc4AnnexEFormat(parent, Integer.toString(trackId), language, + drmInitData); } else if (childAtomType == Atom.TYPE_ddts) { out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 72bcaa9fe28..1d458546471 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.audio.Ac4Util; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.extractor.ChunkIndex; @@ -1276,10 +1277,17 @@ private boolean readSample(ExtractorInput input) throws IOException, Interrupted } } } else { + int sampleHeaderSize = 0; + if (MimeTypes.AUDIO_AC4.equals(track.format.sampleMimeType)) { + ParsableByteArray ac4SampleHeaderData = Ac4Util.getAc4SampleHeader(sampleSize); + output.sampleData(ac4SampleHeaderData, ac4SampleHeaderData.capacity()); + sampleHeaderSize = ac4SampleHeaderData.capacity(); + } while (sampleBytesWritten < sampleSize) { int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false); sampleBytesWritten += writtenBytes; } + sampleSize += sampleHeaderSize; } @C.BufferFlags int sampleFlags = fragment.sampleIsSyncFrameTable[sampleIndex] diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index ec24bed9649..7c9fe2a1879 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -19,6 +19,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.audio.Ac4Util; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorOutput; @@ -31,6 +32,7 @@ import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; @@ -536,11 +538,18 @@ private int readSample(ExtractorInput input, PositionHolder positionHolder) } } } else { + int sampleHeaderSize = 0; + if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType)) { + ParsableByteArray ac4SampleHeaderData = Ac4Util.getAc4SampleHeader(sampleSize); + trackOutput.sampleData(ac4SampleHeaderData, ac4SampleHeaderData.capacity()); + sampleHeaderSize = ac4SampleHeaderData.capacity(); + } while (sampleBytesWritten < sampleSize) { int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false); sampleBytesWritten += writtenBytes; sampleCurrentNalBytesRemaining -= writtenBytes; } + sampleSize += sampleHeaderSize; } trackOutput.sampleMetadata(track.sampleTable.timestampsUs[sampleIndex], track.sampleTable.flags[sampleIndex], sampleSize, 0, null); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Extractor.java new file mode 100644 index 00000000000..87b106de426 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Extractor.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2018 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 static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.audio.Ac4Util; +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.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.android.exoplayer2.util.Util; + +import java.io.IOException; + +/** + * Extracts data from AC-4 bitstreams. + */ +public final class Ac4Extractor implements Extractor { + + /** + * Factory for {@link Ac4Extractor} instances. + */ + public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new Ac4Extractor()}; + + /** + * 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 AC40_SYNC_WORD = 0xAC40; + private static final int AC41_SYNC_WORD = 0xAC41; + /** + * Typical AC-4 frame size (in bytes) + * The value 16 kB is determined by the maximum frame size used in broadcast applications. + */ + private static final int FRAMELEN_TYPICAL = 16384; /* in bytes (16 kB) */ + private static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); + + private final long firstSampleTimestampUs; + private final Ac4Reader reader; + private final ParsableByteArray sampleData; + + private boolean startedPacket; + + public Ac4Extractor() { + this(0); + } + + public Ac4Extractor(long firstSampleTimestampUs) { + this.firstSampleTimestampUs = firstSampleTimestampUs; + reader = new Ac4Reader(); + sampleData = new ParsableByteArray(FRAMELEN_TYPICAL); + } + + // Extractor implementation. + + @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, 7); + scratch.setPosition(0); + int syncBytes = scratch.readUnsignedShort(); + if (syncBytes != AC40_SYNC_WORD && syncBytes != AC41_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 = Ac4Util.parseAc4SyncframeSize(scratch.data, syncBytes); + if (frameSize == C.LENGTH_UNSET) { + return false; + } + input.advancePeekPosition(frameSize - 7); + } + } + } + + @Override + public void init(ExtractorOutput output) { + reader.createTracks(output, new TrackIdGenerator(0, 1)); + output.endTracks(); + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + } + + @Override + public void seek(long position, long timeUs) { + 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, FRAMELEN_TYPICAL); + 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, FLAG_DATA_ALIGNMENT_INDICATOR); + 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; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java new file mode 100644 index 00000000000..f5d50cde92b --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2018 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.support.annotation.IntDef; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.audio.Ac4Util; +import com.google.android.exoplayer2.audio.Ac4Util.SyncFrameInfo; +import com.google.android.exoplayer2.extractor.ExtractorOutput; +import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.google.android.exoplayer2.util.ParsableBitArray; +import com.google.android.exoplayer2.util.ParsableByteArray; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Parses a continuous AC-4 byte stream and extracts individual samples. + */ +public final class Ac4Reader implements ElementaryStreamReader { + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_FINDING_SYNC, STATE_READING_HEADER, STATE_READING_SAMPLE}) + private @interface State {} + + 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 final ParsableBitArray headerScratchBits; + private final ParsableByteArray headerScratchBytes; + private final String language; + + private String trackFormatId; + private TrackOutput output; + + @State private int state; + private int bytesRead; + + // Used to find the header. + private boolean lastByteWasAC; + private boolean hasCRC; + + // Used when parsing the header. + private long sampleDurationUs; + private Format format; + private int sampleSize; + + // Used when reading the samples. + private long timeUs; + + /** + * Constructs a new reader for AC-4 elementary streams. + */ + public Ac4Reader() { + this(null); + } + + /** + * Constructs a new reader for AC-4 elementary streams. + * + * @param language Track language. + */ + public Ac4Reader(String language) { + headerScratchBits = new ParsableBitArray(new byte[Ac4Util.HEADER_SIZE_FOR_PARSER]); + headerScratchBytes = new ParsableByteArray(headerScratchBits.data); + state = STATE_FINDING_SYNC; + bytesRead = 0; + lastByteWasAC = false; + hasCRC = false; + this.language = language; + } + + @Override + public void seek() { + state = STATE_FINDING_SYNC; + bytesRead = 0; + lastByteWasAC = false; + hasCRC = false; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) { + generator.generateNewId(); + trackFormatId = generator.getFormatId(); + output = extractorOutput.track(generator.getTrackId(), C.TRACK_TYPE_AUDIO); + } + + @Override + public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) { + timeUs = pesTimeUs; + } + + @Override + public void consume(ParsableByteArray data) { + while (data.bytesLeft() > 0) { + switch (state) { + case STATE_FINDING_SYNC: + if (skipToNextSync(data)) { + state = STATE_READING_HEADER; + headerScratchBytes.data[0] = (byte)0xAC; + headerScratchBytes.data[1] = 0x40; + if (hasCRC) + headerScratchBytes.data[1] = 0x41; + bytesRead = 2; + } + break; + case STATE_READING_HEADER: + if (continueRead(data, headerScratchBytes.data, Ac4Util.HEADER_SIZE_FOR_PARSER)) { + parseHeader(); + headerScratchBytes.setPosition(0); + output.sampleData(headerScratchBytes, Ac4Util.HEADER_SIZE_FOR_PARSER); + 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.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + timeUs += sampleDurationUs; + state = STATE_FINDING_SYNC; + } + break; + default: + 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 syncword, advancing the position to the byte that immediately follows it. If a + * syncword was not located, the position is advanced to the limit. + * + * @param pesBuffer The buffer whose position should be advanced. + * @return Whether a syncword position was found. + */ + private boolean skipToNextSync(ParsableByteArray pesBuffer) { + while (pesBuffer.bytesLeft() > 0) { + if (!lastByteWasAC) { + lastByteWasAC = (pesBuffer.readUnsignedByte() == 0xAC); + continue; + } + int secondByte = pesBuffer.readUnsignedByte(); + if (secondByte == 0x40 || secondByte == 0x41) { + lastByteWasAC = false; + hasCRC = (secondByte == 0x41); + return true; + } else { + lastByteWasAC = (secondByte == 0xAC); + } + } + return false; + } + + /** + * Parses the sample header. + */ + @SuppressWarnings("ReferenceEquality") + private void parseHeader() { + headerScratchBits.setPosition(0); + SyncFrameInfo frameInfo = Ac4Util.parseAc4SyncframeInfo(headerScratchBits); + if (format == null || frameInfo.channelCount != format.channelCount + || frameInfo.sampleRate != format.sampleRate + || frameInfo.mimeType != format.sampleMimeType) { + format = Format.createAudioSampleFormat(trackFormatId, frameInfo.mimeType, null, + Format.NO_VALUE, Format.NO_VALUE, frameInfo.channelCount, frameInfo.sampleRate, null, + null, 0, language); + output.format(format); + } + sampleSize = frameInfo.frameSize; + // In this class a sample is an AC-4 sync frame, but the MediaFormat sample rate specifies the + // number of PCM audio samples per second. + sampleDurationUs = C.MICROS_PER_SECOND * frameInfo.sampleCount / format.sampleRate; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index a5506e2cfbe..488480d10e3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -142,6 +142,8 @@ public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { case TsExtractor.TS_STREAM_TYPE_AC3: case TsExtractor.TS_STREAM_TYPE_E_AC3: return new PesReader(new Ac3Reader(esInfo.language)); + case TsExtractor.TS_STREAM_TYPE_AC4: + return new PesReader(new Ac4Reader(esInfo.language)); case TsExtractor.TS_STREAM_TYPE_DTS: case TsExtractor.TS_STREAM_TYPE_HDMV_DTS: return new PesReader(new DtsReader(esInfo.language)); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index f47a481d7e0..21dec0d1111 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -85,6 +85,7 @@ public final class TsExtractor implements Extractor { public static final int TS_STREAM_TYPE_DTS = 0x8A; public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82; public static final int TS_STREAM_TYPE_E_AC3 = 0x87; + public static final int TS_STREAM_TYPE_AC4 = 0xAC; /* DVB/ATSC AC-4 Descriptor */ public static final int TS_STREAM_TYPE_H262 = 0x02; public static final int TS_STREAM_TYPE_H264 = 0x1B; public static final int TS_STREAM_TYPE_H265 = 0x24; @@ -100,6 +101,7 @@ public final class TsExtractor implements Extractor { private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3"); private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3"); + private static final long AC4_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-4"); private static final long HEVC_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("HEVC"); private static final int BUFFER_SIZE = TS_PACKET_SIZE * 50; @@ -484,8 +486,11 @@ private class PmtReader implements SectionPayloadReader { private static final int TS_PMT_DESC_AC3 = 0x6A; private static final int TS_PMT_DESC_EAC3 = 0x7A; private static final int TS_PMT_DESC_DTS = 0x7B; + private static final int TS_PMT_DESC_DVB_EXT = 0x7F; private static final int TS_PMT_DESC_DVBSUBS = 0x59; + private static final int TS_PMT_DESC_DVB_EXT_AC4 = 0x15; + private final ParsableBitArray pmtScratch; private final SparseArray trackIdToReaderScratch; private final SparseIntArray trackIdToPidScratch; @@ -638,6 +643,8 @@ private EsInfo readEsInfo(ParsableByteArray data, int length) { streamType = TS_STREAM_TYPE_AC3; } else if (formatIdentifier == E_AC3_FORMAT_IDENTIFIER) { streamType = TS_STREAM_TYPE_E_AC3; + } else if (formatIdentifier == AC4_FORMAT_IDENTIFIER) { + streamType = TS_STREAM_TYPE_AC4; } else if (formatIdentifier == HEVC_FORMAT_IDENTIFIER) { streamType = TS_STREAM_TYPE_H265; } @@ -645,6 +652,13 @@ private EsInfo readEsInfo(ParsableByteArray data, int length) { streamType = TS_STREAM_TYPE_AC3; } else if (descriptorTag == TS_PMT_DESC_EAC3) { // enhanced_AC-3_descriptor streamType = TS_STREAM_TYPE_E_AC3; + } else if (descriptorTag == TS_PMT_DESC_DVB_EXT) { + // extension descriptor in DVB (ETSI EN 300 468) + int descriptorTagExt = data.readUnsignedByte(); + if (descriptorTagExt == TS_PMT_DESC_DVB_EXT_AC4) { + // AC-4_descriptor in DVB (ETSI EN 300 468) + streamType = TS_STREAM_TYPE_AC4; + } } else if (descriptorTag == TS_PMT_DESC_DTS) { // DTS_descriptor streamType = TS_STREAM_TYPE_DTS; } else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index e506ae1b19b..e6cf5ed95ee 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -55,6 +55,7 @@ public final class MimeTypes { public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3"; public static final String AUDIO_E_AC3 = BASE_TYPE_AUDIO + "/eac3"; public static final String AUDIO_E_AC3_JOC = BASE_TYPE_AUDIO + "/eac3-joc"; + public static final String AUDIO_AC4 = BASE_TYPE_AUDIO + "/ac4"; public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd"; public static final String AUDIO_DTS = BASE_TYPE_AUDIO + "/vnd.dts"; public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd"; @@ -237,6 +238,8 @@ public static boolean isApplication(@Nullable String mimeType) { return MimeTypes.AUDIO_E_AC3; } else if (codec.startsWith("ec+3")) { return MimeTypes.AUDIO_E_AC3_JOC; + } else if (codec.startsWith("ac-4") || codec.startsWith("dac4")) { + return MimeTypes.AUDIO_AC4; } else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) { return MimeTypes.AUDIO_DTS; } else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) { @@ -301,6 +304,8 @@ public static String getMimeTypeFromMp4ObjectType(int objectType) { return MimeTypes.AUDIO_DTS_HD; case 0xAD: return MimeTypes.AUDIO_OPUS; + case 0xAE: + return MimeTypes.AUDIO_AC4; default: return null; } @@ -354,6 +359,8 @@ public static int getTrackType(@Nullable String mimeType) { case MimeTypes.AUDIO_E_AC3: case MimeTypes.AUDIO_E_AC3_JOC: return C.ENCODING_E_AC3; + case MimeTypes.AUDIO_AC4: + return C.ENCODING_AC4; case MimeTypes.AUDIO_DTS: return C.ENCODING_DTS; case MimeTypes.AUDIO_DTS_HD: diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java index 8a403c37592..b1e78c22f27 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; +import com.google.android.exoplayer2.extractor.ts.Ac4Extractor; import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; import com.google.android.exoplayer2.extractor.ts.TsExtractor; @@ -43,6 +44,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { public static final String AAC_FILE_EXTENSION = ".aac"; public static final String AC3_FILE_EXTENSION = ".ac3"; + public static final String AC4_FILE_EXTENSION = ".ac4"; public static final String EC3_FILE_EXTENSION = ".ec3"; public static final String MP3_FILE_EXTENSION = ".mp3"; public static final String MP4_FILE_EXTENSION = ".mp4"; @@ -94,6 +96,8 @@ public Pair createExtractor( return buildResult(new AdtsExtractor()); } else if (previousExtractor instanceof Ac3Extractor) { return buildResult(new Ac3Extractor()); + } else if (previousExtractor instanceof Ac4Extractor) { + return buildResult(new Ac4Extractor()); } else if (previousExtractor instanceof Mp3Extractor) { return buildResult(new Mp3Extractor()); } else { @@ -135,6 +139,13 @@ public Pair createExtractor( } } + if (!(extractorByFileExtension instanceof Ac4Extractor)) { + Ac4Extractor ac4Extractor = new Ac4Extractor(); + if (sniffQuietly(ac4Extractor, extractorInput)) { + return buildResult(ac4Extractor); + } + } + if (!(extractorByFileExtension instanceof Mp3Extractor)) { Mp3Extractor mp3Extractor = new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0); @@ -188,6 +199,8 @@ private Extractor createExtractorByFileExtension( } else if (lastPathSegment.endsWith(AC3_FILE_EXTENSION) || lastPathSegment.endsWith(EC3_FILE_EXTENSION)) { return new Ac3Extractor(); + } else if (lastPathSegment.endsWith(AC4_FILE_EXTENSION)) { + return new Ac4Extractor(); } else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) { return new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0); } else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION) @@ -254,6 +267,7 @@ private static Pair buildResult(Extractor extractor) { extractor, extractor instanceof AdtsExtractor || extractor instanceof Ac3Extractor + || extractor instanceof Ac4Extractor || extractor instanceof Mp3Extractor); } From 460f57614230837eab74b1f6bcd200bb3e3da15f Mon Sep 17 00:00:00 2001 From: ybai001 Date: Mon, 24 Dec 2018 09:38:52 +0800 Subject: [PATCH 2/6] Add AC-4 format support * Add AC-4 MIME type definition * Add AC-4 format support in Mp4Extractor and TsExtractor * Add AC-4 Extractor * Add AC-4 playback support in MPEG-4, MPEG-DASH, TS and HLS --- .../extractor/DefaultExtractorsFactory.java | 1 + .../extractor/mp4/FragmentedMp4Extractor.java | 16 ++- .../extractor/mp4/Mp4Extractor.java | 16 ++- .../exoplayer2/extractor/ts/Ac4Reader.java | 4 +- library/core/src/test/assets/ts/sample.ac4 | Bin 0 -> 7594 bytes .../core/src/test/assets/ts/sample.ac4.0.dump | 106 ++++++++++++++++++ .../DefaultExtractorsFactoryTest.java | 4 +- .../extractor/ts/Ac4ExtractorTest.java | 32 ++++++ 8 files changed, 167 insertions(+), 12 deletions(-) create mode 100644 library/core/src/test/assets/ts/sample.ac4 create mode 100644 library/core/src/test/assets/ts/sample.ac4.0.dump create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac4ExtractorTest.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java index 14db0b2ae1b..e2f8887b5c7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -49,6 +49,7 @@ *
  • WAV ({@link WavExtractor}) *
  • AC3 ({@link Ac3Extractor}) *
  • AMR ({@link AmrExtractor}) + *
  • AC4 ({@link Ac4Extractor}) *
  • FLAC (only available if the FLAC extension is built and included) * */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 1d458546471..b1b598d6ef3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -161,6 +161,8 @@ public class FragmentedMp4Extractor implements Extractor { private int sampleBytesWritten; private int sampleCurrentNalBytesRemaining; private boolean processSeiNalUnitPayload; + private boolean isAc4HeaderAdded; + private int ac4SampleHeaderSize; // Extractor output. private ExtractorOutput extractorOutput; @@ -262,6 +264,8 @@ public FragmentedMp4Extractor( durationUs = C.TIME_UNSET; pendingSeekTimeUs = C.TIME_UNSET; segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET; + isAc4HeaderAdded = false; + ac4SampleHeaderSize = 0; enterReadingAtomHeaderState(); } @@ -1217,6 +1221,7 @@ private boolean readSample(ExtractorInput input) throws IOException, Interrupted sampleSize += sampleBytesWritten; parserState = STATE_READING_SAMPLE_CONTINUE; sampleCurrentNalBytesRemaining = 0; + isAc4HeaderAdded = false; } TrackFragment fragment = currentTrackBundle.fragment; @@ -1277,17 +1282,20 @@ private boolean readSample(ExtractorInput input) throws IOException, Interrupted } } } else { - int sampleHeaderSize = 0; - if (MimeTypes.AUDIO_AC4.equals(track.format.sampleMimeType)) { + if (MimeTypes.AUDIO_AC4.equals(track.format.sampleMimeType) && !isAc4HeaderAdded) { ParsableByteArray ac4SampleHeaderData = Ac4Util.getAc4SampleHeader(sampleSize); output.sampleData(ac4SampleHeaderData, ac4SampleHeaderData.capacity()); - sampleHeaderSize = ac4SampleHeaderData.capacity(); + ac4SampleHeaderSize = ac4SampleHeaderData.capacity(); + isAc4HeaderAdded = true; } while (sampleBytesWritten < sampleSize) { int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false); sampleBytesWritten += writtenBytes; } - sampleSize += sampleHeaderSize; + if (MimeTypes.AUDIO_AC4.equals(track.format.sampleMimeType)) { + sampleSize += ac4SampleHeaderSize; + isAc4HeaderAdded = false; + } } @C.BufferFlags int sampleFlags = fragment.sampleIsSyncFrameTable[sampleIndex] diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 7c9fe2a1879..49f5fac7a4b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -118,6 +118,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { private int firstVideoTrackIndex; private long durationUs; private boolean isQuickTime; + private boolean isAc4HeaderAdded; + private int ac4SampleHeaderSize; /** * Creates a new extractor for unfragmented MP4 streams. @@ -139,6 +141,8 @@ public Mp4Extractor(@Flags int flags) { nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); sampleTrackIndex = C.INDEX_UNSET; + isAc4HeaderAdded = false; + ac4SampleHeaderSize = 0; } @Override @@ -489,6 +493,7 @@ private int readSample(ExtractorInput input, PositionHolder positionHolder) if (sampleTrackIndex == C.INDEX_UNSET) { return RESULT_END_OF_INPUT; } + isAc4HeaderAdded = false; } Mp4Track track = tracks[sampleTrackIndex]; TrackOutput trackOutput = track.trackOutput; @@ -538,18 +543,21 @@ private int readSample(ExtractorInput input, PositionHolder positionHolder) } } } else { - int sampleHeaderSize = 0; - if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType)) { + if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType) && !isAc4HeaderAdded) { ParsableByteArray ac4SampleHeaderData = Ac4Util.getAc4SampleHeader(sampleSize); trackOutput.sampleData(ac4SampleHeaderData, ac4SampleHeaderData.capacity()); - sampleHeaderSize = ac4SampleHeaderData.capacity(); + ac4SampleHeaderSize = ac4SampleHeaderData.capacity(); + isAc4HeaderAdded = true; } while (sampleBytesWritten < sampleSize) { int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false); sampleBytesWritten += writtenBytes; sampleCurrentNalBytesRemaining -= writtenBytes; } - sampleSize += sampleHeaderSize; + if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType)) { + sampleSize += ac4SampleHeaderSize; + isAc4HeaderAdded = false; + } } trackOutput.sampleMetadata(track.sampleTable.timestampsUs[sampleIndex], track.sampleTable.flags[sampleIndex], sampleSize, 0, null); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java index f5d50cde92b..2486ab54002 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java @@ -115,9 +115,7 @@ public void consume(ParsableByteArray data) { if (skipToNextSync(data)) { state = STATE_READING_HEADER; headerScratchBytes.data[0] = (byte)0xAC; - headerScratchBytes.data[1] = 0x40; - if (hasCRC) - headerScratchBytes.data[1] = 0x41; + headerScratchBytes.data[1] = headerScratchBytes.data[1] = (byte)(hasCRC ? 0x41 : 0x40); bytesRead = 2; } break; diff --git a/library/core/src/test/assets/ts/sample.ac4 b/library/core/src/test/assets/ts/sample.ac4 new file mode 100644 index 0000000000000000000000000000000000000000..721f53cdd7f844f2459a014047a6e3d1d87c892a GIT binary patch literal 7594 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 a8_0ekcO;V@pg-E8wD*(rzw|VFT>b$mUG&rd literal 0 HcmV?d00001 diff --git a/library/core/src/test/assets/ts/sample.ac4.0.dump b/library/core/src/test/assets/ts/sample.ac4.0.dump new file mode 100644 index 00000000000..03ae07707a2 --- /dev/null +++ b/library/core/src/test/assets/ts/sample.ac4.0.dump @@ -0,0 +1,106 @@ +seekMap: + isSeekable = false + duration = UNSET TIME + getPosition(0) = [[timeUs=0, position=0]] +numberOfTracks = 1 +track 0: + format: + bitrate = -1 + id = 0 + containerMimeType = null + sampleMimeType = audio/ac4 + maxInputSize = -1 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = 0 + pixelWidthHeightRatio = 1.0 + channelCount = 2 + sampleRate = 48000 + pcmEncoding = -1 + encoderDelay = 0 + encoderPadding = 0 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + total output bytes = 7594 + sample count = 19 + sample 0: + time = 0 + flags = 1 + data = length 366, hash B4277F9E + sample 1: + time = 40000 + flags = 1 + data = length 366, hash E8E0A142 + sample 2: + time = 80000 + flags = 1 + data = length 366, hash 2E5073D0 + sample 3: + time = 120000 + flags = 1 + data = length 366, hash 850E71D8 + sample 4: + time = 160000 + flags = 1 + data = length 366, hash 69CD444E + sample 5: + time = 200000 + flags = 1 + data = length 366, hash BD24F36D + sample 6: + time = 240000 + flags = 1 + data = length 366, hash E24F2490 + sample 7: + time = 280000 + flags = 1 + data = length 366, hash EE6F1F06 + sample 8: + time = 320000 + flags = 1 + data = length 366, hash 2DAB000F + sample 9: + time = 360000 + flags = 1 + data = length 366, hash 8102B7EC + sample 10: + time = 400000 + flags = 1 + data = length 366, hash 55BF59AC + sample 11: + time = 440000 + flags = 1 + data = length 494, hash CBC2E09F + sample 12: + time = 480000 + flags = 1 + data = length 519, hash 9DAF56E9 + sample 13: + time = 520000 + flags = 1 + data = length 598, hash 8169EE2 + sample 14: + time = 560000 + flags = 1 + data = length 435, hash 28C21246 + sample 15: + time = 600000 + flags = 1 + data = length 365, hash FF14716D + sample 16: + time = 640000 + flags = 1 + data = length 392, hash 4CC96B29 + sample 17: + time = 680000 + flags = 1 + data = length 373, hash D7AC6D4E + sample 18: + time = 720000 + flags = 1 + data = length 392, hash 99F2511F +tracksEnded = true diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java index 148e04ca77a..bbe7985568e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; import com.google.android.exoplayer2.extractor.ogg.OggExtractor; import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; +import com.google.android.exoplayer2.extractor.ts.Ac4Extractor; import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; import com.google.android.exoplayer2.extractor.ts.PsExtractor; import com.google.android.exoplayer2.extractor.ts.TsExtractor; @@ -62,7 +63,8 @@ public void testCreateExtractors_returnExpectedClasses() { OggExtractor.class, PsExtractor.class, WavExtractor.class, - AmrExtractor.class + AmrExtractor.class, + Ac4Extractor.class }; assertThat(listCreatedExtractorClasses).containsNoDuplicates(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac4ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac4ExtractorTest.java new file mode 100644 index 00000000000..782956dee7b --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac4ExtractorTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 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.testutil.ExtractorAsserts; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit test for {@link Ac4Extractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class Ac4ExtractorTest { + + @Test + public void testAc4Sample() throws Exception { + ExtractorAsserts.assertBehavior(Ac4Extractor::new, "ts/sample.ac4"); + } +} + From e462c6aab7dc5cab47c3daae5298626612d352be Mon Sep 17 00:00:00 2001 From: ybai001 Date: Mon, 24 Dec 2018 09:38:52 +0800 Subject: [PATCH 3/6] Add AC-4 format support * Add AC-4 MIME type definition * Add AC-4 format support in Mp4Extractor and TsExtractor * Add AC-4 Extractor * Add AC-4 playback support in MPEG-4, MPEG-DASH, TS and HLS --- .../extractor/DefaultExtractorsFactory.java | 1 + .../extractor/mp4/FragmentedMp4Extractor.java | 16 ++- .../extractor/mp4/Mp4Extractor.java | 16 ++- .../exoplayer2/extractor/ts/Ac4Reader.java | 4 +- library/core/src/test/assets/ts/sample.ac4 | Bin 0 -> 7594 bytes .../core/src/test/assets/ts/sample.ac4.0.dump | 106 ++++++++++++++++++ .../DefaultExtractorsFactoryTest.java | 4 +- .../extractor/ts/Ac4ExtractorTest.java | 32 ++++++ 8 files changed, 167 insertions(+), 12 deletions(-) create mode 100644 library/core/src/test/assets/ts/sample.ac4 create mode 100644 library/core/src/test/assets/ts/sample.ac4.0.dump create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac4ExtractorTest.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java index 14db0b2ae1b..e2f8887b5c7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -49,6 +49,7 @@ *
  • WAV ({@link WavExtractor}) *
  • AC3 ({@link Ac3Extractor}) *
  • AMR ({@link AmrExtractor}) + *
  • AC4 ({@link Ac4Extractor}) *
  • FLAC (only available if the FLAC extension is built and included) * */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 1d458546471..b1b598d6ef3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -161,6 +161,8 @@ public class FragmentedMp4Extractor implements Extractor { private int sampleBytesWritten; private int sampleCurrentNalBytesRemaining; private boolean processSeiNalUnitPayload; + private boolean isAc4HeaderAdded; + private int ac4SampleHeaderSize; // Extractor output. private ExtractorOutput extractorOutput; @@ -262,6 +264,8 @@ public FragmentedMp4Extractor( durationUs = C.TIME_UNSET; pendingSeekTimeUs = C.TIME_UNSET; segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET; + isAc4HeaderAdded = false; + ac4SampleHeaderSize = 0; enterReadingAtomHeaderState(); } @@ -1217,6 +1221,7 @@ private boolean readSample(ExtractorInput input) throws IOException, Interrupted sampleSize += sampleBytesWritten; parserState = STATE_READING_SAMPLE_CONTINUE; sampleCurrentNalBytesRemaining = 0; + isAc4HeaderAdded = false; } TrackFragment fragment = currentTrackBundle.fragment; @@ -1277,17 +1282,20 @@ private boolean readSample(ExtractorInput input) throws IOException, Interrupted } } } else { - int sampleHeaderSize = 0; - if (MimeTypes.AUDIO_AC4.equals(track.format.sampleMimeType)) { + if (MimeTypes.AUDIO_AC4.equals(track.format.sampleMimeType) && !isAc4HeaderAdded) { ParsableByteArray ac4SampleHeaderData = Ac4Util.getAc4SampleHeader(sampleSize); output.sampleData(ac4SampleHeaderData, ac4SampleHeaderData.capacity()); - sampleHeaderSize = ac4SampleHeaderData.capacity(); + ac4SampleHeaderSize = ac4SampleHeaderData.capacity(); + isAc4HeaderAdded = true; } while (sampleBytesWritten < sampleSize) { int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false); sampleBytesWritten += writtenBytes; } - sampleSize += sampleHeaderSize; + if (MimeTypes.AUDIO_AC4.equals(track.format.sampleMimeType)) { + sampleSize += ac4SampleHeaderSize; + isAc4HeaderAdded = false; + } } @C.BufferFlags int sampleFlags = fragment.sampleIsSyncFrameTable[sampleIndex] diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 7c9fe2a1879..49f5fac7a4b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -118,6 +118,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { private int firstVideoTrackIndex; private long durationUs; private boolean isQuickTime; + private boolean isAc4HeaderAdded; + private int ac4SampleHeaderSize; /** * Creates a new extractor for unfragmented MP4 streams. @@ -139,6 +141,8 @@ public Mp4Extractor(@Flags int flags) { nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); sampleTrackIndex = C.INDEX_UNSET; + isAc4HeaderAdded = false; + ac4SampleHeaderSize = 0; } @Override @@ -489,6 +493,7 @@ private int readSample(ExtractorInput input, PositionHolder positionHolder) if (sampleTrackIndex == C.INDEX_UNSET) { return RESULT_END_OF_INPUT; } + isAc4HeaderAdded = false; } Mp4Track track = tracks[sampleTrackIndex]; TrackOutput trackOutput = track.trackOutput; @@ -538,18 +543,21 @@ private int readSample(ExtractorInput input, PositionHolder positionHolder) } } } else { - int sampleHeaderSize = 0; - if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType)) { + if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType) && !isAc4HeaderAdded) { ParsableByteArray ac4SampleHeaderData = Ac4Util.getAc4SampleHeader(sampleSize); trackOutput.sampleData(ac4SampleHeaderData, ac4SampleHeaderData.capacity()); - sampleHeaderSize = ac4SampleHeaderData.capacity(); + ac4SampleHeaderSize = ac4SampleHeaderData.capacity(); + isAc4HeaderAdded = true; } while (sampleBytesWritten < sampleSize) { int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false); sampleBytesWritten += writtenBytes; sampleCurrentNalBytesRemaining -= writtenBytes; } - sampleSize += sampleHeaderSize; + if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType)) { + sampleSize += ac4SampleHeaderSize; + isAc4HeaderAdded = false; + } } trackOutput.sampleMetadata(track.sampleTable.timestampsUs[sampleIndex], track.sampleTable.flags[sampleIndex], sampleSize, 0, null); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java index f5d50cde92b..a1e5e51e9d9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java @@ -115,9 +115,7 @@ public void consume(ParsableByteArray data) { if (skipToNextSync(data)) { state = STATE_READING_HEADER; headerScratchBytes.data[0] = (byte)0xAC; - headerScratchBytes.data[1] = 0x40; - if (hasCRC) - headerScratchBytes.data[1] = 0x41; + headerScratchBytes.data[1] = (byte)(hasCRC ? 0x41 : 0x40); bytesRead = 2; } break; diff --git a/library/core/src/test/assets/ts/sample.ac4 b/library/core/src/test/assets/ts/sample.ac4 new file mode 100644 index 0000000000000000000000000000000000000000..721f53cdd7f844f2459a014047a6e3d1d87c892a GIT binary patch literal 7594 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 a8_0ekcO;V@pg-E8wD*(rzw|VFT>b$mUG&rd literal 0 HcmV?d00001 diff --git a/library/core/src/test/assets/ts/sample.ac4.0.dump b/library/core/src/test/assets/ts/sample.ac4.0.dump new file mode 100644 index 00000000000..03ae07707a2 --- /dev/null +++ b/library/core/src/test/assets/ts/sample.ac4.0.dump @@ -0,0 +1,106 @@ +seekMap: + isSeekable = false + duration = UNSET TIME + getPosition(0) = [[timeUs=0, position=0]] +numberOfTracks = 1 +track 0: + format: + bitrate = -1 + id = 0 + containerMimeType = null + sampleMimeType = audio/ac4 + maxInputSize = -1 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = 0 + pixelWidthHeightRatio = 1.0 + channelCount = 2 + sampleRate = 48000 + pcmEncoding = -1 + encoderDelay = 0 + encoderPadding = 0 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + total output bytes = 7594 + sample count = 19 + sample 0: + time = 0 + flags = 1 + data = length 366, hash B4277F9E + sample 1: + time = 40000 + flags = 1 + data = length 366, hash E8E0A142 + sample 2: + time = 80000 + flags = 1 + data = length 366, hash 2E5073D0 + sample 3: + time = 120000 + flags = 1 + data = length 366, hash 850E71D8 + sample 4: + time = 160000 + flags = 1 + data = length 366, hash 69CD444E + sample 5: + time = 200000 + flags = 1 + data = length 366, hash BD24F36D + sample 6: + time = 240000 + flags = 1 + data = length 366, hash E24F2490 + sample 7: + time = 280000 + flags = 1 + data = length 366, hash EE6F1F06 + sample 8: + time = 320000 + flags = 1 + data = length 366, hash 2DAB000F + sample 9: + time = 360000 + flags = 1 + data = length 366, hash 8102B7EC + sample 10: + time = 400000 + flags = 1 + data = length 366, hash 55BF59AC + sample 11: + time = 440000 + flags = 1 + data = length 494, hash CBC2E09F + sample 12: + time = 480000 + flags = 1 + data = length 519, hash 9DAF56E9 + sample 13: + time = 520000 + flags = 1 + data = length 598, hash 8169EE2 + sample 14: + time = 560000 + flags = 1 + data = length 435, hash 28C21246 + sample 15: + time = 600000 + flags = 1 + data = length 365, hash FF14716D + sample 16: + time = 640000 + flags = 1 + data = length 392, hash 4CC96B29 + sample 17: + time = 680000 + flags = 1 + data = length 373, hash D7AC6D4E + sample 18: + time = 720000 + flags = 1 + data = length 392, hash 99F2511F +tracksEnded = true diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java index 148e04ca77a..bbe7985568e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; import com.google.android.exoplayer2.extractor.ogg.OggExtractor; import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; +import com.google.android.exoplayer2.extractor.ts.Ac4Extractor; import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; import com.google.android.exoplayer2.extractor.ts.PsExtractor; import com.google.android.exoplayer2.extractor.ts.TsExtractor; @@ -62,7 +63,8 @@ public void testCreateExtractors_returnExpectedClasses() { OggExtractor.class, PsExtractor.class, WavExtractor.class, - AmrExtractor.class + AmrExtractor.class, + Ac4Extractor.class }; assertThat(listCreatedExtractorClasses).containsNoDuplicates(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac4ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac4ExtractorTest.java new file mode 100644 index 00000000000..782956dee7b --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac4ExtractorTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 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.testutil.ExtractorAsserts; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit test for {@link Ac4Extractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class Ac4ExtractorTest { + + @Test + public void testAc4Sample() throws Exception { + ExtractorAsserts.assertBehavior(Ac4Extractor::new, "ts/sample.ac4"); + } +} + From 51632f027d09a1c1d456c184cf3b2ea79fc1330f Mon Sep 17 00:00:00 2001 From: BAI Yanning <30304609+ybai001@users.noreply.github.com> Date: Fri, 25 Jan 2019 16:49:43 +0800 Subject: [PATCH 4/6] Update Ac4Reader.java --- .../com/google/android/exoplayer2/extractor/ts/Ac4Reader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java index 2486ab54002..a1e5e51e9d9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java @@ -115,7 +115,7 @@ public void consume(ParsableByteArray data) { if (skipToNextSync(data)) { state = STATE_READING_HEADER; headerScratchBytes.data[0] = (byte)0xAC; - headerScratchBytes.data[1] = headerScratchBytes.data[1] = (byte)(hasCRC ? 0x41 : 0x40); + headerScratchBytes.data[1] = (byte)(hasCRC ? 0x41 : 0x40); bytesRead = 2; } break; From 8f32c29cc6691536255d6d93bfd6537f1e245a5f Mon Sep 17 00:00:00 2001 From: ybai001 Date: Thu, 31 Jan 2019 11:31:19 +0800 Subject: [PATCH 5/6] Update code based on code review result * remove field mimeType in Ac4Util.java * change constant CHANNEL_COUNT_2 to private in Ac4Util.java --- .../com/google/android/exoplayer2/audio/Ac4Util.java | 11 ++--------- .../android/exoplayer2/extractor/ts/Ac4Reader.java | 5 +++-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac4Util.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac4Util.java index 9b845a14d43..fc4c8efb855 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac4Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac4Util.java @@ -33,10 +33,6 @@ public final class Ac4Util { */ public static final class SyncFrameInfo { - /** - * The sample mime type of the bitstream is {@link MimeTypes#AUDIO_AC4}. - */ - public final String mimeType; /** * The bitstream version. */ @@ -59,13 +55,11 @@ public static final class SyncFrameInfo { public final int sampleCount; private SyncFrameInfo( - String mimeType, int bitstreamVersion, int channelCount, int sampleRate, int frameSize, int sampleCount) { - this.mimeType = mimeType; this.bitstreamVersion = bitstreamVersion; this.channelCount = channelCount; this.sampleRate = sampleRate; @@ -78,7 +72,7 @@ private SyncFrameInfo( * The channel count of AC-4 stream. */ // TODO: Parse AC-4 stream channel count. - public static final int CHANNEL_COUNT_2 = 2; + private static final int CHANNEL_COUNT_2 = 2; /** * The header size for AC-4 parser. Only needs to be as big as we need to read, not the full * header size. @@ -207,8 +201,7 @@ public static SyncFrameInfo parseAc4SyncframeInfo(ParsableBitArray data) { break; } } - return new SyncFrameInfo( - MimeTypes.AUDIO_AC4, bitstreamVersion, CHANNEL_COUNT_2, sampleRate, frameSize, sampleCount); + return new SyncFrameInfo(bitstreamVersion, CHANNEL_COUNT_2, sampleRate, frameSize, sampleCount); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java index a1e5e51e9d9..fd1a2bae442 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac4Reader.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import java.lang.annotation.Documented; @@ -198,8 +199,8 @@ private void parseHeader() { SyncFrameInfo frameInfo = Ac4Util.parseAc4SyncframeInfo(headerScratchBits); if (format == null || frameInfo.channelCount != format.channelCount || frameInfo.sampleRate != format.sampleRate - || frameInfo.mimeType != format.sampleMimeType) { - format = Format.createAudioSampleFormat(trackFormatId, frameInfo.mimeType, null, + || !MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) { + format = Format.createAudioSampleFormat(trackFormatId, MimeTypes.AUDIO_AC4, null, Format.NO_VALUE, Format.NO_VALUE, frameInfo.channelCount, frameInfo.sampleRate, null, null, 0, language); output.format(format); From 55ed5cfac665a563f6369056b387453223e7b223 Mon Sep 17 00:00:00 2001 From: ybai001 Date: Fri, 1 Feb 2019 09:03:40 +0800 Subject: [PATCH 6/6] Set ac4-is sync only if SDK_INT <= 28 --- .../android/exoplayer2/audio/MediaCodecAudioRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index f9749450c3c..381a2ad682b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -787,7 +787,7 @@ protected MediaFormat getMediaFormat( mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate); } } - if (MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) { + if (Util.SDK_INT <= 28 && MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) { // Some devices handle AC-4 raw frame by default. // Need to notify the codec to handle AC-4 sync frame. mediaFormat.setInteger("ac4-is-sync", 1);