1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/media/libopus/silk/float/encode_frame_FLP.c Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,372 @@ 1.4 +/*********************************************************************** 1.5 +Copyright (c) 2006-2011, Skype Limited. All rights reserved. 1.6 +Redistribution and use in source and binary forms, with or without 1.7 +modification, are permitted provided that the following conditions 1.8 +are met: 1.9 +- Redistributions of source code must retain the above copyright notice, 1.10 +this list of conditions and the following disclaimer. 1.11 +- Redistributions in binary form must reproduce the above copyright 1.12 +notice, this list of conditions and the following disclaimer in the 1.13 +documentation and/or other materials provided with the distribution. 1.14 +- Neither the name of Internet Society, IETF or IETF Trust, nor the 1.15 +names of specific contributors, may be used to endorse or promote 1.16 +products derived from this software without specific prior written 1.17 +permission. 1.18 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 1.19 +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1.20 +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1.21 +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 1.22 +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 1.23 +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 1.24 +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 1.25 +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 1.26 +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 1.27 +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 1.28 +POSSIBILITY OF SUCH DAMAGE. 1.29 +***********************************************************************/ 1.30 + 1.31 +#ifdef HAVE_CONFIG_H 1.32 +#include "config.h" 1.33 +#endif 1.34 + 1.35 +#include "main_FLP.h" 1.36 +#include "tuning_parameters.h" 1.37 + 1.38 +/* Low Bitrate Redundancy (LBRR) encoding. Reuse all parameters but encode with lower bitrate */ 1.39 +static OPUS_INLINE void silk_LBRR_encode_FLP( 1.40 + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ 1.41 + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ 1.42 + const silk_float xfw[], /* I Input signal */ 1.43 + opus_int condCoding /* I The type of conditional coding used so far for this frame */ 1.44 +); 1.45 + 1.46 +void silk_encode_do_VAD_FLP( 1.47 + silk_encoder_state_FLP *psEnc /* I/O Encoder state FLP */ 1.48 +) 1.49 +{ 1.50 + /****************************/ 1.51 + /* Voice Activity Detection */ 1.52 + /****************************/ 1.53 + silk_VAD_GetSA_Q8( &psEnc->sCmn, psEnc->sCmn.inputBuf + 1 ); 1.54 + 1.55 + /**************************************************/ 1.56 + /* Convert speech activity into VAD and DTX flags */ 1.57 + /**************************************************/ 1.58 + if( psEnc->sCmn.speech_activity_Q8 < SILK_FIX_CONST( SPEECH_ACTIVITY_DTX_THRES, 8 ) ) { 1.59 + psEnc->sCmn.indices.signalType = TYPE_NO_VOICE_ACTIVITY; 1.60 + psEnc->sCmn.noSpeechCounter++; 1.61 + if( psEnc->sCmn.noSpeechCounter < NB_SPEECH_FRAMES_BEFORE_DTX ) { 1.62 + psEnc->sCmn.inDTX = 0; 1.63 + } else if( psEnc->sCmn.noSpeechCounter > MAX_CONSECUTIVE_DTX + NB_SPEECH_FRAMES_BEFORE_DTX ) { 1.64 + psEnc->sCmn.noSpeechCounter = NB_SPEECH_FRAMES_BEFORE_DTX; 1.65 + psEnc->sCmn.inDTX = 0; 1.66 + } 1.67 + psEnc->sCmn.VAD_flags[ psEnc->sCmn.nFramesEncoded ] = 0; 1.68 + } else { 1.69 + psEnc->sCmn.noSpeechCounter = 0; 1.70 + psEnc->sCmn.inDTX = 0; 1.71 + psEnc->sCmn.indices.signalType = TYPE_UNVOICED; 1.72 + psEnc->sCmn.VAD_flags[ psEnc->sCmn.nFramesEncoded ] = 1; 1.73 + } 1.74 +} 1.75 + 1.76 +/****************/ 1.77 +/* Encode frame */ 1.78 +/****************/ 1.79 +opus_int silk_encode_frame_FLP( 1.80 + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ 1.81 + opus_int32 *pnBytesOut, /* O Number of payload bytes; */ 1.82 + ec_enc *psRangeEnc, /* I/O compressor data structure */ 1.83 + opus_int condCoding, /* I The type of conditional coding to use */ 1.84 + opus_int maxBits, /* I If > 0: maximum number of output bits */ 1.85 + opus_int useCBR /* I Flag to force constant-bitrate operation */ 1.86 +) 1.87 +{ 1.88 + silk_encoder_control_FLP sEncCtrl; 1.89 + opus_int i, iter, maxIter, found_upper, found_lower, ret = 0; 1.90 + silk_float *x_frame, *res_pitch_frame; 1.91 + silk_float xfw[ MAX_FRAME_LENGTH ]; 1.92 + silk_float res_pitch[ 2 * MAX_FRAME_LENGTH + LA_PITCH_MAX ]; 1.93 + ec_enc sRangeEnc_copy, sRangeEnc_copy2; 1.94 + silk_nsq_state sNSQ_copy, sNSQ_copy2; 1.95 + opus_int32 seed_copy, nBits, nBits_lower, nBits_upper, gainMult_lower, gainMult_upper; 1.96 + opus_int32 gainsID, gainsID_lower, gainsID_upper; 1.97 + opus_int16 gainMult_Q8; 1.98 + opus_int16 ec_prevLagIndex_copy; 1.99 + opus_int ec_prevSignalType_copy; 1.100 + opus_int8 LastGainIndex_copy2; 1.101 + opus_int32 pGains_Q16[ MAX_NB_SUBFR ]; 1.102 + opus_uint8 ec_buf_copy[ 1275 ]; 1.103 + 1.104 + /* This is totally unnecessary but many compilers (including gcc) are too dumb to realise it */ 1.105 + LastGainIndex_copy2 = nBits_lower = nBits_upper = gainMult_lower = gainMult_upper = 0; 1.106 + 1.107 + psEnc->sCmn.indices.Seed = psEnc->sCmn.frameCounter++ & 3; 1.108 + 1.109 + /**************************************************************/ 1.110 + /* Set up Input Pointers, and insert frame in input buffer */ 1.111 + /**************************************************************/ 1.112 + /* pointers aligned with start of frame to encode */ 1.113 + x_frame = psEnc->x_buf + psEnc->sCmn.ltp_mem_length; /* start of frame to encode */ 1.114 + res_pitch_frame = res_pitch + psEnc->sCmn.ltp_mem_length; /* start of pitch LPC residual frame */ 1.115 + 1.116 + /***************************************/ 1.117 + /* Ensure smooth bandwidth transitions */ 1.118 + /***************************************/ 1.119 + silk_LP_variable_cutoff( &psEnc->sCmn.sLP, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.frame_length ); 1.120 + 1.121 + /*******************************************/ 1.122 + /* Copy new frame to front of input buffer */ 1.123 + /*******************************************/ 1.124 + silk_short2float_array( x_frame + LA_SHAPE_MS * psEnc->sCmn.fs_kHz, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.frame_length ); 1.125 + 1.126 + /* Add tiny signal to avoid high CPU load from denormalized floating point numbers */ 1.127 + for( i = 0; i < 8; i++ ) { 1.128 + x_frame[ LA_SHAPE_MS * psEnc->sCmn.fs_kHz + i * ( psEnc->sCmn.frame_length >> 3 ) ] += ( 1 - ( i & 2 ) ) * 1e-6f; 1.129 + } 1.130 + 1.131 + if( !psEnc->sCmn.prefillFlag ) { 1.132 + /*****************************************/ 1.133 + /* Find pitch lags, initial LPC analysis */ 1.134 + /*****************************************/ 1.135 + silk_find_pitch_lags_FLP( psEnc, &sEncCtrl, res_pitch, x_frame, psEnc->sCmn.arch ); 1.136 + 1.137 + /************************/ 1.138 + /* Noise shape analysis */ 1.139 + /************************/ 1.140 + silk_noise_shape_analysis_FLP( psEnc, &sEncCtrl, res_pitch_frame, x_frame ); 1.141 + 1.142 + /***************************************************/ 1.143 + /* Find linear prediction coefficients (LPC + LTP) */ 1.144 + /***************************************************/ 1.145 + silk_find_pred_coefs_FLP( psEnc, &sEncCtrl, res_pitch, x_frame, condCoding ); 1.146 + 1.147 + /****************************************/ 1.148 + /* Process gains */ 1.149 + /****************************************/ 1.150 + silk_process_gains_FLP( psEnc, &sEncCtrl, condCoding ); 1.151 + 1.152 + /*****************************************/ 1.153 + /* Prefiltering for noise shaper */ 1.154 + /*****************************************/ 1.155 + silk_prefilter_FLP( psEnc, &sEncCtrl, xfw, x_frame ); 1.156 + 1.157 + /****************************************/ 1.158 + /* Low Bitrate Redundant Encoding */ 1.159 + /****************************************/ 1.160 + silk_LBRR_encode_FLP( psEnc, &sEncCtrl, xfw, condCoding ); 1.161 + 1.162 + /* Loop over quantizer and entroy coding to control bitrate */ 1.163 + maxIter = 6; 1.164 + gainMult_Q8 = SILK_FIX_CONST( 1, 8 ); 1.165 + found_lower = 0; 1.166 + found_upper = 0; 1.167 + gainsID = silk_gains_ID( psEnc->sCmn.indices.GainsIndices, psEnc->sCmn.nb_subfr ); 1.168 + gainsID_lower = -1; 1.169 + gainsID_upper = -1; 1.170 + /* Copy part of the input state */ 1.171 + silk_memcpy( &sRangeEnc_copy, psRangeEnc, sizeof( ec_enc ) ); 1.172 + silk_memcpy( &sNSQ_copy, &psEnc->sCmn.sNSQ, sizeof( silk_nsq_state ) ); 1.173 + seed_copy = psEnc->sCmn.indices.Seed; 1.174 + ec_prevLagIndex_copy = psEnc->sCmn.ec_prevLagIndex; 1.175 + ec_prevSignalType_copy = psEnc->sCmn.ec_prevSignalType; 1.176 + for( iter = 0; ; iter++ ) { 1.177 + if( gainsID == gainsID_lower ) { 1.178 + nBits = nBits_lower; 1.179 + } else if( gainsID == gainsID_upper ) { 1.180 + nBits = nBits_upper; 1.181 + } else { 1.182 + /* Restore part of the input state */ 1.183 + if( iter > 0 ) { 1.184 + silk_memcpy( psRangeEnc, &sRangeEnc_copy, sizeof( ec_enc ) ); 1.185 + silk_memcpy( &psEnc->sCmn.sNSQ, &sNSQ_copy, sizeof( silk_nsq_state ) ); 1.186 + psEnc->sCmn.indices.Seed = seed_copy; 1.187 + psEnc->sCmn.ec_prevLagIndex = ec_prevLagIndex_copy; 1.188 + psEnc->sCmn.ec_prevSignalType = ec_prevSignalType_copy; 1.189 + } 1.190 + 1.191 + /*****************************************/ 1.192 + /* Noise shaping quantization */ 1.193 + /*****************************************/ 1.194 + silk_NSQ_wrapper_FLP( psEnc, &sEncCtrl, &psEnc->sCmn.indices, &psEnc->sCmn.sNSQ, psEnc->sCmn.pulses, xfw ); 1.195 + 1.196 + /****************************************/ 1.197 + /* Encode Parameters */ 1.198 + /****************************************/ 1.199 + silk_encode_indices( &psEnc->sCmn, psRangeEnc, psEnc->sCmn.nFramesEncoded, 0, condCoding ); 1.200 + 1.201 + /****************************************/ 1.202 + /* Encode Excitation Signal */ 1.203 + /****************************************/ 1.204 + silk_encode_pulses( psRangeEnc, psEnc->sCmn.indices.signalType, psEnc->sCmn.indices.quantOffsetType, 1.205 + psEnc->sCmn.pulses, psEnc->sCmn.frame_length ); 1.206 + 1.207 + nBits = ec_tell( psRangeEnc ); 1.208 + 1.209 + if( useCBR == 0 && iter == 0 && nBits <= maxBits ) { 1.210 + break; 1.211 + } 1.212 + } 1.213 + 1.214 + if( iter == maxIter ) { 1.215 + if( found_lower && ( gainsID == gainsID_lower || nBits > maxBits ) ) { 1.216 + /* Restore output state from earlier iteration that did meet the bitrate budget */ 1.217 + silk_memcpy( psRangeEnc, &sRangeEnc_copy2, sizeof( ec_enc ) ); 1.218 + silk_assert( sRangeEnc_copy2.offs <= 1275 ); 1.219 + silk_memcpy( psRangeEnc->buf, ec_buf_copy, sRangeEnc_copy2.offs ); 1.220 + silk_memcpy( &psEnc->sCmn.sNSQ, &sNSQ_copy2, sizeof( silk_nsq_state ) ); 1.221 + psEnc->sShape.LastGainIndex = LastGainIndex_copy2; 1.222 + } 1.223 + break; 1.224 + } 1.225 + 1.226 + if( nBits > maxBits ) { 1.227 + if( found_lower == 0 && iter >= 2 ) { 1.228 + /* Adjust the quantizer's rate/distortion tradeoff and discard previous "upper" results */ 1.229 + sEncCtrl.Lambda *= 1.5f; 1.230 + found_upper = 0; 1.231 + gainsID_upper = -1; 1.232 + } else { 1.233 + found_upper = 1; 1.234 + nBits_upper = nBits; 1.235 + gainMult_upper = gainMult_Q8; 1.236 + gainsID_upper = gainsID; 1.237 + } 1.238 + } else if( nBits < maxBits - 5 ) { 1.239 + found_lower = 1; 1.240 + nBits_lower = nBits; 1.241 + gainMult_lower = gainMult_Q8; 1.242 + if( gainsID != gainsID_lower ) { 1.243 + gainsID_lower = gainsID; 1.244 + /* Copy part of the output state */ 1.245 + silk_memcpy( &sRangeEnc_copy2, psRangeEnc, sizeof( ec_enc ) ); 1.246 + silk_assert( psRangeEnc->offs <= 1275 ); 1.247 + silk_memcpy( ec_buf_copy, psRangeEnc->buf, psRangeEnc->offs ); 1.248 + silk_memcpy( &sNSQ_copy2, &psEnc->sCmn.sNSQ, sizeof( silk_nsq_state ) ); 1.249 + LastGainIndex_copy2 = psEnc->sShape.LastGainIndex; 1.250 + } 1.251 + } else { 1.252 + /* Within 5 bits of budget: close enough */ 1.253 + break; 1.254 + } 1.255 + 1.256 + if( ( found_lower & found_upper ) == 0 ) { 1.257 + /* Adjust gain according to high-rate rate/distortion curve */ 1.258 + opus_int32 gain_factor_Q16; 1.259 + gain_factor_Q16 = silk_log2lin( silk_LSHIFT( nBits - maxBits, 7 ) / psEnc->sCmn.frame_length + SILK_FIX_CONST( 16, 7 ) ); 1.260 + gain_factor_Q16 = silk_min_32( gain_factor_Q16, SILK_FIX_CONST( 2, 16 ) ); 1.261 + if( nBits > maxBits ) { 1.262 + gain_factor_Q16 = silk_max_32( gain_factor_Q16, SILK_FIX_CONST( 1.3, 16 ) ); 1.263 + } 1.264 + gainMult_Q8 = silk_SMULWB( gain_factor_Q16, gainMult_Q8 ); 1.265 + } else { 1.266 + /* Adjust gain by interpolating */ 1.267 + gainMult_Q8 = gainMult_lower + ( ( gainMult_upper - gainMult_lower ) * ( maxBits - nBits_lower ) ) / ( nBits_upper - nBits_lower ); 1.268 + /* New gain multplier must be between 25% and 75% of old range (note that gainMult_upper < gainMult_lower) */ 1.269 + if( gainMult_Q8 > silk_ADD_RSHIFT32( gainMult_lower, gainMult_upper - gainMult_lower, 2 ) ) { 1.270 + gainMult_Q8 = silk_ADD_RSHIFT32( gainMult_lower, gainMult_upper - gainMult_lower, 2 ); 1.271 + } else 1.272 + if( gainMult_Q8 < silk_SUB_RSHIFT32( gainMult_upper, gainMult_upper - gainMult_lower, 2 ) ) { 1.273 + gainMult_Q8 = silk_SUB_RSHIFT32( gainMult_upper, gainMult_upper - gainMult_lower, 2 ); 1.274 + } 1.275 + } 1.276 + 1.277 + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { 1.278 + pGains_Q16[ i ] = silk_LSHIFT_SAT32( silk_SMULWB( sEncCtrl.GainsUnq_Q16[ i ], gainMult_Q8 ), 8 ); 1.279 + } 1.280 + 1.281 + /* Quantize gains */ 1.282 + psEnc->sShape.LastGainIndex = sEncCtrl.lastGainIndexPrev; 1.283 + silk_gains_quant( psEnc->sCmn.indices.GainsIndices, pGains_Q16, 1.284 + &psEnc->sShape.LastGainIndex, condCoding == CODE_CONDITIONALLY, psEnc->sCmn.nb_subfr ); 1.285 + 1.286 + /* Unique identifier of gains vector */ 1.287 + gainsID = silk_gains_ID( psEnc->sCmn.indices.GainsIndices, psEnc->sCmn.nb_subfr ); 1.288 + 1.289 + /* Overwrite unquantized gains with quantized gains and convert back to Q0 from Q16 */ 1.290 + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { 1.291 + sEncCtrl.Gains[ i ] = pGains_Q16[ i ] / 65536.0f; 1.292 + } 1.293 + } 1.294 + } 1.295 + 1.296 + /* Update input buffer */ 1.297 + silk_memmove( psEnc->x_buf, &psEnc->x_buf[ psEnc->sCmn.frame_length ], 1.298 + ( psEnc->sCmn.ltp_mem_length + LA_SHAPE_MS * psEnc->sCmn.fs_kHz ) * sizeof( silk_float ) ); 1.299 + 1.300 + /* Exit without entropy coding */ 1.301 + if( psEnc->sCmn.prefillFlag ) { 1.302 + /* No payload */ 1.303 + *pnBytesOut = 0; 1.304 + return ret; 1.305 + } 1.306 + 1.307 + /* Parameters needed for next frame */ 1.308 + psEnc->sCmn.prevLag = sEncCtrl.pitchL[ psEnc->sCmn.nb_subfr - 1 ]; 1.309 + psEnc->sCmn.prevSignalType = psEnc->sCmn.indices.signalType; 1.310 + 1.311 + /****************************************/ 1.312 + /* Finalize payload */ 1.313 + /****************************************/ 1.314 + psEnc->sCmn.first_frame_after_reset = 0; 1.315 + /* Payload size */ 1.316 + *pnBytesOut = silk_RSHIFT( ec_tell( psRangeEnc ) + 7, 3 ); 1.317 + 1.318 + return ret; 1.319 +} 1.320 + 1.321 +/* Low-Bitrate Redundancy (LBRR) encoding. Reuse all parameters but encode excitation at lower bitrate */ 1.322 +static OPUS_INLINE void silk_LBRR_encode_FLP( 1.323 + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ 1.324 + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ 1.325 + const silk_float xfw[], /* I Input signal */ 1.326 + opus_int condCoding /* I The type of conditional coding used so far for this frame */ 1.327 +) 1.328 +{ 1.329 + opus_int k; 1.330 + opus_int32 Gains_Q16[ MAX_NB_SUBFR ]; 1.331 + silk_float TempGains[ MAX_NB_SUBFR ]; 1.332 + SideInfoIndices *psIndices_LBRR = &psEnc->sCmn.indices_LBRR[ psEnc->sCmn.nFramesEncoded ]; 1.333 + silk_nsq_state sNSQ_LBRR; 1.334 + 1.335 + /*******************************************/ 1.336 + /* Control use of inband LBRR */ 1.337 + /*******************************************/ 1.338 + if( psEnc->sCmn.LBRR_enabled && psEnc->sCmn.speech_activity_Q8 > SILK_FIX_CONST( LBRR_SPEECH_ACTIVITY_THRES, 8 ) ) { 1.339 + psEnc->sCmn.LBRR_flags[ psEnc->sCmn.nFramesEncoded ] = 1; 1.340 + 1.341 + /* Copy noise shaping quantizer state and quantization indices from regular encoding */ 1.342 + silk_memcpy( &sNSQ_LBRR, &psEnc->sCmn.sNSQ, sizeof( silk_nsq_state ) ); 1.343 + silk_memcpy( psIndices_LBRR, &psEnc->sCmn.indices, sizeof( SideInfoIndices ) ); 1.344 + 1.345 + /* Save original gains */ 1.346 + silk_memcpy( TempGains, psEncCtrl->Gains, psEnc->sCmn.nb_subfr * sizeof( silk_float ) ); 1.347 + 1.348 + if( psEnc->sCmn.nFramesEncoded == 0 || psEnc->sCmn.LBRR_flags[ psEnc->sCmn.nFramesEncoded - 1 ] == 0 ) { 1.349 + /* First frame in packet or previous frame not LBRR coded */ 1.350 + psEnc->sCmn.LBRRprevLastGainIndex = psEnc->sShape.LastGainIndex; 1.351 + 1.352 + /* Increase Gains to get target LBRR rate */ 1.353 + psIndices_LBRR->GainsIndices[ 0 ] += psEnc->sCmn.LBRR_GainIncreases; 1.354 + psIndices_LBRR->GainsIndices[ 0 ] = silk_min_int( psIndices_LBRR->GainsIndices[ 0 ], N_LEVELS_QGAIN - 1 ); 1.355 + } 1.356 + 1.357 + /* Decode to get gains in sync with decoder */ 1.358 + silk_gains_dequant( Gains_Q16, psIndices_LBRR->GainsIndices, 1.359 + &psEnc->sCmn.LBRRprevLastGainIndex, condCoding == CODE_CONDITIONALLY, psEnc->sCmn.nb_subfr ); 1.360 + 1.361 + /* Overwrite unquantized gains with quantized gains and convert back to Q0 from Q16 */ 1.362 + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { 1.363 + psEncCtrl->Gains[ k ] = Gains_Q16[ k ] * ( 1.0f / 65536.0f ); 1.364 + } 1.365 + 1.366 + /*****************************************/ 1.367 + /* Noise shaping quantization */ 1.368 + /*****************************************/ 1.369 + silk_NSQ_wrapper_FLP( psEnc, psEncCtrl, psIndices_LBRR, &sNSQ_LBRR, 1.370 + psEnc->sCmn.pulses_LBRR[ psEnc->sCmn.nFramesEncoded ], xfw ); 1.371 + 1.372 + /* Restore original gains */ 1.373 + silk_memcpy( psEncCtrl->Gains, TempGains, psEnc->sCmn.nb_subfr * sizeof( silk_float ) ); 1.374 + } 1.375 +}