blob: 02636010abab3c8843cdd35d80cbf52ddb5651eb [file] [log] [blame]
/*
* 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 android.media.cts;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.Instrumentation;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.cts.DecoderTest.AudioParameter;
import android.media.cts.R;
import android.os.Build;
import android.util.Log;
import android.os.Bundle;
import androidx.test.InstrumentationRegistry;
import com.android.compatibility.common.util.ApiLevelUtil;
import com.android.compatibility.common.util.MediaUtils;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DecoderTestAacDrc {
private static final String TAG = "DecoderTestAacDrc";
private static final boolean sIsAndroidRAndAbove =
ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
private Resources mResources;
@Before
public void setUp() throws Exception {
final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
assertNotNull(inst);
mResources = inst.getContext().getResources();
}
/**
* Verify correct decoding of MPEG-4 AAC with output level normalization to -23dBFS.
*/
@Test
public void testDecodeAacDrcLevelM4a() throws Exception {
AudioParameter decParams = new AudioParameter();
// full boost, full cut, target ref level: -23dBFS, heavy compression: no
DrcParams drcParams = new DrcParams(127, 127, 92, 0);
short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drclevel_mp4,
-1, null, drcParams, null /*decoderName: use default decoder*/);
DecoderTest decTester = new DecoderTest();
decTester.checkEnergy(decSamples, decParams, 2, 0.70f);
}
/**
* Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata.
* Fully apply light compression DRC (default settings).
*/
@Test
public void testDecodeAacDrcFullM4a() throws Exception {
AudioParameter decParams = new AudioParameter();
short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drcfull_mp4,
-1, null, null, null /*decoderName: use default decoder*/);
DecoderTest decTester = new DecoderTest();
decTester.checkEnergy(decSamples, decParams, 2, 0.80f);
}
/**
* Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata.
* Apply only half of the light compression DRC and normalize to -20dBFS output level.
*/
@Test
public void testDecodeAacDrcHalfM4a() throws Exception {
AudioParameter decParams = new AudioParameter();
// half boost, half cut, target ref level: -20dBFS, heavy compression: no
DrcParams drcParams = new DrcParams(63, 63, 80, 0);
short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_drchalf_mp4,
-1, null, drcParams, null /*decoderName: use default decoder*/);
DecoderTest decTester = new DecoderTest();
decTester.checkEnergy(decSamples, decParams, 2, 0.80f);
}
/**
* Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata.
* Disable light compression DRC to test if MediaFormat keys reach the decoder.
*/
@Test
public void testDecodeAacDrcOffM4a() throws Exception {
AudioParameter decParams = new AudioParameter();
// no boost, no cut, target ref level: -16dBFS, heavy compression: no
DrcParams drcParams = new DrcParams(0, 0, 64, 0); // normalize to -16dBFS
short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drcoff_mp4,
-1, null, drcParams, null /*decoderName: use default decoder*/);
DecoderTest decTester = new DecoderTest();
decTester.checkEnergy(decSamples, decParams, 2, 0.80f);
}
/**
* Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata.
* Apply heavy compression gains and normalize to -16dBFS output level.
*/
@Test
public void testDecodeAacDrcHeavyM4a() throws Exception {
AudioParameter decParams = new AudioParameter();
// full boost, full cut, target ref level: -16dBFS, heavy compression: yes
DrcParams drcParams = new DrcParams(127, 127, 64, 1);
short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_drcheavy_mp4,
-1, null, drcParams, null /*decoderName: use default decoder*/);
DecoderTest decTester = new DecoderTest();
decTester.checkEnergy(decSamples, decParams, 2, 0.80f);
}
/**
* Test signal limiting (without clipping) of MPEG-4 AAC decoder with the help of DRC metadata.
* Uses a two channel 248 Hz sine tone at 48 kHz sampling rate for input.
*/
@Test
public void testDecodeAacDrcClipM4a() throws Exception {
AudioParameter decParams = new AudioParameter();
short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drcclip_mp4,
-1, null, null, null /*decoderName: use default decoder*/);
checkClipping(decSamples, decParams, 248.0f /* Hz */);
}
/**
* Test if there is decoder internal clipping of MPEG-4 AAC decoder.
* Uses a two channel 248 Hz sine tone at 48 kHz sampling rate for input.
*/
@Test
public void testDecodeAacInternalClipM4a() throws Exception {
if (!MediaUtils.check(sIsAndroidRAndAbove, "Internal clipping fixed in Android R"))
return;
AudioParameter decParams = new AudioParameter();
short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_internalclip_mp4,
-1, null, null, null /*decoderName: use default decoder*/);
checkClipping(decSamples, decParams, 248.0f /* Hz */);
}
/**
* Default decoder target level.
* The actual default value used by the decoder can differ between platforms, or even devices,
* but tests will measure energy relative to this value.
*/
public static final int DEFAULT_DECODER_TARGET_LEVEL = 64; // -16.0 dBFs
/**
* Test USAC decoder with different target loudness levels
*/
@Test
public void testDecodeUsacLoudnessM4a() throws Exception {
Log.v(TAG, "START testDecodeUsacLoudnessM4a");
ArrayList<String> aacDecoderNames = DecoderTestXheAac.initAacDecoderNames();
assertTrue("No AAC decoder found", aacDecoderNames.size() > 0);
for (String aacDecName : aacDecoderNames) {
// test default loudness
// decoderTargetLevel = 64 --> target output level = -16.0 dBFs
try {
checkUsacLoudness(DEFAULT_DECODER_TARGET_LEVEL, 1, 1.0f, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
aacDecName);
throw new RuntimeException(e);
}
// test loudness boost
// decoderTargetLevel = 40 --> target output level = -10.0 dBFs
// normFactor = 1/(10^(-6/10)) = 3.98f
// where "-6" is the difference between the default level (-16), and -10 for this test
try {
checkUsacLoudness(40, 1, (float)(1.0f/Math.pow(10.0f, -6.0f/10.0f)), aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness boost failed for " + aacDecName);
throw new RuntimeException(e);
}
// test loudness attenuation
// decoderTargetLevel = 96 --> target output level = -24.0 dBFs
// normFactor = 1/(10^(8/10)) = 0.15f
// where 8 is the difference between the default level (-16), and -24 for this test
try {
checkUsacLoudness(96, 0, (float)(1.0f/Math.pow(10.0f, 8.0f/10.0f)), aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness attenuation failed for "
+ aacDecName);
throw new RuntimeException(e);
}
if (sIsAndroidRAndAbove) {
// test loudness normalization off
// decoderTargetLevel = -1 --> target output level = -19.0 dBFs (program loudness of
// waveform)
// normFactor = 1/(10^(3/10)) = 0.5f
// where 3 is the difference between the default level (-16), and -19 for this test
try {
checkUsacLoudness(-1, 0, (float) (1.0f / Math.pow(10.0f, 3.0f / 10.0f)),
aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness attenuation failed for "
+ aacDecName);
throw new RuntimeException(e);
}
}
}
}
/**
* Verify that the correct output loudness values are returned by the MPEG-4 AAC decoder
*/
@Test
public void testDecodeAacDrcOutputLoudnessM4a() throws Exception {
Log.v(TAG, "START testDecodeAacDrcOutputLoudnessM4a");
ArrayList<String> aacDecoderNames = DecoderTestXheAac.initAacDecoderNames();
assertTrue("No AAC decoder found", aacDecoderNames.size() > 0);
for (String aacDecName : aacDecoderNames) {
// test drc output loudness
// testfile without loudness metadata and loudness normalization off
// -> expected value: -1
try {
checkAacDrcOutputLoudness(
R.raw.noise_1ch_24khz_aot5_dr_sbr_sig1_mp4, -1, -1, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
aacDecName);
throw new RuntimeException(e);
}
// test drc output loudness
// testfile without loudness metadata and loudness normalization on
// -> expected value: -1
try {
checkAacDrcOutputLoudness(
R.raw.noise_1ch_24khz_aot5_dr_sbr_sig1_mp4, 70, -1, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
aacDecName);
throw new RuntimeException(e);
}
// test drc output loudness
// testfile with MPEG-4 DRC loudness metadata and loudness normalization off
// -> expected value: loudness metadata in bitstream (-16*-4 = 64)
try {
checkAacDrcOutputLoudness(
R.raw.sine_2ch_48khz_aot2_drchalf_mp4, -1, 64, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
aacDecName);
throw new RuntimeException(e);
}
// test drc output loudness
// testfile with MPEG-4 DRC loudness metadata and loudness normalization off
// -> expected value: loudness metadata in bitstream (-31*-4 = 124)
try {
checkAacDrcOutputLoudness(
R.raw.sine_2ch_48khz_aot5_drcclip_mp4, -1, 124, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
aacDecName);
throw new RuntimeException(e);
}
// test drc output loudness
// testfile with MPEG-4 DRC loudness metadata and loudness normalization on
// -> expected value: target loudness value (85)
try {
checkAacDrcOutputLoudness(
R.raw.sine_2ch_48khz_aot5_drcclip_mp4, 85, 85, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
aacDecName);
throw new RuntimeException(e);
}
}
}
/**
* Internal utilities
*/
/**
* The test routine performs a THD+N (Total Harmonic Distortion + Noise) analysis on a given
* audio signal (decSamples). The THD+N value is defined here as harmonic distortion (+ noise)
* RMS over full signal RMS.
*
* After the energy measurement of the unprocessed signal the routine creates and applies a
* notch filter at the given frequency (sineFrequency). Afterwards the signal energy is
* measured again. Then the THD+N value is calculated as the ratio of the filtered and the full
* signal energy.
*
* The test passes if the THD+N value is lower than -60 dB. Otherwise it fails.
*
* @param decSamples the decoded audio samples to be tested
* @param decParams the audio parameters of the given audio samples (decSamples)
* @param sineFrequency frequency of the test signal tone used for testing
* @throws RuntimeException
*/
private void checkClipping(short[] decSamples, AudioParameter decParams, float sineFrequency)
throws RuntimeException
{
final double threshold_clipping = -60.0; // dB
final int numChannels = decParams.getNumChannels();
final int startSample = 2 * 2048 * numChannels; // exclude signal on- & offset to
final int stopSample = decSamples.length - startSample; // ... measure only the stationary
// ... sine tone
// get full energy of signal (all channels)
double nrgFull = getEnergy(decSamples, startSample, stopSample);
// create notch filter to suppress sine-tone at 248 Hz
Biquad filter = new Biquad(sineFrequency, decParams.getSamplingRate());
for (int channel = 0; channel < numChannels; channel++) {
// apply notch-filter on buffer for each channel to filter out the sine tone.
// only the harmonics (and noise) remain. */
filter.apply(decSamples, channel, numChannels);
}
// get energy of harmonic distortion (signal without sine-tone)
double nrgHd = getEnergy(decSamples, startSample, stopSample);
// Total Harmonic Distortion + Noise, defined here as harmonic distortion (+ noise) RMS
// over full signal RMS, given in dB
double THDplusN = 10 * Math.log10(nrgHd / nrgFull);
assertTrue("signal has clipping samples", THDplusN <= threshold_clipping);
}
/**
* Measure the energy of a given signal over all channels within a given signal range.
* @param signal audio signal samples
* @param start start offset of the measuring range
* @param stop stop sample which is the last sample of the measuring range
* @return the signal energy in the given range
*/
private double getEnergy(short[] signal, int start, int stop) {
double nrg = 0.0;
for (int sample = start; sample < stop; sample++) {
double v = signal[sample];
nrg += v * v;
}
return nrg;
}
// Notch filter implementation
private class Biquad {
// filter coefficients for biquad filter (2nd order IIR filter)
float[] a;
float[] b;
// filter states
float[] state_ff;
float[] state_fb;
protected float alpha = 0.95f;
public Biquad(float f_notch, float f_s) {
// Create filter coefficients of notch filter which suppresses a sine tone with f_notch
// Hz at sampling frequency f_s. Zeros placed at unit circle at f_notch, poles placed
// nearby the unit circle at f_notch.
state_ff = new float[2];
state_fb = new float[2];
state_ff[0] = state_ff[1] = state_fb[0] = state_fb[1] = 0.0f;
a = new float[3];
b = new float[3];
double omega = 2.0 * Math.PI * f_notch / f_s;
a[0] = b[0] = b[2] = 1.0f;
a[1] = -2.0f * alpha * (float)Math.cos(omega);
a[2] = alpha * alpha;
b[1] = -2.0f * (float)Math.cos(omega);
}
public void apply(short[] signal, int offset, int stride) {
// reset states
state_ff[0] = state_ff[1] = 0.0f;
state_fb[0] = state_fb[1] = 0.0f;
// process 2nd order IIR filter in Direct Form I
float x_0, x_1, x_2, y_0, y_1, y_2;
x_2 = state_ff[0]; // x[n-2]
x_1 = state_ff[1]; // x[n-1]
y_2 = state_fb[0]; // y[n-2]
y_1 = state_fb[1]; // y[n-1]
for (int sample = offset; sample < signal.length; sample += stride) {
x_0 = signal[sample];
y_0 = b[0] * x_0 + b[1] * x_1 + b[2] * x_2
- a[1] * y_1 - a[2] * y_2;
x_2 = x_1;
x_1 = x_0;
y_2 = y_1;
y_1 = y_0;
signal[sample] = (short)y_0;
}
state_ff[0] = x_2; // next x[n-2]
state_ff[1] = x_1; // next x[n-1]
state_fb[0] = y_2; // next y[n-2]
state_fb[1] = y_1; // next y[n-1]
}
}
/**
* USAC test DRC loudness
*/
private void checkUsacLoudness(int decoderTargetLevel, int heavy, float normFactor,
String decoderName) throws Exception {
for (boolean runtimeChange : new boolean[] {false, true}) {
if (runtimeChange && !sIsAndroidRAndAbove) {
// changing decoder configuration after it has been initialized requires R and above
continue;
}
AudioParameter decParams = new AudioParameter();
DrcParams drcParams_def = new DrcParams(127, 127, DEFAULT_DECODER_TARGET_LEVEL, 1);
DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, heavy);
short[] decSamples_def = decodeToMemory(decParams,
R.raw.noise_2ch_48khz_aot42_19_lufs_mp4,
-1, null, drcParams_def, decoderName);
short[] decSamples_test = decodeToMemory(decParams,
R.raw.noise_2ch_48khz_aot42_19_lufs_mp4,
-1, null, drcParams_test, decoderName, runtimeChange);
DecoderTestXheAac decTesterXheAac = new DecoderTestXheAac();
float[] nrg_def = decTesterXheAac.checkEnergyUSAC(decSamples_def, decParams, 2, 1);
float[] nrg_test = decTesterXheAac.checkEnergyUSAC(decSamples_test, decParams, 2, 1);
float[] nrgThreshold = {2602510595620.0f, 2354652443657.0f};
// Check default loudness behavior
if (nrg_def[0] > nrgThreshold[0] || nrg_def[0] < nrgThreshold[1]) {
throw new Exception("Default loudness behavior not as expected");
}
float nrgRatio = nrg_def[0]/nrg_test[0];
// Check for loudness boost/attenuation if decoderTargetLevel deviates from default value
// used in these tests (note that the default target level can change from platform
// to platform, or device to device)
if (decoderTargetLevel != -1) {
if ((decoderTargetLevel < DEFAULT_DECODER_TARGET_LEVEL) // boosted loudness
&& (nrg_def[0] > nrg_test[0])) {
throw new Exception("Signal not attenuated");
}
if ((decoderTargetLevel > DEFAULT_DECODER_TARGET_LEVEL) // attenuated loudness
&& (nrg_def[0] < nrg_test[0])) {
throw new Exception("Signal not boosted");
}
}
nrgRatio = nrgRatio * normFactor;
// Check whether loudness behavior is as expected
if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
throw new Exception("Loudness behavior not as expected");
}
}
}
/**
* AAC test Output Loudness
*/
private void checkAacDrcOutputLoudness(int testInput, int decoderTargetLevel, int expectedOutputLoudness, String decoderName) throws Exception {
for (boolean runtimeChange : new boolean[] {false, true}) {
AudioParameter decParams = new AudioParameter();
DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, 0, 6);
// Check drc loudness preference
short[] decSamples_test = decodeToMemory(decParams, testInput, -1, null,
drcParams_test, decoderName, runtimeChange, expectedOutputLoudness);
}
}
/**
* Class handling all MPEG-4 and MPEG-D Dynamic Range Control (DRC) parameter relevant
* for testing
*/
protected static class DrcParams {
int mBoost; // scaling of boosting gains
int mCut; // scaling of compressing gains
int mDecoderTargetLevel; // desired target output level (for normalization)
int mHeavy; // en-/disable heavy compression
int mEffectType; // MPEG-D DRC Effect Type
int mAlbumMode; // MPEG-D DRC Album Mode
public DrcParams() {
mBoost = 127; // no scaling
mCut = 127; // no scaling
mHeavy = 1; // enabled
}
public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy) {
mBoost = boost;
mCut = cut;
mDecoderTargetLevel = decoderTargetLevel;
mHeavy = heavy;
}
public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy, int effectType) {
this(boost, cut, decoderTargetLevel, heavy);
mEffectType = effectType;
}
public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy, int effectType,
int albumMode) {
this(boost, cut, decoderTargetLevel, heavy, effectType);
mAlbumMode = albumMode;
}
}
// TODO: code is the same as in DecoderTest, differences are:
// - addition of application of DRC parameters
// - no need/use of resetMode, configMode
// Split method so code can be shared
private short[] decodeToMemory(AudioParameter audioParams, int testinput, int eossample,
List<Long> timestamps, DrcParams drcParams, String decoderName, boolean runtimeChange,
int expectedOutputLoudness)
throws IOException
{
String localTag = TAG + "#decodeToMemory";
short [] decoded = new short[0];
int decodedIdx = 0;
AssetFileDescriptor testFd = mResources.openRawResourceFd(testinput);
MediaExtractor extractor;
MediaCodec codec;
ByteBuffer[] codecInputBuffers;
ByteBuffer[] codecOutputBuffers;
extractor = new MediaExtractor();
extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
testFd.getLength());
testFd.close();
assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
MediaFormat format = extractor.getTrackFormat(0);
String mime = format.getString(MediaFormat.KEY_MIME);
assertTrue("not an audio file", mime.startsWith("audio/"));
MediaFormat configFormat = format;
if (decoderName == null) {
codec = MediaCodec.createDecoderByType(mime);
} else {
codec = MediaCodec.createByCodecName(decoderName);
}
// set DRC parameters
if (drcParams != null) {
configFormat.setInteger(MediaFormat.KEY_AAC_DRC_BOOST_FACTOR, drcParams.mBoost);
configFormat.setInteger(MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR, drcParams.mCut);
if (!runtimeChange) {
if (drcParams.mDecoderTargetLevel != 0) {
configFormat.setInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
drcParams.mDecoderTargetLevel);
}
}
configFormat.setInteger(MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION, drcParams.mHeavy);
}
Log.v(localTag, "configuring with " + configFormat);
codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
if (drcParams != null && sIsAndroidRAndAbove) { // querying output format requires R
if(!runtimeChange) {
// check if MediaCodec gives back correct drc parameters
if (drcParams.mDecoderTargetLevel != 0) {
final int targetLevelFromCodec = codec.getOutputFormat()
.getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
fail("DRC Target Ref Level received from MediaCodec is not the level set");
}
}
}
}
codec.start();
codecInputBuffers = codec.getInputBuffers();
codecOutputBuffers = codec.getOutputBuffers();
if (drcParams != null) {
if (runtimeChange) {
if (drcParams.mDecoderTargetLevel != 0) {
Bundle b = new Bundle();
b.putInt(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
drcParams.mDecoderTargetLevel);
codec.setParameters(b);
}
}
}
extractor.selectTrack(0);
// start decoding
final long kTimeOutUs = 5000;
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
boolean sawInputEOS = false;
boolean sawOutputEOS = false;
int noOutputCounter = 0;
int samplecounter = 0;
while (!sawOutputEOS && noOutputCounter < 50) {
noOutputCounter++;
if (!sawInputEOS) {
int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
if (inputBufIndex >= 0) {
ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
int sampleSize =
extractor.readSampleData(dstBuf, 0 /* offset */);
long presentationTimeUs = 0;
if (sampleSize < 0 && eossample > 0) {
fail("test is broken: never reached eos sample");
}
if (sampleSize < 0) {
Log.d(TAG, "saw input EOS.");
sawInputEOS = true;
sampleSize = 0;
} else {
if (samplecounter == eossample) {
sawInputEOS = true;
}
samplecounter++;
presentationTimeUs = extractor.getSampleTime();
}
codec.queueInputBuffer(
inputBufIndex,
0 /* offset */,
sampleSize,
presentationTimeUs,
sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
if (!sawInputEOS) {
extractor.advance();
}
}
}
int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
if (res >= 0) {
//Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs);
if (info.size > 0) {
noOutputCounter = 0;
if (timestamps != null) {
timestamps.add(info.presentationTimeUs);
}
}
int outputBufIndex = res;
ByteBuffer buf = codecOutputBuffers[outputBufIndex];
if (decodedIdx + (info.size / 2) >= decoded.length) {
decoded = Arrays.copyOf(decoded, decodedIdx + (info.size / 2));
}
buf.position(info.offset);
for (int i = 0; i < info.size; i += 2) {
decoded[decodedIdx++] = buf.getShort();
}
codec.releaseOutputBuffer(outputBufIndex, false /* render */);
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.d(TAG, "saw output EOS.");
sawOutputEOS = true;
}
} else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
codecOutputBuffers = codec.getOutputBuffers();
Log.d(TAG, "output buffers have changed.");
} else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat oformat = codec.getOutputFormat();
audioParams.setNumChannels(oformat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
audioParams.setSamplingRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
Log.d(TAG, "output format has changed to " + oformat);
} else {
Log.d(TAG, "dequeueOutputBuffer returned " + res);
}
}
if (noOutputCounter >= 50) {
fail("decoder stopped outputing data");
}
// check if MediaCodec gives back correct drc parameters (R and above)
if (drcParams != null && sIsAndroidRAndAbove) {
if (drcParams.mDecoderTargetLevel != 0) {
final int targetLevelFromCodec = codec.getOutputFormat()
.getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
fail("DRC Target Ref Level received from MediaCodec is not the level set");
}
}
final MediaFormat outputFormat = codec.getOutputFormat();
final int cutFromCodec = outputFormat.getInteger(
MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR);
assertEquals("Attenuation factor received from MediaCodec differs from set:",
drcParams.mCut, cutFromCodec);
final int boostFromCodec = outputFormat.getInteger(
MediaFormat.KEY_AAC_DRC_BOOST_FACTOR);
assertEquals("Boost factor received from MediaCodec differs from set:",
drcParams.mBoost, boostFromCodec);
}
// expectedOutputLoudness == -2 indicates that output loudness is not tested
if (expectedOutputLoudness != -2 && sIsAndroidRAndAbove) {
final int outputLoudnessFromCodec = codec.getOutputFormat()
.getInteger(MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
if (outputLoudnessFromCodec != expectedOutputLoudness) {
fail("Received decoder output loudness is not the expected value");
}
}
codec.stop();
codec.release();
return decoded;
}
private short[] decodeToMemory(AudioParameter audioParams, int testinput,
int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName)
throws IOException
{
final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
drcParams, decoderName, false, -2);
return decoded;
}
private short[] decodeToMemory(AudioParameter audioParams, int testinput,
int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName,
boolean runtimeChange)
throws IOException
{
final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
drcParams, decoderName, runtimeChange, -2);
return decoded;
}
}