diff spandsp-0.0.3/spandsp-0.0.3/src/t38_terminal.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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spandsp-0.0.3/spandsp-0.0.3/src/t38_terminal.c	Fri Jun 25 16:00:21 2010 +0200
@@ -0,0 +1,722 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t38_terminal.c - An implementation of a T.38 terminal, less the packet exchange part
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005, 2006 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: t38_terminal.c,v 1.50 2006/12/08 12:47:29 steveu Exp $
+ */
+
+/*! \file */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <time.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 <tiffio.h>
+
+#include "spandsp/telephony.h"
+#include "spandsp/logging.h"
+#include "spandsp/bit_operations.h"
+#include "spandsp/queue.h"
+#include "spandsp/power_meter.h"
+#include "spandsp/complex.h"
+#include "spandsp/tone_generate.h"
+#include "spandsp/async.h"
+#include "spandsp/hdlc.h"
+#include "spandsp/fsk.h"
+#include "spandsp/v29rx.h"
+#include "spandsp/v29tx.h"
+#include "spandsp/v27ter_rx.h"
+#include "spandsp/v27ter_tx.h"
+#if defined(ENABLE_V17)
+#include "spandsp/v17rx.h"
+#include "spandsp/v17tx.h"
+#endif
+#include "spandsp/t4.h"
+
+#include "spandsp/t30_fcf.h"
+#include "spandsp/t35.h"
+#include "spandsp/t30.h"
+
+#include "spandsp/t38_core.h"
+#include "spandsp/t38_terminal.h"
+
+#define MS_PER_TX_CHUNK             30
+
+#define INDICATOR_TX_COUNT          3
+/* Backstop timeout if reception of packets stops in the middle of a burst */
+#define MID_RX_TIMEOUT              15000
+
+enum
+{
+    T38_TIMED_STEP_NONE = 0,
+    T38_TIMED_STEP_NON_ECM_MODEM,
+    T38_TIMED_STEP_NON_ECM_MODEM_2,
+    T38_TIMED_STEP_NON_ECM_MODEM_3,
+    T38_TIMED_STEP_HDLC_MODEM,
+    T38_TIMED_STEP_HDLC_MODEM_2,
+    T38_TIMED_STEP_HDLC_MODEM_3,
+    T38_TIMED_STEP_CED,
+    T38_TIMED_STEP_CNG,
+    T38_TIMED_STEP_PAUSE
+};
+
+static int get_non_ecm_image_chunk(t38_terminal_state_t *s, uint8_t *buf, int len)
+{
+    int i;
+    int j;
+    int bit;
+    int byte;
+
+    for (i = 0;  i < len;  i++)
+    {
+        byte = 0;
+        for (j = 0;  j < 8;  j++)
+        {
+            if ((bit = t30_non_ecm_get_bit(&s->t30_state)) == PUTBIT_END_OF_DATA)
+            {
+                if (j > 0)
+                {
+                    byte <<= (8 - j);
+                    buf[i++] = (uint8_t) byte;
+                }
+                return -i;
+            }
+            byte = (byte << 1) | (bit & 0x01);
+        }
+        buf[i] = (uint8_t) byte;
+    }
+    return i;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int process_rx_missing(t38_core_state_t *s, void *user_data, int rx_seq_no, int expected_seq_no)
+{
+    t38_terminal_state_t *t;
+    
+    t = (t38_terminal_state_t *) user_data;
+    t->missing_data = TRUE;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int process_rx_indicator(t38_core_state_t *s, void *user_data, int indicator)
+{
+    t38_terminal_state_t *t;
+    
+    t = (t38_terminal_state_t *) user_data;
+    /* In termination mode we don't care very much about indicators telling us training
+       is starting. We only care about the actual data. */
+    switch (indicator)
+    {
+    case T38_IND_NO_SIGNAL:
+        if (t->t38.current_rx_indicator == T38_IND_V21_PREAMBLE
+            &&
+            (t->current_rx_type == T30_MODEM_V21  ||  t->current_rx_type == T30_MODEM_CNG))
+        {
+            t30_hdlc_accept(&(t->t30_state), TRUE, NULL, PUTBIT_CARRIER_DOWN);
+        }
+        t->timeout_rx_samples = 0;
+        break;
+    case T38_IND_CNG:
+        break;
+    case T38_IND_CED:
+        break;
+    case T38_IND_V21_PREAMBLE:
+        if (t->current_rx_type == T30_MODEM_V21)
+        {
+            t30_hdlc_accept(&(t->t30_state), TRUE, NULL, PUTBIT_CARRIER_UP);
+            t30_hdlc_accept(&(t->t30_state), TRUE, NULL, PUTBIT_FRAMING_OK);
+        }
+        break;
+    case T38_IND_V27TER_2400_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V27TER_4800_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V29_7200_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V29_9600_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V17_7200_SHORT_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V17_7200_LONG_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V17_9600_SHORT_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V17_9600_LONG_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V17_12000_SHORT_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V17_12000_LONG_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V17_14400_SHORT_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V17_14400_LONG_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V8_ANSAM:
+        break;
+    case T38_IND_V8_SIGNAL:
+        break;
+    case T38_IND_V34_CNTL_CHANNEL_1200:
+        break;
+    case T38_IND_V34_PRI_CHANNEL:
+        break;
+    case T38_IND_V34_CC_RETRAIN:
+        break;
+    case T38_IND_V33_12000_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_IND_V33_14400_TRAINING:
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    default:
+        break;
+    }
+    t->tx_out_bytes = 0;
+    t->missing_data = FALSE;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int process_rx_data(t38_core_state_t *s, void *user_data, int data_type, int field_type, const uint8_t *buf, int len)
+{
+    int i;
+    t38_terminal_state_t *t;
+    
+    t = (t38_terminal_state_t *) user_data;
+#if 0
+    /* In termination mode we don't care very much what the data type is. */
+    switch (data_type)
+    {
+    case T38_DATA_V21:
+    case T38_DATA_V27TER_2400:
+    case T38_DATA_V27TER_4800:
+    case T38_DATA_V29_7200:
+    case T38_DATA_V29_9600:
+    case T38_DATA_V17_7200:
+    case T38_DATA_V17_9600:
+    case T38_DATA_V17_12000:
+    case T38_DATA_V17_14400:
+    case T38_DATA_V8:
+    case T38_DATA_V34_PRI_RATE:
+    case T38_DATA_V34_CC_1200:
+    case T38_DATA_V34_PRI_CH:
+    case T38_DATA_V33_12000:
+    case T38_DATA_V33_14400:
+    default:
+        break;
+    }
+#endif
+
+    switch (field_type)
+    {
+    case T38_FIELD_HDLC_DATA:
+        if (t->tx_out_bytes + len <= T38_MAX_HDLC_LEN)
+        {
+            for (i = 0;  i < len;  i++)
+                t->tx_data[t->tx_out_bytes++] = bit_reverse8(buf[i]);
+        }
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_FIELD_HDLC_FCS_OK:
+        if (len > 0)
+        {
+            span_log(&t->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_FCS_OK!\n");
+            /* The sender has incorrectly included data in this message. It is unclear what we should do
+               with it, to maximise tolerance of buggy implementations. */
+        }
+        span_log(&t->logging, SPAN_LOG_FLOW, "Type %s - CRC OK (%s)\n", (t->tx_out_bytes >= 3)  ?  t30_frametype(t->tx_data[2])  :  "???", (t->missing_data)  ?  "missing octets"  :  "clean");
+        /* Don't deal with zero length frames. Some T.38 implementations send multiple T38_FIELD_HDLC_FCS_OK
+           packets, when they have sent no data for the body of the frame. */
+        if (t->tx_out_bytes > 0)
+            t30_hdlc_accept(&(t->t30_state), !t->missing_data, t->tx_data, t->tx_out_bytes);
+        t->tx_out_bytes = 0;
+        t->missing_data = FALSE;
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_FIELD_HDLC_FCS_BAD:
+        if (len > 0)
+        {
+            span_log(&t->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_FCS_BAD!\n");
+            /* The sender has incorrectly included data in this message. We can safely ignore it, as the
+               bad FCS means we will throw away the whole message, anyway. */
+        }
+        span_log(&t->logging, SPAN_LOG_FLOW, "Type %s - CRC bad (%s)\n", (t->tx_out_bytes >= 3)  ?  t30_frametype(t->tx_data[2])  :  "???", (t->missing_data)  ?  "missing octets"  :  "clean");
+        if (t->tx_out_bytes > 0)
+            t30_hdlc_accept(&(t->t30_state), FALSE, t->tx_data, t->tx_out_bytes);
+        t->tx_out_bytes = 0;
+        t->missing_data = FALSE;
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_FIELD_HDLC_FCS_OK_SIG_END:
+        if (len > 0)
+        {
+            span_log(&t->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_FCS_OK_SIG_END!\n");
+            /* The sender has incorrectly included data in this message. It is unclear what we should do
+               with it, to maximise tolerance of buggy implementations. */
+        }
+        span_log(&t->logging, SPAN_LOG_FLOW, "Type %s - CRC OK, sig end (%s)\n", (t->tx_out_bytes >= 3)  ?  t30_frametype(t->tx_data[2])  :  "???", (t->missing_data)  ?  "missing octets"  :  "clean");
+        /* Don't deal with zero length frames. Some T.38 implementations send multiple T38_FIELD_HDLC_FCS_OK
+           packets, when they have sent no data for the body of the frame. */
+        if (t->tx_out_bytes > 0)
+            t30_hdlc_accept(&(t->t30_state), !t->missing_data, t->tx_data, t->tx_out_bytes);
+        t30_hdlc_accept(&(t->t30_state), TRUE, NULL, PUTBIT_CARRIER_DOWN);
+        t->tx_out_bytes = 0;
+        t->missing_data = FALSE;
+        t->timeout_rx_samples = 0;
+        break;
+    case T38_FIELD_HDLC_FCS_BAD_SIG_END:
+        if (len > 0)
+        {
+            span_log(&t->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_FCS_BAD_SIG_END!\n");
+            /* The sender has incorrectly included data in this message. We can safely ignore it, as the
+               bad FCS means we will throw away the whole message, anyway. */
+        }
+        span_log(&t->logging, SPAN_LOG_FLOW, "Type %s - CRC bad, sig end (%s)\n", (t->tx_out_bytes >= 3)  ?  t30_frametype(t->tx_data[2])  :  "???", (t->missing_data)  ?  "missing octets"  :  "clean");
+        if (t->tx_out_bytes > 0)
+            t30_hdlc_accept(&(t->t30_state), FALSE, t->tx_data, t->tx_out_bytes);
+        t30_hdlc_accept(&(t->t30_state), TRUE, NULL, PUTBIT_CARRIER_DOWN);
+        t->tx_out_bytes = 0;
+        t->missing_data = FALSE;
+        t->timeout_rx_samples = 0;
+        break;
+    case T38_FIELD_HDLC_SIG_END:
+        if (len > 0)
+        {
+            span_log(&t->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_SIG_END!\n");
+            /* The sender has incorrectly included data in this message, but there seems nothing meaningful
+               it could be. There could not be an FCS good/bad report beyond this. */
+        }
+        /* This message is expected under 2 circumstances. One is as an alternative to T38_FIELD_HDLC_FCS_OK_SIG_END - 
+           i.e. they send T38_FIELD_HDLC_FCS_OK, and then T38_FIELD_HDLC_SIG_END when the carrier actually drops.
+           The other is because the HDLC signal drops unexpectedly - i.e. not just after a final frame. */
+        /* WORKAROUND: At least some Mediatrix boxes have a bug, where they can send this message at the
+                       end of non-ECM data. We need to tolerate this. We use the generic receive complete
+                       indication, rather than the specific HDLC carrier down. */
+        t->tx_out_bytes = 0;
+        t->missing_data = FALSE;
+        t->timeout_rx_samples = 0;
+        t30_receive_complete(&(t->t30_state));
+        break;
+    case T38_FIELD_T4_NON_ECM_DATA:
+        if (!t->rx_signal_present)
+        {
+            t30_non_ecm_put_bit(&(t->t30_state), PUTBIT_TRAINING_SUCCEEDED);
+            t->rx_signal_present = TRUE;
+        }
+        for (i = 0;  i < len;  i++)
+            t30_non_ecm_putbyte(&(t->t30_state), buf[i]);
+        t->timeout_rx_samples = t->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_FIELD_T4_NON_ECM_SIG_END:
+        if (len > 0)
+        {
+            if (!t->rx_signal_present)
+            {
+                t30_non_ecm_put_bit(&(t->t30_state), PUTBIT_TRAINING_SUCCEEDED);
+                t->rx_signal_present = TRUE;
+            }
+            for (i = 0;  i < len;  i++)
+                t30_non_ecm_putbyte(&(t->t30_state), buf[i]);
+        }
+        /* WORKAROUND: At least some Mediatrix boxes have a bug, where they can send HDLC signal end where
+                       they should send non-ECM signal end. It is possible they also do the opposite.
+                       We need to tolerate this, so we use the generic receive complete
+                       indication, rather than the specific non-ECM carrier down. */
+        t30_receive_complete(&(t->t30_state));
+        t->rx_signal_present = FALSE;
+        t->timeout_rx_samples = 0;
+        break;
+    case T38_FIELD_CM_MESSAGE:
+    case T38_FIELD_JM_MESSAGE:
+    case T38_FIELD_CI_MESSAGE:
+    case T38_FIELD_V34RATE:
+    default:
+        break;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void send_hdlc(void *user_data, const uint8_t *msg, int len)
+{
+    t38_terminal_state_t *s;
+    int j;
+
+    s = (t38_terminal_state_t *) user_data;
+    if (len <= 0)
+    {
+        s->hdlc_tx_len = -1;
+    }
+    else
+    {
+        for (j = 0;  j < len;  j++)
+            s->hdlc_tx_buf[j] = bit_reverse8(msg[j]);
+        s->hdlc_tx_len = len;
+        s->hdlc_tx_ptr = 0;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+int t38_terminal_send_timeout(t38_terminal_state_t *s, int samples)
+{
+    int len;
+    int i;
+    int previous;
+    uint8_t buf[100];
+    /* Training times for all the modem options, with and without TEP */
+    static const int training_time[] =
+    {
+           0,      0,   /* T38_IND_NO_SIGNAL */
+           0,      0,   /* T38_IND_CNG */
+           0,      0,   /* T38_IND_CED */
+        1000,   1000,   /* T38_IND_V21_PREAMBLE */
+         943,   1158,   /* T38_IND_V27TER_2400_TRAINING */
+         708,    923,   /* T38_IND_V27TER_4800_TRAINING */
+         234,    454,   /* T38_IND_V29_7200_TRAINING */
+         234,    454,   /* T38_IND_V29_9600_TRAINING */
+         142,    367,   /* T38_IND_V17_7200_SHORT_TRAINING */
+        1393,   1618,   /* T38_IND_V17_7200_LONG_TRAINING */
+         142,    367,   /* T38_IND_V17_9600_SHORT_TRAINING */
+        1393,   1618,   /* T38_IND_V17_9600_LONG_TRAINING */
+         142,    367,   /* T38_IND_V17_12000_SHORT_TRAINING */
+        1393,   1618,   /* T38_IND_V17_12000_LONG_TRAINING */
+         142,    367,   /* T38_IND_V17_14400_SHORT_TRAINING */
+        1393,   1618,   /* T38_IND_V17_14400_LONG_TRAINING */
+           0,      0,   /* T38_IND_V8_ANSAM */
+           0,      0,   /* T38_IND_V8_SIGNAL */
+           0,      0,   /* T38_IND_V34_CNTL_CHANNEL_1200 */
+           0,      0,   /* T38_IND_V34_PRI_CHANNEL */
+           0,      0,   /* T38_IND_V34_CC_RETRAIN */
+           0,      0,   /* T38_IND_V33_12000_TRAINING */
+           0,      0,   /* T38_IND_V33_14400_TRAINING */
+    };
+
+    if (s->current_rx_type == T30_MODEM_DONE  ||  s->current_tx_type == T30_MODEM_DONE)
+        return TRUE;
+
+    s->samples += samples;
+    t30_timer_update(&s->t30_state, samples);
+    if (s->timeout_rx_samples  &&  s->samples > s->timeout_rx_samples)
+    {
+        span_log(&s->logging, SPAN_LOG_FLOW, "Timeout mid-receive\n");
+        s->timeout_rx_samples = 0;
+        t30_receive_complete(&(s->t30_state));
+    }
+    if (s->timed_step == T38_TIMED_STEP_NONE)
+        return FALSE;
+    if (s->samples < s->next_tx_samples)
+        return FALSE;
+    /* Its time to send something */
+    switch (s->timed_step)
+    {
+    case T38_TIMED_STEP_NON_ECM_MODEM:
+        /* Create a 75ms silence */
+        if (s->t38.current_tx_indicator != T38_IND_NO_SIGNAL)
+            t38_core_send_indicator(&s->t38, T38_IND_NO_SIGNAL, INDICATOR_TX_COUNT);
+        s->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_2;
+        s->next_tx_samples += ms_to_samples(75);
+        break;
+    case T38_TIMED_STEP_NON_ECM_MODEM_2:
+        /* Switch on a fast modem, and give the training time to complete */
+        t38_core_send_indicator(&s->t38, s->next_tx_indicator, INDICATOR_TX_COUNT);
+        s->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_3;
+        s->next_tx_samples += ms_to_samples(training_time[s->next_tx_indicator << 1]);
+        break;
+    case T38_TIMED_STEP_NON_ECM_MODEM_3:
+        /* Send a chunk of non-ECM image data */
+        /* T.38 says it is OK to send the last of the non-ECM data in the signal end message.
+           However, I think the early versions of T.38 said the signal end message should not
+           contain data. Hopefully, following the current spec will not cause compatibility
+           issues. */
+        if ((len = get_non_ecm_image_chunk(s, buf, s->octets_per_data_packet)) > 0)
+        {
+            s->next_tx_samples += ms_to_samples(MS_PER_TX_CHUNK);
+            t38_core_send_data(&s->t38, s->current_tx_data_type, T38_FIELD_T4_NON_ECM_DATA, buf, len);
+        }
+        else
+        {
+            t38_core_send_data(&s->t38, s->current_tx_data_type, T38_FIELD_T4_NON_ECM_SIG_END, buf, -len);
+            /* This should not be needed, since the message above indicates the end of the signal, but it
+               seems like it can improve compatibility with quirky implementations. */
+            t38_core_send_indicator(&s->t38, T38_IND_NO_SIGNAL, INDICATOR_TX_COUNT);
+            s->timed_step = T38_TIMED_STEP_NONE;
+            t30_send_complete(&(s->t30_state));
+        }
+        break;
+    case T38_TIMED_STEP_HDLC_MODEM:
+        /* Send HDLC preambling */
+        t38_core_send_indicator(&s->t38, s->next_tx_indicator, INDICATOR_TX_COUNT);
+        s->next_tx_samples += ms_to_samples(training_time[s->next_tx_indicator << 1]);
+        s->timed_step = T38_TIMED_STEP_HDLC_MODEM_2;
+        break;
+    case T38_TIMED_STEP_HDLC_MODEM_2:
+        /* Send a chunk of HDLC data */
+        i = s->octets_per_data_packet;
+        if (i >= (s->hdlc_tx_len - s->hdlc_tx_ptr))
+        {
+            i = s->hdlc_tx_len - s->hdlc_tx_ptr;
+            s->timed_step = T38_TIMED_STEP_HDLC_MODEM_3;
+        }
+        t38_core_send_data(&s->t38, s->current_tx_data_type, T38_FIELD_HDLC_DATA, &s->hdlc_tx_buf[s->hdlc_tx_ptr], i);
+        s->hdlc_tx_ptr += i;
+        s->next_tx_samples += ms_to_samples(MS_PER_TX_CHUNK);
+        break;
+    case T38_TIMED_STEP_HDLC_MODEM_3:
+        /* End of HDLC frame */
+        previous = s->current_tx_data_type;
+        s->hdlc_tx_ptr = 0;
+        s->hdlc_tx_len = 0;
+        t30_send_complete(&s->t30_state);
+        if (s->hdlc_tx_len < 0)
+        {
+            t38_core_send_data(&s->t38, previous, T38_FIELD_HDLC_FCS_OK_SIG_END, NULL, 0);
+            /* We have already sent T38_FIELD_HDLC_FCS_OK_SIG_END. It seems some boxes may not like
+               us sending a T38_FIELD_HDLC_SIG_END at this point. Just say there is no signal. */
+            t38_core_send_indicator(&s->t38, T38_IND_NO_SIGNAL, INDICATOR_TX_COUNT);
+            s->hdlc_tx_len = 0;
+            t30_send_complete(&s->t30_state);
+            if (s->hdlc_tx_len)
+                s->timed_step = T38_TIMED_STEP_HDLC_MODEM;
+        }
+        else
+        {
+            t38_core_send_data(&s->t38, previous, T38_FIELD_HDLC_FCS_OK, NULL, 0);
+            if (s->hdlc_tx_len)
+                s->timed_step = T38_TIMED_STEP_HDLC_MODEM_2;
+        }
+        s->next_tx_samples += ms_to_samples(MS_PER_TX_CHUNK);
+        break;
+    case T38_TIMED_STEP_CED:
+        /* Initial 200ms delay over. Send the CED indicator */
+        s->next_tx_samples = s->samples + ms_to_samples(3000);
+        s->timed_step = T38_TIMED_STEP_PAUSE;
+        t38_core_send_indicator(&s->t38, T38_IND_CED, INDICATOR_TX_COUNT);
+        s->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T38_TIMED_STEP_CNG:
+        /* Initial short delay over. Send the CNG indicator */
+        s->timed_step = T38_TIMED_STEP_NONE;
+        t38_core_send_indicator(&s->t38, T38_IND_CNG, INDICATOR_TX_COUNT);
+        s->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T38_TIMED_STEP_PAUSE:
+        /* End of timed pause */
+        s->timed_step = T38_TIMED_STEP_NONE;
+        t30_send_complete(&s->t30_state);
+        break;
+    }
+    return FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void set_rx_type(void *user_data, int type, int short_train, int use_hdlc)
+{
+    t38_terminal_state_t *s;
+
+    s = (t38_terminal_state_t *) user_data;
+    span_log(&s->logging, SPAN_LOG_FLOW, "Set rx type %d\n", type);
+    s->current_rx_type = type;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void set_tx_type(void *user_data, int type, int short_train, int use_hdlc)
+{
+    t38_terminal_state_t *s;
+
+    s = (t38_terminal_state_t *) user_data;
+    span_log(&s->logging, SPAN_LOG_FLOW, "Set tx type %d\n", type);
+    if (s->current_tx_type == type)
+        return;
+
+    switch (type)
+    {
+    case T30_MODEM_NONE:
+        s->timed_step = T38_TIMED_STEP_NONE;
+        s->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T30_MODEM_PAUSE:
+        s->next_tx_samples = s->samples + ms_to_samples(short_train);
+        s->timed_step = T38_TIMED_STEP_PAUSE;
+        s->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T30_MODEM_CED:
+        /* A 200ms initial delay is specified. Delay this amount before the CED indicator is sent. */
+        s->next_tx_samples = s->samples + ms_to_samples(200);
+        s->timed_step = T38_TIMED_STEP_CED;
+        s->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T30_MODEM_CNG:
+        /* Allow a short initial delay, so the chances of the other end actually being ready to receive
+           the CNG indicator are improved. */
+        s->next_tx_samples = s->samples + ms_to_samples(200);
+        s->timed_step = T38_TIMED_STEP_CNG;
+        s->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T30_MODEM_V21:
+        if (s->current_tx_type > T30_MODEM_V21)
+        {
+            /* Pause before switching from phase C, as per T.30. If we omit this, the receiver
+               might not see the carrier fall between the high speed and low speed sections. */
+            s->next_tx_samples = s->samples + ms_to_samples(75);
+        }
+        else
+        {
+            s->next_tx_samples = s->samples;
+        }
+        s->octets_per_data_packet = MS_PER_TX_CHUNK*300/(8*1000);
+        s->next_tx_indicator = T38_IND_V21_PREAMBLE;
+        s->current_tx_data_type = T38_DATA_V21;
+        s->timed_step = (use_hdlc)  ?  T38_TIMED_STEP_HDLC_MODEM  :  T38_TIMED_STEP_NON_ECM_MODEM;
+        break;
+    case T30_MODEM_V27TER_2400:
+        s->octets_per_data_packet = MS_PER_TX_CHUNK*2400/(8*1000);
+        s->next_tx_indicator = T38_IND_V27TER_2400_TRAINING;
+        s->current_tx_data_type = T38_DATA_V27TER_2400;
+        s->next_tx_samples = s->samples + ms_to_samples(MS_PER_TX_CHUNK);
+        s->timed_step = (use_hdlc)  ?  T38_TIMED_STEP_HDLC_MODEM  :  T38_TIMED_STEP_NON_ECM_MODEM;
+        break;
+    case T30_MODEM_V27TER_4800:
+        s->octets_per_data_packet = MS_PER_TX_CHUNK*4800/(8*1000);
+        s->next_tx_indicator = T38_IND_V27TER_4800_TRAINING;
+        s->current_tx_data_type = T38_DATA_V27TER_4800;
+        s->next_tx_samples = s->samples + ms_to_samples(MS_PER_TX_CHUNK);
+        s->timed_step = (use_hdlc)  ?  T38_TIMED_STEP_HDLC_MODEM  :  T38_TIMED_STEP_NON_ECM_MODEM;
+        break;
+    case T30_MODEM_V29_7200:
+        s->octets_per_data_packet = MS_PER_TX_CHUNK*7200/(8*1000);
+        s->next_tx_indicator = T38_IND_V29_7200_TRAINING;
+        s->current_tx_data_type = T38_DATA_V29_7200;
+        s->next_tx_samples = s->samples + ms_to_samples(MS_PER_TX_CHUNK);
+        s->timed_step = (use_hdlc)  ?  T38_TIMED_STEP_HDLC_MODEM  :  T38_TIMED_STEP_NON_ECM_MODEM;
+        break;
+    case T30_MODEM_V29_9600:
+        s->octets_per_data_packet = MS_PER_TX_CHUNK*9600/(8*1000);
+        s->next_tx_indicator = T38_IND_V29_9600_TRAINING;
+        s->current_tx_data_type = T38_DATA_V29_9600;
+        s->next_tx_samples = s->samples + ms_to_samples(MS_PER_TX_CHUNK);
+        s->timed_step = (use_hdlc)  ?  T38_TIMED_STEP_HDLC_MODEM  :  T38_TIMED_STEP_NON_ECM_MODEM;
+        break;
+    case T30_MODEM_V17_7200:
+        s->octets_per_data_packet = MS_PER_TX_CHUNK*7200/(8*1000);
+        s->next_tx_indicator = (short_train)  ?  T38_IND_V17_7200_SHORT_TRAINING  :  T38_IND_V17_7200_LONG_TRAINING;
+        s->current_tx_data_type = T38_DATA_V17_7200;
+        s->next_tx_samples = s->samples + ms_to_samples(MS_PER_TX_CHUNK);
+        s->timed_step = (use_hdlc)  ?  T38_TIMED_STEP_HDLC_MODEM  :  T38_TIMED_STEP_NON_ECM_MODEM;
+        break;
+    case T30_MODEM_V17_9600:
+        s->octets_per_data_packet = MS_PER_TX_CHUNK*9600/(8*1000);
+        s->next_tx_indicator = (short_train)  ?  T38_IND_V17_9600_SHORT_TRAINING  :  T38_IND_V17_9600_LONG_TRAINING;
+        s->current_tx_data_type = T38_DATA_V17_9600;
+        s->next_tx_samples = s->samples + ms_to_samples(MS_PER_TX_CHUNK);
+        s->timed_step = (use_hdlc)  ?  T38_TIMED_STEP_HDLC_MODEM  :  T38_TIMED_STEP_NON_ECM_MODEM;
+        break;
+    case T30_MODEM_V17_12000:
+        s->octets_per_data_packet = MS_PER_TX_CHUNK*12000/(8*1000);
+        s->next_tx_indicator = (short_train)  ?  T38_IND_V17_12000_SHORT_TRAINING  :  T38_IND_V17_12000_LONG_TRAINING;
+        s->current_tx_data_type = T38_DATA_V17_12000;
+        s->next_tx_samples = s->samples + ms_to_samples(MS_PER_TX_CHUNK);
+        s->timed_step = (use_hdlc)  ?  T38_TIMED_STEP_HDLC_MODEM  :  T38_TIMED_STEP_NON_ECM_MODEM;
+        break;
+    case T30_MODEM_V17_14400:
+        s->octets_per_data_packet = MS_PER_TX_CHUNK*14400/(8*1000);
+        s->next_tx_indicator = (short_train)  ?  T38_IND_V17_14400_SHORT_TRAINING  :  T38_IND_V17_14400_LONG_TRAINING;
+        s->current_tx_data_type = T38_DATA_V17_14400;
+        s->next_tx_samples = s->samples + ms_to_samples(MS_PER_TX_CHUNK);
+        s->timed_step = (use_hdlc)  ?  T38_TIMED_STEP_HDLC_MODEM  :  T38_TIMED_STEP_NON_ECM_MODEM;
+        break;
+    case T30_MODEM_DONE:
+        span_log(&s->logging, SPAN_LOG_FLOW, "FAX exchange complete\n");
+        s->timed_step = T38_TIMED_STEP_NONE;
+        s->current_tx_data_type = T38_DATA_NONE;
+        break;
+    }
+    s->current_tx_type = type;
+}
+/*- End of function --------------------------------------------------------*/
+
+t38_terminal_state_t *t38_terminal_init(t38_terminal_state_t *s,
+                                        int calling_party,
+                                        t38_tx_packet_handler_t *tx_packet_handler,
+                                        void *tx_packet_user_data)
+{
+    if (tx_packet_handler == NULL)
+        return NULL;
+
+    memset(s, 0, sizeof(*s));
+    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
+    span_log_set_protocol(&s->logging, "T.38T");
+    s->rx_signal_present = FALSE;
+
+    s->timed_step = T38_TIMED_STEP_NONE;
+    s->hdlc_tx_ptr = 0;
+
+    t38_core_init(&s->t38, process_rx_indicator, process_rx_data, process_rx_missing, (void *) s);
+    s->t38.iaf = TRUE;
+    s->t38.tx_packet_handler = tx_packet_handler;
+    s->t38.tx_packet_user_data = tx_packet_user_data;
+    s->t38.fastest_image_data_rate = 14400;
+
+    s->timed_step = T38_TIMED_STEP_NONE;
+    s->current_tx_data_type = T38_DATA_NONE;
+    s->next_tx_samples = 0;
+
+    t30_init(&(s->t30_state),
+             calling_party,
+             set_rx_type,
+             (void *) s,
+             set_tx_type,
+             (void *) s,
+             send_hdlc,
+             (void *) s);
+    t30_set_supported_modems(&(s->t30_state),
+                             T30_SUPPORT_V27TER | T30_SUPPORT_V29 | T30_SUPPORT_V17);
+    t30_restart(&s->t30_state);
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

Repositories maintained by Peter Meerwald, pmeerw@pmeerw.net.