view 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 source

/*
 * 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.