Commit fe324017 by hoangtc Committed by Oliver Woodman

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
parent 59f01ec3
...@@ -35,6 +35,8 @@ ...@@ -35,6 +35,8 @@
* IMA: Allow setting the ad media load timeout * IMA: Allow setting the ad media load timeout
([#3691](https://github.com/google/ExoPlayer/issues/3691)). ([#3691](https://github.com/google/ExoPlayer/issues/3691)).
* Audio: * Audio:
* Support extracting data from AMR container formats, including both narrow
and wide band ([#2527](https://github.com/google/ExoPlayer/issues/2527)).
* FLAC: * FLAC:
* Sniff FLAC files correctly if they have ID3 headers * Sniff FLAC files correctly if they have ID3 headers
([#4055](https://github.com/google/ExoPlayer/issues/4055)). ([#4055](https://github.com/google/ExoPlayer/issues/4055)).
......
...@@ -34,6 +34,7 @@ dependencies { ...@@ -34,6 +34,7 @@ dependencies {
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'com.android.support:support-annotations:' + supportLibraryVersion
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
androidTestImplementation project(modulePrefix + 'testutils') androidTestImplementation project(modulePrefix + 'testutils')
testImplementation project(modulePrefix + 'testutils-robolectric')
} }
ext { ext {
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor; 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.flv.FlvExtractor;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
...@@ -35,18 +36,19 @@ import java.lang.reflect.Constructor; ...@@ -35,18 +36,19 @@ import java.lang.reflect.Constructor;
* An {@link ExtractorsFactory} that provides an array of extractors for the following formats: * An {@link ExtractorsFactory} that provides an array of extractors for the following formats:
* *
* <ul> * <ul>
* <li>MP4, including M4A ({@link Mp4Extractor})</li> * <li>MP4, including M4A ({@link Mp4Extractor})
* <li>fMP4 ({@link FragmentedMp4Extractor})</li> * <li>fMP4 ({@link FragmentedMp4Extractor})
* <li>Matroska and WebM ({@link MatroskaExtractor})</li> * <li>Matroska and WebM ({@link MatroskaExtractor})
* <li>Ogg Vorbis/FLAC ({@link OggExtractor}</li> * <li>Ogg Vorbis/FLAC ({@link OggExtractor}
* <li>MP3 ({@link Mp3Extractor})</li> * <li>MP3 ({@link Mp3Extractor})
* <li>AAC ({@link AdtsExtractor})</li> * <li>AAC ({@link AdtsExtractor})
* <li>MPEG TS ({@link TsExtractor})</li> * <li>MPEG TS ({@link TsExtractor})
* <li>MPEG PS ({@link PsExtractor})</li> * <li>MPEG PS ({@link PsExtractor})
* <li>FLV ({@link FlvExtractor})</li> * <li>FLV ({@link FlvExtractor})
* <li>WAV ({@link WavExtractor})</li> * <li>WAV ({@link WavExtractor})
* <li>AC3 ({@link Ac3Extractor})</li> * <li>AC3 ({@link Ac3Extractor})
* <li>FLAC (only available if the FLAC extension is built and included)</li> * <li>AMR ({@link AmrExtractor})
* <li>FLAC (only available if the FLAC extension is built and included)
* </ul> * </ul>
*/ */
public final class DefaultExtractorsFactory implements ExtractorsFactory { public final class DefaultExtractorsFactory implements ExtractorsFactory {
...@@ -159,7 +161,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { ...@@ -159,7 +161,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
@Override @Override
public synchronized Extractor[] createExtractors() { 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[0] = new MatroskaExtractor(matroskaFlags);
extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags); extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags);
extractors[2] = new Mp4Extractor(mp4Flags); extractors[2] = new Mp4Extractor(mp4Flags);
...@@ -171,9 +173,10 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { ...@@ -171,9 +173,10 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
extractors[8] = new OggExtractor(); extractors[8] = new OggExtractor();
extractors[9] = new PsExtractor(); extractors[9] = new PsExtractor();
extractors[10] = new WavExtractor(); extractors[10] = new WavExtractor();
extractors[11] = new AmrExtractor();
if (FLAC_EXTRACTOR_CONSTRUCTOR != null) { if (FLAC_EXTRACTOR_CONSTRUCTOR != null) {
try { try {
extractors[11] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance(); extractors[12] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance();
} catch (Exception e) { } catch (Exception e) {
// Should never happen. // Should never happen.
throw new IllegalStateException("Unexpected error creating FLAC extractor", e); throw new IllegalStateException("Unexpected error creating FLAC extractor", e);
......
/*
* 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.
*
* <p>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);
}
}
/*
* 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<Class> 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);
}
}
/*
* 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();
}
};
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment