diff options
author | mnicholson <mnicholson@f38db490-d61c-443f-a65b-d21fe96a405b> | 2010-03-02 23:11:06 +0000 |
---|---|---|
committer | mnicholson <mnicholson@f38db490-d61c-443f-a65b-d21fe96a405b> | 2010-03-02 23:11:06 +0000 |
commit | bc9bd7bb7c38d2ec2bd0b0814d19473e1615b3b5 (patch) | |
tree | 86bf423e9c685e5cf2fc85851455381bf94a112a /res/res_fax_spandsp.c | |
parent | dee9dac842e903fe47181704062e03ce066e939c (diff) |
Merge res_fax and res_fax_spandsp.
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@250190 f38db490-d61c-443f-a65b-d21fe96a405b
Diffstat (limited to 'res/res_fax_spandsp.c')
-rw-r--r-- | res/res_fax_spandsp.c | 762 |
1 files changed, 762 insertions, 0 deletions
diff --git a/res/res_fax_spandsp.c b/res/res_fax_spandsp.c new file mode 100644 index 000000000..630cccb3b --- /dev/null +++ b/res/res_fax_spandsp.c @@ -0,0 +1,762 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009-2010, Digium, Inc. + * + * Matthew Nicholson <mnicholson@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Spandsp T.38 and G.711 FAX Resource + * + * \author Matthew Nicholson <mnicholson@digium.com> + * + * This module registers the Spandsp FAX technology with the res_fax module. + */ + +/*** MODULEINFO + <depend>spandsp</depend> +***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES +#include <spandsp.h> +#include <spandsp/version.h> + +#include "asterisk/logger.h" +#include "asterisk/module.h" +#include "asterisk/strings.h" +#include "asterisk/cli.h" +#include "asterisk/utils.h" +#include "asterisk/timing.h" +#include "asterisk/astobj2.h" +#include "asterisk/res_fax.h" + +#define SPANDSP_FAX_SAMPLES 160 +#define SPANDSP_FAX_TIMER_RATE 8000 / SPANDSP_FAX_SAMPLES /* 50 ticks per second, 20ms, 160 samples per second */ + +static void *spandsp_fax_new(struct ast_fax_session *s, struct ast_fax_tech_token *token); +static void spandsp_fax_destroy(struct ast_fax_session *s); +static struct ast_frame *spandsp_fax_read(struct ast_fax_session *s); +static int spandsp_fax_write(struct ast_fax_session *s, const struct ast_frame *f); +static int spandsp_fax_start(struct ast_fax_session *s); +static int spandsp_fax_cancel(struct ast_fax_session *s); +static int spandsp_fax_switch_to_t38(struct ast_fax_session *s); + +static char *spandsp_fax_cli_show_capabilities(int fd); +static char *spandsp_fax_cli_show_session(struct ast_fax_session *s, int fd); +static char *spandsp_fax_cli_show_stats(int fd); + +static struct ast_fax_tech spandsp_fax_tech = { + .type = "Spandsp", + .description = "Spandsp FAX Driver", +#if SPANDSP_RELEASE_DATE >= 20090220 + /* spandsp 0.0.6 */ + .version = SPANDSP_RELEASE_DATETIME_STRING, +#else + /* spandsp 0.0.5 + * TODO: maybe we should determine the version better way + */ + .version = "pre-20090220", +#endif + .caps = AST_FAX_TECH_AUDIO | AST_FAX_TECH_T38 | AST_FAX_TECH_SEND | AST_FAX_TECH_RECEIVE, + .new_session = spandsp_fax_new, + .destroy_session = spandsp_fax_destroy, + .read = spandsp_fax_read, + .write = spandsp_fax_write, + .start_session = spandsp_fax_start, + .cancel_session = spandsp_fax_cancel, + .switch_to_t38 = spandsp_fax_switch_to_t38, + .cli_show_capabilities = spandsp_fax_cli_show_capabilities, + .cli_show_session = spandsp_fax_cli_show_session, + .cli_show_stats = spandsp_fax_cli_show_stats, +}; + +struct spandsp_fax_stats { + int success; + int nofax; + int neg_failed; + int failed_to_train; + int rx_protocol_error; + int tx_protocol_error; + int protocol_error; + int retries_exceeded; + int file_error; + int mem_error; + int call_dropped; + int unknown_error; + int switched; +}; + +static struct { + ast_mutex_t lock; + struct spandsp_fax_stats g711; + struct spandsp_fax_stats t38; +} spandsp_global_stats; + +struct spandsp_pvt { + unsigned int ist38:1; + unsigned int isdone:1; + fax_state_t fax_state; + t38_terminal_state_t t38_state; + t30_state_t *t30_state; + t38_core_state_t *t38_core_state; + + struct spandsp_fax_stats *stats; + + struct ast_timer *timer; + AST_LIST_HEAD(frame_queue, ast_frame) read_frames; +}; + +static void session_destroy(struct spandsp_pvt *p); +static int t38_tx_packet_handler(t38_core_state_t *t38_core_state, void *data, const uint8_t *buf, int len, int count); +static void t30_phase_e_handler(t30_state_t *t30_state, void *data, int completion_code); +static void spandsp_log(int level, const char *msg); +static int update_stats(struct spandsp_pvt *p, int completion_code); + +static void set_logging(logging_state_t *state, struct ast_fax_session_details *details); +static void set_local_info(t30_state_t *t30_state, struct ast_fax_session_details *details); +static void set_file(t30_state_t *t30_state, struct ast_fax_session_details *details); +static void set_ecm(t30_state_t *t30_state, struct ast_fax_session_details *details); + +static void session_destroy(struct spandsp_pvt *p) +{ + struct ast_frame *f; + + t30_terminate(p->t30_state); + p->isdone = 1; + + ast_timer_close(p->timer); + + fax_release(&p->fax_state); + t38_terminal_release(&p->t38_state); + + while ((f = AST_LIST_REMOVE_HEAD(&p->read_frames, frame_list))) { + ast_frfree(f); + } +} + +/*! \brief + * + */ +static int t38_tx_packet_handler(t38_core_state_t *t38_core_state, void *data, const uint8_t *buf, int len, int count) +{ + struct spandsp_pvt *p = data; + struct ast_frame fax_frame = { + .frametype = AST_FRAME_MODEM, + .subclass.integer = AST_MODEM_T38, + .src = "res_fax_spandsp_t38", + }; + + struct ast_frame *f = &fax_frame; + + + /* TODO: Asterisk does not provide means of resending the same packet multiple + times so count is ignored at the moment */ + + AST_FRAME_SET_BUFFER(f, buf, 0, len); + + if (!(f = ast_frisolate(f))) { + return -1; + } + + /* no need to lock, this all runs in the same thread */ + AST_LIST_INSERT_TAIL(&p->read_frames, f, frame_list); + + return 0; +} + +static int update_stats(struct spandsp_pvt *p, int completion_code) +{ + switch (completion_code) { + case T30_ERR_OK: + ast_atomic_fetchadd_int(&p->stats->success, 1); + break; + + /* Link problems */ + case T30_ERR_CEDTONE: /*! The CED tone exceeded 5s */ + case T30_ERR_T0_EXPIRED: /*! Timed out waiting for initial communication */ + case T30_ERR_T1_EXPIRED: /*! Timed out waiting for the first message */ + case T30_ERR_T3_EXPIRED: /*! Timed out waiting for procedural interrupt */ + case T30_ERR_HDLC_CARRIER: /*! The HDLC carrier did not stop in a timely manner */ + case T30_ERR_CANNOT_TRAIN: /*! Failed to train with any of the compatible modems */ + ast_atomic_fetchadd_int(&p->stats->failed_to_train, 1); + break; + + case T30_ERR_OPER_INT_FAIL: /*! Operator intervention failed */ + case T30_ERR_INCOMPATIBLE: /*! Far end is not compatible */ + case T30_ERR_RX_INCAPABLE: /*! Far end is not able to receive */ + case T30_ERR_TX_INCAPABLE: /*! Far end is not able to transmit */ + case T30_ERR_NORESSUPPORT: /*! Far end cannot receive at the resolution of the image */ + case T30_ERR_NOSIZESUPPORT: /*! Far end cannot receive at the size of image */ + ast_atomic_fetchadd_int(&p->stats->neg_failed, 1); + break; + + case T30_ERR_UNEXPECTED: /*! Unexpected message received */ + ast_atomic_fetchadd_int(&p->stats->protocol_error, 1); + break; + + /* Phase E status values returned to a transmitter */ + case T30_ERR_TX_BADDCS: /*! Received bad response to DCS or training */ + case T30_ERR_TX_BADPG: /*! Received a DCN from remote after sending a page */ + case T30_ERR_TX_ECMPHD: /*! Invalid ECM response received from receiver */ + case T30_ERR_TX_GOTDCN: /*! Received a DCN while waiting for a DIS */ + case T30_ERR_TX_INVALRSP: /*! Invalid response after sending a page */ + case T30_ERR_TX_NODIS: /*! Received other than DIS while waiting for DIS */ + case T30_ERR_TX_PHBDEAD: /*! Received no response to DCS, training or TCF */ + case T30_ERR_TX_PHDDEAD: /*! No response after sending a page */ + case T30_ERR_TX_T5EXP: /*! Timed out waiting for receiver ready (ECM mode) */ + ast_atomic_fetchadd_int(&p->stats->tx_protocol_error, 1); + break; + + /* Phase E status values returned to a receiver */ + case T30_ERR_RX_ECMPHD: /*! Invalid ECM response received from transmitter */ + case T30_ERR_RX_GOTDCS: /*! DCS received while waiting for DTC */ + case T30_ERR_RX_INVALCMD: /*! Unexpected command after page received */ + case T30_ERR_RX_NOCARRIER: /*! Carrier lost during fax receive */ + case T30_ERR_RX_NOEOL: /*! Timed out while waiting for EOL (end Of line) */ + ast_atomic_fetchadd_int(&p->stats->rx_protocol_error, 1); + break; + case T30_ERR_RX_NOFAX: /*! Timed out while waiting for first line */ + ast_atomic_fetchadd_int(&p->stats->nofax, 1); + break; + case T30_ERR_RX_T2EXPDCN: /*! Timer T2 expired while waiting for DCN */ + case T30_ERR_RX_T2EXPD: /*! Timer T2 expired while waiting for phase D */ + case T30_ERR_RX_T2EXPFAX: /*! Timer T2 expired while waiting for fax page */ + case T30_ERR_RX_T2EXPMPS: /*! Timer T2 expired while waiting for next fax page */ + case T30_ERR_RX_T2EXPRR: /*! Timer T2 expired while waiting for RR command */ + case T30_ERR_RX_T2EXP: /*! Timer T2 expired while waiting for NSS, DCS or MCF */ + case T30_ERR_RX_DCNWHY: /*! Unexpected DCN while waiting for DCS or DIS */ + case T30_ERR_RX_DCNDATA: /*! Unexpected DCN while waiting for image data */ + case T30_ERR_RX_DCNFAX: /*! Unexpected DCN while waiting for EOM, EOP or MPS */ + case T30_ERR_RX_DCNPHD: /*! Unexpected DCN after EOM or MPS sequence */ + case T30_ERR_RX_DCNRRD: /*! Unexpected DCN after RR/RNR sequence */ + case T30_ERR_RX_DCNNORTN: /*! Unexpected DCN after requested retransmission */ + ast_atomic_fetchadd_int(&p->stats->rx_protocol_error, 1); + break; + + /* TIFF file problems */ + case T30_ERR_FILEERROR: /*! TIFF/F file cannot be opened */ + case T30_ERR_NOPAGE: /*! TIFF/F page not found */ + case T30_ERR_BADTIFF: /*! TIFF/F format is not compatible */ + case T30_ERR_BADPAGE: /*! TIFF/F page number tag missing */ + case T30_ERR_BADTAG: /*! Incorrect values for TIFF/F tags */ + case T30_ERR_BADTIFFHDR: /*! Bad TIFF/F header - incorrect values in fields */ + ast_atomic_fetchadd_int(&p->stats->file_error, 1); + break; + case T30_ERR_NOMEM: /*! Cannot allocate memory for more pages */ + ast_atomic_fetchadd_int(&p->stats->mem_error, 1); + break; + + /* General problems */ + case T30_ERR_RETRYDCN: /*! Disconnected after permitted retries */ + ast_atomic_fetchadd_int(&p->stats->retries_exceeded, 1); + break; + case T30_ERR_CALLDROPPED: /*! The call dropped prematurely */ + ast_atomic_fetchadd_int(&p->stats->call_dropped, 1); + break; + + /* Feature negotiation issues */ + case T30_ERR_NOPOLL: /*! Poll not accepted */ + case T30_ERR_IDENT_UNACCEPTABLE: /*! Far end's ident is not acceptable */ + case T30_ERR_SUB_UNACCEPTABLE: /*! Far end's sub-address is not acceptable */ + case T30_ERR_SEP_UNACCEPTABLE: /*! Far end's selective polling address is not acceptable */ + case T30_ERR_PSA_UNACCEPTABLE: /*! Far end's polled sub-address is not acceptable */ + case T30_ERR_SID_UNACCEPTABLE: /*! Far end's sender identification is not acceptable */ + case T30_ERR_PWD_UNACCEPTABLE: /*! Far end's password is not acceptable */ + case T30_ERR_TSA_UNACCEPTABLE: /*! Far end's transmitting subscriber internet address is not acceptable */ + case T30_ERR_IRA_UNACCEPTABLE: /*! Far end's internet routing address is not acceptable */ + case T30_ERR_CIA_UNACCEPTABLE: /*! Far end's calling subscriber internet address is not acceptable */ + case T30_ERR_ISP_UNACCEPTABLE: /*! Far end's internet selective polling address is not acceptable */ + case T30_ERR_CSA_UNACCEPTABLE: /*! Far end's called subscriber internet address is not acceptable */ + ast_atomic_fetchadd_int(&p->stats->neg_failed, 1); + break; + default: + ast_atomic_fetchadd_int(&p->stats->unknown_error, 1); + ast_log(LOG_WARNING, "unknown FAX session result '%d' (%s)\n", completion_code, t30_completion_code_to_str(completion_code)); + return -1; + } + return 0; +} + +/*! \brief Phase E handler callback. + * \param t30_state the span t30 state + * \param data this will be the ast_fax_session + * \param completion_code the result of the fax session + * + * This function pulls stats from the spandsp stack and stores them for res_fax + * to use later. + */ +static void t30_phase_e_handler(t30_state_t *t30_state, void *data, int completion_code) +{ + struct ast_fax_session *s = data; + struct spandsp_pvt *p = s->tech_pvt; + char headerinfo[T30_MAX_PAGE_HEADER_INFO + 1]; + const char *c; + t30_stats_t stats; + + ast_debug(5, "FAX session '%d' entering phase E\n", s->id); + + p->isdone = 1; + + update_stats(p, completion_code); + + t30_get_transfer_statistics(t30_state, &stats); + + if (completion_code == T30_ERR_OK) { + ast_string_field_set(s->details, result, "SUCCESS"); + } else { + ast_string_field_set(s->details, result, "FAILED"); + ast_string_field_set(s->details, error, t30_completion_code_to_str(completion_code)); + } + + ast_string_field_set(s->details, resultstr, t30_completion_code_to_str(completion_code)); + + ast_debug(5, "FAX session '%d' completed with result: %s (%s)\n", s->id, s->details->result, s->details->resultstr); + + if ((c = t30_get_tx_ident(t30_state))) { + ast_string_field_set(s->details, localstationid, c); + } + + if ((c = t30_get_rx_ident(t30_state))) { + ast_string_field_set(s->details, remotestationid, c); + } + +#if SPANDSP_RELEASE_DATE >= 20090220 + s->details->pages_transferred = (s->details->caps & AST_FAX_TECH_RECEIVE) ? stats.pages_rx : stats.pages_tx; +#else + s->details->pages_transferred = stats.pages_transferred; +#endif + + ast_string_field_build(s->details, transfer_rate, "%d", stats.bit_rate); + + ast_string_field_build(s->details, resolution, "%dx%d", stats.x_resolution, stats.y_resolution); + + t30_get_tx_page_header_info(t30_state, headerinfo); + ast_string_field_set(s->details, headerinfo, headerinfo); +} + +/*! \brief Send spandsp log messages to asterisk. + * \param level the spandsp logging level + * \param msg the log message + * + * \note This function is a callback function called by spandsp. + */ +static void spandsp_log(int level, const char *msg) +{ + if (level == SPAN_LOG_ERROR) { + ast_log(LOG_ERROR, "%s", msg); + } else if (level == SPAN_LOG_WARNING) { + ast_log(LOG_WARNING, "%s", msg); + } else { + ast_log(LOG_DEBUG, "%s", msg); + } +} + +static void set_logging(logging_state_t *state, struct ast_fax_session_details *details) +{ + int level = SPAN_LOG_WARNING; + + if (details->option.debug) { + level = SPAN_LOG_DEBUG_3; + } + + span_log_set_message_handler(state, spandsp_log); + span_log_set_level(state, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | level); +} + +static void set_local_info(t30_state_t *t30_state, struct ast_fax_session_details *details) +{ + if (!ast_strlen_zero(details->localstationid)) { + t30_set_tx_ident(t30_state, details->localstationid); + } + + if (!ast_strlen_zero(details->headerinfo)) { + t30_set_tx_page_header_info(t30_state, details->headerinfo); + } +} + +static void set_file(t30_state_t *t30_state, struct ast_fax_session_details *details) +{ + if (details->caps & AST_FAX_TECH_RECEIVE) { + t30_set_rx_file(t30_state, AST_LIST_FIRST(&details->documents)->filename, -1); + } else { + /* if not AST_FAX_TECH_RECEIVE, assume AST_FAX_TECH_SEND, this + * should be safe because we ensure either RECEIVE or SEND is + * indicated in spandsp_fax_new() */ + t30_set_tx_file(t30_state, AST_LIST_FIRST(&details->documents)->filename, -1, -1); + } +} + +static void set_ecm(t30_state_t *t30_state, struct ast_fax_session_details *details) +{ + t30_set_ecm_capability(t30_state, (details->option.ecm == AST_FAX_OPTFLAG_DEFAULT) ? 1 : details->option.ecm ); + t30_set_supported_compressions(t30_state, T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION | T30_SUPPORT_T6_COMPRESSION); +} + +/*! \brief create an instance of the spandsp tech_pvt for a fax session */ +static void *spandsp_fax_new(struct ast_fax_session *s, struct ast_fax_tech_token *token) +{ + struct spandsp_pvt *p; + int caller_mode; + + if ((!(p = ast_calloc(1, sizeof(*p))))) { + ast_log(LOG_ERROR, "Cannot initialize the spandsp private FAX technology structure.\n"); + goto e_return; + } + + AST_LIST_HEAD_INIT(&p->read_frames); + + if (s->details->caps & AST_FAX_TECH_RECEIVE) { + caller_mode = 0; + } else if (s->details->caps & AST_FAX_TECH_SEND) { + caller_mode = 1; + } else { + ast_log(LOG_ERROR, "Are we sending or receiving? The FAX requirements (capabilities: 0x%X) were not properly set.\n", s->details->caps); + goto e_free; + } + + if (!(p->timer = ast_timer_open())) { + ast_log(LOG_ERROR, "Channel '%s' FAX session '%d' failed to create timing source.\n", s->channame, s->id); + goto e_free; + } + + s->fd = ast_timer_fd(p->timer); + + p->stats = &spandsp_global_stats.g711; + + if (s->details->caps & AST_FAX_TECH_T38) { + if ((s->details->caps & AST_FAX_TECH_AUDIO) == 0) { + /* audio mode was not requested, start in T.38 mode */ + p->ist38 = 1; + p->stats = &spandsp_global_stats.t38; + } + + /* init t38 stuff */ + t38_terminal_init(&p->t38_state, caller_mode, t38_tx_packet_handler, p); + set_logging(&p->t38_state.logging, s->details); + } + + if (s->details->caps & AST_FAX_TECH_AUDIO) { + /* init audio stuff */ + fax_init(&p->fax_state, caller_mode); + set_logging(&p->fax_state.logging, s->details); + } + + s->state = AST_FAX_STATE_INITIALIZED; + return p; + +e_free: + ast_free(p); +e_return: + return NULL; +} + +/*! \brief Destroy a spandsp fax session. + */ +static void spandsp_fax_destroy(struct ast_fax_session *s) +{ + struct spandsp_pvt *p = s->tech_pvt; + + session_destroy(p); + ast_free(p); + s->tech_pvt = NULL; + s->fd = -1; +} + +/*! \brief Read a frame from the spandsp fax stack. + */ +static struct ast_frame *spandsp_fax_read(struct ast_fax_session *s) +{ + struct spandsp_pvt *p = s->tech_pvt; + uint8_t buffer[AST_FRIENDLY_OFFSET + SPANDSP_FAX_SAMPLES * sizeof(uint16_t)]; + int16_t *buf = (int16_t *) (buffer + AST_FRIENDLY_OFFSET); + int samples; + + struct ast_frame fax_frame = { + .frametype = AST_FRAME_VOICE, + .subclass.codec = AST_FORMAT_SLINEAR, + .src = "res_fax_spandsp_g711", + }; + + struct ast_frame *f = &fax_frame; + + ast_timer_ack(p->timer, 1); + + /* XXX do we need to lock here? */ + if (p->isdone) { + s->state = AST_FAX_STATE_COMPLETE; + ast_debug(5, "FAX session '%d' is complete.\n", s->id); + return NULL; + } + + if (p->ist38) { + t38_terminal_send_timeout(&p->t38_state, SPANDSP_FAX_SAMPLES); + if ((f = AST_LIST_REMOVE_HEAD(&p->read_frames, frame_list))) { + return f; + } + } else { + if ((samples = fax_tx(&p->fax_state, buf, SPANDSP_FAX_SAMPLES)) > 0) { + f->samples = samples; + AST_FRAME_SET_BUFFER(f, buffer, AST_FRIENDLY_OFFSET, samples * sizeof(int16_t)); + return ast_frisolate(f); + } + } + + return &ast_null_frame; +} + +/*! \brief Write a frame to the spandsp fax stack. + * \param s a fax session + * \param f the frame to write + * + * \note res_fax does not currently use the return value of this function. + * Also the fax_rx() function never fails. + * + * \retval 0 success + * \retval -1 failure + */ +static int spandsp_fax_write(struct ast_fax_session *s, const struct ast_frame *f) +{ + struct spandsp_pvt *p = s->tech_pvt; + + /* XXX do we need to lock here? */ + if (s->state == AST_FAX_STATE_COMPLETE) { + ast_log(LOG_WARNING, "FAX session '%d' is in the '%s' state.\n", s->id, ast_fax_state_to_str(s->state)); + return -1; + } + + if (p->ist38) { + return t38_core_rx_ifp_packet(p->t38_core_state, f->data.ptr, f->datalen, f->seqno); + } else { + return fax_rx(&p->fax_state, f->data.ptr, f->samples); + } +} + +/*! \brief */ +static int spandsp_fax_start(struct ast_fax_session *s) +{ + struct spandsp_pvt *p = s->tech_pvt; + + s->state = AST_FAX_STATE_OPEN; + + if (p->ist38) { +#if SPANDSP_RELEASE_DATE >= 20080725 + /* for spandsp shaphots 0.0.6 and higher */ + p->t30_state = &p->t38_state.t30; + p->t38_core_state = &p->t38_state.t38_fe.t38; +#else + /* for spandsp releases 0.0.5 */ + p->t30_state = &p->t38_state.t30_state; + p->t38_core_state = &p->t38_state.t38; +#endif + } else { +#if SPANDSP_RELEASE_DATE >= 20080725 + /* for spandsp shaphots 0.0.6 and higher */ + p->t30_state = &p->fax_state.t30; +#else + /* for spandsp release 0.0.5 */ + p->t30_state = &p->fax_state.t30_state; +#endif + } + + set_logging(&p->t30_state->logging, s->details); + + /* set some parameters */ + set_local_info(p->t30_state, s->details); + set_file(p->t30_state, s->details); + set_ecm(p->t30_state, s->details); + + /* perhaps set_transmit_on_idle() should be called */ + + t30_set_phase_e_handler(p->t30_state, t30_phase_e_handler, s); + + /* set T.38 parameters */ + if (p->ist38) { + set_logging(&p->t38_core_state->logging, s->details); + + t38_set_max_datagram_size(p->t38_core_state, s->details->their_t38_parameters.max_ifp); + + if (s->details->their_t38_parameters.fill_bit_removal) { + t38_set_fill_bit_removal(p->t38_core_state, TRUE); + } + + if (s->details->their_t38_parameters.transcoding_mmr) { + t38_set_mmr_transcoding(p->t38_core_state, TRUE); + } + + if (s->details->their_t38_parameters.transcoding_jbig) { + t38_set_jbig_transcoding(p->t38_core_state, TRUE); + } + } else { + /* have the fax stack generate silence if it has no data to send */ + fax_set_transmit_on_idle(&p->fax_state, 1); + } + + + /* start the timer */ + if (ast_timer_set_rate(p->timer, SPANDSP_FAX_TIMER_RATE)) { + ast_log(LOG_ERROR, "FAX session '%d' error setting rate on timing source.\n", s->id); + return -1; + } + + s->state = AST_FAX_STATE_ACTIVE; + + return 0; +} + +/*! \brief */ +static int spandsp_fax_cancel(struct ast_fax_session *s) +{ + struct spandsp_pvt *p = s->tech_pvt; + t30_terminate(p->t30_state); + p->isdone = 1; + return 0; +} + +/*! \brief */ +static int spandsp_fax_switch_to_t38(struct ast_fax_session *s) +{ + struct spandsp_pvt *p = s->tech_pvt; + + /* prevent the phase E handler from running, this is not a real termination */ + t30_set_phase_e_handler(p->t30_state, NULL, NULL); + + t30_terminate(p->t30_state); + + s->details->option.switch_to_t38 = 1; + ast_atomic_fetchadd_int(&p->stats->switched, 1); + + p->ist38 = 1; + p->stats = &spandsp_global_stats.t38; + spandsp_fax_start(s); + + return 0; +} + +/*! \brief */ +static char *spandsp_fax_cli_show_capabilities(int fd) +{ + ast_cli(fd, "SEND RECEIVE T.38 G.711\n\n"); + return CLI_SUCCESS; +} + +/*! \brief */ +static char *spandsp_fax_cli_show_session(struct ast_fax_session *s, int fd) +{ + struct spandsp_pvt *p = s->tech_pvt; + t30_stats_t stats; + + ao2_lock(s); + ast_cli(fd, "%-22s : %d\n", "session", s->id); + ast_cli(fd, "%-22s : %s\n", "operation", (s->details->caps & AST_FAX_TECH_RECEIVE) ? "Receive" : "Transmit"); + ast_cli(fd, "%-22s : %s\n", "state", ast_fax_state_to_str(s->state)); + if (s->state != AST_FAX_STATE_UNINITIALIZED) { + t30_get_transfer_statistics(p->t30_state, &stats); + ast_cli(fd, "%-22s : %s\n", "Last Status", t30_completion_code_to_str(stats.current_status)); + ast_cli(fd, "%-22s : %s\n", "ECM Mode", stats.error_correcting_mode ? "Yes" : "No"); + ast_cli(fd, "%-22s : %d\n", "Data Rate", stats.bit_rate); + ast_cli(fd, "%-22s : %dx%d\n", "Image Resolution", stats.x_resolution, stats.y_resolution); +#if SPANDSP_RELEASE_DATE >= 20090220 + ast_cli(fd, "%-22s : %d\n", "Page Number", (s->details->caps & AST_FAX_TECH_RECEIVE) ? stats.pages_rx : stats.pages_tx); +#else + ast_cli(fd, "%-22s : %d\n", "Page Number", stats.pages_transferred); +#endif + ast_cli(fd, "%-22s : %s\n", "File Name", s->details->caps & AST_FAX_TECH_RECEIVE ? p->t30_state->rx_file : p->t30_state->tx_file); + + ast_cli(fd, "\nData Statistics:\n"); +#if SPANDSP_RELEASE_DATE >= 20090220 + ast_cli(fd, "%-22s : %d\n", "Tx Pages", stats.pages_tx); + ast_cli(fd, "%-22s : %d\n", "Rx Pages", stats.pages_rx); +#else + ast_cli(fd, "%-22s : %d\n", "Tx Pages", (s->details->caps & AST_FAX_TECH_SEND) ? stats.pages_transferred : 0); + ast_cli(fd, "%-22s : %d\n", "Rx Pages", (s->details->caps & AST_FAX_TECH_RECEIVE) ? stats.pages_transferred : 0); +#endif + ast_cli(fd, "%-22s : %d\n", "Longest Bad Line Run", stats.longest_bad_row_run); + ast_cli(fd, "%-22s : %d\n", "Total Bad Lines", stats.bad_rows); + } + ao2_unlock(s); + ast_cli(fd, "\n\n"); + return CLI_SUCCESS; +} + +/*! \brief */ +static char *spandsp_fax_cli_show_stats(int fd) +{ + ast_mutex_lock(&spandsp_global_stats.lock); + ast_cli(fd, "\n%-20.20s\n", "Spandsp G.711"); + ast_cli(fd, "%-20.20s : %d\n", "Success", spandsp_global_stats.g711.success); + ast_cli(fd, "%-20.20s : %d\n", "Switched to T.38", spandsp_global_stats.g711.switched); + ast_cli(fd, "%-20.20s : %d\n", "Call Dropped", spandsp_global_stats.g711.call_dropped); + ast_cli(fd, "%-20.20s : %d\n", "No FAX", spandsp_global_stats.g711.nofax); + ast_cli(fd, "%-20.20s : %d\n", "Negotation Failed", spandsp_global_stats.g711.neg_failed); + ast_cli(fd, "%-20.20s : %d\n", "Train Failure", spandsp_global_stats.g711.failed_to_train); + ast_cli(fd, "%-20.20s : %d\n", "Retries Exceeded", spandsp_global_stats.g711.retries_exceeded); + ast_cli(fd, "%-20.20s : %d\n", "Protocol Error", spandsp_global_stats.g711.protocol_error); + ast_cli(fd, "%-20.20s : %d\n", "TX Protocol Error", spandsp_global_stats.g711.tx_protocol_error); + ast_cli(fd, "%-20.20s : %d\n", "RX Protocol Error", spandsp_global_stats.g711.rx_protocol_error); + ast_cli(fd, "%-20.20s : %d\n", "File Error", spandsp_global_stats.g711.file_error); + ast_cli(fd, "%-20.20s : %d\n", "Memory Error", spandsp_global_stats.g711.mem_error); + ast_cli(fd, "%-20.20s : %d\n", "Unknown Error", spandsp_global_stats.g711.unknown_error); + + ast_cli(fd, "\n%-20.20s\n", "Spandsp T.38"); + ast_cli(fd, "%-20.20s : %d\n", "Success", spandsp_global_stats.t38.success); + ast_cli(fd, "%-20.20s : %d\n", "Call Dropped", spandsp_global_stats.t38.call_dropped); + ast_cli(fd, "%-20.20s : %d\n", "No FAX", spandsp_global_stats.t38.nofax); + ast_cli(fd, "%-20.20s : %d\n", "Negotation Failed", spandsp_global_stats.t38.neg_failed); + ast_cli(fd, "%-20.20s : %d\n", "Train Failure", spandsp_global_stats.t38.failed_to_train); + ast_cli(fd, "%-20.20s : %d\n", "Retries Exceeded", spandsp_global_stats.t38.retries_exceeded); + ast_cli(fd, "%-20.20s : %d\n", "Protocol Error", spandsp_global_stats.t38.protocol_error); + ast_cli(fd, "%-20.20s : %d\n", "TX Protocol Error", spandsp_global_stats.t38.tx_protocol_error); + ast_cli(fd, "%-20.20s : %d\n", "RX Protocol Error", spandsp_global_stats.t38.rx_protocol_error); + ast_cli(fd, "%-20.20s : %d\n", "File Error", spandsp_global_stats.t38.file_error); + ast_cli(fd, "%-20.20s : %d\n", "Memory Error", spandsp_global_stats.t38.mem_error); + ast_cli(fd, "%-20.20s : %d\n", "Unknown Error", spandsp_global_stats.t38.unknown_error); + ast_mutex_unlock(&spandsp_global_stats.lock); + + return CLI_SUCCESS; +} + +/*! \brief unload res_fax_spandsp */ +static int unload_module(void) +{ + ast_fax_tech_unregister(&spandsp_fax_tech); + ast_mutex_destroy(&spandsp_global_stats.lock); + return AST_MODULE_LOAD_SUCCESS; +} + +/*! \brief load res_fax_spandsp */ +static int load_module(void) +{ + ast_mutex_init(&spandsp_global_stats.lock); + spandsp_fax_tech.module = ast_module_info->self; + if (ast_fax_tech_register(&spandsp_fax_tech) < 0) { + ast_log(LOG_ERROR, "failed to register FAX technology\n"); + return AST_MODULE_LOAD_DECLINE; + } + + /* prevent logging to stderr */ + span_set_message_handler(NULL); + + return AST_MODULE_LOAD_SUCCESS; +} + + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Spandsp G.711 and T.38 FAX Technologies", + .load = load_module, + .unload = unload_module, + ); |