blob: 12b53d4b588e5c7ff3b563469393139a095b62e7 [file] [log] [blame]
/*
* Copyright (C) 2022 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 android.mediav2.cts;
import static android.mediav2.common.cts.CodecTestBase.SupportClass.CODEC_OPTIONAL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import android.media.AudioFormat;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.mediav2.common.cts.CodecDecoderTestBase;
import android.mediav2.common.cts.EncoderConfigParams;
import android.mediav2.common.cts.EncoderTestBase;
import android.mediav2.common.cts.OutputManager;
import androidx.test.filters.LargeTest;
import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.CddTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* The test verifies encoders present in media codec list in bytebuffer mode. The test feeds raw
* input data to the component and receives compressed bitstream from the component.
* <p>
* At the end of encoding process, the test enforces following checks :-
* <ul>
* <li>For lossless audio codecs, this file is decoded and the decoded output is expected to
* be bit-exact with encoder input.</li>
* <li>For lossy audio codecs, the output sample count (after stripping priming and padding
* samples) should be close enough to input sample count else it would result in noticeable
* av sync errors.</li>
* </ul>
*/
@RunWith(Parameterized.class)
public class AudioEncoderTest extends EncoderTestBase {
public AudioEncoderTest(String encoder, String mediaType, EncoderConfigParams encCfgParams,
@SuppressWarnings("unused") String testLabel, String allTestParams) {
super(encoder, mediaType, new EncoderConfigParams[]{encCfgParams}, allTestParams);
}
private static EncoderConfigParams getAudioEncoderCfgParams(String mediaType, int qualityPreset,
int sampleRate, int channelCount, int pcmEncoding) {
EncoderConfigParams.Builder foreman = new EncoderConfigParams.Builder(mediaType)
.setSampleRate(sampleRate)
.setChannelCount(channelCount)
.setPcmEncoding(pcmEncoding);
if (mediaType.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
foreman = foreman.setCompressionLevel(qualityPreset);
} else {
foreman = foreman.setBitRate(qualityPreset);
}
return foreman.build();
}
private static List<Object[]> flattenParams(List<Object[]> params) {
List<Object[]> argsList = new ArrayList<>();
for (Object[] param : params) {
String mediaType = (String) param[0];
int[] qualityPresets = (int[]) param[1];
int[] sampleRates = (int[]) param[2];
int[] channelCounts = (int[]) param[3];
int pcmEncoding = (int) param[4];
for (int qualityPreset : qualityPresets) {
for (int sampleRate : sampleRates) {
for (int channelCount : channelCounts) {
Object[] testArgs = new Object[3];
testArgs[0] = param[0];
testArgs[1] = getAudioEncoderCfgParams(mediaType, qualityPreset, sampleRate,
channelCount, pcmEncoding);
testArgs[2] = String.format("%d%s_%dkHz_%dch_%s",
mediaType.equals(MediaFormat.MIMETYPE_AUDIO_FLAC) ? qualityPreset :
qualityPreset / 1000,
mediaType.equals(MediaFormat.MIMETYPE_AUDIO_FLAC) ? "clevel" :
"kbps", sampleRate / 1000, channelCount,
audioEncodingToString(pcmEncoding));
argsList.add(testArgs);
}
}
}
}
return argsList;
}
@Parameterized.Parameters(name = "{index}({0}_{1}_{3})")
public static Collection<Object[]> input() {
final boolean isEncoder = true;
final boolean needAudio = true;
final boolean needVideo = false;
List<Object[]> defArgsList = new ArrayList<>(Arrays.asList(new Object[][]{
// mediaType, arrays of bit-rates, sample rates, channel counts, pcm encoding
{MediaFormat.MIMETYPE_AUDIO_AAC, new int[]{64000, 128000}, new int[]{8000, 12000,
16000, 22050, 24000, 32000, 44100, 48000}, new int[]{1, 2},
AudioFormat.ENCODING_PCM_16BIT},
{MediaFormat.MIMETYPE_AUDIO_OPUS, new int[]{64000, 128000}, new int[]{8000, 12000,
16000, 24000, 48000}, new int[]{1, 2},
AudioFormat.ENCODING_PCM_16BIT},
{MediaFormat.MIMETYPE_AUDIO_AMR_NB, new int[]{4750, 5150, 5900, 6700, 7400, 7950,
10200, 12200}, new int[]{8000}, new int[]{1},
AudioFormat.ENCODING_PCM_16BIT},
{MediaFormat.MIMETYPE_AUDIO_AMR_WB, new int[]{6600, 8850, 12650, 14250, 15850,
18250, 19850, 23050, 23850}, new int[]{16000}, new int[]{1},
AudioFormat.ENCODING_PCM_16BIT},
/* TODO(169310292) */
{MediaFormat.MIMETYPE_AUDIO_FLAC, new int[]{/* 0, 1, 2, */ 3, 4, 5, 6, 7, 8},
new int[]{8000, 16000, 32000, 48000, 96000, 192000}, new int[]{1, 2},
AudioFormat.ENCODING_PCM_16BIT},
{MediaFormat.MIMETYPE_AUDIO_FLAC, new int[]{/* 0, 1, 2, */ 3, 4, 5, 6, 7, 8},
new int[]{8000, 16000, 32000, 48000, 96000, 192000}, new int[]{1, 2},
AudioFormat.ENCODING_PCM_FLOAT},
}));
List<Object[]> argsList = flattenParams(defArgsList);
return prepareParamList(argsList, isEncoder, needAudio, needVideo, false);
}
void encodeAndValidate() throws IOException, InterruptedException {
// encode
setUpSource(mActiveRawRes.mFileName);
mSaveToMem = true;
mOutputBuff = new OutputManager();
mCodec = MediaCodec.createByCodecName(mCodecName);
configureCodec(mActiveEncCfg.getFormat(), false, true, true);
MediaFormat acceptedFmt = mCodec.getInputFormat();
assertEquals(String.format("cdd required audio encoding %s, not supported by %s \n",
audioEncodingToString(mActiveEncCfg.mPcmEncoding), mCodecName) + mTestConfig
+ mTestEnv, mActiveEncCfg.mPcmEncoding,
acceptedFmt.getInteger(MediaFormat.KEY_PCM_ENCODING,
AudioFormat.ENCODING_PCM_16BIT));
mCodec.start();
doWork(Integer.MAX_VALUE);
queueEOS();
waitForAllOutputs();
mCodec.stop();
mCodec.release();
// decode
ArrayList<MediaFormat> fmts = new ArrayList<>();
mOutFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, mActiveEncCfg.mPcmEncoding);
fmts.add(mOutFormat);
ArrayList<String> listOfDecoders = selectCodecs(mMime, fmts, null, false);
assertFalse("no suitable codecs found for fmt: " + mOutFormat + "\n" + mTestConfig
+ mTestEnv, listOfDecoders.isEmpty());
CodecDecoderTestBase cdtb = new CodecDecoderTestBase(listOfDecoders.get(0), mMime,
null, mAllTestParams);
cdtb.decodeToMemory(mOutputBuff.getBuffer(), mInfoList, mOutFormat, listOfDecoders.get(0));
assertEquals(String.format("cdd required audio encoding %s, not supported by %s \n",
audioEncodingToString(mActiveEncCfg.mPcmEncoding), listOfDecoders.get(0))
+ mTestConfig + mTestEnv, mActiveEncCfg.mPcmEncoding,
cdtb.getOutputFormat().getInteger(MediaFormat.KEY_PCM_ENCODING,
AudioFormat.ENCODING_PCM_16BIT));
// validate
ByteBuffer out = cdtb.getOutputManager().getBuffer();
if (isMediaTypeLossless(mMime)) {
if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)
&& mActiveEncCfg.mPcmEncoding == AudioFormat.ENCODING_PCM_FLOAT) {
CodecDecoderTest.verify(cdtb.getOutputManager(), mActiveRawRes.mFileName, 3.446394f,
mActiveEncCfg.mPcmEncoding, -1L,
mTestConfig.toString() + mTestEnv.toString());
} else {
assertEquals("Identity test failed for lossless codec \n " + mTestConfig
+ mTestEnv, out, ByteBuffer.wrap(mInputData));
}
} else {
float tolerance = ACCEPTABLE_AV_SYNC_ERROR * mActiveEncCfg.mSampleRate
* mActiveEncCfg.mChannelCount * mActiveRawRes.mBytesPerSample / 1000;
if (mInputData.length > out.limit() + tolerance) {
String errMsg = "################ Error Details #################\n";
errMsg += String.format("Input sample size is %d, output sample size is %d",
mInputData.length, out.limit());
fail("In the process {[i/p] -> Encode -> Decode [o/p]}, the output sample count "
+ "is less than input sample count. This could be due to encoder-delay "
+ "and/or encoder-padding not communicated cleanly. A/V sync errors "
+ "possible \n" + mTestConfig + mTestEnv + errMsg + errMsg);
}
}
}
/**
* Check description of class {@link AudioEncoderTest}
*/
@ApiTest(apis = {"android.media.AudioFormat#ENCODING_PCM_16BIT",
"android.media.AudioFormat#ENCODING_PCM_FLOAT"})
@CddTest(requirements = "5.1.1")
@LargeTest
@Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
public void testEncodeAndValidate() throws IOException, InterruptedException {
// pre run checks
mActiveEncCfg = mEncCfgParams[0];
ArrayList<MediaFormat> formats = new ArrayList<>();
formats.add(mActiveEncCfg.getFormat());
checkFormatSupport(mCodecName, mMime, true, formats, null, CODEC_OPTIONAL);
// encode and validate
mActiveRawRes = EncoderInput.getRawResource(mActiveEncCfg);
assertNotNull("no raw resource found for testing config : " + mActiveEncCfg + mTestConfig
+ mTestEnv, mActiveRawRes);
encodeAndValidate();
}
}