From 460f57614230837eab74b1f6bcd200bb3e3da15f Mon Sep 17 00:00:00 2001 From: ybai001 Date: Mon, 24 Dec 2018 09:38:52 +0800 Subject: [PATCH] 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 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 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 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"); + } +} +