diff --git a/lib_util/ambi_convert.c b/lib_util/ambi_convert.c new file mode 100644 index 0000000000000000000000000000000000000000..282f4a08726273423d57d4986c8cad17d2666b1e --- /dev/null +++ b/lib_util/ambi_convert.c @@ -0,0 +1,438 @@ +/****************************************************************************************************** + + (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. + +*******************************************************************************************************/ + +#include +#include +#include "ambi_convert.h" + +#define SQRT_2_ 1.4142135f /* sqrtf(2.0f) */ +#define SQRT_3_ 1.7320508f /* sqrtf(3.0f) */ +#define SQRT_5_ 2.2360679f /* sqrtf(5.0f) */ +#define SQRT_7_ 2.6457513f /* sqrtf(7.0f) */ + +#define INV_SQRT_2_ 0.7071067f /* 1.0f/sqrtf(2.0f) */ +#define INV_SQRT_3_ 0.5773502f /* 1.0f/sqrtf(3.0f) */ +#define INV_SQRT_5_ 0.4472135f /* 1.0f/sqrtf(5.0f) */ +#define INV_SQRT_7_ 0.3779644f /* 1.0f/sqrtf(7.0f) */ + +#define TWO_OVER_SQRT_3_ 1.1547005f /* 2.0f/sqrtf(3.0f) */ +#define THREE_OVER_SQRT_5_ 1.3416408f /* 3.0f/sqrtf(5.0f) */ +#define SQRT_8_OVER_5_ 1.2649110f /* sqrtf(8.0f/5.0f) */ +#define SQRT_45_OVER_32_ 1.1858541f /* sqrtf(45.0f/32.0f) */ + +#define INV_TWO_OVER_SQRT_3_ 0.8660254f /* 1.0f/TWO_OVER_SQRT_3_ */ +#define INV_THREE_OVER_SQRT_5_ 0.7453560f /* 1.0f/THREE_OVER_SQRT_5_ */ +#define INV_SQRT_8_OVER_5_ 0.7905694f /* 1.0f/INV_SQRT_8_OVER_5_ */ +#define INV_SQRT_45_OVER_32_ 0.8432740f /* 1.0f/SQRT_45_OVER_32_ */ + +/* -------------------------------- + normalization conversion tables + -------------------------------- */ + +static const float SN3D_N3D[AMBI_MAX_CHANNELS] = { 1.0f, + SQRT_3_, SQRT_3_, SQRT_3_, + SQRT_5_, SQRT_5_, SQRT_5_, SQRT_5_, SQRT_5_, + SQRT_7_, SQRT_7_, SQRT_7_, SQRT_7_, SQRT_7_, SQRT_7_, SQRT_7_ }; + +static const float N3D_SN3D[AMBI_MAX_CHANNELS] = { 1.0f, + INV_SQRT_3_, INV_SQRT_3_, INV_SQRT_3_, + INV_SQRT_5_, INV_SQRT_5_, INV_SQRT_5_, INV_SQRT_5_, INV_SQRT_5_, + INV_SQRT_7_, INV_SQRT_7_, INV_SQRT_7_, INV_SQRT_7_, INV_SQRT_7_, INV_SQRT_7_, INV_SQRT_7_ }; + + +static const float SN3D_MAXN[AMBI_MAX_CHANNELS] = { 1.0f, + 1.0f, 1.0f, 1.0f, + TWO_OVER_SQRT_3_, TWO_OVER_SQRT_3_, 1.0f, TWO_OVER_SQRT_3_, TWO_OVER_SQRT_3_, + SQRT_8_OVER_5_, THREE_OVER_SQRT_5_, SQRT_45_OVER_32_, 1.0f, SQRT_45_OVER_32_, THREE_OVER_SQRT_5_, SQRT_8_OVER_5_ }; + +static const float MAXN_SN3D[AMBI_MAX_CHANNELS] = { 1.0f, + 1.0f, 1.0f, 1.0f, + INV_TWO_OVER_SQRT_3_, INV_TWO_OVER_SQRT_3_, 1.0f, INV_TWO_OVER_SQRT_3_, INV_TWO_OVER_SQRT_3_, + INV_SQRT_8_OVER_5_, INV_THREE_OVER_SQRT_5_, INV_SQRT_45_OVER_32_, 1.0f, INV_SQRT_45_OVER_32_, INV_THREE_OVER_SQRT_5_, INV_SQRT_8_OVER_5_ }; + +static const float SN3D_FM[AMBI_MAX_CHANNELS] = { INV_SQRT_2_, + 1.0f, 1.0f, 1.0f, + TWO_OVER_SQRT_3_, TWO_OVER_SQRT_3_, 1.0f, TWO_OVER_SQRT_3_, TWO_OVER_SQRT_3_, + SQRT_8_OVER_5_, THREE_OVER_SQRT_5_, SQRT_45_OVER_32_, 1.0f, SQRT_45_OVER_32_, THREE_OVER_SQRT_5_, SQRT_8_OVER_5_ }; + +static const float FM_SN3D[AMBI_MAX_CHANNELS] = { SQRT_2_, + 1.0f, 1.0f, 1.0f, + INV_TWO_OVER_SQRT_3_, INV_TWO_OVER_SQRT_3_, 1.0f, INV_TWO_OVER_SQRT_3_, INV_TWO_OVER_SQRT_3_, + INV_SQRT_8_OVER_5_, INV_THREE_OVER_SQRT_5_, INV_SQRT_45_OVER_32_, 1.0f, INV_SQRT_45_OVER_32_, INV_THREE_OVER_SQRT_5_, INV_SQRT_8_OVER_5_ }; + +/* ---------------------------- + channel re-ordering tables + ---------------------------- */ + +static const int16_t REORDER_FM_ACN[AMBI_MAX_CHANNELS] = { 0, + 2, 3, 1, + 8, 6, 4, 5, 7, + 15, 13, 11, 9, 10, 12, 14 }; + +static const int16_t REORDER_SID_ACN[AMBI_MAX_CHANNELS] = { 0, + 2, 3, 1, + 5, 7, 8, 6, 4, + 10, 12, 14, 15, 13, 11, 9 }; + +static const int16_t REORDER_ACN_FM[AMBI_MAX_CHANNELS] = { 0, + 3, 1, 2, + 6, 7, 5, 8, 4, + 12, 13, 11, 14, 10, 15, 9 }; + +static const int16_t REORDER_ACN_SID[AMBI_MAX_CHANNELS] = { 0, + 3, 1, 2, + 8, 4, 7, 5, 6, + 15, 9, 14, 10, 13, 11, 12 }; + +/* ---------------------------------- + API functions for the conversion + ---------------------------------- */ + +/*-------------------------------------------------------------------------* +* convert_ambi_format() +* +* Convert signal between ACN-SN3D and other common ambisonics conventions +--------------------------------------------------------------------------*/ + +AMBI_CONVERT_ERROR convert_ambi_format( + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ + int16_t order, /* i: ambisonics order */ + AMBI_FMT in_format, /* i: input ambisonics format */ + AMBI_FMT out_format /* i: output ambisonics format */ +) +{ + + float tmp[AMBI_MAX_CHANNELS * L_FRAME48k]; + float *p_tmp[AMBI_MAX_CHANNELS]; + AMBI_CONVERT_ERROR err = AMBI_CONVERT_OK; + + AMBI_CHANNEL_ORDER ch_ord_in = AMBI_CHANNEL_ORDER_ACN; + AMBI_CHANNEL_ORDER ch_ord_out = AMBI_CHANNEL_ORDER_ACN; + + AMBI_CHANNEL_NORM ch_norm_in = AMBI_NORM_SN3D; + AMBI_CHANNEL_NORM ch_norm_out = AMBI_NORM_SN3D; + + assert( order <= 3 ); + + if ( in_format != AMBI_FMT_ACN_SN3D && out_format != AMBI_FMT_ACN_SN3D ) + { + assert( 0 && "Conversion only supported to and from ACN-SN3D" ); + } + + for ( int16_t j = 0; j < AMBI_MAX_CHANNELS; j++ ) + { + p_tmp[j] = &tmp[j * L_FRAME48k]; + } + + switch ( in_format ) + { + case AMBI_FMT_ACN_SN3D: + ch_ord_in = AMBI_CHANNEL_ORDER_ACN; + ch_norm_in = AMBI_NORM_SN3D; + break; + case AMBI_FMT_ACN_N3D: + ch_ord_in = AMBI_CHANNEL_ORDER_ACN; + ch_norm_in = AMBI_NORM_N3D; + break; + case AMBI_FMT_FM_MAXN: + ch_ord_in = AMBI_CHANNEL_ORDER_FM; + ch_norm_in = AMBI_NORM_MAXN; + break; + case AMBI_FMT_FM_FM: + ch_ord_in = AMBI_CHANNEL_ORDER_FM; + ch_norm_in = AMBI_NORM_FM; + break; + case AMBI_FMT_SID_SN3D: + ch_ord_in = AMBI_CHANNEL_ORDER_SID; + ch_norm_in = AMBI_NORM_SN3D; + break; + case AMBI_FMT_SID_N3D: + ch_ord_in = AMBI_CHANNEL_ORDER_SID; + ch_norm_in = AMBI_NORM_N3D; + break; + default: + return AMBI_CONVERT_UNSUPPORTED_CONVERSION; + } + + switch ( out_format ) + { + case AMBI_FMT_ACN_SN3D: + ch_ord_out = AMBI_CHANNEL_ORDER_ACN; + ch_norm_out = AMBI_NORM_SN3D; + break; + case AMBI_FMT_ACN_N3D: + ch_ord_out = AMBI_CHANNEL_ORDER_ACN; + ch_norm_out = AMBI_NORM_N3D; + break; + case AMBI_FMT_FM_MAXN: + ch_ord_out = AMBI_CHANNEL_ORDER_FM; + ch_norm_out = AMBI_NORM_MAXN; + break; + case AMBI_FMT_FM_FM: + ch_ord_out = AMBI_CHANNEL_ORDER_FM; + ch_norm_out = AMBI_NORM_FM; + break; + case AMBI_FMT_SID_SN3D: + ch_ord_out = AMBI_CHANNEL_ORDER_SID; + ch_norm_out = AMBI_NORM_SN3D; + break; + case AMBI_FMT_SID_N3D: + ch_ord_out = AMBI_CHANNEL_ORDER_SID; + ch_norm_out = AMBI_NORM_N3D; + break; + default: + return AMBI_CONVERT_UNSUPPORTED_CONVERSION; + } + + if ( in_format == AMBI_FMT_ACN_SN3D && ch_norm_in != ch_norm_out ) + { + if ( ch_ord_in != ch_ord_out ) + { + if ( ( err = renormalize_channels( in, p_tmp, order, ch_norm_in, ch_norm_out ) ) != AMBI_CONVERT_OK ) + { + return err; + } + if ( ( err = reorder_channels( p_tmp, out, order, ch_ord_in, ch_ord_out ) ) != AMBI_CONVERT_OK ) + { + return err; + } + } + else + { + if ( ( err = renormalize_channels( in, out, order, ch_norm_in, ch_norm_out ) ) != AMBI_CONVERT_OK ) + { + return err; + } + } + } + else if ( in_format == AMBI_FMT_ACN_SN3D && ch_ord_in != ch_ord_out ) + { + if ( ( err = reorder_channels( in, out, order, ch_ord_in, ch_ord_out ) ) != AMBI_CONVERT_OK ) + { + return err; + } + } + else if ( out_format == AMBI_FMT_ACN_SN3D && ch_norm_in != ch_norm_out ) + { + if ( ch_ord_in != ch_ord_out ) + { + if ( ( err = reorder_channels( in, p_tmp, order, ch_ord_in, ch_ord_out ) ) != AMBI_CONVERT_OK ) + { + return err; + } + if ( ( err = renormalize_channels( p_tmp, out, order, ch_norm_in, ch_norm_out ) ) != AMBI_CONVERT_OK ) + { + return err; + } + } + else + { + if ( ( err = renormalize_channels( in, out, order, ch_norm_in, ch_norm_out ) ) != AMBI_CONVERT_OK ) + { + return err; + } + } + } + else if ( out_format == AMBI_FMT_ACN_SN3D && ch_ord_in != ch_ord_out ) + { + if ( ( err = reorder_channels( in, out, order, ch_ord_in, ch_ord_out ) ) != AMBI_CONVERT_OK ) + { + return err; + } + } + else if ( out_format == AMBI_FMT_ACN_SN3D && in_format == AMBI_FMT_ACN_SN3D ) + { + int16_t i_chan = 0; + int16_t n_chan = ( order + 1 ) * ( order + 1 ); + + for ( i_chan = 0; i_chan < n_chan; i_chan++ ) + { + int16_t i = 0; + for ( i = 0; i < L_FRAME48k; i++ ) + { + out[i_chan][i] = in[i_chan][i]; + } + } + } + else + { + assert( 0 && "This should never happen!" ); + } + + return AMBI_CONVERT_OK; +} + +/*-------------------------------------------------------------------------* +* renormalize_channels() +* +* Rescale audio channels according to the selected ambisonics convention +--------------------------------------------------------------------------*/ + +AMBI_CONVERT_ERROR renormalize_channels( + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ + int16_t order, /* i: ambisonics order */ + AMBI_CHANNEL_NORM in_format, /* i: input ambisonics format */ + AMBI_CHANNEL_NORM out_format /* i: output ambisonics format */ +) +{ + int16_t n_chan = ( order + 1 ) * ( order + 1 ); + int16_t i_chan, i; + const float *conversion_table = 0; + + /* conversion factors are applied on the channels assuming that they are still/already in ACN order */ + if ( in_format == AMBI_NORM_SN3D ) + { + if ( out_format == AMBI_NORM_N3D ) + { + conversion_table = SN3D_N3D; + } + else if ( out_format == AMBI_NORM_MAXN ) + { + conversion_table = SN3D_MAXN; + } + else if ( out_format == AMBI_NORM_FM ) + { + conversion_table = SN3D_FM; + } + else + { + return AMBI_CONVERT_UNSUPPORTED_CONVERSION; + } + } + else if ( out_format == AMBI_NORM_SN3D ) + { + if ( in_format == AMBI_NORM_N3D ) + { + conversion_table = N3D_SN3D; + } + else if ( in_format == AMBI_NORM_MAXN ) + { + conversion_table = MAXN_SN3D; + } + else if ( in_format == AMBI_NORM_FM ) + { + conversion_table = FM_SN3D; + } + else + { + return AMBI_CONVERT_UNSUPPORTED_CONVERSION; + } + } + else + { + return AMBI_CONVERT_UNSUPPORTED_CONVERSION; + } + + + for ( i_chan = 0; i_chan < n_chan; i_chan++ ) + { + float conversion_factor = conversion_table[i_chan]; + for ( i = 0; i < L_FRAME48k; i++ ) + { + out[i_chan][i] = in[i_chan][i] * conversion_factor; + } + } + + return AMBI_CONVERT_OK; +} + +/*-------------------------------------------------------------------------* +* reorder_channels() +* +* Reorder channels according to the selected ambisonics convention +--------------------------------------------------------------------------*/ + +AMBI_CONVERT_ERROR reorder_channels( + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ + int16_t order, /* i: ambisonics order */ + AMBI_CHANNEL_ORDER in_format, /* i: input ambisonics format */ + AMBI_CHANNEL_ORDER out_format /* i: output ambisonics format */ +) +{ + int16_t n_chan = ( order + 1 ) * ( order + 1 ); + int16_t i_chan, i; + float tmp[AMBI_MAX_CHANNELS]; + const int16_t *idx_table = 0; + + if ( in_format == AMBI_CHANNEL_ORDER_ACN ) + { + if ( out_format == AMBI_CHANNEL_ORDER_FM ) + { + idx_table = REORDER_ACN_FM; + } + else if ( out_format == AMBI_CHANNEL_ORDER_SID ) + { + idx_table = REORDER_ACN_SID; + } + else + { + return AMBI_CONVERT_UNSUPPORTED_CONVERSION; + } + } + else if ( out_format == AMBI_CHANNEL_ORDER_ACN ) + { + if ( in_format == AMBI_CHANNEL_ORDER_FM ) + { + idx_table = REORDER_FM_ACN; + } + else if ( in_format == AMBI_CHANNEL_ORDER_SID ) + { + idx_table = REORDER_SID_ACN; + } + else + { + return AMBI_CONVERT_UNSUPPORTED_CONVERSION; + } + } + else + { + return AMBI_CONVERT_UNSUPPORTED_CONVERSION; + } + + for ( i = 0; i < L_FRAME48k; i++ ) + { + for ( i_chan = 0; i_chan < n_chan; i_chan++ ) + { + int16_t idx = idx_table[i_chan]; + tmp[i_chan] = in[idx][i]; + } + for ( i_chan = 0; i_chan < n_chan; i_chan++ ) + { + out[i_chan][i] = tmp[i_chan]; + } + } + + return AMBI_CONVERT_OK; +} diff --git a/lib_util/ambi_convert.h b/lib_util/ambi_convert.h new file mode 100644 index 0000000000000000000000000000000000000000..825d65127c3a1e8bd922d68f737ab1258390679e --- /dev/null +++ b/lib_util/ambi_convert.h @@ -0,0 +1,95 @@ +/****************************************************************************************************** + + (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. + +*******************************************************************************************************/ + +#ifndef AMBI_CONVERT_H +#define AMBI_CONVERT_H + +#include + +#define L_FRAME48k 960 +#define AMBI_MAX_CHANNELS 16 + +typedef enum +{ + AMBI_FMT_ACN_SN3D = 0, + AMBI_FMT_ACN_N3D, + AMBI_FMT_FM_MAXN, + AMBI_FMT_FM_FM, + AMBI_FMT_SID_SN3D, + AMBI_FMT_SID_N3D +} AMBI_FMT; + +typedef enum +{ + AMBI_NORM_SN3D = 0, + AMBI_NORM_N3D, + AMBI_NORM_MAXN, + AMBI_NORM_FM +} AMBI_CHANNEL_NORM; + +typedef enum +{ + AMBI_CHANNEL_ORDER_ACN = 0, + AMBI_CHANNEL_ORDER_FM, + AMBI_CHANNEL_ORDER_SID +} AMBI_CHANNEL_ORDER; + +typedef enum +{ + AMBI_CONVERT_OK = 0, + AMBI_CONVERT_UNSUPPORTED_CONVERSION +} AMBI_CONVERT_ERROR; + +AMBI_CONVERT_ERROR convert_ambi_format( + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ + int16_t order, /* i: ambisonics order */ + AMBI_FMT in_format, /* i: input ambisonics format */ + AMBI_FMT out_format /* i: output ambisonics format */ +); + +AMBI_CONVERT_ERROR renormalize_channels( + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ + int16_t order, /* i: ambisonics order */ + AMBI_CHANNEL_NORM in_format, /* i: input ambisonics format */ + AMBI_CHANNEL_NORM out_format /* i: output ambisonics format */ +); + +AMBI_CONVERT_ERROR reorder_channels( + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ + int16_t order, /* i: ambisonics order */ + AMBI_CHANNEL_ORDER in_format, /* i: input ambisonics format */ + AMBI_CHANNEL_ORDER out_format /* i: output ambisonics format */ +); +#endif