blob: 03b6fcb394fcbd4497b3c3104c9e0d4e56a6e861 [file] [log] [blame]
/*
* 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 androidx.test.ext.junit.runners.AndroidJUnit4;
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;
/** Unit test for {@link AmrExtractor}. */
@RunWith(AndroidJUnit4.class)
public final class AmrExtractorTest {
private static final Random RANDOM = new Random(1234);
@Test
public void sniff_nonAmrSignature_returnFalse() throws IOException {
AmrExtractor amrExtractor = setupAmrExtractorWithOutput();
FakeExtractorInput input = fakeExtractorInputWithData(Util.getUtf8Bytes("0#!AMR\n123"));
boolean result = amrExtractor.sniff(input);
assertThat(result).isFalse();
}
@Test
public void read_nonAmrSignature_throwParserException() throws IOException {
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 read_amrNb_returnParserException_forInvalidFrameType() throws IOException {
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 read_amrWb_returnParserException_forInvalidFrameType() throws IOException {
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 read_amrNb_returnEndOfInput_ifInputEncountersEoF() throws IOException {
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 read_amrWb_returnEndOfInput_ifInputEncountersEoF() throws IOException {
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 read_amrNb_returnParserException_forInvalidFrameHeader() throws IOException {
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 read_amrWb_returnParserException_forInvalidFrameHeader() throws IOException {
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 extractingNarrowBandSamples() throws Exception {
ExtractorAsserts.assertBehavior(
createAmrExtractorFactory(/* withSeeking= */ false), "amr/sample_nb.amr");
}
@Test
public void extractingWideBandSamples() throws Exception {
ExtractorAsserts.assertBehavior(
createAmrExtractorFactory(/* withSeeking= */ false), "amr/sample_wb.amr");
}
@Test
public void extractingNarrowBandSamples_withSeeking() throws Exception {
ExtractorAsserts.assertBehavior(
createAmrExtractorFactory(/* withSeeking= */ true), "amr/sample_nb_cbr.amr");
}
@Test
public void extractingWideBandSamples_withSeeking() throws Exception {
ExtractorAsserts.assertBehavior(
createAmrExtractorFactory(/* withSeeking= */ true), "amr/sample_wb_cbr.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;
}
private static AmrExtractor setupAmrExtractorWithOutput() {
AmrExtractor amrExtractor = new AmrExtractor();
FakeExtractorOutput output = new FakeExtractorOutput();
amrExtractor.init(output);
return amrExtractor;
}
private static FakeExtractorInput fakeExtractorInputWithData(byte[] data) {
return new FakeExtractorInput.Builder().setData(data).build();
}
private static ExtractorAsserts.ExtractorFactory createAmrExtractorFactory(boolean withSeeking) {
return () -> {
if (!withSeeking) {
return new AmrExtractor();
} else {
return new AmrExtractor(AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING);
}
};
}
}