blob: a4b8e4e1328a68da636581575dba8a36f956c23c [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 android.media.cts;
import static org.junit.Assert.*;
import android.app.Instrumentation;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.cts.DecoderTest.AudioParameter;
import android.media.cts.DecoderTestAacDrc.DrcParams;
import android.media.cts.R;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
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 DecoderTestXheAac {
private static final String TAG = "DecoderTestXheAac";
private Resources mResources;
// list of all AAC decoders as enumerated through the MediaCodecList
// lazy initialization in setUp()
private static ArrayList<String> sAacDecoderNames;
@Before
public void setUp() throws Exception {
final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
assertNotNull(inst);
mResources = inst.getContext().getResources();
// build a list of all AAC decoders on which to run the test
if (sAacDecoderNames == null) {
sAacDecoderNames = initAacDecoderNames();
}
}
protected static ArrayList<String> initAacDecoderNames() {
// at least 1 AAC decoder expected
ArrayList<String> aacDecoderNames = new ArrayList<String>(1);
final MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
final MediaCodecInfo[] mediaCodecInfos = mediaCodecList.getCodecInfos();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfos) {
if (mediaCodecInfo.isEncoder()) {
continue;
}
final String[] mimeTypes = mediaCodecInfo.getSupportedTypes();
for (String mimeType : mimeTypes) {
if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) {
aacDecoderNames.add(mediaCodecInfo.getName());
break;
}
}
}
return aacDecoderNames;
}
/**
* Verify the correct decoding of USAC bitstreams with different MPEG-D DRC effect types.
*/
@Test
public void testDecodeUsacDrcEffectTypeM4a() throws Exception {
Log.v(TAG, "START testDecodeUsacDrcEffectTypeM4a");
assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
for (String aacDecName : sAacDecoderNames) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a running for dec=" + aacDecName);
// test DRC effectTypeID 1 "NIGHT"
// L -3dB -> normalization factor = 1/(10^(-3/10)) = 0.5011f
// R +3dB -> normalization factor = 1/(10^( 3/10)) = 1.9952f
try {
checkUsacDrcEffectType(1, 0.5011f, 1.9952f, "Night", 2, 0, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/2/0 failed for dec=" + aacDecName);
throw new RuntimeException(e);
}
// test DRC effectTypeID 2 "NOISY"
// L +3dB -> normalization factor = 1/(10^( 3/10)) = 1.9952f
// R -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
try {
checkUsacDrcEffectType(2, 1.9952f, 0.2511f, "Noisy", 2, 0, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Noisy/2/0 failed for dec=" + aacDecName);
throw new RuntimeException(e);
}
// test DRC effectTypeID 3 "LIMITED"
// L -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
// R +6dB -> normalization factor = 1/(10^( 6/10)) = 3.9810f
try {
checkUsacDrcEffectType(3, 0.2511f, 3.9810f, "Limited", 2, 0, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Limited/2/0 failed for dec="
+ aacDecName);
throw new RuntimeException(e);
}
// test DRC effectTypeID 6 "GENERAL"
// L +6dB -> normalization factor = 1/(10^( 6/10)) = 3.9810f
// R -3dB -> normalization factor = 1/(10^(-3/10)) = 0.5011f
try {
checkUsacDrcEffectType(6, 3.9810f, 0.5011f, "General", 2, 0, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a General/2/0 failed for dec="
+ aacDecName);
throw new RuntimeException(e);
}
// test DRC effectTypeID 1 "NIGHT"
// L -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
// R +6dB -> normalization factor = 1/(10^( 6/10)) = 3.9810f
// mono -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
try {
checkUsacDrcEffectType(1, 0.2511f, 3.9810f, "Night", 2, 1, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/2/1 for dec=" + aacDecName);
throw new RuntimeException(e);
}
try {
checkUsacDrcEffectType(1, 0.2511f, 0.0f, "Night", 1, 1, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/1/1 for dec=" + aacDecName);
throw new RuntimeException(e);
}
// test DRC effectTypeID 2 "NOISY"
// L +6dB -> normalization factor = 1/(10^( 6/10)) = 3.9810f
// R -9dB -> normalization factor = 1/(10^(-9/10)) = 0.1258f
// mono +6dB -> normalization factor = 1/(10^( 6/10)) = 3.9810f
try {
checkUsacDrcEffectType(2, 3.9810f, 0.1258f, "Noisy", 2, 1, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Noisy/2/1 for dec=" + aacDecName);
throw new RuntimeException(e);
}
try {
checkUsacDrcEffectType(2, 3.9810f, 0.0f, "Noisy", 1, 1, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/2/1 for dec=" + aacDecName);
throw new RuntimeException(e);
}
// test DRC effectTypeID 3 "LIMITED"
// L -9dB -> normalization factor = 1/(10^(-9/10)) = 0.1258f
// R +9dB -> normalization factor = 1/(10^( 9/10)) = 7.9432f
// mono -9dB -> normalization factor = 1/(10^(-9/10)) = 0.1258f
try {
checkUsacDrcEffectType(3, 0.1258f, 7.9432f, "Limited", 2, 1, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Limited/2/1 for dec=" + aacDecName);
throw new RuntimeException(e);
}
try {
checkUsacDrcEffectType(3, 0.1258f, 0.0f, "Limited", 1, 1, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Limited/1/1 for dec=" + aacDecName);
throw new RuntimeException(e);
}
// test DRC effectTypeID 6 "GENERAL"
// L +9dB -> normalization factor = 1/(10^( 9/10)) = 7.9432f
// R -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
// mono +9dB -> normalization factor = 1/(10^( 9/10)) = 7.9432f
try {
checkUsacDrcEffectType(6, 7.9432f, 0.2511f, "General", 2, 1, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a General/2/1 for dec=" + aacDecName);
throw new RuntimeException(e);
}
try {
checkUsacDrcEffectType(6, 7.9432f, 0.0f, "General", 1, 1, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a General/1/1 for dec=" + aacDecName);
throw new RuntimeException(e);
}
}
}
/**
* Verify the correct decoding of USAC bitstreams with config changes.
*/
@Test
public void testDecodeUsacStreamSwitchingM4a() throws Exception {
Log.v(TAG, "START testDecodeUsacStreamSwitchingM4a");
assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
for (String aacDecName : sAacDecoderNames) {
// Stereo
// switch between SBR ratios and stereo modes
try {
checkUsacStreamSwitching(2.5459829E12f, 2,
R.raw.noise_2ch_44_1khz_aot42_19_lufs_config_change_mp4, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacStreamSwitchingM4a failed 2ch sbr/stereo switch for "
+ aacDecName);
throw new RuntimeException(e);
}
// Mono
// switch between SBR ratios and stereo modes
try {
checkUsacStreamSwitching(2.24669126E12f, 1,
R.raw.noise_1ch_38_4khz_aot42_19_lufs_config_change_mp4, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacStreamSwitchingM4a failed 1ch sbr/stereo switch for "
+ aacDecName);
throw new RuntimeException(e);
}
// Stereo
// switch between USAC modes
try {
checkUsacStreamSwitching(2.1E12f, 2,
R.raw.noise_2ch_35_28khz_aot42_19_lufs_drc_config_change_mp4, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacStreamSwitchingM4a failed 2ch USAC mode switch for "
+ aacDecName);
throw new RuntimeException(e);
}
// Mono
// switch between USAC modes
try {
checkUsacStreamSwitching(1.7E12f, 1,
R.raw.noise_1ch_29_4khz_aot42_19_lufs_drc_config_change_mp4, aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacStreamSwitchingM4a failed 1ch USAC mode switch for "
+ aacDecName);
throw new RuntimeException(e);
}
}
}
/**
* Verify the correct decoding of USAC bitstreams with various sampling rates.
*/
@Test
public void testDecodeUsacSamplingRatesM4a() throws Exception {
Log.v(TAG, "START testDecodeUsacSamplingRatesM4a");
assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
for (String aacDecName : sAacDecoderNames) {
try {
checkUsacSamplingRate(R.raw.noise_2ch_08khz_aot42_19_lufs_mp4, aacDecName);
checkUsacSamplingRate(R.raw.noise_2ch_12khz_aot42_19_lufs_mp4, aacDecName);
checkUsacSamplingRate(R.raw.noise_2ch_22_05khz_aot42_19_lufs_mp4, aacDecName);
checkUsacSamplingRate(R.raw.noise_2ch_64khz_aot42_19_lufs_mp4, aacDecName);
checkUsacSamplingRate(R.raw.noise_2ch_88_2khz_aot42_19_lufs_mp4, aacDecName);
checkUsacSamplingRateWoLoudness(R.raw.noise_2ch_19_2khz_aot42_no_ludt_mp4,
aacDecName);
} catch (Exception e) {
Log.v(TAG, "testDecodeUsacSamplingRatesM4a for decoder" + aacDecName);
throw new RuntimeException(e);
}
}
}
/**
* Internal utilities
*/
/**
* USAC test DRC Effect Type
*/
private void checkUsacDrcEffectType(int effectTypeID, float normFactor_L, float normFactor_R,
String effectTypeName, int nCh, int aggressiveDrc, String decoderName)
throws Exception {
int testinput = -1;
AudioParameter decParams = new AudioParameter();
DrcParams drcParams_def = new DrcParams(127, 127, 96, 0, -1);
DrcParams drcParams_test = new DrcParams(127, 127, 96, 0, effectTypeID);
if (aggressiveDrc == 0) {
testinput = R.raw.noise_2ch_32khz_aot42_19_lufs_drc_mp4;
} else {
if (nCh == 2) {
testinput = R.raw.noise_2ch_35_28khz_aot42_19_lufs_drc_config_change_mp4;
} else if (nCh == 1){
testinput = R.raw.noise_1ch_29_4khz_aot42_19_lufs_drc_config_change_mp4;
}
}
short[] decSamples_def = decodeToMemory(decParams, testinput,
-1, null, drcParams_def, decoderName);
short[] decSamples_test = decodeToMemory(decParams, testinput,
-1, null, drcParams_test, decoderName);
float[] nrg_def = checkEnergyUSAC(decSamples_def, decParams, nCh, 1, 0);
float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, nCh, 1, 1);
if (nCh == 2) {
float nrgRatio_L = (nrg_test[1]/nrg_def[1])/normFactor_L;
float nrgRatio_R = (nrg_test[2]/nrg_def[2])/normFactor_R;
if ((nrgRatio_R > 1.05f || nrgRatio_R < 0.95f)
|| (nrgRatio_L > 1.05f || nrgRatio_L < 0.95f) ){
throw new Exception("DRC Effect Type '" + effectTypeName + "' not as expected");
}
} else if (nCh == 1){
float nrgRatio_L = (nrg_test[0]/nrg_def[0])/normFactor_L;
if (nrgRatio_L > 1.05f || nrgRatio_L < 0.95f){
throw new Exception("DRC Effect Type '" + effectTypeName + "' not as expected");
}
}
}
/**
* USAC test stream switching
*/
private void checkUsacStreamSwitching(float nrg_ref, int encNch, int testinput,
String decoderName) throws Exception
{
AudioParameter decParams = new AudioParameter();
DrcParams drcParams = new DrcParams(127, 127, 64, 0, -1);
// Check stereo stream switching
short[] decSamples = decodeToMemory(decParams, testinput,
-1, null, drcParams, decoderName);
float[] nrg = checkEnergyUSAC(decSamples, decParams, encNch, 1);
float nrgRatio = nrg[0] / nrg_ref;
// Check if energy levels are within 15% of the reference
// Energy drops within the decoded stream are checked by checkEnergyUSAC() within every
// 250ms interval
if (nrgRatio > 1.15f || nrgRatio < 0.85f ) {
throw new Exception("Config switching not as expected");
}
}
/**
* USAC test sampling rate
*/
private void checkUsacSamplingRate(int testinput, String decoderName) throws Exception {
AudioParameter decParams = new AudioParameter();
DrcParams drcParams_def = new DrcParams(127, 127, 64, 0, -1);
DrcParams drcParams_test = new DrcParams(127, 127, 96, 0, -1);
short[] decSamples_def = decodeToMemory(decParams, testinput,
-1, null, drcParams_def, decoderName);
short[] decSamples_test = decodeToMemory(decParams, testinput,
-1, null, drcParams_test, decoderName);
float[] nrg_def = checkEnergyUSAC(decSamples_def, decParams, 2, 1);
float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, 2, 1);
float nrgRatio = nrg_def[0]/nrg_test[0];
// normFactor = 1/(10^(-8/10)) = 6.3f
nrgRatio = nrgRatio / 6.3f;
// Check whether behavior is as expected
if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
throw new Exception("Sampling rate not supported");
}
}
/**
* USAC test sampling rate for streams without loudness application
*/
private void checkUsacSamplingRateWoLoudness(int testinput, String decoderName) throws Exception
{
AudioParameter decParams = new AudioParameter();
DrcParams drcParams = new DrcParams();
short[] decSamples = decodeToMemory(decParams, testinput, -1, null, drcParams, decoderName);
float[] nrg = checkEnergyUSAC(decSamples, decParams, 2, 1);
float nrg_ref = 3.15766394E12f;
float nrgRatio = nrg_ref/nrg[0];
// Check whether behavior is as expected
if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
throw new Exception("Sampling rate not supported");
}
}
/**
* Perform a segmented energy analysis on given audio signal samples and run several tests on
* the energy values.
*
* The main purpose is to verify whether a USAC decoder implementation applies Spectral Band
* Replication (SBR), Parametric Stereo (PS) and Dynamic Range Control (DRC) correctly. All
* tools are inherent parts to either the MPEG-D USAC audio codec or the MPEG-D DRC tool.
*
* In addition, this test can verify the correct decoding of multi-channel (e.g. 5.1 channel)
* streams or the creation of a downmixed signal.
*
* Note: This test procedure is not an MPEG Conformance Test and can not serve as a replacement.
*
* @param decSamples the decoded audio samples to be tested
* @param decParams the audio parameters of the given audio samples (decSamples)
* @param encNch the encoded number of audio channels (number of channels of the original
* input)
* @param drcContext indicate to use test criteria applicable for DRC testing
* @return array of energies, at index 0: accumulated energy of all channels, and
* index 1 and over contain the individual channel energies
* @throws RuntimeException
*/
protected float[] checkEnergyUSAC(short[] decSamples, AudioParameter decParams,
int encNch, int drcContext)
{
final float[] nrg = checkEnergyUSAC(decSamples, decParams, encNch, drcContext, 0);
return nrg;
}
/**
* Same as {@link #checkEnergyUSAC(short[], AudioParameter, int, int)} but with DRC effec type
* @param decSamples
* @param decParams
* @param encNch
* @param drcContext
* @param drcApplied indicate if MPEG-D DRC Effect Type has been applied
* @return
* @throws RuntimeException
*/
private float[] checkEnergyUSAC(short[] decSamples, AudioParameter decParams,
int encNch, int drcContext, int drcApplied)
throws RuntimeException
{
String localTag = TAG + "#checkEnergyUSAC";
// the number of segments per block
final int nSegPerBlk = 4;
// the number of input channels
final int nCh = encNch;
// length of one (LB/HB) block [samples]
final int nBlkSmp = decParams.getSamplingRate();
// length of one segment [samples]
final int nSegSmp = nBlkSmp / nSegPerBlk;
// actual # samples per channel (total)
final int smplPerChan = decSamples.length / nCh;
// actual # samples per segment (all ch)
final int nSegSmpTot = nSegSmp * nCh;
// signal offset between chans [segments]
final int nSegChOffst = 2 * nSegPerBlk;
// // the number of channels to be analyzed
final int procNch = Math.min(nCh, encNch);
// all original configs with more than five channel have an LFE
final int encEffNch = (encNch > 5) ? encNch-1 : encNch;
// expected number of decoded audio samples
final int expSmplPerChan = Math.max(encEffNch, 2) * nSegChOffst * nSegSmp;
// flag telling that input is dmx signal
final boolean isDmx = nCh < encNch;
final float nrgRatioThresh = 0.0f;
// the num analyzed channels with signal
int effProcNch = procNch;
// get the signal offset by counting zero samples at the very beginning (over all channels)
// sample value threshold 4 signal search
final int zeroSigThresh = 1;
// receives the number of samples that are in front of the actual signal
int signalStart = smplPerChan;
// receives the number of null samples (per chan) at the very beginning
int noiseStart = signalStart;
for (int smpl = 0; smpl < decSamples.length; smpl++) {
int value = Math.abs(decSamples[smpl]);
if (value > 0 && noiseStart == signalStart) {
// store start of prepended noise (can be same as signalStart)
noiseStart = smpl / nCh;
}
if (value > zeroSigThresh) {
// store signal start offset [samples]
signalStart = smpl / nCh;
break;
}
}
signalStart = (signalStart > noiseStart+1) ? signalStart : noiseStart;
// check if the decoder reproduced a waveform or kept silent
assertTrue ("no signal found in any channel!", signalStart < smplPerChan);
// max num seg that fit into signal
final int totSeg = (smplPerChan - signalStart) / nSegSmp;
// max num relevant samples (per channel)
final int totSmp = nSegSmp * totSeg;
// check if the number of reproduced segments in the audio file is valid
assertTrue("no segments left to test after signal search", totSeg > 0);
// get the energies and the channel offsets by searching for the first segment above the
// energy threshold:
// ratio of zeroNrgThresh to the max nrg
final double zeroMaxNrgRatio = 0.001f;
// threshold to classify segment energies
double zeroNrgThresh = nSegSmp * nSegSmp;
// will store the max seg nrg over all ch
double totMaxNrg = 0.0f;
// array receiving the segment energies
double[][] nrg = new double[procNch][totSeg];
// array for channel offsets
int[] offset = new int[procNch];
// array receiving the segment energy status over all channels
boolean[] sigSeg = new boolean[totSeg];
// energy per channel
double[] nrgPerChannel = new double[procNch];
// return value: [0]: total energy of all channels
// [1 ... procNch + 1]: energy of the individual channels
float[] nrgTotal = new float[procNch + 1];
// mapping array to sort channels
int[] chMap = new int[nCh];
// calculate the segmental energy for each channel
for (int ch = 0; ch < procNch; ch++) {
offset[ch] = -1;
for (int seg = 0; seg < totSeg; seg++) {
final int smpStart = (signalStart * nCh) + (seg * nSegSmpTot) + ch;
final int smpStop = smpStart + nSegSmpTot;
for (int smpl = smpStart; smpl < smpStop; smpl += nCh) {
// accumulate total energy per channel
nrgPerChannel[ch] += decSamples[smpl] * decSamples[smpl];
// accumulate segment energy
nrg[ch][seg] += nrgPerChannel[ch];
}
// store 1st segment (index) per channel which has energy above the threshold to get
// the ch offsets
if (nrg[ch][seg] > zeroNrgThresh && offset[ch] < 0) {
offset[ch] = seg / nSegChOffst;
}
// store the max segment nrg over all ch
if (nrg[ch][seg] > totMaxNrg) {
totMaxNrg = nrg[ch][seg];
}
// store whether the channel has energy in this segment
sigSeg[seg] |= nrg[ch][seg] > zeroNrgThresh;
}
// if one channel has no signal it is most probably the LFE the LFE is no
// effective channel
if (offset[ch] < 0) {
effProcNch -= 1;
offset[ch] = effProcNch;
}
// recalculate the zero signal threshold based on the 1st channels max energy for
// all subsequent checks
if (ch == 0) {
zeroNrgThresh = zeroMaxNrgRatio * totMaxNrg;
}
}
// check if the LFE is decoded properly
assertTrue("more than one LFE detected", effProcNch >= procNch - 1);
// check if the amount of samples is valid
assertTrue(String.format("less samples decoded than expected: %d < %d",
decSamples.length - (signalStart * nCh),
totSmp * effProcNch),
decSamples.length - (signalStart * nCh) >= totSmp * effProcNch);
// for multi-channel signals the only valid front channel orders
// are L, R, C or C, L, R (L=left, R=right, C=center)
if (procNch >= 5) {
final int[] frontChMap1 = {2, 0, 1};
final int[] frontChMap2 = {0, 1, 2};
// check if the channel mapping is valid
if (!(Arrays.equals(Arrays.copyOfRange(offset, 0, 3), frontChMap1)
|| Arrays.equals(Arrays.copyOfRange(offset, 0, 3), frontChMap2))) {
fail("wrong front channel mapping");
}
}
// create mapping table to address channels from front to back the LFE must be last
if (drcContext == 0) {
// check whether every channel occurs exactly once
for (int ch = 0; ch < effProcNch; ch++) {
int occurred = 0;
for (int idx = 0; idx < procNch; idx++) {
if (offset[idx] == ch) {
occurred += 1;
chMap[ch] = idx;
}
}
// check if one channel is mapped more than one time
assertTrue(String.format("channel %d occurs %d times in the mapping", ch, occurred),
occurred == 1);
}
} else {
for (int ch = 0; ch < procNch; ch++) {
chMap[ch] = ch;
}
}
// reference min energy for the 1st ch; others will be compared against 1st
double refMinNrg = zeroNrgThresh;
// calculate total energy, min and max energy
for (int ch = 0; ch < procNch; ch++) {
// resolve channel mapping
int idx = chMap[ch];
// signal offset [segments]
final int ofst = offset[idx] * nSegChOffst;
if (ch <= effProcNch && ofst < totSeg) {
// the last segment that has energy
int nrgSegEnd;
// the number of segments with energy
int nrgSeg;
if (drcContext == 0) {
// the first channel of a mono or stereo signal has full signal all others have
// one LB + one HB block
if ((encNch <= 2) && (ch == 0)) {
nrgSeg = totSeg;
} else {
nrgSeg = Math.min(totSeg, (2 * nSegPerBlk) + ofst) - ofst;
}
} else {
nrgSeg = totSeg;
}
nrgSegEnd = ofst + nrgSeg;
// find min and max energy of all segments that should have signal
double minNrg = nrg[idx][ofst]; // channels minimum segment energy
double maxNrg = nrg[idx][ofst]; // channels maximum segment energy
// values of 1st segment already assigned
for (int seg = ofst + 1; seg < nrgSegEnd; seg++) {
if (nrg[idx][seg] < minNrg) {
minNrg = nrg[idx][seg];
}
if (nrg[idx][seg] > maxNrg) {
maxNrg = nrg[idx][seg];
}
}
// check if the energy of this channel is > 0
assertTrue(String.format("max energy of channel %d is zero", ch),maxNrg > 0.0f);
if (drcContext == 0) {
// check the channels minimum energy >= refMinNrg
assertTrue(String.format("channel %d has not enough energy", ch),
minNrg >= refMinNrg);
if (ch == 0) {
// use 85% of 1st channels min energy as reference the other chs must meet
refMinNrg = minNrg * 0.85f;
} else if (isDmx && (ch == 1)) {
// in case of downmixed signal the energy can be lower depending on the
refMinNrg *= 0.50f;
}
// calculate and check the energy ratio
final double nrgRatio = minNrg / maxNrg;
// check if the threshold is exceeded
assertTrue(String.format("energy ratio of channel %d below threshold", ch),
nrgRatio >= nrgRatioThresh);
if (!isDmx) {
if (nrgSegEnd < totSeg) {
// consider that some noise can extend into the subsequent segment
// allow this to be at max 20% of the channels minimum energy
assertTrue(
String.format("min energy after noise above threshold (%.2f)",
nrg[idx][nrgSegEnd]),
nrg[idx][nrgSegEnd] < minNrg * 0.20f);
nrgSegEnd += 1;
}
} else {
// ignore all subsequent segments in case of a downmixed signal
nrgSegEnd = totSeg;
}
// zero-out the verified energies to simplify the subsequent check
for (int seg = ofst; seg < nrgSegEnd; seg++) {
nrg[idx][seg] = 0.0f;
}
// check zero signal parts
for (int seg = 0; seg < totSeg; seg++) {
assertTrue(String.format("segment %d in channel %d has signal where should "
+ "be none (%.2f)", seg, ch, nrg[idx][seg]),
nrg[idx][seg] < zeroNrgThresh);
}
}
}
// test whether each segment has energy in at least one channel
for (int seg = 0; seg < totSeg; seg++) {
assertTrue(String.format("no channel has energy in segment %d", seg), sigSeg[seg]);
}
nrgTotal[0] += (float)nrgPerChannel[ch];
nrgTotal[ch + 1] = (float)nrgPerChannel[ch];
}
float errorMargin = 0.0f;
if (drcApplied == 0) {
errorMargin = 0.25f;
} else if (drcApplied == 1) {
errorMargin = 0.40f;
}
float totSegEnergy = 0.0f;
float[] segEnergy = new float[totSeg];
for (int seg = totSeg - 1; seg >= 0; seg--) {
if (seg != 0) {
for (int ch = 0; ch < nCh; ch++) {
segEnergy[seg] += nrg[ch][seg] - nrg[ch][seg - 1];
}
totSegEnergy += segEnergy[seg];
} else {
for (int ch = 0; ch < nCh; ch++) {
segEnergy[seg] += nrg[ch][seg];
}
}
}
float avgSegEnergy = totSegEnergy / (totSeg - 1);
for (int seg = 1; seg < totSeg; seg++) {
float energyRatio = segEnergy[seg] / avgSegEnergy;
if ((energyRatio > (1.0f + errorMargin) ) || (energyRatio < (1.0f - errorMargin) )) {
fail("Energy drop out");
}
}
// return nrgTotal: [0]: accumulated energy of all channels, [1 ... ] channel energies
return nrgTotal;
}
/**
* Decodes a compressed bitstream in the ISOBMFF into the RAM of the device.
*
* The decoder decodes compressed audio data as stored in the ISO Base Media File Format
* (ISOBMFF) aka .mp4/.m4a. The decoder is not reproducing the waveform but stores the decoded
* samples in the RAM of the device under test.
*
* @param audioParams the decoder parameter configuration
* @param testinput the compressed audio stream
* @param eossample the End-Of-Stream indicator
* @param timestamps the time stamps to decode
* @param drcParams the MPEG-D DRC decoder parameter configuration
* @param decoderName if non null, the name of the decoder to use for the decoding, otherwise
* the default decoder for the format will be used
* @throws RuntimeException
*/
public short[] decodeToMemory(AudioParameter audioParams, int testinput,
int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName)
throws IOException
{
// 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
final 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 (drcParams.mDecoderTargetLevel != 0) {
configFormat.setInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
drcParams.mDecoderTargetLevel);
}
configFormat.setInteger(MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION, drcParams.mHeavy);
if (drcParams.mEffectType != 0){
configFormat.setInteger(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE,
drcParams.mEffectType);
}
}
Log.v(localTag, "configuring with " + configFormat);
codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
codec.start();
codecInputBuffers = codec.getInputBuffers();
codecOutputBuffers = codec.getOutputBuffers();
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;
// main decoding loop
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 outputting data");
}
codec.stop();
codec.release();
return decoded;
}
}