Loading scripts/binauralRenderer_interface/generate_crend_ivas_tables_from_sofa.c +10 −10 Original line number Diff line number Diff line Loading @@ -1282,7 +1282,7 @@ int generate_crend_ivas_tables_from_sofa( const char *file_path, ConfigReader *c ivas_get_hrtf_lens( ivas_hrtf, &hrtf_data_flt, frame_len ); if ( sample_rates[indSR] != DEFAULT_SAMPLERATE ) { hrtf_data.latency_s = latency_48k_optim; hrtf_data_flt.latency_s = latency_48k_optim; } if ( ( hrtf_data_flt.num_iterations[0][0] > ( frame_len_ms == 5 ? NUM_ITERATIONS_TO_ALLOW_OPTIM_5_MS : NUM_ITERATIONS_TO_ALLOW_OPTIM_20_MS ) ) && ( cfgReader->optimize == 1 ) ) Loading Loading @@ -1310,7 +1310,7 @@ int generate_crend_ivas_tables_from_sofa( const char *file_path, ConfigReader *c if ( sample_rates[indSR] == DEFAULT_SAMPLERATE ) { latency_48k_optim = hrtf_data.latency_s; latency_48k_optim = hrtf_data_flt.latency_s; } hrtf_data.index_frequency_max_diffuse = hrtf_data_flt.index_frequency_max_diffuse; Loading Loading @@ -2481,7 +2481,7 @@ void update_c_file( HRTFS_DATA *hrtf, struct ivas_layout_config lscfg, const int fprintf( fp, "\n};" ); /* float *pOut_to_bin_diffuse_re[BINAURAL_CHANNELS];*/ if ( hrtf->pOut_to_bin_diffuse_re[0] == NULL ) if ( hrtf->pOut_to_bin_diffuse_re_dyn[0] == NULL ) { fprintf( fp, "\nconst Word16 *%s_%s_coeff_diffuse_re_%2dkHz[BINAURAL_CHANNELS]={NULL,NULL};", DECLARATION_NAME, lscfg.name, samplerate / 1000 ); } Loading @@ -2495,7 +2495,7 @@ void update_c_file( HRTFS_DATA *hrtf, struct ivas_layout_config lscfg, const int } /* float *pOut_to_bin_diffuse_im[BINAURAL_CHANNELS];*/ if ( hrtf->pOut_to_bin_diffuse_im[0] == NULL ) if ( hrtf->pOut_to_bin_diffuse_im_dyn[0] == NULL ) { fprintf( fp, "\nconst Word16 *%s_%s_coeff_diffuse_im_%2dkHz[BINAURAL_CHANNELS]={NULL,NULL};", DECLARATION_NAME, lscfg.name, samplerate / 1000 ); } Loading Loading @@ -2654,7 +2654,7 @@ void update_h_file( HRTFS_DATA *hrtf, struct ivas_layout_config lscfg, const int fprintf( fp, "\nextern const Word16 %s_%s_inv_diffuse_weight_%2dkHz[BINAURAL_CHANNELS][%s];", DECLARATION_NAME, lscfg.name, samplerate / 1000, lscfg.output_config_num_channel_name ); /* uint16_t *pIndex_frequency_max_diffuse[BINAURAL_CHANNELS];*/ if ( hrtf->pIndex_frequency_max_diffuse[0] == NULL ) if ( hrtf->pIndex_frequency_max_diffuse_dyn[0] == NULL ) { fprintf( fp, "\nextern const Word16 *%s_%s_pIndex_frequency_max_diffuse_%2dkHz[BINAURAL_CHANNELS];", DECLARATION_NAME, lscfg.name, samplerate / 1000 ); } Loading @@ -2669,7 +2669,7 @@ void update_h_file( HRTFS_DATA *hrtf, struct ivas_layout_config lscfg, const int fprintf( fp, "\nextern const Word16 %s_%s_coeff_im_%2dkHz[%s][BINAURAL_CHANNELS][%u];", DECLARATION_NAME, lscfg.name, samplerate / 1000, lscfg.output_config_num_channel_name, maxTotalNumFreqSampPerIterations ); /* float *pOut_to_bin_diffuse_re_fx[BINAURAL_CHANNELS];*/ if ( hrtf->pOut_to_bin_diffuse_re[0] == NULL ) if ( hrtf->pOut_to_bin_diffuse_re_dyn[0] == NULL ) { fprintf( fp, "\nextern Word16 *%s_%s_coeff_diffuse_re_%2dkHz[BINAURAL_CHANNELS];", DECLARATION_NAME, lscfg.name, samplerate / 1000 ); } Loading @@ -2679,7 +2679,7 @@ void update_h_file( HRTFS_DATA *hrtf, struct ivas_layout_config lscfg, const int } /* float *pOut_to_bin_diffuse_im_fx[BINAURAL_CHANNELS];*/ if ( hrtf->pOut_to_bin_diffuse_im[0] == NULL ) if ( hrtf->pOut_to_bin_diffuse_im_dyn[0] == NULL ) { fprintf( fp, "\nextern Word16 *%s_%s_coeff_diffuse_im_%2dkHz[BINAURAL_CHANNELS];", DECLARATION_NAME, lscfg.name, samplerate / 1000 ); } Loading Loading @@ -2855,7 +2855,7 @@ ivas_error make_fx_be( HRTFS_DATA_flt *hrtf_flt, HRTFS_DATA *hrtf, struct ivas_l maxDiff = 0; if ( hrtf->latency_s > 0 ) if ( hrtf_flt->latency_s > 0 ) { hrtf->factor_Q_latency_s = (int16_t) floorf( 31.f - logf( hrtf_flt->latency_s ) / logf( 2.f ) ); } Loading @@ -2871,7 +2871,7 @@ ivas_error make_fx_be( HRTFS_DATA_flt *hrtf_flt, HRTFS_DATA *hrtf, struct ivas_l tmp = hrtf_flt->latency_s; hrtf->latency_s = float2int32_t( hrtf_flt->latency_s, hrtf->factor_Q_latency_s ); hrtf_flt->latency_s = (float) hrtf->latency_s * powf( 2.f, -1.f * (float) hrtf->factor_Q_latency_s ); maxDiff = fabsf( hrtf->latency_s - tmp ); maxDiff = fabsf( hrtf_flt->latency_s - tmp ); maxVal = 0; for ( iIR = 0; iIR < hrtf_flt->max_num_ir; iIR++ ) Loading Loading @@ -3341,7 +3341,7 @@ ivas_error write_binary_file( HRTFS_DATA *hrtf, struct ivas_layout_config lscfg, } /*---------------------------------------------------------------------* * make_fx_be(); * make_reverb_fx_be(); *---------------------------------------------------------------------*/ ivas_error make_reverb_fx_be( float *pEner_l, float *pEner_r, float *pCoherence, const int32_t samplerate, int16_t *factorQ ) { Loading scripts/binauralRenderer_interface/ivas_crend_binaural_filter_design.h +1 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ #define IVAS_SOFA_MAX_VAL_I ( 1 ) #define IVAS_MAX_HRTF_LATENCY_MS ( 0.1 ) /* max hrtf latency */ #define GAIN_LFE 1.88364911f /* Gain applied to LFE during renderering */ typedef struct ivas_hrtfs_structure_flt { Loading tests/cmp_pcm.py 100755 → 100644 +92 −17 Original line number Diff line number Diff line Loading @@ -3,6 +3,11 @@ import argparse import os import sys import tempfile import re import subprocess from pathlib import Path from typing import Optional THIS_PATH = os.path.join(os.getcwd(), __file__) sys.path.append(os.path.join(os.path.dirname(THIS_PATH), "../scripts")) Loading @@ -10,9 +15,21 @@ sys.path.append(os.path.join(os.path.dirname(THIS_PATH), "../scripts")) import numpy as np import pyaudio3dtools import pyivastest def cmp_pcm(file1, file2, out_config, fs, get_mld=False, mld_lim=0, abs_tol=0) -> (int, str): from .constants import ODG_PATTERN_PQEVALAUDIO def cmp_pcm( ref_file, cmp_file, out_config, fs, get_mld=False, allow_differing_lengths=False, mld_lim=0, abs_tol=0, get_ssnr=False, get_odg=False, ) -> (int, str): """ Compare 2 PCM files for bitexactness """ Loading @@ -20,36 +37,52 @@ def cmp_pcm(file1, file2, out_config, fs, get_mld=False, mld_lim=0, abs_tol=0) - print("=====================") out_config = "MONO" if out_config == "" else out_config if out_config.upper() not in pyivastest.constants.OC_TO_NCHANNELS: out_config_in_file_names = os.path.splitext(os.path.basename(out_config))[0] # out_config may be a string or a Path. Wrap in str() to avoid error in case it is a Path. if str(out_config).upper() not in pyivastest.constants.OC_TO_NCHANNELS: nchannels = ( pyivastest.IvasScriptsCommon.IvasScript.get_n_channels_from_ls_layout( out_config ) ) else: out_config_in_file_names = out_config nchannels = pyivastest.constants.OC_TO_NCHANNELS[out_config.upper()] s1, _ = pyaudio3dtools.audiofile.readfile(file1, nchannels, fs, outdtype=np.int16) s2, _ = pyaudio3dtools.audiofile.readfile(file2, nchannels, fs, outdtype=np.int16) s1, _ = pyaudio3dtools.audiofile.readfile( ref_file, nchannels, fs, outdtype=np.int16 ) s2, _ = pyaudio3dtools.audiofile.readfile( cmp_file, nchannels, fs, outdtype=np.int16 ) nchannels = s1.shape[ 1 ] # In case of wav input, override the nchannels with the one from the wav header # In case of wav input, override the nchannels with the one from the wav header nchannels = s1.shape[1] if s1.shape != s2.shape: if allow_differing_lengths: # to allow for MLD comparison, shorten longer file min_len = min(s1.shape[0], s2.shape[0]) s1 = s1[:min_len, :] s2 = s2[:min_len, :] elif s1.shape != s2.shape: print( f"file size in samples: file 1 = {s1.shape[0]},", f"file 2 = {s2.shape[0]}", ) return 1, "FAIL: File lengths differ" reason = "FAIL: File lengths differ. MAXIMUM ABS DIFF: None" if get_mld: reason += " - MLD: None" return 1, reason cmp_result = pyaudio3dtools.audioarray.compare( s1, s2, fs, per_frame=False, get_mld=get_mld s1, s2, fs, per_frame=False, get_mld=get_mld, get_ssnr=get_ssnr, ssnr_thresh_low=-50, ) output_differs = 0 reason = "SUCCESS: Files are bitexact" Loading @@ -75,13 +108,55 @@ def cmp_pcm(file1, file2, out_config, fs, get_mld=False, mld_lim=0, abs_tol=0) - else: reason += f" > {mld_lim}" if get_ssnr: reason += " - " for i, s in enumerate(cmp_result["SSNR"], start=1): msg = f"Channel {i} SSNR: {s}" reason += msg + " - " if get_odg: for n in range(nchannels): pqeval_output = pqevalaudio_wrapper(s1[:, n], s2[:, n], fs) match_odg = re.search(ODG_PATTERN_PQEVALAUDIO, pqeval_output) odg = float(match_odg.groups()[0]) msg = f"Channel {n} ODG: {odg}" reason += " - " + msg print(msg) return output_differs, reason def pqevalaudio_wrapper( ref_sig: np.ndarray, eval_sig: np.ndarray, fs: int, ) -> str: with tempfile.TemporaryDirectory() as tmp_dir: tmp_dir = Path(tmp_dir) tmp_file_ref = str(tmp_dir.joinpath("ref.wav")) tmp_file_eval = str(tmp_dir.joinpath("eval.wav")) # PQevalAudio neeeds 48 kHz sampling rate r48 = np.clip( pyaudio3dtools.audioarray.resample(ref_sig.astype(float), fs, 48000), -32768, 32767 ).astype(np.int16) t48 = np.clip( pyaudio3dtools.audioarray.resample(eval_sig.astype(float), fs, 48000), -32768, 32767 ).astype(np.int16) pyaudio3dtools.audiofile.writefile(tmp_file_ref, r48, 48000) pyaudio3dtools.audiofile.writefile(tmp_file_eval, t48, 48000) cmd = ["PQevalAudio", tmp_file_ref, tmp_file_eval] result = subprocess.run(cmd, capture_output=True) if result.returncode != 0: raise RuntimeError(f"Error running PQevalaudio: {result.stderr}") return result.stdout.decode("utf8") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("file1", type=str) parser.add_argument("file2", type=str) parser.add_argument("ref_file", type=str) parser.add_argument("cmp_file", type=str) parser.add_argument( "-o", "--out_config", Loading tests/conftest.py +157 −13 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ Pytest customization (configuration and fixtures) for the IVAS codec test suite. import logging import os import re from tests import testconfig import pytest import platform Loading @@ -41,6 +42,7 @@ import textwrap from pathlib import Path from subprocess import TimeoutExpired, run from typing import Optional, Union from .constants import MLD_PATTERN, MAX_DIFF_PATTERN, SSNR_PATTERN, ODG_PATTERN logger = logging.getLogger(__name__) USE_LOGGER_FOR_DBG = False # current tests do not make use of the logger feature Loading Loading @@ -128,6 +130,7 @@ def pytest_addoption(parser): parser.addoption( "--param_file", action="store", type=Path, help="If specified, use given param file in test_param_file.", ) Loading Loading @@ -170,6 +173,19 @@ def pytest_addoption(parser): help="MLD limit for comparison (default: 0)", default="0", ) parser.addoption( "--ssnr", action="store_true", help="Compute Segmental SNR (SSNR) between ref and dut output instead of just comparing for bitexactness", ) parser.addoption( "--odg", action="store_true", help="Get Objective Difference Grade for both conditions during comparison and report difference", ) parser.addoption( "--create_ref", action="store_true", Loading @@ -182,6 +198,19 @@ def pytest_addoption(parser): default=False, ) parser.addoption( "--use_ltv", action="store_true", default=False, ) parser.addoption( "--ltv_dir", action="store", type=Path, default=None, ) parser.addoption( "--dut_fr", help="Render frame size for the DUT output.", Loading Loading @@ -241,6 +270,22 @@ def get_mld_lim(request): return float(request.config.getoption("--mld-lim")) @pytest.fixture(scope="session", autouse=True) def get_ssnr(request): """ Return indication to compute ssnr during ref/dut comparison. """ return request.config.option.ssnr @pytest.fixture(scope="session", autouse=True) def get_odg(request): """ Return indication to compute ssnr during ref/dut comparison. """ return request.config.option.odg @pytest.fixture(scope="session") def abs_tol(request) -> int: """ Loading Loading @@ -312,6 +357,7 @@ class EncoderFrontend: pca: Optional[bool] = None, quiet_mode: Optional[bool] = True, add_option_list: Optional[list] = None, run_dir: Optional[Path] = None, ) -> None: command = [self._path] Loading Loading @@ -347,7 +393,11 @@ class EncoderFrontend: try: result = run( command, capture_output=True, check=False, timeout=self.timeout command, capture_output=True, check=False, timeout=self.timeout, cwd=run_dir, ) except TimeoutExpired: pytest.fail(f"{self._type} encoder run timed out after {self.timeout}s.") Loading Loading @@ -485,7 +535,9 @@ class DecoderFrontend: output_path: Path, quiet_mode: Optional[bool] = True, plc_file: Optional[Path] = None, netsim_profile: Optional[Path] = None, add_option_list: Optional[list] = None, run_dir: Optional[Path] = None, ) -> None: command = [self._path] Loading @@ -512,7 +564,7 @@ class DecoderFrontend: eid_command = [eid_path] eid_command.extend(["-fer", "-vbr", "-bs", "g192", "-ep", "g192"]) eid_output_suffix = "." + os.path.basename(plc_file) + ".fer" eid_output_suffix = ".fer" eid_command += [ str(input_bitstream_path), str(plc_file), Loading @@ -521,12 +573,53 @@ class DecoderFrontend: try: if not os.path.exists(str(input_bitstream_path) + eid_output_suffix): result = run(eid_command, check=True) except Exception: pytest.fail("eid-xor operation failed!") result = run(eid_command, check=True, cwd=run_dir) except Exception as e: pytest.fail(f"eid-xor operation failed! - {e}") input_bitstream_path += eid_output_suffix if netsim_profile is not None: system = platform.system() # TODO: centralize this in a utils file if system == "Windows": netsim_path = "./scripts/tools/Win32/networkSimulator_g192.exe" elif system == "Linux": netsim_path = "./scripts/tools/Linux/networkSimulator_g192" elif system == "Darwin": netsim_path = "./scripts/tools/Darwin/networkSimulator_g192" else: raise ValueError(f'Wrong system "{system}"!') if not os.path.isfile(netsim_path): raise FileNotFoundError( f"network simulator binary {netsim_path} not found!\n" ) netsim_bitstream_path = input_bitstream_path.with_suffix(".netsimout") tracefile_path = input_bitstream_path.with_suffix(".netsimtrace") # TODO: need to check if the "1" works with every profile netsim_command = [ netsim_path, netsim_profile, input_bitstream_path, netsim_bitstream_path, tracefile_path, "1", ] print(netsim_command) try: run(netsim_command, check=True, cwd=run_dir) except Exception as e: pytest.fail(f"netsim operation failed! - {e}") input_bitstream_path = netsim_bitstream_path voip_opt = ["-voip"] if add_option_list is None: add_option_list = voip_opt elif "-voip" not in add_option_list: add_option_list.extend(voip_opt) if add_option_list is not None: command.extend(add_option_list) Loading @@ -545,7 +638,11 @@ class DecoderFrontend: try: result = run( command, capture_output=True, check=False, timeout=self.timeout command, capture_output=True, check=False, timeout=self.timeout, cwd=run_dir, ) except TimeoutExpired: pytest.fail(f"{self._type} decoder run timed out after {self.timeout}s.") Loading Loading @@ -727,15 +824,10 @@ def decoder_only(request) -> bool: def pytest_configure(config): config.addinivalue_line("markers", "serial: mark test to run only in serial") config.addinivalue_line( "markers", "create_ref: mark test capable of producing references" ) config.addinivalue_line( "markers", "create_ref_part2: reference creation test that depends on create_ref references", ) if config.option.param_file: testconfig.PARAM_FILE = config.option.param_file if config.option.use_ltv: testconfig.use_ltv = True if config.option.selection_be_md5_file: md5_file_path = config.option.selection_be_md5_file if not platform.system() == "Windows": Loading @@ -746,3 +838,55 @@ def pytest_configure(config): testconfig.MD5_REF_DICT = { line.split()[0]: line.split()[1] for line in f.readlines() } @pytest.fixture(scope="session") def props_to_record(request, get_mld, get_ssnr, get_odg) -> str: props = ["MAXIMUM ABS DIFF"] if get_mld: props.append("MLD") if get_ssnr: props.append("SSNR") if get_odg: props.append("ODG") return props def parse_properties(text_to_parse: str, output_differs: bool, props_to_record: list): """ Record the given properties in the report by parsing their values from the text. """ props = dict() for prop in props_to_record: if prop == "MLD": mld = float(re.search(MLD_PATTERN, text_to_parse).groups(1)[0]) props[prop] = mld elif prop == "MAXIMUM ABS DIFF": max_diff = 0 if output_differs: if (match := re.search(MAX_DIFF_PATTERN, text_to_parse)) is not None: max_diff = match.groups(1)[0] else: raise MaxDiffPatternNotFound() props[prop] = max_diff elif prop == "SSNR": ssnrs = re.findall(SSNR_PATTERN, text_to_parse) min_ssnr = min(ssnrs) min_ssnr_channel = ssnrs.index(min_ssnr) props["MIN_SSNR"] = min_ssnr props["MIN_SSNR_CHANNEL"] = min_ssnr_channel elif prop == "ODG": odgs = re.findall(ODG_PATTERN, text_to_parse) min_odg = min(odgs) min_odg_channel = odgs.index(min_odg) props["MIN_ODG"] = min_odg props["MIN_ODG_CHANNEL"] = min_odg_channel return props class MaxDiffPatternNotFound(Exception): pass tests/constants.py 0 → 100644 +12 −0 Original line number Diff line number Diff line import pathlib HERE = pathlib.Path(__file__).parent.absolute() SCRIPTS_DIR = HERE.parent.joinpath("scripts") TESTV_DIR = SCRIPTS_DIR.joinpath("testv") # regex patterns for parsing the output from cmp_pcm -> mainly for BASOP ci MLD_PATTERN = r"MLD: ([\d\.]*)" MAX_DIFF_PATTERN = r"MAXIMUM ABS DIFF: (\d*)" ODG_PATTERN_PQEVALAUDIO = r"Objective Difference Grade: (-*\d*\.\d*)" ODG_PATTERN = r"ODG: (-*\d*\.\d*)" SSNR_PATTERN = r"Channel \d* SSNR: (nan|[+-]*inf|[-*\d\.]*)" Loading
scripts/binauralRenderer_interface/generate_crend_ivas_tables_from_sofa.c +10 −10 Original line number Diff line number Diff line Loading @@ -1282,7 +1282,7 @@ int generate_crend_ivas_tables_from_sofa( const char *file_path, ConfigReader *c ivas_get_hrtf_lens( ivas_hrtf, &hrtf_data_flt, frame_len ); if ( sample_rates[indSR] != DEFAULT_SAMPLERATE ) { hrtf_data.latency_s = latency_48k_optim; hrtf_data_flt.latency_s = latency_48k_optim; } if ( ( hrtf_data_flt.num_iterations[0][0] > ( frame_len_ms == 5 ? NUM_ITERATIONS_TO_ALLOW_OPTIM_5_MS : NUM_ITERATIONS_TO_ALLOW_OPTIM_20_MS ) ) && ( cfgReader->optimize == 1 ) ) Loading Loading @@ -1310,7 +1310,7 @@ int generate_crend_ivas_tables_from_sofa( const char *file_path, ConfigReader *c if ( sample_rates[indSR] == DEFAULT_SAMPLERATE ) { latency_48k_optim = hrtf_data.latency_s; latency_48k_optim = hrtf_data_flt.latency_s; } hrtf_data.index_frequency_max_diffuse = hrtf_data_flt.index_frequency_max_diffuse; Loading Loading @@ -2481,7 +2481,7 @@ void update_c_file( HRTFS_DATA *hrtf, struct ivas_layout_config lscfg, const int fprintf( fp, "\n};" ); /* float *pOut_to_bin_diffuse_re[BINAURAL_CHANNELS];*/ if ( hrtf->pOut_to_bin_diffuse_re[0] == NULL ) if ( hrtf->pOut_to_bin_diffuse_re_dyn[0] == NULL ) { fprintf( fp, "\nconst Word16 *%s_%s_coeff_diffuse_re_%2dkHz[BINAURAL_CHANNELS]={NULL,NULL};", DECLARATION_NAME, lscfg.name, samplerate / 1000 ); } Loading @@ -2495,7 +2495,7 @@ void update_c_file( HRTFS_DATA *hrtf, struct ivas_layout_config lscfg, const int } /* float *pOut_to_bin_diffuse_im[BINAURAL_CHANNELS];*/ if ( hrtf->pOut_to_bin_diffuse_im[0] == NULL ) if ( hrtf->pOut_to_bin_diffuse_im_dyn[0] == NULL ) { fprintf( fp, "\nconst Word16 *%s_%s_coeff_diffuse_im_%2dkHz[BINAURAL_CHANNELS]={NULL,NULL};", DECLARATION_NAME, lscfg.name, samplerate / 1000 ); } Loading Loading @@ -2654,7 +2654,7 @@ void update_h_file( HRTFS_DATA *hrtf, struct ivas_layout_config lscfg, const int fprintf( fp, "\nextern const Word16 %s_%s_inv_diffuse_weight_%2dkHz[BINAURAL_CHANNELS][%s];", DECLARATION_NAME, lscfg.name, samplerate / 1000, lscfg.output_config_num_channel_name ); /* uint16_t *pIndex_frequency_max_diffuse[BINAURAL_CHANNELS];*/ if ( hrtf->pIndex_frequency_max_diffuse[0] == NULL ) if ( hrtf->pIndex_frequency_max_diffuse_dyn[0] == NULL ) { fprintf( fp, "\nextern const Word16 *%s_%s_pIndex_frequency_max_diffuse_%2dkHz[BINAURAL_CHANNELS];", DECLARATION_NAME, lscfg.name, samplerate / 1000 ); } Loading @@ -2669,7 +2669,7 @@ void update_h_file( HRTFS_DATA *hrtf, struct ivas_layout_config lscfg, const int fprintf( fp, "\nextern const Word16 %s_%s_coeff_im_%2dkHz[%s][BINAURAL_CHANNELS][%u];", DECLARATION_NAME, lscfg.name, samplerate / 1000, lscfg.output_config_num_channel_name, maxTotalNumFreqSampPerIterations ); /* float *pOut_to_bin_diffuse_re_fx[BINAURAL_CHANNELS];*/ if ( hrtf->pOut_to_bin_diffuse_re[0] == NULL ) if ( hrtf->pOut_to_bin_diffuse_re_dyn[0] == NULL ) { fprintf( fp, "\nextern Word16 *%s_%s_coeff_diffuse_re_%2dkHz[BINAURAL_CHANNELS];", DECLARATION_NAME, lscfg.name, samplerate / 1000 ); } Loading @@ -2679,7 +2679,7 @@ void update_h_file( HRTFS_DATA *hrtf, struct ivas_layout_config lscfg, const int } /* float *pOut_to_bin_diffuse_im_fx[BINAURAL_CHANNELS];*/ if ( hrtf->pOut_to_bin_diffuse_im[0] == NULL ) if ( hrtf->pOut_to_bin_diffuse_im_dyn[0] == NULL ) { fprintf( fp, "\nextern Word16 *%s_%s_coeff_diffuse_im_%2dkHz[BINAURAL_CHANNELS];", DECLARATION_NAME, lscfg.name, samplerate / 1000 ); } Loading Loading @@ -2855,7 +2855,7 @@ ivas_error make_fx_be( HRTFS_DATA_flt *hrtf_flt, HRTFS_DATA *hrtf, struct ivas_l maxDiff = 0; if ( hrtf->latency_s > 0 ) if ( hrtf_flt->latency_s > 0 ) { hrtf->factor_Q_latency_s = (int16_t) floorf( 31.f - logf( hrtf_flt->latency_s ) / logf( 2.f ) ); } Loading @@ -2871,7 +2871,7 @@ ivas_error make_fx_be( HRTFS_DATA_flt *hrtf_flt, HRTFS_DATA *hrtf, struct ivas_l tmp = hrtf_flt->latency_s; hrtf->latency_s = float2int32_t( hrtf_flt->latency_s, hrtf->factor_Q_latency_s ); hrtf_flt->latency_s = (float) hrtf->latency_s * powf( 2.f, -1.f * (float) hrtf->factor_Q_latency_s ); maxDiff = fabsf( hrtf->latency_s - tmp ); maxDiff = fabsf( hrtf_flt->latency_s - tmp ); maxVal = 0; for ( iIR = 0; iIR < hrtf_flt->max_num_ir; iIR++ ) Loading Loading @@ -3341,7 +3341,7 @@ ivas_error write_binary_file( HRTFS_DATA *hrtf, struct ivas_layout_config lscfg, } /*---------------------------------------------------------------------* * make_fx_be(); * make_reverb_fx_be(); *---------------------------------------------------------------------*/ ivas_error make_reverb_fx_be( float *pEner_l, float *pEner_r, float *pCoherence, const int32_t samplerate, int16_t *factorQ ) { Loading
scripts/binauralRenderer_interface/ivas_crend_binaural_filter_design.h +1 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ #define IVAS_SOFA_MAX_VAL_I ( 1 ) #define IVAS_MAX_HRTF_LATENCY_MS ( 0.1 ) /* max hrtf latency */ #define GAIN_LFE 1.88364911f /* Gain applied to LFE during renderering */ typedef struct ivas_hrtfs_structure_flt { Loading
tests/cmp_pcm.py 100755 → 100644 +92 −17 Original line number Diff line number Diff line Loading @@ -3,6 +3,11 @@ import argparse import os import sys import tempfile import re import subprocess from pathlib import Path from typing import Optional THIS_PATH = os.path.join(os.getcwd(), __file__) sys.path.append(os.path.join(os.path.dirname(THIS_PATH), "../scripts")) Loading @@ -10,9 +15,21 @@ sys.path.append(os.path.join(os.path.dirname(THIS_PATH), "../scripts")) import numpy as np import pyaudio3dtools import pyivastest def cmp_pcm(file1, file2, out_config, fs, get_mld=False, mld_lim=0, abs_tol=0) -> (int, str): from .constants import ODG_PATTERN_PQEVALAUDIO def cmp_pcm( ref_file, cmp_file, out_config, fs, get_mld=False, allow_differing_lengths=False, mld_lim=0, abs_tol=0, get_ssnr=False, get_odg=False, ) -> (int, str): """ Compare 2 PCM files for bitexactness """ Loading @@ -20,36 +37,52 @@ def cmp_pcm(file1, file2, out_config, fs, get_mld=False, mld_lim=0, abs_tol=0) - print("=====================") out_config = "MONO" if out_config == "" else out_config if out_config.upper() not in pyivastest.constants.OC_TO_NCHANNELS: out_config_in_file_names = os.path.splitext(os.path.basename(out_config))[0] # out_config may be a string or a Path. Wrap in str() to avoid error in case it is a Path. if str(out_config).upper() not in pyivastest.constants.OC_TO_NCHANNELS: nchannels = ( pyivastest.IvasScriptsCommon.IvasScript.get_n_channels_from_ls_layout( out_config ) ) else: out_config_in_file_names = out_config nchannels = pyivastest.constants.OC_TO_NCHANNELS[out_config.upper()] s1, _ = pyaudio3dtools.audiofile.readfile(file1, nchannels, fs, outdtype=np.int16) s2, _ = pyaudio3dtools.audiofile.readfile(file2, nchannels, fs, outdtype=np.int16) s1, _ = pyaudio3dtools.audiofile.readfile( ref_file, nchannels, fs, outdtype=np.int16 ) s2, _ = pyaudio3dtools.audiofile.readfile( cmp_file, nchannels, fs, outdtype=np.int16 ) nchannels = s1.shape[ 1 ] # In case of wav input, override the nchannels with the one from the wav header # In case of wav input, override the nchannels with the one from the wav header nchannels = s1.shape[1] if s1.shape != s2.shape: if allow_differing_lengths: # to allow for MLD comparison, shorten longer file min_len = min(s1.shape[0], s2.shape[0]) s1 = s1[:min_len, :] s2 = s2[:min_len, :] elif s1.shape != s2.shape: print( f"file size in samples: file 1 = {s1.shape[0]},", f"file 2 = {s2.shape[0]}", ) return 1, "FAIL: File lengths differ" reason = "FAIL: File lengths differ. MAXIMUM ABS DIFF: None" if get_mld: reason += " - MLD: None" return 1, reason cmp_result = pyaudio3dtools.audioarray.compare( s1, s2, fs, per_frame=False, get_mld=get_mld s1, s2, fs, per_frame=False, get_mld=get_mld, get_ssnr=get_ssnr, ssnr_thresh_low=-50, ) output_differs = 0 reason = "SUCCESS: Files are bitexact" Loading @@ -75,13 +108,55 @@ def cmp_pcm(file1, file2, out_config, fs, get_mld=False, mld_lim=0, abs_tol=0) - else: reason += f" > {mld_lim}" if get_ssnr: reason += " - " for i, s in enumerate(cmp_result["SSNR"], start=1): msg = f"Channel {i} SSNR: {s}" reason += msg + " - " if get_odg: for n in range(nchannels): pqeval_output = pqevalaudio_wrapper(s1[:, n], s2[:, n], fs) match_odg = re.search(ODG_PATTERN_PQEVALAUDIO, pqeval_output) odg = float(match_odg.groups()[0]) msg = f"Channel {n} ODG: {odg}" reason += " - " + msg print(msg) return output_differs, reason def pqevalaudio_wrapper( ref_sig: np.ndarray, eval_sig: np.ndarray, fs: int, ) -> str: with tempfile.TemporaryDirectory() as tmp_dir: tmp_dir = Path(tmp_dir) tmp_file_ref = str(tmp_dir.joinpath("ref.wav")) tmp_file_eval = str(tmp_dir.joinpath("eval.wav")) # PQevalAudio neeeds 48 kHz sampling rate r48 = np.clip( pyaudio3dtools.audioarray.resample(ref_sig.astype(float), fs, 48000), -32768, 32767 ).astype(np.int16) t48 = np.clip( pyaudio3dtools.audioarray.resample(eval_sig.astype(float), fs, 48000), -32768, 32767 ).astype(np.int16) pyaudio3dtools.audiofile.writefile(tmp_file_ref, r48, 48000) pyaudio3dtools.audiofile.writefile(tmp_file_eval, t48, 48000) cmd = ["PQevalAudio", tmp_file_ref, tmp_file_eval] result = subprocess.run(cmd, capture_output=True) if result.returncode != 0: raise RuntimeError(f"Error running PQevalaudio: {result.stderr}") return result.stdout.decode("utf8") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("file1", type=str) parser.add_argument("file2", type=str) parser.add_argument("ref_file", type=str) parser.add_argument("cmp_file", type=str) parser.add_argument( "-o", "--out_config", Loading
tests/conftest.py +157 −13 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ Pytest customization (configuration and fixtures) for the IVAS codec test suite. import logging import os import re from tests import testconfig import pytest import platform Loading @@ -41,6 +42,7 @@ import textwrap from pathlib import Path from subprocess import TimeoutExpired, run from typing import Optional, Union from .constants import MLD_PATTERN, MAX_DIFF_PATTERN, SSNR_PATTERN, ODG_PATTERN logger = logging.getLogger(__name__) USE_LOGGER_FOR_DBG = False # current tests do not make use of the logger feature Loading Loading @@ -128,6 +130,7 @@ def pytest_addoption(parser): parser.addoption( "--param_file", action="store", type=Path, help="If specified, use given param file in test_param_file.", ) Loading Loading @@ -170,6 +173,19 @@ def pytest_addoption(parser): help="MLD limit for comparison (default: 0)", default="0", ) parser.addoption( "--ssnr", action="store_true", help="Compute Segmental SNR (SSNR) between ref and dut output instead of just comparing for bitexactness", ) parser.addoption( "--odg", action="store_true", help="Get Objective Difference Grade for both conditions during comparison and report difference", ) parser.addoption( "--create_ref", action="store_true", Loading @@ -182,6 +198,19 @@ def pytest_addoption(parser): default=False, ) parser.addoption( "--use_ltv", action="store_true", default=False, ) parser.addoption( "--ltv_dir", action="store", type=Path, default=None, ) parser.addoption( "--dut_fr", help="Render frame size for the DUT output.", Loading Loading @@ -241,6 +270,22 @@ def get_mld_lim(request): return float(request.config.getoption("--mld-lim")) @pytest.fixture(scope="session", autouse=True) def get_ssnr(request): """ Return indication to compute ssnr during ref/dut comparison. """ return request.config.option.ssnr @pytest.fixture(scope="session", autouse=True) def get_odg(request): """ Return indication to compute ssnr during ref/dut comparison. """ return request.config.option.odg @pytest.fixture(scope="session") def abs_tol(request) -> int: """ Loading Loading @@ -312,6 +357,7 @@ class EncoderFrontend: pca: Optional[bool] = None, quiet_mode: Optional[bool] = True, add_option_list: Optional[list] = None, run_dir: Optional[Path] = None, ) -> None: command = [self._path] Loading Loading @@ -347,7 +393,11 @@ class EncoderFrontend: try: result = run( command, capture_output=True, check=False, timeout=self.timeout command, capture_output=True, check=False, timeout=self.timeout, cwd=run_dir, ) except TimeoutExpired: pytest.fail(f"{self._type} encoder run timed out after {self.timeout}s.") Loading Loading @@ -485,7 +535,9 @@ class DecoderFrontend: output_path: Path, quiet_mode: Optional[bool] = True, plc_file: Optional[Path] = None, netsim_profile: Optional[Path] = None, add_option_list: Optional[list] = None, run_dir: Optional[Path] = None, ) -> None: command = [self._path] Loading @@ -512,7 +564,7 @@ class DecoderFrontend: eid_command = [eid_path] eid_command.extend(["-fer", "-vbr", "-bs", "g192", "-ep", "g192"]) eid_output_suffix = "." + os.path.basename(plc_file) + ".fer" eid_output_suffix = ".fer" eid_command += [ str(input_bitstream_path), str(plc_file), Loading @@ -521,12 +573,53 @@ class DecoderFrontend: try: if not os.path.exists(str(input_bitstream_path) + eid_output_suffix): result = run(eid_command, check=True) except Exception: pytest.fail("eid-xor operation failed!") result = run(eid_command, check=True, cwd=run_dir) except Exception as e: pytest.fail(f"eid-xor operation failed! - {e}") input_bitstream_path += eid_output_suffix if netsim_profile is not None: system = platform.system() # TODO: centralize this in a utils file if system == "Windows": netsim_path = "./scripts/tools/Win32/networkSimulator_g192.exe" elif system == "Linux": netsim_path = "./scripts/tools/Linux/networkSimulator_g192" elif system == "Darwin": netsim_path = "./scripts/tools/Darwin/networkSimulator_g192" else: raise ValueError(f'Wrong system "{system}"!') if not os.path.isfile(netsim_path): raise FileNotFoundError( f"network simulator binary {netsim_path} not found!\n" ) netsim_bitstream_path = input_bitstream_path.with_suffix(".netsimout") tracefile_path = input_bitstream_path.with_suffix(".netsimtrace") # TODO: need to check if the "1" works with every profile netsim_command = [ netsim_path, netsim_profile, input_bitstream_path, netsim_bitstream_path, tracefile_path, "1", ] print(netsim_command) try: run(netsim_command, check=True, cwd=run_dir) except Exception as e: pytest.fail(f"netsim operation failed! - {e}") input_bitstream_path = netsim_bitstream_path voip_opt = ["-voip"] if add_option_list is None: add_option_list = voip_opt elif "-voip" not in add_option_list: add_option_list.extend(voip_opt) if add_option_list is not None: command.extend(add_option_list) Loading @@ -545,7 +638,11 @@ class DecoderFrontend: try: result = run( command, capture_output=True, check=False, timeout=self.timeout command, capture_output=True, check=False, timeout=self.timeout, cwd=run_dir, ) except TimeoutExpired: pytest.fail(f"{self._type} decoder run timed out after {self.timeout}s.") Loading Loading @@ -727,15 +824,10 @@ def decoder_only(request) -> bool: def pytest_configure(config): config.addinivalue_line("markers", "serial: mark test to run only in serial") config.addinivalue_line( "markers", "create_ref: mark test capable of producing references" ) config.addinivalue_line( "markers", "create_ref_part2: reference creation test that depends on create_ref references", ) if config.option.param_file: testconfig.PARAM_FILE = config.option.param_file if config.option.use_ltv: testconfig.use_ltv = True if config.option.selection_be_md5_file: md5_file_path = config.option.selection_be_md5_file if not platform.system() == "Windows": Loading @@ -746,3 +838,55 @@ def pytest_configure(config): testconfig.MD5_REF_DICT = { line.split()[0]: line.split()[1] for line in f.readlines() } @pytest.fixture(scope="session") def props_to_record(request, get_mld, get_ssnr, get_odg) -> str: props = ["MAXIMUM ABS DIFF"] if get_mld: props.append("MLD") if get_ssnr: props.append("SSNR") if get_odg: props.append("ODG") return props def parse_properties(text_to_parse: str, output_differs: bool, props_to_record: list): """ Record the given properties in the report by parsing their values from the text. """ props = dict() for prop in props_to_record: if prop == "MLD": mld = float(re.search(MLD_PATTERN, text_to_parse).groups(1)[0]) props[prop] = mld elif prop == "MAXIMUM ABS DIFF": max_diff = 0 if output_differs: if (match := re.search(MAX_DIFF_PATTERN, text_to_parse)) is not None: max_diff = match.groups(1)[0] else: raise MaxDiffPatternNotFound() props[prop] = max_diff elif prop == "SSNR": ssnrs = re.findall(SSNR_PATTERN, text_to_parse) min_ssnr = min(ssnrs) min_ssnr_channel = ssnrs.index(min_ssnr) props["MIN_SSNR"] = min_ssnr props["MIN_SSNR_CHANNEL"] = min_ssnr_channel elif prop == "ODG": odgs = re.findall(ODG_PATTERN, text_to_parse) min_odg = min(odgs) min_odg_channel = odgs.index(min_odg) props["MIN_ODG"] = min_odg props["MIN_ODG_CHANNEL"] = min_odg_channel return props class MaxDiffPatternNotFound(Exception): pass
tests/constants.py 0 → 100644 +12 −0 Original line number Diff line number Diff line import pathlib HERE = pathlib.Path(__file__).parent.absolute() SCRIPTS_DIR = HERE.parent.joinpath("scripts") TESTV_DIR = SCRIPTS_DIR.joinpath("testv") # regex patterns for parsing the output from cmp_pcm -> mainly for BASOP ci MLD_PATTERN = r"MLD: ([\d\.]*)" MAX_DIFF_PATTERN = r"MAXIMUM ABS DIFF: (\d*)" ODG_PATTERN_PQEVALAUDIO = r"Objective Difference Grade: (-*\d*\.\d*)" ODG_PATTERN = r"ODG: (-*\d*\.\d*)" SSNR_PATTERN = r"Channel \d* SSNR: (nan|[+-]*inf|[-*\d\.]*)"