aacdec: split off channel coupling into a new file
This commit is contained in:
parent
b7387ea00e
commit
87a93a5670
9 changed files with 253 additions and 168 deletions
|
|
@ -568,4 +568,7 @@ const AACDecDSP AAC_RENAME(aac_dsp) = {
|
|||
.imdct_and_windowing_960 = AAC_RENAME(imdct_and_windowing_960),
|
||||
.imdct_and_windowing_ld = AAC_RENAME(imdct_and_windowing_ld),
|
||||
.imdct_and_windowing_eld = AAC_RENAME(imdct_and_windowing_eld),
|
||||
|
||||
.apply_dependent_coupling = AAC_RENAME(apply_dependent_coupling),
|
||||
.apply_independent_coupling = AAC_RENAME(apply_independent_coupling),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -64,8 +64,20 @@ static void init_tables_fixed(void)
|
|||
ff_thread_once(&init_fixed_once, init_tables_fixed_fn);
|
||||
}
|
||||
|
||||
static const int cce_scale_fixed[8] = {
|
||||
Q30(1.0), //2^(0/8)
|
||||
Q30(1.0905077327), //2^(1/8)
|
||||
Q30(1.1892071150), //2^(2/8)
|
||||
Q30(1.2968395547), //2^(3/8)
|
||||
Q30(1.4142135624), //2^(4/8)
|
||||
Q30(1.5422108254), //2^(5/8)
|
||||
Q30(1.6817928305), //2^(6/8)
|
||||
Q30(1.8340080864), //2^(7/8)
|
||||
};
|
||||
|
||||
/** Dequantization-related */
|
||||
#include "aacdec_fixed_dequant.h"
|
||||
|
||||
#include "aacdec_fixed_coupling.h"
|
||||
#include "aacdec_dsp_template.c"
|
||||
#include "aacdec_proc_template.c"
|
||||
|
|
|
|||
137
libavcodec/aac/aacdec_fixed_coupling.h
Normal file
137
libavcodec/aac/aacdec_fixed_coupling.h
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* AAC decoder
|
||||
* Copyright (c) 2005-2006 Oded Shimon ( ods15 ods15 dyndns org )
|
||||
* Copyright (c) 2006-2007 Maxim Gavrilov ( maxim.gavrilov gmail com )
|
||||
* Copyright (c) 2008-2013 Alex Converse <alex.converse@gmail.com>
|
||||
*
|
||||
* AAC LATM decoder
|
||||
* Copyright (c) 2008-2010 Paul Kendall <paul@kcbbs.gen.nz>
|
||||
* Copyright (c) 2010 Janne Grunau <janne-libav@jannau.net>
|
||||
*
|
||||
* AAC decoder fixed-point implementation
|
||||
* Copyright (c) 2013
|
||||
* MIPS Technologies, Inc., California.
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef AVCODEC_AAC_AACDEC_FIXED_COUPLING_H
|
||||
#define AVCODEC_AAC_AACDEC_FIXED_COUPLING_H
|
||||
|
||||
#include "libavcodec/aacdec.h"
|
||||
|
||||
/**
|
||||
* Apply dependent channel coupling (applied before IMDCT).
|
||||
*
|
||||
* @param index index into coupling gain array
|
||||
*/
|
||||
static void AAC_RENAME(apply_dependent_coupling)(AACDecContext *ac,
|
||||
SingleChannelElement *target,
|
||||
ChannelElement *cce, int index)
|
||||
{
|
||||
IndividualChannelStream *ics = &cce->ch[0].ics;
|
||||
const uint16_t *offsets = ics->swb_offset;
|
||||
int *dest = target->coeffs_fixed;
|
||||
const int *src = cce->ch[0].coeffs_fixed;
|
||||
int g, i, group, k, idx = 0;
|
||||
if (ac->oc[1].m4ac.object_type == AOT_AAC_LTP) {
|
||||
av_log(ac->avctx, AV_LOG_ERROR,
|
||||
"Dependent coupling is not supported together with LTP\n");
|
||||
return;
|
||||
}
|
||||
for (g = 0; g < ics->num_window_groups; g++) {
|
||||
for (i = 0; i < ics->max_sfb; i++, idx++) {
|
||||
if (cce->ch[0].band_type[idx] != ZERO_BT) {
|
||||
const int gain = cce->coup.gain[index][idx];
|
||||
int shift, round, c, tmp;
|
||||
|
||||
if (gain < 0) {
|
||||
c = -cce_scale_fixed[-gain & 7];
|
||||
shift = (-gain-1024) >> 3;
|
||||
}
|
||||
else {
|
||||
c = cce_scale_fixed[gain & 7];
|
||||
shift = (gain-1024) >> 3;
|
||||
}
|
||||
|
||||
if (shift < -31) {
|
||||
// Nothing to do
|
||||
} else if (shift < 0) {
|
||||
shift = -shift;
|
||||
round = 1 << (shift - 1);
|
||||
|
||||
for (group = 0; group < ics->group_len[g]; group++) {
|
||||
for (k = offsets[i]; k < offsets[i + 1]; k++) {
|
||||
tmp = (int)(((int64_t)src[group * 128 + k] * c + \
|
||||
(int64_t)0x1000000000) >> 37);
|
||||
dest[group * 128 + k] += (tmp + (int64_t)round) >> shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (group = 0; group < ics->group_len[g]; group++) {
|
||||
for (k = offsets[i]; k < offsets[i + 1]; k++) {
|
||||
tmp = (int)(((int64_t)src[group * 128 + k] * c + \
|
||||
(int64_t)0x1000000000) >> 37);
|
||||
dest[group * 128 + k] += tmp * (1U << shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dest += ics->group_len[g] * 128;
|
||||
src += ics->group_len[g] * 128;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply independent channel coupling (applied after IMDCT).
|
||||
*
|
||||
* @param index index into coupling gain array
|
||||
*/
|
||||
static void AAC_RENAME(apply_independent_coupling)(AACDecContext *ac,
|
||||
SingleChannelElement *target,
|
||||
ChannelElement *cce, int index)
|
||||
{
|
||||
int i, c, shift, round, tmp;
|
||||
const int gain = cce->coup.gain[index][0];
|
||||
const int *src = cce->ch[0].output_fixed;
|
||||
unsigned int *dest = target->output_fixed;
|
||||
const int len = 1024 << (ac->oc[1].m4ac.sbr == 1);
|
||||
|
||||
c = cce_scale_fixed[gain & 7];
|
||||
shift = (gain-1024) >> 3;
|
||||
if (shift < -31) {
|
||||
return;
|
||||
} else if (shift < 0) {
|
||||
shift = -shift;
|
||||
round = 1 << (shift - 1);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
tmp = (int)(((int64_t)src[i] * c + (int64_t)0x1000000000) >> 37);
|
||||
dest[i] += (tmp + round) >> shift;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i = 0; i < len; i++) {
|
||||
tmp = (int)(((int64_t)src[i] * c + (int64_t)0x1000000000) >> 37);
|
||||
dest[i] += tmp * (1U << shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AVCODEC_AAC_AACDEC_FIXED_COUPLING_H */
|
||||
|
|
@ -138,5 +138,6 @@ static inline float *VMUL4S(float *dst, const float *v, unsigned idx,
|
|||
}
|
||||
#endif
|
||||
|
||||
#include "aacdec_float_coupling.h"
|
||||
#include "aacdec_dsp_template.c"
|
||||
#include "aacdec_proc_template.c"
|
||||
|
|
|
|||
90
libavcodec/aac/aacdec_float_coupling.h
Normal file
90
libavcodec/aac/aacdec_float_coupling.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* AAC decoder
|
||||
* Copyright (c) 2005-2006 Oded Shimon ( ods15 ods15 dyndns org )
|
||||
* Copyright (c) 2006-2007 Maxim Gavrilov ( maxim.gavrilov gmail com )
|
||||
* Copyright (c) 2008-2013 Alex Converse <alex.converse@gmail.com>
|
||||
*
|
||||
* AAC LATM decoder
|
||||
* Copyright (c) 2008-2010 Paul Kendall <paul@kcbbs.gen.nz>
|
||||
* Copyright (c) 2010 Janne Grunau <janne-libav@jannau.net>
|
||||
*
|
||||
* AAC decoder fixed-point implementation
|
||||
* Copyright (c) 2013
|
||||
* MIPS Technologies, Inc., California.
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef AVCODEC_AAC_AACDEC_FLOAT_COUPLING_H
|
||||
#define AVCODEC_AAC_AACDEC_FLOAT_COUPLING_H
|
||||
|
||||
#include "libavcodec/aacdec.h"
|
||||
|
||||
/**
|
||||
* Apply dependent channel coupling (applied before IMDCT).
|
||||
*
|
||||
* @param index index into coupling gain array
|
||||
*/
|
||||
static void AAC_RENAME(apply_dependent_coupling)(AACDecContext *ac,
|
||||
SingleChannelElement *target,
|
||||
ChannelElement *cce, int index)
|
||||
{
|
||||
IndividualChannelStream *ics = &cce->ch[0].ics;
|
||||
const uint16_t *offsets = ics->swb_offset;
|
||||
float *dest = target->coeffs;
|
||||
const float *src = cce->ch[0].coeffs;
|
||||
int g, i, group, k, idx = 0;
|
||||
if (ac->oc[1].m4ac.object_type == AOT_AAC_LTP) {
|
||||
av_log(ac->avctx, AV_LOG_ERROR,
|
||||
"Dependent coupling is not supported together with LTP\n");
|
||||
return;
|
||||
}
|
||||
for (g = 0; g < ics->num_window_groups; g++) {
|
||||
for (i = 0; i < ics->max_sfb; i++, idx++) {
|
||||
if (cce->ch[0].band_type[idx] != ZERO_BT) {
|
||||
const float gain = cce->coup.gain[index][idx];
|
||||
for (group = 0; group < ics->group_len[g]; group++) {
|
||||
for (k = offsets[i]; k < offsets[i + 1]; k++) {
|
||||
// FIXME: SIMDify
|
||||
dest[group * 128 + k] += gain * src[group * 128 + k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dest += ics->group_len[g] * 128;
|
||||
src += ics->group_len[g] * 128;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply independent channel coupling (applied after IMDCT).
|
||||
*
|
||||
* @param index index into coupling gain array
|
||||
*/
|
||||
static void AAC_RENAME(apply_independent_coupling)(AACDecContext *ac,
|
||||
SingleChannelElement *target,
|
||||
ChannelElement *cce, int index)
|
||||
{
|
||||
const float gain = cce->coup.gain[index][0];
|
||||
const float *src = cce->ch[0].output;
|
||||
float *dest = target->output;
|
||||
const int len = 1024 << (ac->oc[1].m4ac.sbr == 1);
|
||||
|
||||
ac->fdsp->vector_fmac_scalar(dest, src, gain, len);
|
||||
}
|
||||
|
||||
#endif /* AVCODEC_AAC_AACDEC_FLOAT_COUPLING_H */
|
||||
|
|
@ -128,59 +128,6 @@ static av_always_inline void predict(PredictorState *ps, float *coef,
|
|||
ps->r0 = flt16_trunc(a * e0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply dependent channel coupling (applied before IMDCT).
|
||||
*
|
||||
* @param index index into coupling gain array
|
||||
*/
|
||||
static void apply_dependent_coupling(AACDecContext *ac,
|
||||
SingleChannelElement *target,
|
||||
ChannelElement *cce, int index)
|
||||
{
|
||||
IndividualChannelStream *ics = &cce->ch[0].ics;
|
||||
const uint16_t *offsets = ics->swb_offset;
|
||||
float *dest = target->coeffs;
|
||||
const float *src = cce->ch[0].coeffs;
|
||||
int g, i, group, k, idx = 0;
|
||||
if (ac->oc[1].m4ac.object_type == AOT_AAC_LTP) {
|
||||
av_log(ac->avctx, AV_LOG_ERROR,
|
||||
"Dependent coupling is not supported together with LTP\n");
|
||||
return;
|
||||
}
|
||||
for (g = 0; g < ics->num_window_groups; g++) {
|
||||
for (i = 0; i < ics->max_sfb; i++, idx++) {
|
||||
if (cce->ch[0].band_type[idx] != ZERO_BT) {
|
||||
const float gain = cce->coup.gain[index][idx];
|
||||
for (group = 0; group < ics->group_len[g]; group++) {
|
||||
for (k = offsets[i]; k < offsets[i + 1]; k++) {
|
||||
// FIXME: SIMDify
|
||||
dest[group * 128 + k] += gain * src[group * 128 + k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dest += ics->group_len[g] * 128;
|
||||
src += ics->group_len[g] * 128;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply independent channel coupling (applied after IMDCT).
|
||||
*
|
||||
* @param index index into coupling gain array
|
||||
*/
|
||||
static void apply_independent_coupling(AACDecContext *ac,
|
||||
SingleChannelElement *target,
|
||||
ChannelElement *cce, int index)
|
||||
{
|
||||
const float gain = cce->coup.gain[index][0];
|
||||
const float *src = cce->ch[0].output;
|
||||
float *dest = target->output;
|
||||
const int len = 1024 << (ac->oc[1].m4ac.sbr == 1);
|
||||
|
||||
ac->fdsp->vector_fmac_scalar(dest, src, gain, len);
|
||||
}
|
||||
|
||||
#include "aacdec_template.c"
|
||||
|
||||
#define LOAS_SYNC_WORD 0x2b7 ///< 11 bits LOAS sync word
|
||||
|
|
|
|||
|
|
@ -228,6 +228,13 @@ typedef struct AACDecDSP {
|
|||
void (*apply_ltp)(AACDecContext *ac, SingleChannelElement *sce);
|
||||
void (*update_ltp)(AACDecContext *ac, SingleChannelElement *sce);
|
||||
|
||||
void (*apply_dependent_coupling)(AACDecContext *ac,
|
||||
SingleChannelElement *target,
|
||||
ChannelElement *cce, int index);
|
||||
void (*apply_independent_coupling)(AACDecContext *ac,
|
||||
SingleChannelElement *target,
|
||||
ChannelElement *cce, int index);
|
||||
|
||||
void (*imdct_and_windowing)(AACDecContext *ac, SingleChannelElement *sce);
|
||||
void (*imdct_and_windowing_960)(AACDecContext *ac, SingleChannelElement *sce);
|
||||
void (*imdct_and_windowing_ld)(AACDecContext *ac, SingleChannelElement *sce);
|
||||
|
|
|
|||
|
|
@ -245,118 +245,6 @@ static av_always_inline void predict(PredictorState *ps, int *coef,
|
|||
ps->r0 = flt16_trunc(av_mul_sf(a, e0));
|
||||
}
|
||||
|
||||
|
||||
static const int cce_scale_fixed[8] = {
|
||||
Q30(1.0), //2^(0/8)
|
||||
Q30(1.0905077327), //2^(1/8)
|
||||
Q30(1.1892071150), //2^(2/8)
|
||||
Q30(1.2968395547), //2^(3/8)
|
||||
Q30(1.4142135624), //2^(4/8)
|
||||
Q30(1.5422108254), //2^(5/8)
|
||||
Q30(1.6817928305), //2^(6/8)
|
||||
Q30(1.8340080864), //2^(7/8)
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply dependent channel coupling (applied before IMDCT).
|
||||
*
|
||||
* @param index index into coupling gain array
|
||||
*/
|
||||
static void apply_dependent_coupling_fixed(AACDecContext *ac,
|
||||
SingleChannelElement *target,
|
||||
ChannelElement *cce, int index)
|
||||
{
|
||||
IndividualChannelStream *ics = &cce->ch[0].ics;
|
||||
const uint16_t *offsets = ics->swb_offset;
|
||||
int *dest = target->coeffs_fixed;
|
||||
const int *src = cce->ch[0].coeffs_fixed;
|
||||
int g, i, group, k, idx = 0;
|
||||
if (ac->oc[1].m4ac.object_type == AOT_AAC_LTP) {
|
||||
av_log(ac->avctx, AV_LOG_ERROR,
|
||||
"Dependent coupling is not supported together with LTP\n");
|
||||
return;
|
||||
}
|
||||
for (g = 0; g < ics->num_window_groups; g++) {
|
||||
for (i = 0; i < ics->max_sfb; i++, idx++) {
|
||||
if (cce->ch[0].band_type[idx] != ZERO_BT) {
|
||||
const int gain = cce->coup.gain[index][idx];
|
||||
int shift, round, c, tmp;
|
||||
|
||||
if (gain < 0) {
|
||||
c = -cce_scale_fixed[-gain & 7];
|
||||
shift = (-gain-1024) >> 3;
|
||||
}
|
||||
else {
|
||||
c = cce_scale_fixed[gain & 7];
|
||||
shift = (gain-1024) >> 3;
|
||||
}
|
||||
|
||||
if (shift < -31) {
|
||||
// Nothing to do
|
||||
} else if (shift < 0) {
|
||||
shift = -shift;
|
||||
round = 1 << (shift - 1);
|
||||
|
||||
for (group = 0; group < ics->group_len[g]; group++) {
|
||||
for (k = offsets[i]; k < offsets[i + 1]; k++) {
|
||||
tmp = (int)(((int64_t)src[group * 128 + k] * c + \
|
||||
(int64_t)0x1000000000) >> 37);
|
||||
dest[group * 128 + k] += (tmp + (int64_t)round) >> shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (group = 0; group < ics->group_len[g]; group++) {
|
||||
for (k = offsets[i]; k < offsets[i + 1]; k++) {
|
||||
tmp = (int)(((int64_t)src[group * 128 + k] * c + \
|
||||
(int64_t)0x1000000000) >> 37);
|
||||
dest[group * 128 + k] += tmp * (1U << shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dest += ics->group_len[g] * 128;
|
||||
src += ics->group_len[g] * 128;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply independent channel coupling (applied after IMDCT).
|
||||
*
|
||||
* @param index index into coupling gain array
|
||||
*/
|
||||
static void apply_independent_coupling_fixed(AACDecContext *ac,
|
||||
SingleChannelElement *target,
|
||||
ChannelElement *cce, int index)
|
||||
{
|
||||
int i, c, shift, round, tmp;
|
||||
const int gain = cce->coup.gain[index][0];
|
||||
const int *src = cce->ch[0].output_fixed;
|
||||
unsigned int *dest = target->output_fixed;
|
||||
const int len = 1024 << (ac->oc[1].m4ac.sbr == 1);
|
||||
|
||||
c = cce_scale_fixed[gain & 7];
|
||||
shift = (gain-1024) >> 3;
|
||||
if (shift < -31) {
|
||||
return;
|
||||
} else if (shift < 0) {
|
||||
shift = -shift;
|
||||
round = 1 << (shift - 1);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
tmp = (int)(((int64_t)src[i] * c + (int64_t)0x1000000000) >> 37);
|
||||
dest[i] += (tmp + round) >> shift;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i = 0; i < len; i++) {
|
||||
tmp = (int)(((int64_t)src[i] * c + (int64_t)0x1000000000) >> 37);
|
||||
dest[i] += tmp * (1U << shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "aacdec_template.c"
|
||||
|
||||
const FFCodec ff_aac_fixed_decoder = {
|
||||
|
|
|
|||
|
|
@ -2095,7 +2095,7 @@ static void spectral_to_sample(AACDecContext *ac, int samples)
|
|||
ChannelElement *che = ac->che[type][i];
|
||||
if (che && che->present) {
|
||||
if (type <= TYPE_CPE)
|
||||
apply_channel_coupling(ac, che, type, i, BEFORE_TNS, AAC_RENAME(apply_dependent_coupling));
|
||||
apply_channel_coupling(ac, che, type, i, BEFORE_TNS, ac->dsp.apply_dependent_coupling);
|
||||
if (ac->oc[1].m4ac.object_type == AOT_AAC_LTP) {
|
||||
if (che->ch[0].ics.predictor_present) {
|
||||
if (che->ch[0].ics.ltp.present)
|
||||
|
|
@ -2111,7 +2111,7 @@ static void spectral_to_sample(AACDecContext *ac, int samples)
|
|||
ac->dsp.apply_tns(che->ch[1].AAC_RENAME(coeffs),
|
||||
&che->ch[1].tns, &che->ch[1].ics, 1);
|
||||
if (type <= TYPE_CPE)
|
||||
apply_channel_coupling(ac, che, type, i, BETWEEN_TNS_AND_IMDCT, AAC_RENAME(apply_dependent_coupling));
|
||||
apply_channel_coupling(ac, che, type, i, BETWEEN_TNS_AND_IMDCT, ac->dsp.apply_dependent_coupling);
|
||||
if (type != TYPE_CCE || che->coup.coupling_point == AFTER_IMDCT) {
|
||||
imdct_and_window(ac, &che->ch[0]);
|
||||
if (ac->oc[1].m4ac.object_type == AOT_AAC_LTP)
|
||||
|
|
@ -2128,7 +2128,7 @@ static void spectral_to_sample(AACDecContext *ac, int samples)
|
|||
}
|
||||
}
|
||||
if (type <= TYPE_CCE)
|
||||
apply_channel_coupling(ac, che, type, i, AFTER_IMDCT, AAC_RENAME(apply_independent_coupling));
|
||||
apply_channel_coupling(ac, che, type, i, AFTER_IMDCT, ac->dsp.apply_independent_coupling);
|
||||
|
||||
#if USE_FIXED
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue