diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index d4e4f91430bed50d2f1a45ee7154cbe172b44434..3ac7f16b2685783247344d891db7bc637c0a084f 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -45,6 +45,7 @@ #include #ifdef DEBUGGING #include "debug.h" +#include #endif #include "wmc_auto.h" diff --git a/lib_enc/ivas_core_pre_proc_front.c b/lib_enc/ivas_core_pre_proc_front.c index 97d6643c08572d5c06888dfa9db71c039b6ab06d..36da36c12855342de02d62add0867dbba2713dfb 100644 --- a/lib_enc/ivas_core_pre_proc_front.c +++ b/lib_enc/ivas_core_pre_proc_front.c @@ -34,6 +34,7 @@ #include "options.h" #ifdef DEBUGGING #include "debug.h" +#include #endif #include "cnst.h" #include "ivas_cnst.h" @@ -220,6 +221,15 @@ ivas_error pre_proc_front_ivas( } } +#ifdef DEBUG_MODE_INFO + if ( !( hCPE->hStereoTD != NULL && n > 0 ) ) + { + int16_t tmp_dmx_in[L_FRAME48k]; + mvr2s( signal_in - NS2SA( st->input_Fs, ACELP_LOOK_NS ), tmp_dmx_in, input_frame ); + dbgwrite( tmp_dmx_in, sizeof( int16_t ), input_frame, 1, strcat( fname( debug_dir, "ivas_input_dmx", 0, n + 1, ENC ), ".pcm" ) ); + } +#endif + lMemRecalc_12k8 = 0; lMemRecalc = 0; if ( element_mode == IVAS_CPE_TD || element_mode == IVAS_CPE_MDCT ) diff --git a/scripts/pyaudio3dtools/audioarray.py b/scripts/pyaudio3dtools/audioarray.py index 1ed31dff4da773915a04a018a4690c94d59398c0..eff1beb634e4b2fb11f5a8c3678dcf361850bc67 100644 --- a/scripts/pyaudio3dtools/audioarray.py +++ b/scripts/pyaudio3dtools/audioarray.py @@ -426,8 +426,9 @@ def compare( raise NotImplementedError( f"wav-diff tool not available for {curr_platform}" ) + search_path = toolsdir.joinpath(curr_platform.replace("Windows", "Win32")) - wdiff = search_path.joinpath("wav-diff") + wdiff = search_path.joinpath("wav-diff").with_suffix(".exe" if curr_platform == "Windows" else "") if not wdiff.exists(): wdiff = shutil.which("wav-diff") diff --git a/tests/cmp_pcm.py b/tests/cmp_pcm.py index 17a8803cae5a730a9159b8f823619642318a49d8..8bb4dd92f43841e827c103a4fd47d46a7ee00dc2 100755 --- a/tests/cmp_pcm.py +++ b/tests/cmp_pcm.py @@ -26,7 +26,7 @@ except ImportError: def cmp_pcm( ref_file, cmp_file, - out_config, + nchannels: int, fs, get_mld=False, allow_differing_lengths=False, @@ -39,23 +39,18 @@ def cmp_pcm( odg_ref=None, ref_jbm_tf: Optional[Path] = None, cut_jbm_tf: Optional[Path] = None, + quiet: Optional[bool] = False, ) -> tuple[int, str]: """ Compare 2 PCM files for bitexactness """ - print("Cmp PCM Report") - print("=====================") - out_config = "MONO" if out_config == "" else out_config - # 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: - nchannels = pyivastest.constants.OC_TO_NCHANNELS[out_config.upper()] + output_target = sys.stdout + if quiet: + output_target = open(os.devnull, "w") + + print("Cmp PCM Report", file=output_target) + print("=====================", file=output_target) s1, fs1 = pyaudio3dtools.audiofile.readfile( ref_file, nchannels, fs, outdtype=np.int16 @@ -85,6 +80,7 @@ def cmp_pcm( print( f"file size in samples: file 1 = {s1.shape[0]},", f"file 2 = {s2.shape[0]}", + file=output_target, ) reason = "FAIL: File lengths differ. MAXIMUM ABS DIFF: None" if get_mld: @@ -95,7 +91,7 @@ def cmp_pcm( cmp_result = pyaudio3dtools.audioarray.compare( s1, s2, - fs, + fs * 1000, per_frame=False, get_mld=get_mld, get_ssnr=get_ssnr, @@ -113,8 +109,8 @@ def cmp_pcm( elif not cmp_result["bitexact"]: diff_msg = f"MAXIMUM ABS DIFF ==> {cmp_result['max_abs_diff']} at sample num {cmp_result['max_abs_diff_pos_sample']} (assuming {nchannels} channels)" first_msg = f"First diff found at sample num {cmp_result['first_diff_pos_sample']} in channel {cmp_result['first_diff_pos_channel']}, frame {cmp_result['first_diff_pos_frame']} (assuming {nchannels} channels, {fs} sampling rate)" - print(diff_msg) - print(first_msg) + print(diff_msg, file=output_target) + print(first_msg, file=output_target) reason = f"Non-BE - MAXIMUM ABS DIFF: {cmp_result['max_abs_diff']}" output_differs = 1 @@ -122,7 +118,7 @@ def cmp_pcm( if get_mld: mld_msg = f"MLD: {cmp_result['MLD']}" reason += " - " + mld_msg - print(mld_msg) + print(mld_msg, file=output_target) if cmp_result["MLD"] <= mld_lim: output_differs = 0 @@ -171,7 +167,10 @@ def cmp_pcm( msg = f"ODG: {odg}" reason += " - " + msg - print(msg) + print(msg, file=output_target) + + if quiet: + output_target.close() return output_differs, reason @@ -214,6 +213,21 @@ def pqevalaudio_wrapper( return result.stdout.decode("utf8") +def out_config_2_nchannels(out_config): + out_config = "MONO" if out_config == "" else out_config + # 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: + nchannels = pyivastest.constants.OC_TO_NCHANNELS[out_config.upper()] + + return nchannels + + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("ref_file", type=str) @@ -231,8 +245,11 @@ if __name__ == "__main__": parser.add_argument("--get_odg", action="store_true") parser.add_argument("--get_ssnr", action="store_true") parser.add_argument("--allow_differing_lengths", action="store_true") - args = parser.parse_args() + parser.add_argument("--quiet", action="store_true") + args = vars(parser.parse_args()) + + args["nchannels"] = out_config_2_nchannels(args.pop("out_config")) - result, msg = cmp_pcm(**vars(args)) + result, msg = cmp_pcm(**args) print(msg) sys.exit(result) diff --git a/tests/codec_be_on_mr_nonselection/test_param_file.py b/tests/codec_be_on_mr_nonselection/test_param_file.py index 5a9c86675ce8f71898128da18ee735c4828bb495..98c54b7cdb9d25c54256cfe9e2f66490bb57a5b9 100644 --- a/tests/codec_be_on_mr_nonselection/test_param_file.py +++ b/tests/codec_be_on_mr_nonselection/test_param_file.py @@ -39,14 +39,19 @@ from pathlib import Path from subprocess import run import pytest import re +import sys import numpy as np -from tests.cmp_pcm import cmp_pcm +THIS_PATH = os.path.join(os.getcwd(), __file__) +sys.path.append(os.path.join(os.path.dirname(THIS_PATH), "../../scripts")) + +from tests.cmp_pcm import cmp_pcm, out_config_2_nchannels from tests.cmp_stats_files import cmp_stats_files from tests.conftest import ( DecoderFrontend, EncoderFrontend, parse_properties, + compare_dmx_signals, log_dbg_msg, ) from tests.testconfig import PARAM_FILE @@ -55,6 +60,9 @@ from tests.constants import ( MAX_ENC_STATS_DIFF, SCRIPTS_DIR, MAX_ENC_DIFF, + DMX_DIFF, + DMX_MLD, + DMX_SSNR, ) from tests.renderer.utils import check_and_makedir, binauralize_input_and_output @@ -210,6 +218,7 @@ def test_param_file_tests( get_enc_stats, get_odg, compare_to_input, + compare_enc_dmx, ): enc_opts, dec_opts, sim_opts, eid_opts = param_file_test_dict[test_tag] @@ -240,6 +249,7 @@ def test_param_file_tests( get_enc_stats, get_odg, compare_to_input, + compare_enc_dmx, ) @@ -270,6 +280,7 @@ def run_test( get_enc_stats, get_odg, compare_to_input, + compare_enc_dmx, ): # If compare_to_input is set, only run pass-through test cases if compare_to_input: @@ -331,6 +342,7 @@ def run_test( enc_split, update_ref, get_enc_stats, + compare_enc_dmx, ) # compare binary files extracted from the encoder @@ -492,6 +504,20 @@ def run_test( ) if update_ref in [0, 2]: + if compare_enc_dmx: + dut_dmx_files = sorted( + Path(f"{dut_base_path}/param_file/enc/{bitstream_file}").parent.glob( + f"{Path(bitstream_file).stem}.dmx.ch*.pcm" + ) + ) + ref_dmx_files = [ + str(f).replace(dut_base_path, reference_path) for f in dut_dmx_files + ] + + prop_results = compare_dmx_signals(ref_dmx_files, dut_dmx_files, in_sr) + for prop, val in prop_results.items(): + dut_decoder_frontend.record_property(prop, val) + # Output file names for comparison dut_output_file = f"{dut_base_path}/param_file/dec/{output_file}" ref_output_file = f"{reference_path}/param_file/dec/{output_file}" @@ -561,7 +587,7 @@ def run_test( output_differs, reason = cmp_pcm( ref_file, dut_output_file, - output_config, + out_config_2_nchannels(output_config), fs, get_mld=get_mld, mld_lim=get_mld_lim, @@ -662,6 +688,7 @@ def encode( enc_opts_list, update_ref, get_enc_stats=False, + compare_enc_dmx=False, ): """ Call REF and/or DUT encoder. @@ -692,6 +719,7 @@ def encode( ref_out_file, add_option_list=enc_opts_list, stats_file=ref_stats_file, + compare_enc_dmx=compare_enc_dmx, ) if update_ref in [0, 2]: @@ -705,6 +733,7 @@ def encode( dut_out_file, add_option_list=enc_opts_list, stats_file=dut_stats_file, + compare_enc_dmx=compare_enc_dmx, ) diff --git a/tests/codec_be_on_mr_nonselection/test_sba.py b/tests/codec_be_on_mr_nonselection/test_sba.py index da3909156745db1891bd2844bac3a7e3d5df9879..a0c72b64f020ba9f4da4cac630035b496a86a886 100644 --- a/tests/codec_be_on_mr_nonselection/test_sba.py +++ b/tests/codec_be_on_mr_nonselection/test_sba.py @@ -33,14 +33,18 @@ __doc__ = """ The outputs are compared with C generated references. """ -import errno import os import pytest from cut_bs import cut_from_start +from pathlib import Path from tests.cmp_pcm import cmp_pcm -from tests.conftest import DecoderFrontend, EncoderFrontend -from ..conftest import parse_properties +from tests.conftest import ( + DecoderFrontend, + EncoderFrontend, + compare_dmx_signals, + parse_properties, +) from ..cmp_stats_files import cmp_stats_files from ..constants import TESTV_DIR, MAX_ENC_FILE_LENGTH_DIFF, MAX_ENC_STATS_DIFF from tests.testconfig import use_ltv @@ -81,7 +85,8 @@ plc_patterns = ["PLperc12mblen5", "PLperc40mblen50", "PLperc42mblen2"] ivas_br_plc = ["13200", "16400", "32000", "64000", "96000", "256000"] # SBA order to IVAS_rend format table -SBA_FORMAT={1: "FOA", 2: "HOA2", 3: "HOA3"} +SBA_FORMAT = {1: "FOA", 2: "HOA2", 3: "HOA3"} + @pytest.mark.parametrize("tag", tag_list) @pytest.mark.parametrize("sampling_rate", sample_rate_list) @@ -109,6 +114,7 @@ def test_pca_enc( get_odg, get_enc_stats, compare_to_input, + compare_enc_dmx, ): pca = True bitrate = "256000" @@ -156,6 +162,7 @@ def test_pca_enc( pca=pca, plc_pattern=plc_pattern, get_enc_stats=get_enc_stats, + compare_enc_dmx=compare_enc_dmx, ) if not encoder_only: @@ -226,8 +233,8 @@ def test_sba_enc_system( get_odg, get_enc_stats, compare_to_input, + compare_enc_dmx, ): - plc_pattern = None pca = False max_bw = "FB" @@ -297,6 +304,7 @@ def test_sba_enc_system( pca=pca, plc_pattern=plc_pattern, get_enc_stats=get_enc_stats, + compare_enc_dmx=compare_enc_dmx, ) if update_ref == 0 and get_enc_stats: @@ -394,6 +402,7 @@ def test_spar_hoa2_enc_system( get_odg, get_enc_stats, compare_to_input, + compare_enc_dmx, ): sampling_rate = "48" pca = False @@ -439,6 +448,7 @@ def test_spar_hoa2_enc_system( pca=pca, plc_pattern=plc_pattern, get_enc_stats=get_enc_stats, + compare_enc_dmx=compare_enc_dmx, ) if update_ref == 0 and get_enc_stats: @@ -536,6 +546,7 @@ def test_spar_hoa3_enc_system( get_odg, get_enc_stats, compare_to_input, + compare_enc_dmx, ): sampling_rate = "48" pca = False @@ -581,6 +592,7 @@ def test_spar_hoa3_enc_system( pca=pca, plc_pattern=plc_pattern, get_enc_stats=get_enc_stats, + compare_enc_dmx=compare_enc_dmx, ) if get_enc_stats: @@ -676,6 +688,7 @@ def test_sba_enc_BWforce_system( get_odg, get_enc_stats, compare_to_input, + compare_enc_dmx, ): sid = 0 plc_pattern = None @@ -731,6 +744,7 @@ def test_sba_enc_BWforce_system( pca=pca, plc_pattern=plc_pattern, get_enc_stats=get_enc_stats, + compare_enc_dmx=compare_enc_dmx, ) if update_ref == 0 and get_enc_stats: @@ -836,6 +850,7 @@ def test_sba_plc_system( get_odg, get_enc_stats, compare_to_input, + compare_enc_dmx, ): sid = 0 pca = False @@ -903,6 +918,7 @@ def test_sba_plc_system( pca=pca, plc_pattern=plc_pattern, get_enc_stats=get_enc_stats, + compare_enc_dmx=compare_enc_dmx, ) if not encoder_only: @@ -964,8 +980,8 @@ def sba_enc( pca=False, plc_pattern=None, get_enc_stats=False, + compare_enc_dmx=False, ): - input_path = f"{test_vector_path}/{tag}.wav" dtx_mode = dtx == "1" @@ -1028,6 +1044,7 @@ def sba_enc( pca=pca, dtx_mode=dtx_mode, stats_file=ref_stats_file, + compare_enc_dmx=compare_enc_dmx, ) if update_ref == 0: @@ -1042,7 +1059,22 @@ def sba_enc( pca=pca, dtx_mode=dtx_mode, stats_file=dut_stats_file, + compare_enc_dmx=compare_enc_dmx, + ) + + if update_ref in [0, 2] and compare_enc_dmx: + dut_dmx_files = sorted( + Path(dut_pkt_file).parent.glob(f"{Path(dut_pkt_file).stem}.dmx.ch*.pcm") + ) + ref_dmx_files = [ + str(f).replace(dut_pkt_dir, ref_pkt_dir) for f in dut_dmx_files + ] + + prop_results = compare_dmx_signals( + ref_dmx_files, dut_dmx_files, int(sampling_rate) ) + for prop, val in prop_results.items(): + dut_encoder_frontend.record_property(prop, val) if sid == 1: # cut .pkt files such that they start with SID frame diff --git a/tests/conftest.py b/tests/conftest.py index 1e1cfa1b1d4f87b94a720cc9cc1c138e6843ed4f..853412cb180d5afc3899956309082e3398e04722 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,17 +36,23 @@ import logging import os import re import json +import shutil from tests import testconfig import pytest import platform import textwrap from pathlib import Path from subprocess import TimeoutExpired, run +from tempfile import NamedTemporaryFile +from shutil import move import tempfile from typing import Optional, Union import numpy as np from .constants import ( # MAX_ENC_DIFF_NAME_PATTERN, + DMX_DIFF, + DMX_MLD, + DMX_SSNR, MAX_ENC_DIFF_PARAM_NAME, MLD_PATTERN, MAX_DIFF_PATTERN, @@ -75,6 +81,8 @@ import sys sys.path.append(str(SCRIPTS_DIR)) import prepare_combined_format_inputs +import pyaudio3dtools +from tests.cmp_pcm import cmp_pcm def log_dbg_msg(message): @@ -275,6 +283,13 @@ def pytest_addoption(parser): default=False, ) + parser.addoption( + "--compare_enc_dmx", + action="store_true", + help="Trigger comparison of dmx signals written out from the encoder. If --update_ref is 1, dmx is written without comparison.", + default=False, + ) + @pytest.fixture(scope="session", autouse=True) def update_ref(request): @@ -360,6 +375,11 @@ def compare_bitstream(request) -> bool: return request.config.option.compare_bitstream +@pytest.fixture(scope="session") +def compare_enc_dmx(request) -> bool: + return request.config.option.compare_enc_dmx + + @pytest.fixture(scope="session") def dut_encoder_path(request) -> str: """ @@ -494,6 +514,7 @@ class EncoderFrontend: add_option_list: Optional[list] = None, run_dir: Optional[Path] = None, stats_file: Optional[Path] = None, + compare_enc_dmx: Optional[bool] = False, ) -> None: command = [str(self._path)] @@ -527,13 +548,13 @@ class EncoderFrontend: cmd_str = textwrap.indent(" ".join(command), prefix="\t") log_dbg_msg(f"{self._type} encoder command:\n{cmd_str}") - try: - with tempfile.TemporaryDirectory() as tmp_dir: - if run_dir is None: - cwd = Path(tmp_dir).absolute() - else: - cwd = Path(run_dir).absolute() + with tempfile.TemporaryDirectory() as tmp_dir: + if run_dir is None: + cwd = Path(tmp_dir).absolute() + else: + cwd = Path(run_dir).absolute() + try: result = run( command, capture_output=True, @@ -541,14 +562,29 @@ class EncoderFrontend: timeout=self.timeout, cwd=cwd, ) + except TimeoutExpired: + pytest.fail( + f"{self._type} encoder run timed out after {self.timeout}s." + ) - if stats_file is not None: - self.extract_enc_stats( - cwd.joinpath("res"), stats_file, input_sampling_rate - ) + if stats_file is not None: + self.extract_enc_stats( + cwd.joinpath("res"), stats_file, input_sampling_rate + ) - except TimeoutExpired: - pytest.fail(f"{self._type} encoder run timed out after {self.timeout}s.") + if compare_enc_dmx: + for dmx_file in cwd.glob("res/ivas_input_dmx.id*.pcm"): + id_match = re.search(r"id(\d+).pcm", dmx_file.name) + if id_match is None: + pytest.fail( + "No dmx signal files found - did you build with DEBUG_MODE_INFO?" + ) + else: + id = id_match.group(1) + move( + dmx_file, + Path(output_bitstream_path).with_suffix(f".dmx.ch{id}.pcm"), + ) self.returncode = result.returncode self.stderr = result.stderr.decode("ascii") @@ -1101,10 +1137,10 @@ def parse_properties(text_to_parse: str, output_differs: bool, props_to_record: props = dict() for prop in props_to_record: - if prop == MLD: + if prop == MLD or prop == DMX_MLD: mld = float(re.search(MLD_PATTERN, text_to_parse).groups(1)[0]) props[prop] = mld - elif prop == MAX_ABS_DIFF: + elif prop == MAX_ABS_DIFF or prop == DMX_DIFF: max_diff = 0 if output_differs: if (match := re.search(MAX_DIFF_PATTERN, text_to_parse)) is not None: @@ -1112,12 +1148,14 @@ def parse_properties(text_to_parse: str, output_differs: bool, props_to_record: else: raise MaxDiffPatternNotFound() props[prop] = max_diff - elif prop == SSNR: + elif prop == SSNR or prop == DMX_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 + + prefix = "MIN" if prop == SSNR else "DMX" + props[f"{prefix}_SSNR"] = min_ssnr + props[f"{prefix}_SSNR_CHANNEL"] = min_ssnr_channel elif prop == ODG: odgs = re.findall(ODG_PATTERN, text_to_parse) min_odg = min(odgs) @@ -1137,3 +1175,36 @@ def parse_properties(text_to_parse: str, output_differs: bool, props_to_record: class MaxDiffPatternNotFound(Exception): pass + + +def compare_dmx_signals(ref_dmx_files, dut_dmx_files, fs) -> dict: + nchannels = len(dut_dmx_files) + with NamedTemporaryFile(suffix=".ref_dmx.pcm") as dmx_file_ref_tmp: + with NamedTemporaryFile(suffix=".dut_dmx.pcm") as dmx_file_dut_tmp: + pyaudio3dtools.audiofile.combinefiles( + ref_dmx_files, + dmx_file_ref_tmp.name, + out_nchans=nchannels, + in_fs=fs, + ) + pyaudio3dtools.audiofile.combinefiles( + dut_dmx_files, + dmx_file_dut_tmp.name, + out_nchans=nchannels, + in_fs=fs, + ) + + dmx_differs, reason = cmp_pcm( + dmx_file_ref_tmp.name, + dmx_file_dut_tmp.name, + nchannels, + fs, + get_mld=True, + get_ssnr=True, + quiet=True, + ) + + dmx_props = [DMX_DIFF, DMX_MLD, DMX_SSNR] + prop_results = parse_properties(reason, dmx_differs, dmx_props) + + return prop_results diff --git a/tests/constants.py b/tests/constants.py index 562035f8f484c1497fd07313e2a59933422e95d8..865a7061a7fdab6d2e21eb14f0e317ad58fd1cd8 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -16,6 +16,9 @@ MAX_ENC_DIFF_PARAM_NAME = "MAXIMUM ENC DIFF PARAM" ENC_CORE_OVL = "ENC_CORE_OVL" MAX_OVL = "MAX_OVL" MIN_OVL = "MIN_OVL" +DMX_DIFF = "DMX MAXIMUM ABS DIFF" +DMX_MLD = "DMX MLD" +DMX_SSNR = "DMX_SSNR" # regex patterns for parsing the output from comparisons -> mainly for BASOP ci MLD_PATTERN = r"MLD: ([\d\.]*)"