diff spandsp-0.0.6pre17/src/t38_non_ecm_buffer.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/t38_non_ecm_buffer.c	Fri Jun 25 15:50:58 2010 +0200
@@ -0,0 +1,380 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t38_non_ecm_buffer.c - A rate adapting buffer for T.38 non-ECM image
+ *                        and TCF data
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005, 2006, 2007, 2008 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: t38_non_ecm_buffer.c,v 1.9.4.1 2009/12/19 06:43:28 steveu Exp $
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.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 "floating_fudge.h"
+#include <assert.h>
+
+#include "spandsp/telephony.h"
+#include "spandsp/logging.h"
+#include "spandsp/queue.h"
+#include "spandsp/dc_restore.h"
+#include "spandsp/bit_operations.h"
+#include "spandsp/async.h"
+#include "spandsp/t38_non_ecm_buffer.h"
+
+#include "spandsp/private/t38_non_ecm_buffer.h"
+
+/* Phases */
+enum
+{
+    TCF_AT_INITIAL_ALL_ONES = 0,
+    TCF_AT_ALL_ZEROS = 1,
+    IMAGE_WAITING_FOR_FIRST_EOL = 2,
+    IMAGE_IN_PROGRESS = 3
+};
+
+static void restart_buffer(t38_non_ecm_buffer_state_t *s)
+{
+    /* This should be called when draining the buffer is complete, which should
+       occur before any fresh data can possibly arrive to begin refilling it. */
+    s->octet = 0xFF;
+    s->flow_control_fill_octet = 0xFF;
+    s->input_phase = (s->image_data_mode)  ?  IMAGE_WAITING_FOR_FIRST_EOL  :  TCF_AT_INITIAL_ALL_ONES;
+    s->bit_stream = 0xFFFF;
+    s->out_ptr = 0;
+    s->in_ptr = 0;
+    s->latest_eol_ptr = 0;
+    s->data_finished = FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE_NONSTD(int) t38_non_ecm_buffer_get_bit(void *user_data)
+{
+    t38_non_ecm_buffer_state_t *s;
+    int bit;
+
+    s = (t38_non_ecm_buffer_state_t *) user_data;
+
+    if (s->bit_no <= 0)
+    {
+        /* We need another byte */
+        if (s->out_ptr != s->latest_eol_ptr)
+        {
+            s->octet = s->data[s->out_ptr];
+            s->out_ptr = (s->out_ptr + 1) & (T38_NON_ECM_TX_BUF_LEN - 1);
+        }
+        else
+        {
+            if (s->data_finished)
+            {
+                /* The queue is empty, and we have received the end of data signal. This must
+                   really be the end to transmission. */
+                restart_buffer(s);
+                return SIG_STATUS_END_OF_DATA;
+            }
+            /* The queue is blocked, but this does not appear to be the end of the data. Idle with
+               fill octets, which should be safe at this point. */
+            s->octet = s->flow_control_fill_octet;
+            s->flow_control_fill_octets++;
+        }
+        s->out_octets++;
+        s->bit_no = 8;
+    }
+    s->bit_no--;
+    bit = (s->octet >> 7) & 1;
+    s->octet <<= 1;
+    return bit;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_non_ecm_buffer_push(t38_non_ecm_buffer_state_t *s)
+{
+    /* Don't flow control the data any more. Just push out the remainder of the data
+       in the buffer as fast as we can, and shut down. */
+    s->latest_eol_ptr = s->in_ptr;
+    s->data_finished = TRUE;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_non_ecm_buffer_inject(t38_non_ecm_buffer_state_t *s, const uint8_t *buf, int len)
+{
+    int i;
+    int upper;
+    int lower;
+
+    /* TCF consists of:
+            - zero or more ones, followed by
+            - about 1.5s of zeros
+       There may be a little junk at the end, as the modem shuts down.
+
+       We can stuff with extra ones in the initial period of all ones, and we can stuff with extra
+       zeros once the zeros start. The thing we need to be wary about is the odd zero bit in the
+       midst of the ones, due to a bit error. */
+
+    /* Non-ECM image data consists of:
+            - zero or more ones, followed by
+            - zero or more zeros, followed by
+            - an EOL (end of line), which marks the start of the image, followed by
+            - a succession of data rows, with an EOL at the end of each, followed by
+            - an RTC (return to control)
+       There may be a little junk at the end, as the modem shuts down.
+
+       An EOL 11 zeros followed by a one in a T.4 1D image or 11 zeros followed by a one followed
+       by a one or a zero in a T.4 2D image. An RTC consists of 6 EOLs in succession, with no
+       pixel data between them.
+    
+       We can stuff with ones until we get the first EOL into our buffer, then we can stuff with
+       zeros in front of each EOL at any point up the the RTC. We should not pad between the EOLs
+       which make up the RTC. Most FAX machines don't care about this, but a few will not recognise
+       the RTC if here is padding between the EOLs.
+    
+       We need to buffer whole rows before we output their beginning, so there is no possibility
+       of underflow mid-row. */
+
+    /* FoIP has latency issues, because of the fairly tight timeouts in the T.30 spec. We must
+       ensure our buffering does everything needed to avoid underflows, and to meet the minimum
+       row length requirements imposed by many mechanical FAX machines. We cannot, however,
+       afford to bulk up the data, by sending superfluous bytes. The resulting loop delay could
+       provoke an erroneous timeout of the acknowledgement signal. */
+
+    i = 0;
+    switch (s->input_phase)
+    {
+    case TCF_AT_INITIAL_ALL_ONES:
+        /* Dump initial 0xFF bytes. We will add enough of our own to makes things flow
+           smoothly. If we don't strip these off, we might end up delaying the start of
+           forwarding by a substantial amount, as we could end up with a large block of 0xFF
+           bytes before the real data begins. This is especially true with PC FAX
+           systems. This test is very simplistic, as bit errors could confuse it. */
+        for (  ;  i < len;  i++)
+        {
+            if (buf[i] != 0xFF)
+            {
+                s->input_phase = TCF_AT_ALL_ZEROS;
+                s->flow_control_fill_octet = 0x00;
+                break;
+            }
+        }
+        /* Fall through */
+    case TCF_AT_ALL_ZEROS:
+        for (  ;  i < len;  i++)
+        {
+            s->data[s->in_ptr] = buf[i];
+            s->latest_eol_ptr = s->in_ptr;
+            /* TODO: We can't buffer overflow, since we wrap around. However, the tail could
+                     overwrite itself if things fall badly behind. */
+            s->in_ptr = (s->in_ptr + 1) & (T38_NON_ECM_TX_BUF_LEN - 1);
+            s->in_octets++;
+        }
+        break;
+    case IMAGE_WAITING_FOR_FIRST_EOL:
+        /* Dump anything up to the first EOL. Let the output side stuff with 0xFF bytes while waiting
+           for that first EOL. What occurs before the first EOL is expected to be a period of all ones
+           and then a period of all zeros. We really don't care what junk might be there. By definition,
+           the image only starts at the first EOL. */
+        for (  ;  i < len;  i++)
+        {
+            if (buf[i])
+            {
+                /* There might be an EOL here. Look for at least 11 zeros, followed by a one, split
+                   between two octets. Between those two octets we can insert numerous zero octets
+                   as a means of flow control. Note that we stuff in blocks of 8 bits, and not at
+                   the minimal level. */
+                /* Or'ing with 0x800 here is to avoid zero words looking like they have -1
+                   trailing zeros */
+                upper = bottom_bit(s->bit_stream | 0x800);
+                lower = top_bit(buf[i]);
+                if ((upper - lower) > (11 - 8))
+                {
+                    /* This is an EOL - our first row is beginning. */
+                    s->input_phase = IMAGE_IN_PROGRESS;
+                    /* Start a new row */
+                    s->row_bits = lower - 8;
+                    s->latest_eol_ptr = s->in_ptr;
+                    s->flow_control_fill_octet = 0x00;
+
+                    /* If we push out two bytes of zero, and our latest non-zero byte
+                       we should definitely form a proper EOL to begin things, with a
+                       few harmless extra zero bits at the front. */
+                    s->data[s->in_ptr] = 0x00;
+                    s->in_ptr = (s->in_ptr + 1) & (T38_NON_ECM_TX_BUF_LEN - 1);
+                    s->data[s->in_ptr] = 0x00;
+                    s->in_ptr = (s->in_ptr + 1) & (T38_NON_ECM_TX_BUF_LEN - 1);
+                    s->data[s->in_ptr] = buf[i];
+                    s->in_ptr = (s->in_ptr + 1) & (T38_NON_ECM_TX_BUF_LEN - 1);
+                    s->in_octets += 3;
+                    s->bit_stream = (s->bit_stream << 8) | buf[i];
+                    i++;
+                    break;
+                }
+            }
+            s->bit_stream = (s->bit_stream << 8) | buf[i];
+        }
+        if (i >= len)
+            break;
+        /* Fall through */
+    case IMAGE_IN_PROGRESS:
+        /* Now we have seen an EOL, we can stuff with zeros just in front of that EOL, or any
+           subsequent EOL that does not immediately follow a previous EOL (i.e. a candidate RTC).
+           We need to track our way through the image data, allowing the output side to only send
+           up to the last EOL. This prevents the possibility of underflow mid-row, where we cannot
+           safely stuff anything in the bit stream. */
+        for (  ;  i < len;  i++)
+        {
+            if (buf[i])
+            {
+                /* There might be an EOL here. Look for at least 11 zeros, followed by a one, split
+                   between two octets. Between those two octets we can insert numerous zero octets
+                   as a means of flow control. Note that we stuff in blocks of 8 bits, and not at
+                   the minimal level. */
+                /* Or'ing with 0x800 here is to avoid zero words looking like they have -1
+                   trailing zeros */
+                upper = bottom_bit(s->bit_stream | 0x800);
+                lower = top_bit(buf[i]);
+                if ((upper - lower) > (11 - 8))
+                {
+                    /* This is an EOL. */
+                    s->row_bits += (8 - lower);
+                    /* Make sure we don't stretch back to back EOLs, as that could spoil the RTC.
+                       This is a slightly crude check, as we don't know if we are processing a T.4 1D
+                       or T.4 2D image. Accepting 12 or 12 bits apart as meaning back to back is fine,
+                       as no 1D image row could be 1 bit long. */
+                    if (s->row_bits < 12  ||  s->row_bits > 13)
+                    {
+                        /* If the row is too short, extend it in chunks of a whole byte. */
+                        /* TODO: extend by the precise amount we should, instead of this
+                                 rough approach. */
+                        while (s->row_bits < s->min_bits_per_row)
+                        {
+                            s->min_row_bits_fill_octets++;
+                            s->data[s->in_ptr] = 0;
+                            s->row_bits += 8;
+                            /* TODO: We can't buffer overflow, since we wrap around. However,
+                                     the tail could overwrite itself if things fall badly behind. */
+                            s->in_ptr = (s->in_ptr + 1) & (T38_NON_ECM_TX_BUF_LEN - 1);
+                        }
+                        /* This is now the limit for the output side, before it starts
+                           stuffing. */
+                        s->latest_eol_ptr = s->in_ptr;
+                    }
+                    /* Start a new row */
+                    s->row_bits = lower - 8;
+                    s->in_rows++;
+                }
+            }
+            s->bit_stream = (s->bit_stream << 8) | buf[i];
+            s->data[s->in_ptr] = buf[i];
+            s->row_bits += 8;
+            /* TODO: We can't buffer overflow, since we wrap around. However, the tail could overwrite
+                     itself if things fall badly behind. */
+            s->in_ptr = (s->in_ptr + 1) & (T38_NON_ECM_TX_BUF_LEN - 1);
+            s->in_octets++;
+        }
+        break;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_non_ecm_buffer_report_input_status(t38_non_ecm_buffer_state_t *s, logging_state_t *logging)
+{
+    if (s->in_octets  ||  s->min_row_bits_fill_octets)
+    {
+        span_log(logging,
+                 SPAN_LOG_FLOW,
+                 "%d+%d incoming non-ECM octets, %d rows.\n",
+                 s->in_octets,
+                 s->min_row_bits_fill_octets,
+                 s->in_rows);
+        s->in_octets = 0;
+        s->in_rows = 0;
+        s->min_row_bits_fill_octets = 0;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_non_ecm_buffer_report_output_status(t38_non_ecm_buffer_state_t *s, logging_state_t *logging)
+{
+    if (s->out_octets  ||  s->flow_control_fill_octets)
+    {
+        span_log(logging,
+                 SPAN_LOG_FLOW,
+                 "%d+%d outgoing non-ECM octets, %d rows.\n",
+                 s->out_octets - s->flow_control_fill_octets,
+                 s->flow_control_fill_octets,
+                 s->out_rows);
+        s->out_octets = 0;
+        s->out_rows = 0;
+        s->flow_control_fill_octets = 0;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_non_ecm_buffer_set_mode(t38_non_ecm_buffer_state_t *s, int mode, int min_bits_per_row)
+{
+    s->image_data_mode = mode;
+    s->min_bits_per_row = min_bits_per_row;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(t38_non_ecm_buffer_state_t *) t38_non_ecm_buffer_init(t38_non_ecm_buffer_state_t *s, int mode, int min_bits_per_row)
+{
+    if (s == NULL)
+    {
+        if ((s = (t38_non_ecm_buffer_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    memset(s, 0, sizeof(*s));
+    s->image_data_mode = mode;
+    s->min_bits_per_row = min_bits_per_row;
+    restart_buffer(s);
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_non_ecm_buffer_release(t38_non_ecm_buffer_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_non_ecm_buffer_free(t38_non_ecm_buffer_state_t *s)
+{
+    if (s)
+        free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

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