| /* |
| * 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.CodecEncoderTestBase; |
| import android.mediav2.common.cts.EncoderConfigParams; |
| 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 CodecEncoderTestBase { |
| 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(); |
| } |
| } |