Mercurial > hg > audiostuff
diff spandsp-0.0.6pre17/src/v8.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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spandsp-0.0.6pre17/src/v8.c Fri Jun 25 15:50:58 2010 +0200 @@ -0,0 +1,1089 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v8.c - V.8 modem negotiation processing. + * + * Written by Steve Underwood <steveu@coppice.org> + * + * Copyright (C) 2004 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1, + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v8.c,v 1.42.4.3 2009/12/28 12:20:47 steveu Exp $ + */ + +/*! \file */ + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include <memory.h> +#if defined(HAVE_TGMATH_H) +#include <tgmath.h> +#endif +#if defined(HAVE_MATH_H) +#include <math.h> +#endif +#include "floating_fudge.h" + +#include "spandsp/telephony.h" +#include "spandsp/logging.h" +#include "spandsp/queue.h" +#include "spandsp/async.h" +#include "spandsp/vector_int.h" +#include "spandsp/complex.h" +#include "spandsp/dds.h" +#include "spandsp/tone_detect.h" +#include "spandsp/tone_generate.h" +#include "spandsp/super_tone_rx.h" +#include "spandsp/power_meter.h" +#include "spandsp/fsk.h" +#include "spandsp/modem_connect_tones.h" +#include "spandsp/v8.h" + +#include "spandsp/private/logging.h" +#include "spandsp/private/fsk.h" +#include "spandsp/private/modem_connect_tones.h" +#include "spandsp/private/v8.h" + +enum +{ + V8_WAIT_1S, + V8_CI_ON, + V8_CI_OFF, + V8_HEARD_ANSAM, + V8_CM_ON, + V8_CJ_ON, + V8_CM_WAIT, + + V8_SIGC, + V8_JM_ON, + V8_SIGA, + + V8_PARKED +} v8_states_e; + +enum +{ + V8_SYNC_UNKNOWN = 0, + V8_SYNC_CI, + V8_SYNC_CM_JM, + V8_SYNC_V92 +} v8_sync_types_e; + +enum +{ + V8_CALL_FUNCTION_TAG = 0x01, + V8_MODULATION_TAG = 0x05, + V8_PROTOCOLS_TAG = 0x0A, + V8_PSTN_ACCESS_TAG = 0x0D, + V8_NSF_TAG = 0x0F, + V8_PCM_MODEM_AVAILABILITY_TAG = 0x07, + V8_T66_TAG = 0x0E +}; + +enum +{ + V8_CI_SYNC_OCTET = 0x00, + V8_CM_JM_SYNC_OCTET = 0xE0 +}; + +SPAN_DECLARE(const char *) v8_call_function_to_str(int call_function) +{ + switch (call_function) + { + case V8_CALL_TBS: + return "TBS"; + case V8_CALL_H324: + return "H.324 PSTN multimedia terminal"; + case V8_CALL_V18: + return "V.18 textphone"; + case V8_CALL_T101: + return "T.101 videotext"; + case V8_CALL_T30_TX: + return "T.30 Tx FAX"; + case V8_CALL_T30_RX: + return "T.30 Rx FAX"; + case V8_CALL_V_SERIES: + return "V series modem data"; + case V8_CALL_FUNCTION_EXTENSION: + return "Call function is in extention octet"; + } + return "???"; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(const char *) v8_modulation_to_str(int modulation_scheme) +{ + switch (modulation_scheme) + { + case V8_MOD_V17: + return "V.17 half-duplex"; + case V8_MOD_V21: + return "V.21 duplex"; + case V8_MOD_V22: + return "V.22/V.22bis duplex"; + case V8_MOD_V23HALF: + return "V.23 half-duplex"; + case V8_MOD_V23: + return "V.23 duplex"; + case V8_MOD_V26BIS: + return "V.26bis duplex"; + case V8_MOD_V26TER: + return "V.26ter duplex"; + case V8_MOD_V27TER: + return "V.27ter duplex"; + case V8_MOD_V29: + return "V.29 half-duplex"; + case V8_MOD_V32: + return "V.32/V.32bis duplex"; + case V8_MOD_V34HALF: + return "V.34 half-duplex"; + case V8_MOD_V34: + return "V.34 duplex"; + case V8_MOD_V90: + return "V.90 duplex"; + case V8_MOD_V92: + return "V.92 duplex"; + case V8_MOD_FAILED: + return "negotiation failed"; + } + return "???"; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(const char *) v8_protocol_to_str(int protocol) +{ + switch (protocol) + { + case V8_PROTOCOL_NONE: + return "None"; + case V8_PROTOCOL_LAPM_V42: + return "LAPM"; + case V8_PROTOCOL_EXTENSION: + return "Extension"; + } + return "Undefined"; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(const char *) v8_pstn_access_to_str(int pstn_access) +{ + switch (pstn_access) + { + case V8_PSTN_ACCESS_CALL_DCE_CELLULAR: + return "Calling modem on cellular"; + case V8_PSTN_ACCESS_ANSWER_DCE_CELLULAR: + return "Answering modem on cellular"; + case V8_PSTN_ACCESS_ANSWER_DCE_CELLULAR | V8_PSTN_ACCESS_CALL_DCE_CELLULAR: + return "Answering and calling modems on cellular"; + case V8_PSTN_ACCESS_DCE_ON_DIGITAL: + return "DCE on digital"; + case V8_PSTN_ACCESS_DCE_ON_DIGITAL | V8_PSTN_ACCESS_CALL_DCE_CELLULAR: + return "DCE on digital, and calling modem on cellular"; + case V8_PSTN_ACCESS_DCE_ON_DIGITAL | V8_PSTN_ACCESS_ANSWER_DCE_CELLULAR: + return "DCE on digital, answering modem on cellular"; + case V8_PSTN_ACCESS_DCE_ON_DIGITAL | V8_PSTN_ACCESS_ANSWER_DCE_CELLULAR | V8_PSTN_ACCESS_CALL_DCE_CELLULAR: + return "DCE on digital, and answering and calling modems on cellular"; + } + return "???"; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(const char *) v8_nsf_to_str(int nsf) +{ + switch (nsf) + { + case 0: + return "???"; + } + return "???"; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(const char *) v8_pcm_modem_availability_to_str(int pcm_modem_availability) +{ + switch (pcm_modem_availability) + { + case 0: + return "PCM unavailable"; + case V8_PSTN_PCM_MODEM_V90_V92_ANALOGUE: + return "V.90/V.92 analogue available"; + case V8_PSTN_PCM_MODEM_V90_V92_DIGITAL: + return "V.90/V.92 digital available"; + case V8_PSTN_PCM_MODEM_V90_V92_DIGITAL | V8_PSTN_PCM_MODEM_V90_V92_ANALOGUE: + return "V.90/V.92 digital/analogue available"; + case V8_PSTN_PCM_MODEM_V91: + return "V.91 available"; + case V8_PSTN_PCM_MODEM_V91 | V8_PSTN_PCM_MODEM_V90_V92_ANALOGUE: + return "V.91 and V.90/V.92 analogue available"; + case V8_PSTN_PCM_MODEM_V91 | V8_PSTN_PCM_MODEM_V90_V92_DIGITAL: + return "V.91 and V.90/V.92 digital available"; + case V8_PSTN_PCM_MODEM_V91 | V8_PSTN_PCM_MODEM_V90_V92_DIGITAL | V8_PSTN_PCM_MODEM_V90_V92_ANALOGUE: + return "V.91 and V.90/V.92 digital/analogue available"; + } + return "???"; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(const char *) v8_t66_to_str(int t66) +{ + /* T.66 doesn't really define any V.8 values. The bits are all reserved. */ + switch (t66) + { + case 0: + return "???"; + case 1: + return "Reserved TIA"; + case 2: + return "Reserved"; + case 3: + return "Reserved TIA + others"; + case 4: + return "Reserved"; + case 5: + return "Reserved TIA + others"; + case 6: + return "Reserved"; + case 7: + return "Reserved TIA + others"; + } + return "???"; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) v8_log_supported_modulations(v8_state_t *s, int modulation_schemes) +{ + const char *comma; + int i; + + comma = ""; + span_log(&s->logging, SPAN_LOG_FLOW, ""); + for (i = 0; i < 32; i++) + { + if ((modulation_schemes & (1 << i))) + { + span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "%s%s", comma, v8_modulation_to_str(modulation_schemes & (1 << i))); + comma = ", "; + } + } + span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, " supported\n"); +} +/*- End of function --------------------------------------------------------*/ + +static const uint8_t *process_call_function(v8_state_t *s, const uint8_t *p) +{ + s->result.call_function = (*p >> 5) & 0x07; + span_log(&s->logging, SPAN_LOG_FLOW, "%s\n", v8_call_function_to_str(s->result.call_function)); + return ++p; +} +/*- End of function --------------------------------------------------------*/ + +static const uint8_t *process_modulation_mode(v8_state_t *s, const uint8_t *p) +{ + unsigned int far_end_modulations; + + /* Modulation mode octet */ + far_end_modulations = 0; + if (*p & 0x80) + far_end_modulations |= V8_MOD_V34HALF; + if (*p & 0x40) + far_end_modulations |= V8_MOD_V34; + if (*p & 0x20) + far_end_modulations |= V8_MOD_V90; + + /* Check for an extension octet */ + if ((*++p & 0x38) == 0x10) + { + if (*p & 0x80) + far_end_modulations |= V8_MOD_V27TER; + if (*p & 0x40) + far_end_modulations |= V8_MOD_V29; + if (*p & 0x04) + far_end_modulations |= V8_MOD_V17; + if (*p & 0x02) + far_end_modulations |= V8_MOD_V22; + if (*p & 0x01) + far_end_modulations |= V8_MOD_V32; + + /* Check for an extension octet */ + if ((*++p & 0x38) == 0x10) + { + if (*p & 0x80) + far_end_modulations |= V8_MOD_V21; + if (*p & 0x40) + far_end_modulations |= V8_MOD_V23HALF; + if (*p & 0x04) + far_end_modulations |= V8_MOD_V23; + if (*p & 0x02) + far_end_modulations |= V8_MOD_V26BIS; + if (*p & 0x01) + far_end_modulations |= V8_MOD_V26TER; + } + } + s->far_end_modulations = + s->result.modulations = far_end_modulations; + v8_log_supported_modulations(s, far_end_modulations); + return ++p; +} +/*- End of function --------------------------------------------------------*/ + +static const uint8_t *process_protocols(v8_state_t *s, const uint8_t *p) +{ + s->result.protocol = (*p >> 5) & 0x07; + span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "%s\n", v8_protocol_to_str(s->result.protocol)); + return ++p; +} +/*- End of function --------------------------------------------------------*/ + +static const uint8_t *process_pstn_access(v8_state_t *s, const uint8_t *p) +{ + s->result.pstn_access = (*p >> 5) & 0x07; + span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "%s\n", v8_pstn_access_to_str(s->result.pstn_access)); + return ++p; +} +/*- End of function --------------------------------------------------------*/ + +static const uint8_t *process_non_standard_facilities(v8_state_t *s, const uint8_t *p) +{ + s->result.nsf = (*p >> 5) & 0x07; + span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "%s\n", v8_nsf_to_str(s->result.nsf)); + return p; +} +/*- End of function --------------------------------------------------------*/ + +static const uint8_t *process_pcm_modem_availability(v8_state_t *s, const uint8_t *p) +{ + s->result.pcm_modem_availability = (*p >> 5) & 0x07; + span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "%s\n", v8_pcm_modem_availability_to_str(s->result.pcm_modem_availability)); + return ++p; +} +/*- End of function --------------------------------------------------------*/ + +static const uint8_t *process_t66(v8_state_t *s, const uint8_t *p) +{ + s->result.t66 = (*p >> 5) & 0x07; + span_log(&s->logging, SPAN_LOG_FLOW | SPAN_LOG_SUPPRESS_LABELLING, "%s\n", v8_t66_to_str(s->result.t66)); + return ++p; +} +/*- End of function --------------------------------------------------------*/ + +static void ci_decode(v8_state_t *s) +{ + if ((s->rx_data[0] & 0x1F) == V8_CALL_FUNCTION_TAG) + process_call_function(s, &s->rx_data[0]); +} +/*- End of function --------------------------------------------------------*/ + +static void cm_jm_decode(v8_state_t *s) +{ + const uint8_t *p; + + if (s->got_cm_jm) + return; + + /* We must receive two consecutive identical CM or JM sequences to accept it. */ + if (s->cm_jm_len <= 0 + || + s->cm_jm_len != s->rx_data_ptr + || + memcmp(s->cm_jm_data, s->rx_data, s->rx_data_ptr)) + { + /* Save the current CM or JM sequence */ + s->cm_jm_len = s->rx_data_ptr; + memcpy(s->cm_jm_data, s->rx_data, s->rx_data_ptr); + return; + } + /* We have a matching pair of CMs or JMs, so we are happy this is correct. */ + s->got_cm_jm = TRUE; + + span_log(&s->logging, SPAN_LOG_FLOW, "Decoding\n"); + + /* Zero indicates the end */ + s->cm_jm_data[s->cm_jm_len] = 0; + + s->result.modulations = 0; + p = s->cm_jm_data; + + while (*p) + { + switch (*p & 0x1F) + { + case V8_CALL_FUNCTION_TAG: + p = process_call_function(s, p); + break; + case V8_MODULATION_TAG: + p = process_modulation_mode(s, p); + break; + case V8_PROTOCOLS_TAG: + p = process_protocols(s, p); + break; + case V8_PSTN_ACCESS_TAG: + p = process_pstn_access(s, p); + break; + case V8_NSF_TAG: + p = process_non_standard_facilities(s, p); + break; + case V8_PCM_MODEM_AVAILABILITY_TAG: + p = process_pcm_modem_availability(s, p); + break; + case V8_T66_TAG: + p = process_t66(s, p); + break; + default: + p++; + break; + } + /* Skip any future extensions we do not understand */ + while ((*p & 0x38) == 0x10) + p++; + } +} +/*- End of function --------------------------------------------------------*/ + +static void put_bit(void *user_data, int bit) +{ + v8_state_t *s; + int new_preamble_type; + const char *tag; + uint8_t data; + + s = user_data; + if (bit < 0) + { + /* Special conditions */ + switch (bit) + { + case SIG_STATUS_CARRIER_UP: + case SIG_STATUS_CARRIER_DOWN: + case SIG_STATUS_TRAINING_SUCCEEDED: + case SIG_STATUS_TRAINING_FAILED: + break; + default: + break; + } + return; + } + //span_log(&s->logging, SPAN_LOG_FLOW, "Bit %d\n", bit); + /* Wait until we sync. */ + s->bit_stream = (s->bit_stream >> 1) | (bit << 19); + /* CI preamble is 10 ones then a framed 0x00 + CM/JM preamble is 10 ones then a framed 0x07 + V.92 preamble is 10 ones then a framed 0x55 + Should we look at all 10 ones? The first couple might be + settling down. */ + /* The preamble + synchronisation bit sequence should be unique in + any bit stream, so we can rely on seeing this at any time as being + a real sync code. */ + switch (s->bit_stream) + { + case 0x803FF: + new_preamble_type = V8_SYNC_CI; + break; + case 0xF03FF: + new_preamble_type = V8_SYNC_CM_JM; + break; + case 0xAABFF: + new_preamble_type = V8_SYNC_V92; + break; + default: + new_preamble_type = V8_SYNC_UNKNOWN; + break; + } + if (new_preamble_type != V8_SYNC_UNKNOWN) + { + /* We have seen a fresh sync code */ + /* Debug */ + if (span_log_test(&s->logging, SPAN_LOG_FLOW)) + { + if (s->preamble_type != V8_SYNC_UNKNOWN) + { + switch (s->preamble_type) + { + case V8_SYNC_CI: + tag = "CI: "; + break; + case V8_SYNC_CM_JM: + tag = (s->calling_party) ? "JM: " : "CM: "; + break; + case V8_SYNC_V92: + tag = "V92: "; + break; + default: + tag = "??: "; + break; + } + span_log_buf(&s->logging, SPAN_LOG_FLOW, tag, s->rx_data, s->rx_data_ptr); + } + } + /* If we were handling a valid sync code then we should process what has been + received to date. */ + switch (s->preamble_type) + { + case V8_SYNC_CI: + ci_decode(s); + break; + case V8_SYNC_CM_JM: + cm_jm_decode(s); + break; + } + s->preamble_type = new_preamble_type; + s->bit_cnt = 0; + s->rx_data_ptr = 0; + } + + if (s->preamble_type != V8_SYNC_UNKNOWN) + { + /* Parse octets with 1 bit start, 1 bit stop */ + s->bit_cnt++; + /* Start, stop? */ + if ((s->bit_stream & 0x80400) == 0x80000 && s->bit_cnt >= 10) + { + /* Store the available data */ + data = (uint8_t) ((s->bit_stream >> 11) & 0xFF); + /* CJ (3 successive zero octets) detection */ + if (data == 0) + { + if (++s->zero_byte_count == 3) + s->got_cj = TRUE; + } + else + { + s->zero_byte_count = 0; + } + + if (s->rx_data_ptr < (int) (sizeof(s->rx_data) - 1)) + s->rx_data[s->rx_data_ptr++] = data; + s->bit_cnt = 0; + } + } +} +/*- End of function --------------------------------------------------------*/ + +static void v8_decode_init(v8_state_t *s) +{ + fsk_rx_init(&s->v21rx, + &preset_fsk_specs[(s->calling_party) ? FSK_V21CH2 : FSK_V21CH1], + FSK_FRAME_MODE_ASYNC, + put_bit, + s); + fsk_rx_signal_cutoff(&s->v21rx, -45.5f); + s->preamble_type = V8_SYNC_UNKNOWN; + s->bit_stream = 0; + s->cm_jm_len = 0; + s->got_cm_jm = FALSE; + s->got_cj = FALSE; + s->zero_byte_count = 0; + s->rx_data_ptr = 0; +} +/*- End of function --------------------------------------------------------*/ + +static int get_bit(void *user_data) +{ + v8_state_t *s; + uint8_t bit; + + s = user_data; + if (queue_read(s->tx_queue, &bit, 1) <= 0) + return SIG_STATUS_END_OF_DATA; + return bit; +} +/*- End of function --------------------------------------------------------*/ + +static void v8_put_preamble(v8_state_t *s) +{ + static const uint8_t preamble[10] = + { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + queue_write(s->tx_queue, preamble, 10); +} +/*- End of function --------------------------------------------------------*/ + +static void v8_put_byte(v8_state_t *s, int data) +{ + int i; + uint8_t bits[10]; + + /* Insert start & stop bits */ + bits[0] = 0; + for (i = 1; i < 9; i++) + { + bits[i] = (uint8_t) (data & 1); + data >>= 1; + } + bits[9] = 1; + queue_write(s->tx_queue, bits, 10); +} +/*- End of function --------------------------------------------------------*/ + +static void send_cm_jm(v8_state_t *s) +{ + int val; + unsigned int offered_modulations; + + offered_modulations = s->parms.modulations & s->far_end_modulations; + + /* Send a CM, or a JM as appropriate */ + v8_put_preamble(s); + v8_put_byte(s, V8_CM_JM_SYNC_OCTET); + /* Data call */ + v8_put_byte(s, (s->result.call_function << 5) | V8_CALL_FUNCTION_TAG); + + /* Supported modulations */ + val = 0x05; + if (offered_modulations & V8_MOD_V90) + val |= 0x20; + if (offered_modulations & V8_MOD_V34) + val |= 0x40; + v8_put_byte(s, val); + + val = 0x10; + if (offered_modulations & V8_MOD_V32) + val |= 0x01; + if (offered_modulations & V8_MOD_V22) + val |= 0x02; + if (offered_modulations & V8_MOD_V17) + val |= 0x04; + if (offered_modulations & V8_MOD_V29) + val |= 0x40; + if (offered_modulations & V8_MOD_V27TER) + val |= 0x80; + v8_put_byte(s, val); + + val = 0x10; + if (offered_modulations & V8_MOD_V26TER) + val |= 0x01; + if (offered_modulations & V8_MOD_V26BIS) + val |= 0x02; + if (offered_modulations & V8_MOD_V23) + val |= 0x04; + if (offered_modulations & V8_MOD_V23HALF) + val |= 0x40; + if (offered_modulations & V8_MOD_V21) + val |= 0x80; + v8_put_byte(s, val); + + if (s->parms.protocol) + v8_put_byte(s, (s->parms.protocol << 5) | V8_PROTOCOLS_TAG); + if (s->parms.pcm_modem_availability) + v8_put_byte(s, (s->parms.pcm_modem_availability << 5) | V8_PCM_MODEM_AVAILABILITY_TAG); + if (s->parms.pstn_access) + v8_put_byte(s, (s->parms.pstn_access << 5) | V8_PSTN_ACCESS_TAG); + if (s->parms.t66 >= 0) + v8_put_byte(s, (s->parms.t66 << 5) | V8_T66_TAG); + + /* No NSF */ + //v8_put_byte(s, (0 << 5) | V8_NSF_TAG); +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE_NONSTD(int) v8_tx(v8_state_t *s, int16_t *amp, int max_len) +{ + int len; + + //span_log(&s->logging, SPAN_LOG_FLOW, "v8_tx state %d\n", s->state); + len = 0; + if (s->modem_connect_tone_tx_on) + { + if (s->modem_connect_tone_tx_on > ms_to_samples(75)) + { + /* Send the ANSam tone */ + len = modem_connect_tones_tx(&s->ansam_tx, amp, max_len); + if (len < max_len) + { + span_log(&s->logging, SPAN_LOG_FLOW, "ANSam or ANSam/ ended\n"); + s->modem_connect_tone_tx_on = ms_to_samples(75); + } + } + else + { + /* Send the 75ms of silence after the ANSam tone */ + if (max_len > s->modem_connect_tone_tx_on) + len = s->modem_connect_tone_tx_on; + else + len = max_len; + vec_zeroi16(amp, len); + s->modem_connect_tone_tx_on -= len; + } + } + if (s->fsk_tx_on && len < max_len) + { + max_len -= len; + len = fsk_tx(&s->v21tx, amp + len, max_len); + if (len < max_len) + { + span_log(&s->logging, SPAN_LOG_FLOW, "FSK ends\n"); + s->fsk_tx_on = FALSE; + } + } + return len; +} +/*- End of function --------------------------------------------------------*/ + +static void v8_send_ci(v8_state_t *s) +{ + int i; + + /* Send 4 CI packets in a burst (the spec says at least 3) */ + for (i = 0; i < 4; i++) + { + v8_put_preamble(s); + v8_put_byte(s, V8_CI_SYNC_OCTET); + v8_put_byte(s, (s->result.call_function << 5) | V8_CALL_FUNCTION_TAG); + } +} +/*- End of function --------------------------------------------------------*/ + +static void handle_modem_connect_tone(v8_state_t *s, int tone) +{ + s->result.modem_connect_tone = tone; + span_log(&s->logging, SPAN_LOG_FLOW, "'%s' recognised\n", modem_connect_tone_to_str(tone)); + if (tone == MODEM_CONNECT_TONES_ANSAM + || + tone == MODEM_CONNECT_TONES_ANSAM_PR) + { + /* Set the Te interval. The spec. says 500ms is the minimum, + but gives reasons why 1 second is a better value. */ + s->state = V8_HEARD_ANSAM; + s->ci_timer = ms_to_samples(1000); + } + else + { + /* If we found a connect tone, and it isn't one of the modulated answer tones, + indicating V.8 startup, we are not going to do V.8 processing. */ + span_log(&s->logging, SPAN_LOG_FLOW, "Non-V.8 modem connect tone detected\n"); + s->state = V8_PARKED; + if (s->result_handler) + s->result_handler(s->result_handler_user_data, &s->result); + } +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE_NONSTD(int) v8_rx(v8_state_t *s, const int16_t *amp, int len) +{ + int i; + int residual_samples; + int tone; + + //span_log(&s->logging, SPAN_LOG_FLOW, "v8_rx state %d\n", s->state); + residual_samples = 0; + switch (s->state) + { + case V8_WAIT_1S: + residual_samples = modem_connect_tones_rx(&s->ansam_rx, amp, len); + /* Wait 1 second before sending the first CI packet */ + if ((s->negotiation_timer -= len) > 0) + break; + fsk_tx_restart(&s->v21tx, &preset_fsk_specs[FSK_V21CH1]); + v8_send_ci(s); + s->state = V8_CI_ON; + s->fsk_tx_on = TRUE; + break; + case V8_CI_ON: + residual_samples = modem_connect_tones_rx(&s->ansam_rx, amp, len); + /* Check if an ANSam or ANSam/ tone has been detected */ + if ((tone = modem_connect_tones_rx_get(&s->ansam_rx)) != MODEM_CONNECT_TONES_NONE) + { + handle_modem_connect_tone(s, tone); + break; + } + if (queue_empty(s->tx_queue)) + { + s->state = V8_CI_OFF; + s->ci_timer = ms_to_samples(500); + break; + } + break; + case V8_CI_OFF: + residual_samples = modem_connect_tones_rx(&s->ansam_rx, amp, len); + /* Check if an ANSam or ANSam/ tone has been detected */ + if ((tone = modem_connect_tones_rx_get(&s->ansam_rx)) != MODEM_CONNECT_TONES_NONE) + { + handle_modem_connect_tone(s, tone); + break; + } + if ((s->ci_timer -= len) <= 0) + { + if (++s->ci_count >= 10) + { + /* The spec says we should give up now. */ + span_log(&s->logging, SPAN_LOG_FLOW, "Timeout waiting for modem connect tone\n"); + s->state = V8_PARKED; + if (s->result_handler) + s->result_handler(s->result_handler_user_data, NULL); + } + else + { + /* Try again */ + fsk_tx_restart(&s->v21tx, &preset_fsk_specs[FSK_V21CH1]); + v8_send_ci(s); + s->state = V8_CI_ON; + s->fsk_tx_on = TRUE; + } + } + break; + case V8_HEARD_ANSAM: + /* We have heard the ANSam or ANSam/ signal, but we still need to wait for the + end of the Te timeout period to comply with the spec. */ + if ((s->ci_timer -= len) <= 0) + { + v8_decode_init(s); + s->negotiation_timer = ms_to_samples(5000); + fsk_tx_restart(&s->v21tx, &preset_fsk_specs[FSK_V21CH1]); + send_cm_jm(s); + s->state = V8_CM_ON; + s->fsk_tx_on = TRUE; + } + break; + case V8_CM_ON: + residual_samples = fsk_rx(&s->v21rx, amp, len); + if (s->got_cm_jm) + { + span_log(&s->logging, SPAN_LOG_FLOW, "JM recognised\n"); + /* Now JM has been detected, we send CJ and wait for 75 ms + before finishing the V.8 analysis. */ + fsk_tx_restart(&s->v21tx, &preset_fsk_specs[FSK_V21CH1]); + for (i = 0; i < 3; i++) + v8_put_byte(s, 0); + s->state = V8_CJ_ON; + s->fsk_tx_on = TRUE; + break; + } + if ((s->negotiation_timer -= len) <= 0) + { + /* Timeout */ + span_log(&s->logging, SPAN_LOG_FLOW, "Timeout waiting for JM\n"); + s->state = V8_PARKED; + if (s->result_handler) + s->result_handler(s->result_handler_user_data, NULL); + } + if (queue_contents(s->tx_queue) < 10) + { + /* Send CM again */ + send_cm_jm(s); + } + break; + case V8_CJ_ON: + residual_samples = fsk_rx(&s->v21rx, amp, len); + if (queue_empty(s->tx_queue)) + { + s->negotiation_timer = ms_to_samples(75); + s->state = V8_SIGC; + } + break; + case V8_SIGC: + if ((s->negotiation_timer -= len) <= 0) + { + /* The V.8 negotiation has succeeded. */ + span_log(&s->logging, SPAN_LOG_FLOW, "Negotiation succeeded\n"); + s->state = V8_PARKED; + if (s->result_handler) + s->result_handler(s->result_handler_user_data, &s->result); + } + break; + case V8_CM_WAIT: + residual_samples = fsk_rx(&s->v21rx, amp, len); + if (s->got_cm_jm) + { + span_log(&s->logging, SPAN_LOG_FLOW, "CM recognised\n"); + + /* TODO: negotiate if the call function is acceptable */ + + /* Stop sending ANSam or ANSam/ and send JM instead */ + fsk_tx_init(&s->v21tx, &preset_fsk_specs[FSK_V21CH2], get_bit, s); + /* Set the timeout for JM */ + s->negotiation_timer = ms_to_samples(5000); + s->state = V8_JM_ON; + send_cm_jm(s); + s->modem_connect_tone_tx_on = ms_to_samples(75); + s->fsk_tx_on = TRUE; + break; + } + if ((s->negotiation_timer -= len) <= 0) + { + /* Timeout */ + span_log(&s->logging, SPAN_LOG_FLOW, "Timeout waiting for CM\n"); + s->state = V8_PARKED; + if (s->result_handler) + s->result_handler(s->result_handler_user_data, NULL); + } + break; + case V8_JM_ON: + residual_samples = fsk_rx(&s->v21rx, amp, len); + if (s->got_cj) + { + span_log(&s->logging, SPAN_LOG_FLOW, "CJ recognised\n"); + /* Stop sending JM, flushing anything left in the buffer, and wait 75 ms */ + queue_flush(s->tx_queue); + s->negotiation_timer = ms_to_samples(75); + s->state = V8_SIGA; + break; + } + if ((s->negotiation_timer -= len) <= 0) + { + /* Timeout */ + span_log(&s->logging, SPAN_LOG_FLOW, "Timeout waiting for CJ\n"); + s->state = V8_PARKED; + if (s->result_handler) + s->result_handler(s->result_handler_user_data, NULL); + break; + } + if (queue_contents(s->tx_queue) < 10) + { + /* Send JM */ + send_cm_jm(s); + } + break; + case V8_SIGA: + if ((s->negotiation_timer -= len) <= 0) + { + /* The V.8 negotiation has succeeded. */ + span_log(&s->logging, SPAN_LOG_FLOW, "Negotiation succeeded\n"); + s->state = V8_PARKED; + if (s->result_handler) + s->result_handler(s->result_handler_user_data, &s->result); + } + break; + case V8_PARKED: + residual_samples = len; + break; + } + return residual_samples; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(logging_state_t *) v8_get_logging_state(v8_state_t *s) +{ + return &s->logging; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) v8_restart(v8_state_t *s, + int calling_party, + v8_parms_t *parms) +{ + memcpy(&s->parms, parms, sizeof(s->parms)); + memset(&s->result, 0, sizeof(s->result)); + + s->result.call_function = s->parms.call_function; + s->result.nsf = -1; + s->result.t66 = -1; + + s->ci_timer = 0; + if (calling_party) + { + s->calling_party = TRUE; + s->state = V8_WAIT_1S; + s->negotiation_timer = ms_to_samples(1000); + s->ci_count = 0; + modem_connect_tones_rx_init(&s->ansam_rx, MODEM_CONNECT_TONES_ANS_PR, NULL, NULL); + fsk_tx_init(&s->v21tx, &preset_fsk_specs[FSK_V21CH1], get_bit, s); + } + else + { + /* Send the ANSam or ANSam/ tone */ + s->calling_party = FALSE; + modem_connect_tones_tx_init(&s->ansam_tx, s->parms.modem_connect_tone); + + v8_decode_init(s); + s->state = V8_CM_WAIT; + s->negotiation_timer = ms_to_samples(200 + 5000); + s->modem_connect_tone_tx_on = ms_to_samples(75) + 1; + } + s->result.modem_connect_tone = MODEM_CONNECT_TONES_NONE; + + if ((s->tx_queue = queue_init(NULL, 1024, 0)) == NULL) + return -1; + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(v8_state_t *) v8_init(v8_state_t *s, + int calling_party, + v8_parms_t *parms, + v8_result_handler_t *result_handler, + void *user_data) +{ + if (s == NULL) + { + if ((s = (v8_state_t *) malloc(sizeof(*s))) == NULL) + return NULL; + } + memset(s, 0, sizeof(*s)); + span_log_init(&s->logging, SPAN_LOG_NONE, NULL); + span_log_set_protocol(&s->logging, "V.8"); + s->result_handler = result_handler; + s->result_handler_user_data = user_data; + + v8_restart(s, calling_party, parms); + + memcpy(&s->parms, parms, sizeof(s->parms)); + + s->result.call_function = s->parms.call_function; + s->result.nsf = -1; + s->result.t66 = -1; + + s->ci_timer = 0; + if (calling_party) + { + s->calling_party = TRUE; + s->state = V8_WAIT_1S; + s->negotiation_timer = ms_to_samples(1000); + s->ci_count = 0; + modem_connect_tones_rx_init(&s->ansam_rx, MODEM_CONNECT_TONES_ANS_PR, NULL, NULL); + fsk_tx_init(&s->v21tx, &preset_fsk_specs[FSK_V21CH1], get_bit, s); + } + else + { + /* Send the ANSam or ANSam/ tone */ + s->calling_party = FALSE; + modem_connect_tones_tx_init(&s->ansam_tx, s->parms.modem_connect_tone); + + v8_decode_init(s); + s->state = V8_CM_WAIT; + s->negotiation_timer = ms_to_samples(200 + 5000); + s->modem_connect_tone_tx_on = ms_to_samples(75) + 1; + } + s->result.modem_connect_tone = MODEM_CONNECT_TONES_NONE; + + if ((s->tx_queue = queue_init(NULL, 1024, 0)) == NULL) + return NULL; + return s; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) v8_release(v8_state_t *s) +{ + return queue_free(s->tx_queue); +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) v8_free(v8_state_t *s) +{ + int ret; + + ret = queue_free(s->tx_queue); + free(s); + return ret; +} +/*- End of function --------------------------------------------------------*/ +/*- End of file ------------------------------------------------------------*/