PR #24335 opened by mkver URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24335 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24335.patch
Also add a checkasm test for compute_mantissa_size and constify the ac3dsp API. >From 871c13a38d610cf47bf9a4b0c28e7d749fc2d711 Mon Sep 17 00:00:00 2001 From: Andreas Rheinhardt <[email protected]> Date: Mon, 31 Aug 2026 17:22:51 +0200 Subject: [PATCH 1/8] avcodec/ac3dsp: Constify Signed-off-by: Andreas Rheinhardt <[email protected]> --- libavcodec/aarch64/ac3dsp_init_aarch64.c | 2 +- libavcodec/ac3dsp.c | 6 +++--- libavcodec/ac3dsp.h | 6 +++--- libavcodec/arm/ac3dsp_init_arm.c | 4 ++-- libavcodec/mips/ac3dsp_mips.c | 2 +- libavcodec/riscv/ac3dsp_init.c | 4 ++-- libavcodec/x86/ac3dsp.asm | 4 ++-- libavcodec/x86/ac3dsp_init.c | 6 +++--- tests/checkasm/ac3dsp.c | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/libavcodec/aarch64/ac3dsp_init_aarch64.c b/libavcodec/aarch64/ac3dsp_init_aarch64.c index e367353e11..8edfe911ac 100644 --- a/libavcodec/aarch64/ac3dsp_init_aarch64.c +++ b/libavcodec/aarch64/ac3dsp_init_aarch64.c @@ -26,7 +26,7 @@ #include "config.h" void ff_ac3_exponent_min_neon(uint8_t *exp, int num_reuse_blocks, int nb_coefs); -void ff_ac3_extract_exponents_neon(uint8_t *exp, int32_t *coef, int nb_coefs); +void ff_ac3_extract_exponents_neon(uint8_t *exp, const int32_t *coef, int nb_coefs); void ff_float_to_fixed24_neon(int32_t *dst, const float *src, size_t len); void ff_ac3_sum_square_butterfly_int32_neon(int64_t sum[4], const int32_t *coef0, diff --git a/libavcodec/ac3dsp.c b/libavcodec/ac3dsp.c index a4a28c8672..090287e42d 100644 --- a/libavcodec/ac3dsp.c +++ b/libavcodec/ac3dsp.c @@ -97,7 +97,7 @@ static void ac3_bit_alloc_calc_bap_c(int16_t *mask, int16_t *psd, } while (end > band_end); } -static void ac3_update_bap_counts_c(uint16_t mant_cnt[16], uint8_t *bap, +static void ac3_update_bap_counts_c(uint16_t mant_cnt[16], const uint8_t bap[], int len) { while (len-- > 0) @@ -108,7 +108,7 @@ DECLARE_ALIGNED(16, const uint16_t, ff_ac3_bap_bits)[16] = { 0, 0, 0, 3, 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16 }; -static int ac3_compute_mantissa_size_c(uint16_t mant_cnt[6][16]) +static int ac3_compute_mantissa_size_c(const uint16_t mant_cnt[6][16]) { int blk, bap; int bits = 0; @@ -128,7 +128,7 @@ static int ac3_compute_mantissa_size_c(uint16_t mant_cnt[6][16]) return bits; } -static void ac3_extract_exponents_c(uint8_t *exp, int32_t *coef, int nb_coefs) +static void ac3_extract_exponents_c(uint8_t *exp, const int32_t *coef, int nb_coefs) { int i; diff --git a/libavcodec/ac3dsp.h b/libavcodec/ac3dsp.h index b1b2bced8f..084876cb05 100644 --- a/libavcodec/ac3dsp.h +++ b/libavcodec/ac3dsp.h @@ -82,7 +82,7 @@ typedef struct AC3DSPContext { * @param[in] bap array of bap, pointing to start coef bin * @param[in] len number of elements to process */ - void (*update_bap_counts)(uint16_t mant_cnt[16], uint8_t *bap, int len); + void (*update_bap_counts)(uint16_t mant_cnt[16], const uint8_t bap[], int len); /** * Calculate the number of bits needed to encode a set of mantissas. @@ -90,9 +90,9 @@ typedef struct AC3DSPContext { * @param[in] mant_cnt bap counts for all blocks * @return mantissa bit count */ - int (*compute_mantissa_size)(uint16_t mant_cnt[6][16]); + int (*compute_mantissa_size)(const uint16_t mant_cnt[6][16]); - void (*extract_exponents)(uint8_t *exp, int32_t *coef, int nb_coefs); + void (*extract_exponents)(uint8_t *exp, const int32_t *coef, int nb_coefs); void (*sum_square_butterfly_int32)(int64_t sum[4], const int32_t *coef0, const int32_t *coef1, int len); diff --git a/libavcodec/arm/ac3dsp_init_arm.c b/libavcodec/arm/ac3dsp_init_arm.c index ae989069c9..36e2efb968 100644 --- a/libavcodec/arm/ac3dsp_init_arm.c +++ b/libavcodec/arm/ac3dsp_init_arm.c @@ -27,7 +27,7 @@ void ff_ac3_exponent_min_neon(uint8_t *exp, int num_reuse_blocks, int nb_coefs); void ff_float_to_fixed24_neon(int32_t *dst, const float *src, size_t len); -void ff_ac3_extract_exponents_neon(uint8_t *exp, int32_t *coef, int nb_coefs); +void ff_ac3_extract_exponents_neon(uint8_t *exp, const int32_t *coef, int nb_coefs); void ff_ac3_sum_square_butterfly_int32_neon(int64_t sum[4], const int32_t *coef0, const int32_t *coef1, @@ -42,7 +42,7 @@ void ff_ac3_bit_alloc_calc_bap_armv6(int16_t *mask, int16_t *psd, int snr_offset, int floor, const uint8_t *bap_tab, uint8_t *bap); -void ff_ac3_update_bap_counts_arm(uint16_t mant_cnt[16], uint8_t *bap, int len); +void ff_ac3_update_bap_counts_arm(uint16_t mant_cnt[16], const uint8_t bap[], int len); av_cold void ff_ac3dsp_init_arm(AC3DSPContext *c) { diff --git a/libavcodec/mips/ac3dsp_mips.c b/libavcodec/mips/ac3dsp_mips.c index cc49ba3888..5d15468c28 100644 --- a/libavcodec/mips/ac3dsp_mips.c +++ b/libavcodec/mips/ac3dsp_mips.c @@ -142,7 +142,7 @@ static void ac3_bit_alloc_calc_bap_mips(int16_t *mask, int16_t *psd, } while (end > band_end); } -static void ac3_update_bap_counts_mips(uint16_t mant_cnt[16], uint8_t *bap, +static void ac3_update_bap_counts_mips(uint16_t mant_cnt[16], const uint8_t bap[], int len) { void *temp0, *temp2, *temp4, *temp5, *temp6, *temp7; diff --git a/libavcodec/riscv/ac3dsp_init.c b/libavcodec/riscv/ac3dsp_init.c index f68a592839..22b1a5f962 100644 --- a/libavcodec/riscv/ac3dsp_init.c +++ b/libavcodec/riscv/ac3dsp_init.c @@ -28,8 +28,8 @@ void ff_ac3_exponent_min_rvb(uint8_t *exp, int, int); void ff_ac3_exponent_min_rvv(uint8_t *exp, int, int); -void ff_extract_exponents_rvb(uint8_t *exp, int32_t *coef, int nb_coefs); -void ff_extract_exponents_rvvb(uint8_t *exp, int32_t *coef, int nb_coefs); +void ff_extract_exponents_rvb(uint8_t *exp, const int32_t *coef, int nb_coefs); +void ff_extract_exponents_rvvb(uint8_t *exp, const int32_t *coef, int nb_coefs); void ff_float_to_fixed24_rvv(int32_t *dst, const float *src, size_t len); void ff_sum_square_butterfly_int32_rvv(int64_t *, const int32_t *, const int32_t *, int); diff --git a/libavcodec/x86/ac3dsp.asm b/libavcodec/x86/ac3dsp.asm index 5b16af99c5..5d07c5f04c 100644 --- a/libavcodec/x86/ac3dsp.asm +++ b/libavcodec/x86/ac3dsp.asm @@ -153,7 +153,7 @@ cglobal float_to_fixed24, 3, 3, 5, dst, src, len RET ;------------------------------------------------------------------------------ -; int ff_ac3_compute_mantissa_size(uint16_t mant_cnt[6][16]) +; int ff_ac3_compute_mantissa_size(const uint16_t mant_cnt[6][16]) ;------------------------------------------------------------------------------ %macro PHADDD4 2 ; xmm src, xmm tmp @@ -201,7 +201,7 @@ cglobal ac3_compute_mantissa_size, 1, 2, 4, mant_cnt, sum RET ;------------------------------------------------------------------------------ -; void ff_ac3_extract_exponents(uint8_t *exp, int32_t *coef, int nb_coefs) +; void ff_ac3_extract_exponents(uint8_t *exp, const int32_t *coef, int nb_coefs) ;------------------------------------------------------------------------------ %macro PABSD 1-2 ; src/dst, unused diff --git a/libavcodec/x86/ac3dsp_init.c b/libavcodec/x86/ac3dsp_init.c index b8d25f72a5..5e14cefcff 100644 --- a/libavcodec/x86/ac3dsp_init.c +++ b/libavcodec/x86/ac3dsp_init.c @@ -28,10 +28,10 @@ void ff_ac3_exponent_min_sse2 (uint8_t *exp, int num_reuse_blocks, int nb_coefs void ff_float_to_fixed24_sse2 (int32_t *dst, const float *src, size_t len); void ff_float_to_fixed24_avx (int32_t *dst, const float *src, size_t len); -int ff_ac3_compute_mantissa_size_sse2(uint16_t mant_cnt[6][16]); +int ff_ac3_compute_mantissa_size_sse2(const uint16_t mant_cnt[6][16]); -void ff_ac3_extract_exponents_sse2 (uint8_t *exp, int32_t *coef, int nb_coefs); -void ff_ac3_extract_exponents_ssse3(uint8_t *exp, int32_t *coef, int nb_coefs); +void ff_ac3_extract_exponents_sse2 (uint8_t *exp, const int32_t *coef, int nb_coefs); +void ff_ac3_extract_exponents_ssse3(uint8_t *exp, const int32_t *coef, int nb_coefs); av_cold void ff_ac3dsp_init_x86(AC3DSPContext *c) { diff --git a/tests/checkasm/ac3dsp.c b/tests/checkasm/ac3dsp.c index fa2fe17276..f49a5b2281 100644 --- a/tests/checkasm/ac3dsp.c +++ b/tests/checkasm/ac3dsp.c @@ -95,7 +95,7 @@ static void check_ac3_extract_exponents(AC3DSPContext *c) { LOCAL_ALIGNED_16(uint8_t, v2, [MAX_EXPS]); int n; - declare_func(void, uint8_t *, int32_t *, int); + declare_func(void, uint8_t *, const int32_t *, int); for (n = 512; n <= MAX_EXPS; n += 256) { if (check_func(c->extract_exponents, "ac3_extract_exponents_n%d", n)) { -- 2.52.0 >From 8f2dc4a623d1ddc5da84abe9b46227b7fad174d2 Mon Sep 17 00:00:00 2001 From: Andreas Rheinhardt <[email protected]> Date: Mon, 31 Aug 2026 17:25:12 +0200 Subject: [PATCH 2/8] tests/checkasm/ac3dsp: Add test for compute_mantissa_size Signed-off-by: Andreas Rheinhardt <[email protected]> --- tests/checkasm/ac3dsp.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/checkasm/ac3dsp.c b/tests/checkasm/ac3dsp.c index f49a5b2281..75a2b1d7ac 100644 --- a/tests/checkasm/ac3dsp.c +++ b/tests/checkasm/ac3dsp.c @@ -22,9 +22,9 @@ #include <stdint.h> #include <string.h> -#include "libavutil/mem.h" #include "libavutil/mem_internal.h" +#include "libavcodec/ac3defs.h" #include "libavcodec/ac3dsp.h" #include "checkasm.h" @@ -139,6 +139,29 @@ static void check_float_to_fixed24(AC3DSPContext *c) { report("float_to_fixed24"); } +static void check_compute_mantissa_size(AC3DSPContext *const c) +{ + declare_func(int, const uint16_t mant_cnt[6][16]); + + if (!check_func(c->compute_mantissa_size, "compute_mantissa_size")) + return; + + DECLARE_ALIGNED_16(uint16_t, mant_cnt)[AC3_MAX_BLOCKS][16]; + + // The maximum of a single value is 3*60 (max_bandwith_code) + 73 + 2 + checkasm_randomize_mask16((uint16_t*)mant_cnt, AC3_MAX_BLOCKS*16, 0xFF); + + int size_ref = call_ref(mant_cnt); + int size_new = call_new(mant_cnt); + + if (size_ref != size_new) + fail(); + + bench_new(mant_cnt); + + report("compute_mantissa_size"); +} + static void check_ac3_sum_square_butterfly_int32(AC3DSPContext *c) { #define ELEMS 240 LOCAL_ALIGNED_16(int32_t, lt, [ELEMS]); @@ -198,6 +221,7 @@ void checkasm_check_ac3dsp(void) check_ac3_exponent_min(&c); check_ac3_extract_exponents(&c); check_float_to_fixed24(&c); + check_compute_mantissa_size(&c); check_ac3_sum_square_butterfly_int32(&c); check_ac3_sum_square_butterfly_float(&c); } -- 2.52.0 >From 724cb2233960029f9e491298776482c31302f5e9 Mon Sep 17 00:00:00 2001 From: Andreas Rheinhardt <[email protected]> Date: Mon, 31 Aug 2026 17:35:38 +0200 Subject: [PATCH 3/8] avcodec/x86/ac3dsp: Avoid unnecessary horizontal addition Old benchmarks: compute_mantissa_size_c: 112.1 compute_mantissa_size_sse2: 6.5 (17.29x) New benchmarks: compute_mantissa_size_c: 111.6 compute_mantissa_size_sse2: 5.5 (20.35x) Signed-off-by: Andreas Rheinhardt <[email protected]> --- libavcodec/x86/ac3dsp.asm | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/libavcodec/x86/ac3dsp.asm b/libavcodec/x86/ac3dsp.asm index 5d07c5f04c..51874bc5f5 100644 --- a/libavcodec/x86/ac3dsp.asm +++ b/libavcodec/x86/ac3dsp.asm @@ -164,40 +164,38 @@ cglobal float_to_fixed24, 3, 3, 5, dst, src, len %endmacro INIT_XMM sse2 -cglobal ac3_compute_mantissa_size, 1, 2, 4, mant_cnt, sum +cglobal ac3_compute_mantissa_size, 1, 1, 5, mant_cnt movdqa m0, [mant_cntq ] movdqa m1, [mant_cntq+ 1*16] + movdqa m4, [pw_bap_mul1] paddw m0, [mant_cntq+ 2*16] paddw m1, [mant_cntq+ 3*16] + movhpd m2, [mant_cntq +2] paddw m0, [mant_cntq+ 4*16] paddw m1, [mant_cntq+ 5*16] + movlpd m2, [mant_cntq+1*32+2] paddw m0, [mant_cntq+ 6*16] paddw m1, [mant_cntq+ 7*16] paddw m0, [mant_cntq+ 8*16] + pmulhuw m2, m4 paddw m1, [mant_cntq+ 9*16] paddw m0, [mant_cntq+10*16] paddw m1, [mant_cntq+11*16] pmaddwd m0, [ac3_bap_bits ] pmaddwd m1, [ac3_bap_bits+16] paddd m0, m1 - PHADDD4 m0, m1 - movd sumd, m0 - movdqa m3, [pw_bap_mul1] - movhpd m0, [mant_cntq +2] - movlpd m0, [mant_cntq+1*32+2] movhpd m1, [mant_cntq+2*32+2] movlpd m1, [mant_cntq+3*32+2] - movhpd m2, [mant_cntq+4*32+2] - movlpd m2, [mant_cntq+5*32+2] - pmulhuw m0, m3 - pmulhuw m1, m3 - pmulhuw m2, m3 - paddusw m0, m1 - paddusw m0, m2 - pmaddwd m0, [pw_bap_mul2] + movhpd m3, [mant_cntq+4*32+2] + movlpd m3, [mant_cntq+5*32+2] + pmulhuw m1, m4 + pmulhuw m3, m4 + paddusw m1, m2 + paddusw m1, m3 + pmaddwd m1, [pw_bap_mul2] + paddd m0, m1 PHADDD4 m0, m1 movd eax, m0 - add eax, sumd RET ;------------------------------------------------------------------------------ -- 2.52.0 >From 25d8f08529265c2ba436f5f5917ed23b9f848c1d Mon Sep 17 00:00:00 2001 From: Andreas Rheinhardt <[email protected]> Date: Mon, 31 Aug 2026 17:46:32 +0200 Subject: [PATCH 4/8] avcodec/x86/ac3dsp: Use movq instead of movlpd Avoids a dependency on dst. Signed-off-by: Andreas Rheinhardt <[email protected]> --- libavcodec/x86/ac3dsp.asm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libavcodec/x86/ac3dsp.asm b/libavcodec/x86/ac3dsp.asm index 51874bc5f5..9acf595131 100644 --- a/libavcodec/x86/ac3dsp.asm +++ b/libavcodec/x86/ac3dsp.asm @@ -170,10 +170,10 @@ cglobal ac3_compute_mantissa_size, 1, 1, 5, mant_cnt movdqa m4, [pw_bap_mul1] paddw m0, [mant_cntq+ 2*16] paddw m1, [mant_cntq+ 3*16] - movhpd m2, [mant_cntq +2] + movq m2, [mant_cntq +2] paddw m0, [mant_cntq+ 4*16] paddw m1, [mant_cntq+ 5*16] - movlpd m2, [mant_cntq+1*32+2] + movhpd m2, [mant_cntq+1*32+2] paddw m0, [mant_cntq+ 6*16] paddw m1, [mant_cntq+ 7*16] paddw m0, [mant_cntq+ 8*16] @@ -184,10 +184,10 @@ cglobal ac3_compute_mantissa_size, 1, 1, 5, mant_cnt pmaddwd m0, [ac3_bap_bits ] pmaddwd m1, [ac3_bap_bits+16] paddd m0, m1 - movhpd m1, [mant_cntq+2*32+2] - movlpd m1, [mant_cntq+3*32+2] - movhpd m3, [mant_cntq+4*32+2] - movlpd m3, [mant_cntq+5*32+2] + movq m1, [mant_cntq+2*32+2] + movhpd m1, [mant_cntq+3*32+2] + movq m3, [mant_cntq+4*32+2] + movhpd m3, [mant_cntq+5*32+2] pmulhuw m1, m4 pmulhuw m3, m4 paddusw m1, m2 -- 2.52.0 >From 1697c6868f0a5c5a3d4dc13e2ab5d9018235aa8c Mon Sep 17 00:00:00 2001 From: Andreas Rheinhardt <[email protected]> Date: Mon, 31 Aug 2026 17:48:12 +0200 Subject: [PATCH 5/8] avcodec/x86/ac3dsp: Don't use saturated addition unnecessarily These numbers always fit into 16bit. Signed-off-by: Andreas Rheinhardt <[email protected]> --- libavcodec/x86/ac3dsp.asm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libavcodec/x86/ac3dsp.asm b/libavcodec/x86/ac3dsp.asm index 9acf595131..90ead9814b 100644 --- a/libavcodec/x86/ac3dsp.asm +++ b/libavcodec/x86/ac3dsp.asm @@ -190,8 +190,8 @@ cglobal ac3_compute_mantissa_size, 1, 1, 5, mant_cnt movhpd m3, [mant_cntq+5*32+2] pmulhuw m1, m4 pmulhuw m3, m4 - paddusw m1, m2 - paddusw m1, m3 + paddw m1, m2 + paddw m1, m3 pmaddwd m1, [pw_bap_mul2] paddd m0, m1 PHADDD4 m0, m1 -- 2.52.0 >From 2bbebeb58f13793000ff45ac9ce97d7f470fef9d Mon Sep 17 00:00:00 2001 From: Andreas Rheinhardt <[email protected]> Date: Mon, 31 Aug 2026 17:50:13 +0200 Subject: [PATCH 6/8] avcodec/x86/ac3dsp: Don't duplicate HADDD Signed-off-by: Andreas Rheinhardt <[email protected]> --- libavcodec/x86/ac3dsp.asm | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/libavcodec/x86/ac3dsp.asm b/libavcodec/x86/ac3dsp.asm index 90ead9814b..deb2a71a26 100644 --- a/libavcodec/x86/ac3dsp.asm +++ b/libavcodec/x86/ac3dsp.asm @@ -156,13 +156,6 @@ cglobal float_to_fixed24, 3, 3, 5, dst, src, len ; int ff_ac3_compute_mantissa_size(const uint16_t mant_cnt[6][16]) ;------------------------------------------------------------------------------ -%macro PHADDD4 2 ; xmm src, xmm tmp - movhlps %2, %1 - paddd %1, %2 - pshufd %2, %1, 0x1 - paddd %1, %2 -%endmacro - INIT_XMM sse2 cglobal ac3_compute_mantissa_size, 1, 1, 5, mant_cnt movdqa m0, [mant_cntq ] @@ -194,7 +187,7 @@ cglobal ac3_compute_mantissa_size, 1, 1, 5, mant_cnt paddw m1, m3 pmaddwd m1, [pw_bap_mul2] paddd m0, m1 - PHADDD4 m0, m1 + HADDD m0, m1 movd eax, m0 RET -- 2.52.0 >From 0a75042d2abe91ef1ee568118b399c9b50da99f6 Mon Sep 17 00:00:00 2001 From: Andreas Rheinhardt <[email protected]> Date: Mon, 31 Aug 2026 17:58:01 +0200 Subject: [PATCH 7/8] avcodec/x86/ac3dsp: Use memory src operand when possible Signed-off-by: Andreas Rheinhardt <[email protected]> --- libavcodec/x86/ac3dsp.asm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libavcodec/x86/ac3dsp.asm b/libavcodec/x86/ac3dsp.asm index deb2a71a26..9822f35465 100644 --- a/libavcodec/x86/ac3dsp.asm +++ b/libavcodec/x86/ac3dsp.asm @@ -215,10 +215,14 @@ cglobal ac3_extract_exponents, 3, 3, 4, exp, coef, len mova m2, [pd_1] mova m3, [pd_151] .loop: +%if cpuflag(ssse3) + pabsd m0, [coefq+4*lenq] +%else ; move 4 32-bit coefs to xmm0 mova m0, [coefq+4*lenq] ; absolute value PABSD m0, m1 +%endif ; convert to float and extract exponents pslld m0, 1 por m0, m2 -- 2.52.0 >From 3453390682c98db251f05a2d5756d17b1887ea93 Mon Sep 17 00:00:00 2001 From: Andreas Rheinhardt <[email protected]> Date: Mon, 31 Aug 2026 18:05:27 +0200 Subject: [PATCH 8/8] avcodec/x86/ac3dsp: Avoid reg-reg mov when forcing VEX encoding Signed-off-by: Andreas Rheinhardt <[email protected]> --- libavcodec/x86/ac3dsp.asm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libavcodec/x86/ac3dsp.asm b/libavcodec/x86/ac3dsp.asm index 9822f35465..702ea1579d 100644 --- a/libavcodec/x86/ac3dsp.asm +++ b/libavcodec/x86/ac3dsp.asm @@ -228,8 +228,7 @@ cglobal ac3_extract_exponents, 3, 3, 4, exp, coef, len por m0, m2 cvtdq2ps m1, m0 psrld m1, 23 - mova m0, m3 - psubd m0, m1 + psubd m0, m3, m1 ; move the lowest byte in each of 4 dwords to the low dword ; NOTE: We cannot just extract the low bytes with pshufb because the dword ; result for 16777215 is -1 due to float inaccuracy. Using packuswb -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
