diff --git a/examples/TEMPLATE.yml b/examples/TEMPLATE.yml index 72fcd82ce898597aa7b64c62a7e5e7a33c89650e..6248a8888db058115d447b0c1eb332debeff6e20 100755 --- a/examples/TEMPLATE.yml +++ b/examples/TEMPLATE.yml @@ -37,7 +37,7 @@ input_path: ".../ivas/items/HOA3" output_path: ".../temp_output" ### Metadata path or file(s) ### If input format is ISM{1-4} a path for the metadata files can be specified; -### default = null (for ISM search for item_name.{wav, raw, pcm}.{0-3}.csv in input folder, otherise ignored) +### default = null (for ISM search for item_name.{wav, raw, pcm}.{0-3}.csv in input folder, otherwise ignored) # metadata_path: ### Path can be set for all items with the 'all_items' key (automatic search for item_name.{wav, raw, pcm}.{0-3}.csv within this folder) # all_items: ".../metadata_folder" @@ -115,7 +115,7 @@ input: ### Horizontally concatenate input items into one long file; default = false # concatenate_input: true ### if concatenation is applied, the following two keys can be used to add zeros before or after the items - ### duration is specified in miliseconds + ### duration is specified in milliseconds # silence_pre: 2000 # silence_post: 2000 ### Specify the concatenation order in a list of strings. If not specified, the concatenation order would be @@ -126,7 +126,7 @@ input: # concatenation_order: [] ### Specify preamble duration in ms; default = 0 # preamble: 10000 - ### Flag wheter to use noise (amplitude +-4) for the preamble or silence; default = false (silence) + ### Flag whether to use noise (amplitude +-4) for the preamble or silence; default = false (silence) # preamble_noise: true ### Additive background noise # background_noise: @@ -154,7 +154,7 @@ input: ### REQUIRED: either error_pattern (and errpatt_late_loss_rate or errpatt_delay) or error_profile ### delay error profile file # error_pattern: ".../dly_error_profile.dat" - ### Late loss rate in precent for EVS + ### Late loss rate in percent for EVS # errpatt_late_loss_rate: 1 ### Constant JBM delay in milliseconds for EVS # errpatt_delay: 200 diff --git a/ivas_processing_scripts/__init__.py b/ivas_processing_scripts/__init__.py index f36bdc9af33039bb02c1d63e191bcfef671e2acc..a457eb5e403f62f5c44f054eb24a9ab2c6813ab1 100755 --- a/ivas_processing_scripts/__init__.py +++ b/ivas_processing_scripts/__init__.py @@ -31,10 +31,7 @@ # import logging -import sys from itertools import product -from multiprocessing import Pool -from time import sleep from ivas_processing_scripts.audiotools.metadata import ( check_ISM_metadata, @@ -53,12 +50,7 @@ from ivas_processing_scripts.processing.processing import ( process_item, reorder_items_list, ) -from ivas_processing_scripts.utils import ( - DirManager, - apply_func_parallel, - progressbar_update, - spinner, -) +from ivas_processing_scripts.utils import DirManager, apply_func_parallel def logging_init(args, cfg): @@ -190,33 +182,9 @@ def main(args): (item, tmp_dir, out_dir, chain["processes"], logger, metadata) ) - if cfg.multiprocessing: - # set up values for progress display and chunksize - count = len(item_args) - width = 80 - - # submit tasks to the pool - p = Pool() - results = p.starmap_async( - process_item, - item_args, - ) - - # poll progress - progressbar_update(0, count, width) - while not results.ready(): - progressbar_update(count - int(results._number_left), count, width) - spinner() - sleep(0.1) - progressbar_update(count, count, width) - print("", flush=True, file=sys.stdout) - results.get() - - p.close() - p.join() - - else: - apply_func_parallel(process_item, item_args, None, None, True) + apply_func_parallel( + process_item, item_args, None, "mp" if cfg.multiprocessing else None, True + ) # copy configuration to output directory cfg.to_file(cfg.output_path.joinpath(f"{cfg.name}.yml")) diff --git a/ivas_processing_scripts/audiotools/metadata.py b/ivas_processing_scripts/audiotools/metadata.py index 6eb1199abc40be3edcb03c1a6d18fce88a5c3682..8bcd466224e01d3bc2855a69a5a44b6ee580b88f 100755 --- a/ivas_processing_scripts/audiotools/metadata.py +++ b/ivas_processing_scripts/audiotools/metadata.py @@ -376,7 +376,9 @@ def concat_meta_from_file( # add preamble if preamble: - concat_meta_all_obj = add_remove_preamble(concat_meta_all_obj, preamble) + concat_meta_all_obj = add_remove_metadata_preamble( + concat_meta_all_obj, preamble + ) write_ISM_metadata_in_file(concat_meta_all_obj, out_file) @@ -621,7 +623,7 @@ def metadata_search_MASA( return list_meta -def add_remove_preamble( +def add_remove_metadata_preamble( metadata, preamble, add: Optional[bool] = True, diff --git a/ivas_processing_scripts/processing/preprocessing_2.py b/ivas_processing_scripts/processing/preprocessing_2.py index 646dc3cbd645b4bddf4ced33fa78fb7007ce5fa4..cce42bbf20a5f7d9775cd799e6ac35a7c03859b5 100644 --- a/ivas_processing_scripts/processing/preprocessing_2.py +++ b/ivas_processing_scripts/processing/preprocessing_2.py @@ -40,7 +40,7 @@ from ivas_processing_scripts.audiotools import audio from ivas_processing_scripts.audiotools.audioarray import trim from ivas_processing_scripts.audiotools.audiofile import write from ivas_processing_scripts.audiotools.metadata import ( - add_remove_preamble, + add_remove_metadata_preamble, write_ISM_metadata_in_file, ) from ivas_processing_scripts.audiotools.wrappers.bs1770 import ( @@ -83,7 +83,7 @@ class Preprocessing2(Processing): metadata = audio_object.object_pos # add preamble - metadata = add_remove_preamble(metadata, preamble) + metadata = add_remove_metadata_preamble(metadata, preamble) # repeat signal if self.repeat_signal: diff --git a/ivas_processing_scripts/processing/processing.py b/ivas_processing_scripts/processing/processing.py index ca4c6940fc6f5fce6f6c71e33d80254deb4b152e..51380988b2bdfa225d2dc0a054ac22f62136376f 100755 --- a/ivas_processing_scripts/processing/processing.py +++ b/ivas_processing_scripts/processing/processing.py @@ -31,35 +31,26 @@ # import logging -import sys from abc import ABC, abstractmethod from itertools import repeat -from multiprocessing import Pool from pathlib import Path from shutil import copyfile -from time import sleep from typing import Iterable, Union from warnings import warn -import numpy as np - from ivas_processing_scripts.audiotools import audio from ivas_processing_scripts.audiotools.audioarray import window from ivas_processing_scripts.audiotools.audiofile import concat, trim from ivas_processing_scripts.audiotools.constants import IVAS_FRAME_LEN_MS from ivas_processing_scripts.audiotools.convert.__init__ import convert from ivas_processing_scripts.audiotools.metadata import ( - add_remove_preamble, + add_remove_metadata_preamble, concat_meta_from_file, ) from ivas_processing_scripts.constants import LOGGER_DATEFMT, LOGGER_FORMAT from ivas_processing_scripts.processing.config import TestConfig -from ivas_processing_scripts.utils import ( - list_audio, - pairwise, - progressbar_update, - spinner, -) +from ivas_processing_scripts.processing.tx import get_timescaled_splits +from ivas_processing_scripts.utils import apply_func_parallel, list_audio, pairwise class Processing(ABC): @@ -202,7 +193,17 @@ def concat_setup(cfg: TestConfig, chain, logger: logging.Logger): def concat_teardown( - x, splits, out_fmt, fs, in_fs, meta, tracefile, ivas_jbm, logger: logging.Logger + x, + splits, + out_fmt, + fs, + in_fs, + meta, + tracefile, + ivas_jbm, + repeat_signal, + preamble, + logger: logging.Logger, ): if splits is None: raise ValueError("Splitting not possible without split marker") @@ -213,7 +214,7 @@ def concat_teardown( if (out_fmt.startswith("ISM") or out_fmt.startswith("MASA")) and ivas_jbm: raise ValueError( - "Splitting with JBM compensation not supportet for formats with metadata (e.g. MASA, ISM)" + "Splitting with JBM compensation not supported for formats with metadata (e.g. MASA, ISM)" ) if logger and ivas_jbm: @@ -228,59 +229,28 @@ def concat_teardown( relative_fs_change = fs_new / fs_old new_splits = [0] for split_i in splits: - new_splits.append(int(float(split_i) * relative_fs_change)) + new_splits.append(int(split_i * relative_fs_change)) splits = new_splits else: - # adjust splits for jbm ivas conditions - # following code is based on jbmtrim.cpp script - rtpTimeScale = 1000 # in ms - playTimeScale = 1000 # in ms - new_splits = [None] * (len(splits) + 1) - - split_start = 1 / float(fs) - i = 0 - lastRtpTs = 0 - lastPlayTime = 0 - # find last JBM trace entry with lower or equal RTP time stamp - for j in range(tracefile.shape[0]): - entry = tracefile[j] - # ignore frames with unknown RTP time stamp or playout time - if entry[1] == -1 or entry[3] < 0: - continue - # check if the next position to cut is found - if entry[1] / rtpTimeScale >= split_start: - # interpolate between current and previous RTP time stamp to - # increase accuracy in case of DTX where lot of time stamps are missing - if (num := entry[1] / rtpTimeScale - split_start) == 0: - rtpTsRelErr = num - else: - rtpTsRelErr = num / ( - ((entry[1] - lastRtpTs) / rtpTimeScale) + sys.float_info.epsilon - ) - playTimeAbsErr = rtpTsRelErr * (entry[3] - lastPlayTime) / playTimeScale - # found one split, save in list and search for next - new_splits[i] = entry[3] / playTimeScale - playTimeAbsErr - # get next split marker; add one to make computation more similar to jbmtrim - split_start = (float(splits[i]) + 1) / float(fs) - i += 1 - if i >= len(new_splits): - break - lastRtpTs = entry[1] - lastPlayTime = entry[3] - - # check if all splits are found - if i < (len(new_splits) - 1): - raise ValueError("Error in item splitting with JBM compensation") - elif i < (len(new_splits)): - # catch item with missing end - warn("Last split after end of file for IVAS JBM condition") - new_splits[i] = lastPlayTime / playTimeScale - - # set new values and use new sampling rate - splits = new_splits - for s in range(len(splits)): - # subtract one again (was only used to make computation more similar to jbmtrim) - splits[s] = int(np.floor(splits[s] * float(in_fs))) - 1 + # handle signal repeat and preamble for JBM conditions since we have trace data + + # for preamble, the first split point should be at the end of the preamble + preamble_smp = preamble * in_fs // 1000 if preamble > 0 else 0 + # for repeat signal, the first split point should be the start of the repetition + repeat_start = preamble_smp + splits[-1] if repeat_signal else 0 + + # adjust the offsets of the splits accordingly + splits = [ + preamble_smp + repeat_start, + *[s + preamble_smp + repeat_start for s in splits], + ] + + splits_ts = get_timescaled_splits(tracefile, splits, fs, in_fs) + + # the above function assumes the starting point is the first playout frame + # for repeat signal or preamble, we don't want the split to start there + if repeat_signal or preamble > 0: + splits = splits_ts[1:] # check if last split ending coincides with last sample of signal if splits[-1] > len(x): @@ -331,10 +301,6 @@ def preprocess(cfg, logger): logger.info(f" Generating condition: {preprocessing['name']}") - # set up values for progress display and chunksize - count = len(cfg.items_list) - width = 80 - # run preprocessing args = list( zip( @@ -346,25 +312,10 @@ def preprocess(cfg, logger): cfg.metadata_path, ) ) - p = Pool() - results = p.starmap_async( - process_item, - args, + apply_func_parallel( + process_item, args, None, "mp" if cfg.multiprocessing else None, True ) - # poll progress - progressbar_update(0, count, width) - while not results.ready(): - progressbar_update(count - int(results._number_left), count, width) - spinner() - sleep(0.1) - progressbar_update(count, count, width) - print("", flush=True, file=sys.stdout) - results.get() - - p.close() - p.join() - # update the configuration to use preprocessing outputs as new inputs cfg.items_list = list_audio( cfg.out_dirs[0], select_list=getattr(cfg, "input_select", None) @@ -427,10 +378,6 @@ def preprocess_2(cfg, logger): if chain[0].concatenate_input: concat_setup(cfg, chain, logger) - # set up values for progress display and chunksize - count = len(cfg.items_list) - width = 80 - # run preprocessing 2 args = list( zip( @@ -442,25 +389,10 @@ def preprocess_2(cfg, logger): cfg.metadata_path, ) ) - p = Pool() - results = p.starmap_async( - process_item, - args, + apply_func_parallel( + process_item, args, None, "mp" if cfg.multiprocessing else None, True ) - # poll progress - progressbar_update(0, count, width) - while not results.ready(): - progressbar_update(count - int(results._number_left), count, width) - spinner() - sleep(0.1) - progressbar_update(count, count, width) - print("", flush=True, file=sys.stdout) - results.get() - - p.close() - p.join() - # update the configuration to use preprocessing 2 outputs as new inputs cfg.items_list = list_audio( cfg.out_dirs[0], select_list=getattr(cfg, "input_select", None) @@ -604,7 +536,7 @@ def remove_preamble(x, out_fmt, fs, repeat_signal, preamble_len_ms, meta, logger # remove preamble if preamble_len_ms > 0: - meta = add_remove_preamble(meta, preamble_len_ms, add=False) + meta = add_remove_metadata_preamble(meta, preamble_len_ms, add=False) # cut first half of signal if repeat_signal: diff --git a/ivas_processing_scripts/processing/processing_splitting_scaling.py b/ivas_processing_scripts/processing/processing_splitting_scaling.py index 2280f2036e1f8553d19fe2cf1d4a89aa5b6d19b2..597e256ad51eaf45a061d171545f36e5f3e2188f 100644 --- a/ivas_processing_scripts/processing/processing_splitting_scaling.py +++ b/ivas_processing_scripts/processing/processing_splitting_scaling.py @@ -203,8 +203,17 @@ class Processing_splitting_scaling(Processing): def revert_preamble_concatenation( self, x, fs, in_file, out_file, in_meta, noerror=False, logger=None ): + if self.ivas_jbm and not noerror: + # read JBM data + tracefile = in_file.with_name(f"{in_file.name.split('.')[0]}.tracefile.csv") + tracefile_data = np.genfromtxt(tracefile, delimiter=";") + validate_tracefile(tracefile_data) + else: + tracefile_data = None + # remove preamble and first half of signal due to repetition - if self.preamble or self.repeat_signal: + # JBM conditions are handled later in concat_teardown + if not self.ivas_jbm and (self.preamble or self.repeat_signal): x, in_meta = remove_preamble( x, self.out_fmt, @@ -215,7 +224,7 @@ class Processing_splitting_scaling(Processing): logger, ) - # reverse concatenation + # reverse concatenation, also handles repeat_signal/preamble reversal for JBM if self.concatenate_input: # read out splits file -> start/end, names, sampling rate splits_info_file = Path( @@ -224,15 +233,6 @@ class Processing_splitting_scaling(Processing): ) ) splits, split_names, split_fs = read_splits_file(splits_info_file) - if self.ivas_jbm and not noerror: - # read out tracefile with jbm info - tracefile_info_file = Path( - f"{in_file.with_suffix('').with_suffix('')}.tracefile.csv" - ) - tracefile_info = np.genfromtxt(tracefile_info_file, delimiter=";") - validate_tracefile(tracefile_info) - else: - tracefile_info = None # split file if self.ivas_jbm and not noerror: @@ -248,8 +248,10 @@ class Processing_splitting_scaling(Processing): fs, split_fs, in_meta, - tracefile_info, + tracefile_data, ivas_jbm_splitting_flag, + self.repeat_signal, + self.preamble, logger, ) @@ -311,8 +313,9 @@ def read_splits_file(splits_file): """Read out splitting information from split log in preproceesing 2 temp folder""" with open(splits_file, "r") as f: splits = f.readline()[:-1].split(", ") + splits = [int(s) for s in splits] names = f.readline()[:-1].split(", ") - fs = f.readline()[:-1] + fs = int(f.readline()[:-1]) return splits, names, fs diff --git a/ivas_processing_scripts/processing/tx.py b/ivas_processing_scripts/processing/tx.py index e8f3f954a45f32e471d232cc2c282bca75b75eb8..3ec929cb143f613b1e93368069fad10245f66b20 100755 --- a/ivas_processing_scripts/processing/tx.py +++ b/ivas_processing_scripts/processing/tx.py @@ -30,7 +30,11 @@ # the United Nations Convention on Contracts on the International Sales of Goods. # +import sys from typing import Optional +from warnings import warn + +import numpy as np from ivas_processing_scripts.processing.config import TestConfig from ivas_processing_scripts.utils import get_abs_path @@ -70,3 +74,55 @@ def get_tx_cfg(cfg: TestConfig, cond_cfg: dict, is_EVS: bool = False) -> Optiona raise ValueError("Type of bitstream procesing either missing or not valid") return tx_cfg + + +def get_timescaled_splits(tracefile, splits, out_fs, in_fs): + # adjust splits for jbm ivas conditions + # following code is based on jbmtrim.cpp script + rtpTimeScale = 1000 # in ms + playTimeScale = 1000 # in ms + new_splits = [None] * (len(splits) + 1) + + split_start = 1 / float(out_fs) + i = 0 + lastRtpTs = 0 + lastPlayTime = 0 + # find last JBM trace entry with lower or equal RTP time stamp + for entry in tracefile: + # ignore frames with unknown RTP time stamp or playout time + if entry[1] == -1 or entry[3] < 0: + continue + # check if the next position to cut is found + if entry[1] / rtpTimeScale >= split_start: + # interpolate between current and previous RTP time stamp to + # increase accuracy in case of DTX where lot of time stamps are missing + if (num := entry[1] / rtpTimeScale - split_start) == 0: + rtpTsRelErr = num + else: + rtpTsRelErr = num / ( + ((entry[1] - lastRtpTs) / rtpTimeScale) + sys.float_info.epsilon + ) + playTimeAbsErr = rtpTsRelErr * (entry[3] - lastPlayTime) / playTimeScale + # found one split, save in list and search for next + new_splits[i] = entry[3] / playTimeScale - playTimeAbsErr + # get next split marker; add one to make computation more similar to jbmtrim + split_start = (splits[i] + 1) / float(out_fs) + i += 1 + if i >= len(new_splits): + break + lastRtpTs = entry[1] + lastPlayTime = entry[3] + + # check if all splits are found + if i < (len(new_splits) - 1): + raise ValueError("Error in item splitting with JBM compensation") + elif i < (len(new_splits)): + # catch item with missing end + warn("Last split after end of file for IVAS JBM condition") + new_splits[i] = lastPlayTime / playTimeScale + + # set new values and use new sampling rate + # subtract one again (was only used to make computation more similar to jbmtrim) + new_splits = [int(np.floor(s * float(in_fs))) - 1 for s in new_splits] + + return new_splits