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 14db0b2ae1..e2f8887b5c 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 @@ import java.lang.reflect.Constructor; *
  • 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 1d45854647..b1b598d6ef 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 class FragmentedMp4Extractor implements Extractor { durationUs = C.TIME_UNSET; pendingSeekTimeUs = C.TIME_UNSET; segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET; + isAc4HeaderAdded = false; + ac4SampleHeaderSize = 0; enterReadingAtomHeaderState(); } @@ -1217,6 +1221,7 @@ public class FragmentedMp4Extractor implements Extractor { sampleSize += sampleBytesWritten; parserState = STATE_READING_SAMPLE_CONTINUE; sampleCurrentNalBytesRemaining = 0; + isAc4HeaderAdded = false; } TrackFragment fragment = currentTrackBundle.fragment; @@ -1277,17 +1282,20 @@ public class FragmentedMp4Extractor implements Extractor { } } } 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 7c9fe2a187..49f5fac7a4 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 final class Mp4Extractor implements Extractor, SeekMap { nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); sampleTrackIndex = C.INDEX_UNSET; + isAc4HeaderAdded = false; + ac4SampleHeaderSize = 0; } @Override @@ -489,6 +493,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { if (sampleTrackIndex == C.INDEX_UNSET) { return RESULT_END_OF_INPUT; } + isAc4HeaderAdded = false; } Mp4Track track = tracks[sampleTrackIndex]; TrackOutput trackOutput = track.trackOutput; @@ -538,18 +543,21 @@ public final class Mp4Extractor implements Extractor, SeekMap { } } } 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 f5d50cde92..2486ab5400 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 final class Ac4Reader implements ElementaryStreamReader { 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 0000000000..721f53cdd7 Binary files /dev/null and b/library/core/src/test/assets/ts/sample.ac4 differ 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 0000000000..03ae07707a --- /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 148e04ca77..bbe7985568 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.FragmentedMp4Extractor; 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 final class DefaultExtractorsFactoryTest { 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 0000000000..782956dee7 --- /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"); + } +} +