diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a34939ac4236af6c4763a946c10f8d7867f3c8e3..8a08c4baf79832f4204b3b0cabd59abcc21e55d7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -273,7 +273,7 @@ basop-ref-compat-stv: extends: - .basop-ci-branch-compat-template variables: - TEST_SUITE: "$SHORT_TEST_SUITE tests/renderer" + TEST_SUITE: "$SHORT_TEST_SUITE tests/renderer_short" TESTCASE_TIMEOUT: $TESTCASE_TIMEOUT_STV_SANITIZERS parallel: matrix: @@ -435,7 +435,7 @@ codec-smoke-test: - if cat smoke_test_output_hrtf.txt | grep -c "failed"; then echo "Smoke test with external hrtf files failed"; ret_val=1; fi - exit $ret_val artifacts: - name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--stage-$CI_JOB_STAGE--results" + name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" expire_in: 1 week when: always paths: @@ -523,7 +523,7 @@ pytest-compare-20ms-and-5ms-rendering: - if [ $exit_code10 -ne 0 ]; then echo "Non-bitexact cases encountered with 10ms rendering!"; exit_code=1; fi - if [ $exit_code -ne 0 ]; then exit $EXIT_CODE_FAIL; fi artifacts: - name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--stage-$CI_JOB_STAGE--results" + name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" expire_in: 1 week when: always expose_as: "pytest 5ms and 10ms vs 20ms results" @@ -824,6 +824,12 @@ ivas-pytest-on-merge-request: stage: compare needs: ["build-codec-linux-cmake", "codec-smoke-test"] timeout: "14 minutes" + parallel: + matrix: + # note: keep in sync with list in $TESTS_DIR_CODEC_BE_ON_MR + - PYTEST_SCRIPT: + - test_param_file.py + - test_sba.py script: - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh - commits_behind_count="$(bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/get-commits-behind-count.sh)" @@ -841,7 +847,7 @@ ivas-pytest-on-merge-request: ### prepare pytest # create references - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v --update_ref 1 + - python3 -m pytest "$TESTS_DIR_CODEC_BE_ON_MR"/"$PYTEST_SCRIPT" -v --update_ref 1 ### Run test using branch scripts and input - if [ $ref_using_main == 1 ]; then git checkout $source_branch_commit_sha; fi @@ -849,7 +855,7 @@ ivas-pytest-on-merge-request: ### run pytest - exit_code=0 - testcase_timeout=60 - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v --html=report.html --self-contained-html --junit-xml=report-junit.xml --testcase_timeout=$testcase_timeout || exit_code=$? + - python3 -m pytest "$TESTS_DIR_CODEC_BE_ON_MR"/"$PYTEST_SCRIPT" -v --html=report.html --self-contained-html --junit-xml=report-junit.xml --testcase_timeout=$testcase_timeout || exit_code=$? - zero_errors=$(cat report-junit.xml | grep -c 'errors="0"') || true - *merge-request-comparison-check @@ -858,7 +864,7 @@ ivas-pytest-on-merge-request: exit_codes: - 123 artifacts: - name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--stage-$CI_JOB_STAGE--results" + name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" expire_in: 1 week when: always paths: @@ -908,7 +914,7 @@ ivas-interop-on-merge-request: exit_codes: - 123 artifacts: - name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--stage-$CI_JOB_STAGE--results" + name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" expire_in: 1 week when: always paths: @@ -960,7 +966,7 @@ evs-pytest-on-merge-request: exit_codes: - 123 artifacts: - name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--stage-$CI_JOB_STAGE--results" + name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" expire_in: 1 week when: always paths: @@ -1056,7 +1062,7 @@ check-first-frame-is-sid: expose_as: "logs-sidstart" expire_in: "5 days" -.lc3plus-ensure-no-code-changes: +lc3plus-ensure-no-code-changes: extends: - .test-job-linux - .rules-merge-request-to-main @@ -1068,7 +1074,7 @@ check-first-frame-is-sid: - ./scripts/lc3plus_lib_setup/get_lc3plus.sh # Ensure git reports no changes - - modified_files=$(git status -s) + - modified_files=$(git status -su lib_lc3plus) - if [[ $modified_files ]]; then printf 'LC3plus codebase was modified!\n\n'"$modified_files"'\n\n'; exit $EXIT_CODE_FAIL; fi check-bitexactness-hrtf-rom-and-file: @@ -1171,7 +1177,6 @@ be-2-evs-windows: # - cd evs_be_test # - python3 ../ci/run_evs_be_test.py - # TODO: do we still need this? # codec-comparison-on-main-push: # extends: @@ -2211,7 +2216,7 @@ sanitizer-test-osba-planar-hoa3-ism4: - if: $MANUAL_PIPELINE_TYPE == "coverage" timeout: 6 hours before_script: - - !reference [.job-linux, before_script] + - !reference [.test-job-linux-needs-testv-dir, before_script] - set -e - 'trap ''echo "Command failed at line $LINENO: $BASH_COMMAND"'' ERR' - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh diff --git a/ci/basop-pages/basop_index.html b/ci/basop-pages/basop_index.html index 5790b6f594dc35aa8e0d32e47b0040bd4f04c38b..77f1220cfafd5df8a3325d82afc2af7f406e485f 100644 --- a/ci/basop-pages/basop_index.html +++ b/ci/basop-pages/basop_index.html @@ -7,9 +7,7 @@

Regression tracking

- +
  • Long term regression
  • Daily long testvector tests

    @@ -29,5 +27,5 @@

    Test Coverage

    {} - + diff --git a/ci/remove_unsupported_testcases.py b/ci/remove_unsupported_testcases.py deleted file mode 100644 index 611452a89e2025daa2ef647ba503657d2b8182f4..0000000000000000000000000000000000000000 --- a/ci/remove_unsupported_testcases.py +++ /dev/null @@ -1,92 +0,0 @@ -__copyright__ = """ -(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, -Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., -Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, -Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -contributors to this repository. All Rights Reserved. - -This software is protected by copyright law and by international treaties. -The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, -Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., -Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, -Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -contributors to this repository retain full ownership rights in their respective contributions in -the software. This notice grants no license of any kind, including but not limited to patent -license, nor is any license granted by implication, estoppel or otherwise. - -Contributors are required to enter into the IVAS codec Public Collaboration agreement before making -contributions. - -This software is provided "AS IS", without any express or implied warranties. The software is in the -development stage. It is intended exclusively for experts who have experience with such software and -solely for the purpose of inspection. All implied warranties of non-infringement, merchantability -and fitness for a particular purpose are hereby disclaimed and excluded. - -Any dispute, controversy or claim arising under or in relation to providing this software shall be -submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in -accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and -the United Nations Convention on Contracts on the International Sales of Goods. -""" - -from pathlib import Path -import argparse - -# Enter tag of testcases to remove here WITHOUT the leading // -TESTCASES = [ - # self_test.prm - # currently still crashing on BASOP main only, ivas-float-update is fine - "Multi-channel 7_1_4 bitrate switching, 48kHz in, 48kHz out, BINAURAL out, HR, JBM Prof 5", - "OMASA 2Dir2TC 4ISM at br sw techs 13.2 to 512 kbps start 80 kbps, 48kHz in, 48kHz out, EXT out", - "OMASA vdir2TC 3ISM at br sw techs 13.2 to 512 kbps start 160 kbps, 48kHz in, 48kHz out, MONO out, JBM Prof 5", - # self_test_ltv.prm - # rtpdump tests (also skipped in pytest code, but for documentation purposes added here as well) - "stereo bitrate switching from 13.2 kbps to 128 kbps, 48kHz in, 48kHz out, DTX on, EXT out, rtpdump", - "4 ISM with metadata bitrate switching from 24.4 kbps to 512 kbps, 48 kHz in, 48 kHz out, DTX on, BINAURAL out, rtpdump, PI data", - "SBA FOA bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, DTX on, BINAURAL out, rtpdump, PI data", - "MASA 2dir 2TC bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, DTX on, BINAURAL out, rtpdump, PI data", - "Multi-channel 5_1 bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data", - "Stereo downmix to bit-exact EVS at 24400 kbps, 48kHz in, 48kHz out, rtpdump", - "EVS at 13.2 kbps, 48kHz in, 48kHz out, STEREO out, rtpdump", - "OMASA 2Dir2TC 3ISM at br sw techs 13.2 to 512 kbps start 160 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data", - "OSBA 2ISM 2OA at bitrate switching 13.2 to 512 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data", - "OMASA vdir2TC 3ISM at br sw techs 13.2 to 512 kbps start 160 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data", - "OMASA vdir2TC 4ISM at br sw techs 13.2 to 512 kbps start 80 kbps, 48kHz in, 48kHz out, HOA3 out", - "OMASA vdir1TC 3ISM at br sw techs 13.2 to 512 kbps start 48 kbps, 48kHz in, 32kHz out, STEREO out, JBM Prof 5", - "OMASA vdir2TC 4ISM at br sw techs 13.2 to 512 kbps start 80 kbps, 48kHz in, 48kHz out, EXT out", - "OMASA vdir2TC 4ISM at br sw techs 13.2 to 512 kbps start 80 kbps, 48kHz in, 48kHz out, FOA out, JBM Prof 5", - "OMASA vdir1TC 3ISM at br sw techs 13.2 to 512 kbps start 48 kbps, 48kHz in, 32kHz out, STEREO out, FER at 10%", -] - - - -def remove_testcases(cfg: Path, testcases: list): - """ - Go through file line by line and copy all testcases except the given ones - """ - with open(cfg, "r") as f: - content_in = f.readlines() - - content_out = list() - copy_flag = True - for line in content_in: - if any([tc in line for tc in testcases]): - copy_flag = False - - if copy_flag: - content_out.append(line) - elif line == "\n": - copy_flag = True - - with open(cfg, "w") as f: - f.write("".join(content_out)) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("cfg_files", nargs="+", type=Path) - args = parser.parse_args() - - testcases = TESTCASES - - for f in args.cfg_files: - remove_testcases(f, testcases) diff --git a/ci/setup_pages.py b/ci/setup_pages.py index b0251a264fcd466b69b1c2f964a6d6dfd084a97c..947526c78d4c97326cad26b5b4bc8452a4627dc0 100755 --- a/ci/setup_pages.py +++ b/ci/setup_pages.py @@ -71,7 +71,7 @@ JOBS_BASOP_REPO = { "complexity-osba-in-binaural_room_ir-out": "OSBA in, BINAURAL_ROOM_IR out", "complexity-stereo-in-stereo-out": "Stereo in, Stereo out", # "timeless" jobs (not complexity) - "coverage-test-on-main-scheduled": "Coverage report", + "ivas-conformance-linux": "Conformance test coverage", "ivas-long-term-job-logs-overview": "Long term logs", } @@ -81,13 +81,10 @@ JOBS_FOR_PROJECT_ID = { } ARTIFACT_FOLDER_4_COVERAGE_JOBS = { - # for float "coverage-test-on-main-scheduled-stv": "coverage_stv", "coverage-test-on-main-scheduled-ltv": "coverage_ltv", "ivas-conformance-linux": "coverage_conformance", "coverage-merge": "coverage-merged", - # for BASOP - "coverage-test-on-main-scheduled": "coverage_enc_dec_rend", } ARTIFACTS = "artifacts.zip" @@ -146,7 +143,6 @@ def create_landing_page(jobs, index_html, project_id): link_html_complexity_text = "\n".join(link_html_complexity) link_html_coverage_text = "\n".join(link_html_coverage) - index_pages_tmpl = index_pages_tmpl.format( link_html_complexity_text, link_html_coverage_text ) diff --git a/lib_com/disclaimer.c b/lib_com/disclaimer.c index 170d58a3fab8bafb115909bda3390722696b10fd..ac95ffb59c2aafa41d21dbf43ba57a03d700d345 100644 --- a/lib_com/disclaimer.c +++ b/lib_com/disclaimer.c @@ -47,10 +47,12 @@ int16_t print_disclaimer( FILE *fPtr ) { fprintf( fPtr, "\n==================================================================================================\n" ); - fprintf( fPtr, " \n IVAS Codec Version IVAS-FL-2.0\n" ); - fprintf( fPtr, " \n" ); + fprintf( fPtr, "\n" ); + fprintf( fPtr, " 3GPP TS26.258 IVAS Codec Version IVAS-FL-3.0 (floating-point C-Code)\n" ); + fprintf( fPtr, "\n" ); fprintf( fPtr, " Based on EVS Codec (Floating Point) 3GPP TS26.443 Nov 04, 2021,\n" ); fprintf( fPtr, " Version 12.14.0 / 13.10.0 / 14.6.0 / 15.4.0 / 16.3.0\n" ); + fprintf( fPtr, "\n" ); fprintf( fPtr, "==================================================================================================\n\n" ); return 0; diff --git a/lib_com/options.h b/lib_com/options.h index 7aa9f1c91547b45b18620de0c0715d221efc4f31..9e2052b43b8b3a7b1414469911054d74ff74e0d6 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -169,6 +169,8 @@ #define DECODER_FORMAT_SWITCHING /* Re-initialize the decoder when the format/subformat of the incoming stream is changed */ #define RTP_SR_CODEC_FRAME_SIZE_IN_TOC_BYTE /* CR for split rendering codec framesize signalling in Toc Byte*/ #define FIX_SPLIT_RENDERING_ON_DECODER_RESTART /* Re-configure split rendering on decoder restart */ +#define RTP_UPDATES_SA4_134 /* Updates to RTP during SA4 134 */ +#define COMPACT_ORIENTATION_PI_DATA /* ################### Start BE switches ################################# */ /* only BE switches wrt selection floating point code */ diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index b8e79fad53158de060083438cabeaac72fe0d7cb..009a158c49ee22d121f172195332d64975bc1439 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -144,11 +144,10 @@ static ivas_error IvasRtpFile_Read( static const char *const PiDataNames[IVAS_PI_MAX_ID] = { "SCENE_ORIENTATION", "DEVICE_ORIENTATION_COMPENSATED", "DEVICE_ORIENTATION_UNCOMPENSATED", "ACOUSTIC_ENVIRONMENT", "AUDIO_DESCRIPTION", "ISM_NUM", "ISM_ID", "ISM_GAIN", "ISM_ORIENTATION", - "ISM_POSITION", "ISM_DISTANCE_ATTENUATION", "ISM_DIRECTIVITY", "DIEGETIC_TYPE", "DYNAMIC_AUDIO_SUPPRESSION_INDICATION", - "AUDIO_FOCUS_INDICATION", "RESERVED15", "PLAYBACK_DEVICE_ORIENTATION", "HEAD_ORIENTATION", "LISTENER_POSITION", + "ISM_POSITION", "ISM_POSITION_COMPACT", "ISM_DISTANCE_ATTENUATION", "ISM_DIRECTIVITY", "DIEGETIC_TYPE", "DYNAMIC_AUDIO_SUPPRESSION_INDICATION", + "AUDIO_FOCUS_INDICATION", "PLAYBACK_DEVICE_ORIENTATION", "HEAD_ORIENTATION", "LISTENER_POSITION", "DYNAMIC_AUDIO_SUPPRESSION_REQUEST", "AUDIO_FOCUS_REQUEST", "PI_LATENCY", "R_ISM_ID", "R_ISM_GAIN", - "R_ISM_ORIENTATION", "R_ISM_POSITION", "R_ISM_DIRECTION", "RESERVED27", "RESERVED28", "RESERVED29", - "RESERVED30", "NO_DATA" + "R_ISM_ORIENTATION", "R_ISM_POSITION", "R_ISM_POSITION_COMPACT", "R_ISM_DIRECTION", "RESERVED27", "RESERVED28", "RESERVED29", "NO_DATA" }; void IVAS_RTP_LogPiData( @@ -335,11 +334,17 @@ void IVAS_RTP_LogPiData( fprintf( f_piDataOut, "\n\t\t}" ); } break; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case IVAS_PI_RESERVED27: + case IVAS_PI_RESERVED28: + case IVAS_PI_RESERVED29: +#else case IVAS_PI_RESERVED15: case IVAS_PI_RESERVED27: case IVAS_PI_RESERVED28: case IVAS_PI_RESERVED29: case IVAS_PI_RESERVED30: +#endif { fprintf( f_piDataOut, "{}" ); } @@ -413,6 +418,22 @@ void IVAS_RTP_LogPiData( fprintf( f_piDataOut, "\n\t\t]" ); } break; +#ifdef RTP_UPDATES_SA4_134 + case IVAS_PI_ISM_POSITION_COMPACT: + { + fprintf( f_piDataOut, "[\n" ); + for ( n = 0; n < cur->data.ismPositionCompact.numObjects; n++ ) + { + if ( n != 0 ) + { + fprintf( f_piDataOut, ",\n" ); + } + fprintf( f_piDataOut, "\t\t\t{\n\t\t\t\t\"x\": %f,\n\t\t\t\t\"y\": %f,\n\t\t\t\t\"z\": %f \n\t\t\t}", cur->data.ismPositionCompact.position[n].x, cur->data.ismPositionCompact.position[n].y, cur->data.ismPositionCompact.position[n].z ); + } + fprintf( f_piDataOut, "\n\t\t]" ); + } + break; +#endif case IVAS_PI_ISM_DISTANCE_ATTENUATION: { fprintf( f_piDataOut, "[\n" ); @@ -491,6 +512,14 @@ void IVAS_RTP_LogPiData( cur->data.ismEditPosition.position.x, cur->data.ismEditPosition.position.y, cur->data.ismEditPosition.position.z ); } break; +#ifdef RTP_UPDATES_SA4_134 + case IVAS_PI_R_ISM_POSITION_COMPACT: + { + fprintf( f_piDataOut, "{\n\t\t\t\"x\": %f,\n\t\t\t\"y\": %f,\n\t\t\t\"z\": %f \n\t\t}", + cur->data.ismEditPositionCompact.position.x, cur->data.ismEditPositionCompact.position.y, cur->data.ismEditPositionCompact.position.z ); + } + break; +#endif #endif case IVAS_PI_R_ISM_DIRECTION: #ifdef REVERSE_ISM_PI_DATA @@ -631,6 +660,20 @@ void IVAS_RTP_WriteExtPiData( } } break; +#ifdef RTP_UPDATES_SA4_134 + case IVAS_PI_ISM_POSITION_COMPACT: + { + for ( i = 0; i < numObj; ++i ) + { + if ( i != 0 ) + { + fprintf( f_piDataOut, "," ); + } + fprintf( f_piDataOut, "%f,%f,%f", cur->data.ismPositionCompact.position[i].x, cur->data.ismPositionCompact.position[i].y, cur->data.ismPositionCompact.position[i].z ); + } + } + break; +#endif case IVAS_PI_ISM_DISTANCE_ATTENUATION: { for ( i = 0; i < numObj; ++i ) @@ -746,16 +789,29 @@ void IVAS_RTP_WriteExtPiData( fprintf( f_piDataOut, "%f,%f,%f", cur->data.ismEditPosition.position.x, cur->data.ismEditPosition.position.y, cur->data.ismEditPosition.position.z ); } break; +#ifdef RTP_UPDATES_SA4_134 + case IVAS_PI_R_ISM_POSITION_COMPACT: + { + fprintf( f_piDataOut, "%f,%f,%f", cur->data.ismEditPositionCompact.position.x, cur->data.ismEditPositionCompact.position.y, cur->data.ismEditPositionCompact.position.z ); + } + break; +#endif case IVAS_PI_R_ISM_DIRECTION: { fprintf( f_piDataOut, "%f,%f", cur->data.ismEditDirection.azimuth, cur->data.ismEditDirection.elevation ); } break; +#ifdef RTP_UPDATES_SA4_134 + case IVAS_PI_RESERVED27: + case IVAS_PI_RESERVED28: + case IVAS_PI_RESERVED29: +#else case IVAS_PI_RESERVED15: case IVAS_PI_RESERVED27: case IVAS_PI_RESERVED28: case IVAS_PI_RESERVED29: case IVAS_PI_RESERVED30: +#endif break; #endif /* RTP_S4_251135_CR26253_0016_REV1 */ } diff --git a/lib_util/ivas_rtp_internal.h b/lib_util/ivas_rtp_internal.h index a550892548f3b0d82815ca935562b13c8a23d306..2ab816fab730714db8e3a0469ef5fddd74765b8f 100644 --- a/lib_util/ivas_rtp_internal.h +++ b/lib_util/ivas_rtp_internal.h @@ -51,6 +51,10 @@ enum MASK_BITS #ifdef REVERSE_ISM_PI_DATA MASK_9BIT = 0x1FF, #endif +#ifdef RTP_UPDATES_SA4_134 + MASK_10BIT = 0x3FF, + MASK_11BIT = 0x7FF, +#endif }; @@ -69,6 +73,13 @@ enum MASK_BITS #define MAX_PI_POSITION_METERS ( 327.68f ) #define FLOAT_FROM_Q15( q15Val ) ( (float) ( q15Val ) / 32768.0f ) +#ifdef RTP_UPDATES_SA4_134 +#define MAX_PI_COMPACT_POSITION_XY_METERS ( 10.24f ) +#define MAX_PI_COMPACT_POSITION_Z_METERS ( 5.12f ) +#define FLOAT_FROM_Q10( q10Val ) ( (float) ( q10Val ) / 1024.0f ) +#define FLOAT_FROM_Q9( q9Val ) ( (float) ( q9Val ) / 512.0f ) +#define FLOAT_FROM_Q7( q7Val ) ( (float) ( q7Val ) / 128.0f ) +#endif extern const float mapDSR[1u << NBITS_DSR]; extern const float mapRT60[1u << NBITS_RT60]; diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 0398d9ce401ff633a1b52d48b5533e0daeb193eb..6a816f36de3bb5ae000ef745aa73a00b90661477 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -1568,6 +1568,7 @@ static ivas_error parsePIData( IVAS_RTP_UNPACK_HANDLE hUnpack, uint32_t rtpTimes { bool PF = true; uint32_t nBytes = *numBytes; + bool isFirstPiSize = true; while ( PF ) { @@ -1585,6 +1586,18 @@ static ivas_error parsePIData( IVAS_RTP_UNPACK_HANDLE hUnpack, uint32_t rtpTimes PM = ( piHeader0 & ( ~MASK_5BIT ) ) & MASK_7BIT; /* PI Marker Bits */ piDataType = ( piHeader0 & MASK_5BIT ); +#ifdef RTP_UPDATES_SA4_134 + do + { + byte = payload->buffer[nBytes++]; + piSize += isFirstPiSize ? ( byte & MASK_5BIT ) : byte; + if ( nBytes >= payload->length ) + { + return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "Underflow during reading piSize" ); + } + isFirstPiSize = false; + } while ( byte == 255 || piSize == 32 ); +#else do { byte = payload->buffer[nBytes++]; @@ -1594,6 +1607,7 @@ static ivas_error parsePIData( IVAS_RTP_UNPACK_HANDLE hUnpack, uint32_t rtpTimes return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "Underflow during reading piSize" ); } } while ( byte == 255 ); +#endif if ( piDataType == IVAS_PI_NO_DATA ) { diff --git a/lib_util/ivas_rtp_pi_data.c b/lib_util/ivas_rtp_pi_data.c index b9b7bcc7e44fbd19ca75570c793acd9e697bc588..10adfc7ba699275bf5892d386be4b309ed62c2f8 100644 --- a/lib_util/ivas_rtp_pi_data.c +++ b/lib_util/ivas_rtp_pi_data.c @@ -79,6 +79,137 @@ static int16_t ivasPayload_convertToQ15( float value ) return (int16_t) ( value ); } +#ifdef RTP_UPDATES_SA4_134 +/*-----------------------------------------------------------------------* + * ivasPayload_convertToQ10() + * + * Convert a float value into a Q10 encoded value. + *-----------------------------------------------------------------------*/ +static int16_t ivasPayload_convertToQ10( float value ) +{ + value = ( value * 1024.0f ); + value = value > +1024.0f ? +1024.0f : value; + value = value < -1024.0f ? -1024.0f : value; + return (int16_t) ( value ); +} + +/*-----------------------------------------------------------------------* + * ivasPayload_convertToQ9() + * + * Convert a float value into a Q9 encoded value. + *-----------------------------------------------------------------------*/ +static int16_t ivasPayload_convertToQ9( float value ) +{ + value = ( value * 512.0f ); + value = value > +512.0f ? +512.0f : value; + value = value < -512.0f ? -512.0f : value; + return (int16_t) ( value ); +} + +#ifdef COMPACT_ORIENTATION_PI_DATA +static uint32_t packQuaternion( IVAS_QUATERNION orientation, uint8_t *buffer ) +{ + uint32_t nBytes = 0; + float q[4], q_max; + uint16_t max_q_idx, n, k; + uint32_t lWord; + + q[0] = orientation.w; + q[1] = orientation.x; + q[2] = orientation.y; + q[3] = orientation.z; + + max_q_idx = 0; + q_max = (float) fabs( q[0] ); + for ( n = 1; n < 4; n++ ) + { + if ( (float) fabs( q[n] ) > q_max ) + { + q_max = (float) fabs( q[n] ); + max_q_idx = n; + } + } + + if ( q[max_q_idx] < 0 ) + { + for ( n = 0; n < 4; n++ ) + { + q[n] = -q[n]; + } + } + lWord = ( (uint32_t) max_q_idx ) << 30; + + k = 1; + for ( n = 0; n < 4; n++ ) + { + if ( n == max_q_idx ) + { + continue; + } + lWord |= ( ( (int16_t) ( ( q[n] + 1 / sqrt( 2.0f ) ) * 1023 / sqrt( 2.0f ) ) & MASK_10BIT ) << ( 30 - k * 10 ) ); + k++; + } + + buffer[nBytes++] = ( lWord >> 24 ) & MASK_8BIT; + buffer[nBytes++] = ( lWord >> 16 ) & MASK_8BIT; + buffer[nBytes++] = ( lWord >> 8 ) & MASK_8BIT; + buffer[nBytes++] = (lWord) &MASK_8BIT; + + return nBytes; +} + + +static ivas_error unpackQuaternion( const uint8_t *buffer, IVAS_QUATERNION *orientation ) +{ + uint32_t i, k, lWord; + uint16_t max_q_idx, tmp; + float q[4], qs; + + lWord = ( (uint32_t) buffer[0] ) << 24; + lWord |= ( (uint32_t) buffer[1] ) << 16; + lWord |= ( (uint32_t) buffer[2] ) << 8; + lWord |= (uint32_t) buffer[3]; + + max_q_idx = ( lWord >> 30 ) & MASK_2BIT; + k = 1; + qs = 0.0f; + for ( i = 0; i < 4; i++ ) + { + if ( i == max_q_idx ) + { + continue; + } + tmp = ( lWord >> ( 30 - k * 10 ) ) & MASK_10BIT; + q[i] = tmp / 1023.0f * sqrtf( 2.0f ) - 1 / sqrtf( 2.0f ); + qs += q[i] * q[i]; + k++; + } + q[max_q_idx] = sqrtf( 1 - qs ); + orientation->w = q[0]; + orientation->x = q[1]; + orientation->y = q[2]; + orientation->z = q[3]; + + return IVAS_ERR_OK; +} + +#else + +/*-----------------------------------------------------------------------* + * ivasPayload_convertToQ7() + * + * Convert a float value into a Q7 encoded value. + *-----------------------------------------------------------------------*/ +static int16_t ivasPayload_convertToQ7( float value ) +{ + value = ( value * 128.0f ); + value = value > +128.0f ? +128.0f : value; + value = value < -128.0f ? -128.0f : value; + return (int16_t) ( value ); +} +#endif +#endif + static ivas_error packUnsupportedData( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) { (void) piData; @@ -152,18 +283,38 @@ static ivas_error packOrientation( const IVAS_PIDATA_GENERIC *piData, uint8_t *b return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect PI ID in Orientation PI data" ); } +#ifdef RTP_UPDATES_SA4_134 + /* Orientation data is 4 bytes, header is 2 bytes */ + if ( maxDataBytes < 4 + 2 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack Orientation PI data" ); + } +#else /* Orientation data is 8 bytes, header is 2 bytes */ if ( maxDataBytes < 8 + 2 ) { return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack Orientation PI data" ); } +#endif buffer[nBytes++] = ( orientation->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ +#ifdef RTP_UPDATES_SA4_134 + buffer[nBytes++] = 4; +#ifdef COMPACT_ORIENTATION_PI_DATA + nBytes += packQuaternion( orientation->orientation, &buffer[nBytes] ); +#else + buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation.w ); + buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation.x ); + buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation.y ); + buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation.z ); +#endif +#else buffer[nBytes++] = 8; nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( orientation->orientation.w ) ); nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( orientation->orientation.x ) ); nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( orientation->orientation.y ) ); nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( orientation->orientation.z ) ); +#endif *nBytesWritten = nBytes; return IVAS_ERR_OK; @@ -186,13 +337,35 @@ static ivas_error packISMOrientation( const IVAS_PIDATA_GENERIC *piData, uint8_t { return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect PI ID in Orientation PI data" ); } +#ifdef RTP_UPDATES_SA4_134 + /* Orientation data is 4 bytes, header is 2 bytes */ + if ( maxDataBytes < 4 * IVAS_MAX_NUM_OBJECTS + 2 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack Orientation PI data" ); + } +#else /* Orientation data is 8 bytes, header is 2 bytes */ if ( maxDataBytes < 8 * IVAS_MAX_NUM_OBJECTS + 2 ) { return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack Orientation PI data" ); } +#endif buffer[nBytes++] = ( orientation->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ +#ifdef RTP_UPDATES_SA4_134 + buffer[nBytes++] = (uint8_t) orientation->numObjects * 4; + for ( n = 0; n < orientation->numObjects; n++ ) + { +#ifdef COMPACT_ORIENTATION_PI_DATA + nBytes += packQuaternion( orientation->orientation[n], &buffer[nBytes] ); +#else + buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation[n].w ); + buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation[n].x ); + buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation[n].y ); + buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation[n].z ); +#endif + } +#else buffer[nBytes++] = (uint8_t) orientation->numObjects * 8; for ( n = 0; n < orientation->numObjects; n++ ) { @@ -201,6 +374,7 @@ static ivas_error packISMOrientation( const IVAS_PIDATA_GENERIC *piData, uint8_t nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( orientation->orientation[n].y ) ); nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( orientation->orientation[n].z ) ); } +#endif *nBytesWritten = nBytes; return IVAS_ERR_OK; } @@ -210,6 +384,23 @@ static ivas_error unpackOrientation( const uint8_t *buffer, uint32_t numDataByte { IVAS_PIDATA_ORIENTATION *orientation = (IVAS_PIDATA_ORIENTATION *) piData; +#ifdef RTP_UPDATES_SA4_134 + /* Orientation data is 4 bytes */ + if ( numDataBytes != 4 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack Orientation PI data" ); + } + + piData->size = sizeof( IVAS_PIDATA_ORIENTATION ); +#ifdef COMPACT_ORIENTATION_PI_DATA + unpackQuaternion( buffer, &( orientation->orientation ) ); +#else + orientation->orientation.w = FLOAT_FROM_Q7( (int8_t) buffer[0] ); + orientation->orientation.x = FLOAT_FROM_Q7( (int8_t) buffer[1] ); + orientation->orientation.y = FLOAT_FROM_Q7( (int8_t) buffer[2] ); + orientation->orientation.z = FLOAT_FROM_Q7( (int8_t) buffer[3] ); +#endif +#else /* Orientation data is 8 bytes */ if ( numDataBytes != 8 ) { @@ -217,11 +408,12 @@ static ivas_error unpackOrientation( const uint8_t *buffer, uint32_t numDataByte } piData->size = sizeof( IVAS_PIDATA_ORIENTATION ); + orientation->orientation.w = FLOAT_FROM_Q15( readInt16( &buffer[0] ) ); orientation->orientation.x = FLOAT_FROM_Q15( readInt16( &buffer[2] ) ); orientation->orientation.y = FLOAT_FROM_Q15( readInt16( &buffer[4] ) ); orientation->orientation.z = FLOAT_FROM_Q15( readInt16( &buffer[6] ) ); - +#endif return IVAS_ERR_OK; } @@ -230,6 +422,29 @@ static ivas_error unpackISMOrientation( const uint8_t *buffer, uint32_t numDataB { IVAS_PIDATA_ISM_ORIENTATION *ism_orientation = (IVAS_PIDATA_ISM_ORIENTATION *) piData; +#ifdef RTP_UPDATES_SA4_134 + /* Orientation data is 4 bytes */ + uint16_t n; + if ( numDataBytes % 4 != 0 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack Orientation PI data" ); + } + + ism_orientation->size = sizeof( IVAS_PIDATA_ISM_ORIENTATION ); + ism_orientation->numObjects = (uint16_t) numDataBytes / 4; + + for ( n = 0; n < ism_orientation->numObjects; n++ ) + { +#ifdef COMPACT_ORIENTATION_PI_DATA + unpackQuaternion( &buffer[4 * n], &( ism_orientation->orientation[n] ) ); +#else + ism_orientation->orientation[n].w = FLOAT_FROM_Q7( (int8_t) buffer[4 * n] ); + ism_orientation->orientation[n].x = FLOAT_FROM_Q7( (int8_t) buffer[4 * n + 1] ); + ism_orientation->orientation[n].y = FLOAT_FROM_Q7( (int8_t) buffer[4 * n + 2] ); + ism_orientation->orientation[n].z = FLOAT_FROM_Q7( (int8_t) buffer[4 * n + 3] ); +#endif + } +#else /* Orientation data is 8 bytes */ uint16_t n; if ( numDataBytes % 8 != 0 ) @@ -247,6 +462,7 @@ static ivas_error unpackISMOrientation( const uint8_t *buffer, uint32_t numDataB ism_orientation->orientation[n].y = FLOAT_FROM_Q15( readInt16( &buffer[8 * n + 4] ) ); ism_orientation->orientation[n].z = FLOAT_FROM_Q15( readInt16( &buffer[8 * n + 6] ) ); } +#endif for ( ; n < IVAS_MAX_NUM_OBJECTS; n++ ) { ism_orientation->orientation[n].w = 0.0f; @@ -614,6 +830,70 @@ static ivas_error unpackListenerPosition( const uint8_t *buffer, uint32_t numDat } #endif +#ifdef RTP_UPDATES_SA4_134 +static ivas_error packPositionCompact( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) +{ + uint32_t nBytes = 0; + uint16_t posX, posY, posZ = 0; + const IVAS_PIDATA_POSITION *position = (const IVAS_PIDATA_POSITION *) piData; + + *nBytesWritten = 0; + + if ( piData->size != sizeof( IVAS_PIDATA_POSITION ) ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect size in compact position PI data" ); + } + + if ( piData->piDataType != IVAS_PI_R_ISM_POSITION ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect PI ID in compact position PI data" ); + } + + /* Compact position data is 4 bytes, header is 2 bytes */ + if ( maxDataBytes < 4 + 2 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack compact position PI data" ); + } + + buffer[nBytes++] = ( position->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ + buffer[nBytes++] = 4; + posX = (uint16_t) ivasPayload_convertToQ10( position->position.x / MAX_PI_COMPACT_POSITION_XY_METERS ); + posY = (uint16_t) ivasPayload_convertToQ10( position->position.y / MAX_PI_COMPACT_POSITION_XY_METERS ); + posZ = (uint16_t) ivasPayload_convertToQ9( position->position.z / MAX_PI_COMPACT_POSITION_Z_METERS ); + buffer[nBytes++] = (uint8_t) ( posX >> 8 ); + buffer[nBytes++] = (uint8_t) ( ( ( posX & MASK_3BIT ) << 5 ) | posY >> 6 ); + buffer[nBytes++] = (uint8_t) ( ( ( posY & MASK_6BIT ) << 2 ) | posZ >> 8 ); + buffer[nBytes++] = (uint8_t) ( posZ >> 2 ); + + *nBytesWritten = nBytes; + + return IVAS_ERR_OK; +} + +static ivas_error unpackPositionCompact( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ) +{ + IVAS_PIDATA_POSITION *position = (IVAS_PIDATA_POSITION *) piData; + int32_t compactRead = 0; + + /* Compact position data is 4 bytes */ + if ( numDataBytes != 4 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack compact position PI data" ); + } + + position->size = sizeof( IVAS_PIDATA_POSITION ); + + compactRead = ( (int32_t) buffer[0] << 24 ) | ( (int32_t) buffer[1] << 16 ) | ( (int32_t) buffer[2] << 8 ) | ( (int32_t) buffer[3] ); + position->position.x = FLOAT_FROM_Q10( (int16_t) ( compactRead >> 21 ) ) * MAX_PI_COMPACT_POSITION_XY_METERS; /* Shift preserves sign bit */ + compactRead = compactRead << 11; /* Discard read bits */ + position->position.y = FLOAT_FROM_Q10( (int16_t) ( compactRead >> 21 ) ) * MAX_PI_COMPACT_POSITION_XY_METERS; /* Shift preserves sign bit */ + compactRead = compactRead << 11; /* Discard read bits */ + position->position.z = FLOAT_FROM_Q9( (int16_t) ( compactRead >> 22 ) ) * MAX_PI_COMPACT_POSITION_Z_METERS; /* Shift preserves sign bit */ + + return IVAS_ERR_OK; +} +#endif + #ifdef ISM_PI_DATA static ivas_error packISMPosition( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) { @@ -675,6 +955,76 @@ static ivas_error unpackISMPosition( const uint8_t *buffer, uint32_t numDataByte } #endif +#ifdef RTP_UPDATES_SA4_134 +static ivas_error packISMPositionCompact( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) +{ + uint32_t nBytes = 0, n; + uint16_t posX, posY, posZ = 0; + const IVAS_PIDATA_ISM_POSITION *ism_position = (const IVAS_PIDATA_ISM_POSITION *) piData; + + *nBytesWritten = 0; + + if ( piData->size != sizeof( IVAS_PIDATA_ISM_POSITION ) ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect size in ISM POSITION COMPACT PI data" ); + } + + if ( piData->piDataType != IVAS_PI_ISM_POSITION_COMPACT ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect PI ID in ISM POSITION COMPACT PI data" ); + } + + /* Compact position data is 4 bytes, header is 2 bytes */ + if ( maxDataBytes < 4 * IVAS_MAX_NUM_OBJECTS + 2 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack ISM POSITION COMPACT PI data" ); + } + + buffer[nBytes++] = ( ism_position->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ + buffer[nBytes++] = (uint8_t) ism_position->numObjects * 4; + for ( n = 0; n < ism_position->numObjects; n++ ) + { + posX = (uint16_t) ivasPayload_convertToQ10( ism_position->position[n].x / MAX_PI_COMPACT_POSITION_XY_METERS ); + posY = (uint16_t) ivasPayload_convertToQ10( ism_position->position[n].y / MAX_PI_COMPACT_POSITION_XY_METERS ); + posZ = (uint16_t) ivasPayload_convertToQ9( ism_position->position[n].z / MAX_PI_COMPACT_POSITION_Z_METERS ); + buffer[nBytes++] = (uint8_t) ( posX >> 8 ); + buffer[nBytes++] = (uint8_t) ( ( ( posX & MASK_3BIT ) << 5 ) | posY >> 6 ); + buffer[nBytes++] = (uint8_t) ( ( ( posY & MASK_6BIT ) << 2 ) | posZ >> 8 ); + buffer[nBytes++] = (uint8_t) ( posZ >> 2 ); + } + *nBytesWritten = nBytes; + return IVAS_ERR_OK; +} + +static ivas_error unpackISMPositionCompact( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ) +{ + uint16_t n; + int32_t compactRead = 0; + IVAS_PIDATA_ISM_POSITION *ism_position = (IVAS_PIDATA_ISM_POSITION *) piData; + + /* Compact position data is 4 bytes */ + if ( numDataBytes % 4 != 0 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack ISM POSITION COMPACT PI data" ); + } + + ism_position->size = sizeof( IVAS_PIDATA_ISM_POSITION ); + ism_position->piDataType = IVAS_PI_ISM_POSITION_COMPACT; + ism_position->numObjects = (uint16_t) numDataBytes / 4; + + for ( n = 0; n < ism_position->numObjects; n++ ) + { + compactRead = ( (int32_t) buffer[n * 4] << 24 ) | ( (int32_t) buffer[n * 4 + 1] << 16 ) | ( (int32_t) buffer[n * 4 + 2] << 8 ) | ( (int32_t) buffer[n * 4 + 3] ); + ism_position->position[n].x = FLOAT_FROM_Q10( (int16_t) ( compactRead >> 21 ) ) * MAX_PI_COMPACT_POSITION_XY_METERS; /* Shift preserves sign bit */ + compactRead = compactRead << 11; /* Discard read bits */ + ism_position->position[n].y = FLOAT_FROM_Q10( (int16_t) ( compactRead >> 21 ) ) * MAX_PI_COMPACT_POSITION_XY_METERS; /* Shift preserves sign bit */ + compactRead = compactRead << 11; /* Discard read bits */ + ism_position->position[n].z = FLOAT_FROM_Q9( (int16_t) ( compactRead >> 22 ) ) * MAX_PI_COMPACT_POSITION_Z_METERS; /* Shift preserves sign bit */ + } + return IVAS_ERR_OK; +} +#endif + static ivas_error packDiegetic( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) { uint32_t nBytes = 0, n; @@ -752,6 +1102,16 @@ static ivas_error packAudioFocusCommon( const IVAS_PIDATA_GENERIC *piData, uint8 return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect size in PI data of type Audio Focus" ); } +#ifdef RTP_UPDATES_SA4_134 + if ( audioFocus->availDirection && audioFocus->availLevel ) + { + packedSize = 5; + } + else if ( audioFocus->availDirection ) + { + packedSize = 4; + } +#else if ( audioFocus->availDirection && audioFocus->availLevel ) { packedSize = 9; @@ -760,6 +1120,7 @@ static ivas_error packAudioFocusCommon( const IVAS_PIDATA_GENERIC *piData, uint8 { packedSize = 8; } +#endif else if ( audioFocus->availLevel ) { packedSize = 1; @@ -778,6 +1139,23 @@ static ivas_error packAudioFocusCommon( const IVAS_PIDATA_GENERIC *piData, uint8 buffer[nBytes++] = ( audioFocus->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ buffer[nBytes++] = packedSize; +#ifdef RTP_UPDATES_SA4_134 + if ( packedSize == 5 || packedSize == 4 ) + { +#ifdef COMPACT_ORIENTATION_PI_DATA + nBytes += packQuaternion( audioFocus->direction, &buffer[nBytes] ); +#else + buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( audioFocus->direction.w ); + buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( audioFocus->direction.x ); + buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( audioFocus->direction.y ); + buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( audioFocus->direction.z ); +#endif + } + if ( packedSize == 5 || packedSize == 1 ) + { + buffer[nBytes++] = ( (uint8_t) audioFocus->flvl & MASK_4BIT ) << 4; + } +#else if ( packedSize == 9 || packedSize == 8 ) { nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( audioFocus->direction.w ) ); @@ -789,6 +1167,7 @@ static ivas_error packAudioFocusCommon( const IVAS_PIDATA_GENERIC *piData, uint8 { buffer[nBytes++] = ( (uint8_t) audioFocus->flvl & MASK_4BIT ) << 4; } +#endif *nBytesWritten = nBytes; return IVAS_ERR_OK; @@ -798,6 +1177,38 @@ static ivas_error unpackAudioFocusCommon( const uint8_t *buffer, uint32_t numDat { IVAS_PIDATA_AUDIO_FOCUS *audioFocus = (IVAS_PIDATA_AUDIO_FOCUS *) piData; +#ifdef RTP_UPDATES_SA4_134 + /* Audio Focus data is either 1, 4 or 5 bytes */ + if ( numDataBytes != 1 && numDataBytes != 4 && numDataBytes != 5 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack PI data of type Audio Focus" ); + } + + piData->size = sizeof( IVAS_PIDATA_AUDIO_FOCUS ); + audioFocus->availDirection = ( numDataBytes >= 4 ); + audioFocus->availLevel = ( numDataBytes == 1 || numDataBytes == 5 ); + + if ( numDataBytes == 1 ) + { + audioFocus->flvl = ( buffer[0] >> 4 ); + } + else + { +#ifdef COMPACT_ORIENTATION_PI_DATA + unpackQuaternion( buffer, &( audioFocus->direction ) ); +#else + audioFocus->direction.w = FLOAT_FROM_Q7( (int8_t) buffer[0] ); + audioFocus->direction.x = FLOAT_FROM_Q7( (int8_t) buffer[1] ); + audioFocus->direction.y = FLOAT_FROM_Q7( (int8_t) buffer[2] ); + audioFocus->direction.z = FLOAT_FROM_Q7( (int8_t) buffer[3] ); +#endif + + if ( numDataBytes == 5 ) + { + audioFocus->flvl = ( buffer[4] >> 4 ); + } + } +#else /* Audio Focus data is either 1, 8 or 9 bytes */ if ( numDataBytes != 1 && numDataBytes != 8 && numDataBytes != 9 ) { @@ -824,6 +1235,7 @@ static ivas_error unpackAudioFocusCommon( const uint8_t *buffer, uint32_t numDat audioFocus->flvl = ( buffer[8] >> 4 ); } } +#endif return IVAS_ERR_OK; } @@ -1462,11 +1874,14 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { packUnsupportedData, /* AUDIO_DESCRIPTION */ #endif /* RTP_S4_251135_CR26253_0016_REV1 */ #ifdef ISM_PI_DATA - packISMNum, /* ISM_NUM */ - packISMID, /* ISM_ID */ - packISMGain, /* ISM_GAIN */ - packISMOrientation, /* ISM_ORIENTATION */ - packISMPosition, /* ISM_POSITION */ + packISMNum, /* ISM_NUM */ + packISMID, /* ISM_ID */ + packISMGain, /* ISM_GAIN */ + packISMOrientation, /* ISM_ORIENTATION */ + packISMPosition, /* ISM_POSITION */ +#ifdef RTP_UPDATES_SA4_134 + packISMPositionCompact, /* ISM_POSITION_COMPACT */ +#endif packISMDistanceAttenuation, /* ISM_DISTANCE_ATTENUATION */ packISMDirectivity, /* ISM_DIRECTIVITY */ #else @@ -1483,27 +1898,37 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { #else packUnsupportedData, /* DIEGETIC_TYPE */ #endif - packUnsupportedData, /* RESERVED13 */ +#ifdef RTP_UPDATES_SA4_134 + packDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION_INDICATION */ +#else + packUnsupportedData, /* RESERVED13 */ +#endif #ifdef RTP_S4_251135_CR26253_0016_REV1 packAudioFocusCommon, /* AUDIO_FOCUS_INDICATION */ #else packUnsupportedData, /* AUDIO_FOCUS_INDICATION */ #endif +#ifndef RTP_UPDATES_SA4_134 packUnsupportedData, /* RESERVED15 */ +#endif #ifdef RTP_S4_251135_CR26253_0016_REV1 packOrientation, /* PLAYBACK_DEVICE_ORIENTATION */ packOrientation, /* HEAD_ORIENTATION */ #ifdef REVERSE_ISM_PI_DATA packPosition, /* LISTENER_POSITION */ #else - packListenerPosition, /* LISTENER_POSITION */ + packListenerPosition, /* LISTENER_POSITION */ +#endif +#ifdef RTP_UPDATES_SA4_134 + packDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION_REQUEST */ +#else + packDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ #endif - packDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ - packAudioFocusCommon, /* AUDIO_FOCUS_REQUEST */ + packAudioFocusCommon, /* AUDIO_FOCUS_REQUEST */ #ifdef PI_LATENCY packPiLatency, /* PI_LATENCY */ #else - packUnsupportedData, /* PI_LATENCY */ + packUnsupportedData, /* PI_LATENCY */ #endif #else packUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ @@ -1526,7 +1951,10 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { packUnsupportedData, /* R_ISM_ORIENTATION */ #endif #ifdef REVERSE_ISM_PI_DATA - packPosition, /* R_ISM_POSITION */ + packPosition, /* R_ISM_POSITION */ +#ifdef RTP_UPDATES_SA4_134 + packPositionCompact, /* R_ISM_POSITION_COMPACT */ +#endif packReverseISMDirection, /* R_ISM_DIRECTION */ #else packUnsupportedData, /* R_ISM_POSITION */ @@ -1535,8 +1963,10 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { packUnsupportedData, /* RESERVED27 */ packUnsupportedData, /* RESERVED28 */ packUnsupportedData, /* RESERVED29 */ +#ifndef RTP_UPDATES_SA4_134 packUnsupportedData, /* RESERVED30 */ - packNoPiData /* NO_DATA */ +#endif + packNoPiData /* NO_DATA */ }; static const UNPACK_PI_FN unpackPiDataFuntions[IVAS_PI_MAX_ID] = { @@ -1550,11 +1980,14 @@ static const UNPACK_PI_FN unpackPiDataFuntions[IVAS_PI_MAX_ID] = { unpackUnsupportedData, /* AUDIO_DESCRIPTION */ #endif #ifdef ISM_PI_DATA - unpackISMNum, /* ISM_NUM */ - unpackISMID, /* ISM_ID */ - unpackISMGain, /* ISM_GAIN */ - unpackISMOrientation, /* ISM_ORIENTATION */ - unpackISMPosition, /* ISM_POSITION */ + unpackISMNum, /* ISM_NUM */ + unpackISMID, /* ISM_ID */ + unpackISMGain, /* ISM_GAIN */ + unpackISMOrientation, /* ISM_ORIENTATION */ + unpackISMPosition, /* ISM_POSITION */ +#ifdef RTP_UPDATES_SA4_134 + unpackISMPositionCompact, /* ISM_POSITION_COMPACT */ +#endif unpackISMDistanceAttenuation, /* ISM_DISTANCE_ATTENUATION */ unpackISMDirectivity, /* ISM_DIRECTIVITY */ #else @@ -1571,27 +2004,37 @@ static const UNPACK_PI_FN unpackPiDataFuntions[IVAS_PI_MAX_ID] = { #else unpackUnsupportedData, /* DIEGETIC_TYPE */ #endif +#ifdef RTP_UPDATES_SA4_134 + unpackDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION_INDICATION */ +#else unpackUnsupportedData, /* RESERVED13 */ +#endif #ifdef RTP_S4_251135_CR26253_0016_REV1 unpackAudioFocusCommon, /* AUDIO_FOCUS_INDICATION */ #else unpackUnsupportedData, /* AUDIO_FOCUS_INDICATION */ #endif +#ifndef RTP_UPDATES_SA4_134 unpackUnsupportedData, /* RESERVED15 */ +#endif #ifdef RTP_S4_251135_CR26253_0016_REV1 unpackOrientation, /* PLAYBACK_DEVICE_ORIENTATION */ unpackOrientation, /* HEAD_ORIENTATION */ #ifdef REVERSE_ISM_PI_DATA unpackPosition, /* LISTENER_POSITION */ #else - unpackListenerPosition, /* LISTENER_POSITION */ + unpackListenerPosition, /* LISTENER_POSITION */ #endif +#ifdef RTP_UPDATES_SA4_134 + unpackDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION_REQUEST */ +#else unpackDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ - unpackAudioFocusCommon, /* AUDIO_FOCUS_REQUEST */ +#endif + unpackAudioFocusCommon, /* AUDIO_FOCUS_REQUEST */ #ifdef PI_LATENCY unpackPiLatency, /* PI_LATENCY */ #else - unpackUnsupportedData, /* PI_LATENCY */ + unpackUnsupportedData, /* PI_LATENCY */ #endif #else unpackUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ @@ -1614,7 +2057,10 @@ static const UNPACK_PI_FN unpackPiDataFuntions[IVAS_PI_MAX_ID] = { unpackUnsupportedData, /* R_ISM_ORIENTATION */ #endif #ifdef REVERSE_ISM_PI_DATA - unpackPosition, /* R_ISM_POSITION */ + unpackPosition, /* R_ISM_POSITION */ +#ifdef RTP_UPDATES_SA4_134 + unpackPositionCompact, /* R_ISM_POSITION_COMPACT */ +#endif unpackReverseISMDirection, /* R_ISM_DIRECTION */ #else unpackUnsupportedData, /* R_ISM_POSITION */ @@ -1623,10 +2069,48 @@ static const UNPACK_PI_FN unpackPiDataFuntions[IVAS_PI_MAX_ID] = { unpackUnsupportedData, /* RESERVED27 */ unpackUnsupportedData, /* RESERVED28 */ unpackUnsupportedData, /* RESERVED29 */ +#ifndef RTP_UPDATES_SA4_134 unpackUnsupportedData, /* RESERVED30 */ - unpackNoPiData /* NO_DATA */ +#endif + unpackNoPiData /* NO_DATA */ }; +#ifdef RTP_UPDATES_SA4_134 +static const uint32_t maxPiDataSize[IVAS_PI_MAX_ID] = { + 4, /* IVAS_PI_SCENE_ORIENTATION */ + 4, /* IVAS_PI_DEVICE_ORIENTATION_COMPENSATED */ + 4, /* IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED */ + 8, /* IVAS_PI_ACOUSTIC_ENVIRONMENT */ + 5, /* IVAS_PI_AUDIO_DESCRIPTION */ + 1, /* IVAS_PI_ISM_NUM */ + 4, /* IVAS_PI_ISM_ID */ + 4, /* IVAS_PI_ISM_GAIN */ + 16, /* IVAS_PI_ISM_ORIENTATION */ + 24, /* IVAS_PI_ISM_POSITION */ + 16, /* IVAS_PI_ISM_POSITION_COMPACT */ + 12, /* IVAS_PI_ISM_DISTANCE_ATTENUATION */ + 8, /* IVAS_PI_ISM_DIRECTIVITY */ + 1, /* IVAS_PI_DIEGETIC_TYPE */ + 2, /* IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_INDICATION */ + 5, /* IVAS_PI_AUDIO_FOCUS_INDICATION */ + 4, /* IVAS_PI_PLAYBACK_DEVICE_ORIENTATION */ + 4, /* IVAS_PI_HEAD_ORIENTATION */ + 6, /* IVAS_PI_LISTENER_POSITION */ + 2, /* IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION */ + 5, /* IVAS_PI_AUDIO_FOCUS_REQUEST */ + 4, /* IVAS_PI_PI_LATENCY */ + 1, /* IVAS_PI_R_ISM_ID */ + 1, /* IVAS_PI_R_ISM_GAIN */ + 4, /* IVAS_PI_R_ISM_ORIENTATION */ + 6, /* IVAS_PI_R_ISM_POSITION */ + 4, /* IVAS_PI_R_ISM_POSITION_COMPACT */ + 2, /* IVAS_PI_R_ISM_DIRECTION */ + 0, /* IVAS_PI_RESERVED27 */ + 0, /* IVAS_PI_RESERVED28 */ + 0, /* IVAS_PI_RESERVED29 */ + 0, /* NO_DATA */ +}; +#else static const uint32_t maxPiDataSize[IVAS_PI_MAX_ID] = { 8, /* IVAS_PI_SCENE_ORIENTATION */ 8, /* IVAS_PI_DEVICE_ORIENTATION_COMPENSATED */ @@ -1661,6 +2145,7 @@ static const uint32_t maxPiDataSize[IVAS_PI_MAX_ID] = { 0, /* IVAS_PI_RESERVED30 */ 0, /* IVAS_PI_NO_DATA = 31 */ }; +#endif ivas_error PI_PackData( const IVAS_PIDATA_GENERIC *piData, PIDATA_PACKED *packed, uint8_t pmBits ) { diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h index 95a2ac99429f7ba825e594eee4bf56d1dfdb1309..fa319c6b2ed7fb27d9a19fd4cfc016775f5532f3 100644 --- a/lib_util/ivas_rtp_pi_data.h +++ b/lib_util/ivas_rtp_pi_data.h @@ -55,18 +55,23 @@ typedef enum IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED, /* orientation of device in unit quaternions (un-compensated) */ IVAS_PI_ACOUSTIC_ENVIRONMENT, /* describe the acoustic environment */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_PI_AUDIO_DESCRIPTION, /* audio content description (voice/music/ambiance) */ - IVAS_PI_ISM_NUM, /* Number of objects */ - IVAS_PI_ISM_ID, /* id of each object */ - IVAS_PI_ISM_GAIN, /* gain of each object */ - IVAS_PI_ISM_ORIENTATION, /* orientation of each object */ - IVAS_PI_ISM_POSITION, /* position of each object */ + IVAS_PI_AUDIO_DESCRIPTION, /* audio content description (voice/music/ambiance) */ + IVAS_PI_ISM_NUM, /* Number of objects */ + IVAS_PI_ISM_ID, /* id of each object */ + IVAS_PI_ISM_GAIN, /* gain of each object */ + IVAS_PI_ISM_ORIENTATION, /* orientation of each object */ + IVAS_PI_ISM_POSITION, /* position of each object */ +#ifdef RTP_UPDATES_SA4_134 + IVAS_PI_ISM_POSITION_COMPACT, /* position of each object in compact representation */ +#endif IVAS_PI_ISM_DISTANCE_ATTENUATION, /* distance attenuation for each object */ IVAS_PI_ISM_DIRECTIVITY, /* directivity of each object */ IVAS_PI_DIEGETIC_TYPE, /* digetic audio indication */ IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_INDICATION, /* audio suppression indication */ IVAS_PI_AUDIO_FOCUS_INDICATION, /* audio focus indication (direction in Quaternions and/or level) */ - IVAS_PI_RESERVED15, /* reserved */ +#ifndef RTP_UPDATES_SA4_134 + IVAS_PI_RESERVED15, /* reserved */ +#endif /* Reverse direction PI types */ IVAS_PI_PLAYBACK_DEVICE_ORIENTATION, /* orientation of the playback device in quaternions */ @@ -79,14 +84,19 @@ typedef enum IVAS_PI_R_ISM_GAIN, /* editing request for gain factor for received object */ IVAS_PI_R_ISM_ORIENTATION, /* editing request for orientation for received object */ IVAS_PI_R_ISM_POSITION, /* editing request for position for received object */ - IVAS_PI_R_ISM_DIRECTION, /* editing request for direction for received object */ - IVAS_PI_RESERVED27, /* reserved */ - IVAS_PI_RESERVED28, /* reserved */ - IVAS_PI_RESERVED29, /* reserved */ - IVAS_PI_RESERVED30, /* reserved */ -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - IVAS_PI_NO_DATA = 31, /* Indicates an empty PI data frame */ - IVAS_PI_MAX_ID /* Max number of PI data IDs supprted */ +#ifdef RTP_UPDATES_SA4_134 + IVAS_PI_R_ISM_POSITION_COMPACT, /* editing request for position for received object in a compact representation*/ +#endif + IVAS_PI_R_ISM_DIRECTION, /* editing request for direction for received object */ + IVAS_PI_RESERVED27, /* reserved */ + IVAS_PI_RESERVED28, /* reserved */ + IVAS_PI_RESERVED29, /* reserved */ +#ifndef RTP_UPDATES_SA4_134 + IVAS_PI_RESERVED30, /* reserved */ +#endif +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + IVAS_PI_NO_DATA = 31, /* Indicates an empty PI data frame */ + IVAS_PI_MAX_ID /* Max number of PI data IDs supprted */ } IVAS_PI_TYPE; /* cartesian coordinates (X,Y,Z) in 3D space */ @@ -490,6 +500,7 @@ typedef union IVAS_PIDATA_ISM_GAIN ismGain; IVAS_PIDATA_ISM_ORIENTATION ismOrientation; IVAS_PIDATA_ISM_POSITION ismPosition; + IVAS_PIDATA_ISM_POSITION ismPositionCompact; IVAS_PIDATA_ISM_ATTENUATION ismAttenuation; IVAS_PIDATA_ISM_DIRECTIVITY ismDirectivity; IVAS_PIDATA_DIEGETIC digeticIndicator; @@ -514,6 +525,7 @@ typedef union #else IVAS_PIDATA_ISM_EDIT_POSITION ismEditPosition; #endif + IVAS_PIDATA_POSITION ismEditPositionCompact; IVAS_PIDATA_ISM_EDIT_DIRECTION ismEditDirection; #endif /* RTP_S4_251135_CR26253_0016_REV1 */ IVAS_PIDATA_NO_DATA noPiData; diff --git a/readme.txt b/readme.txt index 62d21f51bacdf05764e470b763bbd0ae659f0023..129393976b0c84c780f7fb194fa45bdbb3407ebd 100644 --- a/readme.txt +++ b/readme.txt @@ -35,10 +35,11 @@ These files represent the 3GPP EVS Codec Extension for Immersive Voice and Audio Services (IVAS) floating-point C simulation. All code is writtten in ISO/IEC C99. The system is implemented as six separate programs: - IVAS_cod IVAS Encoder - IVAS_dec IVAS Decoder - IVAS_rend IVAS External Renderer - IVAS_cod_fmtsw IVAS Encoder with support for format switching + IVAS_cod IVAS Encoder + IVAS_dec IVAS Decoder + IVAS_rend IVAS External Renderer + ISAR_post_rend ISAR Post Renderer + IVAS_cod_fmtsw IVAS Encoder with support for format switching ambi_converter example program for Ambisonics format conversion For encoding using the coder program, the input is a binary @@ -124,7 +125,7 @@ should have the following structure: . `-- c-code |-- readme.txt - |-- Makefile + |-- Makefile |-- Workspace_msvc |-- apps |-- lib_com @@ -133,8 +134,9 @@ should have the following structure: |-- lib_enc |-- lib_isar |-- lib_lc3plus - |-- lib_rend + |-- lib_rend |-- lib_util + |-- scripts The package includes a Makefile for gcc, which has been verified on 32-bit Linux systems. The code can be compiled by entering the directory @@ -150,7 +152,7 @@ To compile the code, please open "Workspace_msvc\Workspace_msvc.sln" and build "encoder" for the encoder, "decoder" for the decoder, and "renderer" for the renderer executable. The resulting encoder/decoder/renderer/ISAR_post_renderer executables are "IVAS_cod.exe", "IVAS_dec.exe", "IVAS_rend.exe", and -"ISAR_post_rend.exe". All reside in the c-code main directory. In addition, this +"ISAR_post_rend.exe". All reside in the c-code main directory. In addition, this directory will contain a version of the encoder with support for format switching (named "IVAS_cod_fmtsw.exe") and an example program for Ambisonics format conversion (named "ambi_converter.exe"). @@ -294,7 +296,7 @@ Options: EVS RTP Payload Format or rtpdump files containing TS26.253 Annex A IVAS RTP Payload Format. The SDP parameter hf_only is required. Reading RFC4867 AMR/AMR-WB RTP payload format is not supported. --Tracefile TF : VoIP mode: Generate trace file named TF. Requires -no_delay_cmp to +-Tracefile TF : VoIP mode: Generate trace file named TF. Requires -no_delay_cmp to be enabled so that trace contents remain in sync with audio output. -fec_cfg_file : Optimal channel aware configuration computed by the JBM as described in Section 6.3.1 of TS26.448. The output is @@ -306,7 +308,7 @@ Options: Format files, the magic word in the mime file is used to determine which of the two supported formats is in use. default bitstream file format is G.192 --fr L : render frame size in ms L=(5,10,20), default is 20 +-fr L : render frame size in ms L=(5,10,20), default is 20 -hrtf File : HRTF filter File used in BINAURAL rendering -T File : Head rotation specified by external trajectory File -otr tracking_type : Head orientation tracking type: 'none', 'ref', 'avg', 'ref_vec' @@ -322,14 +324,13 @@ Options: left or l or 90->left, right or r or -90->right, center or c or 0->middle -exof File : External orientation trajectory File for simulation of external orientations -dpid ID : Directivity pattern ID(s) (space-separated list of up to 4 numbers can be - specified) for binaural output configuration --aeid ID | File : Acoustic environment ID (number > 0) or - alternatively, it can be a text file where each line contains "ID duration" - for BINAURAL_ROOM_REVERB output configuration. + specified) for binaural output configurations +-aeid ID | File : Acoustic environment ID (number > 0) or a text file where each line + contains "ID duration" for BINAURAL_ROOM_REVERB output configuration -obj_edit File : Object editing instructions file or NULL for built-in example --level level : Complexity level, level = (1, 2, 3), will be defined after characterisation. --om File : Coded metadata File for BINAURAL_SPLIT_PCM OutputConf - Currently, all values default to level 3 (full functionality). +-level level : Complexity level, level = (1, 2, 3), will be defined after characterisation + Currently, all values default to level 3 (full functionality) +-om File : Coded metadata File for BINAURAL_SPLIT_PCM output configuration -q : Quiet mode, limit printouts to terminal, default is deactivated @@ -358,31 +359,30 @@ Options: -render_config File : Binaural renderer configuration parameters in File (only for binaural outputs) -room_size (S|M|L) : Selects default reverb based on a room size (S - small | M - medium | L - large) -non_diegetic_pan P : Panning mono non-diegetic sound to stereo -90<= P <= 90 - left or l or 90->left, right or r or -90->right, center or c or 0 ->middle + left or l or 90->left, right or r or -90->right, center or c or 0 ->middle -exof File : External orientation trajectory File for simulation of external orientations -dpid ID : Directivity pattern ID(s) (space-separated list of up to 4 numbers can be - specified) for binaural outputs --aeid ID | File : Acoustic environment ID (number > 0) - alternatively, it can be a text file where each line contains "ID duration" for BINAURAL_ROOM_REVERB output. + specified) for binaural output configurations +-aeid ID | File : Acoustic environment ID (number > 0) or a text file where each line + contains "ID duration" for BINAURAL_ROOM_REVERB output configuration -lp Position : Output LFE position. Comma-delimited triplet of [gain, azimuth, elevation] where gain is linear - (like --gain, -g) and azimuth, elevation are in degrees. - If specified, overrides the default behavior which attempts to map input to output LFE channel(s) + (like --gain, -g) and azimuth, elevation are in degrees + If specified, overrides the default behavior which attempts to map input to output LFE channel(s) -lm File : LFE panning matrix File (CSV table) containing a matrix of dimensions [ num_input_lfe x num_output_channels ] with elements specifying linear routing gain (like --gain, -g). - If specified, overrides the output LFE position option and the default behavior which attempts to map input to output LFE channel(s) + If specified, overrides the output LFE position option and the default behavior which attempts to map input to output LFE channel(s) -no_delay_cmp : Turn off delay compensation -g : Input gain (linear, not in dB) to be applied to input audio file -l : List supported audio formats -smd : Metadata Synchronization Delay in ms, Default is 0. Quantized by 5ms subframes. --om File : Coded metadata File (only for BINAURAL_SPLIT_PCM output) --prbfi File : BFI File (only for BINAURAL_SPLIT_PCM output) --level level : Complexity level, level = (1, 2, 3), will be defined after characterisation. +-om File : Coded metadata File for BINAURAL_SPLIT_PCM output configuration +-level level : Complexity level, level = (1, 2, 3), will be defined after characterisation Currently, all values default to level 3 (full functionality). -q : Quiet mode, limit printouts to terminal, default is deactivated -The usage of the "ISAR_post_rend" program as follows: ------------------------------------------------------ +The usage of the "ISAR_post_rend" program is as follows: +-------------------------------------------------------- Usage: ISAR_post_rend [options] @@ -396,6 +396,34 @@ Options: -prbfi File : BFI File +The usage of the "ambi_converter" program is as follows: +-------------------------------------------------------- + +Usage: ambi_converter input_file output_file input_convention output_convention + +input_convention and output convention must be an integer number in [0,5] +the following conventions are supported: +0 : ACN-SN3D +1 : ACN-N3D +2 : FuMa-MaxN +3 : FuMa-FuMa +4 : SID-SN3D +5 : SID-N3D + +Either the input or the output convention must always be ACN-SN3D. + + +The usage of the "IVAS_cod_fmtsw" program is as follows: +-------------------------------------------------------- + +Usage: IVAS_cod_fmtsw format_switching_file + +Mandatory parameters: +--------------------- +format_switching_file: Text file containing a valid encoder command line in each line + + + MULTICHANNEL LOUDSPEAKER INPUT / OUTPUT CONFIGURATIONS ====================================================== The loudspeaker positions for each MC layouts are assumed to have the following azimuth and elevation @@ -423,31 +451,6 @@ omitted, the LFE input is downmixed to all channels with a factor of 1/N. Positi the LFE channel. Maximum number of supported loudskpeakers N is 16. An example custom loudspeaker layout file is available: ls_setup_16ch_8+4+4.txt -The usage of the "ambi_converter" program as follows: ------------------------------------------------------ - -Usage: ambi_converter input_file output_file input_convention output_convention - -input_convention and output convention must be an integer number in [0,5] -the following conventions are supported: -0 : ACN-SN3D -1 : ACN-N3D -2 : FuMa-MaxN -3 : FuMa-FuMa -4 : SID-SN3D -5 : SID-N3D - -Either the input or the output convention must always be ACN-SN3D. - -The usage of the "IVAS_cod_fmtsw" program is as follows: --------------------------------------------------------- - -Usage: IVAS_cod_fmtsw format_switching_file - -Mandatory parameters: ---------------------- -format_switching_file: Text file containing a valid encoder command line in each line - RUNNING THE SELF TEST ===================== @@ -691,17 +694,17 @@ The parameters for the object editing in decoder for the supported formats can b parameter file. Each row of the file corresponds to one 20 ms IVAS frame. The row contains one or more of the following parameters separated by a comma: -bg_gain= linear gain to be applied on the SBA/MASA component in OSBA/OMASA, no effect for ISM -obj__gain= linear gain to be applied on object , 0-based indexing -obj__relgain=0|1 if 1, obj__gain is interpreted as a relative modification. default is absolute modification -obj__azi= azimuth angle in degrees to be applied on object , 0-based indexing -obj__relazi=0|1 if 1, obj__azi is interpreted as a relative modification. default is absolute modification -obj__ele= elevation angle in degrees to be applied on object , 0-based indexing -obj__relele=0|1 if 1, obj__ele is interpreted as a relative modification. default is absolute modification +bg_gain= linear gain to be applied on the SBA/MASA component in OSBA/OMASA, no effect for ISM +obj__gain= linear gain to be applied on object , 0-based indexing +obj__relgain=0|1 if 1, obj__gain is interpreted as a relative modification. default is absolute modification +obj__azi= azimuth angle in degrees to be applied on object , 0-based indexing +obj__relazi=0|1 if 1, obj__azi is interpreted as a relative modification. default is absolute modification +obj__ele= elevation angle in degrees to be applied on object , 0-based indexing +obj__relele=0|1 if 1, obj__ele is interpreted as a relative modification. default is absolute modification obj__radius= linear radius to be applied on object , 0-based indexing obj__relradius=0|1 if 1, obj__radius is interpreted as a relative modification. default is absolute modification -obj__yaw= yaw angle in degrees to be applied on object , 0-based indexing -obj__relyaw=0|1 if 1, obj__yaw is interpreted as a relative modification. default is absolute modification +obj__yaw= yaw angle in degrees to be applied on object , 0-based indexing +obj__relyaw=0|1 if 1, obj__yaw is interpreted as a relative modification. default is absolute modification obj__pitch= pitch angle in degrees to be applied on object , 0-based indexing obj__relpitch=0|1 if 1, obj__pitch is interpreted as a relative modification. default is absolute modification @@ -720,4 +723,3 @@ typedef struct { u_int32 length; /* size of the RTP packet in bytes */ (u_int8 * length) RTP_packet; /* RTP packet (sized length * byte) */ } RTP_streaming_packet; - diff --git a/scripts/config/self_test_evs.prm b/scripts/config/self_test_evs.prm index ec611fee2a551ea95e9a7a1086cbd2fd9631e432..b68b9509a3bf90bc485caa47d53d80bf9c1c8b5c 100644 --- a/scripts/config/self_test_evs.prm +++ b/scripts/config/self_test_evs.prm @@ -250,14 +250,14 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 // Codec A at 13.20 kbps, 32kHz in, 32kHz out, DTX, JBM Prof 5 ../IVAS_cod -dtx 13200 32 testv/stv32c.wav bit networkSimulator_g192 ../scripts/dly_error_profiles/dly_error_profile_5.dat bit netsimoutput tracefile_sim 2 0 -../IVAS_dec -Tracefile tracefile_dec -VOIP 32 netsimoutput testv/stv32c_13k20_32-32_DTX_JBM5.tst +../IVAS_dec -no_delay_cmp -Tracefile tracefile_dec -VOIP 32 netsimoutput testv/stv32c_13k20_32-32_DTX_JBM5.tst // Codec B at 16.40 kbps, 32kHz in, 32kHz out, DTX, JBM Prof 5 ../IVAS_cod -dtx 16400 32 testv/stv32c.wav bit networkSimulator_g192 ../scripts/dly_error_profiles/dly_error_profile_5.dat bit netsimoutput tracefile_sim 2 0 -../IVAS_dec -Tracefile tracefile_dec -VOIP 32 netsimoutput testv/stv32c_16k40_32-32_DTX_JBM5.tst +../IVAS_dec -no_delay_cmp -Tracefile tracefile_dec -VOIP 32 netsimoutput testv/stv32c_16k40_32-32_DTX_JBM5.tst // Codec B at 13.20 kbps, 32kHz in, 32kHz out, JBM Prof 9, Channel aware ../IVAS_cod -rf 13200 32 testv/stv32c.wav bit networkSimulator_g192 ../scripts/dly_error_profiles/dly_error_profile_5.dat bit netsimoutput tracefile_sim 2 0 -../IVAS_dec -Tracefile tracefile_dec -VOIP 32 netsimoutput testv/stv32c_13k20_CA_32-32_JBM9.tst +../IVAS_dec -no_delay_cmp -Tracefile tracefile_dec -VOIP 32 netsimoutput testv/stv32c_13k20_CA_32-32_JBM9.tst diff --git a/scripts/patch_code_headers.sh b/scripts/patch_code_headers.sh index d65dcb6f65425f8189d5a5fbf34e986e2beec3a2..3140b0575a6473b5a09a8b6e22f7287342ee9d23 100755 --- a/scripts/patch_code_headers.sh +++ b/scripts/patch_code_headers.sh @@ -58,7 +58,7 @@ fi #date="Nov 11, 2025" date=`date "+%b %d, %Y"` -version="IVAS-FL-3.0-RC01" +version="IVAS-FL-3.0" ########################## # # @@ -72,7 +72,7 @@ if [ -d ${WORKDIR}/lib_basop ]; then fi if [ $BASOP -eq 1 ]; then - version="IVAS-FX-3.0-RC01" + version="IVAS-FX-3.0" fi # @@ -178,7 +178,7 @@ rm -f $tmpfile # Patch Printout # -sed -i.bak -e "s/IVAS\ Codec\ Baseline/IVAS\ Codec\ Version\ $version/g" $WORKDIR/lib_com/disclaimer.c +#sed -i.bak -e "s/IVAS\ Codec\ Baseline/IVAS\ Codec\ Version\ $version/g" $WORKDIR/lib_com/disclaimer.c # # Patch Matlab Scripts diff --git a/tests/conformance-test/test_26252.py b/tests/conformance-test/test_26252.py index 0179f58bfcfb30d01fc37046896b960d82e97bad..183f01e2ea27c96bbe45cad04126e4931886267c 100644 --- a/tests/conformance-test/test_26252.py +++ b/tests/conformance-test/test_26252.py @@ -59,7 +59,7 @@ def replace_paths(instr, testv_path, ref_path, cut_path): test_dict = {} TEST_DIR = "." -scripts=["Readme_IVAS_enc.txt", "Readme_IVAS_dec.txt", "Readme_IVAS_rend.txt", "Readme_IVAS_JBM_dec.txt", "Readme_IVAS_ISAR_dec.txt", "Readme_IVAS_ISAR_post_rend.txt"] +scripts=["Readme_IVAS_enc.txt", "Readme_IVAS_dec.txt", "Readme_IVAS_rend.txt", "Readme_IVAS_JBM_dec.txt", "Readme_IVAS_ISAR_dec.txt", "Readme_IVAS_ISAR_post_rend.txt"] for s in scripts: with open(os.path.join(TEST_DIR, s), "r", encoding="UTF-8") as fp: @@ -125,12 +125,45 @@ def test_26252(test_tag, encoder_path, decoder_path, renderer_path, isar_post_re subprocess.run([renderer_path] + rend_opts.split()[1:], check = True) if isar_post_rend_opts: isar_post_rend_opts = replace_paths(isar_post_rend_opts, testv_path, ref_path, cut_path) - subprocess.run([isar_post_renderer_path] + isar_post_rend_opts.split()[1:], check = True) + subprocess.run([isar_post_renderer_path] + isar_post_rend_opts.split()[1:], check = True) diff_opts = replace_paths(diff_opts, testv_path, ref_path, cut_path) - result = True + + diff_in = {".wav": False, ".met": False, ".csv": False, ".192": False, ".bit": False} + for cmd in diff_opts.split(';'): - result = result and filecmp.cmp(cmd.split()[1], cmd.split()[2]) - if not result: - assert False, "Output differs" + file_a = Path(cmd.split()[1]) + file_b = Path(cmd.split()[2]) + + suffix = file_a.suffix + assert suffix == file_b.suffix + assert suffix in diff_in + + # for .csv ISM metadata files, do text-based comparison to not take line endings into account + # everything else (.wav output files and MASA metadata files) is compared as binary files + if suffix == ".csv": + with open(file_a, "r") as f_a: + with open(file_b, "r") as f_b: + a_content = f_a.read() + b_content = f_b.read() + + files_equal = a_content == b_content + else: + files_equal = filecmp.cmp(file_a, file_b) + + if not files_equal: + diff_in[suffix] = True + + if any(diff_in.values()): + result_str = "Output differs in: " + if diff_in[".csv"]: + result_str += "object metadata " + if diff_in[".met"]: + result_str += "MASA metadata " + if diff_in[".wav"]: + result_str += "waveform" + if diff_in[".192"] or diff_in[".bit"]: + result_str += "bitstream" + + pytest.fail(result_str) diff --git a/tests/conftest.py b/tests/conftest.py index 6f293b9502b058903416b4d99f4601055300c26e..9e2948ca23cdbab4136b6f93c357e1f9954ee0f9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -570,6 +570,7 @@ class EncoderFrontend: run_dir: Optional[Path] = None, stats_file: Optional[Path] = None, compare_enc_dmx: Optional[bool] = False, + fmtsw_command: Optional[bool] = False, ) -> None: command = [str(self._path)] @@ -593,12 +594,17 @@ class EncoderFrontend: command.extend(add_option_list) # add mandatory parameters - command += [ - str(bitrate), - str(input_sampling_rate), - str(input_path), - str(output_bitstream_path), - ] + if fmtsw_command: + command += [ + str(input_path), + ] + else: + command += [ + str(bitrate), + str(input_sampling_rate), + str(input_path), + str(output_bitstream_path), + ] cmd_str = textwrap.indent(" ".join(command), prefix="\t") log_dbg_msg(f"{self._type} encoder command:\n{cmd_str}") diff --git a/tests/renderer/constants.py b/tests/renderer/constants.py index 766e64722b80132b963c007880ac732222855dfc..387ab508db3c4d57892732b5c12eabd6adb8b568 100644 --- a/tests/renderer/constants.py +++ b/tests/renderer/constants.py @@ -30,8 +30,8 @@ accordance with the laws of the Federal Republic of Germany excluding its confli the United Nations Convention on Contracts on the International Sales of Goods. """ -from pathlib import Path import platform +from pathlib import Path """ Set up paths """ TESTS_DIR = Path(__file__).parent @@ -55,6 +55,8 @@ elif platform.system() in ["Linux", "Darwin"]: else: assert False, f"Unsupported platform {platform.system()}" +SAMPLING_RATES = ["48kHz", "32kHz", "16kHz"] + """ Renderer commandline template """ RENDERER_CMD = [ str(TESTS_DIR.parent.parent.joinpath("IVAS_rend")), @@ -115,27 +117,27 @@ FORMAT_TO_FILE_SMOKETEST = { "NDP_ISM4": NCHAN_TO_FILE[4], "MASA1": NCHAN_TO_FILE[1], "MASA2": NCHAN_TO_FILE[2], - "OMASA_1_1": NCHAN_TO_FILE[2], - "OMASA_1_2": NCHAN_TO_FILE[3], - "OMASA_1_3": NCHAN_TO_FILE[4], - "OMASA_1_4": NCHAN_TO_FILE[5], - "OMASA_2_1": NCHAN_TO_FILE[3], - "OMASA_2_2": NCHAN_TO_FILE[4], - "OMASA_2_3": NCHAN_TO_FILE[5], - "OMASA_2_4": NCHAN_TO_FILE[6], - "OSBA_1_1": NCHAN_TO_FILE[5], - "OSBA_2_1": NCHAN_TO_FILE[6], - "OSBA_3_1": NCHAN_TO_FILE[7], - "OSBA_4_1": NCHAN_TO_FILE[8], - "OSBA_1_2": NCHAN_TO_FILE[10], - "OSBA_2_2": NCHAN_TO_FILE[11], - "OSBA_3_2": NCHAN_TO_FILE[12], - "OSBA_4_2": NCHAN_TO_FILE[13], - "OSBA_1_3": NCHAN_TO_FILE[17], - "OSBA_2_3": NCHAN_TO_FILE[18], - "OSBA_3_3": NCHAN_TO_FILE[19], - "OSBA_4_3": NCHAN_TO_FILE[20], - "META": TEST_VECTOR_DIR.joinpath("mixed_scene.txt"), + "ISM1MASA1": NCHAN_TO_FILE[2], + "ISM2MASA1": NCHAN_TO_FILE[3], + "ISM3MASA1": NCHAN_TO_FILE[4], + "ISM4MASA1": NCHAN_TO_FILE[5], + "ISM1MASA2": NCHAN_TO_FILE[3], + "ISM2MASA2": NCHAN_TO_FILE[4], + "ISM3MASA2": NCHAN_TO_FILE[5], + "ISM4MASA2": NCHAN_TO_FILE[6], + "ISM1SBA1": NCHAN_TO_FILE[5], + "ISM2SBA1": NCHAN_TO_FILE[6], + "ISM3SBA1": NCHAN_TO_FILE[7], + "ISM4SBA1": NCHAN_TO_FILE[8], + "ISM1SBA2": NCHAN_TO_FILE[10], + "ISM2SBA2": NCHAN_TO_FILE[11], + "ISM3SBA2": NCHAN_TO_FILE[12], + "ISM4SBA2": NCHAN_TO_FILE[13], + "ISM1SBA3": NCHAN_TO_FILE[17], + "ISM2SBA3": NCHAN_TO_FILE[18], + "ISM3SBA3": NCHAN_TO_FILE[19], + "ISM4SBA3": NCHAN_TO_FILE[20], + "META": TEST_VECTOR_DIR.joinpath("mixed_scene_48.txt"), "16ch_8+4+4": NCHAN_TO_FILE[16], "4d4": NCHAN_TO_FILE[8], "t_design_4": NCHAN_TO_FILE[12], @@ -158,27 +160,27 @@ FORMAT_TO_FILE_COMPARETEST = { "ISM4": TESTV_DIR.joinpath("stv4ISM48s.wav"), "MASA1": TESTV_DIR.joinpath("stv1MASA1TC48c.wav"), "MASA2": TESTV_DIR.joinpath("stv2MASA2TC48c.wav"), - "OMASA_1_1": TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA1TC48c.wav"), - "OMASA_1_2": TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA1TC48c.wav"), - "OMASA_1_3": TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA1TC48c.wav"), - "OMASA_1_4": TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA1TC48c.wav"), - "OMASA_2_1": TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA2TC48c.wav"), - "OMASA_2_2": TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA2TC48c.wav"), - "OMASA_2_3": TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA2TC48c.wav"), - "OMASA_2_4": TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA2TC48c.wav"), - "OSBA_1_1": TESTV_DIR.joinpath("stvOSBA_1ISM_FOA48c.wav"), - "OSBA_1_2": TESTV_DIR.joinpath("stvOSBA_1ISM_2OA48c.wav"), - "OSBA_1_3": TESTV_DIR.joinpath("stvOSBA_1ISM_3OA48c.wav"), - "OSBA_2_1": TESTV_DIR.joinpath("stvOSBA_2ISM_FOA48c.wav"), - "OSBA_2_2": TESTV_DIR.joinpath("stvOSBA_2ISM_2OA48c.wav"), - "OSBA_2_3": TESTV_DIR.joinpath("stvOSBA_2ISM_3OA48c.wav"), - "OSBA_3_1": TESTV_DIR.joinpath("stvOSBA_3ISM_FOA48c.wav"), - "OSBA_3_2": TESTV_DIR.joinpath("stvOSBA_3ISM_2OA48c.wav"), - "OSBA_3_3": TESTV_DIR.joinpath("stvOSBA_3ISM_3OA48c.wav"), - "OSBA_4_1": TESTV_DIR.joinpath("stvOSBA_4ISM_FOA48c.wav"), - "OSBA_4_2": TESTV_DIR.joinpath("stvOSBA_4ISM_2OA48c.wav"), - "OSBA_4_3": TESTV_DIR.joinpath("stvOSBA_4ISM_3OA48c.wav"), - "META": TEST_VECTOR_DIR.joinpath("mixed_scene.txt"), + "ISM1MASA1": TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA1TC48c.wav"), + "ISM2MASA1": TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA1TC48c.wav"), + "ISM3MASA1": TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA1TC48c.wav"), + "ISM4MASA1": TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA1TC48c.wav"), + "ISM1MASA2": TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA2TC48c.wav"), + "ISM2MASA2": TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA2TC48c.wav"), + "ISM3MASA2": TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA2TC48c.wav"), + "ISM4MASA2": TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA2TC48c.wav"), + "ISM1SBA1": TESTV_DIR.joinpath("stvOSBA_1ISM_FOA48c.wav"), + "ISM1SBA2": TESTV_DIR.joinpath("stvOSBA_1ISM_2OA48c.wav"), + "ISM1SBA3": TESTV_DIR.joinpath("stvOSBA_1ISM_3OA48c.wav"), + "ISM2SBA1": TESTV_DIR.joinpath("stvOSBA_2ISM_FOA48c.wav"), + "ISM2SBA2": TESTV_DIR.joinpath("stvOSBA_2ISM_2OA48c.wav"), + "ISM2SBA3": TESTV_DIR.joinpath("stvOSBA_2ISM_3OA48c.wav"), + "ISM3SBA1": TESTV_DIR.joinpath("stvOSBA_3ISM_FOA48c.wav"), + "ISM3SBA2": TESTV_DIR.joinpath("stvOSBA_3ISM_2OA48c.wav"), + "ISM3SBA3": TESTV_DIR.joinpath("stvOSBA_3ISM_3OA48c.wav"), + "ISM4SBA1": TESTV_DIR.joinpath("stvOSBA_4ISM_FOA48c.wav"), + "ISM4SBA2": TESTV_DIR.joinpath("stvOSBA_4ISM_2OA48c.wav"), + "ISM4SBA3": TESTV_DIR.joinpath("stvOSBA_4ISM_3OA48c.wav"), + "META": TEST_VECTOR_DIR.joinpath("mixed_scene_48.txt"), "16ch_8+4+4": TESTV_DIR.joinpath("stv3OA48c.wav"), "4d4": TESTV_DIR.joinpath("stv71MC48c.wav"), "t_design_4": TESTV_DIR.joinpath("stv714MC48c.wav"), @@ -201,27 +203,27 @@ FORMAT_TO_FILE_LTV = { "ISM4": LTV_DIR.joinpath("ltv48_4ISM.wav"), "MASA1": LTV_DIR.joinpath("ltv48_MASA1TC.wav"), "MASA2": LTV_DIR.joinpath("ltv48_MASA2TC.wav"), - "OMASA_1_1": LTV_DIR.joinpath("ltv48_OMASA_1ISM_1TC.wav"), - "OMASA_1_2": LTV_DIR.joinpath("ltv48_OMASA_2ISM_1TC.wav"), - "OMASA_1_3": LTV_DIR.joinpath("ltv48_OMASA_3ISM_1TC.wav"), - "OMASA_1_4": LTV_DIR.joinpath("ltv48_OMASA_4ISM_1TC.wav"), - "OMASA_2_1": LTV_DIR.joinpath("ltv48_OMASA_1ISM_2TC.wav"), - "OMASA_2_2": LTV_DIR.joinpath("ltv48_OMASA_2ISM_2TC.wav"), - "OMASA_2_3": LTV_DIR.joinpath("ltv48_OMASA_3ISM_2TC.wav"), - "OMASA_2_4": LTV_DIR.joinpath("ltv48_OMASA_4ISM_2TC.wav"), - "OSBA_1_1": LTV_DIR.joinpath("ltv48_OSBA_1ISM_FOA.wav"), - "OSBA_1_2": LTV_DIR.joinpath("ltv48_OSBA_1ISM_HOA2.wav"), - "OSBA_1_3": LTV_DIR.joinpath("ltv48_OSBA_1ISM_HOA3.wav"), - "OSBA_2_1": LTV_DIR.joinpath("ltv48_OSBA_2ISM_FOA.wav"), - "OSBA_2_2": LTV_DIR.joinpath("ltv48_OSBA_2ISM_HOA2.wav"), - "OSBA_2_3": LTV_DIR.joinpath("ltv48_OSBA_2ISM_HOA3.wav"), - "OSBA_3_1": LTV_DIR.joinpath("ltv48_OSBA_3ISM_FOA.wav"), - "OSBA_3_2": LTV_DIR.joinpath("ltv48_OSBA_3ISM_HOA2.wav"), - "OSBA_3_3": LTV_DIR.joinpath("ltv48_OSBA_3ISM_HOA3.wav"), - "OSBA_4_1": LTV_DIR.joinpath("ltv48_OSBA_4ISM_FOA.wav"), - "OSBA_4_2": LTV_DIR.joinpath("ltv48_OSBA_4ISM_HOA2.wav"), - "OSBA_4_3": LTV_DIR.joinpath("ltv48_OSBA_4ISM_HOA3.wav"), - "META": TEST_VECTOR_DIR.joinpath("mixed_scene.txt"), + "ISM1MASA1": LTV_DIR.joinpath("ltv48_OMASA_1ISM_1TC.wav"), + "ISM2MASA1": LTV_DIR.joinpath("ltv48_OMASA_2ISM_1TC.wav"), + "ISM3MASA1": LTV_DIR.joinpath("ltv48_OMASA_3ISM_1TC.wav"), + "ISM4MASA1": LTV_DIR.joinpath("ltv48_OMASA_4ISM_1TC.wav"), + "ISM1MASA2": LTV_DIR.joinpath("ltv48_OMASA_1ISM_2TC.wav"), + "ISM2MASA2": LTV_DIR.joinpath("ltv48_OMASA_2ISM_2TC.wav"), + "ISM3MASA2": LTV_DIR.joinpath("ltv48_OMASA_3ISM_2TC.wav"), + "ISM4MASA2": LTV_DIR.joinpath("ltv48_OMASA_4ISM_2TC.wav"), + "ISM1SBA1": LTV_DIR.joinpath("ltv48_OSBA_1ISM_FOA.wav"), + "ISM1SBA2": LTV_DIR.joinpath("ltv48_OSBA_1ISM_HOA2.wav"), + "ISM1SBA3": LTV_DIR.joinpath("ltv48_OSBA_1ISM_HOA3.wav"), + "ISM2SBA1": LTV_DIR.joinpath("ltv48_OSBA_2ISM_FOA.wav"), + "ISM2SBA2": LTV_DIR.joinpath("ltv48_OSBA_2ISM_HOA2.wav"), + "ISM2SBA3": LTV_DIR.joinpath("ltv48_OSBA_2ISM_HOA3.wav"), + "ISM3SBA1": LTV_DIR.joinpath("ltv48_OSBA_3ISM_FOA.wav"), + "ISM3SBA2": LTV_DIR.joinpath("ltv48_OSBA_3ISM_HOA2.wav"), + "ISM3SBA3": LTV_DIR.joinpath("ltv48_OSBA_3ISM_HOA3.wav"), + "ISM4SBA1": LTV_DIR.joinpath("ltv48_OSBA_4ISM_FOA.wav"), + "ISM4SBA2": LTV_DIR.joinpath("ltv48_OSBA_4ISM_HOA2.wav"), + "ISM4SBA3": LTV_DIR.joinpath("ltv48_OSBA_4ISM_HOA3.wav"), + "META": TEST_VECTOR_DIR.joinpath("mixed_scene_48.txt"), "16ch_8+4+4": LTV_DIR.joinpath("ltv48_HOA3.wav"), "4d4": LTV_DIR.joinpath("ltv48_MC71.wav"), "t_design_4": LTV_DIR.joinpath("ltv48_MC714.wav"), @@ -252,44 +254,44 @@ FORMAT_TO_METADATA_FILES = { ], "MASA1": [str(TESTV_DIR.joinpath("stv1MASA1TC48c.met"))], "MASA2": [str(TESTV_DIR.joinpath("stv2MASA2TC48c.met"))], - "OMASA_1_1": [ + "ISM1MASA1": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA1TC48c.met")), ], - "OMASA_1_2": [ + "ISM2MASA1": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvISM2.csv")), str(TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA1TC48c.met")), ], - "OMASA_1_3": [ + "ISM3MASA1": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvISM2.csv")), str(TESTV_DIR.joinpath("stvISM3.csv")), str(TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA1TC48c.met")), ], - "OMASA_1_4": [ + "ISM4MASA1": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvISM2.csv")), str(TESTV_DIR.joinpath("stvISM3.csv")), str(TESTV_DIR.joinpath("stvISM4.csv")), str(TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA1TC48c.met")), ], - "OMASA_2_1": [ + "ISM1MASA2": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA2TC48c.met")), ], - "OMASA_2_2": [ + "ISM2MASA2": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvISM2.csv")), str(TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA2TC48c.met")), ], - "OMASA_2_3": [ + "ISM3MASA2": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvISM2.csv")), str(TESTV_DIR.joinpath("stvISM3.csv")), str(TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA2TC48c.met")), ], - "OMASA_2_4": [ + "ISM4MASA2": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvISM2.csv")), str(TESTV_DIR.joinpath("stvISM3.csv")), @@ -315,7 +317,7 @@ FORMAT_TO_METADATA_FILES_LTV = { str(LTV_DIR.joinpath("ltvISM3.csv")), str(LTV_DIR.joinpath("ltvISM4.csv")), ], - "NDP_ISM4": [ # Should not be needed, because it is included in all ISM metadata files. + "NDP_ISM4": [ # Should not be needed, because it is included in all ISM metadata files. str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltvISM3.csv")), @@ -323,44 +325,44 @@ FORMAT_TO_METADATA_FILES_LTV = { ], "MASA1": [str(LTV_DIR.joinpath("ltv48_MASA1TC.met"))], "MASA2": [str(LTV_DIR.joinpath("ltv48_MASA2TC.met"))], - "OMASA_1_1": [ + "ISM1MASA1": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_1ISM_1TC.met")), ], - "OMASA_1_2": [ + "ISM2MASA1": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_2ISM_1TC.met")), ], - "OMASA_1_3": [ + "ISM3MASA1": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltvISM3.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_3ISM_1TC.met")), ], - "OMASA_1_4": [ + "ISM4MASA1": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltvISM3.csv")), str(LTV_DIR.joinpath("ltvISM4.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_4ISM_1TC.met")), ], - "OMASA_2_1": [ + "ISM1MASA2": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_1ISM_2TC.met")), ], - "OMASA_2_2": [ + "ISM2MASA2": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_2ISM_2TC.met")), ], - "OMASA_2_3": [ + "ISM3MASA2": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltvISM3.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_3ISM_2TC.met")), ], - "OMASA_2_4": [ + "ISM4MASA2": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltvISM3.csv")), @@ -374,6 +376,31 @@ INPUT_FORMATS_AMBI = ["FOA", "HOA2", "HOA3"] INPUT_FORMATS_MC = ["MONO", "STEREO", "5_1", "5_1_2", "5_1_4", "7_1", "7_1_4"] INPUT_FORMATS_ISM = ["ISM1", "ISM2", "ISM3", "ISM4"] INPUT_FORMATS_MASA = ["MASA1", "MASA2"] +INPUT_FORMATS_OMASA = [ + "ISM1MASA1", + "ISM2MASA1", + "ISM3MASA1", + "ISM4MASA1", + "ISM1MASA2", + "ISM2MASA2", + "ISM3MASA2", + "ISM4MASA2", +] +INPUT_FORMATS_OSBA = [ + "ISM1SBA1", + "ISM1SBA2", + "ISM1SBA3", + "ISM2SBA1", + "ISM2SBA2", + "ISM2SBA3", + "ISM3SBA1", + "ISM3SBA2", + "ISM3SBA3", + "ISM4SBA1", + "ISM4SBA2", + "ISM4SBA3", +] + """ Non binaural / parametric output formats """ OUTPUT_FORMATS = [ @@ -399,7 +426,7 @@ CUSTOM_LS_TO_TEST = [ """ Mixed scene ( metadata ) rendering """ METADATA_SCENES_TO_TEST = ["mixed_scene", "mixed_scene_simple"] METADATA_SCENES_TO_TEST_NO_BE = ["masa_scene"] -METADATA_SCENES_TO_TEST_MASA_PREREND = ["mixed_mc714_foa_masa2_ism4"] +METADATA_SCENES_TO_TEST_MASA_PREREND = ["mixed_mc714_foa_masa2_ism4", "mixed_10sec_mc714_foa_masa1_ism4", "mixed_10sec_mc714_foa_masa2_ism4"] """ Binaural rendering """ OUTPUT_FORMATS_BINAURAL = ["BINAURAL", "BINAURAL_ROOM_IR", "BINAURAL_ROOM_REVERB"] @@ -418,4 +445,3 @@ PEAQ_SUPPORTED_FMT = [ "BINAURAL_ROOM_IR", "BINAURAL_ROOM_REVERB", ] - diff --git a/tests/renderer/test_renderer.py b/tests/renderer/test_renderer.py index 8cfda2f6dbbed56e3c31f39b6ffd818408bc7e4d..d12cb47b19cbc1d02ef4439bb94cf6fa76233d92 100644 --- a/tests/renderer/test_renderer.py +++ b/tests/renderer/test_renderer.py @@ -33,25 +33,26 @@ the United Nations Convention on Contracts on the International Sales of Goods. import pytest from .constants import ( - FORMAT_TO_METADATA_FILES_LTV, - OUTPUT_FORMATS, - INPUT_FORMATS_AMBI, - FRAMING_TO_TEST, + CUSTOM_LAYOUT_DIR, + CUSTOM_LS_TO_TEST, EXE_SUFFIX, - OUTPUT_FORMATS_BINAURAL, + FRAMING_TO_TEST, HR_TRAJECTORIES_TO_TEST, HR_TRAJECTORY_DIR, - INPUT_FORMATS_MC, + INPUT_FORMATS_AMBI, INPUT_FORMATS_ISM, INPUT_FORMATS_MASA, + INPUT_FORMATS_MC, + INPUT_FORMATS_OMASA, + INPUT_FORMATS_OSBA, + METADATA_SCENES_TO_TEST, METADATA_SCENES_TO_TEST_MASA_PREREND, + OUTPUT_FORMATS, + OUTPUT_FORMATS_BINAURAL, + SAMPLING_RATES, TEST_VECTOR_DIR, - CUSTOM_LS_TO_TEST, - CUSTOM_LAYOUT_DIR, - METADATA_SCENES_TO_TEST, ) -from .utils import run_renderer, compare_renderer_args -from ..conftest import props_to_record +from .utils import compare_renderer_args, run_renderer ############################################################################## # Bit-exactness tests @@ -65,6 +66,7 @@ from ..conftest import props_to_record @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_ambisonics( record_property, props_to_record, @@ -72,6 +74,7 @@ def test_ambisonics( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -93,12 +96,14 @@ def test_ambisonics( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_ambisonics_binaural_static( record_property, props_to_record, @@ -106,6 +111,7 @@ def test_ambisonics_binaural_static( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -127,6 +133,7 @@ def test_ambisonics_binaural_static( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -134,6 +141,7 @@ def test_ambisonics_binaural_static( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_ambisonics_binaural_headrotation( record_property, props_to_record, @@ -142,6 +150,7 @@ def test_ambisonics_binaural_headrotation( out_fmt, trj_file, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -164,14 +173,25 @@ def test_ambisonics_binaural_headrotation( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) -@pytest.mark.skip(reason="Not supported for BASOP code currently") @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL[2:]) -@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +@pytest.mark.parametrize( + "in_fmt", + [ + *INPUT_FORMATS_AMBI, + *INPUT_FORMATS_MC, + *INPUT_FORMATS_ISM, + *INPUT_FORMATS_MASA, + *INPUT_FORMATS_OMASA, + *INPUT_FORMATS_OSBA, + ], +) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) @pytest.mark.parametrize("aeid", ["1", "0"]) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_dynamic_acoustic_environment( record_property, props_to_record, @@ -179,6 +199,7 @@ def test_dynamic_acoustic_environment( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -187,8 +208,11 @@ def test_dynamic_acoustic_environment( aeid, split_comparison, ): - rend_config_path = TEST_VECTOR_DIR.joinpath(f"rend_config_combined.cfg") - rend_config_path.with_stem(f"rend_config") + if in_fmt in ["MONO", "STEREO"]: + pytest.skip("MONO or STEREO to Binaural rendering unsupported") + + rend_config_path = TEST_VECTOR_DIR.joinpath("rend_config_combined.cfg") + rend_config_path.with_stem("rend_config") run_renderer( record_property, @@ -206,13 +230,24 @@ def test_dynamic_acoustic_environment( config_file=rend_config_path, aeid=aeid, split_comparison=split_comparison, + sr=fs, ) -@pytest.mark.skip("MSAN errors in BASOP need to be fixed") @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL[2:]) -@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +@pytest.mark.parametrize( + "in_fmt", + [ + *INPUT_FORMATS_AMBI, + *INPUT_FORMATS_MC, + *INPUT_FORMATS_ISM, + *INPUT_FORMATS_MASA, + *INPUT_FORMATS_OMASA, + *INPUT_FORMATS_OSBA, + ], +) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_dynamic_acoustic_environment_file( record_property, props_to_record, @@ -220,6 +255,7 @@ def test_dynamic_acoustic_environment_file( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -227,10 +263,13 @@ def test_dynamic_acoustic_environment_file( get_odg_bin, split_comparison, ): - rend_config_path = TEST_VECTOR_DIR.joinpath(f"rend_config_combined.cfg") - rend_config_path.with_stem(f"rend_config") + if in_fmt in ["MONO", "STEREO"]: + pytest.skip("MONO or STEREO to Binaural rendering unsupported") + + rend_config_path = TEST_VECTOR_DIR.joinpath("rend_config_combined.cfg") + rend_config_path.with_stem("rend_config") - aeid = TEST_VECTOR_DIR.joinpath(f"aeid1.txt") + aeid = TEST_VECTOR_DIR.joinpath("aeid1.txt") run_renderer( record_property, @@ -248,6 +287,7 @@ def test_dynamic_acoustic_environment_file( config_file=rend_config_path, aeid=aeid, split_comparison=split_comparison, + sr=fs, ) @@ -257,6 +297,7 @@ def test_dynamic_acoustic_environment_file( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MC) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_multichannel( record_property, props_to_record, @@ -264,6 +305,7 @@ def test_multichannel( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -285,12 +327,14 @@ def test_multichannel( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MC) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_multichannel_binaural_static( record_property, props_to_record, @@ -298,6 +342,7 @@ def test_multichannel_binaural_static( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -322,6 +367,7 @@ def test_multichannel_binaural_static( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -329,6 +375,7 @@ def test_multichannel_binaural_static( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MC) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_multichannel_binaural_headrotation( record_property, props_to_record, @@ -337,6 +384,7 @@ def test_multichannel_binaural_headrotation( out_fmt, trj_file, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -362,6 +410,7 @@ def test_multichannel_binaural_headrotation( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -371,6 +420,7 @@ def test_multichannel_binaural_headrotation( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_ISM) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_ism( record_property, props_to_record, @@ -378,6 +428,7 @@ def test_ism( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -399,12 +450,14 @@ def test_ism( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_ISM) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_ism_binaural_static( record_property, props_to_record, @@ -412,6 +465,7 @@ def test_ism_binaural_static( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -433,6 +487,7 @@ def test_ism_binaural_static( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -440,6 +495,7 @@ def test_ism_binaural_static( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_ISM) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_ism_binaural_headrotation( record_property, props_to_record, @@ -448,6 +504,7 @@ def test_ism_binaural_headrotation( out_fmt, trj_file, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -470,6 +527,7 @@ def test_ism_binaural_headrotation( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -479,6 +537,7 @@ def test_ism_binaural_headrotation( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MASA) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_masa( record_property, props_to_record, @@ -486,6 +545,7 @@ def test_masa( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -507,12 +567,14 @@ def test_masa( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MASA) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_masa_binaural_static( record_property, props_to_record, @@ -520,6 +582,7 @@ def test_masa_binaural_static( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -527,9 +590,6 @@ def test_masa_binaural_static( get_odg_bin, split_comparison, ): - if out_fmt in ["BINAURAL_ROOM_IR", "BINAURAL_ROOM_REVERB"]: - pytest.skip("Skipping binaural room outputs for MASA as unimplemented.") - run_renderer( record_property, props_to_record, @@ -544,6 +604,7 @@ def test_masa_binaural_static( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -551,6 +612,7 @@ def test_masa_binaural_static( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MASA) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_masa_binaural_headrotation( record_property, props_to_record, @@ -559,6 +621,7 @@ def test_masa_binaural_headrotation( out_fmt, trj_file, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -566,9 +629,6 @@ def test_masa_binaural_headrotation( get_odg_bin, split_comparison, ): - if out_fmt in ["BINAURAL_ROOM_IR", "BINAURAL_ROOM_REVERB"]: - pytest.skip("Skipping binaural room outputs for MASA as unimplemented.") - run_renderer( record_property, props_to_record, @@ -584,15 +644,54 @@ def test_masa_binaural_headrotation( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) -@pytest.mark.parametrize("in_fmt", METADATA_SCENES_TO_TEST_MASA_PREREND) +@pytest.mark.parametrize("out_fmt", INPUT_FORMATS_MASA) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OMASA) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_masa_prerend( record_property, props_to_record, test_info, in_fmt, + out_fmt, + fs, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +@pytest.mark.parametrize("out_fmt", INPUT_FORMATS_MASA) +@pytest.mark.parametrize("in_fmt", METADATA_SCENES_TO_TEST_MASA_PREREND) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_masa_prerend_scenes( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, get_mld, get_mld_lim, get_ssnr, @@ -605,15 +704,250 @@ def test_masa_prerend( props_to_record, test_info, "META", - "MASA2", - metadata_input=TEST_VECTOR_DIR.joinpath(f"{in_fmt}.txt"), + out_fmt, + metadata_input=TEST_VECTOR_DIR.joinpath(f"{in_fmt}_{fs[:2]}.txt"), + binary_suffix=EXE_SUFFIX, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +""" OMASA """ + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OMASA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_omasa( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OMASA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_omasa_binaural_static( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +@pytest.mark.parametrize("trj_file", HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OMASA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_omasa_binaural_headrotation( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, + trj_file, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file=HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +""" OSBA """ + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OSBA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_osba( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OSBA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_osba_binaural_static( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +@pytest.mark.parametrize("trj_file", HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OSBA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_osba_binaural_headrotation( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, + trj_file, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file=HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), binary_suffix=EXE_SUFFIX, + frame_size=frame_size, get_mld=get_mld, mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -623,6 +957,7 @@ def test_masa_prerend( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) @pytest.mark.parametrize("in_layout", CUSTOM_LS_TO_TEST) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_custom_ls_input( record_property, props_to_record, @@ -630,6 +965,7 @@ def test_custom_ls_input( in_layout, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -651,20 +987,30 @@ def test_custom_ls_input( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", CUSTOM_LS_TO_TEST) @pytest.mark.parametrize( "in_fmt", - [*INPUT_FORMATS_AMBI, *INPUT_FORMATS_MC, *INPUT_FORMATS_ISM, *INPUT_FORMATS_MASA], + [ + *INPUT_FORMATS_AMBI, + *INPUT_FORMATS_MC, + *INPUT_FORMATS_ISM, + *INPUT_FORMATS_MASA, + *INPUT_FORMATS_OMASA, + *INPUT_FORMATS_OSBA, + ], ) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_custom_ls_output( record_property, props_to_record, test_info, in_fmt, out_fmt, + fs, get_mld, get_mld_lim, get_ssnr, @@ -672,11 +1018,6 @@ def test_custom_ls_output( get_odg_bin, split_comparison, ): - # TODO: revert once BASOP is brought up-to-date - if in_fmt in INPUT_FORMATS_MASA: - pytest.skip("MASA to custom LS not supported on ivas-float-update yet") - if in_fmt in INPUT_FORMATS_ISM and out_fmt == "t_design_4": - pytest.skip("ISMx + t_design_4 skipped until bug in BASOP is fixed") run_renderer( record_property, props_to_record, @@ -690,17 +1031,20 @@ def test_custom_ls_output( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", CUSTOM_LS_TO_TEST) @pytest.mark.parametrize("in_fmt", CUSTOM_LS_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_custom_ls_input_output( record_property, props_to_record, test_info, in_fmt, out_fmt, + fs, get_mld, get_mld_lim, get_ssnr, @@ -721,12 +1065,14 @@ def test_custom_ls_input_output( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_layout", CUSTOM_LS_TO_TEST) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_custom_ls_input_binaural( record_property, props_to_record, @@ -734,6 +1080,7 @@ def test_custom_ls_input_binaural( in_layout, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -755,6 +1102,7 @@ def test_custom_ls_input_binaural( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -762,6 +1110,7 @@ def test_custom_ls_input_binaural( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_layout", CUSTOM_LS_TO_TEST) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_custom_ls_input_binaural_headrotation( record_property, props_to_record, @@ -770,6 +1119,7 @@ def test_custom_ls_input_binaural_headrotation( out_fmt, trj_file, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -792,6 +1142,7 @@ def test_custom_ls_input_binaural_headrotation( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -801,6 +1152,7 @@ def test_custom_ls_input_binaural_headrotation( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) @pytest.mark.parametrize("in_fmt", METADATA_SCENES_TO_TEST) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_metadata( record_property, props_to_record, @@ -808,6 +1160,7 @@ def test_metadata( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -821,7 +1174,7 @@ def test_metadata( test_info, "META", out_fmt, - metadata_input=TEST_VECTOR_DIR.joinpath(f"{in_fmt}.txt"), + metadata_input=TEST_VECTOR_DIR.joinpath(f"{in_fmt}_{fs[:2]}.txt"), binary_suffix=EXE_SUFFIX, frame_size=frame_size, get_mld=get_mld, @@ -830,6 +1183,7 @@ def test_metadata( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -839,6 +1193,7 @@ def test_metadata( @pytest.mark.parametrize("out_fmt", ["STEREO"]) @pytest.mark.parametrize("in_fmt", ["MONO"]) @pytest.mark.parametrize("non_diegetic_pan", ["0", "-30", "45", "90", "-90"]) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_non_diegetic_pan_static( record_property, props_to_record, @@ -846,6 +1201,7 @@ def test_non_diegetic_pan_static( in_fmt, out_fmt, non_diegetic_pan, + fs, get_mld, get_mld_lim, get_ssnr, @@ -867,18 +1223,21 @@ def test_non_diegetic_pan_static( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", ["STEREO"]) @pytest.mark.parametrize("in_fmt", ["ISM1"]) @pytest.mark.parametrize("non_diegetic_pan", ["0", "-30", "45", "90", "-90"]) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_non_diegetic_pan_ism_static( record_property, props_to_record, test_info, in_fmt, out_fmt, + fs, non_diegetic_pan, get_mld, get_mld_lim, @@ -901,6 +1260,7 @@ def test_non_diegetic_pan_ism_static( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -1044,31 +1404,27 @@ def test_ambisonics_binaural_headrotation_refvecequal( if test_info.config.option.create_ref or test_info.config.option.create_cut: pytest.skip("OTR tests only run for smoke test") - # TODO revert - if in_fmt == "HOA3" and out_fmt == "BINAURAL_ROOM_REVERB": - pytest.xfail("WIP : minor differences to be resolved") - else: - compare_renderer_args( - record_property, - props_to_record, - test_info, - in_fmt, - out_fmt, - ref_kwargs={ - "name_extension": "refvecequal", - "frame_size": "5", - }, - cut_kwargs={ - "trj_file": HR_TRAJECTORY_DIR.joinpath( - "full-circle-with-up-and-down-4s.csv" - ), - "refvec_file": HR_TRAJECTORY_DIR.joinpath( - "full-circle-with-up-and-down-4s-Vector3.csv" - ), - "frame_size": "5", - }, - split_comparison=split_comparison, - ) + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refvecequal", + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s.csv" + ), + "refvec_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s-Vector3.csv" + ), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) # This test compares rendering with: @@ -1088,32 +1444,28 @@ def test_ambisonics_binaural_headrotation_refvec_rotating( if test_info.config.option.create_ref or test_info.config.option.create_cut: pytest.skip("OTR tests only run for smoke test") - # TODO revert - if in_fmt == "HOA2" and out_fmt == "BINAURAL_ROOM_REVERB": - pytest.xfail("WIP : minor differences to be resolved") - else: - compare_renderer_args( - record_property, - props_to_record, - test_info, - in_fmt, - out_fmt, - ref_kwargs={ - "name_extension": "refvec_rotating", - "trj_file": HR_TRAJECTORY_DIR.joinpath( - "full-circle-with-up-and-down-4s.csv" - ), - "frame_size": "5", - }, - cut_kwargs={ - "trj_file": HR_TRAJECTORY_DIR.joinpath("const000.csv"), - "refvec_file": HR_TRAJECTORY_DIR.joinpath( - "full-circle-with-up-and-down-4s-ccw-Vector3.csv" - ), - "frame_size": "5", - }, - split_comparison=split_comparison, - ) + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refvec_rotating", + "trj_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s.csv" + ), + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath("const000.csv"), + "refvec_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s-ccw-Vector3.csv" + ), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) # This test compares rendering with: diff --git a/tests/renderer/utils.py b/tests/renderer/utils.py index f34512d965590d60efa63cbf44b132dbc65c38c6..1aa7370f08dd8d919e2cf028bf3e4cfb223447b8 100644 --- a/tests/renderer/utils.py +++ b/tests/renderer/utils.py @@ -30,44 +30,44 @@ accordance with the laws of the Federal Republic of Germany excluding its confli the United Nations Convention on Contracts on the International Sales of Goods. """ +import errno import filecmp import logging import os -from pathlib import Path +import re import subprocess as sp import sys +import tempfile +from pathlib import Path from typing import Dict, Optional, Union import numpy as np import pytest -import re -import errno -import tempfile +from ..constants import CAT_NORMAL from .compare_audio import compare_audio_arrays from .constants import ( - LTV_DIR, - SCRIPTS_DIR, - OUTPUT_PATH_REF, - OUTPUT_PATH_CUT, + BIN_SUFFIX_MERGETARGET, FORMAT_TO_FILE_COMPARETEST, FORMAT_TO_FILE_LTV, + FORMAT_TO_FILE_SMOKETEST, FORMAT_TO_METADATA_FILES, FORMAT_TO_METADATA_FILES_LTV, - FORMAT_TO_FILE_SMOKETEST, - RENDERER_CMD, - BIN_SUFFIX_MERGETARGET, + LTV_DIR, + OUTPUT_PATH_CUT, + OUTPUT_PATH_REF, PEAQ_SUPPORTED_FMT, + RENDERER_CMD, + SCRIPTS_DIR, ) -from ..constants import CAT_NORMAL sys.path.append(SCRIPTS_DIR) from pyaudio3dtools.audiofile import readfile -from ..cmp_pcm import cmp_pcm -from ..conftest import parse_properties, get_split_idx +from ..cmp_pcm import cmp_pcm +from ..conftest import get_split_idx, parse_properties -def _run_cmd(cmd, env, test_info=None): +def _run_cmd(cmd, test_info=None, env=None ): """ Helper function for running some command. Raises a SystemError if either the return code is non-zero or a USAN printout is detected @@ -92,32 +92,30 @@ def _run_cmd(cmd, env, test_info=None): raise SystemError(error) -def run_cmd(cmd, test_info, env=None): +def run_cmd(cmd, test_info=None, env=None): logging.info(f"\nRunning command\n{' '.join(cmd)}\n") - _run_cmd(cmd, env, test_info) + _run_cmd(cmd, test_info=test_info, env=env) -def run_isar_ext_rend_cmd(cmd, env=None): +def run_isar_ext_rend_cmd(cmd, test_info=None, env=None): logging.info(f"\nRunning ISAR EXT REND command\n{' '.join(cmd)}\n") - _run_cmd(cmd, env) + _run_cmd(cmd, test_info=test_info, env=env) -def run_ivas_isar_enc_cmd(cmd, env=None): +def run_ivas_isar_enc_cmd(cmd, test_info=None, env=None): logging.info(f"\nRunning IVAS ISAR encoder command\n{' '.join(cmd)}\n") - _run_cmd(cmd, env) + _run_cmd(cmd, test_info=test_info, env=env) - -def run_ivas_isar_dec_cmd(cmd, env=None): +def run_ivas_isar_dec_cmd(cmd, test_info=None, env=None): if BIN_SUFFIX_MERGETARGET in cmd[0]: logging.info(f"\nREF decoder command:\n\t{' '.join(cmd)}\n") else: logging.info(f"\nDUT decoder command:\n\t{' '.join(cmd)}\n") - _run_cmd(cmd, env) - + _run_cmd(cmd, test_info=test_info, env=env) -def run_isar_post_rend_cmd(cmd, env=None): +def run_isar_post_rend_cmd(cmd, test_info=None, env=None): logging.info(f"\nRunning ISAR post renderer command\n{' '.join(cmd)}\n") - _run_cmd(cmd, env) + _run_cmd(cmd, test_info=test_info, env=env) def check_BE( @@ -178,7 +176,7 @@ def run_renderer( aeid: Optional[Union[Path, int]] = None, in_file=None, out_file=None, - sr=48, + sr="48kHz", render_for_peaq=False, split_comparison=False, ) -> str: @@ -270,12 +268,20 @@ def run_renderer( else: in_file = format_to_file[in_fmt] in_name = in_fmt + in_file = str(in_file).replace("48", sr[:2]) if in_meta_files is None and in_fmt in format_to_metadata_files: in_meta_files = format_to_metadata_files[in_fmt] + # If metadata not given with ISM input, use default NULL + if in_meta_files is None and isinstance(in_fmt, str) and "ism" in in_fmt.lower(): + match = re.search(r"ism(\d)", in_fmt.lower()) + assert match is not None + num_obj = int(match[1]) + in_meta_files = ["NULL"] * num_obj + if out_file is None: - out_file_stem = f"{in_name}_to_{out_name}{trj_name}{non_diegetic_pan}{refrot_name}{refvec_name}{refveclev_name}{config_name}{framing_name}{hrtf_file_name}{name_extension}{aeid_name}.wav" + out_file_stem = f"{in_name}_to_{out_name}{trj_name}{non_diegetic_pan}{refrot_name}{refvec_name}{refveclev_name}{config_name}{framing_name}{hrtf_file_name}{name_extension}{aeid_name}_{sr}.wav" out_file = str(output_path_base.joinpath(out_file_stem)) cmd = RENDERER_CMD[:] @@ -283,7 +289,7 @@ def run_renderer( cmd[4] = str(in_fmt) cmd[6] = str(out_file) cmd[8] = str(out_fmt) - cmd[10] = str(sr) + cmd[10] = str(sr[:2]) if test_info.config.option.create_ref: cmd[0] += BIN_SUFFIX_MERGETARGET @@ -373,7 +379,7 @@ def run_renderer( cmd2[4] = str(out_fmt) # in_fmt cmd2[6] = odg_test # out_file cmd2[8] = new_fmt # out_fmt - cmd2[10] = str(sr) + cmd2[10] = str(sr[:2]) cmd2[0] += BIN_SUFFIX_MERGETARGET # Use IVAS_rend_ref for re-rendering cmd2[0] += binary_suffix if "MASA" in str(out_fmt): @@ -554,8 +560,8 @@ def binauralize_input_and_output( # If extended metadata is not used, strip the metadata for the external renderer extended_md_used = ( re.search(r"-ism\s?\+[1-4]", enc_opts) - and not "OMASA" in in_fmt - and not "OSBA" in in_fmt + and "OMASA" not in in_fmt + and "OSBA" not in in_fmt ) if not extended_md_used and n_obj > 0: truncated_meta_files = [] @@ -637,7 +643,7 @@ def binauralize_input_and_output( aeid = findstr(r"-aeid\s+(\S+)", dec_opts) - if not output_config.upper() in PEAQ_SUPPORTED_FMT: + if output_config.upper() not in PEAQ_SUPPORTED_FMT: # Render output to BINAURAL output_reformat = "BINAURAL" diff --git a/tests/renderer_short/constants.py b/tests/renderer_short/constants.py index 766e64722b80132b963c007880ac732222855dfc..1c9eabbd13fa84e8bb6f3d81202bdeff90235f94 100644 --- a/tests/renderer_short/constants.py +++ b/tests/renderer_short/constants.py @@ -30,8 +30,8 @@ accordance with the laws of the Federal Republic of Germany excluding its confli the United Nations Convention on Contracts on the International Sales of Goods. """ -from pathlib import Path import platform +from pathlib import Path """ Set up paths """ TESTS_DIR = Path(__file__).parent @@ -55,6 +55,8 @@ elif platform.system() in ["Linux", "Darwin"]: else: assert False, f"Unsupported platform {platform.system()}" +SAMPLING_RATES = ["48kHz", "32kHz", "16kHz"] + """ Renderer commandline template """ RENDERER_CMD = [ str(TESTS_DIR.parent.parent.joinpath("IVAS_rend")), @@ -115,29 +117,28 @@ FORMAT_TO_FILE_SMOKETEST = { "NDP_ISM4": NCHAN_TO_FILE[4], "MASA1": NCHAN_TO_FILE[1], "MASA2": NCHAN_TO_FILE[2], - "OMASA_1_1": NCHAN_TO_FILE[2], - "OMASA_1_2": NCHAN_TO_FILE[3], - "OMASA_1_3": NCHAN_TO_FILE[4], - "OMASA_1_4": NCHAN_TO_FILE[5], - "OMASA_2_1": NCHAN_TO_FILE[3], - "OMASA_2_2": NCHAN_TO_FILE[4], - "OMASA_2_3": NCHAN_TO_FILE[5], - "OMASA_2_4": NCHAN_TO_FILE[6], - "OSBA_1_1": NCHAN_TO_FILE[5], - "OSBA_2_1": NCHAN_TO_FILE[6], - "OSBA_3_1": NCHAN_TO_FILE[7], - "OSBA_4_1": NCHAN_TO_FILE[8], - "OSBA_1_2": NCHAN_TO_FILE[10], - "OSBA_2_2": NCHAN_TO_FILE[11], - "OSBA_3_2": NCHAN_TO_FILE[12], - "OSBA_4_2": NCHAN_TO_FILE[13], - "OSBA_1_3": NCHAN_TO_FILE[17], - "OSBA_2_3": NCHAN_TO_FILE[18], - "OSBA_3_3": NCHAN_TO_FILE[19], - "OSBA_4_3": NCHAN_TO_FILE[20], - "META": TEST_VECTOR_DIR.joinpath("mixed_scene.txt"), + "ISM1MASA1": NCHAN_TO_FILE[2], + "ISM2MASA1": NCHAN_TO_FILE[3], + "ISM3MASA1": NCHAN_TO_FILE[4], + "ISM4MASA1": NCHAN_TO_FILE[5], + "ISM1MASA2": NCHAN_TO_FILE[3], + "ISM2MASA2": NCHAN_TO_FILE[4], + "ISM3MASA2": NCHAN_TO_FILE[5], + "ISM4MASA2": NCHAN_TO_FILE[6], + "ISM1SBA1": NCHAN_TO_FILE[5], + "ISM2SBA1": NCHAN_TO_FILE[6], + "ISM3SBA1": NCHAN_TO_FILE[7], + "ISM4SBA1": NCHAN_TO_FILE[8], + "ISM1SBA2": NCHAN_TO_FILE[10], + "ISM2SBA2": NCHAN_TO_FILE[11], + "ISM3SBA2": NCHAN_TO_FILE[12], + "ISM4SBA2": NCHAN_TO_FILE[13], + "ISM1SBA3": NCHAN_TO_FILE[17], + "ISM2SBA3": NCHAN_TO_FILE[18], + "ISM3SBA3": NCHAN_TO_FILE[19], + "ISM4SBA3": NCHAN_TO_FILE[20], + "META": TEST_VECTOR_DIR.joinpath("mixed_scene_48.txt"), "16ch_8+4+4": NCHAN_TO_FILE[16], - "4d4": NCHAN_TO_FILE[8], "t_design_4": NCHAN_TO_FILE[12], } @@ -158,29 +159,28 @@ FORMAT_TO_FILE_COMPARETEST = { "ISM4": TESTV_DIR.joinpath("stv4ISM48s.wav"), "MASA1": TESTV_DIR.joinpath("stv1MASA1TC48c.wav"), "MASA2": TESTV_DIR.joinpath("stv2MASA2TC48c.wav"), - "OMASA_1_1": TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA1TC48c.wav"), - "OMASA_1_2": TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA1TC48c.wav"), - "OMASA_1_3": TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA1TC48c.wav"), - "OMASA_1_4": TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA1TC48c.wav"), - "OMASA_2_1": TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA2TC48c.wav"), - "OMASA_2_2": TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA2TC48c.wav"), - "OMASA_2_3": TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA2TC48c.wav"), - "OMASA_2_4": TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA2TC48c.wav"), - "OSBA_1_1": TESTV_DIR.joinpath("stvOSBA_1ISM_FOA48c.wav"), - "OSBA_1_2": TESTV_DIR.joinpath("stvOSBA_1ISM_2OA48c.wav"), - "OSBA_1_3": TESTV_DIR.joinpath("stvOSBA_1ISM_3OA48c.wav"), - "OSBA_2_1": TESTV_DIR.joinpath("stvOSBA_2ISM_FOA48c.wav"), - "OSBA_2_2": TESTV_DIR.joinpath("stvOSBA_2ISM_2OA48c.wav"), - "OSBA_2_3": TESTV_DIR.joinpath("stvOSBA_2ISM_3OA48c.wav"), - "OSBA_3_1": TESTV_DIR.joinpath("stvOSBA_3ISM_FOA48c.wav"), - "OSBA_3_2": TESTV_DIR.joinpath("stvOSBA_3ISM_2OA48c.wav"), - "OSBA_3_3": TESTV_DIR.joinpath("stvOSBA_3ISM_3OA48c.wav"), - "OSBA_4_1": TESTV_DIR.joinpath("stvOSBA_4ISM_FOA48c.wav"), - "OSBA_4_2": TESTV_DIR.joinpath("stvOSBA_4ISM_2OA48c.wav"), - "OSBA_4_3": TESTV_DIR.joinpath("stvOSBA_4ISM_3OA48c.wav"), - "META": TEST_VECTOR_DIR.joinpath("mixed_scene.txt"), + "ISM1MASA1": TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA1TC48c.wav"), + "ISM2MASA1": TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA1TC48c.wav"), + "ISM3MASA1": TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA1TC48c.wav"), + "ISM4MASA1": TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA1TC48c.wav"), + "ISM1MASA2": TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA2TC48c.wav"), + "ISM2MASA2": TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA2TC48c.wav"), + "ISM3MASA2": TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA2TC48c.wav"), + "ISM4MASA2": TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA2TC48c.wav"), + "ISM1SBA1": TESTV_DIR.joinpath("stvOSBA_1ISM_FOA48c.wav"), + "ISM1SBA2": TESTV_DIR.joinpath("stvOSBA_1ISM_2OA48c.wav"), + "ISM1SBA3": TESTV_DIR.joinpath("stvOSBA_1ISM_3OA48c.wav"), + "ISM2SBA1": TESTV_DIR.joinpath("stvOSBA_2ISM_FOA48c.wav"), + "ISM2SBA2": TESTV_DIR.joinpath("stvOSBA_2ISM_2OA48c.wav"), + "ISM2SBA3": TESTV_DIR.joinpath("stvOSBA_2ISM_3OA48c.wav"), + "ISM3SBA1": TESTV_DIR.joinpath("stvOSBA_3ISM_FOA48c.wav"), + "ISM3SBA2": TESTV_DIR.joinpath("stvOSBA_3ISM_2OA48c.wav"), + "ISM3SBA3": TESTV_DIR.joinpath("stvOSBA_3ISM_3OA48c.wav"), + "ISM4SBA1": TESTV_DIR.joinpath("stvOSBA_4ISM_FOA48c.wav"), + "ISM4SBA2": TESTV_DIR.joinpath("stvOSBA_4ISM_2OA48c.wav"), + "ISM4SBA3": TESTV_DIR.joinpath("stvOSBA_4ISM_3OA48c.wav"), + "META": TEST_VECTOR_DIR.joinpath("mixed_scene_48.txt"), "16ch_8+4+4": TESTV_DIR.joinpath("stv3OA48c.wav"), - "4d4": TESTV_DIR.joinpath("stv71MC48c.wav"), "t_design_4": TESTV_DIR.joinpath("stv714MC48c.wav"), } @@ -201,29 +201,28 @@ FORMAT_TO_FILE_LTV = { "ISM4": LTV_DIR.joinpath("ltv48_4ISM.wav"), "MASA1": LTV_DIR.joinpath("ltv48_MASA1TC.wav"), "MASA2": LTV_DIR.joinpath("ltv48_MASA2TC.wav"), - "OMASA_1_1": LTV_DIR.joinpath("ltv48_OMASA_1ISM_1TC.wav"), - "OMASA_1_2": LTV_DIR.joinpath("ltv48_OMASA_2ISM_1TC.wav"), - "OMASA_1_3": LTV_DIR.joinpath("ltv48_OMASA_3ISM_1TC.wav"), - "OMASA_1_4": LTV_DIR.joinpath("ltv48_OMASA_4ISM_1TC.wav"), - "OMASA_2_1": LTV_DIR.joinpath("ltv48_OMASA_1ISM_2TC.wav"), - "OMASA_2_2": LTV_DIR.joinpath("ltv48_OMASA_2ISM_2TC.wav"), - "OMASA_2_3": LTV_DIR.joinpath("ltv48_OMASA_3ISM_2TC.wav"), - "OMASA_2_4": LTV_DIR.joinpath("ltv48_OMASA_4ISM_2TC.wav"), - "OSBA_1_1": LTV_DIR.joinpath("ltv48_OSBA_1ISM_FOA.wav"), - "OSBA_1_2": LTV_DIR.joinpath("ltv48_OSBA_1ISM_HOA2.wav"), - "OSBA_1_3": LTV_DIR.joinpath("ltv48_OSBA_1ISM_HOA3.wav"), - "OSBA_2_1": LTV_DIR.joinpath("ltv48_OSBA_2ISM_FOA.wav"), - "OSBA_2_2": LTV_DIR.joinpath("ltv48_OSBA_2ISM_HOA2.wav"), - "OSBA_2_3": LTV_DIR.joinpath("ltv48_OSBA_2ISM_HOA3.wav"), - "OSBA_3_1": LTV_DIR.joinpath("ltv48_OSBA_3ISM_FOA.wav"), - "OSBA_3_2": LTV_DIR.joinpath("ltv48_OSBA_3ISM_HOA2.wav"), - "OSBA_3_3": LTV_DIR.joinpath("ltv48_OSBA_3ISM_HOA3.wav"), - "OSBA_4_1": LTV_DIR.joinpath("ltv48_OSBA_4ISM_FOA.wav"), - "OSBA_4_2": LTV_DIR.joinpath("ltv48_OSBA_4ISM_HOA2.wav"), - "OSBA_4_3": LTV_DIR.joinpath("ltv48_OSBA_4ISM_HOA3.wav"), - "META": TEST_VECTOR_DIR.joinpath("mixed_scene.txt"), + "ISM1MASA1": LTV_DIR.joinpath("ltv48_OMASA_1ISM_1TC.wav"), + "ISM2MASA1": LTV_DIR.joinpath("ltv48_OMASA_2ISM_1TC.wav"), + "ISM3MASA1": LTV_DIR.joinpath("ltv48_OMASA_3ISM_1TC.wav"), + "ISM4MASA1": LTV_DIR.joinpath("ltv48_OMASA_4ISM_1TC.wav"), + "ISM1MASA2": LTV_DIR.joinpath("ltv48_OMASA_1ISM_2TC.wav"), + "ISM2MASA2": LTV_DIR.joinpath("ltv48_OMASA_2ISM_2TC.wav"), + "ISM3MASA2": LTV_DIR.joinpath("ltv48_OMASA_3ISM_2TC.wav"), + "ISM4MASA2": LTV_DIR.joinpath("ltv48_OMASA_4ISM_2TC.wav"), + "ISM1SBA1": LTV_DIR.joinpath("ltv48_OSBA_1ISM_FOA.wav"), + "ISM1SBA2": LTV_DIR.joinpath("ltv48_OSBA_1ISM_HOA2.wav"), + "ISM1SBA3": LTV_DIR.joinpath("ltv48_OSBA_1ISM_HOA3.wav"), + "ISM2SBA1": LTV_DIR.joinpath("ltv48_OSBA_2ISM_FOA.wav"), + "ISM2SBA2": LTV_DIR.joinpath("ltv48_OSBA_2ISM_HOA2.wav"), + "ISM2SBA3": LTV_DIR.joinpath("ltv48_OSBA_2ISM_HOA3.wav"), + "ISM3SBA1": LTV_DIR.joinpath("ltv48_OSBA_3ISM_FOA.wav"), + "ISM3SBA2": LTV_DIR.joinpath("ltv48_OSBA_3ISM_HOA2.wav"), + "ISM3SBA3": LTV_DIR.joinpath("ltv48_OSBA_3ISM_HOA3.wav"), + "ISM4SBA1": LTV_DIR.joinpath("ltv48_OSBA_4ISM_FOA.wav"), + "ISM4SBA2": LTV_DIR.joinpath("ltv48_OSBA_4ISM_HOA2.wav"), + "ISM4SBA3": LTV_DIR.joinpath("ltv48_OSBA_4ISM_HOA3.wav"), + "META": TEST_VECTOR_DIR.joinpath("mixed_scene_48.txt"), "16ch_8+4+4": LTV_DIR.joinpath("ltv48_HOA3.wav"), - "4d4": LTV_DIR.joinpath("ltv48_MC71.wav"), "t_design_4": LTV_DIR.joinpath("ltv48_MC714.wav"), } @@ -252,44 +251,44 @@ FORMAT_TO_METADATA_FILES = { ], "MASA1": [str(TESTV_DIR.joinpath("stv1MASA1TC48c.met"))], "MASA2": [str(TESTV_DIR.joinpath("stv2MASA2TC48c.met"))], - "OMASA_1_1": [ + "ISM1MASA1": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA1TC48c.met")), ], - "OMASA_1_2": [ + "ISM2MASA1": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvISM2.csv")), str(TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA1TC48c.met")), ], - "OMASA_1_3": [ + "ISM3MASA1": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvISM2.csv")), str(TESTV_DIR.joinpath("stvISM3.csv")), str(TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA1TC48c.met")), ], - "OMASA_1_4": [ + "ISM4MASA1": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvISM2.csv")), str(TESTV_DIR.joinpath("stvISM3.csv")), str(TESTV_DIR.joinpath("stvISM4.csv")), str(TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA1TC48c.met")), ], - "OMASA_2_1": [ + "ISM1MASA2": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA2TC48c.met")), ], - "OMASA_2_2": [ + "ISM2MASA2": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvISM2.csv")), str(TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA2TC48c.met")), ], - "OMASA_2_3": [ + "ISM3MASA2": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvISM2.csv")), str(TESTV_DIR.joinpath("stvISM3.csv")), str(TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA2TC48c.met")), ], - "OMASA_2_4": [ + "ISM4MASA2": [ str(TESTV_DIR.joinpath("stvISM1.csv")), str(TESTV_DIR.joinpath("stvISM2.csv")), str(TESTV_DIR.joinpath("stvISM3.csv")), @@ -315,7 +314,7 @@ FORMAT_TO_METADATA_FILES_LTV = { str(LTV_DIR.joinpath("ltvISM3.csv")), str(LTV_DIR.joinpath("ltvISM4.csv")), ], - "NDP_ISM4": [ # Should not be needed, because it is included in all ISM metadata files. + "NDP_ISM4": [ # Should not be needed, because it is included in all ISM metadata files. str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltvISM3.csv")), @@ -323,44 +322,44 @@ FORMAT_TO_METADATA_FILES_LTV = { ], "MASA1": [str(LTV_DIR.joinpath("ltv48_MASA1TC.met"))], "MASA2": [str(LTV_DIR.joinpath("ltv48_MASA2TC.met"))], - "OMASA_1_1": [ + "ISM1MASA1": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_1ISM_1TC.met")), ], - "OMASA_1_2": [ + "ISM2MASA1": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_2ISM_1TC.met")), ], - "OMASA_1_3": [ + "ISM3MASA1": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltvISM3.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_3ISM_1TC.met")), ], - "OMASA_1_4": [ + "ISM4MASA1": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltvISM3.csv")), str(LTV_DIR.joinpath("ltvISM4.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_4ISM_1TC.met")), ], - "OMASA_2_1": [ + "ISM1MASA2": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_1ISM_2TC.met")), ], - "OMASA_2_2": [ + "ISM2MASA2": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_2ISM_2TC.met")), ], - "OMASA_2_3": [ + "ISM3MASA2": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltvISM3.csv")), str(LTV_DIR.joinpath("ltv48_OMASA_3ISM_2TC.met")), ], - "OMASA_2_4": [ + "ISM4MASA2": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), str(LTV_DIR.joinpath("ltvISM3.csv")), @@ -374,6 +373,31 @@ INPUT_FORMATS_AMBI = ["FOA", "HOA2", "HOA3"] INPUT_FORMATS_MC = ["MONO", "STEREO", "5_1", "5_1_2", "5_1_4", "7_1", "7_1_4"] INPUT_FORMATS_ISM = ["ISM1", "ISM2", "ISM3", "ISM4"] INPUT_FORMATS_MASA = ["MASA1", "MASA2"] +INPUT_FORMATS_OMASA = [ + "ISM1MASA1", + "ISM2MASA1", + "ISM3MASA1", + "ISM4MASA1", + "ISM1MASA2", + "ISM2MASA2", + "ISM3MASA2", + "ISM4MASA2", +] +INPUT_FORMATS_OSBA = [ + "ISM1SBA1", + "ISM1SBA2", + "ISM1SBA3", + "ISM2SBA1", + "ISM2SBA2", + "ISM2SBA3", + "ISM3SBA1", + "ISM3SBA2", + "ISM3SBA3", + "ISM4SBA1", + "ISM4SBA2", + "ISM4SBA3", +] + """ Non binaural / parametric output formats """ OUTPUT_FORMATS = [ @@ -392,14 +416,13 @@ OUTPUT_FORMATS = [ """ Custom loudspeaker input/output """ CUSTOM_LS_TO_TEST = [ "t_design_4", - "4d4", "16ch_8+4+4", ] """ Mixed scene ( metadata ) rendering """ METADATA_SCENES_TO_TEST = ["mixed_scene", "mixed_scene_simple"] METADATA_SCENES_TO_TEST_NO_BE = ["masa_scene"] -METADATA_SCENES_TO_TEST_MASA_PREREND = ["mixed_mc714_foa_masa2_ism4"] +METADATA_SCENES_TO_TEST_MASA_PREREND = ["mixed_10sec_mc714_foa_masa1_ism4", "mixed_10sec_mc714_foa_masa2_ism4"] """ Binaural rendering """ OUTPUT_FORMATS_BINAURAL = ["BINAURAL", "BINAURAL_ROOM_IR", "BINAURAL_ROOM_REVERB"] @@ -418,4 +441,3 @@ PEAQ_SUPPORTED_FMT = [ "BINAURAL_ROOM_IR", "BINAURAL_ROOM_REVERB", ] - diff --git a/tests/renderer_short/test_renderer.py b/tests/renderer_short/test_renderer.py index 8cfda2f6dbbed56e3c31f39b6ffd818408bc7e4d..c49750928e0b700cb3f1cc8974b307db757e4451 100644 --- a/tests/renderer_short/test_renderer.py +++ b/tests/renderer_short/test_renderer.py @@ -33,25 +33,26 @@ the United Nations Convention on Contracts on the International Sales of Goods. import pytest from .constants import ( - FORMAT_TO_METADATA_FILES_LTV, - OUTPUT_FORMATS, - INPUT_FORMATS_AMBI, - FRAMING_TO_TEST, + CUSTOM_LAYOUT_DIR, + CUSTOM_LS_TO_TEST, EXE_SUFFIX, - OUTPUT_FORMATS_BINAURAL, + FRAMING_TO_TEST, HR_TRAJECTORIES_TO_TEST, HR_TRAJECTORY_DIR, - INPUT_FORMATS_MC, + INPUT_FORMATS_AMBI, INPUT_FORMATS_ISM, INPUT_FORMATS_MASA, + INPUT_FORMATS_MC, + INPUT_FORMATS_OMASA, + INPUT_FORMATS_OSBA, + METADATA_SCENES_TO_TEST, METADATA_SCENES_TO_TEST_MASA_PREREND, + OUTPUT_FORMATS, + OUTPUT_FORMATS_BINAURAL, + SAMPLING_RATES, TEST_VECTOR_DIR, - CUSTOM_LS_TO_TEST, - CUSTOM_LAYOUT_DIR, - METADATA_SCENES_TO_TEST, ) -from .utils import run_renderer, compare_renderer_args -from ..conftest import props_to_record +from .utils import compare_renderer_args, run_renderer ############################################################################## # Bit-exactness tests @@ -65,6 +66,7 @@ from ..conftest import props_to_record @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_ambisonics( record_property, props_to_record, @@ -72,6 +74,7 @@ def test_ambisonics( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -79,6 +82,26 @@ def test_ambisonics( get_odg_bin, split_comparison, ): + if in_fmt == "HOA2": + if out_fmt != "5_1": + pytest.skip() + if frame_size != "20ms": + pytest.skip() + if fs != "48kHz": + pytest.skip() + + if in_fmt == "FOA": + if frame_size != "5ms": + pytest.skip() + if fs != "32kHz": + pytest.skip() + + if in_fmt == "HOA3": + if frame_size != "20ms": + pytest.skip() + if fs == "32kHz": + pytest.skip() + run_renderer( record_property, props_to_record, @@ -93,12 +116,14 @@ def test_ambisonics( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_ambisonics_binaural_static( record_property, props_to_record, @@ -106,6 +131,7 @@ def test_ambisonics_binaural_static( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -113,6 +139,15 @@ def test_ambisonics_binaural_static( get_odg_bin, split_comparison, ): + if in_fmt != "HOA2": + pytest.skip() + + if frame_size != "20ms": + pytest.skip() + + if fs != "48kHz": + pytest.skip() + run_renderer( record_property, props_to_record, @@ -127,6 +162,7 @@ def test_ambisonics_binaural_static( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -134,6 +170,7 @@ def test_ambisonics_binaural_static( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_ambisonics_binaural_headrotation( record_property, props_to_record, @@ -142,6 +179,7 @@ def test_ambisonics_binaural_headrotation( out_fmt, trj_file, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -149,6 +187,14 @@ def test_ambisonics_binaural_headrotation( get_odg_bin, split_comparison, ): + if in_fmt == "HOA2": + pytest.skip() + + if in_fmt == "FOA" and frame_size != "20ms" and fs != "48kHz": + pytest.skip() + + # Run everything for HOA3 input + run_renderer( record_property, props_to_record, @@ -164,14 +210,25 @@ def test_ambisonics_binaural_headrotation( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) -@pytest.mark.skip(reason="Not supported for BASOP code currently") @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL[2:]) -@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +@pytest.mark.parametrize( + "in_fmt", + [ + *INPUT_FORMATS_AMBI, + *INPUT_FORMATS_MC, + *INPUT_FORMATS_ISM, + *INPUT_FORMATS_MASA, + *INPUT_FORMATS_OMASA, + *INPUT_FORMATS_OSBA, + ], +) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) @pytest.mark.parametrize("aeid", ["1", "0"]) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_dynamic_acoustic_environment( record_property, props_to_record, @@ -179,6 +236,7 @@ def test_dynamic_acoustic_environment( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -187,8 +245,20 @@ def test_dynamic_acoustic_environment( aeid, split_comparison, ): - rend_config_path = TEST_VECTOR_DIR.joinpath(f"rend_config_combined.cfg") - rend_config_path.with_stem(f"rend_config") + if in_fmt in ["MONO", "STEREO"]: + pytest.skip("MONO or STEREO to Binaural rendering unsupported") + + if in_fmt not in ["ISM4", "HOA3", "5_1", "MASA2", "ISM1MASA1", "ISM1SBA1"]: + pytest.skip() + + if frame_size == "5ms": + pytest.skip() + + if fs == "32kHz" and in_fmt in ["ISM4", "HOA3", "ISM1MASA1"]: + pytest.skip() + + rend_config_path = TEST_VECTOR_DIR.joinpath("rend_config_combined.cfg") + rend_config_path.with_stem("rend_config") run_renderer( record_property, @@ -206,13 +276,24 @@ def test_dynamic_acoustic_environment( config_file=rend_config_path, aeid=aeid, split_comparison=split_comparison, + sr=fs, ) -@pytest.mark.skip("MSAN errors in BASOP need to be fixed") @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL[2:]) -@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +@pytest.mark.parametrize( + "in_fmt", + [ + *INPUT_FORMATS_AMBI, + *INPUT_FORMATS_MC, + *INPUT_FORMATS_ISM, + *INPUT_FORMATS_MASA, + *INPUT_FORMATS_OMASA, + *INPUT_FORMATS_OSBA, + ], +) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_dynamic_acoustic_environment_file( record_property, props_to_record, @@ -220,6 +301,7 @@ def test_dynamic_acoustic_environment_file( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -227,10 +309,22 @@ def test_dynamic_acoustic_environment_file( get_odg_bin, split_comparison, ): - rend_config_path = TEST_VECTOR_DIR.joinpath(f"rend_config_combined.cfg") - rend_config_path.with_stem(f"rend_config") + if in_fmt in ["MONO", "STEREO"]: + pytest.skip("MONO or STEREO to Binaural rendering unsupported") + + if in_fmt not in ["ISM4", "HOA3", "5_1", "MASA2", "ISM1MASA1", "ISM1SBA1"]: + pytest.skip() + + if frame_size == "5ms": + pytest.skip() + + if fs == "16kHz" and in_fmt not in ["ISM4", "HOA3", "ISM1MASA1"]: + pytest.skip() - aeid = TEST_VECTOR_DIR.joinpath(f"aeid1.txt") + rend_config_path = TEST_VECTOR_DIR.joinpath("rend_config_combined.cfg") + rend_config_path.with_stem("rend_config") + + aeid = TEST_VECTOR_DIR.joinpath("aeid1.txt") run_renderer( record_property, @@ -248,6 +342,7 @@ def test_dynamic_acoustic_environment_file( config_file=rend_config_path, aeid=aeid, split_comparison=split_comparison, + sr=fs, ) @@ -257,6 +352,7 @@ def test_dynamic_acoustic_environment_file( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MC) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_multichannel( record_property, props_to_record, @@ -264,6 +360,7 @@ def test_multichannel( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -271,6 +368,18 @@ def test_multichannel( get_odg_bin, split_comparison, ): + if in_fmt not in ["STEREO", "7_1_4"]: + if frame_size != "20ms": + pytest.skip() + if fs != "48kHz": + pytest.skip() + if out_fmt not in ["MONO", "STEREO", "FOA", "7_1_4"]: + pytest.skip() + if frame_size != "5ms": + pytest.skip() + + # Run everything for 7_1_4 input + run_renderer( record_property, props_to_record, @@ -285,12 +394,14 @@ def test_multichannel( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MC) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_multichannel_binaural_static( record_property, props_to_record, @@ -298,6 +409,7 @@ def test_multichannel_binaural_static( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -308,6 +420,15 @@ def test_multichannel_binaural_static( if in_fmt in ["MONO", "STEREO"]: pytest.skip("MONO or STEREO to Binaural rendering unsupported") + if fs != "48kHz": + pytest.skip() + + if frame_size != "20ms": + pytest.skip() + + if in_fmt not in ["5_1", "7_1_4"]: + pytest.skip() + run_renderer( record_property, props_to_record, @@ -322,6 +443,7 @@ def test_multichannel_binaural_static( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -329,6 +451,7 @@ def test_multichannel_binaural_static( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MC) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_multichannel_binaural_headrotation( record_property, props_to_record, @@ -337,6 +460,7 @@ def test_multichannel_binaural_headrotation( out_fmt, trj_file, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -347,6 +471,16 @@ def test_multichannel_binaural_headrotation( if in_fmt in ["MONO", "STEREO"]: pytest.skip("MONO or STEREO to Binaural rendering unsupported") + if in_fmt != "5_1_4": + if fs != "48kHz": + pytest.skip() + if in_fmt in ["MONO, 5_1, 7_1_4"] and frame_size != "20ms": + pytest.skip() + if in_fmt not in ["MONO, 5_1, 7_1_4"] and frame_size == "20ms": + pytest.skip() + + # Run everything for 5_1_4 + run_renderer( record_property, props_to_record, @@ -362,6 +496,7 @@ def test_multichannel_binaural_headrotation( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -371,6 +506,7 @@ def test_multichannel_binaural_headrotation( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_ISM) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_ism( record_property, props_to_record, @@ -378,6 +514,7 @@ def test_ism( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -385,6 +522,22 @@ def test_ism( get_odg_bin, split_comparison, ): + if in_fmt != "ISM4": + if frame_size != "20ms": + pytest.skip() + if fs != "48kHz": + pytest.skip() + if out_fmt not in ["STEREO", "5_1"]: + pytest.skip() + + if out_fmt not in ["HOA3", "5_1_2"]: + if frame_size != "5ms": + pytest.skip() + if fs != "32kHz": + pytest.skip() + + # Run almost everything for ISM4 + run_renderer( record_property, props_to_record, @@ -399,12 +552,14 @@ def test_ism( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_ISM) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_ism_binaural_static( record_property, props_to_record, @@ -412,6 +567,7 @@ def test_ism_binaural_static( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -419,6 +575,20 @@ def test_ism_binaural_static( get_odg_bin, split_comparison, ): + if in_fmt != "ISM4": + if frame_size != "20ms": + pytest.skip() + if fs != "48kHz": + pytest.skip() + + if fs == "32kHz" and frame_size == "5ms": + pytest.skip() + + if fs == "16kHz" and frame_size == "20ms": + pytest.skip() + + # Run everything for ISM4 + run_renderer( record_property, props_to_record, @@ -433,6 +603,7 @@ def test_ism_binaural_static( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -440,6 +611,7 @@ def test_ism_binaural_static( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_ISM) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_ism_binaural_headrotation( record_property, props_to_record, @@ -448,6 +620,7 @@ def test_ism_binaural_headrotation( out_fmt, trj_file, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -455,6 +628,14 @@ def test_ism_binaural_headrotation( get_odg_bin, split_comparison, ): + if in_fmt != "ISM4": + if frame_size != "5ms": + pytest.skip() + if fs != "32kHz": + pytest.skip() + + # Run everything for ISM4 + run_renderer( record_property, props_to_record, @@ -470,6 +651,7 @@ def test_ism_binaural_headrotation( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -479,6 +661,7 @@ def test_ism_binaural_headrotation( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MASA) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_masa( record_property, props_to_record, @@ -486,6 +669,7 @@ def test_masa( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -493,6 +677,46 @@ def test_masa( get_odg_bin, split_comparison, ): + if fs == "32kHz" and out_fmt in [ + "STEREO", + "5_1", + "5_1_2", + "5_1_4", + "7_1", + "7_1_4", + "FOA", + "HOA2", + ]: + pytest.skip() + + if fs == "16kHz" and out_fmt in [ + "5_1_2", + "5_1_4", + "7_1", + "7_1_4", + "FOA", + "HOA2", + "HOA3", + ]: + pytest.skip() + + if fs in ["16kHz", "32kHz"] and frame_size == "5ms": + pytest.skip() + + if ( + out_fmt in ["5_1_2", "5_1_4", "7_1", "HOA2", "HOA3"] + and frame_size != "20ms" + and in_fmt == "MASA1" + ): + pytest.skip() + + if ( + out_fmt in ["5_1_2", "7_1_4", "5_1", "FOA"] + and frame_size != "20ms" + and in_fmt == "MASA2" + ): + pytest.skip() + run_renderer( record_property, props_to_record, @@ -507,12 +731,14 @@ def test_masa( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MASA) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_masa_binaural_static( record_property, props_to_record, @@ -520,6 +746,7 @@ def test_masa_binaural_static( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -527,8 +754,10 @@ def test_masa_binaural_static( get_odg_bin, split_comparison, ): - if out_fmt in ["BINAURAL_ROOM_IR", "BINAURAL_ROOM_REVERB"]: - pytest.skip("Skipping binaural room outputs for MASA as unimplemented.") + if fs in ["16kHz", "32kHz"] and frame_size == "5ms": + pytest.skip() + + # fs == 48 tests everything run_renderer( record_property, @@ -544,6 +773,7 @@ def test_masa_binaural_static( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -551,6 +781,7 @@ def test_masa_binaural_static( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MASA) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_masa_binaural_headrotation( record_property, props_to_record, @@ -559,6 +790,7 @@ def test_masa_binaural_headrotation( out_fmt, trj_file, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -566,8 +798,13 @@ def test_masa_binaural_headrotation( get_odg_bin, split_comparison, ): - if out_fmt in ["BINAURAL_ROOM_IR", "BINAURAL_ROOM_REVERB"]: - pytest.skip("Skipping binaural room outputs for MASA as unimplemented.") + if fs == 32 and frame_size == "20ms": + pytest.skip() + + if fs == 16 and frame_size == "5ms": + pytest.skip() + + # fs == 48 tests everything run_renderer( record_property, @@ -584,15 +821,57 @@ def test_masa_binaural_headrotation( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) -@pytest.mark.parametrize("in_fmt", METADATA_SCENES_TO_TEST_MASA_PREREND) +@pytest.mark.parametrize("out_fmt", INPUT_FORMATS_MASA) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OMASA) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_masa_prerend( record_property, props_to_record, test_info, in_fmt, + out_fmt, + fs, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + if in_fmt not in ["ISM4MASA2", "ISM1MASA1"] and fs != "48kHz": + pytest.skip() + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +@pytest.mark.parametrize("out_fmt", INPUT_FORMATS_MASA) +@pytest.mark.parametrize("in_fmt", METADATA_SCENES_TO_TEST_MASA_PREREND) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_masa_prerend_scenes( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, get_mld, get_mld_lim, get_ssnr, @@ -600,20 +879,326 @@ def test_masa_prerend( get_odg_bin, split_comparison, ): + # Test all scenes + run_renderer( record_property, props_to_record, test_info, "META", - "MASA2", - metadata_input=TEST_VECTOR_DIR.joinpath(f"{in_fmt}.txt"), + out_fmt, + metadata_input=TEST_VECTOR_DIR.joinpath(f"{in_fmt}_{fs[:2]}.txt"), + binary_suffix=EXE_SUFFIX, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +""" OMASA """ + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OMASA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_omasa( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + if in_fmt != "ISM4MASA2": + if fs != "48kHz": + pytest.skip() + if frame_size != "20ms": + pytest.skip() + if out_fmt not in ["STEREO", "5_1"]: + pytest.skip() + + if in_fmt in ["ISM3MASA1", "ISM2MASA2", "ISM1MASA1"]: + pytest.skip() + + if out_fmt in ["5_1", "5_1_4", "7_1_4", "FOA", "HOA2"]: + if frame_size != "20ms": + pytest.skip() + if fs != "16kHz": + pytest.skip() + + # Test all for ISM4MASA2 + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OMASA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_omasa_binaural_static( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + if in_fmt != "ISM4MASA2": + if fs != "48kHz": + pytest.skip() + if frame_size != "20ms": + pytest.skip() + + if in_fmt in ["ISM3MASA2", "ISM2MASA1", "ISM1MASA2", "ISM4MASA1"]: + pytest.skip() + + # Test all for ISM4MASA2 + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +@pytest.mark.parametrize("trj_file", HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OMASA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_omasa_binaural_headrotation( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, + trj_file, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + if in_fmt not in ["ISM1MASA1", "ISM2MASA2", "ISM4MASA2"]: + if fs != "48kHz": + pytest.skip() + if frame_size != "20ms": + pytest.skip() + + # Test all for ["ISM1MASA1", "ISM2MASA2", "ISM4MASA2"] + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file=HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), binary_suffix=EXE_SUFFIX, + frame_size=frame_size, get_mld=get_mld, mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, + ) + + +""" OSBA """ + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OSBA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_osba( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + if in_fmt != "ISM4SBA3": + if fs != "48kHz": + pytest.skip() + if frame_size != "20ms": + pytest.skip() + if out_fmt not in ["HOA3", "5_1"]: + pytest.skip() + + if out_fmt in ["HOA3", "5_1"]: + pytest.skip() + # Test all for ISM4SBA3 + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OSBA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_osba_binaural_static( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + if in_fmt != "ISM4SBA3": + pytest.skip() + + if fs != "48kHz": + pytest.skip() + + if frame_size != "20ms": + pytest.skip() + + # Test all for ISM4SBA3 + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, + ) + + +@pytest.mark.parametrize("trj_file", HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OSBA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) +def test_osba_binaural_headrotation( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + fs, + trj_file, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + if in_fmt not in ["ISM1SBA1", "ISM2SBA2", "ISM4SBA3"]: + if fs != "48kHz": + pytest.skip() + if frame_size != "20ms": + pytest.skip() + + # Test all for ["ISM1SBA1", "ISM2SBA2", "ISM4SBA3"] + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file=HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + sr=fs, ) @@ -623,6 +1208,7 @@ def test_masa_prerend( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) @pytest.mark.parametrize("in_layout", CUSTOM_LS_TO_TEST) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_custom_ls_input( record_property, props_to_record, @@ -630,6 +1216,7 @@ def test_custom_ls_input( in_layout, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -637,6 +1224,14 @@ def test_custom_ls_input( get_odg_bin, split_comparison, ): + if out_fmt not in ["7_1, FOA, STEREO"]: + if fs != "48kHz": + pytest.skip() + if frame_size != "20ms": + pytest.skip() + + # Test rest + run_renderer( record_property, props_to_record, @@ -651,20 +1246,30 @@ def test_custom_ls_input( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", CUSTOM_LS_TO_TEST) @pytest.mark.parametrize( "in_fmt", - [*INPUT_FORMATS_AMBI, *INPUT_FORMATS_MC, *INPUT_FORMATS_ISM, *INPUT_FORMATS_MASA], + [ + *INPUT_FORMATS_AMBI, + *INPUT_FORMATS_MC, + *INPUT_FORMATS_ISM, + *INPUT_FORMATS_MASA, + *INPUT_FORMATS_OMASA, + *INPUT_FORMATS_OSBA, + ], ) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_custom_ls_output( record_property, props_to_record, test_info, in_fmt, out_fmt, + fs, get_mld, get_mld_lim, get_ssnr, @@ -672,11 +1277,23 @@ def test_custom_ls_output( get_odg_bin, split_comparison, ): - # TODO: revert once BASOP is brought up-to-date - if in_fmt in INPUT_FORMATS_MASA: - pytest.skip("MASA to custom LS not supported on ivas-float-update yet") - if in_fmt in INPUT_FORMATS_ISM and out_fmt == "t_design_4": - pytest.skip("ISMx + t_design_4 skipped until bug in BASOP is fixed") + if in_fmt not in [ + "HOA3", + "MONO", + "STEREO", + "5_1", + "7_1_4", + "MASA1", + "MASA2", + "ISM4", + "ISM4MASA2", + "ISM4SBA3", + ]: + pytest.skip() + + if out_fmt == "t_design_4" and fs != "48kHz": + pytest.skip() + run_renderer( record_property, props_to_record, @@ -690,17 +1307,20 @@ def test_custom_ls_output( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", CUSTOM_LS_TO_TEST) @pytest.mark.parametrize("in_fmt", CUSTOM_LS_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_custom_ls_input_output( record_property, props_to_record, test_info, in_fmt, out_fmt, + fs, get_mld, get_mld_lim, get_ssnr, @@ -721,12 +1341,14 @@ def test_custom_ls_input_output( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_layout", CUSTOM_LS_TO_TEST) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_custom_ls_input_binaural( record_property, props_to_record, @@ -734,6 +1356,7 @@ def test_custom_ls_input_binaural( in_layout, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -741,6 +1364,9 @@ def test_custom_ls_input_binaural( get_odg_bin, split_comparison, ): + if fs != "48kHz" or frame_size != "20ms": + pytest.skip() + run_renderer( record_property, props_to_record, @@ -755,6 +1381,7 @@ def test_custom_ls_input_binaural( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -762,6 +1389,7 @@ def test_custom_ls_input_binaural( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) @pytest.mark.parametrize("in_layout", CUSTOM_LS_TO_TEST) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_custom_ls_input_binaural_headrotation( record_property, props_to_record, @@ -770,6 +1398,7 @@ def test_custom_ls_input_binaural_headrotation( out_fmt, trj_file, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -777,6 +1406,12 @@ def test_custom_ls_input_binaural_headrotation( get_odg_bin, split_comparison, ): + if fs == "32kHz" and frame_size == "5ms": + pytest.skip() + + if fs == "16kHz" and frame_size == "20ms": + pytest.skip() + run_renderer( record_property, props_to_record, @@ -792,6 +1427,7 @@ def test_custom_ls_input_binaural_headrotation( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -801,6 +1437,7 @@ def test_custom_ls_input_binaural_headrotation( @pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) @pytest.mark.parametrize("in_fmt", METADATA_SCENES_TO_TEST) @pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_metadata( record_property, props_to_record, @@ -808,6 +1445,7 @@ def test_metadata( in_fmt, out_fmt, frame_size, + fs, get_mld, get_mld_lim, get_ssnr, @@ -815,13 +1453,22 @@ def test_metadata( get_odg_bin, split_comparison, ): + if fs == "32kHz" and frame_size == "5ms": + pytest.skip() + + if fs == "16kHz" and frame_size == "20ms": + pytest.skip() + + if out_fmt not in ["5_1_2", "HOA3", "STEREO"] and fs != "48kHz": + pytest.skip() + run_renderer( record_property, props_to_record, test_info, "META", out_fmt, - metadata_input=TEST_VECTOR_DIR.joinpath(f"{in_fmt}.txt"), + metadata_input=TEST_VECTOR_DIR.joinpath(f"{in_fmt}_{fs[:2]}.txt"), binary_suffix=EXE_SUFFIX, frame_size=frame_size, get_mld=get_mld, @@ -830,6 +1477,7 @@ def test_metadata( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -839,6 +1487,7 @@ def test_metadata( @pytest.mark.parametrize("out_fmt", ["STEREO"]) @pytest.mark.parametrize("in_fmt", ["MONO"]) @pytest.mark.parametrize("non_diegetic_pan", ["0", "-30", "45", "90", "-90"]) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_non_diegetic_pan_static( record_property, props_to_record, @@ -846,6 +1495,7 @@ def test_non_diegetic_pan_static( in_fmt, out_fmt, non_diegetic_pan, + fs, get_mld, get_mld_lim, get_ssnr, @@ -853,6 +1503,12 @@ def test_non_diegetic_pan_static( get_odg_bin, split_comparison, ): + if non_diegetic_pan in ["0", "45", "-90"] and fs == "32kHz": + pytest.skip() + + if non_diegetic_pan not in ["0", "45", "-90"] and fs == "16kHz": + pytest.skip() + run_renderer( record_property, props_to_record, @@ -867,18 +1523,21 @@ def test_non_diegetic_pan_static( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @pytest.mark.parametrize("out_fmt", ["STEREO"]) @pytest.mark.parametrize("in_fmt", ["ISM1"]) @pytest.mark.parametrize("non_diegetic_pan", ["0", "-30", "45", "90", "-90"]) +@pytest.mark.parametrize("fs", SAMPLING_RATES) def test_non_diegetic_pan_ism_static( record_property, props_to_record, test_info, in_fmt, out_fmt, + fs, non_diegetic_pan, get_mld, get_mld_lim, @@ -887,6 +1546,12 @@ def test_non_diegetic_pan_ism_static( get_odg_bin, split_comparison, ): + if non_diegetic_pan in ["0", "45", "-90"] and fs == "16kHz": + pytest.skip() + + if non_diegetic_pan not in ["0", "45", "-90"] and fs == "32kHz": + pytest.skip() + run_renderer( record_property, props_to_record, @@ -901,6 +1566,7 @@ def test_non_diegetic_pan_ism_static( get_odg=get_odg, get_odg_bin=get_odg_bin, split_comparison=split_comparison, + sr=fs, ) @@ -1044,31 +1710,27 @@ def test_ambisonics_binaural_headrotation_refvecequal( if test_info.config.option.create_ref or test_info.config.option.create_cut: pytest.skip("OTR tests only run for smoke test") - # TODO revert - if in_fmt == "HOA3" and out_fmt == "BINAURAL_ROOM_REVERB": - pytest.xfail("WIP : minor differences to be resolved") - else: - compare_renderer_args( - record_property, - props_to_record, - test_info, - in_fmt, - out_fmt, - ref_kwargs={ - "name_extension": "refvecequal", - "frame_size": "5", - }, - cut_kwargs={ - "trj_file": HR_TRAJECTORY_DIR.joinpath( - "full-circle-with-up-and-down-4s.csv" - ), - "refvec_file": HR_TRAJECTORY_DIR.joinpath( - "full-circle-with-up-and-down-4s-Vector3.csv" - ), - "frame_size": "5", - }, - split_comparison=split_comparison, - ) + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refvecequal", + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s.csv" + ), + "refvec_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s-Vector3.csv" + ), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) # This test compares rendering with: @@ -1088,32 +1750,28 @@ def test_ambisonics_binaural_headrotation_refvec_rotating( if test_info.config.option.create_ref or test_info.config.option.create_cut: pytest.skip("OTR tests only run for smoke test") - # TODO revert - if in_fmt == "HOA2" and out_fmt == "BINAURAL_ROOM_REVERB": - pytest.xfail("WIP : minor differences to be resolved") - else: - compare_renderer_args( - record_property, - props_to_record, - test_info, - in_fmt, - out_fmt, - ref_kwargs={ - "name_extension": "refvec_rotating", - "trj_file": HR_TRAJECTORY_DIR.joinpath( - "full-circle-with-up-and-down-4s.csv" - ), - "frame_size": "5", - }, - cut_kwargs={ - "trj_file": HR_TRAJECTORY_DIR.joinpath("const000.csv"), - "refvec_file": HR_TRAJECTORY_DIR.joinpath( - "full-circle-with-up-and-down-4s-ccw-Vector3.csv" - ), - "frame_size": "5", - }, - split_comparison=split_comparison, - ) + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refvec_rotating", + "trj_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s.csv" + ), + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath("const000.csv"), + "refvec_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s-ccw-Vector3.csv" + ), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) # This test compares rendering with: diff --git a/tests/renderer_short/utils.py b/tests/renderer_short/utils.py index 9afda68b940d90511cf4cb75485f0f526495de3f..1aa7370f08dd8d919e2cf028bf3e4cfb223447b8 100644 --- a/tests/renderer_short/utils.py +++ b/tests/renderer_short/utils.py @@ -30,44 +30,44 @@ accordance with the laws of the Federal Republic of Germany excluding its confli the United Nations Convention on Contracts on the International Sales of Goods. """ +import errno import filecmp import logging import os -from pathlib import Path +import re import subprocess as sp import sys +import tempfile +from pathlib import Path from typing import Dict, Optional, Union import numpy as np import pytest -import re -import errno -import tempfile +from ..constants import CAT_NORMAL from .compare_audio import compare_audio_arrays from .constants import ( - LTV_DIR, - SCRIPTS_DIR, - OUTPUT_PATH_REF, - OUTPUT_PATH_CUT, + BIN_SUFFIX_MERGETARGET, FORMAT_TO_FILE_COMPARETEST, FORMAT_TO_FILE_LTV, + FORMAT_TO_FILE_SMOKETEST, FORMAT_TO_METADATA_FILES, FORMAT_TO_METADATA_FILES_LTV, - FORMAT_TO_FILE_SMOKETEST, - RENDERER_CMD, - BIN_SUFFIX_MERGETARGET, + LTV_DIR, + OUTPUT_PATH_CUT, + OUTPUT_PATH_REF, PEAQ_SUPPORTED_FMT, + RENDERER_CMD, + SCRIPTS_DIR, ) -from ..constants import CAT_NORMAL sys.path.append(SCRIPTS_DIR) from pyaudio3dtools.audiofile import readfile -from ..cmp_pcm import cmp_pcm -from ..conftest import parse_properties, get_split_idx +from ..cmp_pcm import cmp_pcm +from ..conftest import get_split_idx, parse_properties -def _run_cmd(cmd, env, test_info=None): +def _run_cmd(cmd, test_info=None, env=None ): """ Helper function for running some command. Raises a SystemError if either the return code is non-zero or a USAN printout is detected @@ -92,29 +92,30 @@ def _run_cmd(cmd, env, test_info=None): raise SystemError(error) -def run_cmd(cmd, test_info, env=None): +def run_cmd(cmd, test_info=None, env=None): logging.info(f"\nRunning command\n{' '.join(cmd)}\n") - _run_cmd(cmd, env, test_info) + _run_cmd(cmd, test_info=test_info, env=env) -def run_isar_ext_rend_cmd(cmd, env=None): +def run_isar_ext_rend_cmd(cmd, test_info=None, env=None): logging.info(f"\nRunning ISAR EXT REND command\n{' '.join(cmd)}\n") - _run_cmd(cmd, env) + _run_cmd(cmd, test_info=test_info, env=env) -def run_ivas_isar_enc_cmd(cmd, env=None): +def run_ivas_isar_enc_cmd(cmd, test_info=None, env=None): logging.info(f"\nRunning IVAS ISAR encoder command\n{' '.join(cmd)}\n") - _run_cmd(cmd, env) - - -def run_ivas_isar_dec_cmd(cmd, env=None): - logging.info(f"\nDUT decoder command:\n\t{' '.join(cmd)}\n") - _run_cmd(cmd, env) + _run_cmd(cmd, test_info=test_info, env=env) +def run_ivas_isar_dec_cmd(cmd, test_info=None, env=None): + if BIN_SUFFIX_MERGETARGET in cmd[0]: + logging.info(f"\nREF decoder command:\n\t{' '.join(cmd)}\n") + else: + logging.info(f"\nDUT decoder command:\n\t{' '.join(cmd)}\n") + _run_cmd(cmd, test_info=test_info, env=env) -def run_isar_post_rend_cmd(cmd, env=None): +def run_isar_post_rend_cmd(cmd, test_info=None, env=None): logging.info(f"\nRunning ISAR post renderer command\n{' '.join(cmd)}\n") - _run_cmd(cmd, env) + _run_cmd(cmd, test_info=test_info, env=env) def check_BE( @@ -175,7 +176,7 @@ def run_renderer( aeid: Optional[Union[Path, int]] = None, in_file=None, out_file=None, - sr=48, + sr="48kHz", render_for_peaq=False, split_comparison=False, ) -> str: @@ -267,12 +268,20 @@ def run_renderer( else: in_file = format_to_file[in_fmt] in_name = in_fmt + in_file = str(in_file).replace("48", sr[:2]) if in_meta_files is None and in_fmt in format_to_metadata_files: in_meta_files = format_to_metadata_files[in_fmt] + # If metadata not given with ISM input, use default NULL + if in_meta_files is None and isinstance(in_fmt, str) and "ism" in in_fmt.lower(): + match = re.search(r"ism(\d)", in_fmt.lower()) + assert match is not None + num_obj = int(match[1]) + in_meta_files = ["NULL"] * num_obj + if out_file is None: - out_file_stem = f"{in_name}_to_{out_name}{trj_name}{non_diegetic_pan}{refrot_name}{refvec_name}{refveclev_name}{config_name}{framing_name}{hrtf_file_name}{name_extension}{aeid_name}.wav" + out_file_stem = f"{in_name}_to_{out_name}{trj_name}{non_diegetic_pan}{refrot_name}{refvec_name}{refveclev_name}{config_name}{framing_name}{hrtf_file_name}{name_extension}{aeid_name}_{sr}.wav" out_file = str(output_path_base.joinpath(out_file_stem)) cmd = RENDERER_CMD[:] @@ -280,7 +289,7 @@ def run_renderer( cmd[4] = str(in_fmt) cmd[6] = str(out_file) cmd[8] = str(out_fmt) - cmd[10] = str(sr) + cmd[10] = str(sr[:2]) if test_info.config.option.create_ref: cmd[0] += BIN_SUFFIX_MERGETARGET @@ -370,7 +379,7 @@ def run_renderer( cmd2[4] = str(out_fmt) # in_fmt cmd2[6] = odg_test # out_file cmd2[8] = new_fmt # out_fmt - cmd2[10] = str(sr) + cmd2[10] = str(sr[:2]) cmd2[0] += BIN_SUFFIX_MERGETARGET # Use IVAS_rend_ref for re-rendering cmd2[0] += binary_suffix if "MASA" in str(out_fmt): @@ -551,8 +560,8 @@ def binauralize_input_and_output( # If extended metadata is not used, strip the metadata for the external renderer extended_md_used = ( re.search(r"-ism\s?\+[1-4]", enc_opts) - and not "OMASA" in in_fmt - and not "OSBA" in in_fmt + and "OMASA" not in in_fmt + and "OSBA" not in in_fmt ) if not extended_md_used and n_obj > 0: truncated_meta_files = [] @@ -634,7 +643,7 @@ def binauralize_input_and_output( aeid = findstr(r"-aeid\s+(\S+)", dec_opts) - if not output_config.upper() in PEAQ_SUPPORTED_FMT: + if output_config.upper() not in PEAQ_SUPPORTED_FMT: # Render output to BINAURAL output_reformat = "BINAURAL" diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py index b09333fc46c5b2797607efb6d11b5078ec033924..ad54db8d35d6a14150de0f7213c82fe1ceef9a08 100644 --- a/tests/rtp/ivasrtp.py +++ b/tests/rtp/ivasrtp.py @@ -43,6 +43,8 @@ import base64 import argparse from pathlib import Path from typing import cast, Optional +import numpy as np +import ctypes NO_REQ = "NO_REQ" @@ -174,12 +176,12 @@ class PIDATAS(str, Enum): ISM_GAIN = "ISM_GAIN" ISM_ORIENTATION = "ISM_ORIENTATION" ISM_POSITION = "ISM_POSITION" + ISM_POSITION_COMPACT = "ISM_POSITION_COMPACT" ISM_DISTANCE_ATTENUATION = "ISM_DISTANCE_ATTENUATION" ISM_DIRECTIVITY = "ISM_DIRECTIVITY" DIEGETIC_TYPE = "DIEGETIC_TYPE" DYNAMIC_AUDIO_SUPPRESSION_INDICATION = "DYNAMIC_AUDIO_SUPPRESSION_INDICATION" AUDIO_FOCUS_INDICATION = "AUDIO_FOCUS_INDICATION" - RESERVED15 = "RESERVED15" PLAYBACK_DEVICE_ORIENTATION = "PLAYBACK_DEVICE_ORIENTATION" HEAD_ORIENTATION = "HEAD_ORIENTATION" LISTENER_POSITION = "LISTENER_POSITION" @@ -190,11 +192,11 @@ class PIDATAS(str, Enum): R_ISM_GAIN = "R_ISM_GAIN" R_ISM_ORIENTATION = "R_ISM_ORIENTATION" R_ISM_POSITION = "R_ISM_POSITION" + R_ISM_POSITION_COMPACT = "R_ISM_POSITION_COMPACT" R_ISM_DIRECTION = "R_ISM_DIRECTION" RESERVED27 = "RESERVED27" RESERVED28 = "RESERVED28" RESERVED29 = "RESERVED29" - RESERVED30 = "RESERVED30" NO_PI_DATA = "NO_PI_DATA" @@ -389,11 +391,72 @@ class ACOUSTIC_ENVIRONMENT: abscoeff: tuple[float, float, float, float, float, float] = () +@dataclass +class ISM_NUM: + num: int = 1 + +@dataclass +class ISM_ID: + ids: list[int] + +@dataclass +class ISM_GAIN: + gains: list[int] + +@dataclass +class ISM_ORIENTATION: + orientations: list[ORIENTATION] + +@dataclass +class ISM_POSITION: + positions: list[POSITION] + +class ISM_POSITION_COMPACT: + positions: list[POSITION] + +@dataclass +class DISTANCE_ATTENUATION: + ref_dist: float = 1.0 + max_dist: int = 10 + roll_off: float = 1.0 + +@dataclass +class ISM_DISTANCE_ATTENUATION: + distance_attenuations: list[DISTANCE_ATTENUATION] + +@dataclass +class DIRECTIVITY: + inner_ang: int = 360 + outer_ang: int = 0 + outer_att: int = 0 + +@dataclass +class ISM_DIRECTIVITY: + directivities: list[DIRECTIVITY] + +@dataclass +class R_ISM_ID: + id: int + +@dataclass +class R_ISM_GAIN: + gain: int + +@dataclass +class R_ISM_DIRECTION: + azi: float + elev: float + @dataclass class AUDIO_FOCUS: direction: Optional[ORIENTATION] = None level: Optional[AUDIO_FOCUS_LEVEL] = None +@dataclass +class PI_LATENCY: + reverseType: PIDATAS + latency: int + @dataclass class PIDATA: @@ -402,7 +465,7 @@ class PIDATA: data: any = None -MAX_PACKED_PI_SIZE = 32 +MAX_PACKED_PI_SIZE = 48 ivasBitrates = [ 13200, 16400, @@ -581,10 +644,23 @@ roomDimensionValue = [ 90.51, ] absorptionCoeffValues = [0.0800, 0.1656, 0.3430, 0.7101] +ismGains = list(range(0, -25, -1)) + [-128] + list(range(1, 13)) # -128 corresponds to -Inf +refDistances = [round(x * 0.1, 1) for x in range(1, 65)] +maxDistances = list(range(1,65)) +rolloffFactors = [round(x * 0.1, 1) for x in range(0, 41)] +innerOuterAngles = list(range(0,361,15)) +outerAttenuations = [-128] + list(range(-90,1,3)) # -128 corresponds to -Inf codedFormats = list(FORMATS) codedSubFormats = list(SUBFORMATS) PiTypeNames = list(PIDATAS) +azimuthStepSize = 0.703125 +elevationStepSize = 1.417322835 +azimuthDirections = [round(x * azimuthStepSize - 180, 3) for x in range(1, 512)] +elevationDirections = [round(x * elevationStepSize - 90, 3) for x in range(0, 127)] + +MASK_3BIT = 0x7 +MASK_6BIT = 0x3F def mapNearestIndex(table: list, val: float) -> int: for idx, entry in enumerate(table): @@ -592,6 +668,17 @@ def mapNearestIndex(table: list, val: float) -> int: return idx return len(table) - 1 +def mapNearestIndexDirectionAzi(table: list, val: float) -> int: + for idx, entry in enumerate(table): + if abs(entry - val) < azimuthStepSize: + return idx + return len(table) - 1 + +def mapNearestIndexDirectionElev(table: list, val: float) -> int: + for idx, entry in enumerate(table): + if abs(entry - val) < elevationStepSize: + return idx + return len(table) - 1 getListIndex = lambda mylist, val: mylist.index(val) if val in mylist else -1 @@ -647,6 +734,9 @@ cmrLookup = [ ] q15 = lambda x: int(min(32767.0, max(-32768.0, x * 32768.0))) +q10 = lambda x: int(min(1023.0, max(-1024.0, x * 1024.0))) +q9 = lambda x: int(min(511.0, max(-512.0, x * 512.0))) +q7 = lambda x: int(min(127.0, max(-128.0, x * 128.0))) def unpackUnsupported(bitstrm: ConstBitStream, piSize: int) -> any: @@ -668,16 +758,28 @@ def packNoPiData(bitstrm: BitStream, data: any = None): def unpackOrientations(bitstrm: ConstBitStream, piSize: int) -> list[ORIENTATION]: assert ( - piSize % 8 - ) == 0 and piSize <= 32, "Incorrect PI Data Size for list[ORIENTATION]" + piSize % 4 + ) == 0 and piSize <= 16, "Incorrect PI Data Size for list[ORIENTATION]" orientations = list() + q = [(float)]*4 + qs = 0 while piSize > 0: - w = bitstrm.read(16).int / 32768.0 - x = bitstrm.read(16).int / 32768.0 - y = bitstrm.read(16).int / 32768.0 - z = bitstrm.read(16).int / 32768.0 + max_q_idx = bitstrm.read(2).uint + for i in range(0,4): + if i == max_q_idx: + continue + tmp = bitstrm.read(10).uint + q[i] = (tmp / 1023.0) * 2.0**0.5 - 1.0 / (2.0**0.5) + qs = qs + q[i]**2 + + q[max_q_idx] = (1 - qs)**0.5 + + w = q[0] + x = q[1] + y = q[2] + z = q[3] orientations.append(ORIENTATION(w, x, y, z)) - piSize -= 8 + piSize -= 4 return orientations @@ -687,10 +789,16 @@ def packOrientations(bitstrm: BitStream, data: any): assert ( type(orientation) == ORIENTATION ), "Orientation PI Data expects a data of type list[ORIENTATION]" - bitstrm.append(f"intbe:16={q15(orientation.w)}") - bitstrm.append(f"intbe:16={q15(orientation.x)}") - bitstrm.append(f"intbe:16={q15(orientation.y)}") - bitstrm.append(f"intbe:16={q15(orientation.z)}") + q = [orientation.w, orientation.x, orientation.y, orientation.z] + max_q = max(q, key=abs) + max_q_idx = q.index(max_q) + if max_q < 0: + q = [-x for x in q] + bitstrm.append(f"uint:2={max_q_idx}") + for i in range(0,4): + if i == max_q_idx: + continue + bitstrm.append(f"uint:10={(int)((q[i]+1/(2**0.5))*1023/(2**0.5))}") def unpackPositions(bitstrm: ConstBitStream, piSize: int) -> list[POSITION]: @@ -717,9 +825,33 @@ def packPositions(bitstrm: BitStream, data: any): bitstrm.append(f"intbe:16={q15(position.y / 327.68)}") bitstrm.append(f"intbe:16={q15(position.z / 327.68)}") +def unpackPositionsCompact(bitstrm: ConstBitStream, piSize: int) -> list[POSITION]: + assert piSize <= 16 and (piSize % 4) == 0, "Incorrect PI Data Size for Positions" + positions = list() + while piSize > 0: + x = bitstrm.read(11).int / 100.0 + y = bitstrm.read(11).int / 100.0 + z = bitstrm.read(10).int / 100.0 + positions.append(POSITION(x, y, z)) + piSize -= 4 + return positions + +def packPositionsCompact(bitstrm: BitStream, data: any): + assert type(data) == list, "Compact position PI Data expects a data of type list" + positions = cast(list, data) + assert len(positions) <= 4, "Max one compact position per ISM object" + for position in positions: + assert ( + type(position) == POSITION + ), "Compact position PI Data expects a data of type list[POSITIONS]" + + bitstrm.append(f"int:11={q10(position.x / 10.24)}") + bitstrm.append(f"int:11={q10(position.y / 10.24)}") + bitstrm.append(f"int:10={q9(position.z / 5.12)}") + def unpackOrientation(bitstrm: ConstBitStream, piSize: int) -> ORIENTATION: - assert piSize == 8, "Incorrect PI Data Size for ORIENTATION" + assert piSize == 4, "Incorrect PI Data Size for ORIENTATION" orientations = unpackOrientations(bitstrm, piSize) assert len(orientations) == 1 return orientations[0] @@ -745,6 +877,16 @@ def packPosition(bitstrm: BitStream, data: any): position = cast(POSITION, data) packPositions(bitstrm, [position]) +def unpackPositionCompact(bitstrm: ConstBitStream, piSize: int) -> POSITION: + assert piSize == 4, "Incorrect PI Data Size for compact POSITION" + positions = unpackPositionsCompact(bitstrm, piSize) + assert len(positions) == 1 + return positions[0] + +def packPositionCompact(bitstrm: BitStream, data: any): + assert type(data) == POSITION, "Compact position PI Data expects a data of type POSITION" + position = cast(POSITION, data) + packPositionsCompact(bitstrm, [position]) def unpackAudioDescription( bitstrm: ConstBitStream, piSize: int @@ -895,7 +1037,7 @@ def packAcousticEnv(bitstrm: BitStream, data: any): def unpackAudioFocus(bitstrm: ConstBitStream, piSize: int) -> AUDIO_FOCUS: assert ( - piSize == 1 or piSize == 8 or piSize == 9 + piSize == 1 or piSize == 4 or piSize == 5 ), "Incorrect PI Data Size for AUDIO_FOCUS" direction = None level = None @@ -903,8 +1045,8 @@ def unpackAudioFocus(bitstrm: ConstBitStream, piSize: int) -> AUDIO_FOCUS: level = bitstrm.read(4).uint _ = bitstrm.read(4) else: - direction = unpackOrientation(bitstrm, 8) - if piSize == 9: + direction = unpackOrientation(bitstrm, 4) + if piSize == 5: level = bitstrm.read(4).uint _ = bitstrm.read(4) @@ -923,6 +1065,157 @@ def packAudioFocus(bitstrm: BitStream, data: any): bitstrm.append(f"uint:4={level}") bitstrm.append(f"uint:4=0") +def unpackPiLatency(bitstrm: ConstBitStream, piSize: int) -> PI_LATENCY: + assert piSize == 4, "PI_LATENCY must be 4 bytes" + word = bitstrm.read(32).uint + typeBits = (word >> 27) & 0x1F + reverseType = PiTypeNames[typeBits] + raw = word & 0x07FFFFFF + # Sign-extend 27-bit + if raw & (1 << 26): + raw = raw | ~0x07FFFFFF + return PI_LATENCY(reverseType, int(raw)) + +def packPiLatency(bitstrm: BitStream, data: any) -> None: + assert type(data) == PI_LATENCY, "PI_LATENCY pack expects PI_LATENCY data" + idx = PiTypeNames.index(data.reverseType) + latency = data.latency & 0x07FFFFFF + word = (idx << 27) | latency + bitstrm.append(f"uint:32={word}") + +def unpackISMNum(bitstrm: ConstBitStream, piSize: int) -> ISM_NUM: + assert piSize == 1, "Incorrect PI Data Size for ISM_NUM" + numISM = bitstrm.read(2).uint + 1 + bitstrm.bytealign() + return ISM_NUM(num=numISM) + +def packISMNum(bitstrm: BitStream, data: any): + assert type(data) == ISM_NUM, "Data of type ISM_NUM is expected" + ism_num = cast(ISM_NUM, data) + assert ism_num.num <= 4, "Maximum 4 objects" + bitstrm.append(f'uint:2={ism_num.num-1}') + bitstrm.append(f'uint:6=0') + +def unpackISMID(bitstrm: ConstBitStream, piSize: int) -> ISM_ID: + assert piSize == 1 or piSize == 2 or piSize == 3 or piSize == 4, "Incorrect PI Data Size for ISM_ID" + IsmID = list() + for _ in range(piSize): + IsmID.append(bitstrm.read(8).uint+1) + return ISM_ID(ids=IsmID) + +def packISMID(bitstrm: BitStream, data: any): + assert type(data) == ISM_ID, "Data of type ISM_ID is expected" + ism_id = cast(ISM_ID, data) + assert len(ism_id.ids) <= 4, "Maximum 4 objects" + for id in ism_id.ids: + bitstrm.append(f'uint:8={id}') + +def unpackISMGain(bitstrm: ConstBitStream, piSize: int) -> ISM_GAIN: + assert piSize == 1 or piSize == 2 or piSize == 3 or piSize == 4, "Incorrect PI Data Size for ISM_GAIN" + IsmGain = list() + for _ in range(piSize): + IsmGain.append(bitstrm.read(6).uint) + bitstrm.bytealign() + return ISM_GAIN(gains=IsmGain) + +def packISMGain(bitstrm: BitStream, data: any): + assert type(data) == ISM_GAIN, "Data of type ISM_GAIN is expected" + ism_gain = cast(ISM_GAIN, data) + assert len(ism_gain.gains) <= 4, "Maximum 4 objects" + for gain in ism_gain.gains: + gain_idx = getListIndex(ismGains, gain) + bitstrm.append(f'uint:6={gain_idx}') + bitstrm.append(f'uint:2=0') + +def unpackISMDistanceAttenuation(bitstrm: ConstBitStream, piSize: int) -> ISM_DISTANCE_ATTENUATION: + ref_dist = None + max_dist = None + roll_off = None + assert piSize == 1*3 or piSize == 2*3 or piSize == 3*3 or piSize == 4*3, "Incorrect PI Data Size for ISM_DISTANCE_ATTENUATION" + ism_distance_attenutation = list() + for _ in range(piSize): + ref_dist = bitstrm.read(6).uint + max_dist = bitstrm.read(6).uint + roll_off = bitstrm.read(6).uint + ism_distance_attenutation.append(DISTANCE_ATTENUATION(ref_dist=ref_dist, max_dist=max_dist, roll_off=roll_off)) + bitstrm.bytealign() + return ISM_DISTANCE_ATTENUATION(distance_attenuations=ism_distance_attenutation) + +def packISMDistanceAttenuation(bitstrm: BitStream, data: any): + assert type(data) == list, "Data of type list is expected" + + for att in cast(list, data): + assert type(att) == DISTANCE_ATTENUATION, "Data of type list[DISTANCE_ATTENUATION] is expected" + ref_dist_idx = getListIndex(refDistances, att.ref_dist) + max_dist_idx = getListIndex(maxDistances, att.max_dist) + roll_off_idx = getListIndex(rolloffFactors, att.roll_off) + bitstrm.append(f'uint:6={ref_dist_idx}') + bitstrm.append(f'uint:6={max_dist_idx}') + bitstrm.append(f'uint:6={roll_off_idx}') + bitstrm.append(f'uint:6=0') + +def unpackISMDirectivity(bitstrm: ConstBitStream, piSize: int) -> list[DISTANCE_ATTENUATION]: + inner_ang = None + outer_ang = None + outer_att = None + assert piSize == 1*2 or piSize == 2*2 or piSize == 3*2 or piSize == 4*2, "Incorrect PI Data Size for ISM_DISTANCE_ATTENUATION" + directivities = list() + for _ in range(piSize): + inner_ang = bitstrm.read(5).uint + outer_ang = bitstrm.read(5).uint + outer_att = bitstrm.read(5).uint + directivities.append(DIRECTIVITY(inner_ang=inner_ang, outer_ang=outer_ang, outer_att=outer_att)) + bitstrm.bytealign() + return directivities + +def packISMDirectivity(bitstrm: BitStream, data: any): + assert type(data) == list, "Data of type ISM_DIRECTIVITY is expected" + + for dir in cast(list, data): + assert type(dir) == DIRECTIVITY, "Orientation PI Data expects a data of type list[DIRECTIVITY]" + inner_ang_idx = getListIndex(innerOuterAngles, dir.inner_ang) + outer_ang_idx = getListIndex(innerOuterAngles, dir.outer_ang) + outer_att_idx = getListIndex(outerAttenuations, dir.outer_att) + bitstrm.append(f'uint:5={inner_ang_idx}') + bitstrm.append(f'uint:5={outer_ang_idx}') + bitstrm.append(f'uint:5={outer_att_idx}') + bitstrm.append(f'uint:1=0') + +def unpackReverseISMID(bitstrm: ConstBitStream, piSize: int) -> R_ISM_ID: + assert piSize == 1, "Incorrect PI Data Size for R_ISM_ID" + id = bitstrm.read(8).uint + return R_ISM_ID(id=id) + +def packReverseISMID(bitstrm: BitStream, data: any): + assert type(data) == R_ISM_ID, "Data of type R_ISM_ID is expected" + r_ism_id = cast(R_ISM_ID, data) + bitstrm.append(f'uint:8={r_ism_id.id}') + +def unpackReverseISMGain(bitstrm: ConstBitStream, piSize: int) -> R_ISM_GAIN: + assert piSize == 1, "Incorrect PI Data Size for R_ISM_GAIN" + gain = bitstrm.read(6).uint + return R_ISM_GAIN(gain=gain) + +def packReverseISMGain(bitstrm: BitStream, data: any): + assert type(data) == R_ISM_GAIN, "Data of type R_ISM_GAIN is expected" + r_ism_gain = cast(R_ISM_GAIN, data) + gain_idx = getListIndex(ismGains, r_ism_gain.gain) + bitstrm.append(f'uint:6={gain_idx}') + bitstrm.append(f'uint:2=0') + +def unpackReverseISMDirection(bitstrm: ConstBitStream, piSize: int) -> R_ISM_DIRECTION: + assert piSize == 2, "Incorrect PI Data Size for R_ISM_DIRECTION" + azimuth = bitstrm.read(9).uint + elevation = bitstrm.read(7).uint + return R_ISM_DIRECTION(azi=azimuth, elev=elevation) + +def packReverseISMDirection(bitstrm: BitStream, data: any): + assert type(data) == R_ISM_DIRECTION, "Data of type R_ISM_DIRECTION is expected" + direction = cast(R_ISM_DIRECTION, data) + azimuth_idx = mapNearestIndexDirectionAzi(azimuthDirections, direction.azi) + elevation_idx = mapNearestIndexDirectionElev(elevationDirections, direction.elev) + bitstrm.append(f'uint:9={azimuth_idx}') + bitstrm.append(f'uint:7={elevation_idx}') PIDataUnpacker = [ unpackOrientation, # SCENE_ORIENTATION, @@ -930,32 +1223,32 @@ PIDataUnpacker = [ unpackOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED unpackAcousticEnv, # ACOUSTIC_ENVIRONMENT unpackAudioDescription, # AUDIO_DESCRIPTION - unpackUnsupported, # ISM_NUM - unpackUnsupported, # ISM_ID - unpackUnsupported, # ISM_GAIN + unpackISMNum, # ISM_NUM + unpackISMID, # ISM_ID + unpackISMGain, # ISM_GAIN unpackOrientations, # ISM_ORIENTATION unpackPositions, # ISM_POSITION - unpackUnsupported, # ISM_DISTANCE_ATTENUATION - unpackUnsupported, # ISM_DIRECTIVITY + unpackPositionsCompact, # ISM_POSITION_COMPACT + unpackISMDistanceAttenuation, # ISM_DISTANCE_ATTENUATION + unpackISMDirectivity, # ISM_DIRECTIVITY unpackDiegetic, # DIEGETIC_TYPE unpackDAS, # DYNAMIC_AUDIO_SUPPRESSION_INDICATION unpackAudioFocus, # AUDIO_FOCUS_INDICATION - unpackUnsupported, # RESERVED15 unpackOrientation, # PLAYBACK_DEVICE_ORIENTATION unpackOrientation, # HEAD_ORIENTATION unpackPosition, # LISTENER_POSITION unpackDAS, # DYNAMIC_AUDIO_SUPPRESSION_REQUEST unpackAudioFocus, # AUDIO_FOCUS_REQUEST - unpackUnsupported, # PI_LATENCY - unpackUnsupported, # R_ISM_ID - unpackUnsupported, # R_ISM_GAIN + unpackPiLatency, # PI_LATENCY + unpackReverseISMID, # R_ISM_ID + unpackReverseISMGain, # R_ISM_GAIN unpackOrientation, # R_ISM_ORIENTATION unpackPosition, # R_ISM_POSITION - unpackUnsupported, # R_ISM_DIRECTION + unpackPositionCompact, # R_ISM_POSITION_COMPACT + unpackReverseISMDirection, # R_ISM_DIRECTION unpackUnsupported, # RESERVED27 unpackUnsupported, # RESERVED28 unpackUnsupported, # RESERVED29 - unpackUnsupported, # RESERVED30 unpackNoPiData, # NO_DATA ] @@ -965,32 +1258,32 @@ PIDataPacker = [ packOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED packAcousticEnv, # ACOUSTIC_ENVIRONMENT packAudioDescription, # AUDIO_DESCRIPTION - packUnsupported, # ISM_NUM - packUnsupported, # ISM_ID - packUnsupported, # ISM_GAIN + packISMNum, # ISM_NUM + packISMID, # ISM_ID + packISMGain, # ISM_GAIN packOrientations, # ISM_ORIENTATION packPositions, # ISM_POSITION - packUnsupported, # ISM_DISTANCE_ATTENUATION - packUnsupported, # ISM_DIRECTIVITY + packPositionsCompact, # ISM_POSITION_COMPACT + packISMDistanceAttenuation, # ISM_DISTANCE_ATTENUATION + packISMDirectivity, # ISM_DIRECTIVITY packDiegetic, # DIEGETIC_TYPE packDAS, # DYNAMIC_AUDIO_SUPPRESSION_INDICATION packAudioFocus, # AUDIO_FOCUS_INDICATION - packUnsupported, # RESERVED15 packOrientation, # PLAYBACK_DEVICE_ORIENTATION packOrientation, # HEAD_ORIENTATION packPosition, # LISTENER_POSITION packDAS, # DYNAMIC_AUDIO_SUPPRESSION_REQUEST packAudioFocus, # AUDIO_FOCUS_DIRECTION - packUnsupported, # PI_LATENCY - packUnsupported, # R_ISM_ID - packUnsupported, # R_ISM_GAIN + packPiLatency, # PI_LATENCY + packReverseISMID, # R_ISM_ID + packReverseISMGain, # R_ISM_GAIN packOrientation, # R_ISM_ORIENTATION packPosition, # R_ISM_POSITION - packUnsupported, # R_ISM_DIRECTION + packPositionCompact, # R_ISM_POSITION_COMPACT + packReverseISMDirection, # R_ISM_DIRECTION packUnsupported, # RESERVED27 packUnsupported, # RESERVED28 packUnsupported, # RESERVED29 - packUnsupported, # RESERVED30 packNoPiData, # NO_DATA ] diff --git a/tests/rtp/test_format_switching.py b/tests/rtp/test_format_switching.py new file mode 100644 index 0000000000000000000000000000000000000000..43daaa3da7f5c1dcb6813d7b9dad3666c62f62ac --- /dev/null +++ b/tests/rtp/test_format_switching.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 + +__copyright__ = """ +(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository. All Rights Reserved. + +This software is protected by copyright law and by international treaties. +The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository retain full ownership rights in their respective contributions in +the software. This notice grants no license of any kind, including but not limited to patent +license, nor is any license granted by implication, estoppel or otherwise. + +Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +contributions. + +This software is provided "AS IS", without any express or implied warranties. The software is in the +development stage. It is intended exclusively for experts who have experience with such software and +solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +and fitness for a particular purpose are hereby disclaimed and excluded. + +Any dispute, controversy or claim arising under or in relation to providing this software shall be +submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +the United Nations Convention on Contracts on the International Sales of Goods. +""" + +__doc__ = """ +To configure test modules. +""" + +import pytest +import csv +import os +import sys +import random + +from tempfile import TemporaryDirectory +from pathlib import Path +import soundfile as sf +import numpy as np + +ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) +sys.path.append(ROOT_DIR) + +from tests.conftest import EncoderFrontend, DecoderFrontend +from tests.create_short_testvectors import create_short_testvectors +from tests.renderer.constants import ( + FORMAT_TO_FILE_COMPARETEST, + FORMAT_TO_METADATA_FILES, +) +from ivasrtp import * +import platform + +BIN_EXT = ".exe" if platform.system() == "Windows" else "" + +FORMAT_ARGUMENT_MAPPING = { + "MONO": [], + "STEREO": ["-stereo"], + "5_1": ["-mc", "5_1"], + "7_1": ["-mc", "7_1"], + "5_1_2": ["-mc", "5_1_2"], + "5_1_4": ["-mc", "5_1_4"], + "7_1_4": ["-mc", "7_1_4"], + "FOA": ["-sba", "+1"], + "HOA2": ["-sba", "+2"], + "HOA3": ["-sba", "+3"], + "ISM1": ["-ism", "1"], + "ISM2": ["-ism", "2"], + "ISM3": ["-ism", "3"], + "ISM4": ["-ism", "4"], + "MASA1": ["-masa", "1"], + "MASA2": ["-masa", "2"], + "ISM1MASA1": ["-ism_masa", "1", "1"], + "ISM2MASA1": ["-ism_masa", "2", "1"], + "ISM3MASA1": ["-ism_masa", "3", "1"], + "ISM4MASA1": ["-ism_masa", "4", "1"], + "ISM1MASA2": ["-ism_masa", "1", "2"], + "ISM2MASA2": ["-ism_masa", "2", "2"], + "ISM3MASA2": ["-ism_masa", "3", "2"], + "ISM4MASA2": ["-ism_masa", "4", "2"], + "ISM1SBA1": ["-ism_sba", "1", "+1"], + "ISM1SBA2": ["-ism_sba", "1", "+2"], + "ISM1SBA3": ["-ism_sba", "1", "+3"], + "ISM2SBA1": ["-ism_sba", "2", "+1"], + "ISM2SBA2": ["-ism_sba", "2", "+2"], + "ISM2SBA3": ["-ism_sba", "2", "+3"], + "ISM3SBA1": ["-ism_sba", "3", "+1"], + "ISM3SBA2": ["-ism_sba", "3", "+2"], + "ISM3SBA3": ["-ism_sba", "3", "+3"], + "ISM4SBA1": ["-ism_sba", "4", "+1"], + "ISM4SBA2": ["-ism_sba", "4", "+2"], + "ISM4SBA3": ["-ism_sba", "4", "+3"], +} + +def test_format_switching_new_encoder ( + record_property, + dut_decoder_frontend: DecoderFrontend +): + bitrate = 48000 + fs = 48 + bandwidth = "FB" + outMode = "" + + dut_encoder_path = Path(ROOT_DIR).joinpath(f"IVAS_cod{BIN_EXT}") + dut_encoder_fmtsw_path = Path(ROOT_DIR).joinpath(f"IVAS_cod_fmtsw{BIN_EXT}") + dut_encoder_fmtsw_frontend = EncoderFrontend( + dut_encoder_fmtsw_path, "DUT", record_property + ) + + # Create 1s test files + cut_suffix = "_cut.wav" + test_file = Path( str(FORMAT_TO_FILE_COMPARETEST["MONO"]).replace(".wav", cut_suffix) ) + if not test_file.exists(): + create_short_testvectors(1.0, False, None) + + with TemporaryDirectory() as tmp_dir: + + # Create file for encoding commands + temp_format_switching_file = Path(tmp_dir).joinpath("format_switching_input.txt").absolute() + temp_rtpdump = Path(tmp_dir).joinpath("output_concatenated.rtpdump").absolute() + with open (temp_format_switching_file, mode="a") as outFile: + for key, audioFile in FORMAT_TO_FILE_COMPARETEST.items(): + if key in ["META", "16ch_8+4+4", "4d4", "t_design_4"]: + break + + encoder_args = [] + encoder_args += [str(dut_encoder_path)] + encoder_args += ["-rtpdump", "1"] + encoder_args += FORMAT_ARGUMENT_MAPPING[key] + if key in FORMAT_TO_METADATA_FILES: + encoder_args += FORMAT_TO_METADATA_FILES[key] + elif "SBA" in key: + # ISM metadata files for OSBA + encoder_args += FORMAT_TO_METADATA_FILES[key[:4]] + encoder_args += [str(bitrate)] + encoder_args += [str(fs)] + cutFile = Path( str(audioFile).replace(".wav", cut_suffix) ) + encoder_args += [str(cutFile)] + encoder_args += [str(temp_rtpdump)] + + outFile.write(' '.join(encoder_args)) + outFile.write("\n") + + # Encode with the format switch encoder + dut_encoder_fmtsw_frontend.run( + bitrate='', + input_sampling_rate='', + input_path=temp_format_switching_file, + output_bitstream_path='', + quiet_mode=False, + fmtsw_command=True + ) + + # Decode the combined bitstreams + cat_output_rtpdump = Path(tmp_dir).joinpath(f"output_concatenated.wav").absolute() + + dut_decoder_frontend.run( + output_config=outMode, + output_sampling_rate=48, + input_bitstream_path=temp_rtpdump, + output_path=cat_output_rtpdump, + add_option_list= ["-VOIP_HF_ONLY=1"] + ) diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py index 44d08a91e16033ddae42c6859ce5858052535df1..8ef0f57c701e19235cf996d7e0a60e8b3b470619 100644 --- a/tests/rtp/test_rtp.py +++ b/tests/rtp/test_rtp.py @@ -61,6 +61,7 @@ from tempfile import TemporaryDirectory from pathlib import Path from ivasrtp import * import numpy as np +import json from pyaudio3dtools.audiofile import readfile ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) @@ -181,17 +182,27 @@ def generateRequests(startTs: int, endTs: int) -> dict: def generatePiData(startTs: int, endTs: int) -> dict: data = dict() - someOrientation = lambda: ORIENTATION( - w=2 * random.random() - 1.0, - x=2 * random.random() - 1.0, - y=2 * random.random() - 1.0, - z=2 * random.random() - 1.0, - ) + def random_unit_quaternion(): + w = 2*random.random() - 1.0 + x = 2*random.random() - 1.0 + y = 2*random.random() - 1.0 + z = 2*random.random() - 1.0 + n = (w*w + x*x + y*y + z*z)**0.5 + if n == 0.0: + return random_unit_quaternion() + return ORIENTATION(w/n, x/n, y/n, z/n) + + someOrientation = lambda: random_unit_quaternion() somePosition = lambda: POSITION( x=random.randint(-32788, 32767) / 100.0, y=random.randint(-32788, 32767) / 100.0, z=random.randint(-32788, 32767) / 100.0, ) + somePositionCompact = lambda: POSITION( + x=random.randint(-1024, 1023) / 100.0, + y=random.randint(-1024, 1023) / 100.0, + z=random.randint(-512, 511) / 100.0, + ) someDesc = lambda: AUDIO_DESCRIPTION( isSpeech=bool(random.getrandbits(1)), isMusic=bool(random.getrandbits(1)), @@ -208,25 +219,64 @@ def generatePiData(startTs: int, endTs: int) -> dict: someDIG = lambda: DIEGETIC_TYPE( isDigetic=[bool(random.getrandbits(1)) for _ in range(random.randint(1, 5))] ) - someAuFocusDirLvl = lambda: AUDIO_FOCUS( - ORIENTATION( - w=2 * random.random() - 1.0, - x=2 * random.random() - 1.0, - y=2 * random.random() - 1.0, - z=2 * random.random() - 1.0, - ), - level=AUDIO_FOCUS_LEVEL(random.randint(0, 15)), - ) - someAuFocusDir = lambda: AUDIO_FOCUS( - ORIENTATION( - w=2 * random.random() - 1.0, - x=2 * random.random() - 1.0, - y=2 * random.random() - 1.0, - z=2 * random.random() - 1.0, - ) - ) + someAuFocusDirLvl = lambda: AUDIO_FOCUS(random_unit_quaternion(),level=AUDIO_FOCUS_LEVEL(random.randint(0, 15)),) + someAuFocusDir = lambda: AUDIO_FOCUS(random_unit_quaternion()) someAuFocusLvl = lambda: AUDIO_FOCUS(level=AUDIO_FOCUS_LEVEL(random.randint(0, 15))) someAuFocusList = [someAuFocusDirLvl, someAuFocusDir, someAuFocusLvl] + someLatency = lambda: PI_LATENCY( + reverseType=random.choice([ + "PLAYBACK_DEVICE_ORIENTATION", + "HEAD_ORIENTATION", + "LISTENER_POSITION", + "DYNAMIC_AUDIO_SUPPRESSION_REQUEST", + "AUDIO_FOCUS_REQUEST" + ]), + latency=random.randint(- (1 << 26), (1 << 26) - 1) + ) + someAcousticEnvAEID = lambda: ACOUSTIC_ENVIRONMENT( + aeid=random.randint(0, 127) + ) + someAcousticEnvLR = lambda: ACOUSTIC_ENVIRONMENT( + aeid=random.randint(0, 127), + rt60=(float(rt60Value[random.randint(0, len(rt60Value) - 1)]), + float(rt60Value[random.randint(0, len(rt60Value) - 1)]), + float(rt60Value[random.randint(0, len(rt60Value) - 1)])), + dsr=(float(dsrValue[random.randint(0, len(dsrValue) - 1)]), + float(dsrValue[random.randint(0, len(dsrValue) - 1)]), + float(dsrValue[random.randint(0, len(dsrValue) - 1)])), + ) + someAcousticEnvERLR = lambda: ACOUSTIC_ENVIRONMENT( + aeid=random.randint(0, 127), + rt60=(float(rt60Value[random.randint(0, len(rt60Value) - 1)]), + float(rt60Value[random.randint(0, len(rt60Value) - 1)]), + float(rt60Value[random.randint(0, len(rt60Value) - 1)])), + dsr=(float(dsrValue[random.randint(0, len(dsrValue) - 1)]), + float(dsrValue[random.randint(0, len(dsrValue) - 1)]), + float(dsrValue[random.randint(0, len(dsrValue) - 1)])), + dim=(float(roomDimensionValue[random.randint(0, len(roomDimensionValue) - 1)]), + float(roomDimensionValue[random.randint(0, len(roomDimensionValue) - 1)]), + float(roomDimensionValue[random.randint(0, len(roomDimensionValue) - 1)])), + abscoeff=(float(absorptionCoeffValues[random.randint(0, len(absorptionCoeffValues) - 1)]), + float(absorptionCoeffValues[random.randint(0, len(absorptionCoeffValues) - 1)]), + float(absorptionCoeffValues[random.randint(0, len(absorptionCoeffValues) - 1)]), + float(absorptionCoeffValues[random.randint(0, len(absorptionCoeffValues) - 1)]), + float(absorptionCoeffValues[random.randint(0, len(absorptionCoeffValues) - 1)]), + float(absorptionCoeffValues[random.randint(0, len(absorptionCoeffValues) - 1)])), + ) + someAcousticEnvList = [someAcousticEnvAEID, someAcousticEnvLR, someAcousticEnvERLR] + + someNumISM = lambda : ISM_NUM(num=random.randint(1, 4)) + someISMIds = lambda num_ism : ISM_ID(ids=[int(random.getrandbits(8)) for _ in range(num_ism)]) + someISMGains = lambda num_ism : ISM_GAIN(gains=[random.choice([int(random.randint(-24,12)), -128]) for _ in range(num_ism)]) # -128 corresponds to -Inf + someISMOrientations = lambda num_ism : [random_unit_quaternion() for _ in range(num_ism)] + someISMPositions = lambda num_ism : [POSITION( x=random.randint(-32788, 32767)/100.0, y=random.randint(-32788, 32767)/100.0, z=random.randint(-32788, 32767)/100.0) for _ in range(num_ism)] + someISMPositionsCompact = lambda num_ism : [POSITION( x=random.randint(-1024, 1023)/100.0, y=random.randint(-1024, 1023)/100.0, z=random.randint(-512, 511)/100.0) for _ in range(num_ism)] + someISMDistanceAttenuations = lambda num_ism : [DISTANCE_ATTENUATION(ref_dist=random.randint(1,64)/10.0, max_dist=random.randint(1,64), roll_off=random.randint(0,40)/10.0) for _ in range(num_ism)] + someISMDirectivities = lambda num_ism : [DIRECTIVITY(inner_ang=random.randint(0,24)*15, outer_ang=random.randint(0,24)*15, outer_att=random.choice([random.randint(-30,0)*3, -128])) for _ in range(num_ism)] # -128 corresponds to -Inf + + someReverseISMId = lambda : R_ISM_ID(id=(random.getrandbits(8))) + someReverseISMGain = lambda : R_ISM_GAIN(gain=random.choice([int(random.randint(-24,12)), -128])) + someReverseISMDirection = lambda : R_ISM_DIRECTION( azi=random.randint(1,512) * azimuthStepSize - 180.0, elev=random.randint(0,127) * elevationStepSize - 90.0 ) for ts in range(startTs, endTs, 320): pidata = dict() @@ -240,9 +290,23 @@ def generatePiData(startTs: int, endTs: int) -> dict: pidata["DYNAMIC_AUDIO_SUPPRESSION_REQUEST"] = someDAS() pidata["AUDIO_DESCRIPTION"] = [someDesc() for n in range(random.randint(1, 5))] pidata["DIEGETIC_TYPE"] = someDIG() - pidata["ACOUSTIC_ENVIRONMENT"] = ACOUSTIC_ENVIRONMENT( - aeid=random.randint(0, 127) - ) + pidata["ACOUSTIC_ENVIRONMENT"] = random.choice(someAcousticEnvList)() + pidata["PI_LATENCY"] = someLatency() + pidata["ISM_NUM"] = someNumISM() + pidata["ISM_ID"] = someISMIds(pidata["ISM_NUM"].num) + pidata["ISM_GAIN"] = someISMGains(pidata["ISM_NUM"].num) + pidata["ISM_ORIENTATION"] = someISMOrientations(pidata["ISM_NUM"].num) + pidata["ISM_POSITION"] = someISMPositions(pidata["ISM_NUM"].num) + pidata["ISM_POSITION_COMPACT"] = someISMPositionsCompact(pidata["ISM_NUM"].num) + pidata["ISM_DISTANCE_ATTENUATION"] = someISMDistanceAttenuations(pidata["ISM_NUM"].num) + pidata["ISM_DIRECTIVITY"] = someISMDirectivities(pidata["ISM_NUM"].num) + + pidata["R_ISM_ID"] = someReverseISMId() + pidata["R_ISM_GAIN"] = someReverseISMGain() + pidata["R_ISM_ORIENTATION"] = someOrientation() + pidata["R_ISM_POSITION"] = somePosition() + pidata["R_ISM_POSITION_COMPACT"] = somePositionCompact() + pidata["R_ISM_DIRECTION"] = someReverseISMDirection() data[str(ts)] = pidata return data @@ -256,10 +320,10 @@ def isEqualFrame(refFrame: bytes, dutFrame: bytes): def isEqualOrientation(ref: ORIENTATION, dut: ORIENTATION): - assert abs(ref.w - dut.w) < 0.0001, "Scene Orientation PI Data mismatch in w" - assert abs(ref.x - dut.x) < 0.0001, "Scene Orientation PI Data mismatch in x" - assert abs(ref.y - dut.y) < 0.0001, "Scene Orientation PI Data mismatch in y" - assert abs(ref.z - dut.z) < 0.0001, "Scene Orientation PI Data mismatch in z" + assert abs(abs(ref.w) - abs(dut.w)) < 0.01, "Scene Orientation PI Data mismatch in w" + assert abs(abs(ref.x) - abs(dut.x)) < 0.01, "Scene Orientation PI Data mismatch in x" + assert abs(abs(ref.y) - abs(dut.y)) < 0.01, "Scene Orientation PI Data mismatch in y" + assert abs(abs(ref.z) - abs(dut.z)) < 0.01, "Scene Orientation PI Data mismatch in z" def isEqualPosition(ref: POSITION, dut: POSITION): @@ -311,7 +375,13 @@ def isEqualAcousticEnv(ref: ACOUSTIC_ENVIRONMENT, dut: ACOUSTIC_ENVIRONMENT): dut.abscoeff ), "Acoustic Env PI Data mismatch in len(abscoeff)" for r, d in zip(ref.rt60, dut.rt60): - assert r == d, f"Acoustic Env PI Data mismatch in rt60 {r} != {d}" + assert abs(r - d) < 0.0001, f"Acoustic Env PI Data mismatch in RT60 {r} != {d}" + for r, d in zip(ref.dsr, dut.dsr): + assert abs(r - d) < 0.0001, f"Acoustic Env PI Data mismatch in DSR {r} != {d}" + for r, d in zip(ref.dim, dut.dim): + assert abs(r - d) < 0.0001, f"Acoustic Env PI Data mismatch in room dimension {r} != {d}" + for r, d in zip(ref.abscoeff, dut.abscoeff): + assert abs(r - d) < 0.0001, f"Acoustic Env PI Data mismatch in absorption coefficient {r} != {d}" def isEqualAudioFocus(ref: AUDIO_FOCUS, dut: AUDIO_FOCUS): @@ -320,19 +390,56 @@ def isEqualAudioFocus(ref: AUDIO_FOCUS, dut: AUDIO_FOCUS): assert dut.direction is not None, "Audio Focus PI Data missing direction" if ref.direction is not None and dut.direction is not None: assert ( - abs(ref.direction["w"] - dut.direction.w) < 0.0001 + abs(abs(ref.direction["w"]) - abs(dut.direction.w)) < 0.01 ), "Audio Focus PI Data mismatch in direction w" assert ( - abs(ref.direction["x"] - dut.direction.x) < 0.0001 + abs(abs(ref.direction["x"]) - abs(dut.direction.x)) < 0.01 ), "Audio Focus PI Data mismatch in direction x" assert ( - abs(ref.direction["y"] - dut.direction.y) < 0.0001 + abs(abs(ref.direction["y"]) - abs(dut.direction.y)) < 0.01 ), "Audio Focus PI Data mismatch in direction y" assert ( - abs(ref.direction["z"] - dut.direction.z) < 0.0001 + abs(abs(ref.direction["z"]) - abs(dut.direction.z)) < 0.01 ), "Audio Focus PI Data mismatch in direction z" assert ref.level == dut.level, "Audio Focus PI Data mismatch in level" +def isEqualPILatency(ref: PI_LATENCY, dut: PI_LATENCY): + assert ref.reverseType == dut.reverseType, \ + f"PI_LATENCY type mismatch: {dut.reverseType} != {ref.reverseType}" + assert ref.latency == dut.latency, \ + f"PI_LATENCY latency mismatch: {dut.latency} != {ref.latency}" + +def isEqualISMNum(ref: ISM_NUM, dut: ISM_NUM): + assert ref.num == dut.num, "ISM NUM PI Data mismatch" + +def isEqualISMID(ref: ISM_ID, dut: ISM_ID): + for r, d in zip(ref.ids, dut.ids): + assert r == d, f"ISM ID PI Data mismatch {r} != {d}" + +def isEqualISMGain(ref: ISM_GAIN, dut: ISM_GAIN): + for r, d in zip(ref.gains, dut.gains): + assert r == d, f"ISM GAIN PI Data mismatch {r} != {d}" + +def isEqualISMDistanceAttenuation(ref: DISTANCE_ATTENUATION, dut: DISTANCE_ATTENUATION): + assert abs(ref.ref_dist - dut.ref_dist) < 0.0001, "DISTANCE ATTENUATION PI Data mismatch in ref_dist" + assert abs(ref.max_dist - dut.max_dist) < 0.0001, "DISTANCE ATTENUATION PI Data mismatch in max_dist" + assert abs(ref.roll_off - dut.roll_off) < 0.0001, "DISTANCE ATTENUATION PI Data mismatch in roll_off" + +def isEqualISMDirectivity(ref: DIRECTIVITY, dut: DIRECTIVITY): + assert ref.inner_ang == dut.inner_ang, "DIRECTIVITYPI Data mismatch in inner_ang" + assert ref.outer_ang == dut.outer_ang, "DIRECTIVITY PI Data mismatch in outer_ang" + assert abs(ref.outer_att - dut.outer_att) < 0.0001, "DIRECTIVITY PI Data mismatch in outer_att" + +def isEqualReverseISMID(ref: R_ISM_ID, dut: R_ISM_ID): + assert ref.id == dut.id, f"Reverse ISM ID PI Data mismatch {ref.id} != {dut.id}" + +def isEqualReverseISMGain(ref: R_ISM_GAIN, dut: R_ISM_GAIN): + assert ref.gain == dut.gain, f"Reverse ISM GAIN PI Data mismatch {ref.gain} != {dut.gain}" + +def isEqualReverseISMDirection(ref: R_ISM_DIRECTION, dut: R_ISM_DIRECTION): + assert abs(ref.azi - dut.azi) < azimuthStepSize + 0.1, "Reverse ISM Direction PI Data mismatch in azimuth (more than one index difference)" + assert abs(ref.elev - dut.elev) < elevationStepSize + 0.1, "Reverse ISM Direction PI Data mismatch in elevation (more than one index difference)" + class CSVREADER: def __init__(self, csvFile: Path): @@ -632,11 +739,37 @@ def run_rtp_bitstream_tests( isEqualAcousticEnv(ACOUSTIC_ENVIRONMENT(**decoded), data) elif type(generatedPIData[ts][pitype]) == AUDIO_FOCUS: isEqualAudioFocus(AUDIO_FOCUS(**decoded), data) + elif type(generatedPIData[ts][pitype]) == PI_LATENCY: + isEqualPILatency(PI_LATENCY(**decoded), data) elif type(generatedPIData[ts][pitype]) == list: - for r, d in zip( - generatedPIData[ts][pitype], decodedPiData[ts][pitype] - ): - isEqualAD(AUDIO_DESCRIPTION(**d), r) + if pitype == "AUDIO_DESCRIPTION": + for r, d in zip(generatedPIData[ts][pitype], decodedPiData[ts][pitype]): + isEqualAD(AUDIO_DESCRIPTION(**d), r) + elif pitype == "ISM_ORIENTATION": + for r, d in zip(generatedPIData[ts][pitype], decodedPiData[ts][pitype]): + isEqualOrientation(ORIENTATION(**d), r) + elif pitype == "ISM_POSITION" or pitype == "ISM_POSITION_COMPACT": + for r, d in zip(generatedPIData[ts][pitype], decodedPiData[ts][pitype]): + isEqualPosition(POSITION(**d), r) + elif pitype == "ISM_DISTANCE_ATTENUATION": + for r, d in zip(generatedPIData[ts][pitype], decodedPiData[ts][pitype]): + isEqualISMDistanceAttenuation(DISTANCE_ATTENUATION(**d), r) + elif pitype == "ISM_DIRECTIVITY": + for r, d in zip(generatedPIData[ts][pitype], decodedPiData[ts][pitype]): + isEqualISMDirectivity(DIRECTIVITY(**d), r) + elif type(generatedPIData[ts][pitype]) == ISM_NUM: + isEqualISMNum(ISM_NUM(**decoded), data) + elif type(generatedPIData[ts][pitype]) == ISM_ID: + isEqualISMID(ISM_ID(**decoded), data) + elif type(generatedPIData[ts][pitype]) == ISM_GAIN: + isEqualISMGain(ISM_GAIN(**decoded), data) + elif type(generatedPIData[ts][pitype]) == R_ISM_ID: + isEqualReverseISMID(R_ISM_ID(**decoded), data) + elif type(generatedPIData[ts][pitype]) == R_ISM_GAIN: + isEqualReverseISMGain(R_ISM_GAIN(**decoded), data) + elif type(generatedPIData[ts][pitype]) == R_ISM_DIRECTION: + isEqualReverseISMDirection(R_ISM_DIRECTION(**decoded), data) + else: assert False, "Unsupported PI data found" diff --git a/tests/split_rendering/constants.py b/tests/split_rendering/constants.py index 5869ec3b78fe331a57ff69c64515a632e7c9dd2a..7d58a03b0c05a3556603d24c082f15a7750df267 100644 --- a/tests/split_rendering/constants.py +++ b/tests/split_rendering/constants.py @@ -1,46 +1,37 @@ #!/usr/bin/env python3 """ - (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository. All Rights Reserved. - - This software is protected by copyright law and by international treaties. - The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository retain full ownership rights in their respective contributions in - the software. This notice grants no license of any kind, including but not limited to patent - license, nor is any license granted by implication, estoppel or otherwise. - - Contributors are required to enter into the IVAS codec Public Collaboration agreement before making - contributions. - - This software is provided "AS IS", without any express or implied warranties. The software is in the - development stage. It is intended exclusively for experts who have experience with such software and - solely for the purpose of inspection. All implied warranties of non-infringement, merchantability - and fitness for a particular purpose are hereby disclaimed and excluded. - - Any dispute, controversy or claim arising under or in relation to providing this software shall be - submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in - accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and - the United Nations Convention on Contracts on the International Sales of Goods. +(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository. All Rights Reserved. + +This software is protected by copyright law and by international treaties. +The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository retain full ownership rights in their respective contributions in +the software. This notice grants no license of any kind, including but not limited to patent +license, nor is any license granted by implication, estoppel or otherwise. + +Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +contributions. + +This software is provided "AS IS", without any express or implied warranties. The software is in the +development stage. It is intended exclusively for experts who have experience with such software and +solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +and fitness for a particular purpose are hereby disclaimed and excluded. + +Any dispute, controversy or claim arising under or in relation to providing this software shall be +submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +the United Nations Convention on Contracts on the International Sales of Goods. """ -from pathlib import Path import platform - -from tests.renderer.constants import ( - BIN_SUFFIX_MERGETARGET, - CUSTOM_LS_TO_TEST, - FORMAT_TO_FILE_COMPARETEST, - FORMAT_TO_FILE_SMOKETEST, - FORMAT_TO_METADATA_FILES, - METADATA_SCENES_TO_TEST, -) +from pathlib import Path if platform.system() == "Windows": EXE_SUFFIX = ".exe" @@ -52,7 +43,9 @@ else: """ Set up paths """ TESTS_DIR = Path(__file__).parent RENDER_CFG_DIR = TESTS_DIR.joinpath("renderer_configs").resolve() -RENDER_FRAMING_CFG_DIR = TESTS_DIR.joinpath("renderer_configs").joinpath("framing").resolve() +RENDER_FRAMING_CFG_DIR = ( + TESTS_DIR.joinpath("renderer_configs").joinpath("framing").resolve() +) ERROR_PATTERNS_DIR = TESTS_DIR.joinpath("error_patterns").resolve() OUTPUT_PATH_REF = TESTS_DIR.joinpath("ref") @@ -63,6 +56,14 @@ CUSTOM_LAYOUT_DIR = SCRIPTS_DIR.joinpath("ls_layouts") HR_TRAJECTORY_DIR = SCRIPTS_DIR.joinpath("trajectories") TESTV_DIR = SCRIPTS_DIR.joinpath("testv") +if platform.system() == "Windows": + TOOLS_DIR = SCRIPTS_DIR / "tools" / "Win32" +elif platform.system() == "Linux": + TOOLS_DIR = SCRIPTS_DIR / "tools"/ "Linux" +elif platform.system() == "Darwin": + TOOLS_DIR = SCRIPTS_DIR / "tools" / "Darwin" +else: + assert False, "Unsupported platform" """ Renderer configurations """ RENDERER_CONFIGS_DEFAULT_CODEC = [ @@ -100,11 +101,13 @@ RENDERER_CONFIGS_TO_TEST_OMASA = ( RENDERER_CONFIGS_TO_TEST_OSBA = ( RENDERER_CONFIGS_DEFAULT_CODEC + RENDERER_CONFIGS_LC3PLUS_CODEC ) -RENDERER_CONFIGS_TO_TEST_PLC = RENDERER_CONFIGS_FASTCONV_RENDERER + RENDERER_CONFIGS_LC3PLUS_CODEC +RENDERER_CONFIGS_TO_TEST_PLC = ( + RENDERER_CONFIGS_FASTCONV_RENDERER + RENDERER_CONFIGS_LC3PLUS_CODEC +) """ Trajectories """ SPLIT_REND_HR_TRAJECTORIES_TO_TEST = [ - "rotate_euler_quaternion_5s", + "rotate_euler_quaternion_30s", ] """ IVAS specific constants """ @@ -125,43 +128,48 @@ FORMAT_TO_IVAS_COD_FORMAT = { "HOA3": ["-sba", "3"], "MASA1": ["-masa", "1"], "MASA2": ["-masa", "2"], - "OMASA_1_1": ["-ism_masa", "1", "1"], - "OMASA_1_2": ["-ism_masa", "2", "1"], - "OMASA_1_3": ["-ism_masa", "3", "1"], - "OMASA_1_4": ["-ism_masa", "4", "1"], - "OMASA_2_1": ["-ism_masa", "1", "2"], - "OMASA_2_2": ["-ism_masa", "2", "2"], - "OMASA_2_3": ["-ism_masa", "3", "2"], - "OMASA_2_4": ["-ism_masa", "4", "2"], - "OSBA_1_1": ["-ism_sba", "1", "1"], - "OSBA_1_2": ["-ism_sba", "1", "2"], - "OSBA_1_3": ["-ism_sba", "1", "3"], - "OSBA_2_1": ["-ism_sba", "2", "1"], - "OSBA_2_2": ["-ism_sba", "2", "2"], - "OSBA_2_3": ["-ism_sba", "2", "3"], - "OSBA_3_1": ["-ism_sba", "3", "1"], - "OSBA_3_2": ["-ism_sba", "3", "2"], - "OSBA_3_3": ["-ism_sba", "3", "3"], - "OSBA_4_1": ["-ism_sba", "4", "1"], - "OSBA_4_2": ["-ism_sba", "4", "2"], - "OSBA_4_3": ["-ism_sba", "4", "3"], + "ISM1MASA1": ["-ism_masa", "1", "1"], + "ISM2MASA1": ["-ism_masa", "2", "1"], + "ISM3MASA1": ["-ism_masa", "3", "1"], + "ISM4MASA1": ["-ism_masa", "4", "1"], + "ISM1MASA2": ["-ism_masa", "1", "2"], + "ISM2MASA2": ["-ism_masa", "2", "2"], + "ISM3MASA2": ["-ism_masa", "3", "2"], + "ISM4MASA2": ["-ism_masa", "4", "2"], + "ISM1SBA1": ["-ism_sba", "1", "1"], + "ISM1SBA2": ["-ism_sba", "1", "2"], + "ISM1SBA3": ["-ism_sba", "1", "3"], + "ISM2SBA1": ["-ism_sba", "2", "1"], + "ISM2SBA2": ["-ism_sba", "2", "2"], + "ISM2SBA3": ["-ism_sba", "2", "3"], + "ISM3SBA1": ["-ism_sba", "3", "1"], + "ISM3SBA2": ["-ism_sba", "3", "2"], + "ISM3SBA3": ["-ism_sba", "3", "3"], + "ISM4SBA1": ["-ism_sba", "4", "1"], + "ISM4SBA2": ["-ism_sba", "4", "2"], + "ISM4SBA3": ["-ism_sba", "4", "3"], } INPUT_FORMATS_AMBI_SPLIT_REND = ["FOA", "HOA3"] INPUT_FORMATS_MC_SPLIT_REND = ["5_1", "7_1_4"] INPUT_FORMATS_ISM_SPLIT_REND = ["ISM4"] INPUT_FORMATS_MASA_SPLIT_REND = ["MASA1", "MASA2"] -INPUT_FORMATS_OMASA_SPLIT_REND = ["OMASA_1_1", "OMASA_2_4"] # number of TCs in MASA, number of ISM objects +INPUT_FORMATS_OMASA_SPLIT_REND = ["ISM1MASA1", "ISM4MASA2"] INPUT_FORMATS_OSBA_SPLIT_REND = [ - "OSBA_1_1", - "OSBA_4_3", -] # number of ISM objects, then SBA order + "ISM1SBA1", + "ISM4SBA3", +] IVAS_BITRATES_AMBI = ["80000", "512000"] IVAS_BITRATES_MC = ["128000", "160000", "384000"] IVAS_BITRATES_ISM = ["128000"] IVAS_BITRATES_MASA = ["24400", "128000"] -IVAS_BITRATES_OMASA = ["16400", "32000", "96000", "384000"] # test all underlying coding modes (here, for 4 ISM) +IVAS_BITRATES_OMASA = [ + "16400", + "32000", + "96000", + "384000", +] # test all underlying coding modes (here, for 4 ISM) IVAS_BITRATES_OSBA = ["256000", "512000"] IVAS_MAX_ISM_BITRATE = { @@ -180,6 +188,9 @@ INPUT_DURATION_SEC = 5 """ PLC constants """ PLC_ERROR_PATTERNS = [str(ep.stem) for ep in ERROR_PATTERNS_DIR.glob("*.ep")] +""" Delay profiles for testing VoIP mode """ +DELAY_PROFILES = [None, "dly_error_profile_5"] + """ Encoder commandline template """ SPLIT_PRE_COD_CMD = [ str(TESTS_DIR.parent.parent.joinpath("IVAS_cod")), diff --git a/tests/split_rendering/test_split_rendering.py b/tests/split_rendering/test_split_rendering.py index a881a7ad23f65902c620f7de9a1d39e6f9676832..32efd73109fe0d210c61aab117e23eec1c75a343 100644 --- a/tests/split_rendering/test_split_rendering.py +++ b/tests/split_rendering/test_split_rendering.py @@ -1,44 +1,42 @@ #!/usr/bin/env python3 """ - (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository. All Rights Reserved. - - This software is protected by copyright law and by international treaties. - The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository retain full ownership rights in their respective contributions in - the software. This notice grants no license of any kind, including but not limited to patent - license, nor is any license granted by implication, estoppel or otherwise. - - Contributors are required to enter into the IVAS codec Public Collaboration agreement before making - contributions. - - This software is provided "AS IS", without any express or implied warranties. The software is in the - development stage. It is intended exclusively for experts who have experience with such software and - solely for the purpose of inspection. All implied warranties of non-infringement, merchantability - and fitness for a particular purpose are hereby disclaimed and excluded. - - Any dispute, controversy or claim arising under or in relation to providing this software shall be - submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in - accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and - the United Nations Convention on Contracts on the International Sales of Goods. +(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository. All Rights Reserved. + +This software is protected by copyright law and by international treaties. +The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository retain full ownership rights in their respective contributions in +the software. This notice grants no license of any kind, including but not limited to patent +license, nor is any license granted by implication, estoppel or otherwise. + +Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +contributions. + +This software is provided "AS IS", without any express or implied warranties. The software is in the +development stage. It is intended exclusively for experts who have experience with such software and +solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +and fitness for a particular purpose are hereby disclaimed and excluded. + +Any dispute, controversy or claim arising under or in relation to providing this software shall be +submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +the United Nations Convention on Contracts on the International Sales of Goods. """ import pytest from tests.split_rendering.utils import * - """ Ambisonics """ - - +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_AMBI) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_AMBI) @@ -47,11 +45,16 @@ def test_ambisonics_full_chain_split( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, - test_info, in_fmt, bitrate, render_config, trajectory + get_odg_bin, + test_info, + in_fmt, + bitrate, + render_config, + trajectory, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -67,14 +70,14 @@ def test_ambisonics_full_chain_split( pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) - @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_AMBI) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI_SPLIT_REND) @@ -82,34 +85,39 @@ def test_ambisonics_external_split( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, - test_info, in_fmt, render_config, trajectory): + get_odg_bin, + test_info, + in_fmt, + render_config, + trajectory, +): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_external_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, render_config=RENDER_CFG_DIR.joinpath(f"{render_config}.txt"), + binary_suffix=EXE_SUFFIX, pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, ) """ Multichannel """ - +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_MC) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_MC) @@ -118,18 +126,23 @@ def test_multichannel_full_chain_split( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, - test_info, in_fmt, bitrate, render_config, trajectory + get_odg_bin, + test_info, + in_fmt, + bitrate, + render_config, + trajectory, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_full_chain_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, bitrate=bitrate, @@ -138,14 +151,14 @@ def test_multichannel_full_chain_split( pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) - @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_MC) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MC_SPLIT_REND) @@ -153,34 +166,39 @@ def test_multichannel_external_split( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, -test_info, in_fmt, render_config, trajectory): + get_odg_bin, + test_info, + in_fmt, + render_config, + trajectory, +): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_external_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, render_config=RENDER_CFG_DIR.joinpath(f"{render_config}.txt"), + binary_suffix=EXE_SUFFIX, pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, ) """ ISM """ - +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_ISM) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_ISM) @@ -189,17 +207,23 @@ def test_ism_full_chain_split( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, -test_info, in_fmt, bitrate, render_config, trajectory): + get_odg_bin, + test_info, + in_fmt, + bitrate, + render_config, + trajectory, + delay_profile, +): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_full_chain_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, bitrate=bitrate, @@ -208,14 +232,14 @@ test_info, in_fmt, bitrate, render_config, trajectory): pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) - @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_ISM) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_ISM_SPLIT_REND) @@ -223,34 +247,39 @@ def test_ism_external_split( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, -test_info, in_fmt, render_config, trajectory): + get_odg_bin, + test_info, + in_fmt, + render_config, + trajectory, +): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_external_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, render_config=RENDER_CFG_DIR.joinpath(f"{render_config}.txt"), + binary_suffix=EXE_SUFFIX, pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, ) """ MASA """ - +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_MASA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_MASA) @@ -259,17 +288,23 @@ def test_masa_full_chain_split( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, -test_info, in_fmt, bitrate, render_config, trajectory): + get_odg_bin, + test_info, + in_fmt, + bitrate, + render_config, + trajectory, + delay_profile, +): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_full_chain_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, bitrate=bitrate, @@ -278,14 +313,14 @@ test_info, in_fmt, bitrate, render_config, trajectory): pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) - @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_MASA) @pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MASA_SPLIT_REND) @@ -293,34 +328,39 @@ def test_masa_external_split( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, -test_info, in_fmt, render_config, trajectory): + get_odg_bin, + test_info, + in_fmt, + render_config, + trajectory, +): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_external_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, render_config=RENDER_CFG_DIR.joinpath(f"{render_config}.txt"), + binary_suffix=EXE_SUFFIX, pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, ) """ OMASA """ - +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_OMASA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_OMASA) @@ -329,17 +369,23 @@ def test_omasa_full_chain_split( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, -test_info, in_fmt, bitrate, render_config, trajectory): + get_odg_bin, + test_info, + in_fmt, + bitrate, + render_config, + trajectory, + delay_profile, +): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_full_chain_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, bitrate=bitrate, @@ -348,17 +394,54 @@ test_info, in_fmt, bitrate, render_config, trajectory): pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) -""" OSBA """ +@pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_OMASA) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OMASA_SPLIT_REND) +def test_omasa_external_split( + record_property, + props_to_record, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + test_info, + in_fmt, + render_config, + trajectory, +): + post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") + pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") + run_external_split_rendering( + record_property, + props_to_record, + test_info, + in_fmt=in_fmt, + render_config=RENDER_CFG_DIR.joinpath(f"{render_config}.txt"), + binary_suffix=EXE_SUFFIX, + pre_trajectory=pre_trajectory, + post_trajectory=post_trajectory, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + ) +""" OSBA """ + + +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_OSBA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_OSBA) @@ -370,14 +453,20 @@ def test_osba_full_chain_split( get_mld_lim, get_ssnr, get_odg, - get_odg_bin, -test_info, in_fmt, bitrate, render_config, trajectory): + get_odg_bin, + test_info, + in_fmt, + bitrate, + render_config, + trajectory, + delay_profile, +): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_full_chain_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, bitrate=bitrate, @@ -386,15 +475,51 @@ test_info, in_fmt, bitrate, render_config, trajectory): pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) -""" PLC """ +@pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_OSBA) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_OSBA_SPLIT_REND) +def test_osba_external_split( + record_property, + props_to_record, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + test_info, + in_fmt, + render_config, + trajectory, +): + post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") + pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") + run_external_split_rendering( + record_property, + props_to_record, + test_info, + in_fmt=in_fmt, + render_config=RENDER_CFG_DIR.joinpath(f"{render_config}.txt"), + binary_suffix=EXE_SUFFIX, + pre_trajectory=pre_trajectory, + post_trajectory=post_trajectory, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + ) + + +""" PLC """ @pytest.mark.parametrize("error_pattern", PLC_ERROR_PATTERNS) @@ -405,61 +530,71 @@ def test_post_rend_plc( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, -test_info, in_fmt, render_config, trajectory, error_pattern): + get_odg_bin, + test_info, + in_fmt, + render_config, + trajectory, + error_pattern, +): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_external_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, render_config=RENDER_CFG_DIR.joinpath(f"{render_config}.txt"), + binary_suffix=EXE_SUFFIX, pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, plc_error_pattern=ERROR_PATTERNS_DIR.joinpath(f"{error_pattern}.ep"), get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, ) """ BINAURAL_SPLIT_PCM """ - full_chain_split_pcm_params = [ ("HOA3", "96000", "split_renderer_config_1dof_512k_default"), ("7_1_4", "512000", "split_renderer_config_3dofhq_512k_lc3plus"), ("ISM4", "384000", "split_renderer_config_2dof_768k_default"), ("MASA2", "256000", "split_renderer_config_3dof_384k_lcld"), - ("OMASA_2_4", "256000", "split_renderer_config_3dof_384k_lcld"), + ("ISM4MASA2", "256000", "split_renderer_config_3dof_384k_lcld"), ] - +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("in_fmt,bitrate,render_config", full_chain_split_pcm_params) def test_full_chain_split_pcm( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, -test_info, in_fmt, bitrate, render_config): + get_odg_bin, + test_info, + in_fmt, + bitrate, + render_config, + delay_profile, +): trajectory = SPLIT_REND_HR_TRAJECTORIES_TO_TEST[0] post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_full_chain_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, bitrate=bitrate, @@ -469,10 +604,11 @@ test_info, in_fmt, bitrate, render_config): post_trajectory=post_trajectory, renderer_fmt="BINAURAL_SPLIT_PCM", get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -483,35 +619,38 @@ external_split_pcm_params = [ ] - @pytest.mark.parametrize("in_fmt,render_config", external_split_pcm_params) def test_external_split_pcm( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, -test_info, in_fmt, render_config): + get_odg_bin, + test_info, + in_fmt, + render_config, +): trajectory = SPLIT_REND_HR_TRAJECTORIES_TO_TEST[0] post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_external_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, render_config=RENDER_CFG_DIR.joinpath(f"{render_config}.txt"), pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, + binary_suffix=EXE_SUFFIX, renderer_fmt="BINAURAL_SPLIT_PCM", get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, ) @@ -524,52 +663,67 @@ def test_framing_combinations_external_split( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, -test_info, in_fmt, render_config, trajectory, post_rend_fr, pre_rend_fr): + get_odg_bin, + test_info, + in_fmt, + render_config, + trajectory, + post_rend_fr, + pre_rend_fr, +): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_external_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, render_config=RENDER_FRAMING_CFG_DIR.joinpath(f"{render_config}.txt"), pre_trajectory=pre_trajectory, post_trajectory=post_trajectory, + binary_suffix=EXE_SUFFIX, post_rend_fr=post_rend_fr, pre_rend_fr=pre_rend_fr, get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, ) + +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_FRAMING) -@pytest.mark.parametrize("in_fmt", ["5_1"]) +@pytest.mark.parametrize("in_fmt", ["5_1", "FOA"]) @pytest.mark.parametrize("pre_rend_fr", SPLIT_RENDERER_PRE_FRAMINGS) @pytest.mark.parametrize("post_rend_fr", SPLIT_RENDERER_POST_FRAMINGS) def test_framing_combinations_full_chain_split( record_property, props_to_record, get_mld, - get_mld_lim, + get_mld_lim, get_ssnr, get_odg, - get_odg_bin, - test_info, in_fmt, render_config, trajectory, post_rend_fr, pre_rend_fr + get_odg_bin, + test_info, + in_fmt, + render_config, + trajectory, + post_rend_fr, + pre_rend_fr, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") run_full_chain_split_rendering( - record_property, - props_to_record, + record_property, + props_to_record, test_info, in_fmt=in_fmt, render_config=RENDER_FRAMING_CFG_DIR.joinpath(f"{render_config}.txt"), @@ -580,8 +734,9 @@ def test_framing_combinations_full_chain_split( post_rend_fr=post_rend_fr, pre_rend_fr=pre_rend_fr, get_mld=get_mld, - mld_lim=get_mld_lim, + mld_lim=get_mld_lim, get_ssnr=get_ssnr, get_odg=get_odg, - get_odg_bin=get_odg_bin, + get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) diff --git a/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py b/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py new file mode 100644 index 0000000000000000000000000000000000000000..d70640b33aa92d4919784f5853eea28c138acc46 --- /dev/null +++ b/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 + +""" + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. +""" + +import pytest + +from tempfile import TemporaryDirectory +from pathlib import Path +import filecmp + +from tests.split_rendering.utils import * +from tests.split_rendering.constants import SCRIPTS_DIR, TESTV_DIR +from tests.test_be_for_jbm_neutral_dly_profile import ( + INPUT_FILES, + get_options_cod, +) +from pyaudio3dtools import audioarray, audiofile + +IN_FORMATS = [ + "MC_5_1", + "ISM4", + "FOA", + "MASA2TC", +] + +DELAY_PROFILES = ["dly_error_profile_0.dat", "dly_error_profile_5.dat"] + + +# Compares PCM output and tracefile from a VoIP BINAURAL_SPLIT_PCM chain with equivalent BINAURAL +# chain to ensure time-scaling and other JBM operations are BE between the two. +@pytest.mark.parametrize("in_format", IN_FORMATS) +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) +def test_voip_be_splitrend_vs_binaural( + in_format, + delay_profile, + dut_encoder_frontend, + dut_decoder_frontend, + ivas_bitrate=128000, +): + with TemporaryDirectory() as tmp_dir: + tmp_dir = Path(tmp_dir) + + sampling_rate_khz = 48 + delay_profile_path = SCRIPTS_DIR / "dly_error_profiles" / delay_profile + delay_profile_id = int(delay_profile[-5]) + + # run encoder + bitstream_file = (tmp_dir / f"{in_format}-dly{delay_profile_id}.192").absolute() + dtx = False + wav_in = TESTV_DIR / INPUT_FILES[in_format] + dut_encoder_frontend.run( + ivas_bitrate, + sampling_rate_khz, + wav_in, + bitstream_file, + add_option_list=get_options_cod(in_format, dtx), + run_dir=tmp_dir, + ) + + def run_decoder(out_format): + options = [] + + # With CLDFB pose correction (default with BINAURAL_SPLIT_PCM), a 20 ms audio frame is + # rendered with only one head position (first of the 4 per frame). If we want to compare + # the output from BINAURAL_SPLIT_PCM to output from BINAURAL, the head trajectory must + # be static. + head_traj = Path(SCRIPTS_DIR / "trajectories/const000.csv") + options.extend(["-T", str(head_traj)]) + + wav_out = ( + tmp_dir + / f"{in_format}-{ivas_bitrate}-{out_format}-dly{delay_profile_id}.wav" + ).absolute() + + trace_out = wav_out.with_suffix(".trace") + options.extend(["-Tracefile", str(trace_out), "-no_delay_cmp"]) + + if out_format == "BINAURAL_SPLIT_PCM": + isar_md_file = wav_out.with_suffix(".isarmd") + options.extend(["-om", str(isar_md_file)]) + else: + isar_md_file = None + + dut_decoder_frontend.run( + out_format, + sampling_rate_khz, + bitstream_file, + wav_out, + netsim_profile=delay_profile_path, + add_option_list=options, + ) + + return wav_out, trace_out, isar_md_file + + wav_out_bin, trace_out_bin, _ = run_decoder("BINAURAL") + wav_out_sr, trace_out_sr, _ = run_decoder("BINAURAL_SPLIT_PCM") + + # Note regarding delay alignment: both output audio files contain the same decoder delay. + # + # - When outputting to BINAURAL with -no_delay_cmp, decoder delay is present in the audio + # output, as expected. + # + # - When outputting to BINAURAL_SPLIT_PCM, decoder delay is never compensated in output + # audio (irrespective of the -no_delay_cmp flag). The delay value is saved in the ISAR + # metadata file and compensated at the post-rendering stage. + audio_sr, _ = audiofile.readfile(str(wav_out_sr)) + audio_bin, _ = audiofile.readfile(str(wav_out_bin)) + + # Ensure audio and tracefiles are BE + audio_cmp_result = audioarray.compare( + audio_bin, audio_sr, fs=sampling_rate_khz * 1000, per_frame=False + ) + tracefiles_equal = filecmp.cmp(trace_out_bin, trace_out_sr) + failed = not audio_cmp_result["bitexact"] or not tracefiles_equal + if failed: + message = [] + if not audio_cmp_result["bitexact"]: + message.append( + "Difference found between delay-aligned BINAURAL audio and BINAURAL_SPLIT_PCM audio! " + f"Max abs diff: {audio_cmp_result['max_abs_diff']}" + ) + if not tracefiles_equal: + message.append( + "Difference found between BINAURAL tracefile and BINAURAL_SPLIT_PCM tracefile!" + ) + pytest.fail("; ".join(message)) diff --git a/tests/split_rendering/utils.py b/tests/split_rendering/utils.py index 9e80cee070fb62bd0f3aafdf012fab58fadd2f36..39fe5cf1a703e429f2008923067f25686d1fa8e8 100644 --- a/tests/split_rendering/utils.py +++ b/tests/split_rendering/utils.py @@ -30,31 +30,38 @@ accordance with the laws of the Federal Republic of Germany excluding its confli the United Nations Convention on Contracts on the International Sales of Goods. """ -import sys import re +import sys from pathlib import Path from tempfile import TemporaryDirectory from typing import Tuple, Optional +from os import path import numpy as np import pytest import logging +from tests.renderer.constants import ( + BIN_SUFFIX_MERGETARGET, + FORMAT_TO_FILE_COMPARETEST, + FORMAT_TO_FILE_SMOKETEST, + FORMAT_TO_METADATA_FILES, +) from tests.renderer.utils import ( - check_BE, - run_ivas_isar_enc_cmd, - run_ivas_isar_dec_cmd, - run_isar_post_rend_cmd, run_isar_ext_rend_cmd, + run_isar_post_rend_cmd, + run_ivas_isar_dec_cmd, + run_ivas_isar_enc_cmd, + run_cmd, ) from tests.split_rendering.constants import * + from ..cmp_pcm import cmp_pcm from ..conftest import parse_properties sys.path.append(SCRIPTS_DIR) from pyaudio3dtools.audiofile import readfile, writefile - def lc3plus_used(test_info, in_fmt, render_config): return ( # LC3plus used explicitly @@ -111,7 +118,7 @@ def check_xfail( f"Unsupported configuration with {in_fmt} at IVAS bitrate {ivas_bitrate}bps" ) - if not "0dof" in render_config and pre_rend_fr != 20: + if "0dof" not in render_config and pre_rend_fr != 20: pytest.xfail("pose correction (== !0dof) is expected to use 20ms") if ( @@ -140,27 +147,6 @@ def check_xfail( "unsupported framing: LCLD codec doesn't support aggregation. Pre-renderer (ISAR) frame size must match LCLD frame size." ) - -def truncate_signal( - in_file: Path, - out_file: Path, -) -> None: - """ - Truncate the signal in in_file to maximum INPUT_DURATION_SEC seconds, - and write the truncated signal to out_file - """ - data, fs = readfile(in_file) - - if data.ndim == 1: - data_out = data[: INPUT_DURATION_SEC * fs] - elif data.ndim == 2: - data_out = data[: INPUT_DURATION_SEC * fs, :] - else: - raise ValueError(f"Cannot truncate data with dimension of {data.ndim}") - - writefile(out_file, data_out, fs) - - def run_full_chain_split_rendering( record_property, props_to_record, @@ -192,11 +178,13 @@ def run_full_chain_split_rendering( with TemporaryDirectory() as tmp_dir: tmp_dir = Path(tmp_dir) - cut_in_file = tmp_dir.joinpath("cut_input.wav") renderer_fmt_for_filename = renderer_fmt.replace("BINAURAL_", "") filename_base = f"{in_fmt}_{bitrate}_{renderer_fmt_for_filename}_full_cfg_{render_config.stem}_fr_pre_{pre_rend_fr}_post_{post_rend_fr}" + if delay_profile: + filename_base += f"_{delay_profile.stem}" + ivas_bitstream_stem = f"{filename_base}.192" # NOTE: the split bitstream files need to have ".bit" extension otherwise the conformance test breaks split_bitstream_stem = f"{filename_base}.splt.bit" @@ -213,18 +201,15 @@ def run_full_chain_split_rendering( ivas_bitstream = output_path_base.joinpath(ivas_bitstream_stem) split_bitstream = output_path_base.joinpath(split_bitstream_stem) out_file = output_path_base.joinpath(out_file_stem) + if renderer_fmt == "BINAURAL_SPLIT_PCM": split_md_file = output_path_base.joinpath(split_md_file_stem) # check for metadata files - if in_fmt.upper().startswith("OSBA"): + if in_fmt.upper().startswith("ISM") and "SBA" in in_fmt.upper(): # use same MD as ISM - in_meta_files = FORMAT_TO_METADATA_FILES[f"ISM{in_fmt[5]}"] - elif ( - in_fmt.upper().startswith("ISM") - or in_fmt.upper().startswith("MASA") - or in_fmt.upper().startswith("OMASA") - ): + in_meta_files = FORMAT_TO_METADATA_FILES[f"ISM{in_fmt[3]}"] + elif in_fmt.upper().startswith("ISM") or in_fmt.upper().startswith("MASA"): in_meta_files = FORMAT_TO_METADATA_FILES[in_fmt] else: in_meta_files = None @@ -239,8 +224,7 @@ def run_full_chain_split_rendering( # if in REF or CUT creation mode use the comparetestv if test_info.config.option.create_ref or test_info.config.option.create_cut: in_file = FORMAT_TO_FILE_COMPARETEST[in_fmt] - truncate_signal(in_file, cut_in_file) - + cut_in_file = in_file.with_stem(in_file.stem + "_cut") enc_cmd[3] = str(cut_in_file) else: enc_cmd[3] = str(FORMAT_TO_FILE_SMOKETEST[in_fmt]) @@ -251,7 +235,7 @@ def run_full_chain_split_rendering( enc_cmd[1:1] = FORMAT_TO_IVAS_COD_FORMAT[in_fmt] - run_ivas_isar_enc_cmd(enc_cmd) + run_ivas_isar_enc_cmd(enc_cmd, test_info=test_info) if delay_profile: rtp_bitstream = ivas_bitstream.with_suffix(".netsimout") @@ -286,8 +270,8 @@ def run_full_chain_split_rendering( if delay_profile: dec_cmd[5:5] = ["-voip"] - run_ivas_isar_dec_cmd(dec_cmd) - + run_ivas_isar_dec_cmd(dec_cmd, test_info=test_info) + # run split renderer post_rend_cmd = SPLIT_POST_REND_CMD[:] @@ -303,49 +287,31 @@ def run_full_chain_split_rendering( if renderer_fmt == "BINAURAL_SPLIT_PCM": post_rend_cmd[7:7] = ["-im", str(split_md_file)] - run_isar_post_rend_cmd(post_rend_cmd) + run_isar_post_rend_cmd(post_rend_cmd, test_info=test_info) if test_info.config.option.create_cut: # CUT creation mode will run a comparison with REF out_file_ref = str(OUTPUT_PATH_REF.joinpath(out_file_stem)) - try: - ref, ref_fs = readfile(out_file_ref) - except FileNotFoundError: - pytest.fail( - f"Reference vector not found! Ensure they were created with the --create_ref argument.\n{out_file_ref}" - ) - - cut, cut_fs = readfile(out_file) - - if not get_mld: - [diff_found, snr, gain_b, max_diff] = check_BE( - test_info, ref, ref_fs, cut, cut_fs - ) - if diff_found: - pytest.fail( - f"CuT not BE to REF! SNR : {snr:3.2f} dB, Gain CuT: {gain_b:1.3f}, Max Diff = {int(max_diff)}" - ) - else: - output_differs, reason = cmp_pcm( - out_file, - out_file_ref, - 2, # is always "BINAURAL" - ref_fs, - get_mld=get_mld, - mld_lim=mld_lim, - get_ssnr=get_ssnr, - get_odg=get_odg, - get_odg_bin=get_odg_bin, - ) - - # NOTE: split comparison not implemented for split rendering, always unpack values - reason = reason[0] - output_differs = output_differs[0] - - props = parse_properties(reason, output_differs, props_to_record) - for k, v in props.items(): - record_property(k, v) + output_differs, reason = cmp_pcm( + out_file, + out_file_ref, + 2, # is always "BINAURAL" + 48000, # currently only 48 kHz tests + get_mld=get_mld, + mld_lim=mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + ) + + # NOTE: split comparison not implemented for split rendering, always unpack values + reason = reason[0] + output_differs = output_differs[0] + + props = parse_properties(reason, output_differs, props_to_record) + for k, v in props.items(): + record_property(k, v) if output_differs: logging.error(f"Encoder command line was: {' '.join(enc_cmd)}") @@ -386,33 +352,39 @@ def run_external_split_rendering( with TemporaryDirectory() as tmp_dir: tmp_dir = Path(tmp_dir) - cut_in_file = tmp_dir.joinpath("cut_input.wav") - split_bitstream = tmp_dir.joinpath("split.bit") + + renderer_fmt_for_filename = renderer_fmt.replace("BINAURAL_", "") + filename_base = f"{in_fmt}_{renderer_fmt_for_filename}_ext_cfg_{render_config.stem}_fr_pre_{pre_rend_fr}_post_{post_rend_fr}" + + if plc_error_pattern: + filename_base += f"_plc_{plc_error_pattern.stem}" + + split_bitstream_stem = f"{filename_base}.splt.bit" if renderer_fmt == "BINAURAL_SPLIT_PCM": - split_md_file = tmp_dir.joinpath("split_md.bin") + split_md_file_stem = f"{filename_base}.spltmd.bit" - renderer_fmt_for_filename = renderer_fmt.replace("BINAURAL_", "") - out_file_stem = f"{in_fmt}_{renderer_fmt_for_filename}_ext_cfg_{render_config.stem}_fr_pre_{pre_rend_fr}_post_{post_rend_fr}.wav" + out_file_stem = f"{filename_base}.wav" if test_info.config.option.create_ref: output_path_base = OUTPUT_PATH_REF else: output_path_base = OUTPUT_PATH_CUT + split_bitstream = output_path_base.joinpath(split_bitstream_stem) out_file = output_path_base.joinpath(out_file_stem) - - if plc_error_pattern: - out_file = out_file.with_stem( - f"{out_file.stem}_plc_{plc_error_pattern.stem}" - ) + if renderer_fmt == "BINAURAL_SPLIT_PCM": + split_md_file = output_path_base.joinpath(split_md_file_stem) # check for metadata files - if in_fmt.upper().startswith("ISM") or in_fmt.upper().startswith("MASA"): + if in_fmt.upper().startswith("ISM") and "SBA" in in_fmt.upper(): + # use name MD as ISM + in_meta_files = FORMAT_TO_METADATA_FILES[f"ISM{in_fmt[3]}"] + elif in_fmt.upper().startswith("ISM") or in_fmt.upper().startswith("MASA"): in_meta_files = FORMAT_TO_METADATA_FILES[in_fmt] else: in_meta_files = None - # generate split-rendering bitstream + # run ISAR pre-renderer split_pre_cmd = SPLIT_PRE_REND_CMD[:] if test_info.config.option.create_ref: @@ -422,8 +394,7 @@ def run_external_split_rendering( # if in REF or CUT creation mode use the comparetestv if test_info.config.option.create_ref or test_info.config.option.create_cut: in_file = FORMAT_TO_FILE_COMPARETEST[in_fmt] - truncate_signal(in_file, cut_in_file) - + cut_in_file = in_file.with_stem(in_file.stem + "_cut") split_pre_cmd[6] = str(cut_in_file) else: split_pre_cmd[6] = str(FORMAT_TO_FILE_SMOKETEST[in_fmt]) @@ -439,9 +410,9 @@ def run_external_split_rendering( if in_meta_files: split_pre_cmd[9:9] = ["-im", *in_meta_files] - run_isar_ext_rend_cmd(split_pre_cmd) + run_isar_ext_rend_cmd(split_pre_cmd, test_info=test_info) - # run split renderer + # run ISAR post-renderer split_post_cmd = SPLIT_POST_REND_CMD[:] if test_info.config.option.create_ref: @@ -459,53 +430,30 @@ def run_external_split_rendering( if plc_error_pattern: split_post_cmd[1:1] = ["-prbfi", str(plc_error_pattern)] - run_isar_ext_rend_cmd(split_post_cmd) + run_isar_ext_rend_cmd(split_post_cmd, test_info=test_info) if test_info.config.option.create_cut: # CUT creation mode will run a comparison with REF out_file_ref = OUTPUT_PATH_REF.joinpath(out_file_stem) - if plc_error_pattern: - out_file_ref = out_file_ref.with_stem( - f"{out_file_ref.stem}_plc_{plc_error_pattern.stem}" - ) - - try: - ref, ref_fs = readfile(out_file_ref) - except FileNotFoundError: - pytest.fail( - f"Reference vector not found! Ensure they were created with the --create_ref argument.\n{out_file_ref}" - ) - - cut, cut_fs = readfile(out_file) - - if not get_mld: - [diff_found, snr, gain_b, max_diff] = check_BE( - test_info, ref, ref_fs, cut, cut_fs - ) - if diff_found: - pytest.fail( - f"CuT not BE to REF! SNR : {snr:3.2f} dB, Gain CuT: {gain_b:1.3f}, Max Diff = {int(max_diff)}" - ) - else: - # see constants.py - output_differs, reason = cmp_pcm( - out_file, - out_file_ref, - 2, # is always "BINAURAL", - ref_fs, - get_mld=get_mld, - mld_lim=mld_lim, - get_ssnr=get_ssnr, - get_odg=get_odg, - get_odg_bin=get_odg_bin, - ) - # NOTE: split comparison not implemented for split rendering, always unpack values - reason = reason[0] - output_differs = output_differs[0] - - props = parse_properties(reason, output_differs, props_to_record) - for k, v in props.items(): - record_property(k, v) + + output_differs, reason = cmp_pcm( + out_file, + out_file_ref, + 2, # is always "BINAURAL", + 48000, # currently only 48 kHz tests + get_mld=get_mld, + mld_lim=mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + ) + # NOTE: split comparison not implemented for split rendering, always unpack values + reason = reason[0] + output_differs = output_differs[0] + + props = parse_properties(reason, output_differs, props_to_record) + for k, v in props.items(): + record_property(k, v) if output_differs: logging.error(f"Split Pre command line was: {' '.join(split_pre_cmd)}") diff --git a/tests/test_be_for_jbm_neutral_dly_profile.py b/tests/test_be_for_jbm_neutral_dly_profile.py index ce81d091c781958731d015a5d5b6a18dc45528b8..dee54b7d94a6f8dd0984e7d9203176f0028e8512 100644 --- a/tests/test_be_for_jbm_neutral_dly_profile.py +++ b/tests/test_be_for_jbm_neutral_dly_profile.py @@ -21,7 +21,7 @@ DTX_OFF = "DTX_OFF" # testcases where DTX is supported TESTCASES_WITH_DTX = [ # stereo - ["stereo", 32000, "STEREO"], + ["stereo", 32000, "EXT"], ["stereo", 48000, "MONO"], ["stereo", 16400, "5_1"], ["stereo", 256000, "7_1_4"], @@ -43,9 +43,8 @@ TESTCASES_WITH_DTX = [ ["MASA1TC", 128000, "EXT"], # SBA ["HOA3", 64000, "BINAURAL"], - ["HOA2", 80000, "HOA2"], - # NOTE: commented because still some bug in BASOP float reference - # ["FOA", 13200, "stereo"], + ["HOA2", 80000, "EXT"], + ["FOA", 13200, "stereo"], ] # testcases with no DTX support @@ -57,30 +56,27 @@ TESTCASES_NO_DTX = [ # McMasa ["MC_5_1", 16400, "BINAURAL_ROOM_IR"], ["MC_7_1_4", 80000, "mono"], - ["MC_5_1_2", 24400, "5_1_2"], + ["MC_5_1_2", 24400, "EXT"], # paramMC ["MC_5_1_2", 48000, "BINAURAL"], - ["MC_7_1", 80000, "7_1"], + ["MC_7_1", 80000, "EXT"], ["MC_7_1_4", 128000, "FOA"], # paramUpmix ["MC_7_1_4", 160000, "stereo"], # discrete MC ["MC_5_1_2", 512000, "BINAURAL_ROOM_REVERB"], - ["MC_7_1", 128000, "7_1"], + ["MC_7_1", 128000, "EXT"], ["MC_7_1_4", 256000, "5_1"], # OMASA ["OMASA_ISM1", 512000, "BINAURAL"], ["OMASA_ISM2", 24400, "MONO"], ["OMASA_ISM3", 80000, "7_1_4"], ["OMASA_ISM4", 64000, "HOA3"], - # NOTE: commented because EXT unsupported currently - # ["OMASA_ISM2", 32000, "EXT"], + ["OMASA_ISM2", 32000, "EXT"], # OSBA ["OSBA_ISM2_HOA2", 64000, "BINAURAL_ROOM_IR"], - # NOTE: commented because still some bug in BASOP float reference - # ["OSBA_ISM4_FOA", 512000, "BINAURAL_ROOM_REVERB"], - # NOTE: commented because EXT unsupported currently - # ["OSBA_ISM3_HOA3", 128000, "EXT"], + ["OSBA_ISM4_FOA", 512000, "BINAURAL_ROOM_REVERB"], + ["OSBA_ISM3_HOA3", 128000, "EXT"], ["OSBA_ISM2_HOA3", 96000, "5_1"], ["OSBA_ISM1_HOA2", 32000, "mono"], # BINAURAL_SPLIT_PCM as output @@ -155,12 +151,7 @@ def get_options_dec( options = [] if "BINAURAL_SPLIT" in output_format.upper(): - options.extend( - [ - "-render_config", - str(RENDER_CFG_DIR / "split_renderer_config_3dof_512k_default.txt"), - ] - ) + options.extend(["-render_config", str(RENDER_CFG_DIR / "split_renderer_config_3dof_512k_default.txt")]) if output_format.upper() == "BINAURAL_SPLIT_PCM": options.extend(["-om", str(output_file.with_suffix(".isarmd"))]) @@ -215,7 +206,6 @@ OUTPUT_FOLDER_IF_KEEP_FILES_NEUTRAL = OUTPUT_FOLDER_IF_KEEP_FILES.joinpath( "neutral-profile" ) - def compare_audio(non_voip_output, voip_output, sampling_rate_khz): # compare no-jbm and jbm output x, _ = audiofile.readfile(non_voip_output) @@ -243,7 +233,6 @@ def compare_isar_files(non_voip_isar, voip_isar): "Difference between no jbm and zero-delay jbm decoding found! ISAR files differ" ) - @pytest.mark.parametrize( "in_format,bitrate,out_format", TESTCASES_NO_DTX + TESTCASES_WITH_DTX ) @@ -256,9 +245,6 @@ def test_be_for_jbm_neutral_dly_profile_no_dtx( dut_postrend_frontend, keep_files, ): - if out_format == "BINAURAL_SPLIT_CODED" or out_format == "BINAURAL_SPLIT_PCM": - pytest.skip(f"{out_format} skipped in BASOP until suported properly") - run_test( in_format, bitrate, @@ -271,9 +257,6 @@ def test_be_for_jbm_neutral_dly_profile_no_dtx( ) -@pytest.mark.skip( - reason="non-BE bug for DTX cases not yet fixed in BASOP ivas-float-update" -) @pytest.mark.parametrize("in_format,bitrate,out_format", TESTCASES_WITH_DTX) def test_be_for_jbm_neutral_dly_profile_with_dtx( in_format, @@ -364,9 +347,7 @@ def run_test( ) # run decoder with network simulation - output_jbm = output_dir_neutral.joinpath( - output.with_suffix(f".jbm-0.{output_ext}").name - ) + output_jbm = output_dir_neutral.joinpath(output.with_suffix(f".jbm-0.{output_ext}").name) voip_options = get_options_dec(out_format, output_jbm, is_voip=True) dut_decoder_frontend.run( out_format, @@ -398,9 +379,7 @@ def run_test( return # Render non-voip output - postrend_output = output_dir_no_jbm.joinpath( - output.with_suffix(".postrend.wav").name - ) + postrend_output = output_dir_no_jbm.joinpath(output.with_suffix(".postrend.wav").name) dut_postrend_frontend.run( sampling_rate_khz, output, @@ -410,9 +389,7 @@ def run_test( ) # Render voip output - postrend_output_voip = output_dir_neutral.joinpath( - output_jbm.with_suffix(".postrend.wav").name - ) + postrend_output_voip = output_dir_neutral.joinpath(output_jbm.with_suffix(".postrend.wav").name) dut_postrend_frontend.run( sampling_rate_khz, output_jbm, diff --git a/tests/test_enc_passthrough.py b/tests/test_enc_passthrough.py index a2e74589222d6980f1fd6123624add22b35e82b3..83a3f519893b257399282ecd0a00a3c68f596087 100644 --- a/tests/test_enc_passthrough.py +++ b/tests/test_enc_passthrough.py @@ -195,6 +195,7 @@ def test_enc( get_odg_bin, compare_to_input, compare_enc_dmx, + split_comparison, ): enc_opts, dec_opts, sim_opts, eid_opts = test_dict[test_tag] @@ -228,6 +229,7 @@ def test_enc( get_odg_bin, compare_to_input, compare_enc_dmx, + split_comparison, ) \ No newline at end of file