Commit 08e7afc9 authored by Anika Treffehn's avatar Anika Treffehn
Browse files

Merge branch '19-compensation-of-jbm-bitstream-processing-delay-is-missing' into 'main'

Resolve "Compensation of JBM bitstream processing delay is missing"

See merge request !143
parents 95be5475 f1976f0e
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -139,13 +139,19 @@ input:
    # type: "JBM"
    
    ### JBM
    ### REQUIRED: either error_pattern or error_profile
    ### 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"
    ### Index of one of the existing delay error profile files to use (1-11)
    ### Late loss rate in precent or EVS
    # errpatt_late_loss_rate: 1
    ### Constant JBM delay in milliseconds for EVS
    # errpatt_delay: 200
    ### Index of one of the existing delay error profile files to use (1-10)
    # error_profile: 5
    ## nFramesPerPacket parameter for the network simulator; default = 1
    # n_frames_per_packet: 2
    ### Seed for error pattern shift in JBM; default = 0 or determined by profile number
    # errpatt_seed: 0
    
    ### FER
    ### REQUIRED: either error_pattern or error_rate
+275 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3

#
#  (C) 2022-2023 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 os.path
from pathlib import Path
from typing import Optional, Union
from warnings import warn

from ivas_processing_scripts.audiotools.wrappers.networkSimulator import LIST_JBM_PROFILES, ERROR_PATTERNS_DIR
from ivas_processing_scripts.constants import DEFAULT_CONFIG_BINARIES
from ivas_processing_scripts.utils import find_binary, run
from ivas_processing_scripts.audiotools.wrappers.eid_xor import eid_xor
from ivas_processing_scripts.audiotools.wrappers.random_seed import random_seed
from ivas_processing_scripts.audiotools.wrappers.networkSimulator import length_pattern


def dlyerr_2_errpat(
    dlyerr_pattern: Union[str, Path],
    fer_pattern: Union[str, Path],
    length: Optional[int] = None,
    shift: Optional[int] = None,
    num_frames_packet: Optional[int] = None,
    late_loss_rate: Optional[int] = None,
    flag_byte: Optional[bool] = None,
    flag_word: Optional[bool] = None,
    flag_lf: Optional[bool] = None,
    delay: Optional[int] = None,
) -> None:
    """
    Wrapper for dlyerr_2_errpat binary to convert delay and error profiles to frame error patterns for EVS JBM
    bitstream processing

    Parameters
    ----------
    dlyerr_pattern: Union[str, Path]
        Path to delay and error pattern file
    fer_pattern: Union[str, Path]
        Path to frame error pattern file file
    length: Optional[int] = None
        length in frames
    shift: Optional[int] = None
        shift/offset in frames
    num_frames_packet: Optional[int] = None
        Number of frames per packet (1 or 2)
    late_loss_rate: Optional[int] = None
        Late loss rate in percent
    flag_byte: Optional[bool] = None
        Flag for using byte-oriented G.192 format (0x21 okay, 0x20 lost)
    flag_word: Optional[bool] = None
        Flag for using word-oriented G.192 format (0x6b21 okay, 0x6b20 lost)
    flag_lf: Optional[bool] = None
       Flag for using LF for text format to have one entry per line
    delay: Optional[int] = None
        Constant JBM delay in milliseconds
    """

    # find binary
    if "dlyerr_2_errpat" in DEFAULT_CONFIG_BINARIES["binary_paths"]:
        binary = find_binary(
            DEFAULT_CONFIG_BINARIES["binary_paths"]["dlyerr_2_errpat"].name,
            binary_path=DEFAULT_CONFIG_BINARIES["binary_paths"]["dlyerr_2_errpat"].parent,
        )
    else:
        binary = find_binary("dlyerr_2_errpat")

    # check for valid inputs
    if not Path(dlyerr_pattern).is_file():
        raise ValueError(
            f"Delay and error pattern file {dlyerr_pattern} for bitstream processing does not exist"
        )
    if delay is not None and late_loss_rate is not None:
        raise ValueError("Can't scpecify delay and late loss rate for dlyerr_2_err tool but only one of them")

    # set up command line
    cmd = [
        str(binary),
        "-i",  # input file
        str(dlyerr_pattern),
        "-o",  # output file
        str(fer_pattern),
    ]

    if length is not None:
        cmd.extend(["-L", str(length)])
    if shift is not None:
        cmd.extend(["-s", str(shift)])
    if num_frames_packet is not None:
        cmd.extend(["-f", str(num_frames_packet)])
    if late_loss_rate is not None:
        cmd.extend(["-l", str(late_loss_rate)])
    if flag_byte is not None:
        cmd.extend(["-b", str(flag_byte)])
    if flag_word is not None:
        cmd.extend(["-w", str(flag_word)])
    if flag_lf is not None:
        cmd.extend(["-c", str(flag_lf)])
    if delay is not None:
        cmd.extend(["-d", str(delay)])

    # run command
    run(cmd)

    return


def evs_jbm(bitstream, bitstream_processed, error_profile, error_pattern, errpatt_late_loss_rate, errpatt_delay, errpatt_seed, errpatt_frames_packet, master_seed):

    # convert delay and error profile
    delay = None
    num_frames_packet = None
    shift = None
    late_loss_rate = None
    length = None
    flag_word = True
    if errpatt_seed is None:
        errpatt_seed = 0

    if error_pattern is not None:
        # if error pattern and parameter are specified
        delay = errpatt_delay
        late_loss_rate = errpatt_late_loss_rate
        num_frames_packet = errpatt_frames_packet
        dlyerr_pattern = error_pattern
        # compute offset of error pattern
        len_pattern = length_pattern(dlyerr_pattern)
        shift = random_seed((0, len_pattern - 1), master_seed, errpatt_seed)

    elif error_profile is not None:
        # if eror profile number is given
        if error_profile == 1 or error_profile == 2 or error_profile == 3:
            delay = 200
            num_frames_packet = 1
        elif error_profile == 4 or error_profile == 6:
            late_loss_rate = 1
            num_frames_packet = 1
        elif error_profile == 5:
            late_loss_rate = 1
            num_frames_packet = 2
        elif error_profile == 7 or error_profile == 8 or error_profile == 9:
            delay = 200
            num_frames_packet = 1
            length = 8000
        elif error_profile == 10:
            late_loss_rate = 1
            num_frames_packet = 1
            length = 8000
        else:
            raise ValueError("JBM error profile number not an integer between 1 and 10")

        if error_profile in LIST_JBM_PROFILES:
            dlyerr_pattern = ERROR_PATTERNS_DIR.joinpath(
                f"dly_error_profile_{error_profile}.dat"
            )
        else:
            raise ValueError(
                f"JBM profile number {error_profile} does not exist, should be between {LIST_JBM_PROFILES[0]} and {LIST_JBM_PROFILES[-1]}"
            )

        # compute offset of error pattern
        len_pattern = length_pattern(dlyerr_pattern)
        shift = random_seed((0, len_pattern - 1), master_seed, error_profile, False)

    fer_pattern = Path(bitstream).with_suffix(".evs_jbm_fer.192")

    dlyerr_2_errpat(
        dlyerr_pattern=dlyerr_pattern,
        fer_pattern=fer_pattern,
        delay=delay,
        num_frames_packet=num_frames_packet,
        flag_word=flag_word,
        shift=shift,
        late_loss_rate=late_loss_rate,
        length=length,
    )

    # apply FER pattern with eid-xor
    eid_xor(fer_pattern, bitstream, bitstream_processed)


def validate_evs_jbm(
    error_pattern: Optional[Union[Path, str]] = None,
    error_profile: Optional[int] = None,
    errpatt_late_loss_rate: Optional[int] = None,
    errpatt_delay: Optional[int] = None,
    errpatt_seed: Optional[int] = None,
    n_frames_per_packet: Optional[int] = None,
) -> None:
    """
    Validate settings for the EVS JBM processing

    Parameters
    ----------
    error_pattern: Optional[Union[Path, str]]
        Path to existing error pattern
    error_profile: Optional[int]
        Index of existing error pattern
    errpatt_late_loss_rate: Optional[int]
        Late loss rate in precent or EVS
    errpatt_delay: Optional[int]
        Constant JBM delay in milliseconds for EVS
    errpatt_seed: Optional[int]
        Seed for error pattern shift in EVS JBM
    n_frames_per_packet: Optional[int]
        Number of frames per paket
    """

    if "dlyerr_2_errpat" in DEFAULT_CONFIG_BINARIES["binary_paths"]:
        binary = find_binary(
            DEFAULT_CONFIG_BINARIES["binary_paths"]["dlyerr_2_errpat"].name,
            binary_path=DEFAULT_CONFIG_BINARIES["binary_paths"][
                "dlyerr_2_errpat"
            ].parent,
        )
    else:
        binary = find_binary("dlyerr_2_errpat")

    if binary is None:
        raise FileNotFoundError(
            "The dlyerr_2_errpat binary for EVS JBM conditions was not found! Please check the configuration."
        )
    if error_pattern is not None:
        if not Path(error_pattern).exists():
            raise FileNotFoundError(
                f"The EVS JBM error profile file {error_pattern} was not found! Please check the configuration."
            )
        if error_profile is not None:
            raise ValueError(
                "JBM pattern and JBM profile number are specified for bitstream processing. Can't use both! Please check the configuration."
            )
        if errpatt_late_loss_rate is not None and errpatt_delay is not None:
            raise ValueError("For EVS JBM conditions with error pattern only late loss rate OR delay has to be specified, not both!")
        if errpatt_late_loss_rate is None and errpatt_delay is None:
            raise ValueError("For EVS JBM conditions with error pattern either late loss rate or delay has to be specified!")
        if errpatt_seed is None:
            warn("No seed was specified for EVS JBM offset -> Use 0")
    elif error_profile is not None:
        if error_profile not in LIST_JBM_PROFILES:
            raise ValueError(
                f"JBM profile number {error_profile} does not exist, should be between {LIST_JBM_PROFILES[0]} and {LIST_JBM_PROFILES[-1]}"
            )
    if n_frames_per_packet is not None and n_frames_per_packet not in [1, 2]:
        raise ValueError(
            f"n_frames_per_paket is {n_frames_per_packet}. Should be 1 or 2. Please check your configuration."
        )
    return
+25 −4
Original line number Diff line number Diff line
@@ -33,11 +33,13 @@
import logging
from pathlib import Path
from typing import Optional, Union
from warnings import warn

from ivas_processing_scripts.constants import DEFAULT_CONFIG_BINARIES
from ivas_processing_scripts.utils import find_binary, run
from ivas_processing_scripts.audiotools.wrappers.random_seed import random_seed

LIST_JBM_PROFILES = range(12)
LIST_JBM_PROFILES = range(11)
ERROR_PATTERNS_DIR = Path(__file__).parent.parent.parent.joinpath("dly_error_profiles")


@@ -82,6 +84,8 @@ def validate_network_simulator(
            raise ValueError(
                "JBM pattern and JBM profile number are specified for bitstream processing. Can't use both! Please check the configuration."
            )
        if errpatt_seed is None:
            raise warn("No error pattern seed specified for JBM offset -> use 0")
    elif error_profile is not None:
        if error_profile not in LIST_JBM_PROFILES:
            raise ValueError(
@@ -166,7 +170,8 @@ def apply_network_simulator(
    error_pattern: Optional[Union[Path, str]] = None,
    error_profile: Optional[int] = None,
    n_frames_per_packet: Optional[int] = None,
    offset: Optional[int] = 0,
    master_seed: Optional[int] = 0,
    errpatt_seed: Optional[int] = 0,
    logger: Optional[logging.Logger] = None,
) -> None:
    """
@@ -184,8 +189,10 @@ def apply_network_simulator(
        Index of existing error pattern
    n_frames_per_packet: Optional[int]
        Number of frames per paket
    offset: Optional[int]
        delay offset
    master_seed: Optional[int]
        Seed to compute delay offset
    errpatt_seed: Optional[int]
        Seed to compute delay offset
    logger: Optional[logging.Logger]
        logger
    """
@@ -215,9 +222,23 @@ def apply_network_simulator(
        if error_profile is not None and error_profile == 5:
            n_frames_per_packet = 2

    # compute offset of error pattern
    len_pattern = length_pattern(error_pattern)
    if error_profile:
        offset = random_seed((0, len_pattern - 1), master_seed, error_profile, False)
    else:
        offset = random_seed((0, len_pattern - 1), master_seed, errpatt_seed, False)

    # apply error pattern
    network_simulator(
        error_pattern, in_bitstream, out_bitstream, n_frames_per_packet, offset, logger
    )

    return


def length_pattern(path_pattern):
    with open(path_pattern, 'r') as f:
        p = f.readlines()
        length = len(p)
    return length
+1 −0
Original line number Diff line number Diff line
@@ -14,3 +14,4 @@ Necessary additional executables:
| JBM network simulator                           | networkSimulator_g192 | https://www.3gpp.org/ftp/tsg_sa/WG4_CODEC/TSGS4_76/docs/S4-131277.zip                                       |
| MASA rendering (also used in loudness measurement of MASA items)        | masaRenderer        | https://www.3gpp.org/ftp/TSG_SA/WG4_CODEC/TSGS4_122_Athens/Docs/S4-230221.zip                               |
| EVS reference conditions        | EVS_cod, EVS_dec      | https://www.3gpp.org/ftp/Specs/archive/26_series/26.443/26443-h00.zip                                       |
| EVS JBM conditions | dlyerr_2_errpat | http://ftp.3gpp.org/tsg_sa/WG4_CODEC/TSGS4_70/Docs/S4-121077.zip |
 No newline at end of file
+2 −0
Original line number Diff line number Diff line
@@ -30,3 +30,5 @@
# masaRenderer: "path/to/binary/masaRenderer"
# ### Binary for reverberation
# reverb: "path/to/binary/reverb"
# ### Binary for EVS JBM error pattern conversion tool
# dlyerr_2_errpat: "path/to/binary/dlyerr_2_errpat"
Loading