From fe32401792bcf8ec7b674262946549c3136b8b60 Mon Sep 17 00:00:00 2001 From: hoangtc Date: Thu, 26 Apr 2018 09:29:01 -0700 Subject: [PATCH] Supports extracting from AMR container format. Supports extracting data from AMR container format for both narrow and wide band formats. Also added AmrExtractor as one of the default extractor to be used in DefaultExtractorsFactory. GitHub: #2527. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=194407507 --- RELEASENOTES.md | 2 + extensions/flac/build.gradle | 1 + .../extractor/DefaultExtractorsFactory.java | 31 +- .../extractor/amr/AmrExtractor.java | 299 ++++++ .../core/src/test/assets/amr/sample_nb.amr | Bin 0 -> 2840 bytes .../src/test/assets/amr/sample_nb.amr.0.dump | 902 ++++++++++++++++++ .../core/src/test/assets/amr/sample_wb.amr | Bin 0 -> 4065 bytes .../src/test/assets/amr/sample_wb.amr.0.dump | 706 ++++++++++++++ .../DefaultExtractorsFactoryTest.java | 71 ++ .../extractor/amr/AmrExtractorTest.java | 246 +++++ 10 files changed, 2244 insertions(+), 14 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java create mode 100644 library/core/src/test/assets/amr/sample_nb.amr create mode 100644 library/core/src/test/assets/amr/sample_nb.amr.0.dump create mode 100644 library/core/src/test/assets/amr/sample_wb.amr create mode 100644 library/core/src/test/assets/amr/sample_wb.amr.0.dump create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorTest.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 435658a9543..47180ebfc80 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -35,6 +35,8 @@ * IMA: Allow setting the ad media load timeout ([#3691](https://github.com/google/ExoPlayer/issues/3691)). * Audio: + * Support extracting data from AMR container formats, including both narrow + and wide band ([#2527](https://github.com/google/ExoPlayer/issues/2527)). * FLAC: * Sniff FLAC files correctly if they have ID3 headers ([#4055](https://github.com/google/ExoPlayer/issues/4055)). diff --git a/extensions/flac/build.gradle b/extensions/flac/build.gradle index 9bf8d394352..609953130ba 100644 --- a/extensions/flac/build.gradle +++ b/extensions/flac/build.gradle @@ -34,6 +34,7 @@ dependencies { implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation project(modulePrefix + 'library-core') androidTestImplementation project(modulePrefix + 'testutils') + testImplementation project(modulePrefix + 'testutils-robolectric') } ext { 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 b85ecba3a4e..425f2b77cd8 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 @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor; +import com.google.android.exoplayer2.extractor.amr.AmrExtractor; import com.google.android.exoplayer2.extractor.flv.FlvExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; @@ -35,18 +36,19 @@ * An {@link ExtractorsFactory} that provides an array of extractors for the following formats: * * */ public final class DefaultExtractorsFactory implements ExtractorsFactory { @@ -159,7 +161,7 @@ public synchronized DefaultExtractorsFactory setTsExtractorFlags( @Override public synchronized Extractor[] createExtractors() { - Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 11 : 12]; + Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 12 : 13]; extractors[0] = new MatroskaExtractor(matroskaFlags); extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags); extractors[2] = new Mp4Extractor(mp4Flags); @@ -171,9 +173,10 @@ public synchronized Extractor[] createExtractors() { extractors[8] = new OggExtractor(); extractors[9] = new PsExtractor(); extractors[10] = new WavExtractor(); + extractors[11] = new AmrExtractor(); if (FLAC_EXTRACTOR_CONSTRUCTOR != null) { try { - extractors[11] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance(); + extractors[12] = 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/amr/AmrExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java new file mode 100644 index 00000000000..b58e979c262 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java @@ -0,0 +1,299 @@ +/* + * 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.amr; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.ParserException; +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.TrackOutput; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; +import java.io.EOFException; +import java.io.IOException; +import java.util.Arrays; + +/** + * Extracts data from the AMR containers format (either AMR or AMR-WB). This follows RFC-4867, + * section 5. + * + *

This extractor only supports single-channel AMR container formats. + */ +public final class AmrExtractor implements Extractor { + + /** Factory for {@link AmrExtractor} instances. */ + public static final ExtractorsFactory FACTORY = + new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new AmrExtractor()}; + } + }; + + /** + * The frame size in bytes, including header (1 byte), for each of the 16 frame types for AMR + * narrow band. + */ + private static final int[] frameSizeBytesByTypeNb = { + 13, + 14, + 16, + 18, + 20, + 21, + 27, + 32, + 6, // AMR SID + 7, // GSM-EFR SID + 6, // TDMA-EFR SID + 6, // PDC-EFR SID + 1, // Future use + 1, // Future use + 1, // Future use + 1 // No data + }; + + /** + * The frame size in bytes, including header (1 byte), for each of the 16 frame types for AMR wide + * band. + */ + private static final int[] frameSizeBytesByTypeWb = { + 18, + 24, + 33, + 37, + 41, + 47, + 51, + 59, + 61, + 6, // AMR-WB SID + 1, // Future use + 1, // Future use + 1, // Future use + 1, // Future use + 1, // speech lost + 1 // No data + }; + + private static final byte[] amrSignatureNb = Util.getUtf8Bytes("#!AMR\n"); + private static final byte[] amrSignatureWb = Util.getUtf8Bytes("#!AMR-WB\n"); + + /** Theoretical maximum frame size for a AMR frame. */ + private static final int MAX_FRAME_SIZE_BYTES = frameSizeBytesByTypeWb[8]; + + private static final int SAMPLE_RATE_WB = 16_000; + private static final int SAMPLE_RATE_NB = 8_000; + private static final int SAMPLE_TIME_PER_FRAME_US = 20_000; + + private final byte[] scratch; + + private boolean isWideBand; + private long currentSampleTimeUs; + private int currentSampleTotalBytes; + private int currentSampleBytesRemaining; + + private TrackOutput trackOutput; + private boolean hasOutputFormat; + + public AmrExtractor() { + scratch = new byte[1]; + } + + // Extractor implementation. + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + return readAmrHeader(input); + } + + @Override + public void init(ExtractorOutput output) { + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + trackOutput = output.track(/* id= */ 0, C.TRACK_TYPE_AUDIO); + output.endTracks(); + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + if (input.getPosition() == 0) { + if (!readAmrHeader(input)) { + throw new ParserException("Could not find AMR header."); + } + } + maybeOutputFormat(); + return readSample(input); + } + + @Override + public void seek(long position, long timeUs) { + currentSampleTimeUs = 0; + currentSampleTotalBytes = 0; + currentSampleBytesRemaining = 0; + } + + @Override + public void release() { + // Do nothing + } + + /* package */ static int frameSizeBytesByTypeNb(int frameType) { + return frameSizeBytesByTypeNb[frameType]; + } + + /* package */ static int frameSizeBytesByTypeWb(int frameType) { + return frameSizeBytesByTypeWb[frameType]; + } + + /* package */ static byte[] amrSignatureNb() { + return Arrays.copyOf(amrSignatureNb, amrSignatureNb.length); + } + + /* package */ static byte[] amrSignatureWb() { + return Arrays.copyOf(amrSignatureWb, amrSignatureWb.length); + } + + // Internal methods. + + /** + * Peeks the AMR header from the beginning of the input, and consumes it if it exists. + * + * @param input The {@link ExtractorInput} from which data should be peeked/read. + * @return Whether the AMR header has been read. + */ + private boolean readAmrHeader(ExtractorInput input) throws IOException, InterruptedException { + if (peekAmrSignature(input, amrSignatureNb)) { + isWideBand = false; + input.skipFully(amrSignatureNb.length); + return true; + } else if (peekAmrSignature(input, amrSignatureWb)) { + isWideBand = true; + input.skipFully(amrSignatureWb.length); + return true; + } + return false; + } + + /** Peeks from the beginning of the input to see if the given AMR signature exists. */ + private boolean peekAmrSignature(ExtractorInput input, byte[] amrSignature) + throws IOException, InterruptedException { + input.resetPeekPosition(); + byte[] header = new byte[amrSignature.length]; + input.peekFully(header, 0, amrSignature.length); + return Arrays.equals(header, amrSignature); + } + + private void maybeOutputFormat() { + if (!hasOutputFormat) { + hasOutputFormat = true; + String mimeType = isWideBand ? MimeTypes.AUDIO_AMR_WB : MimeTypes.AUDIO_AMR_NB; + int sampleRate = isWideBand ? SAMPLE_RATE_WB : SAMPLE_RATE_NB; + trackOutput.format( + Format.createAudioSampleFormat( + /* id= */ null, + mimeType, + /* codecs= */ null, + /* bitrate= */ Format.NO_VALUE, + MAX_FRAME_SIZE_BYTES, + /* channelCount= */ 1, + sampleRate, + /* pcmEncoding= */ Format.NO_VALUE, + /* initializationData= */ null, + /* drmInitData= */ null, + /* selectionFlags= */ 0, + /* language= */ null)); + } + } + + private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException { + if (currentSampleBytesRemaining == 0) { + try { + currentSampleTotalBytes = readNextSampleSize(extractorInput); + } catch (EOFException e) { + return RESULT_END_OF_INPUT; + } + currentSampleBytesRemaining = currentSampleTotalBytes; + } + + int bytesAppended = + trackOutput.sampleData( + extractorInput, currentSampleBytesRemaining, /* allowEndOfInput= */ true); + if (bytesAppended == C.RESULT_END_OF_INPUT) { + return RESULT_END_OF_INPUT; + } + currentSampleBytesRemaining -= bytesAppended; + if (currentSampleBytesRemaining > 0) { + return RESULT_CONTINUE; + } + + trackOutput.sampleMetadata( + currentSampleTimeUs, + C.BUFFER_FLAG_KEY_FRAME, + currentSampleTotalBytes, + /* offset= */ 0, + /* encryptionData= */ null); + currentSampleTimeUs += SAMPLE_TIME_PER_FRAME_US; + return RESULT_CONTINUE; + } + + private int readNextSampleSize(ExtractorInput extractorInput) + throws IOException, InterruptedException { + extractorInput.resetPeekPosition(); + extractorInput.peekFully(scratch, /* offset= */ 0, /* length= */ 1); + + byte frameHeader = scratch[0]; + if ((frameHeader & 0x83) > 0) { + // The padding bits are at bit-1 positions in the following pattern: 1000 0011 + // Padding bits must be 0. + throw new ParserException("Invalid padding bits for frame header " + frameHeader); + } + + int frameType = (frameHeader >> 3) & 0x0f; + return getFrameSizeInBytes(frameType); + } + + private int getFrameSizeInBytes(int frameType) throws ParserException { + if (!isValidFrameType(frameType)) { + throw new ParserException( + "Illegal AMR " + (isWideBand ? "WB" : "NB") + " frame type " + frameType); + } + + return isWideBand ? frameSizeBytesByTypeWb[frameType] : frameSizeBytesByTypeNb[frameType]; + } + + private boolean isValidFrameType(int frameType) { + return frameType >= 0 + && frameType <= 15 + && (isWideBandValidFrameType(frameType) || isNarrowBandValidFrameType(frameType)); + } + + private boolean isWideBandValidFrameType(int frameType) { + // For wide band, type 10-13 are for future use. + return isWideBand && (frameType < 10 || frameType > 13); + } + + private boolean isNarrowBandValidFrameType(int frameType) { + // For narrow band, type 12-14 are for future use. + return !isWideBand && (frameType < 12 || frameType > 14); + } +} diff --git a/library/core/src/test/assets/amr/sample_nb.amr b/library/core/src/test/assets/amr/sample_nb.amr new file mode 100644 index 0000000000000000000000000000000000000000..2e21cc843ccadc6ce066687371b95df0e568a07d GIT binary patch literal 2840 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 literal 0 HcmV?d00001 diff --git a/library/core/src/test/assets/amr/sample_nb.amr.0.dump b/library/core/src/test/assets/amr/sample_nb.amr.0.dump new file mode 100644 index 00000000000..e0dec9c62c8 --- /dev/null +++ b/library/core/src/test/assets/amr/sample_nb.amr.0.dump @@ -0,0 +1,902 @@ +seekMap: + isSeekable = false + duration = UNSET TIME + getPosition(0) = [[timeUs=0, position=0]] +numberOfTracks = 1 +track 0: + format: + bitrate = -1 + id = null + containerMimeType = null + sampleMimeType = audio/3gpp + maxInputSize = 61 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = 0 + pixelWidthHeightRatio = 1.0 + channelCount = 1 + sampleRate = 8000 + pcmEncoding = -1 + encoderDelay = 0 + encoderPadding = 0 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + total output bytes = 2834 + sample count = 218 + sample 0: + time = 0 + flags = 1 + data = length 13, hash 371B046C + sample 1: + time = 20000 + flags = 1 + data = length 13, hash CE30BF5B + sample 2: + time = 40000 + flags = 1 + data = length 13, hash 19A59975 + sample 3: + time = 60000 + flags = 1 + data = length 13, hash 4879773C + sample 4: + time = 80000 + flags = 1 + data = length 13, hash E8F83019 + sample 5: + time = 100000 + flags = 1 + data = length 13, hash D265CDC9 + sample 6: + time = 120000 + flags = 1 + data = length 13, hash 91653DAA + sample 7: + time = 140000 + flags = 1 + data = length 13, hash C79456F6 + sample 8: + time = 160000 + flags = 1 + data = length 13, hash CDDC4422 + sample 9: + time = 180000 + flags = 1 + data = length 13, hash D9ED3AF1 + sample 10: + time = 200000 + flags = 1 + data = length 13, hash BAB75A33 + sample 11: + time = 220000 + flags = 1 + data = length 13, hash 2221B4FF + sample 12: + time = 240000 + flags = 1 + data = length 13, hash 96400A0B + sample 13: + time = 260000 + flags = 1 + data = length 13, hash 582E6FB + sample 14: + time = 280000 + flags = 1 + data = length 13, hash C4E878E5 + sample 15: + time = 300000 + flags = 1 + data = length 13, hash C849A1BD + sample 16: + time = 320000 + flags = 1 + data = length 13, hash CFA8A9ED + sample 17: + time = 340000 + flags = 1 + data = length 13, hash 70CA4907 + sample 18: + time = 360000 + flags = 1 + data = length 13, hash B47D4454 + sample 19: + time = 380000 + flags = 1 + data = length 13, hash 282998C1 + sample 20: + time = 400000 + flags = 1 + data = length 13, hash 3F3F7A65 + sample 21: + time = 420000 + flags = 1 + data = length 13, hash CC2EAB58 + sample 22: + time = 440000 + flags = 1 + data = length 13, hash 279EF712 + sample 23: + time = 460000 + flags = 1 + data = length 13, hash AA2F4B29 + sample 24: + time = 480000 + flags = 1 + data = length 13, hash F6F658C4 + sample 25: + time = 500000 + flags = 1 + data = length 13, hash D7DEBD17 + sample 26: + time = 520000 + flags = 1 + data = length 13, hash 6DAB9A17 + sample 27: + time = 540000 + flags = 1 + data = length 13, hash 6ECE1571 + sample 28: + time = 560000 + flags = 1 + data = length 13, hash B3D0507F + sample 29: + time = 580000 + flags = 1 + data = length 13, hash 21E356B9 + sample 30: + time = 600000 + flags = 1 + data = length 13, hash 410EA12 + sample 31: + time = 620000 + flags = 1 + data = length 13, hash 533895A8 + sample 32: + time = 640000 + flags = 1 + data = length 13, hash C61B3E5A + sample 33: + time = 660000 + flags = 1 + data = length 13, hash 982170E6 + sample 34: + time = 680000 + flags = 1 + data = length 13, hash 7A0468C5 + sample 35: + time = 700000 + flags = 1 + data = length 13, hash 9C85EAA7 + sample 36: + time = 720000 + flags = 1 + data = length 13, hash B6B341B6 + sample 37: + time = 740000 + flags = 1 + data = length 13, hash 6937532E + sample 38: + time = 760000 + flags = 1 + data = length 13, hash 8CF2A3A0 + sample 39: + time = 780000 + flags = 1 + data = length 13, hash D2682AC6 + sample 40: + time = 800000 + flags = 1 + data = length 13, hash BBC5710F + sample 41: + time = 820000 + flags = 1 + data = length 13, hash 59080B6C + sample 42: + time = 840000 + flags = 1 + data = length 13, hash E4118291 + sample 43: + time = 860000 + flags = 1 + data = length 13, hash A1E5B296 + sample 44: + time = 880000 + flags = 1 + data = length 13, hash D7B8F95B + sample 45: + time = 900000 + flags = 1 + data = length 13, hash CC839BE1 + sample 46: + time = 920000 + flags = 1 + data = length 13, hash D459DFCE + sample 47: + time = 940000 + flags = 1 + data = length 13, hash D6AD19EC + sample 48: + time = 960000 + flags = 1 + data = length 13, hash D05E373D + sample 49: + time = 980000 + flags = 1 + data = length 13, hash 6A4460C7 + sample 50: + time = 1000000 + flags = 1 + data = length 13, hash C9A0D93F + sample 51: + time = 1020000 + flags = 1 + data = length 13, hash 3FA819E7 + sample 52: + time = 1040000 + flags = 1 + data = length 13, hash 1D3CBDFC + sample 53: + time = 1060000 + flags = 1 + data = length 13, hash 8BBBB403 + sample 54: + time = 1080000 + flags = 1 + data = length 13, hash 21B4A0F9 + sample 55: + time = 1100000 + flags = 1 + data = length 13, hash C0F921D1 + sample 56: + time = 1120000 + flags = 1 + data = length 13, hash 5D812AAB + sample 57: + time = 1140000 + flags = 1 + data = length 13, hash 50C9F3F8 + sample 58: + time = 1160000 + flags = 1 + data = length 13, hash 5C2BB5D1 + sample 59: + time = 1180000 + flags = 1 + data = length 13, hash 6BF9BEA5 + sample 60: + time = 1200000 + flags = 1 + data = length 13, hash 2738C1E6 + sample 61: + time = 1220000 + flags = 1 + data = length 13, hash 5FC288A6 + sample 62: + time = 1240000 + flags = 1 + data = length 13, hash 7E8E442A + sample 63: + time = 1260000 + flags = 1 + data = length 13, hash AEAA2BBA + sample 64: + time = 1280000 + flags = 1 + data = length 13, hash 4E2ACD2F + sample 65: + time = 1300000 + flags = 1 + data = length 13, hash D6C90ACF + sample 66: + time = 1320000 + flags = 1 + data = length 13, hash 6FD8A944 + sample 67: + time = 1340000 + flags = 1 + data = length 13, hash A835BBF9 + sample 68: + time = 1360000 + flags = 1 + data = length 13, hash F7713830 + sample 69: + time = 1380000 + flags = 1 + data = length 13, hash 3AA966E5 + sample 70: + time = 1400000 + flags = 1 + data = length 13, hash F939E829 + sample 71: + time = 1420000 + flags = 1 + data = length 13, hash 7676DE49 + sample 72: + time = 1440000 + flags = 1 + data = length 13, hash 93BB890A + sample 73: + time = 1460000 + flags = 1 + data = length 13, hash B57DBEC8 + sample 74: + time = 1480000 + flags = 1 + data = length 13, hash 66B0A5B6 + sample 75: + time = 1500000 + flags = 1 + data = length 13, hash D733E0D + sample 76: + time = 1520000 + flags = 1 + data = length 13, hash 80941726 + sample 77: + time = 1540000 + flags = 1 + data = length 13, hash 556ED633 + sample 78: + time = 1560000 + flags = 1 + data = length 13, hash C5EDF4E1 + sample 79: + time = 1580000 + flags = 1 + data = length 13, hash 6B287445 + sample 80: + time = 1600000 + flags = 1 + data = length 13, hash DC97C4A7 + sample 81: + time = 1620000 + flags = 1 + data = length 13, hash DA8CBDF4 + sample 82: + time = 1640000 + flags = 1 + data = length 13, hash 6F60FF77 + sample 83: + time = 1660000 + flags = 1 + data = length 13, hash 3EB22B96 + sample 84: + time = 1680000 + flags = 1 + data = length 13, hash B3C31AF5 + sample 85: + time = 1700000 + flags = 1 + data = length 13, hash 1854AA92 + sample 86: + time = 1720000 + flags = 1 + data = length 13, hash 6488264B + sample 87: + time = 1740000 + flags = 1 + data = length 13, hash 4CC8C5C1 + sample 88: + time = 1760000 + flags = 1 + data = length 13, hash 19CC7523 + sample 89: + time = 1780000 + flags = 1 + data = length 13, hash 9BE7B928 + sample 90: + time = 1800000 + flags = 1 + data = length 13, hash 47EC7CFD + sample 91: + time = 1820000 + flags = 1 + data = length 13, hash EC940120 + sample 92: + time = 1840000 + flags = 1 + data = length 13, hash 73BDA6D0 + sample 93: + time = 1860000 + flags = 1 + data = length 13, hash FACB3314 + sample 94: + time = 1880000 + flags = 1 + data = length 13, hash EC61D13B + sample 95: + time = 1900000 + flags = 1 + data = length 13, hash B28C7B6C + sample 96: + time = 1920000 + flags = 1 + data = length 13, hash B1A4CECD + sample 97: + time = 1940000 + flags = 1 + data = length 13, hash 56D41BA6 + sample 98: + time = 1960000 + flags = 1 + data = length 13, hash 90499F4 + sample 99: + time = 1980000 + flags = 1 + data = length 13, hash 65D9A9D3 + sample 100: + time = 2000000 + flags = 1 + data = length 13, hash D9004CC + sample 101: + time = 2020000 + flags = 1 + data = length 13, hash 4139C6ED + sample 102: + time = 2040000 + flags = 1 + data = length 13, hash C4F8097C + sample 103: + time = 2060000 + flags = 1 + data = length 13, hash 94D424FA + sample 104: + time = 2080000 + flags = 1 + data = length 13, hash C2C6F5FD + sample 105: + time = 2100000 + flags = 1 + data = length 13, hash 15719008 + sample 106: + time = 2120000 + flags = 1 + data = length 13, hash 4F64F524 + sample 107: + time = 2140000 + flags = 1 + data = length 13, hash F9E01C1E + sample 108: + time = 2160000 + flags = 1 + data = length 13, hash 74C4EE74 + sample 109: + time = 2180000 + flags = 1 + data = length 13, hash 7EE7553D + sample 110: + time = 2200000 + flags = 1 + data = length 13, hash 62DE6539 + sample 111: + time = 2220000 + flags = 1 + data = length 13, hash 7F5EC222 + sample 112: + time = 2240000 + flags = 1 + data = length 13, hash 644067F + sample 113: + time = 2260000 + flags = 1 + data = length 13, hash CDF6C9DC + sample 114: + time = 2280000 + flags = 1 + data = length 13, hash 8B5DBC80 + sample 115: + time = 2300000 + flags = 1 + data = length 13, hash AD4BBA03 + sample 116: + time = 2320000 + flags = 1 + data = length 13, hash 7A76340 + sample 117: + time = 2340000 + flags = 1 + data = length 13, hash 3610F5B0 + sample 118: + time = 2360000 + flags = 1 + data = length 13, hash 430BC60B + sample 119: + time = 2380000 + flags = 1 + data = length 13, hash 99CF1CA6 + sample 120: + time = 2400000 + flags = 1 + data = length 13, hash 1331C70B + sample 121: + time = 2420000 + flags = 1 + data = length 13, hash BD76E69D + sample 122: + time = 2440000 + flags = 1 + data = length 13, hash 5DA652AC + sample 123: + time = 2460000 + flags = 1 + data = length 13, hash 3B7BF6CE + sample 124: + time = 2480000 + flags = 1 + data = length 13, hash ABBFD143 + sample 125: + time = 2500000 + flags = 1 + data = length 13, hash E9447166 + sample 126: + time = 2520000 + flags = 1 + data = length 13, hash EC40068C + sample 127: + time = 2540000 + flags = 1 + data = length 13, hash A2869400 + sample 128: + time = 2560000 + flags = 1 + data = length 13, hash C7E0746B + sample 129: + time = 2580000 + flags = 1 + data = length 13, hash 60601BB1 + sample 130: + time = 2600000 + flags = 1 + data = length 13, hash 975AAE9B + sample 131: + time = 2620000 + flags = 1 + data = length 13, hash 8BBC0EB2 + sample 132: + time = 2640000 + flags = 1 + data = length 13, hash 57FB39E5 + sample 133: + time = 2660000 + flags = 1 + data = length 13, hash 4CDCEEDB + sample 134: + time = 2680000 + flags = 1 + data = length 13, hash EA16E256 + sample 135: + time = 2700000 + flags = 1 + data = length 13, hash 287E7D9E + sample 136: + time = 2720000 + flags = 1 + data = length 13, hash 55AB8FB9 + sample 137: + time = 2740000 + flags = 1 + data = length 13, hash 129890EF + sample 138: + time = 2760000 + flags = 1 + data = length 13, hash 90834F57 + sample 139: + time = 2780000 + flags = 1 + data = length 13, hash 5B3228E0 + sample 140: + time = 2800000 + flags = 1 + data = length 13, hash DD19E175 + sample 141: + time = 2820000 + flags = 1 + data = length 13, hash EE7EA342 + sample 142: + time = 2840000 + flags = 1 + data = length 13, hash DB3AF473 + sample 143: + time = 2860000 + flags = 1 + data = length 13, hash 25AEC43F + sample 144: + time = 2880000 + flags = 1 + data = length 13, hash EE9BF97F + sample 145: + time = 2900000 + flags = 1 + data = length 13, hash FFFBE047 + sample 146: + time = 2920000 + flags = 1 + data = length 13, hash BEACFCB0 + sample 147: + time = 2940000 + flags = 1 + data = length 13, hash AEB5096C + sample 148: + time = 2960000 + flags = 1 + data = length 13, hash B0D381B + sample 149: + time = 2980000 + flags = 1 + data = length 13, hash 3D9D5122 + sample 150: + time = 3000000 + flags = 1 + data = length 13, hash 6C1DDB95 + sample 151: + time = 3020000 + flags = 1 + data = length 13, hash ADACADCF + sample 152: + time = 3040000 + flags = 1 + data = length 13, hash 159E321E + sample 153: + time = 3060000 + flags = 1 + data = length 13, hash B1466264 + sample 154: + time = 3080000 + flags = 1 + data = length 13, hash 4DDF7223 + sample 155: + time = 3100000 + flags = 1 + data = length 13, hash C9BDB82A + sample 156: + time = 3120000 + flags = 1 + data = length 13, hash A49B2D9D + sample 157: + time = 3140000 + flags = 1 + data = length 13, hash D645E7E5 + sample 158: + time = 3160000 + flags = 1 + data = length 13, hash 1C4232DC + sample 159: + time = 3180000 + flags = 1 + data = length 13, hash 83078219 + sample 160: + time = 3200000 + flags = 1 + data = length 13, hash D6D8B072 + sample 161: + time = 3220000 + flags = 1 + data = length 13, hash 975DB40 + sample 162: + time = 3240000 + flags = 1 + data = length 13, hash A15FDD05 + sample 163: + time = 3260000 + flags = 1 + data = length 13, hash 4B839E41 + sample 164: + time = 3280000 + flags = 1 + data = length 13, hash 7418F499 + sample 165: + time = 3300000 + flags = 1 + data = length 13, hash 7A4945E4 + sample 166: + time = 3320000 + flags = 1 + data = length 13, hash 6249558C + sample 167: + time = 3340000 + flags = 1 + data = length 13, hash BD4C5BE3 + sample 168: + time = 3360000 + flags = 1 + data = length 13, hash BAB30F1D + sample 169: + time = 3380000 + flags = 1 + data = length 13, hash 1E1C7012 + sample 170: + time = 3400000 + flags = 1 + data = length 13, hash 9A3F8A89 + sample 171: + time = 3420000 + flags = 1 + data = length 13, hash 20BE6D7B + sample 172: + time = 3440000 + flags = 1 + data = length 13, hash CAA0591D + sample 173: + time = 3460000 + flags = 1 + data = length 13, hash 6D554D17 + sample 174: + time = 3480000 + flags = 1 + data = length 13, hash D97C3B31 + sample 175: + time = 3500000 + flags = 1 + data = length 13, hash 75BC5C3 + sample 176: + time = 3520000 + flags = 1 + data = length 13, hash 7BA1784B + sample 177: + time = 3540000 + flags = 1 + data = length 13, hash 1D175D92 + sample 178: + time = 3560000 + flags = 1 + data = length 13, hash ADCA60FD + sample 179: + time = 3580000 + flags = 1 + data = length 13, hash 37018693 + sample 180: + time = 3600000 + flags = 1 + data = length 13, hash 4553606F + sample 181: + time = 3620000 + flags = 1 + data = length 13, hash CF434565 + sample 182: + time = 3640000 + flags = 1 + data = length 13, hash D264D757 + sample 183: + time = 3660000 + flags = 1 + data = length 13, hash 4FB493EF + sample 184: + time = 3680000 + flags = 1 + data = length 13, hash 919F53A + sample 185: + time = 3700000 + flags = 1 + data = length 13, hash C22B009B + sample 186: + time = 3720000 + flags = 1 + data = length 13, hash 5981470 + sample 187: + time = 3740000 + flags = 1 + data = length 13, hash A5D3937C + sample 188: + time = 3760000 + flags = 1 + data = length 13, hash A2504429 + sample 189: + time = 3780000 + flags = 1 + data = length 13, hash AD1B70BE + sample 190: + time = 3800000 + flags = 1 + data = length 13, hash 2E39ED5E + sample 191: + time = 3820000 + flags = 1 + data = length 13, hash 13A8BE8E + sample 192: + time = 3840000 + flags = 1 + data = length 13, hash 1ACD740B + sample 193: + time = 3860000 + flags = 1 + data = length 13, hash 80F38B3 + sample 194: + time = 3880000 + flags = 1 + data = length 13, hash DA9DA79F + sample 195: + time = 3900000 + flags = 1 + data = length 13, hash 21B95B7E + sample 196: + time = 3920000 + flags = 1 + data = length 13, hash CD22497B + sample 197: + time = 3940000 + flags = 1 + data = length 13, hash 718BB35D + sample 198: + time = 3960000 + flags = 1 + data = length 13, hash 69ABA6AD + sample 199: + time = 3980000 + flags = 1 + data = length 13, hash BAE19549 + sample 200: + time = 4000000 + flags = 1 + data = length 13, hash 2A792FB3 + sample 201: + time = 4020000 + flags = 1 + data = length 13, hash 71FCD8 + sample 202: + time = 4040000 + flags = 1 + data = length 13, hash 44D2B5B3 + sample 203: + time = 4060000 + flags = 1 + data = length 13, hash 1E87B11B + sample 204: + time = 4080000 + flags = 1 + data = length 13, hash 78CD2C11 + sample 205: + time = 4100000 + flags = 1 + data = length 13, hash 9F198DF0 + sample 206: + time = 4120000 + flags = 1 + data = length 13, hash B291F16A + sample 207: + time = 4140000 + flags = 1 + data = length 13, hash CF820EE0 + sample 208: + time = 4160000 + flags = 1 + data = length 13, hash 4E24F683 + sample 209: + time = 4180000 + flags = 1 + data = length 13, hash 52BCD68F + sample 210: + time = 4200000 + flags = 1 + data = length 13, hash 42588CB0 + sample 211: + time = 4220000 + flags = 1 + data = length 13, hash EBBFECA2 + sample 212: + time = 4240000 + flags = 1 + data = length 13, hash C11050CF + sample 213: + time = 4260000 + flags = 1 + data = length 13, hash 6F738603 + sample 214: + time = 4280000 + flags = 1 + data = length 13, hash DAD06E5 + sample 215: + time = 4300000 + flags = 1 + data = length 13, hash 5B036C64 + sample 216: + time = 4320000 + flags = 1 + data = length 13, hash A58DC12E + sample 217: + time = 4340000 + flags = 1 + data = length 13, hash AC59BA7C +tracksEnded = true diff --git a/library/core/src/test/assets/amr/sample_wb.amr b/library/core/src/test/assets/amr/sample_wb.amr new file mode 100644 index 0000000000000000000000000000000000000000..14b85b553c5ea842da30eb875b2d6581bed14152 GIT binary patch literal 4065 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 literal 0 HcmV?d00001 diff --git a/library/core/src/test/assets/amr/sample_wb.amr.0.dump b/library/core/src/test/assets/amr/sample_wb.amr.0.dump new file mode 100644 index 00000000000..1b3b8bd0ddf --- /dev/null +++ b/library/core/src/test/assets/amr/sample_wb.amr.0.dump @@ -0,0 +1,706 @@ +seekMap: + isSeekable = false + duration = UNSET TIME + getPosition(0) = [[timeUs=0, position=0]] +numberOfTracks = 1 +track 0: + format: + bitrate = -1 + id = null + containerMimeType = null + sampleMimeType = audio/amr-wb + maxInputSize = 61 + width = -1 + height = -1 + frameRate = -1.0 + rotationDegrees = 0 + pixelWidthHeightRatio = 1.0 + channelCount = 1 + sampleRate = 16000 + pcmEncoding = -1 + encoderDelay = 0 + encoderPadding = 0 + subsampleOffsetUs = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + total output bytes = 4056 + sample count = 169 + sample 0: + time = 0 + flags = 1 + data = length 24, hash C3025798 + sample 1: + time = 20000 + flags = 1 + data = length 24, hash 39CABAE9 + sample 2: + time = 40000 + flags = 1 + data = length 24, hash 2752F470 + sample 3: + time = 60000 + flags = 1 + data = length 24, hash 394F76F6 + sample 4: + time = 80000 + flags = 1 + data = length 24, hash FF9EEF + sample 5: + time = 100000 + flags = 1 + data = length 24, hash 54ECB1B4 + sample 6: + time = 120000 + flags = 1 + data = length 24, hash 6D7A3A5F + sample 7: + time = 140000 + flags = 1 + data = length 24, hash 684CD144 + sample 8: + time = 160000 + flags = 1 + data = length 24, hash 87B7D176 + sample 9: + time = 180000 + flags = 1 + data = length 24, hash 4C02F9A5 + sample 10: + time = 200000 + flags = 1 + data = length 24, hash B4154108 + sample 11: + time = 220000 + flags = 1 + data = length 24, hash 4448F477 + sample 12: + time = 240000 + flags = 1 + data = length 24, hash 755A4939 + sample 13: + time = 260000 + flags = 1 + data = length 24, hash 8C8BC6C3 + sample 14: + time = 280000 + flags = 1 + data = length 24, hash BC37F63F + sample 15: + time = 300000 + flags = 1 + data = length 24, hash 3352C43C + sample 16: + time = 320000 + flags = 1 + data = length 24, hash 7998E1F2 + sample 17: + time = 340000 + flags = 1 + data = length 24, hash A8ECBEFC + sample 18: + time = 360000 + flags = 1 + data = length 24, hash 944AC118 + sample 19: + time = 380000 + flags = 1 + data = length 24, hash FD2C8E1F + sample 20: + time = 400000 + flags = 1 + data = length 24, hash B3D867AF + sample 21: + time = 420000 + flags = 1 + data = length 24, hash 3DC6E592 + sample 22: + time = 440000 + flags = 1 + data = length 24, hash 32B276CD + sample 23: + time = 460000 + flags = 1 + data = length 24, hash 5488AEF3 + sample 24: + time = 480000 + flags = 1 + data = length 24, hash 7A4D516 + sample 25: + time = 500000 + flags = 1 + data = length 24, hash 570AE83F + sample 26: + time = 520000 + flags = 1 + data = length 24, hash E5CB3477 + sample 27: + time = 540000 + flags = 1 + data = length 24, hash E04C00E4 + sample 28: + time = 560000 + flags = 1 + data = length 24, hash 21B7C97 + sample 29: + time = 580000 + flags = 1 + data = length 24, hash 1633F470 + sample 30: + time = 600000 + flags = 1 + data = length 24, hash 28D65CA6 + sample 31: + time = 620000 + flags = 1 + data = length 24, hash CC6A675C + sample 32: + time = 640000 + flags = 1 + data = length 24, hash 4C91080A + sample 33: + time = 660000 + flags = 1 + data = length 24, hash F6482FB5 + sample 34: + time = 680000 + flags = 1 + data = length 24, hash 2C76F48C + sample 35: + time = 700000 + flags = 1 + data = length 24, hash 6E3B0D72 + sample 36: + time = 720000 + flags = 1 + data = length 24, hash 799AA003 + sample 37: + time = 740000 + flags = 1 + data = length 24, hash DFC0BA81 + sample 38: + time = 760000 + flags = 1 + data = length 24, hash CBDF3826 + sample 39: + time = 780000 + flags = 1 + data = length 24, hash 16862B75 + sample 40: + time = 800000 + flags = 1 + data = length 24, hash 865A828E + sample 41: + time = 820000 + flags = 1 + data = length 24, hash 336BBDC9 + sample 42: + time = 840000 + flags = 1 + data = length 24, hash 6CFC6C34 + sample 43: + time = 860000 + flags = 1 + data = length 24, hash 32C8CD46 + sample 44: + time = 880000 + flags = 1 + data = length 24, hash 9FE11C4C + sample 45: + time = 900000 + flags = 1 + data = length 24, hash AA5A12B7 + sample 46: + time = 920000 + flags = 1 + data = length 24, hash AA0F4A4D + sample 47: + time = 940000 + flags = 1 + data = length 24, hash 34415484 + sample 48: + time = 960000 + flags = 1 + data = length 24, hash 5018928E + sample 49: + time = 980000 + flags = 1 + data = length 24, hash 4A04D162 + sample 50: + time = 1000000 + flags = 1 + data = length 24, hash 4C70F9F0 + sample 51: + time = 1020000 + flags = 1 + data = length 24, hash 99EF3168 + sample 52: + time = 1040000 + flags = 1 + data = length 24, hash C600DAF + sample 53: + time = 1060000 + flags = 1 + data = length 24, hash FDBB192E + sample 54: + time = 1080000 + flags = 1 + data = length 24, hash 99096A48 + sample 55: + time = 1100000 + flags = 1 + data = length 24, hash D793F88B + sample 56: + time = 1120000 + flags = 1 + data = length 24, hash EEB921BD + sample 57: + time = 1140000 + flags = 1 + data = length 24, hash 8B941A4C + sample 58: + time = 1160000 + flags = 1 + data = length 24, hash ED5F5FEE + sample 59: + time = 1180000 + flags = 1 + data = length 24, hash A588E0BB + sample 60: + time = 1200000 + flags = 1 + data = length 24, hash 588CBC01 + sample 61: + time = 1220000 + flags = 1 + data = length 24, hash DE22266C + sample 62: + time = 1240000 + flags = 1 + data = length 24, hash 921B6E5C + sample 63: + time = 1260000 + flags = 1 + data = length 24, hash EC11F041 + sample 64: + time = 1280000 + flags = 1 + data = length 24, hash 5BA9E0A3 + sample 65: + time = 1300000 + flags = 1 + data = length 24, hash DB6D52F3 + sample 66: + time = 1320000 + flags = 1 + data = length 24, hash 8EEBE525 + sample 67: + time = 1340000 + flags = 1 + data = length 24, hash 47A742AE + sample 68: + time = 1360000 + flags = 1 + data = length 24, hash E93F1E03 + sample 69: + time = 1380000 + flags = 1 + data = length 24, hash 3251F57C + sample 70: + time = 1400000 + flags = 1 + data = length 24, hash 3EDBBBDD + sample 71: + time = 1420000 + flags = 1 + data = length 24, hash 2E98465A + sample 72: + time = 1440000 + flags = 1 + data = length 24, hash A09EA52E + sample 73: + time = 1460000 + flags = 1 + data = length 24, hash A2A86FA6 + sample 74: + time = 1480000 + flags = 1 + data = length 24, hash 71DCD51C + sample 75: + time = 1500000 + flags = 1 + data = length 24, hash 2B02DEE1 + sample 76: + time = 1520000 + flags = 1 + data = length 24, hash 7A725192 + sample 77: + time = 1540000 + flags = 1 + data = length 24, hash 929AD483 + sample 78: + time = 1560000 + flags = 1 + data = length 24, hash 68440BF5 + sample 79: + time = 1580000 + flags = 1 + data = length 24, hash 5BD41AD6 + sample 80: + time = 1600000 + flags = 1 + data = length 24, hash 91A381 + sample 81: + time = 1620000 + flags = 1 + data = length 24, hash 8010C408 + sample 82: + time = 1640000 + flags = 1 + data = length 24, hash 482274BE + sample 83: + time = 1660000 + flags = 1 + data = length 24, hash D7DB8BCC + sample 84: + time = 1680000 + flags = 1 + data = length 24, hash 680BD9DD + sample 85: + time = 1700000 + flags = 1 + data = length 24, hash E313577C + sample 86: + time = 1720000 + flags = 1 + data = length 24, hash 9C10B0CD + sample 87: + time = 1740000 + flags = 1 + data = length 24, hash 2D90AC02 + sample 88: + time = 1760000 + flags = 1 + data = length 24, hash 64E8C245 + sample 89: + time = 1780000 + flags = 1 + data = length 24, hash 3954AC1B + sample 90: + time = 1800000 + flags = 1 + data = length 24, hash ACB8999F + sample 91: + time = 1820000 + flags = 1 + data = length 24, hash 43AE3957 + sample 92: + time = 1840000 + flags = 1 + data = length 24, hash 3C664DB7 + sample 93: + time = 1860000 + flags = 1 + data = length 24, hash 9354B576 + sample 94: + time = 1880000 + flags = 1 + data = length 24, hash B5B9C14E + sample 95: + time = 1900000 + flags = 1 + data = length 24, hash 7DA9C98F + sample 96: + time = 1920000 + flags = 1 + data = length 24, hash EFEE54C6 + sample 97: + time = 1940000 + flags = 1 + data = length 24, hash 79DC8CBD + sample 98: + time = 1960000 + flags = 1 + data = length 24, hash A71A475C + sample 99: + time = 1980000 + flags = 1 + data = length 24, hash CA1CBB94 + sample 100: + time = 2000000 + flags = 1 + data = length 24, hash 91922226 + sample 101: + time = 2020000 + flags = 1 + data = length 24, hash C90278BC + sample 102: + time = 2040000 + flags = 1 + data = length 24, hash BD51986F + sample 103: + time = 2060000 + flags = 1 + data = length 24, hash 90AEF368 + sample 104: + time = 2080000 + flags = 1 + data = length 24, hash 1D83C955 + sample 105: + time = 2100000 + flags = 1 + data = length 24, hash 8FA9A915 + sample 106: + time = 2120000 + flags = 1 + data = length 24, hash C6C753E0 + sample 107: + time = 2140000 + flags = 1 + data = length 24, hash 85FA27A7 + sample 108: + time = 2160000 + flags = 1 + data = length 24, hash A0277324 + sample 109: + time = 2180000 + flags = 1 + data = length 24, hash B7696535 + sample 110: + time = 2200000 + flags = 1 + data = length 24, hash D69D668C + sample 111: + time = 2220000 + flags = 1 + data = length 24, hash 34C057CD + sample 112: + time = 2240000 + flags = 1 + data = length 24, hash 4EC5E974 + sample 113: + time = 2260000 + flags = 1 + data = length 24, hash 1C1CD40D + sample 114: + time = 2280000 + flags = 1 + data = length 24, hash 76CC54BC + sample 115: + time = 2300000 + flags = 1 + data = length 24, hash D497ACF5 + sample 116: + time = 2320000 + flags = 1 + data = length 24, hash A1386080 + sample 117: + time = 2340000 + flags = 1 + data = length 24, hash 7ED36954 + sample 118: + time = 2360000 + flags = 1 + data = length 24, hash C11A3BF9 + sample 119: + time = 2380000 + flags = 1 + data = length 24, hash 8FB69488 + sample 120: + time = 2400000 + flags = 1 + data = length 24, hash C6225F59 + sample 121: + time = 2420000 + flags = 1 + data = length 24, hash 122AB6D2 + sample 122: + time = 2440000 + flags = 1 + data = length 24, hash 1E195E7D + sample 123: + time = 2460000 + flags = 1 + data = length 24, hash BD3DF418 + sample 124: + time = 2480000 + flags = 1 + data = length 24, hash D8AE4A5 + sample 125: + time = 2500000 + flags = 1 + data = length 24, hash 977BD182 + sample 126: + time = 2520000 + flags = 1 + data = length 24, hash F361F060 + sample 127: + time = 2540000 + flags = 1 + data = length 24, hash 11EC8CD0 + sample 128: + time = 2560000 + flags = 1 + data = length 24, hash 3798F3D2 + sample 129: + time = 2580000 + flags = 1 + data = length 24, hash B2C2517C + sample 130: + time = 2600000 + flags = 1 + data = length 24, hash FBE0D0D8 + sample 131: + time = 2620000 + flags = 1 + data = length 24, hash 7033172F + sample 132: + time = 2640000 + flags = 1 + data = length 24, hash BE760029 + sample 133: + time = 2660000 + flags = 1 + data = length 24, hash 590AF28C + sample 134: + time = 2680000 + flags = 1 + data = length 24, hash AD28C48F + sample 135: + time = 2700000 + flags = 1 + data = length 24, hash 640AA61B + sample 136: + time = 2720000 + flags = 1 + data = length 24, hash ABE659B + sample 137: + time = 2740000 + flags = 1 + data = length 24, hash ED2691D2 + sample 138: + time = 2760000 + flags = 1 + data = length 24, hash D998C80E + sample 139: + time = 2780000 + flags = 1 + data = length 24, hash 8DC0DF5C + sample 140: + time = 2800000 + flags = 1 + data = length 24, hash 7692247B + sample 141: + time = 2820000 + flags = 1 + data = length 24, hash C1D1CCB9 + sample 142: + time = 2840000 + flags = 1 + data = length 24, hash 362CE78E + sample 143: + time = 2860000 + flags = 1 + data = length 24, hash 54FA84A + sample 144: + time = 2880000 + flags = 1 + data = length 24, hash 29E88C84 + sample 145: + time = 2900000 + flags = 1 + data = length 24, hash 1CD848AC + sample 146: + time = 2920000 + flags = 1 + data = length 24, hash 5C3D4A79 + sample 147: + time = 2940000 + flags = 1 + data = length 24, hash 1AA8E604 + sample 148: + time = 2960000 + flags = 1 + data = length 24, hash 186A4316 + sample 149: + time = 2980000 + flags = 1 + data = length 24, hash 61ACE481 + sample 150: + time = 3000000 + flags = 1 + data = length 24, hash D0C42780 + sample 151: + time = 3020000 + flags = 1 + data = length 24, hash FAD51BA1 + sample 152: + time = 3040000 + flags = 1 + data = length 24, hash F1A9AC71 + sample 153: + time = 3060000 + flags = 1 + data = length 24, hash 24425449 + sample 154: + time = 3080000 + flags = 1 + data = length 24, hash 37AAC3E6 + sample 155: + time = 3100000 + flags = 1 + data = length 24, hash 91F68CB4 + sample 156: + time = 3120000 + flags = 1 + data = length 24, hash F8C92820 + sample 157: + time = 3140000 + flags = 1 + data = length 24, hash ECD39C3E + sample 158: + time = 3160000 + flags = 1 + data = length 24, hash B27D8F78 + sample 159: + time = 3180000 + flags = 1 + data = length 24, hash C9EB3DFB + sample 160: + time = 3200000 + flags = 1 + data = length 24, hash 88DC54A2 + sample 161: + time = 3220000 + flags = 1 + data = length 24, hash 7FC4C5BE + sample 162: + time = 3240000 + flags = 1 + data = length 24, hash E4F684EF + sample 163: + time = 3260000 + flags = 1 + data = length 24, hash 55C08B56 + sample 164: + time = 3280000 + flags = 1 + data = length 24, hash E5A0F006 + sample 165: + time = 3300000 + flags = 1 + data = length 24, hash DE3F3AA7 + sample 166: + time = 3320000 + flags = 1 + data = length 24, hash 3F28AE7F + sample 167: + time = 3340000 + flags = 1 + data = length 24, hash 3949CAFF + sample 168: + time = 3360000 + flags = 1 + data = length 24, hash 772665A0 +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 new file mode 100644 index 00000000000..148e04ca77a --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 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; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.android.exoplayer2.extractor.amr.AmrExtractor; +import com.google.android.exoplayer2.extractor.flv.FlvExtractor; +import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; +import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; +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.AdtsExtractor; +import com.google.android.exoplayer2.extractor.ts.PsExtractor; +import com.google.android.exoplayer2.extractor.ts.TsExtractor; +import com.google.android.exoplayer2.extractor.wav.WavExtractor; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit test for {@link DefaultExtractorsFactory}. */ +@RunWith(RobolectricTestRunner.class) +public final class DefaultExtractorsFactoryTest { + + @Test + public void testCreateExtractors_returnExpectedClasses() { + DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); + + Extractor[] extractors = defaultExtractorsFactory.createExtractors(); + List listCreatedExtractorClasses = new ArrayList<>(); + for (Extractor extractor : extractors) { + listCreatedExtractorClasses.add(extractor.getClass()); + } + + Class[] expectedExtractorClassses = + new Class[] { + MatroskaExtractor.class, + FragmentedMp4Extractor.class, + Mp4Extractor.class, + Mp3Extractor.class, + AdtsExtractor.class, + Ac3Extractor.class, + TsExtractor.class, + FlvExtractor.class, + OggExtractor.class, + PsExtractor.class, + WavExtractor.class, + AmrExtractor.class + }; + + assertThat(listCreatedExtractorClasses).containsNoDuplicates(); + assertThat(listCreatedExtractorClasses).containsExactlyElementsIn(expectedExtractorClassses); + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorTest.java new file mode 100644 index 00000000000..b46612e7c3d --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/amr/AmrExtractorTest.java @@ -0,0 +1,246 @@ +/* + * 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.amr; + +import static com.google.android.exoplayer2.extractor.amr.AmrExtractor.amrSignatureNb; +import static com.google.android.exoplayer2.extractor.amr.AmrExtractor.amrSignatureWb; +import static com.google.android.exoplayer2.extractor.amr.AmrExtractor.frameSizeBytesByTypeNb; +import static com.google.android.exoplayer2.extractor.amr.AmrExtractor.frameSizeBytesByTypeWb; +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.fail; + +import android.support.annotation.NonNull; +import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.extractor.Extractor; +import com.google.android.exoplayer2.extractor.PositionHolder; +import com.google.android.exoplayer2.testutil.ExtractorAsserts; +import com.google.android.exoplayer2.testutil.FakeExtractorInput; +import com.google.android.exoplayer2.testutil.FakeExtractorOutput; +import com.google.android.exoplayer2.util.Util; +import java.io.IOException; +import java.util.Random; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit test for {@link AmrExtractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class AmrExtractorTest { + + private static final Random RANDOM = new Random(1234); + + @Test + public void testSniff_nonAmrSignature_returnFalse() throws IOException, InterruptedException { + AmrExtractor amrExtractor = setupAmrExtractorWithOutput(); + FakeExtractorInput input = fakeExtractorInputWithData(Util.getUtf8Bytes("0#!AMR\n123")); + + boolean result = amrExtractor.sniff(input); + assertThat(result).isFalse(); + } + + @Test + public void testRead_nonAmrSignature_throwParserException() + throws IOException, InterruptedException { + AmrExtractor amrExtractor = setupAmrExtractorWithOutput(); + FakeExtractorInput input = fakeExtractorInputWithData(Util.getUtf8Bytes("0#!AMR-WB\n")); + + try { + amrExtractor.read(input, new PositionHolder()); + fail(); + } catch (ParserException e) { + // expected + } + } + + @Test + public void testRead_amrNb_returnParserException_forInvalidFrameType() + throws IOException, InterruptedException { + AmrExtractor amrExtractor = setupAmrExtractorWithOutput(); + + // Frame type 12-14 for narrow band is reserved for future usage. + byte[] amrFrame = newNarrowBandAmrFrameWithType(12); + byte[] data = joinData(amrSignatureNb(), amrFrame); + FakeExtractorInput input = fakeExtractorInputWithData(data); + + try { + amrExtractor.read(input, new PositionHolder()); + fail(); + } catch (ParserException e) { + // expected + } + } + + @Test + public void testRead_amrWb_returnParserException_forInvalidFrameType() + throws IOException, InterruptedException { + AmrExtractor amrExtractor = setupAmrExtractorWithOutput(); + + // Frame type 10-13 for wide band is reserved for future usage. + byte[] amrFrame = newWideBandAmrFrameWithType(13); + byte[] data = joinData(amrSignatureWb(), amrFrame); + FakeExtractorInput input = fakeExtractorInputWithData(data); + + try { + amrExtractor.read(input, new PositionHolder()); + fail(); + } catch (ParserException e) { + // expected + } + } + + @Test + public void testRead_amrNb_returnEndOfInput_ifInputEncountersEoF() + throws IOException, InterruptedException { + AmrExtractor amrExtractor = setupAmrExtractorWithOutput(); + + byte[] amrFrame = newNarrowBandAmrFrameWithType(3); + byte[] data = joinData(amrSignatureNb(), amrFrame); + FakeExtractorInput input = fakeExtractorInputWithData(data); + + // Read 1st frame, which will put the input at EoF. + amrExtractor.read(input, new PositionHolder()); + + int result = amrExtractor.read(input, new PositionHolder()); + assertThat(result).isEqualTo(Extractor.RESULT_END_OF_INPUT); + } + + @Test + public void testRead_amrWb_returnEndOfInput_ifInputEncountersEoF() + throws IOException, InterruptedException { + AmrExtractor amrExtractor = setupAmrExtractorWithOutput(); + + byte[] amrFrame = newWideBandAmrFrameWithType(5); + byte[] data = joinData(amrSignatureWb(), amrFrame); + FakeExtractorInput input = fakeExtractorInputWithData(data); + + // Read 1st frame, which will put the input at EoF. + amrExtractor.read(input, new PositionHolder()); + + int result = amrExtractor.read(input, new PositionHolder()); + assertThat(result).isEqualTo(Extractor.RESULT_END_OF_INPUT); + } + + @Test + public void testRead_amrNb_returnParserException_forInvalidFrameHeader() + throws IOException, InterruptedException { + AmrExtractor amrExtractor = setupAmrExtractorWithOutput(); + + byte[] invalidHeaderFrame = newNarrowBandAmrFrameWithType(4); + + // The padding bits are at bit-1 positions in the following pattern: 1000 0011 + // Padding bits must be 0. + invalidHeaderFrame[0] = (byte) (invalidHeaderFrame[0] | 0b01111101); + + byte[] data = joinData(amrSignatureNb(), invalidHeaderFrame); + FakeExtractorInput input = fakeExtractorInputWithData(data); + + try { + amrExtractor.read(input, new PositionHolder()); + fail(); + } catch (ParserException e) { + // expected + } + } + + @Test + public void testRead_amrWb_returnParserException_forInvalidFrameHeader() + throws IOException, InterruptedException { + AmrExtractor amrExtractor = setupAmrExtractorWithOutput(); + + byte[] invalidHeaderFrame = newWideBandAmrFrameWithType(6); + + // The padding bits are at bit-1 positions in the following pattern: 1000 0011 + // Padding bits must be 0. + invalidHeaderFrame[0] = (byte) (invalidHeaderFrame[0] | 0b01111110); + + byte[] data = joinData(amrSignatureWb(), invalidHeaderFrame); + FakeExtractorInput input = fakeExtractorInputWithData(data); + + try { + amrExtractor.read(input, new PositionHolder()); + fail(); + } catch (ParserException e) { + // expected + } + } + + @Test + public void testExtractingNarrowBandSamples() throws Exception { + ExtractorAsserts.assertBehavior(createAmrExtractorFactory(), "amr/sample_nb.amr"); + } + + @Test + public void testExtractingWideBandSamples() throws Exception { + ExtractorAsserts.assertBehavior(createAmrExtractorFactory(), "amr/sample_wb.amr"); + } + + private byte[] newWideBandAmrFrameWithType(int frameType) { + byte frameHeader = (byte) ((frameType << 3) & (0b01111100)); + int frameContentInBytes = frameSizeBytesByTypeWb(frameType) - 1; + + return joinData(new byte[] {frameHeader}, randomBytesArrayWithLength(frameContentInBytes)); + } + + private byte[] newNarrowBandAmrFrameWithType(int frameType) { + byte frameHeader = (byte) ((frameType << 3) & (0b01111100)); + int frameContentInBytes = frameSizeBytesByTypeNb(frameType) - 1; + + return joinData(new byte[] {frameHeader}, randomBytesArrayWithLength(frameContentInBytes)); + } + + private static byte[] randomBytesArrayWithLength(int length) { + byte[] result = new byte[length]; + RANDOM.nextBytes(result); + return result; + } + + private static byte[] joinData(byte[]... byteArrays) { + int totalLength = 0; + for (byte[] byteArray : byteArrays) { + totalLength += byteArray.length; + } + byte[] result = new byte[totalLength]; + int offset = 0; + for (byte[] byteArray : byteArrays) { + System.arraycopy(byteArray, /* srcPos= */ 0, result, offset, byteArray.length); + offset += byteArray.length; + } + return result; + } + + @NonNull + private static AmrExtractor setupAmrExtractorWithOutput() { + AmrExtractor amrExtractor = new AmrExtractor(); + FakeExtractorOutput output = new FakeExtractorOutput(); + amrExtractor.init(output); + return amrExtractor; + } + + @NonNull + private static FakeExtractorInput fakeExtractorInputWithData(byte[] data) { + return new FakeExtractorInput.Builder().setData(data).build(); + } + + @NonNull + private static ExtractorAsserts.ExtractorFactory createAmrExtractorFactory() { + return new ExtractorAsserts.ExtractorFactory() { + @Override + public Extractor create() { + return new AmrExtractor(); + } + }; + } +}