v27ter_tests.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. /*
  2. * SpanDSP - a series of DSP components for telephony
  3. *
  4. * v27ter_tests.c
  5. *
  6. * Written by Steve Underwood <steveu@coppice.org>
  7. *
  8. * Copyright (C) 2003 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 General Public License version 2, as
  14. * 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 General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU General Public License
  22. * along with this program; if not, write to the Free Software
  23. * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  24. */
  25. /*! \page v27ter_tests_page V.27ter modem tests
  26. \section v27ter_tests_page_sec_1 What does it do?
  27. These tests test one way paths, as V.27ter is a half-duplex modem. They allow either:
  28. - A V.27ter transmit modem to feed a V.27ter receive modem through a telephone line
  29. model. BER testing is then used to evaluate performance under various line
  30. conditions. This is effective for testing the basic performance of the
  31. receive modem. It is also the only test mode provided for evaluating the
  32. transmit modem.
  33. - A V.27ter receive modem is used to decode V.27ter audio, stored in an audio file.
  34. This is good way to evaluate performance with audio recorded from other
  35. models of modem, and with real world problematic telephone lines.
  36. If the appropriate GUI environment exists, the tests are built such that a visual
  37. display of modem status is maintained.
  38. \section v27ter_tests_page_sec_2 How is it used?
  39. */
  40. #if defined(HAVE_CONFIG_H)
  41. #include "config.h"
  42. #endif
  43. #if defined(HAVE_FL_FL_H) && defined(HAVE_FL_FL_CARTESIAN_H) && defined(HAVE_FL_FL_AUDIO_METER_H)
  44. #define ENABLE_GUI
  45. #endif
  46. #include <stdlib.h>
  47. #include <stdio.h>
  48. #include <fcntl.h>
  49. #include <unistd.h>
  50. #include <string.h>
  51. #include <sndfile.h>
  52. #include <signal.h>
  53. #if defined(HAVE_FENV_H)
  54. #define __USE_GNU
  55. #include <fenv.h>
  56. #endif
  57. #define SPANDSP_EXPOSE_INTERNAL_STRUCTURES
  58. #include "spandsp.h"
  59. #include "spandsp-sim.h"
  60. #if defined(ENABLE_GUI)
  61. #include "modem_monitor.h"
  62. #include "line_model_monitor.h"
  63. #endif
  64. #define BLOCK_LEN 160
  65. #define OUT_FILE_NAME "v27ter.wav"
  66. char *decode_test_file = NULL;
  67. bool use_gui = false;
  68. int symbol_no = 0;
  69. int rx_bits = 0;
  70. bert_state_t bert;
  71. one_way_line_model_state_t *line_model;
  72. #if defined(ENABLE_GUI)
  73. qam_monitor_t *qam_monitor;
  74. #endif
  75. bert_results_t latest_results;
  76. static void reporter(void *user_data, int reason, bert_results_t *results)
  77. {
  78. switch (reason)
  79. {
  80. case BERT_REPORT_REGULAR:
  81. fprintf(stderr, "BERT report regular - %d bits, %d bad bits, %d resyncs\n", results->total_bits, results->bad_bits, results->resyncs);
  82. memcpy(&latest_results, results, sizeof(latest_results));
  83. break;
  84. default:
  85. fprintf(stderr, "BERT report %s\n", bert_event_to_str(reason));
  86. break;
  87. }
  88. }
  89. /*- End of function --------------------------------------------------------*/
  90. static void v27ter_rx_status(void *user_data, int status)
  91. {
  92. v27ter_rx_state_t *s;
  93. int i;
  94. int len;
  95. #if defined(SPANDSP_USE_FIXED_POINT)
  96. complexi16_t *coeffs;
  97. #else
  98. complexf_t *coeffs;
  99. #endif
  100. printf("V.27ter rx status is %s (%d)\n", signal_status_to_str(status), status);
  101. s = (v27ter_rx_state_t *) user_data;
  102. switch (status)
  103. {
  104. case SIG_STATUS_TRAINING_SUCCEEDED:
  105. if ((len = v27ter_rx_equalizer_state(s, &coeffs)))
  106. {
  107. printf("Equalizer:\n");
  108. for (i = 0; i < len; i++)
  109. #if defined(SPANDSP_USE_FIXED_POINT)
  110. printf("%3d (%15.5f, %15.5f)\n", i, coeffs[i].re/V27TER_CONSTELLATION_SCALING_FACTOR, coeffs[i].im/V27TER_CONSTELLATION_SCALING_FACTOR);
  111. #else
  112. printf("%3d (%15.5f, %15.5f) -> %15.5f\n", i, coeffs[i].re, coeffs[i].im, powerf(&coeffs[i]));
  113. #endif
  114. }
  115. break;
  116. }
  117. }
  118. /*- End of function --------------------------------------------------------*/
  119. static void v27terputbit(void *user_data, int bit)
  120. {
  121. if (bit < 0)
  122. {
  123. v27ter_rx_status(user_data, bit);
  124. return;
  125. }
  126. if (decode_test_file)
  127. printf("Rx bit %d - %d\n", rx_bits++, bit);
  128. else
  129. bert_put_bit(&bert, bit);
  130. }
  131. /*- End of function --------------------------------------------------------*/
  132. static void v27ter_tx_status(void *user_data, int status)
  133. {
  134. printf("V.27ter tx status is %s (%d)\n", signal_status_to_str(status), status);
  135. }
  136. /*- End of function --------------------------------------------------------*/
  137. static int v27tergetbit(void *user_data)
  138. {
  139. return bert_get_bit(&bert);
  140. }
  141. /*- End of function --------------------------------------------------------*/
  142. #if defined(SPANDSP_USE_FIXED_POINT)
  143. static void qam_report(void *user_data, const complexi16_t *constel, const complexi16_t *target, int symbol)
  144. #else
  145. static void qam_report(void *user_data, const complexf_t *constel, const complexf_t *target, int symbol)
  146. #endif
  147. {
  148. int i;
  149. int len;
  150. #if defined(SPANDSP_USE_FIXED_POINT)
  151. complexi16_t *coeffs;
  152. #else
  153. complexf_t *coeffs;
  154. #endif
  155. complexf_t constel_point;
  156. complexf_t target_point;
  157. float fpower;
  158. float error;
  159. v27ter_rx_state_t *rx;
  160. static float smooth_power = 0.0f;
  161. static int update_interval = 100;
  162. rx = (v27ter_rx_state_t *) user_data;
  163. if (constel)
  164. {
  165. constel_point.re = constel->re/V27TER_CONSTELLATION_SCALING_FACTOR;
  166. constel_point.im = constel->im/V27TER_CONSTELLATION_SCALING_FACTOR;
  167. target_point.re = target->re/V27TER_CONSTELLATION_SCALING_FACTOR;
  168. target_point.im = target->im/V27TER_CONSTELLATION_SCALING_FACTOR;
  169. fpower = (constel_point.re - target_point.re)*(constel_point.re - target_point.re)
  170. + (constel_point.im - target_point.im)*(constel_point.im - target_point.im);
  171. smooth_power = 0.95f*smooth_power + 0.05f*fpower;
  172. #if defined(ENABLE_GUI)
  173. if (use_gui)
  174. {
  175. qam_monitor_update_constel(qam_monitor, &constel_point);
  176. qam_monitor_update_carrier_tracking(qam_monitor, v27ter_rx_carrier_frequency(rx));
  177. qam_monitor_update_symbol_tracking(qam_monitor, v27ter_rx_symbol_timing_correction(rx));
  178. }
  179. #endif
  180. error = constel->im*target->re - constel->re*target->im;
  181. printf("Tracking error %f %f %f %f %f %f\n", error, v27ter_rx_carrier_frequency(rx), constel_point.re, constel_point.im, target_point.re, target_point.im);
  182. printf("%8d [%8.4f, %8.4f] [%8.4f, %8.4f] %2x %8.4f %8.4f %9.4f %7.3f %7.4f\n",
  183. symbol_no,
  184. constel_point.re,
  185. constel_point.im,
  186. target_point.re,
  187. target_point.im,
  188. symbol,
  189. fpower,
  190. smooth_power,
  191. v27ter_rx_carrier_frequency(rx),
  192. v27ter_rx_signal_power(rx),
  193. v27ter_rx_symbol_timing_correction(rx));
  194. symbol_no++;
  195. if (--update_interval <= 0)
  196. {
  197. if ((len = v27ter_rx_equalizer_state(rx, &coeffs)))
  198. {
  199. printf("Equalizer B:\n");
  200. for (i = 0; i < len; i++)
  201. #if defined(SPANDSP_USE_FIXED_POINT)
  202. printf("%3d (%15.5f, %15.5f)\n", i, coeffs[i].re/V27TER_CONSTELLATION_SCALING_FACTOR, coeffs[i].im/V27TER_CONSTELLATION_SCALING_FACTOR);
  203. #else
  204. printf("%3d (%15.5f, %15.5f) -> %15.5f\n", i, coeffs[i].re, coeffs[i].im, powerf(&coeffs[i]));
  205. #endif
  206. #if defined(ENABLE_GUI)
  207. if (use_gui)
  208. {
  209. #if defined(SPANDSP_USE_FIXED_POINT)
  210. qam_monitor_update_int_equalizer(qam_monitor, coeffs, len);
  211. #else
  212. qam_monitor_update_equalizer(qam_monitor, coeffs, len);
  213. #endif
  214. }
  215. #endif
  216. }
  217. update_interval = 100;
  218. }
  219. }
  220. else
  221. {
  222. printf("Gardner step %d\n", symbol);
  223. if ((len = v27ter_rx_equalizer_state(rx, &coeffs)))
  224. {
  225. printf("Equalizer A:\n");
  226. for (i = 0; i < len; i++)
  227. #if defined(SPANDSP_USE_FIXED_POINT)
  228. printf("%3d (%15.5f, %15.5f)\n", i, coeffs[i].re/V27TER_CONSTELLATION_SCALING_FACTOR, coeffs[i].im/V27TER_CONSTELLATION_SCALING_FACTOR);
  229. #else
  230. printf("%3d (%15.5f, %15.5f) -> %15.5f\n", i, coeffs[i].re, coeffs[i].im, powerf(&coeffs[i]));
  231. #endif
  232. #if defined(ENABLE_GUI)
  233. if (use_gui)
  234. {
  235. #if defined(SPANDSP_USE_FIXED_POINT)
  236. qam_monitor_update_int_equalizer(qam_monitor, coeffs, len);
  237. #else
  238. qam_monitor_update_equalizer(qam_monitor, coeffs, len);
  239. #endif
  240. }
  241. #endif
  242. }
  243. }
  244. }
  245. /*- End of function --------------------------------------------------------*/
  246. #if defined(HAVE_FENV_H)
  247. static void sigfpe_handler(int sig_num, siginfo_t *info, void *data)
  248. {
  249. switch (sig_num)
  250. {
  251. case SIGFPE:
  252. switch (info->si_code)
  253. {
  254. case FPE_INTDIV:
  255. fprintf(stderr, "integer divide by zero at %p\n", info->si_addr);
  256. break;
  257. case FPE_INTOVF:
  258. fprintf(stderr, "integer overflow at %p\n", info->si_addr);
  259. break;
  260. case FPE_FLTDIV:
  261. fprintf(stderr, "FP divide by zero at %p\n", info->si_addr);
  262. break;
  263. case FPE_FLTOVF:
  264. fprintf(stderr, "FP overflow at %p\n", info->si_addr);
  265. break;
  266. case FPE_FLTUND:
  267. fprintf(stderr, "FP underflow at %p\n", info->si_addr);
  268. break;
  269. case FPE_FLTRES:
  270. fprintf(stderr, "FP inexact result at %p\n", info->si_addr);
  271. break;
  272. case FPE_FLTINV:
  273. fprintf(stderr, "FP invalid operation at %p\n", info->si_addr);
  274. break;
  275. case FPE_FLTSUB:
  276. fprintf(stderr, "subscript out of range at %p\n", info->si_addr);
  277. break;
  278. }
  279. break;
  280. default:
  281. fprintf(stderr, "Unexpected signal %d\n", sig_num);
  282. break;
  283. }
  284. exit(2);
  285. }
  286. /*- End of function --------------------------------------------------------*/
  287. static void fpe_trap_setup(void)
  288. {
  289. struct sigaction trap;
  290. sigemptyset(&trap.sa_mask);
  291. trap.sa_flags = SA_SIGINFO;
  292. trap.sa_sigaction = sigfpe_handler;
  293. sigaction(SIGFPE, &trap, NULL);
  294. //feenableexcept(FE_DIVBYZERO | FE_INEXACT | FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW);
  295. //feenableexcept(FE_ALL_EXCEPT);
  296. feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
  297. }
  298. /*- End of function --------------------------------------------------------*/
  299. #endif
  300. int main(int argc, char *argv[])
  301. {
  302. v27ter_rx_state_t *rx;
  303. v27ter_tx_state_t *tx;
  304. bert_results_t bert_results;
  305. int16_t gen_amp[BLOCK_LEN];
  306. int16_t amp[BLOCK_LEN];
  307. SNDFILE *inhandle;
  308. SNDFILE *outhandle;
  309. int outframes;
  310. int samples;
  311. int test_bps;
  312. int noise_level;
  313. int signal_level;
  314. int bits_per_test;
  315. int line_model_no;
  316. int block_no;
  317. int channel_codec;
  318. int rbs_pattern;
  319. int opt;
  320. bool tep;
  321. bool log_audio;
  322. logging_state_t *logging;
  323. channel_codec = MUNGE_CODEC_NONE;
  324. rbs_pattern = 0;
  325. test_bps = 4800;
  326. tep = false;
  327. line_model_no = 0;
  328. decode_test_file = NULL;
  329. use_gui = false;
  330. noise_level = -70;
  331. signal_level = -13;
  332. bits_per_test = 50000;
  333. log_audio = false;
  334. while ((opt = getopt(argc, argv, "b:B:c:d:glm:n:r:s:t")) != -1)
  335. {
  336. switch (opt)
  337. {
  338. case 'b':
  339. test_bps = atoi(optarg);
  340. if (test_bps != 4800 && test_bps != 2400)
  341. {
  342. fprintf(stderr, "Invalid bit rate specified\n");
  343. exit(2);
  344. }
  345. break;
  346. case 'B':
  347. bits_per_test = atoi(optarg);
  348. break;
  349. case 'c':
  350. channel_codec = atoi(optarg);
  351. break;
  352. case 'd':
  353. decode_test_file = optarg;
  354. break;
  355. case 'g':
  356. #if defined(ENABLE_GUI)
  357. use_gui = true;
  358. #else
  359. fprintf(stderr, "Graphical monitoring not available\n");
  360. exit(2);
  361. #endif
  362. break;
  363. case 'l':
  364. log_audio = true;
  365. break;
  366. case 'm':
  367. line_model_no = atoi(optarg);
  368. break;
  369. case 'n':
  370. noise_level = atoi(optarg);
  371. break;
  372. case 'r':
  373. rbs_pattern = atoi(optarg);
  374. break;
  375. case 's':
  376. signal_level = atoi(optarg);
  377. break;
  378. case 't':
  379. tep = true;
  380. break;
  381. default:
  382. //usage();
  383. exit(2);
  384. break;
  385. }
  386. }
  387. inhandle = NULL;
  388. outhandle = NULL;
  389. #if defined(HAVE_FENV_H)
  390. fpe_trap_setup();
  391. #endif
  392. if (log_audio)
  393. {
  394. if ((outhandle = sf_open_telephony_write(OUT_FILE_NAME, 1)) == NULL)
  395. {
  396. fprintf(stderr, " Cannot create audio file '%s'\n", OUT_FILE_NAME);
  397. exit(2);
  398. }
  399. }
  400. if (decode_test_file)
  401. {
  402. /* We will decode the audio from a file. */
  403. tx = NULL;
  404. if ((inhandle = sf_open_telephony_read(decode_test_file, 1)) == NULL)
  405. {
  406. fprintf(stderr, " Cannot open audio file '%s'\n", decode_test_file);
  407. exit(2);
  408. }
  409. }
  410. else
  411. {
  412. /* We will generate V.27ter audio, and add some noise to it. */
  413. tx = v27ter_tx_init(NULL, test_bps, tep, v27tergetbit, NULL);
  414. logging = v27ter_tx_get_logging_state(tx);
  415. span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
  416. span_log_set_tag(logging, "V.27ter-tx");
  417. v27ter_tx_power(tx, signal_level);
  418. v27ter_tx_set_modem_status_handler(tx, v27ter_tx_status, (void *) tx);
  419. /* Move the carrier off a bit */
  420. #if defined(SPANDSP_EXPOSE_INTERNAL_STRUCTURES)
  421. tx->carrier_phase_rate = dds_phase_ratef(1810.0f);
  422. #endif
  423. bert_init(&bert, bits_per_test, BERT_PATTERN_ITU_O152_11, test_bps, 20);
  424. bert_set_report(&bert, 10000, reporter, NULL);
  425. if ((line_model = one_way_line_model_init(line_model_no, (float) noise_level, channel_codec, rbs_pattern)) == NULL)
  426. {
  427. fprintf(stderr, " Failed to create line model\n");
  428. exit(2);
  429. }
  430. }
  431. rx = v27ter_rx_init(NULL, test_bps, v27terputbit, NULL);
  432. logging = v27ter_rx_get_logging_state(rx);
  433. span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
  434. span_log_set_tag(logging, "V.27ter-rx");
  435. v27ter_rx_set_modem_status_handler(rx, v27ter_rx_status, (void *) rx);
  436. v27ter_rx_set_qam_report_handler(rx, qam_report, (void *) rx);
  437. #if defined(ENABLE_GUI)
  438. if (use_gui)
  439. {
  440. qam_monitor = qam_monitor_init(2.0f, V27TER_CONSTELLATION_SCALING_FACTOR, NULL);
  441. if (!decode_test_file)
  442. {
  443. start_line_model_monitor(129);
  444. line_model_monitor_line_model_update(line_model->near_filter, line_model->near_filter_len);
  445. }
  446. }
  447. #endif
  448. memset(&latest_results, 0, sizeof(latest_results));
  449. for (block_no = 0; ; block_no++)
  450. {
  451. if (decode_test_file)
  452. {
  453. samples = sf_readf_short(inhandle, amp, BLOCK_LEN);
  454. #if defined(ENABLE_GUI)
  455. if (use_gui)
  456. qam_monitor_update_audio_level(qam_monitor, amp, samples);
  457. #endif
  458. if (samples == 0)
  459. break;
  460. }
  461. else
  462. {
  463. samples = v27ter_tx(tx, gen_amp, BLOCK_LEN);
  464. #if defined(ENABLE_GUI)
  465. if (use_gui)
  466. qam_monitor_update_audio_level(qam_monitor, gen_amp, samples);
  467. #endif
  468. if (samples == 0)
  469. {
  470. printf("Restarting on zero output\n");
  471. /* Push a little silence through, to ensure all the data bits get out of the buffers */
  472. vec_zeroi16(amp, BLOCK_LEN);
  473. v27ter_rx(rx, amp, BLOCK_LEN);
  474. v27ter_rx(rx, amp, BLOCK_LEN);
  475. v27ter_rx(rx, amp, BLOCK_LEN);
  476. /* Note that we might get a few bad bits as the carrier shuts down. */
  477. bert_result(&bert, &bert_results);
  478. fprintf(stderr, "Final result %ddBm0/%ddBm0, %d bits, %d bad bits, %d resyncs\n", signal_level, noise_level, bert_results.total_bits, bert_results.bad_bits, bert_results.resyncs);
  479. fprintf(stderr, "Last report %ddBm0/%ddBm0, %d bits, %d bad bits, %d resyncs\n", signal_level, noise_level, latest_results.total_bits, latest_results.bad_bits, latest_results.resyncs);
  480. /* See if bit errors are appearing yet. Also check we are getting enough bits out of the receiver. The last regular report
  481. should be error free, though the final report will generally contain bits errors as the carrier was dying. The total
  482. number of bits out of the receiver should be at least the number we sent. Also, since BERT sync should have occurred
  483. rapidly at the start of transmission, the last report should have occurred at not much less than the total number of
  484. bits we sent. */
  485. if (bert_results.total_bits < bits_per_test
  486. ||
  487. latest_results.total_bits < bits_per_test - 100
  488. ||
  489. latest_results.bad_bits != 0)
  490. {
  491. break;
  492. }
  493. memset(&latest_results, 0, sizeof(latest_results));
  494. signal_level--;
  495. v27ter_tx_restart(tx, test_bps, tep);
  496. v27ter_tx_power(tx, signal_level);
  497. v27ter_rx_restart(rx, test_bps, false);
  498. bert_init(&bert, bits_per_test, BERT_PATTERN_ITU_O152_11, test_bps, 20);
  499. bert_set_report(&bert, 10000, reporter, NULL);
  500. one_way_line_model_free(line_model);
  501. if ((line_model = one_way_line_model_init(line_model_no, (float) noise_level, channel_codec, 0)) == NULL)
  502. {
  503. fprintf(stderr, " Failed to create line model\n");
  504. exit(2);
  505. }
  506. }
  507. if (log_audio)
  508. {
  509. outframes = sf_writef_short(outhandle, gen_amp, samples);
  510. if (outframes != samples)
  511. {
  512. fprintf(stderr, " Error writing audio file\n");
  513. exit(2);
  514. }
  515. }
  516. one_way_line_model(line_model, amp, gen_amp, samples);
  517. }
  518. #if defined(ENABLE_GUI)
  519. if (use_gui && !decode_test_file)
  520. line_model_monitor_line_spectrum_update(amp, samples);
  521. #endif
  522. v27ter_rx(rx, amp, samples);
  523. }
  524. if (!decode_test_file)
  525. {
  526. bert_result(&bert, &bert_results);
  527. fprintf(stderr, "At completion:\n");
  528. fprintf(stderr, "Final result %ddBm0/%ddBm0, %d bits, %d bad bits, %d resyncs\n", signal_level, noise_level, bert_results.total_bits, bert_results.bad_bits, bert_results.resyncs);
  529. fprintf(stderr, "Last report %ddBm0/%ddBm0, %d bits, %d bad bits, %d resyncs\n", signal_level, noise_level, latest_results.total_bits, latest_results.bad_bits, latest_results.resyncs);
  530. one_way_line_model_free(line_model);
  531. if (signal_level > -43)
  532. {
  533. printf("Tests failed.\n");
  534. exit(2);
  535. }
  536. printf("Tests passed.\n");
  537. }
  538. v27ter_rx_free(rx);
  539. if (tx)
  540. v27ter_tx_free(tx);
  541. bert_release(&bert);
  542. #if defined(ENABLE_GUI)
  543. if (use_gui)
  544. qam_wait_to_end(qam_monitor);
  545. #endif
  546. if (decode_test_file)
  547. {
  548. if (sf_close_telephony(inhandle))
  549. {
  550. fprintf(stderr, " Cannot close audio file '%s'\n", decode_test_file);
  551. exit(2);
  552. }
  553. }
  554. if (log_audio)
  555. {
  556. if (sf_close_telephony(outhandle))
  557. {
  558. fprintf(stderr, " Cannot close audio file '%s'\n", OUT_FILE_NAME);
  559. exit(2);
  560. }
  561. }
  562. return 0;
  563. }
  564. /*- End of function --------------------------------------------------------*/
  565. /*- End of file ------------------------------------------------------------*/