Mercurial > hg > audiostuff
comparison spandsp-0.0.6pre17/src/v17tx.c @ 4:26cd8f1ef0b1
import spandsp-0.0.6pre17
| author | Peter Meerwald <pmeerw@cosy.sbg.ac.at> |
|---|---|
| date | Fri, 25 Jun 2010 15:50:58 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 3:c6c5a16ce2f2 | 4:26cd8f1ef0b1 |
|---|---|
| 1 /* | |
| 2 * SpanDSP - a series of DSP components for telephony | |
| 3 * | |
| 4 * v17tx.c - ITU V.17 modem transmit part | |
| 5 * | |
| 6 * Written by Steve Underwood <steveu@coppice.org> | |
| 7 * | |
| 8 * Copyright (C) 2004 Steve Underwood | |
| 9 * | |
| 10 * All rights reserved. | |
| 11 * | |
| 12 * This program is free software; you can redistribute it and/or modify | |
| 13 * it under the terms of the GNU Lesser General Public License version 2.1, | |
| 14 * as published by the Free Software Foundation. | |
| 15 * | |
| 16 * This program is distributed in the hope that it will be useful, | |
| 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 19 * GNU Lesser General Public License for more details. | |
| 20 * | |
| 21 * You should have received a copy of the GNU Lesser General Public | |
| 22 * License along with this program; if not, write to the Free Software | |
| 23 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
| 24 * | |
| 25 * $Id: v17tx.c,v 1.75.4.1 2009/12/24 16:52:30 steveu Exp $ | |
| 26 */ | |
| 27 | |
| 28 /*! \file */ | |
| 29 | |
| 30 #if defined(HAVE_CONFIG_H) | |
| 31 #include "config.h" | |
| 32 #endif | |
| 33 | |
| 34 #include <stdio.h> | |
| 35 #include <inttypes.h> | |
| 36 #include <stdlib.h> | |
| 37 #include <string.h> | |
| 38 #if defined(HAVE_TGMATH_H) | |
| 39 #include <tgmath.h> | |
| 40 #endif | |
| 41 #if defined(HAVE_MATH_H) | |
| 42 #include <math.h> | |
| 43 #endif | |
| 44 #include "floating_fudge.h" | |
| 45 | |
| 46 #include "spandsp/telephony.h" | |
| 47 #include "spandsp/fast_convert.h" | |
| 48 #include "spandsp/logging.h" | |
| 49 #include "spandsp/complex.h" | |
| 50 #include "spandsp/vector_float.h" | |
| 51 #include "spandsp/complex_vector_float.h" | |
| 52 #include "spandsp/async.h" | |
| 53 #include "spandsp/dds.h" | |
| 54 #include "spandsp/power_meter.h" | |
| 55 | |
| 56 #include "spandsp/v17tx.h" | |
| 57 | |
| 58 #include "spandsp/private/logging.h" | |
| 59 #include "spandsp/private/v17tx.h" | |
| 60 | |
| 61 #if defined(SPANDSP_USE_FIXED_POINT) | |
| 62 #define SPANDSP_USE_FIXED_POINTx | |
| 63 #endif | |
| 64 | |
| 65 #include "v17_v32bis_tx_constellation_maps.h" | |
| 66 #if defined(SPANDSP_USE_FIXED_POINT) | |
| 67 #include "v17_v32bis_tx_fixed_rrc.h" | |
| 68 #else | |
| 69 #include "v17_v32bis_tx_floating_rrc.h" | |
| 70 #endif | |
| 71 | |
| 72 /*! The nominal frequency of the carrier, in Hertz */ | |
| 73 #define CARRIER_NOMINAL_FREQ 1800.0f | |
| 74 | |
| 75 /* Segments of the training sequence */ | |
| 76 /*! The start of the optional TEP, that may preceed the actual training, in symbols */ | |
| 77 #define V17_TRAINING_SEG_TEP_A 0 | |
| 78 /*! The mid point of the optional TEP, that may preceed the actual training, in symbols */ | |
| 79 #define V17_TRAINING_SEG_TEP_B (V17_TRAINING_SEG_TEP_A + 480) | |
| 80 /*! The start of training segment 1, in symbols */ | |
| 81 #define V17_TRAINING_SEG_1 (V17_TRAINING_SEG_TEP_B + 48) | |
| 82 /*! The start of training segment 2, in symbols */ | |
| 83 #define V17_TRAINING_SEG_2 (V17_TRAINING_SEG_1 + 256) | |
| 84 /*! The start of training segment 3, in symbols */ | |
| 85 #define V17_TRAINING_SEG_3 (V17_TRAINING_SEG_2 + 2976) | |
| 86 /*! The start of training segment 4, in symbols */ | |
| 87 #define V17_TRAINING_SEG_4 (V17_TRAINING_SEG_3 + 64) | |
| 88 /*! The start of training segment 4 in short training mode, in symbols */ | |
| 89 #define V17_TRAINING_SHORT_SEG_4 (V17_TRAINING_SEG_2 + 38) | |
| 90 /*! The end of the training, in symbols */ | |
| 91 #define V17_TRAINING_END (V17_TRAINING_SEG_4 + 48) | |
| 92 #define V17_TRAINING_SHUTDOWN_A (V17_TRAINING_END + 32) | |
| 93 /*! The end of the shutdown sequence, in symbols */ | |
| 94 #define V17_TRAINING_SHUTDOWN_END (V17_TRAINING_SHUTDOWN_A + 48) | |
| 95 | |
| 96 /*! The 16 bit pattern used in the bridge section of the training sequence */ | |
| 97 #define V17_BRIDGE_WORD 0x8880 | |
| 98 | |
| 99 static __inline__ int scramble(v17_tx_state_t *s, int in_bit) | |
| 100 { | |
| 101 int out_bit; | |
| 102 | |
| 103 //out_bit = (in_bit ^ (s->scramble_reg >> s->scrambler_tap) ^ (s->scramble_reg >> (23 - 1))) & 1; | |
| 104 out_bit = (in_bit ^ (s->scramble_reg >> (18 - 1)) ^ (s->scramble_reg >> (23 - 1))) & 1; | |
| 105 s->scramble_reg = (s->scramble_reg << 1) | out_bit; | |
| 106 return out_bit; | |
| 107 } | |
| 108 /*- End of function --------------------------------------------------------*/ | |
| 109 | |
| 110 #if defined(SPANDSP_USE_FIXED_POINT) | |
| 111 static __inline__ complexi16_t training_get(v17_tx_state_t *s) | |
| 112 #else | |
| 113 static __inline__ complexf_t training_get(v17_tx_state_t *s) | |
| 114 #endif | |
| 115 { | |
| 116 static const int cdba_to_abcd[4] = | |
| 117 { | |
| 118 2, 3, 1, 0 | |
| 119 }; | |
| 120 static const int dibit_to_step[4] = | |
| 121 { | |
| 122 1, 0, 2, 3 | |
| 123 }; | |
| 124 #if defined(SPANDSP_USE_FIXED_POINT) | |
| 125 static const complexi16_t zero = {0, 0}; | |
| 126 #else | |
| 127 static const complexf_t zero = {0.0f, 0.0f}; | |
| 128 #endif | |
| 129 int bits; | |
| 130 int shift; | |
| 131 | |
| 132 if (++s->training_step <= V17_TRAINING_SEG_3) | |
| 133 { | |
| 134 if (s->training_step <= V17_TRAINING_SEG_2) | |
| 135 { | |
| 136 if (s->training_step <= V17_TRAINING_SEG_TEP_B) | |
| 137 { | |
| 138 /* Optional segment: Unmodulated carrier (talker echo protection) */ | |
| 139 return v17_v32bis_abcd_constellation[0]; | |
| 140 } | |
| 141 if (s->training_step <= V17_TRAINING_SEG_1) | |
| 142 { | |
| 143 /* Optional segment: silence (talker echo protection) */ | |
| 144 return zero; | |
| 145 } | |
| 146 /* Segment 1: ABAB... */ | |
| 147 return v17_v32bis_abcd_constellation[(s->training_step & 1) ^ 1]; | |
| 148 } | |
| 149 /* Segment 2: CDBA... */ | |
| 150 /* Apply the scrambler */ | |
| 151 bits = scramble(s, 1); | |
| 152 bits = (bits << 1) | scramble(s, 1); | |
| 153 s->constellation_state = cdba_to_abcd[bits]; | |
| 154 if (s->short_train && s->training_step == V17_TRAINING_SHORT_SEG_4) | |
| 155 { | |
| 156 /* Go straight to the ones test. */ | |
| 157 s->training_step = V17_TRAINING_SEG_4; | |
| 158 } | |
| 159 return v17_v32bis_abcd_constellation[s->constellation_state]; | |
| 160 } | |
| 161 /* Segment 3: Bridge... */ | |
| 162 shift = ((s->training_step - V17_TRAINING_SEG_3 - 1) & 0x7) << 1; | |
| 163 //span_log(&s->logging, SPAN_LOG_FLOW, "Seg 3 shift %d\n", shift); | |
| 164 bits = scramble(s, V17_BRIDGE_WORD >> shift); | |
| 165 bits = (bits << 1) | scramble(s, V17_BRIDGE_WORD >> (shift + 1)); | |
| 166 s->constellation_state = (s->constellation_state + dibit_to_step[bits]) & 3; | |
| 167 return v17_v32bis_abcd_constellation[s->constellation_state]; | |
| 168 } | |
| 169 /*- End of function --------------------------------------------------------*/ | |
| 170 | |
| 171 static __inline__ int diff_and_convolutional_encode(v17_tx_state_t *s, int q) | |
| 172 { | |
| 173 static const uint8_t v32bis_4800_differential_encoder[4][4] = | |
| 174 { | |
| 175 {2, 3, 0, 1}, | |
| 176 {0, 2, 1, 3}, | |
| 177 {3, 1, 2, 0}, | |
| 178 {1, 0, 3, 2} | |
| 179 }; | |
| 180 static const uint8_t v17_differential_encoder[4][4] = | |
| 181 { | |
| 182 {0, 1, 2, 3}, | |
| 183 {1, 2, 3, 0}, | |
| 184 {2, 3, 0, 1}, | |
| 185 {3, 0, 1, 2} | |
| 186 }; | |
| 187 static const uint8_t v17_convolutional_coder[8][4] = | |
| 188 { | |
| 189 {0, 2, 3, 1}, | |
| 190 {4, 7, 5, 6}, | |
| 191 {1, 3, 2, 0}, | |
| 192 {7, 4, 6, 5}, | |
| 193 {2, 0, 1, 3}, | |
| 194 {6, 5, 7, 4}, | |
| 195 {3, 1, 0, 2}, | |
| 196 {5, 6, 4, 7} | |
| 197 }; | |
| 198 | |
| 199 if (s->bits_per_symbol == 2) | |
| 200 { | |
| 201 /* 4800bps mode for V.32bis */ | |
| 202 /* There is no trellis. We just differentially encode. */ | |
| 203 s->diff = v32bis_4800_differential_encoder[s->diff][q & 0x03]; | |
| 204 return s->diff; | |
| 205 } | |
| 206 /* Differentially encode */ | |
| 207 s->diff = v17_differential_encoder[s->diff][q & 0x03]; | |
| 208 | |
| 209 /* Convolutionally encode the redundant bit */ | |
| 210 s->convolution = v17_convolutional_coder[s->convolution][s->diff]; | |
| 211 | |
| 212 /* The final result is the combination of some uncoded bits, 2 differentially | |
| 213 encoded bits, and the convolutionally encoded redundant bit. */ | |
| 214 return ((q << 1) & 0x78) | (s->diff << 1) | ((s->convolution >> 2) & 1); | |
| 215 } | |
| 216 /*- End of function --------------------------------------------------------*/ | |
| 217 | |
| 218 static int fake_get_bit(void *user_data) | |
| 219 { | |
| 220 return 1; | |
| 221 } | |
| 222 /*- End of function --------------------------------------------------------*/ | |
| 223 | |
| 224 #if defined(SPANDSP_USE_FIXED_POINT) | |
| 225 static __inline__ complexi16_t getbaud(v17_tx_state_t *s) | |
| 226 #else | |
| 227 static __inline__ complexf_t getbaud(v17_tx_state_t *s) | |
| 228 #endif | |
| 229 { | |
| 230 int i; | |
| 231 int bit; | |
| 232 int bits; | |
| 233 | |
| 234 if (s->in_training) | |
| 235 { | |
| 236 if (s->training_step <= V17_TRAINING_END) | |
| 237 { | |
| 238 /* Send the training sequence */ | |
| 239 if (s->training_step < V17_TRAINING_SEG_4) | |
| 240 return training_get(s); | |
| 241 /* The last step in training is to send some 1's */ | |
| 242 if (++s->training_step > V17_TRAINING_END) | |
| 243 { | |
| 244 /* Training finished - commence normal operation. */ | |
| 245 s->current_get_bit = s->get_bit; | |
| 246 s->in_training = FALSE; | |
| 247 } | |
| 248 } | |
| 249 else | |
| 250 { | |
| 251 if (++s->training_step > V17_TRAINING_SHUTDOWN_A) | |
| 252 { | |
| 253 /* The shutdown sequence is 32 bauds of all 1's, then 48 bauds | |
| 254 of silence */ | |
| 255 #if defined(SPANDSP_USE_FIXED_POINT) | |
| 256 return complex_seti16(0, 0); | |
| 257 #else | |
| 258 return complex_setf(0.0f, 0.0f); | |
| 259 #endif | |
| 260 } | |
| 261 if (s->training_step == V17_TRAINING_SHUTDOWN_END) | |
| 262 { | |
| 263 if (s->status_handler) | |
| 264 s->status_handler(s->status_user_data, SIG_STATUS_SHUTDOWN_COMPLETE); | |
| 265 } | |
| 266 } | |
| 267 } | |
| 268 bits = 0; | |
| 269 for (i = 0; i < s->bits_per_symbol; i++) | |
| 270 { | |
| 271 if ((bit = s->current_get_bit(s->get_bit_user_data)) == SIG_STATUS_END_OF_DATA) | |
| 272 { | |
| 273 /* End of real data. Switch to the fake get_bit routine, until we | |
| 274 have shut down completely. */ | |
| 275 if (s->status_handler) | |
| 276 s->status_handler(s->status_user_data, SIG_STATUS_END_OF_DATA); | |
| 277 s->current_get_bit = fake_get_bit; | |
| 278 s->in_training = TRUE; | |
| 279 bit = 1; | |
| 280 } | |
| 281 bits |= (scramble(s, bit) << i); | |
| 282 } | |
| 283 return s->constellation[diff_and_convolutional_encode(s, bits)]; | |
| 284 } | |
| 285 /*- End of function --------------------------------------------------------*/ | |
| 286 | |
| 287 SPAN_DECLARE_NONSTD(int) v17_tx(v17_tx_state_t *s, int16_t amp[], int len) | |
| 288 { | |
| 289 #if defined(SPANDSP_USE_FIXED_POINT) | |
| 290 complexi_t x; | |
| 291 complexi_t z; | |
| 292 #else | |
| 293 complexf_t x; | |
| 294 complexf_t z; | |
| 295 #endif | |
| 296 int i; | |
| 297 int sample; | |
| 298 | |
| 299 if (s->training_step >= V17_TRAINING_SHUTDOWN_END) | |
| 300 { | |
| 301 /* Once we have sent the shutdown sequence, we stop sending completely. */ | |
| 302 return 0; | |
| 303 } | |
| 304 for (sample = 0; sample < len; sample++) | |
| 305 { | |
| 306 if ((s->baud_phase += 3) >= 10) | |
| 307 { | |
| 308 s->baud_phase -= 10; | |
| 309 s->rrc_filter[s->rrc_filter_step] = | |
| 310 s->rrc_filter[s->rrc_filter_step + V17_TX_FILTER_STEPS] = getbaud(s); | |
| 311 if (++s->rrc_filter_step >= V17_TX_FILTER_STEPS) | |
| 312 s->rrc_filter_step = 0; | |
| 313 } | |
| 314 /* Root raised cosine pulse shaping at baseband */ | |
| 315 #if defined(SPANDSP_USE_FIXED_POINT) | |
| 316 x = complex_seti(0, 0); | |
| 317 for (i = 0; i < V17_TX_FILTER_STEPS; i++) | |
| 318 { | |
| 319 x.re += (int32_t) tx_pulseshaper[TX_PULSESHAPER_COEFF_SETS - 1 - s->baud_phase][i]*(int32_t) s->rrc_filter[i + s->rrc_filter_step].re; | |
| 320 x.im += (int32_t) tx_pulseshaper[TX_PULSESHAPER_COEFF_SETS - 1 - s->baud_phase][i]*(int32_t) s->rrc_filter[i + s->rrc_filter_step].im; | |
| 321 } | |
| 322 /* Now create and modulate the carrier */ | |
| 323 x.re >>= 4; | |
| 324 x.im >>= 4; | |
| 325 z = dds_complexi(&(s->carrier_phase), s->carrier_phase_rate); | |
| 326 /* Don't bother saturating. We should never clip. */ | |
| 327 i = (x.re*z.re - x.im*z.im) >> 15; | |
| 328 amp[sample] = (int16_t) ((i*s->gain) >> 15); | |
| 329 #else | |
| 330 x = complex_setf(0.0f, 0.0f); | |
| 331 for (i = 0; i < V17_TX_FILTER_STEPS; i++) | |
| 332 { | |
| 333 x.re += tx_pulseshaper[TX_PULSESHAPER_COEFF_SETS - 1 - s->baud_phase][i]*s->rrc_filter[i + s->rrc_filter_step].re; | |
| 334 x.im += tx_pulseshaper[TX_PULSESHAPER_COEFF_SETS - 1 - s->baud_phase][i]*s->rrc_filter[i + s->rrc_filter_step].im; | |
| 335 } | |
| 336 /* Now create and modulate the carrier */ | |
| 337 z = dds_complexf(&(s->carrier_phase), s->carrier_phase_rate); | |
| 338 /* Don't bother saturating. We should never clip. */ | |
| 339 amp[sample] = (int16_t) lfastrintf((x.re*z.re - x.im*z.im)*s->gain); | |
| 340 #endif | |
| 341 } | |
| 342 return sample; | |
| 343 } | |
| 344 /*- End of function --------------------------------------------------------*/ | |
| 345 | |
| 346 SPAN_DECLARE(void) v17_tx_power(v17_tx_state_t *s, float power) | |
| 347 { | |
| 348 /* The constellation design seems to keep the average power the same, regardless | |
| 349 of which bit rate is in use. */ | |
| 350 #if defined(SPANDSP_USE_FIXED_POINT) | |
| 351 s->gain = 0.223f*powf(10.0f, (power - DBM0_MAX_POWER)/20.0f)*16.0f*(32767.0f/30672.52f)*32768.0f/TX_PULSESHAPER_GAIN; | |
| 352 #else | |
| 353 s->gain = 0.223f*powf(10.0f, (power - DBM0_MAX_POWER)/20.0f)*32768.0f/TX_PULSESHAPER_GAIN; | |
| 354 #endif | |
| 355 } | |
| 356 /*- End of function --------------------------------------------------------*/ | |
| 357 | |
| 358 SPAN_DECLARE(void) v17_tx_set_get_bit(v17_tx_state_t *s, get_bit_func_t get_bit, void *user_data) | |
| 359 { | |
| 360 if (s->get_bit == s->current_get_bit) | |
| 361 s->current_get_bit = get_bit; | |
| 362 s->get_bit = get_bit; | |
| 363 s->get_bit_user_data = user_data; | |
| 364 } | |
| 365 /*- End of function --------------------------------------------------------*/ | |
| 366 | |
| 367 SPAN_DECLARE(void) v17_tx_set_modem_status_handler(v17_tx_state_t *s, modem_tx_status_func_t handler, void *user_data) | |
| 368 { | |
| 369 s->status_handler = handler; | |
| 370 s->status_user_data = user_data; | |
| 371 } | |
| 372 /*- End of function --------------------------------------------------------*/ | |
| 373 | |
| 374 SPAN_DECLARE(logging_state_t *) v17_tx_get_logging_state(v17_tx_state_t *s) | |
| 375 { | |
| 376 return &s->logging; | |
| 377 } | |
| 378 /*- End of function --------------------------------------------------------*/ | |
| 379 | |
| 380 SPAN_DECLARE(int) v17_tx_restart(v17_tx_state_t *s, int bit_rate, int tep, int short_train) | |
| 381 { | |
| 382 switch (bit_rate) | |
| 383 { | |
| 384 case 14400: | |
| 385 s->bits_per_symbol = 6; | |
| 386 s->constellation = v17_v32bis_14400_constellation; | |
| 387 break; | |
| 388 case 12000: | |
| 389 s->bits_per_symbol = 5; | |
| 390 s->constellation = v17_v32bis_12000_constellation; | |
| 391 break; | |
| 392 case 9600: | |
| 393 s->bits_per_symbol = 4; | |
| 394 s->constellation = v17_v32bis_9600_constellation; | |
| 395 break; | |
| 396 case 7200: | |
| 397 s->bits_per_symbol = 3; | |
| 398 s->constellation = v17_v32bis_7200_constellation; | |
| 399 break; | |
| 400 case 4800: | |
| 401 /* This does not exist in the V.17 spec as a valid mode of operation. | |
| 402 However, it does exist in V.32bis, so it is here for completeness. */ | |
| 403 s->bits_per_symbol = 2; | |
| 404 s->constellation = v17_v32bis_4800_constellation; | |
| 405 break; | |
| 406 default: | |
| 407 return -1; | |
| 408 } | |
| 409 s->bit_rate = bit_rate; | |
| 410 /* NB: some modems seem to use 3 instead of 1 for long training */ | |
| 411 s->diff = (short_train) ? 0 : 1; | |
| 412 #if defined(SPANDSP_USE_FIXED_POINT) | |
| 413 memset(s->rrc_filter, 0, sizeof(s->rrc_filter)); | |
| 414 #else | |
| 415 cvec_zerof(s->rrc_filter, sizeof(s->rrc_filter)/sizeof(s->rrc_filter[0])); | |
| 416 #endif | |
| 417 s->rrc_filter_step = 0; | |
| 418 s->convolution = 0; | |
| 419 s->scramble_reg = 0x2ECDD5; | |
| 420 s->in_training = TRUE; | |
| 421 s->short_train = short_train; | |
| 422 s->training_step = (tep) ? V17_TRAINING_SEG_TEP_A : V17_TRAINING_SEG_1; | |
| 423 s->carrier_phase = 0; | |
| 424 s->baud_phase = 0; | |
| 425 s->constellation_state = 0; | |
| 426 s->current_get_bit = fake_get_bit; | |
| 427 return 0; | |
| 428 } | |
| 429 /*- End of function --------------------------------------------------------*/ | |
| 430 | |
| 431 SPAN_DECLARE(v17_tx_state_t *) v17_tx_init(v17_tx_state_t *s, int bit_rate, int tep, get_bit_func_t get_bit, void *user_data) | |
| 432 { | |
| 433 switch (bit_rate) | |
| 434 { | |
| 435 case 14400: | |
| 436 case 12000: | |
| 437 case 9600: | |
| 438 case 7200: | |
| 439 case 4800: | |
| 440 /* 4800 is an extension of V.17, to provide full converage of the V.32bis modes */ | |
| 441 break; | |
| 442 default: | |
| 443 return NULL; | |
| 444 } | |
| 445 if (s == NULL) | |
| 446 { | |
| 447 if ((s = (v17_tx_state_t *) malloc(sizeof(*s))) == NULL) | |
| 448 return NULL; | |
| 449 } | |
| 450 memset(s, 0, sizeof(*s)); | |
| 451 span_log_init(&s->logging, SPAN_LOG_NONE, NULL); | |
| 452 span_log_set_protocol(&s->logging, "V.17 TX"); | |
| 453 s->get_bit = get_bit; | |
| 454 s->get_bit_user_data = user_data; | |
| 455 //s->scrambler_tap = 18 - 1; | |
| 456 s->carrier_phase_rate = dds_phase_ratef(CARRIER_NOMINAL_FREQ); | |
| 457 v17_tx_power(s, -14.0f); | |
| 458 v17_tx_restart(s, bit_rate, tep, FALSE); | |
| 459 return s; | |
| 460 } | |
| 461 /*- End of function --------------------------------------------------------*/ | |
| 462 | |
| 463 SPAN_DECLARE(int) v17_tx_release(v17_tx_state_t *s) | |
| 464 { | |
| 465 return 0; | |
| 466 } | |
| 467 /*- End of function --------------------------------------------------------*/ | |
| 468 | |
| 469 SPAN_DECLARE(int) v17_tx_free(v17_tx_state_t *s) | |
| 470 { | |
| 471 free(s); | |
| 472 return 0; | |
| 473 } | |
| 474 /*- End of function --------------------------------------------------------*/ | |
| 475 /*- End of file ------------------------------------------------------------*/ |
