Mercurial > hg > audiostuff
view spandsp-0.0.3/spandsp-0.0.3/src/adsi.c @ 5:f762bf195c4b
import spandsp-0.0.3
author | Peter Meerwald <pmeerw@cosy.sbg.ac.at> |
---|---|
date | Fri, 25 Jun 2010 16:00:21 +0200 |
parents | |
children |
line wrap: on
line source
/* * SpanDSP - a series of DSP components for telephony * * adsi.c - Analogue display service interfaces of various types, including * ADSI, TDD and most caller ID formats. * * Written by Steve Underwood <steveu@coppice.org> * * Copyright (C) 2003 Steve Underwood * * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, as * published by the Free Software Foundation. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id: adsi.c,v 1.36 2006/11/19 14:07:23 steveu Exp $ */ /*! \file */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <inttypes.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #if defined(HAVE_TGMATH_H) #include <tgmath.h> #endif #if defined(HAVE_MATH_H) #include <math.h> #endif #include <assert.h> #include "spandsp/telephony.h" #include "spandsp/logging.h" #include "spandsp/dds.h" #include "spandsp/power_meter.h" #include "spandsp/async.h" #include "spandsp/hdlc.h" #include "spandsp/fsk.h" #include "spandsp/tone_detect.h" #include "spandsp/tone_generate.h" #include "spandsp/dtmf.h" #include "spandsp/adsi.h" #define BAUDOT_FIGURE_SHIFT 0x1B #define BAUDOT_LETTER_SHIFT 0x1F #define SOH 0x01 #define STX 0x02 #define ETX 0x03 #define DLE 0x10 #define SUB 0x1A static uint8_t adsi_encode_baudot(adsi_tx_state_t *s, uint8_t ch); static uint8_t adsi_decode_baudot(adsi_rx_state_t *s, uint8_t ch); static int adsi_tx_get_bit(void *user_data) { int bit; adsi_tx_state_t *s; s = (adsi_tx_state_t *) user_data; /* This is similar to the async. handling code in fsk.c, but a few special things are needed in the preamble, and postamble of an ADSI message. */ if (s->bitno < 300) { /* Alternating bit preamble */ bit = s->bitno & 1; s->bitno++; } else if (s->bitno < 300 + s->ones_len) { /* All 1s for receiver conditioning */ /* NB: The receiver is an async one. It needs a rest after the alternating 1/0 sequence so it can reliably pick up on the next start bit, and sync to the byte stream. */ /* The length of this period varies with the circumstance */ bit = 1; s->bitno++; } else if (s->bitno < 300 + s->ones_len + 1) { if (s->bitpos == 0) { /* Start bit */ bit = 0; s->bitpos++; } else if (s->bitpos < 9) { bit = (s->msg[s->byteno] >> (s->bitpos - 1)) & 1; s->bitpos++; } else { /* Stop bit */ bit = 1; s->bitpos = 0; s->byteno++; if (s->byteno > s->msg_len) s->bitno++; } } else if (s->bitno < 300 + s->ones_len + 5) { /* Extra stop bits beyond the last character, to meet the specs., and ensure all bits are out of the DSP before we shut off the FSK modem. */ bit = 1; s->bitno++; } else { bit = 2; if (s->tx_signal_on) { /* The FSK should now be switched off. */ s->tx_signal_on = FALSE; s->msg_len = 0; } } return bit; } /*- End of function --------------------------------------------------------*/ static int adsi_tdd_get_async_byte(void *user_data) { adsi_tx_state_t *s; s = (adsi_tx_state_t *) user_data; if (s->byteno < s->msg_len) return s->msg[s->byteno++]; if (s->tx_signal_on) { /* The FSK should now be switched off. */ s->tx_signal_on = FALSE; s->msg_len = 0; } return 0x1F; } /*- End of function --------------------------------------------------------*/ static void adsi_rx_put_bit(void *user_data, int bit) { adsi_rx_state_t *s; int i; int sum; s = (adsi_rx_state_t *) user_data; if (bit < 0) { /* Special conditions */ switch (bit) { case PUTBIT_CARRIER_UP: span_log(&s->logging, SPAN_LOG_FLOW, "Carrier up.\n"); s->consecutive_ones = 0; s->bitpos = 0; s->in_progress = 0; s->msg_len = 0; s->baudot_shift = 0; break; case PUTBIT_CARRIER_DOWN: span_log(&s->logging, SPAN_LOG_FLOW, "Carrier down.\n"); break; default: span_log(&s->logging, SPAN_LOG_WARNING, "Unexpected special put bit value - %d!\n", bit); break; } return; } bit &= 1; if (s->bitpos == 0) { if (bit == 0) { /* Start bit */ s->bitpos++; if (s->consecutive_ones > 10) { /* This is a line idle condition, which means we should restart message acquisition */ s->msg_len = 0; } s->consecutive_ones = 0; } else { s->consecutive_ones++; } } else if (s->bitpos <= 8) { s->in_progress >>= 1; if (bit) s->in_progress |= 0x80; s->bitpos++; } else { /* Stop bit */ if (bit) { if (s->msg_len < 256) { if (s->standard == ADSI_STANDARD_JCLIP) { if (s->msg_len == 0) { /* A message should start DLE SOH, but let's just check we are starting with a DLE for now */ if (s->in_progress == (0x80 | DLE)) s->msg[s->msg_len++] = (uint8_t) s->in_progress; } else { s->msg[s->msg_len++] = (uint8_t) s->in_progress; } if (s->msg_len >= 11 && s->msg_len == ((s->msg[6] & 0x7F) + 11)) { /* Test the CRC-16 */ if (crc_itu16_calc(s->msg + 2, s->msg_len - 2, 0) == 0) { /* Strip off the parity bits. It doesn't seem worthwhile actually checking the parity if a CRC check has succeeded. */ for (i = 0; i < s->msg_len - 2; i++) s->msg[i] &= 0x7F; /* Put everything, except the CRC octets */ s->put_msg(s->user_data, s->msg, s->msg_len - 2); } else { span_log(&s->logging, SPAN_LOG_WARNING, "CRC failed\n"); } s->msg_len = 0; } } else { s->msg[s->msg_len++] = (uint8_t) s->in_progress; if (s->msg_len >= 3 && s->msg_len == (s->msg[1] + 3)) { /* Test the checksum */ sum = 0; for (i = 0; i < s->msg_len - 1; i++) sum += s->msg[i]; if ((-sum & 0xFF) == s->msg[i]) s->put_msg(s->user_data, s->msg, s->msg_len - 1); else span_log(&s->logging, SPAN_LOG_WARNING, "Sumcheck failed\n"); s->msg_len = 0; } } } } else { s->framing_errors++; } s->bitpos = 0; s->in_progress = 0; } } /*- End of function --------------------------------------------------------*/ static void adsi_tdd_put_async_byte(void *user_data, int byte) { adsi_rx_state_t *s; uint8_t octet; s = (adsi_rx_state_t *) user_data; if (byte < 0) { /* Special conditions */ switch (byte) { case PUTBIT_CARRIER_UP: span_log(&s->logging, SPAN_LOG_FLOW, "Carrier up.\n"); s->consecutive_ones = 0; s->bitpos = 0; s->in_progress = 0; s->msg_len = 0; s->baudot_shift = 0; break; case PUTBIT_CARRIER_DOWN: span_log(&s->logging, SPAN_LOG_FLOW, "Carrier down.\n"); if (s->msg_len > 0) { /* Whatever we have to date constitutes the message */ s->put_msg(s->user_data, s->msg, s->msg_len); s->msg_len = 0; } break; default: span_log(&s->logging, SPAN_LOG_WARNING, "Unexpected special put byte value - %d!\n", byte); break; } return; } if ((octet = adsi_decode_baudot(s, (uint8_t) byte))) s->msg[s->msg_len++] = octet; if (s->msg_len >= 256) { s->put_msg(s->user_data, s->msg, s->msg_len); s->msg_len = 0; } } /*- End of function --------------------------------------------------------*/ static void adsi_rx_dtmf(void *user_data, const char *digits, int len) { adsi_rx_state_t *s; s = (adsi_rx_state_t *) user_data; if (s->msg_len == 0) { /* Message starting. Start a 10s timeout, to make things more noise tolerant for a detector running continuously when on hook. */ s->in_progress = 80000; } for ( ; len && s->msg_len < 256; len--) { if (*digits == '#') { s->put_msg(s->user_data, s->msg, s->msg_len); s->msg_len = 0; } else { s->msg[s->msg_len++] = *digits++; } } } /*- End of function --------------------------------------------------------*/ static void start_tx(adsi_tx_state_t *s) { switch (s->standard) { case ADSI_STANDARD_CLASS: fsk_tx_init(&(s->fsktx), &preset_fsk_specs[FSK_BELL202], adsi_tx_get_bit, s); break; case ADSI_STANDARD_CLIP: case ADSI_STANDARD_ACLIP: case ADSI_STANDARD_JCLIP: fsk_tx_init(&(s->fsktx), &preset_fsk_specs[FSK_V23CH1], adsi_tx_get_bit, s); break; case ADSI_STANDARD_CLIP_DTMF: dtmf_tx_init(&(s->dtmftx)); break; case ADSI_STANDARD_TDD: fsk_tx_init(&(s->fsktx), &preset_fsk_specs[FSK_WEITBRECHT], async_tx_get_bit, &(s->asynctx)); async_tx_init(&(s->asynctx), 5, ASYNC_PARITY_NONE, 2, FALSE, adsi_tdd_get_async_byte, s); /* Schedule an explicit shift at the start of baudot transmission */ s->baudot_shift = 2; break; } s->tx_signal_on = TRUE; } /*- End of function --------------------------------------------------------*/ void adsi_rx(adsi_rx_state_t *s, const int16_t *amp, int len) { switch (s->standard) { case ADSI_STANDARD_CLIP_DTMF: /* Apply a message timeout. */ s->in_progress -= len; if (s->in_progress <= 0) s->msg_len = 0; dtmf_rx(&(s->dtmfrx), amp, len); break; default: fsk_rx(&(s->fskrx), amp, len); break; } } /*- End of function --------------------------------------------------------*/ void adsi_rx_init(adsi_rx_state_t *s, int standard, put_msg_func_t put_msg, void *user_data) { memset(s, 0, sizeof(*s)); s->put_msg = put_msg; s->user_data = user_data; switch (standard) { case ADSI_STANDARD_CLASS: fsk_rx_init(&(s->fskrx), &preset_fsk_specs[FSK_BELL202], FALSE, adsi_rx_put_bit, s); break; case ADSI_STANDARD_CLIP: case ADSI_STANDARD_ACLIP: case ADSI_STANDARD_JCLIP: fsk_rx_init(&(s->fskrx), &preset_fsk_specs[FSK_V23CH1], FALSE, adsi_rx_put_bit, s); break; case ADSI_STANDARD_CLIP_DTMF: dtmf_rx_init(&(s->dtmfrx), adsi_rx_dtmf, s); break; case ADSI_STANDARD_TDD: fsk_rx_init(&(s->fskrx), &preset_fsk_specs[FSK_WEITBRECHT], FALSE, async_rx_put_bit, &(s->asyncrx)); async_rx_init(&(s->asyncrx), 5, ASYNC_PARITY_NONE, 2, TRUE, adsi_tdd_put_async_byte, s); break; } s->standard = standard; span_log_init(&s->logging, SPAN_LOG_NONE, NULL); } /*- End of function --------------------------------------------------------*/ int adsi_tx(adsi_tx_state_t *s, int16_t *amp, int max_len) { int len; len = tone_gen(&(s->alert_tone_gen), amp, max_len); switch (s->standard) { case ADSI_STANDARD_CLIP_DTMF: if (len < max_len) len += dtmf_tx(&(s->dtmftx), amp, max_len - len); break; default: if (len < max_len && s->tx_signal_on) len += fsk_tx(&(s->fsktx), amp + len, max_len - len); break; } return len; } /*- End of function --------------------------------------------------------*/ void adsi_send_alert_tone(adsi_tx_state_t *s) { tone_gen_init(&(s->alert_tone_gen), &(s->alert_tone_desc)); } /*- End of function --------------------------------------------------------*/ int adsi_put_message(adsi_tx_state_t *s, uint8_t *msg, int len) { int i; int j; int k; int byte; int parity; int sum; size_t ii; uint16_t crc_value; /* Don't inject a new message when a previous one is still in progress */ if (s->msg_len > 0) return 0; if (!s->tx_signal_on) { /* We need to restart the modem */ start_tx(s); } switch (s->standard) { case ADSI_STANDARD_CLIP_DTMF: if (len >= 128) return -1; msg[len] = '\0'; len -= (int) dtmf_tx_put(&(s->dtmftx), (char *) msg); break; case ADSI_STANDARD_JCLIP: if (len > 128 - 9) return -1; i = 0; s->msg[i++] = DLE; s->msg[i++] = SOH; s->msg[i++] = 0x07; //header s->msg[i++] = DLE; s->msg[i++] = STX; s->msg[i++] = msg[0]; s->msg[i++] = (uint8_t) (len - 2); /* We might need to byte stuff the overall length, but the rest of the message should already be stuffed. */ if (len - 2 == DLE) s->msg[i++] = DLE; memcpy(&s->msg[i], &msg[2], len - 2); i += len - 2; s->msg[i++] = DLE; s->msg[i++] = ETX; /* Set the parity bits */ for (j = 0; j < i; j++) { byte = s->msg[j]; parity = 0; for (k = 1; k <= 7; k++) parity ^= (byte << k); s->msg[j] = (s->msg[j] & 0x7F) | ((uint8_t) parity & 0x80); } crc_value = crc_itu16_calc(s->msg + 2, i - 2, 0); s->msg[i++] = (uint8_t) (crc_value & 0xFF); s->msg[i++] = (uint8_t) ((crc_value >> 8) & 0xFF); s->msg_len = i; s->ones_len = 80; break; case ADSI_STANDARD_TDD: if (len > 255) return -1; memcpy(s->msg, msg, len); s->msg_len = len; break; default: if (len > 255) return -1; memcpy(s->msg, msg, len); /* Force the length in case it is wrong */ s->msg[1] = (uint8_t) (len - 2); /* Add the sumcheck */ sum = 0; for (ii = 0; ii < (size_t) len; ii++) sum += s->msg[ii]; s->msg[len] = (uint8_t) ((-sum) & 0xFF); s->msg_len = len + 1; s->ones_len = 80; break; } /* Prepare the bit sequencing */ s->byteno = 0; s->bitpos = 0; s->bitno = 0; return len; } /*- End of function --------------------------------------------------------*/ void adsi_tx_init(adsi_tx_state_t *s, int standard) { memset(s, 0, sizeof(*s)); make_tone_gen_descriptor(&(s->alert_tone_desc), 2130, -13, 2750, -13, 110, 60, 0, 0, FALSE); s->standard = standard; span_log_init(&s->logging, SPAN_LOG_NONE, NULL); start_tx(s); } /*- End of function --------------------------------------------------------*/ static uint8_t adsi_encode_baudot(adsi_tx_state_t *s, uint8_t ch) { static const uint8_t conv[128] = { 0x00, /* NUL */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0x42, /* LF */ 0xFF, /* */ 0xFF, /* */ 0x48, /* CR */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0xFF, /* */ 0x44, /* */ 0xFF, /* ! */ 0xFF, /* " */ 0x94, /* # */ 0x89, /* $ */ 0xFF, /* % */ 0xFF, /* & */ 0x85, /* ' */ 0x8F, /* ( */ 0x92, /* ) */ 0x8B, /* * */ 0x91, /* + */ 0x8C, /* , */ 0x83, /* - */ 0x9C, /* . */ 0x9D, /* / */ 0x96, /* 0 */ 0x97, /* 1 */ 0x93, /* 2 */ 0x81, /* 3 */ 0x8A, /* 4 */ 0x90, /* 5 */ 0x95, /* 6 */ 0x87, /* 7 */ 0x86, /* 8 */ 0x98, /* 9 */ 0x8E, /* : */ 0xFF, /* ; */ 0xFF, /* < */ 0x9E, /* = */ 0xFF, /* > */ 0x99, /* ? */ 0xFF, /* @ */ 0x03, /* A */ 0x19, /* B */ 0x0E, /* C */ 0x09, /* D */ 0x01, /* E */ 0x0D, /* F */ 0x1A, /* G */ 0x14, /* H */ 0x06, /* I */ 0x0B, /* J */ 0x0F, /* K */ 0x12, /* L */ 0x1C, /* M */ 0x0C, /* N */ 0x18, /* O */ 0x16, /* P */ 0x17, /* Q */ 0x0A, /* R */ 0x05, /* S */ 0x10, /* T */ 0x07, /* U */ 0x1E, /* V */ 0x13, /* W */ 0x1D, /* X */ 0x15, /* Y */ 0x11, /* Z */ 0xFF, /* [ */ 0xFF, /* \ */ 0xFF, /* ] */ 0x9B, /* ^ */ 0xFF, /* _ */ 0xFF, /* ` */ 0x03, /* a */ 0x19, /* b */ 0x0E, /* c */ 0x09, /* d */ 0x01, /* e */ 0x0D, /* f */ 0x1A, /* g */ 0x14, /* h */ 0x06, /* i */ 0x0B, /* j */ 0x0F, /* k */ 0x12, /* l */ 0x1C, /* m */ 0x0C, /* n */ 0x18, /* o */ 0x16, /* p */ 0x17, /* q */ 0x0A, /* r */ 0x05, /* s */ 0x10, /* t */ 0x07, /* u */ 0x1E, /* v */ 0x13, /* w */ 0x1D, /* x */ 0x15, /* y */ 0x11, /* z */ 0xFF, /* { */ 0xFF, /* | */ 0xFF, /* } */ 0xFF, /* ~ */ 0xFF, /* DEL */ }; ch = conv[ch]; if (ch != 0xFF) { if ((ch & 0x40)) return ch & 0x1F; if ((ch & 0x80)) { if (s->baudot_shift == 1) return ch & 0x1F; s->baudot_shift = 1; return (BAUDOT_FIGURE_SHIFT << 5) | (ch & 0x1F); } else { if (s->baudot_shift == 0) return ch & 0x1F; s->baudot_shift = 0; return (BAUDOT_LETTER_SHIFT << 5) | (ch & 0x1F); } } return 0; } /*- End of function --------------------------------------------------------*/ static uint8_t adsi_decode_baudot(adsi_rx_state_t *s, uint8_t ch) { static const uint8_t conv[2][32] = { {"\000E\nA SIU\rDRJNFCKTZLWHYPQOBG^MXV^"}, {"\0003\n- '87\r$4*,*:(5+)2#6019?*^./=^"} }; switch (ch) { case BAUDOT_FIGURE_SHIFT: s->baudot_shift = 1; break; case BAUDOT_LETTER_SHIFT: s->baudot_shift = 0; break; default: return conv[s->baudot_shift][ch]; } /* return 0 if we did not produce a character */ return 0; } /*- End of function --------------------------------------------------------*/ int adsi_next_field(adsi_rx_state_t *s, const uint8_t *msg, int msg_len, int pos, uint8_t *field_type, uint8_t const **field_body, int *field_len) { int i; /* Return -1 for no more fields. Return -2 for message structure corrupt. */ switch (s->standard) { case ADSI_STANDARD_CLASS: case ADSI_STANDARD_CLIP: case ADSI_STANDARD_ACLIP: if (pos >= msg_len) return -1; /* For MDMF type messages, these standards all use "IE" type fields - type, length, contents - and similar headers */ if (pos <= 0) { /* Return the message type */ *field_type = msg[0]; *field_len = 0; *field_body = NULL; pos = 2; } else { if ((msg[0] & 0x80)) { /* MDMF messages seem to always have a message type with the MSB set. Is that guaranteed? */ *field_type = msg[pos++]; *field_len = msg[pos++]; *field_body = msg + pos; } else { /* SDMF */ *field_type = 0; *field_len = msg_len - pos; *field_body = msg + pos; } pos += *field_len; } if (pos > msg_len) return -2; break; case ADSI_STANDARD_JCLIP: if (pos >= msg_len - 2) return -1; if (pos <= 0) { /* Return the message type */ pos = 5; *field_type = msg[pos++]; if (*field_type == DLE) pos++; if (msg[pos++] == DLE) pos++; *field_len = 0; *field_body = NULL; } else { *field_type = msg[pos++]; if (*field_type == DLE) pos++; *field_len = msg[pos++]; if (*field_len == DLE) pos++; /* TODO: we assume here that the body contains no DLE's that would have been stuffed */ *field_body = msg + pos; pos += *field_len; } if (pos > msg_len - 2) return -2; break; case ADSI_STANDARD_CLIP_DTMF: if (pos >= msg_len) return -1; if (pos < 0) pos = 0; *field_type = msg[pos++]; *field_body = msg + pos; i = pos; while (i < msg_len && msg[i] != '#') i++; *field_len = i - pos; pos = i; if (msg[pos] == '#') pos++; if (pos > msg_len) return -2; break; case ADSI_STANDARD_TDD: if (pos >= msg_len) return -1; *field_type = 0; *field_body = msg; *field_len = msg_len; pos = msg_len; break; } return pos; } /*- End of function --------------------------------------------------------*/ int adsi_add_field(adsi_tx_state_t *s, uint8_t *msg, int len, uint8_t field_type, uint8_t const *field_body, int field_len) { int i; int x; switch (s->standard) { case ADSI_STANDARD_CLASS: case ADSI_STANDARD_CLIP: case ADSI_STANDARD_ACLIP: /* These standards all use "IE" type fields - type, length, value - and similar headers */ if (len <= 0) { /* Initialise a new message. The field type is actually the message type. */ msg[0] = field_type; msg[1] = 0; len = 2; } else { /* Add to a message in progress. */ if (field_type) { msg[len++] = field_type; msg[len++] = (uint8_t) field_len; if (field_len == DLE) msg[len++] = (uint8_t) field_len; memcpy(msg + len, field_body, field_len); len += field_len; } else { /* No field type or length, for restricted single message formats */ memcpy(msg + len, field_body, field_len); len += field_len; } } break; case ADSI_STANDARD_JCLIP: /* This standard uses "IE" type fields - type, length, value - but escapes DLE characters, to prevent immitation of a control octet. */ if (len <= 0) { /* Initialise a new message. The field type is actually the message type. */ msg[0] = field_type; msg[1] = 0; len = 2; } else { /* Add to a message in progress. */ msg[len++] = field_type; if (field_type == DLE) msg[len++] = field_type; msg[len++] = (uint8_t) field_len; if (field_len == DLE) msg[len++] = (uint8_t) field_len; for (i = 0; i < field_len; i++) { msg[len++] = field_body[i]; if (field_body[i] == DLE) msg[len++] = field_body[i]; } } break; case ADSI_STANDARD_CLIP_DTMF: if (len < 0) { len = 0; } else { msg[len] = field_type; memcpy(msg + len + 1, field_body, field_len); msg[len + field_len + 1] = '#'; len += (field_len + 2); } break; case ADSI_STANDARD_TDD: if (len < 0) len = 0; for (i = 0; i < field_len; i++) { if ((x = adsi_encode_baudot(s, field_body[i]))) { if ((x & 0x3E0)) msg[len++] = (uint8_t) ((x >> 5) & 0x1F); msg[len++] = (uint8_t) (x & 0x1F); } } break; } return len; } /*- End of function --------------------------------------------------------*/ const char *adsi_standard_to_str(int standard) { switch (standard) { case ADSI_STANDARD_CLASS: return "CLASS"; case ADSI_STANDARD_CLIP: return "CLIP"; case ADSI_STANDARD_ACLIP: return "A-CLIP"; case ADSI_STANDARD_JCLIP: return "J-CLIP"; case ADSI_STANDARD_CLIP_DTMF: return "CLIP-DTMF"; case ADSI_STANDARD_TDD: return "TDD"; } return "???"; } /*- End of function --------------------------------------------------------*/ /*- End of file ------------------------------------------------------------*/