From fc33f7b57ea250d8d85e0fbf37e3e4268adfa2cd Mon Sep 17 00:00:00 2001 From: Joshua Colp Date: Fri, 26 Jun 2009 19:29:02 +0000 Subject: [PATCH] Merged revisions 203699 via svnmerge from https://origsvn.digium.com/svn/asterisk/trunk ........ r203699 | file | 2009-06-26 16:27:24 -0300 (Fri, 26 Jun 2009) | 2 lines Improve T.38 negotiation by exchanging session parameters between application and channel. ........ git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/1.6.0@203701 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- apps/app_fax.c | 46 ++++--- channels/chan_sip.c | 290 +++++++++++++++++++++++++++------------ configs/sip.conf.sample | 10 +- include/asterisk/frame.h | 26 ++++ main/channel.c | 3 + main/frame.c | 19 +++ main/rtp.c | 6 +- 7 files changed, 287 insertions(+), 113 deletions(-) diff --git a/apps/app_fax.c b/apps/app_fax.c index 51ca0f9bf7..0fe861bb3c 100644 --- a/apps/app_fax.c +++ b/apps/app_fax.c @@ -116,7 +116,7 @@ typedef struct { int direction; /* Fax direction: 0 - receiving, 1 - sending */ int caller_mode; char *file_name; - + struct ast_control_t38_parameters t38parameters; volatile int finished; } fax_session; @@ -327,7 +327,6 @@ static int transmit_audio(fax_session *s) struct ast_frame *fr; int last_state = 0; struct timeval now, start, state_change; - enum ast_control_t38 t38control; #if SPANDSP_RELEASE_DATE >= 20080725 /* for spandsp shaphots 0.0.6 and higher */ @@ -411,9 +410,16 @@ static int transmit_audio(fax_session *s) /* Do not pass channel to ast_dsp_process otherwise it may queue modified audio frame back */ fr = ast_dsp_process(NULL, dsp, fr); if (fr && fr->frametype == AST_FRAME_DTMF && fr->subclass == 'f') { + struct ast_control_t38_parameters parameters = { .request_response = AST_T38_REQUEST_NEGOTIATE, + .version = 0, + .max_datagram = 400, + .rate = AST_T38_RATE_9600, + .rate_management = AST_T38_RATE_MANAGEMENT_TRANSFERED_TCF, + .fill_bit_removal = 1, + .transcoding_mmr = 1, + }; ast_debug(1, "Fax tone detected. Requesting T38\n"); - t38control = AST_T38_REQUEST_NEGOTIATE; - ast_indicate_data(s->chan, AST_CONTROL_T38, &t38control, sizeof(t38control)); + ast_indicate_data(s->chan, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters)); detect_tone = 0; } @@ -438,11 +444,11 @@ static int transmit_audio(fax_session *s) state_change = ast_tvnow(); last_state = t30state->state; } - } else if (inf->frametype == AST_FRAME_CONTROL && inf->subclass == AST_CONTROL_T38 && - inf->datalen == sizeof(enum ast_control_t38)) { - t38control =*((enum ast_control_t38 *) inf->data); - if (t38control == AST_T38_NEGOTIATED) { + } else if (inf->frametype == AST_FRAME_CONTROL && inf->subclass == AST_CONTROL_T38_PARAMETERS) { + struct ast_control_t38_parameters *parameters = inf->data; + if (parameters->request_response == AST_T38_NEGOTIATED) { /* T38 switchover completed */ + s->t38parameters = *parameters; ast_debug(1, "T38 negotiated, finishing audio loop\n"); res = 1; break; @@ -503,8 +509,6 @@ static int transmit_t38(fax_session *s) struct ast_frame *inf = NULL; int last_state = 0; struct timeval now, start, state_change, last_frame; - enum ast_control_t38 t38control; - t30_state_t *t30state; t38_core_state_t *t38state; @@ -525,6 +529,17 @@ static int transmit_t38(fax_session *s) return -1; } + t38_set_max_datagram_size(t38state, s->t38parameters.max_datagram); + + if (s->t38parameters.fill_bit_removal) { + t38_set_fill_bit_removal(t38state, TRUE); + } + if (s->t38parameters.transcoding_mmr) { + t38_set_mmr_transcoding(t38state, TRUE); + } else if (s->t38parameters.transcoding_jbig) { + t38_set_jbig_transcoding(t38state, TRUE); + } + /* Setup logging */ set_logging(&t38.logging); set_logging(&t30state->logging); @@ -568,12 +583,9 @@ static int transmit_t38(fax_session *s) state_change = ast_tvnow(); last_state = t30state->state; } - } else if (inf->frametype == AST_FRAME_CONTROL && inf->subclass == AST_CONTROL_T38 && - inf->datalen == sizeof(enum ast_control_t38)) { - - t38control = *((enum ast_control_t38 *) inf->data); - - if (t38control == AST_T38_TERMINATED || t38control == AST_T38_REFUSED) { + } else if (inf->frametype == AST_FRAME_CONTROL && inf->subclass == AST_CONTROL_T38_PARAMETERS) { + struct ast_control_t38_parameters *parameters = inf->data; + if (parameters->request_response == AST_T38_TERMINATED || parameters->request_response == AST_T38_REFUSED) { ast_debug(1, "T38 down, terminating\n"); res = -1; break; @@ -664,7 +676,7 @@ static int sndfax_exec(struct ast_channel *chan, void *data) { int res = 0; char *parse; - fax_session session; + fax_session session = { 0, }; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(file_name); diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 804116f7aa..41325d1699 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -983,10 +983,10 @@ struct sip_auth { #define SIP_PAGE2_SUBSCRIBEMWIONLY (1 << 18) /*!< GP: Only issue MWI notification if subscribed to */ #define SIP_PAGE2_IGNORESDPVERSION (1 << 19) /*!< GDP: Ignore the SDP session version number we receive and treat all sessions as new */ -#define SIP_PAGE2_T38SUPPORT (7 << 20) /*!< GDP: T38 Fax Passthrough Support */ -#define SIP_PAGE2_T38SUPPORT_UDPTL (1 << 20) /*!< GDP: T38 Fax Passthrough Support */ -#define SIP_PAGE2_T38SUPPORT_RTP (2 << 20) /*!< GDP: T38 Fax Passthrough Support (not implemented) */ -#define SIP_PAGE2_T38SUPPORT_TCP (4 << 20) /*!< GDP: T38 Fax Passthrough Support (not implemented) */ +#define SIP_PAGE2_T38SUPPORT (7 << 20) /*!< GDP: T38 Fax Passthrough Support */ +#define SIP_PAGE2_T38SUPPORT_UDPTL (1 << 20) /*!< GDP: T38 Fax Passthrough Support (no error correction) */ +#define SIP_PAGE2_T38SUPPORT_UDPTL_FEC (2 << 20) /*!< GDP: T38 Fax Passthrough Support (FEC error correction) */ +#define SIP_PAGE2_T38SUPPORT_UDPTL_REDUNDANCY (4 << 20) /*!< GDP: T38 Fax Passthrough Support (redundancy error correction) */ #define SIP_PAGE2_CALL_ONHOLD (3 << 23) /*!< D: Call hold states: */ #define SIP_PAGE2_CALL_ONHOLD_ACTIVE (1 << 23) /*!< D: Active hold */ @@ -1060,7 +1060,6 @@ static int sipdebug_text; enum t38state { T38_DISABLED = 0, /*!< Not enabled */ T38_LOCAL_REINVITE, /*!< Offered from local - REINVITE */ - T38_PEER_DIRECT, /*!< Offered from peer */ T38_PEER_REINVITE, /*!< Offered from peer - REINVITE */ T38_ENABLED /*!< Negotiated (enabled) */ }; @@ -1072,7 +1071,6 @@ struct t38properties { int peercapability; /*!< Peers T38 capability */ int jointcapability; /*!< Supported T38 capability at both ends */ enum t38state state; /*!< T.38 state */ - unsigned int direct:1; /*!< Whether the T38 came from the initial invite or not */ }; /*! \brief Parameters to know status of transfer */ @@ -3291,7 +3289,6 @@ static int sip_queryoption(struct ast_channel *chan, int option, void *data, int if (ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT)) { switch (p->t38.state) { case T38_LOCAL_REINVITE: - case T38_PEER_DIRECT: case T38_PEER_REINVITE: state = T38_STATE_NEGOTIATING; break; @@ -4032,21 +4029,62 @@ static void do_setnat(struct sip_pvt *p, int natflags) } } +/*! \brief Helper function which interprets T.38 capabilities and fills a parameters structure in */ +static void fill_t38_parameters(int capabilities, struct ast_control_t38_parameters *parameters, struct sip_pvt *p) +{ + if (capabilities & T38FAX_VERSION_0) { + parameters->version = 0; + } else if (capabilities & T38FAX_VERSION_1) { + parameters->version = 1; + } + + if (capabilities & T38FAX_RATE_14400) { + parameters->rate = AST_T38_RATE_14400; + } else if (capabilities & T38FAX_RATE_12000) { + parameters->rate = AST_T38_RATE_12000; + } else if (capabilities & T38FAX_RATE_9600) { + parameters->rate = AST_T38_RATE_9600; + } else if (capabilities & T38FAX_RATE_7200) { + parameters->rate = AST_T38_RATE_7200; + } else if (capabilities & T38FAX_RATE_4800) { + parameters->rate = AST_T38_RATE_4800; + } else if (capabilities & T38FAX_RATE_2400) { + parameters->rate = AST_T38_RATE_2400; + } + + if (capabilities & T38FAX_RATE_MANAGEMENT_TRANSFERED_TCF) { + parameters->rate_management = AST_T38_RATE_MANAGEMENT_TRANSFERED_TCF; + } else if (capabilities & T38FAX_RATE_MANAGEMENT_LOCAL_TCF) { + parameters->rate_management = AST_T38_RATE_MANAGEMENT_LOCAL_TCF; + } + + if (capabilities & T38FAX_FILL_BIT_REMOVAL) { + parameters->fill_bit_removal = 1; + } + + if (capabilities & T38FAX_TRANSCODING_MMR) { + parameters->transcoding_mmr = 1; + } + + if (capabilities & T38FAX_TRANSCODING_JBIG) { + parameters->transcoding_jbig = 1; + } + + parameters->max_datagram = ast_udptl_get_far_max_datagram(p->udptl); +} + /*! \brief Change the T38 state on a SIP dialog */ static void change_t38_state(struct sip_pvt *p, int state) { int old = p->t38.state; struct ast_channel *chan = p->owner; enum ast_control_t38 message = 0; + struct ast_control_t38_parameters parameters = { 0, }; /* Don't bother changing if we are already in the state wanted */ if (old == state) return; - if (state == T38_PEER_DIRECT) { - p->t38.direct = 1; - } - p->t38.state = state; ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, chan ? chan->name : ""); @@ -4055,16 +4093,20 @@ static void change_t38_state(struct sip_pvt *p, int state) return; /* Given the state requested and old state determine what control frame we want to queue up */ - if (state == T38_PEER_REINVITE) - message = AST_T38_REQUEST_NEGOTIATE; - else if (state == T38_ENABLED) - message = AST_T38_NEGOTIATED; - else if (state == T38_DISABLED && old == T38_ENABLED) - message = AST_T38_TERMINATED; + if (state == T38_PEER_REINVITE) { + message = parameters.request_response = AST_T38_REQUEST_NEGOTIATE; + fill_t38_parameters(p->t38.peercapability, ¶meters, p); + } else if (state == T38_ENABLED) { + message = parameters.request_response = AST_T38_NEGOTIATED; + fill_t38_parameters(p->t38.jointcapability, ¶meters, p); + } else if (state == T38_DISABLED && old == T38_ENABLED) + message = parameters.request_response = AST_T38_TERMINATED; else if (state == T38_DISABLED && old == T38_LOCAL_REINVITE) - message = AST_T38_REFUSED; + message = parameters.request_response = AST_T38_REFUSED; /* Woot we got a message, create a control frame and send it on! */ + if (parameters.request_response) + ast_queue_control_data(chan, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters)); if (message) ast_queue_control_data(chan, AST_CONTROL_T38, &message, sizeof(message)); } @@ -4074,12 +4116,16 @@ static void set_t38_capabilities(struct sip_pvt *p) { p->t38.capability = global_t38_capability; if (p->udptl) { - if (ast_udptl_get_error_correction_scheme(p->udptl) == UDPTL_ERROR_CORRECTION_FEC ) - p->t38.capability |= T38FAX_UDP_EC_FEC; - else if (ast_udptl_get_error_correction_scheme(p->udptl) == UDPTL_ERROR_CORRECTION_REDUNDANCY ) + if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL_REDUNDANCY) { + ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_REDUNDANCY); p->t38.capability |= T38FAX_UDP_EC_REDUNDANCY; - else if (ast_udptl_get_error_correction_scheme(p->udptl) == UDPTL_ERROR_CORRECTION_NONE ) + } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL_FEC) { + ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_FEC); + p->t38.capability |= T38FAX_UDP_EC_FEC; + } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL) { + ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_NONE); p->t38.capability |= T38FAX_UDP_EC_NONE; + } p->t38.capability |= T38FAX_RATE_MANAGEMENT_TRANSFERED_TCF; } } @@ -5162,9 +5208,6 @@ static int sip_answer(struct ast_channel *ast) ast_setstate(ast, AST_STATE_UP); ast_debug(1, "SIP answering channel: %s\n", ast->name); - if (p->t38.state == T38_PEER_DIRECT) { - change_t38_state(p, T38_ENABLED); - } ast_rtp_new_source(p->rtp); ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); res = transmit_response_with_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL, FALSE); @@ -5204,7 +5247,7 @@ static int sip_write(struct ast_channel *ast, struct ast_frame *frame) p->invitestate = INV_EARLY_MEDIA; transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE); ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT); - } else if (p->t38.state == T38_ENABLED && !p->t38.direct) { + } else if (p->t38.state == T38_ENABLED) { change_t38_state(p, T38_DISABLED); transmit_reinvite_with_sdp(p, FALSE, FALSE); } else { @@ -5390,6 +5433,90 @@ static int sip_transfer(struct ast_channel *ast, const char *dest) return res; } +/*! \brief Helper function which updates T.38 capability information and triggers a reinvite */ +static void interpret_t38_parameters(struct sip_pvt *p, enum ast_control_t38 request_response, const struct ast_control_t38_parameters *parameters) +{ + if (parameters) { + if (!parameters->version) { + p->t38.capability = p->t38.jointcapability |= T38FAX_VERSION_0; + } else if (parameters->version == 1) { + p->t38.capability = p->t38.jointcapability |= T38FAX_VERSION_1; + } + + if (parameters->rate == AST_T38_RATE_14400) { + p->t38.capability = p->t38.jointcapability |= T38FAX_RATE_14400 | T38FAX_RATE_12000 | T38FAX_RATE_9600 | T38FAX_RATE_7200 | T38FAX_RATE_4800 | T38FAX_RATE_2400; + } else if (parameters->rate == AST_T38_RATE_12000) { + p->t38.capability = p->t38.jointcapability |= T38FAX_RATE_12000 | T38FAX_RATE_9600 | T38FAX_RATE_7200 | T38FAX_RATE_4800 | T38FAX_RATE_2400; + } else if (parameters->rate == AST_T38_RATE_9600) { + p->t38.capability = p->t38.jointcapability |= T38FAX_RATE_9600 | T38FAX_RATE_7200 | T38FAX_RATE_4800 | T38FAX_RATE_2400; + } else if (parameters->rate == AST_T38_RATE_7200) { + p->t38.capability = p->t38.jointcapability |= T38FAX_RATE_7200 | T38FAX_RATE_4800 | T38FAX_RATE_2400; + } else if (parameters->rate == AST_T38_RATE_4800) { + p->t38.capability = p->t38.jointcapability |= T38FAX_RATE_4800 | T38FAX_RATE_2400; + } else if (parameters->rate == AST_T38_RATE_2400) { + p->t38.capability = p->t38.jointcapability |= T38FAX_RATE_2400; + } + + if (parameters->rate_management == AST_T38_RATE_MANAGEMENT_TRANSFERED_TCF) { + p->t38.capability = p->t38.jointcapability |= T38FAX_RATE_MANAGEMENT_TRANSFERED_TCF; + } else if (parameters->rate_management == AST_T38_RATE_MANAGEMENT_LOCAL_TCF) { + p->t38.capability = p->t38.jointcapability |= T38FAX_RATE_MANAGEMENT_LOCAL_TCF; + } + + if (parameters->fill_bit_removal) { + p->t38.capability = p->t38.jointcapability |= T38FAX_FILL_BIT_REMOVAL; + } else { + p->t38.capability = p->t38.jointcapability &= ~T38FAX_FILL_BIT_REMOVAL; + } + + if (parameters->transcoding_mmr) { + p->t38.capability = p->t38.jointcapability |= T38FAX_TRANSCODING_MMR; + } else { + p->t38.capability = p->t38.jointcapability &= ~T38FAX_TRANSCODING_MMR; + } + + if (parameters->transcoding_jbig) { + p->t38.capability = p->t38.jointcapability |= T38FAX_TRANSCODING_JBIG; + } else { + p->t38.capability = p->t38.jointcapability &= ~T38FAX_TRANSCODING_JBIG; + } + + if (p->udptl && request_response == AST_T38_REQUEST_NEGOTIATE) { + ast_udptl_set_local_max_datagram(p->udptl, parameters->max_datagram ? parameters->max_datagram : 400); + } + } + + switch (request_response) { + case AST_T38_NEGOTIATED: + case AST_T38_REQUEST_NEGOTIATE: /* Request T38 */ + if (p->t38.state == T38_PEER_REINVITE) { + AST_SCHED_DEL(sched, p->t38id); + change_t38_state(p, T38_ENABLED); + transmit_response_with_t38_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL); + } else if (ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT) && p->t38.state != T38_ENABLED) { + change_t38_state(p, T38_LOCAL_REINVITE); + if (!p->pendinginvite) { + transmit_reinvite_with_sdp(p, TRUE, FALSE); + } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) { + ast_set_flag(&p->flags[0], SIP_NEEDREINVITE); + } + } + break; + case AST_T38_TERMINATED: + case AST_T38_REFUSED: + case AST_T38_REQUEST_TERMINATE: /* Shutdown T38 */ + if (p->t38.state == T38_PEER_REINVITE) { + AST_SCHED_DEL(sched, p->t38id); + change_t38_state(p, T38_DISABLED); + transmit_response_reliable(p, "488 Not acceptable here", &p->initreq); + } else if (p->t38.state == T38_ENABLED) + transmit_reinvite_with_sdp(p, FALSE, FALSE); + break; + default: + break; + } +} + /*! \brief Play indication to user * With SIP a lot of indications is sent as messages, letting the device play the indication - busy signal, congestion etc @@ -5478,35 +5605,15 @@ static int sip_indicate(struct ast_channel *ast, int condition, const void *data if (datalen != sizeof(enum ast_control_t38)) { ast_log(LOG_ERROR, "Invalid datalen for AST_CONTROL_T38. Expected %d, got %d\n", (int)sizeof(enum ast_control_t38), (int)datalen); } else { - switch (*((enum ast_control_t38 *) data)) { - case AST_T38_NEGOTIATED: - case AST_T38_REQUEST_NEGOTIATE: /* Request T38 */ - if (p->t38.state == T38_PEER_REINVITE) { - AST_SCHED_DEL(sched, p->t38id); - change_t38_state(p, T38_ENABLED); - transmit_response_with_t38_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL); - } else if (ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT) && p->t38.state != T38_ENABLED) { - change_t38_state(p, T38_LOCAL_REINVITE); - if (!p->pendinginvite) { - transmit_reinvite_with_sdp(p, TRUE, FALSE); - } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) { - ast_set_flag(&p->flags[0], SIP_NEEDREINVITE); - } - } - break; - case AST_T38_TERMINATED: - case AST_T38_REFUSED: - case AST_T38_REQUEST_TERMINATE: /* Shutdown T38 */ - if (p->t38.state == T38_PEER_REINVITE) { - AST_SCHED_DEL(sched, p->t38id); - change_t38_state(p, T38_DISABLED); - transmit_response_reliable(p, "488 Not acceptable here", &p->initreq); - } else if (p->t38.state == T38_ENABLED) - transmit_reinvite_with_sdp(p, FALSE, FALSE); - break; - default: - break; - } + interpret_t38_parameters(p, *((enum ast_control_t38 *) data), NULL); + } + break; + case AST_CONTROL_T38_PARAMETERS: + if (datalen != sizeof(struct ast_control_t38_parameters)) { + ast_log(LOG_ERROR, "Invalid datalen for AST_CONTROL_T38_PARAMETERS. Expected %d, got %d\n", (int)sizeof(struct ast_control_t38_parameters), (int)datalen); + } else { + const struct ast_control_t38_parameters *parameters = data; + interpret_t38_parameters(p, parameters->request_response, parameters); } break; case AST_CONTROL_SRCUPDATE: @@ -5913,7 +6020,7 @@ static struct ast_frame *sip_rtp_read(struct ast_channel *ast, struct sip_pvt *p if (f && (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) && p->vad) { f = ast_dsp_process(p->owner, p->vad, f); if (f && f->frametype == AST_FRAME_DTMF) { - if (ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT_UDPTL) && f->subclass == 'f') { + if (ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT) && f->subclass == 'f') { ast_debug(1, "Fax CNG detected on %s\n", ast->name); *faxdetect = 1; } else { @@ -5938,7 +6045,7 @@ static struct ast_frame *sip_read(struct ast_channel *ast) /* If we are NOT bridged to another channel, and we have detected fax tone we issue T38 re-invite to a peer */ /* If we are bridged then it is the responsibility of the SIP device to issue T38 re-invite if it detects CNG or fax preamble */ - if (faxdetected && ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT_UDPTL) && (p->t38.state == T38_DISABLED) && !(ast_bridged_channel(ast))) { + if (faxdetected && ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT) && (p->t38.state == T38_DISABLED) && !(ast_bridged_channel(ast))) { if (!ast_test_flag(&p->flags[0], SIP_GOTREFER)) { if (!p->pendinginvite) { ast_debug(3, "Sending reinvite on SIP (%s) for T.38 negotiation.\n", ast->name); @@ -7226,7 +7333,9 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action found = 1; ast_debug(3, "FaxMaxDatagram: %d\n", x); ast_udptl_set_far_max_datagram(p->udptl, x); - ast_udptl_set_local_max_datagram(p->udptl, x); + if (!ast_udptl_get_local_max_datagram(p->udptl)) { + ast_udptl_set_local_max_datagram(p->udptl, x); + } } else if ((strncmp(a, "T38FaxFillBitRemoval", 20) == 0)) { found = 1; if(sscanf(a, "T38FaxFillBitRemoval:%d", &x) == 1) { @@ -7298,8 +7407,6 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action } else if (t38action == SDP_T38_INITIATE) { if (p->owner && p->lastinvite) { change_t38_state(p, T38_PEER_REINVITE); /* T38 Offered in re-invite from remote party */ - } else { - change_t38_state(p, T38_PEER_DIRECT); /* T38 Offered directly from peer in first invite */ } } } else { @@ -8521,13 +8628,6 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int ast_debug(1, "** Our capability: %s Video flag: %s Text flag: %s\n", ast_getformatname_multiple(codecbuf, sizeof(codecbuf), capability), p->novideo ? "True" : "False", p->notext ? "True" : "False"); ast_debug(1, "** Our prefcodec: %s \n", ast_getformatname_multiple(codecbuf, sizeof(codecbuf), p->prefcodec)); - -#ifdef WHEN_WE_HAVE_T38_FOR_OTHER_TRANSPORTS - if (ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT_RTP)) { - ast_str_append(&m_audio, 0, " %d", 191); - ast_str_append(&a_audio, 0, "a=rtpmap:%d %s/%d\r\n", 191, "t38", 8000); - } -#endif /* Check if we need audio */ if (capability & AST_FORMAT_AUDIO_MASK) @@ -8728,8 +8828,10 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int x = ast_udptl_get_local_max_datagram(p->udptl); ast_str_append(&a_modem, 0, "a=T38FaxMaxBuffer:%d\r\n", x); ast_str_append(&a_modem, 0, "a=T38FaxMaxDatagram:%d\r\n", x); - if (p->t38.jointcapability != T38FAX_UDP_EC_NONE) - ast_str_append(&a_modem, 0, "a=T38FaxUdpEC:%s\r\n", (p->t38.jointcapability & T38FAX_UDP_EC_REDUNDANCY) ? "t38UDPRedundancy" : "t38UDPFEC"); + if (p->t38.jointcapability & T38FAX_UDP_EC_REDUNDANCY) + ast_str_append(&a_modem, 0, "a=T38FaxUdpEC:t38UDPRedundancy\r\n"); + else if (p->t38.jointcapability & T38FAX_UDP_EC_FEC) + ast_str_append(&a_modem, 0, "a=T38FaxUdpEC:t38UDPFEC\r\n"); } if (m_audio->len - m_audio->used < 2 || m_video->len - m_video->used < 2 || @@ -8848,7 +8950,7 @@ static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const ast_rtp_codec_setpref(p->rtp, &p->prefs); } try_suggested_sip_codec(p); - if (p->t38.state == T38_PEER_DIRECT || p->t38.state == T38_ENABLED) { + if (p->t38.state == T38_ENABLED) { add_sdp(&resp, p, oldsdp, TRUE, TRUE); } else { add_sdp(&resp, p, oldsdp, TRUE, FALSE); @@ -12078,6 +12180,11 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of, ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); + if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) && p->udptl) { + set_t38_capabilities(p); + p->t38.jointcapability = p->t38.capability; + } + /* Copy SIP extensions profile to peer */ /* XXX is this correct before a successful auth ? */ if (p->sipoptions) @@ -13332,11 +13439,7 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct ast_cli(fd, " Insecure : %s\n", insecure2str(ast_test_flag(&peer->flags[0], SIP_INSECURE))); ast_cli(fd, " Nat : %s\n", nat2str(ast_test_flag(&peer->flags[0], SIP_NAT))); ast_cli(fd, " ACL : %s\n", cli_yesno(peer->ha != NULL)); - ast_cli(fd, " T38 pt UDPTL : %s\n", cli_yesno(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT_UDPTL))); -#ifdef WHEN_WE_HAVE_T38_FOR_OTHER_TRANSPORTS - ast_cli(fd, " T38 pt RTP : %s\n", cli_yesno(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT_RTP))); - ast_cli(fd, " T38 pt TCP : %s\n", cli_yesno(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT_TCP))); -#endif + ast_cli(fd, " T38 pt UDPTL : %s\n", cli_yesno(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT))); ast_cli(fd, " CanReinvite : %s\n", cli_yesno(ast_test_flag(&peer->flags[0], SIP_CAN_REINVITE))); ast_cli(fd, " PromiscRedir : %s\n", cli_yesno(ast_test_flag(&peer->flags[0], SIP_PROMISCREDIR))); ast_cli(fd, " User=Phone : %s\n", cli_yesno(ast_test_flag(&peer->flags[0], SIP_USEREQPHONE))); @@ -13722,11 +13825,8 @@ static char *sip_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_ ast_cli(a->fd, " Record SIP history: %s\n", recordhistory ? "On" : "Off"); ast_cli(a->fd, " Call Events: %s\n", global_callevents ? "On" : "Off"); - ast_cli(a->fd, " T38 fax pt UDPTL: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT_UDPTL))); -#ifdef WHEN_WE_HAVE_T38_FOR_OTHER_TRANSPORTS - ast_cli(a->fd, " T38 fax pt RTP: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT_RTP))); - ast_cli(a->fd, " T38 fax pt TCP: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT_TCP))); -#endif + ast_cli(a->fd, " T38 fax pt UDPTL: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT))); + if (!realtimepeers && !realtimeusers && !realtimeregs) ast_cli(a->fd, " SIP realtime: Disabled\n" ); else @@ -17529,6 +17629,8 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) && !p->udptl && (p->udptl = ast_udptl_new_with_bindaddr(sched, io, 0, bindaddr.sin_addr))) { set_t38_capabilities(p); p->t38.jointcapability = p->t38.capability; + set_t38_capabilities(p); + p->t38.jointcapability = p->t38.capability; } /* We have a succesful authentication, process the SDP portion if there is one */ @@ -20640,16 +20742,24 @@ static int handle_common_options(struct ast_flags *flags, struct ast_flags *mask ast_set_flag(&mask[1], SIP_PAGE2_IGNORESDPVERSION); ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_IGNORESDPVERSION); } else if (!strcasecmp(v->name, "t38pt_udptl")) { - ast_set_flag(&mask[1], SIP_PAGE2_T38SUPPORT_UDPTL); - ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_T38SUPPORT_UDPTL); -#ifdef WHEN_WE_HAVE_T38_FOR_OTHER_TRANSPORTS - } else if (!strcasecmp(v->name, "t38pt_rtp")) { - ast_set_flag(&mask[1], SIP_PAGE2_T38SUPPORT_RTP); - ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_T38SUPPORT_RTP); - } else if (!strcasecmp(v->name, "t38pt_tcp")) { - ast_set_flag(&mask[1], SIP_PAGE2_T38SUPPORT_TCP); - ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_T38SUPPORT_TCP); -#endif + char buf[16], *word, *next = buf; + + ast_set_flag(&mask[1], SIP_PAGE2_T38SUPPORT); + + ast_copy_string(buf, v->value, sizeof(buf)); + + while ((word = strsep(&next, ","))) { + if (ast_true(word) || !strcasecmp(word, "fec")) { + ast_clear_flag(&flags[1], SIP_PAGE2_T38SUPPORT); + ast_set_flag(&flags[1], SIP_PAGE2_T38SUPPORT_UDPTL_FEC); + } else if (!strcasecmp(word, "redundancy")) { + ast_clear_flag(&flags[1], SIP_PAGE2_T38SUPPORT); + ast_set_flag(&flags[1], SIP_PAGE2_T38SUPPORT_UDPTL_REDUNDANCY); + } else if (!strcasecmp(word, "none")) { + ast_clear_flag(&flags[1], SIP_PAGE2_T38SUPPORT); + ast_set_flag(&flags[1], SIP_PAGE2_T38SUPPORT_UDPTL); + } + } } else if (!strcasecmp(v->name, "rfc2833compensate")) { ast_set_flag(&mask[1], SIP_PAGE2_RFC2833_COMPENSATE); ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_RFC2833_COMPENSATE); diff --git a/configs/sip.conf.sample b/configs/sip.conf.sample index a769f189e4..1bf3c1becf 100644 --- a/configs/sip.conf.sample +++ b/configs/sip.conf.sample @@ -391,11 +391,13 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls ; Setting this to yes, enables T.38 fax (UDPTL) passthrough on SIP to SIP calls, provided ; both parties have T38 support enabled in their Asterisk configuration ; This has to be enabled in the general section for all devices to work. You can then -; disable it on a per device basis. +; disable it on a per device basis. +; T.38 faxing only works in SIP to SIP calls. It defaults to off. ; -; T.38 faxing only works in SIP to SIP calls, with no local or agent channel being used. -; -; t38pt_udptl = yes ; Default false +; t38pt_udptl = yes ; Enables T.38 with FEC error correction. +; t38pt_udptl = yes,fec ; Enables T.38 with FEC error correction. +; t38pt_udptl = yes,redundancy ; Enables T.38 with redundancy error correction. +; t38pt_udptl = yes,none ; Enables T.38 with no error correction. ; ;----------------------------------------- OUTBOUND SIP REGISTRATIONS ------------------------ ; Asterisk can register as a SIP user agent to a SIP proxy (provider) diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h index 1440405252..18f8c18b61 100644 --- a/include/asterisk/frame.h +++ b/include/asterisk/frame.h @@ -313,6 +313,7 @@ enum ast_control_frame_type { AST_CONTROL_VIDUPDATE = 18, /*!< Indicate video frame update */ AST_CONTROL_T38 = 19, /*!< T38 state change request/notification */ AST_CONTROL_SRCUPDATE = 20, /*!< Indicate source of media has changed */ + AST_CONTROL_T38_PARAMETERS = 24, /*!< T38 state change request/notification with parameters */ }; enum ast_control_t38 { @@ -323,6 +324,31 @@ enum ast_control_t38 { AST_T38_REFUSED /*!< T38 refused for some reason (usually rejected by remote end) */ }; +enum ast_control_t38_rate { + AST_T38_RATE_2400 = 0, + AST_T38_RATE_4800, + AST_T38_RATE_7200, + AST_T38_RATE_9600, + AST_T38_RATE_12000, + AST_T38_RATE_14400, +}; + +enum ast_control_t38_rate_management { + AST_T38_RATE_MANAGEMENT_TRANSFERED_TCF = 0, + AST_T38_RATE_MANAGEMENT_LOCAL_TCF, +}; + +struct ast_control_t38_parameters { + enum ast_control_t38 request_response; /*!< Request or response of the T38 control frame */ + unsigned int version; /*!< Supported T.38 version */ + unsigned int max_datagram; /*!< Maximum datagram size supported */ + enum ast_control_t38_rate rate; /*!< Maximum fax rate supported */ + enum ast_control_t38_rate_management rate_management; /*!< Rate management setting */ + unsigned int fill_bit_removal:1; /*!< Set if fill bit removal should be used */ + unsigned int transcoding_mmr:1; /*!< Set if MMR transcoding should be used */ + unsigned int transcoding_jbig:1; /*!< Set if JBIG transcoding should be used */ +}; + #define AST_SMOOTHER_FLAG_G729 (1 << 0) #define AST_SMOOTHER_FLAG_BE (1 << 1) diff --git a/main/channel.c b/main/channel.c index 4e0be1000f..f0f4ef0e2d 100644 --- a/main/channel.c +++ b/main/channel.c @@ -3046,6 +3046,7 @@ static int attribute_const is_visible_indication(enum ast_control_frame_type con case AST_CONTROL_ANSWER: case AST_CONTROL_HANGUP: case AST_CONTROL_T38: + case AST_CONTROL_T38_PARAMETERS: return 0; case AST_CONTROL_CONGESTION: @@ -3142,6 +3143,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition, case AST_CONTROL_HOLD: case AST_CONTROL_UNHOLD: case AST_CONTROL_T38: + case AST_CONTROL_T38_PARAMETERS: /* Nothing left to do for these. */ res = 0; break; @@ -4752,6 +4754,7 @@ static enum ast_bridge_result ast_generic_bridge(struct ast_channel *c0, struct case AST_CONTROL_VIDUPDATE: case AST_CONTROL_SRCUPDATE: case AST_CONTROL_T38: + case AST_CONTROL_T38_PARAMETERS: ast_indicate_data(other, f->subclass, f->data, f->datalen); if (jb_in_use) { ast_jb_empty_and_reset(c0, c1); diff --git a/main/frame.c b/main/frame.c index c394718db2..708a7b041a 100644 --- a/main/frame.c +++ b/main/frame.c @@ -851,6 +851,25 @@ void ast_frame_dump(const char *name, struct ast_frame *f, char *prefix) } snprintf(subclass, sizeof(subclass), "T38/%s", message); break; + case AST_CONTROL_T38_PARAMETERS: + if (f->datalen != sizeof(struct ast_control_t38_parameters *)) { + message = "Invalid"; + } else { + struct ast_control_t38_parameters *parameters = f->data; + enum ast_control_t38 state = parameters->request_response; + if (state == AST_T38_REQUEST_NEGOTIATE) + message = "Negotiation Requested"; + else if (state == AST_T38_REQUEST_TERMINATE) + message = "Negotiation Request Terminated"; + else if (state == AST_T38_NEGOTIATED) + message = "Negotiated"; + else if (state == AST_T38_TERMINATED) + message = "Terminated"; + else if (state == AST_T38_REFUSED) + message = "Refused"; + } + snprintf(subclass, sizeof(subclass), "T38_Parameters/%s", message); + break; case -1: strcpy(subclass, "Stop generators"); break; diff --git a/main/rtp.c b/main/rtp.c index 5b46090a46..240c5b17ba 100644 --- a/main/rtp.c +++ b/main/rtp.c @@ -3494,7 +3494,8 @@ static enum ast_bridge_result bridge_native_loop(struct ast_channel *c0, struct (fr->subclass == AST_CONTROL_UNHOLD) || (fr->subclass == AST_CONTROL_VIDUPDATE) || (fr->subclass == AST_CONTROL_T38) || - (fr->subclass == AST_CONTROL_SRCUPDATE)) { + (fr->subclass == AST_CONTROL_SRCUPDATE) || + (fr->subclass == AST_CONTROL_T38_PARAMETERS)) { if (fr->subclass == AST_CONTROL_HOLD) { /* If we someone went on hold we want the other side to reinvite back to us */ if (who == c0) @@ -3734,7 +3735,8 @@ static enum ast_bridge_result bridge_p2p_loop(struct ast_channel *c0, struct ast (fr->subclass == AST_CONTROL_UNHOLD) || (fr->subclass == AST_CONTROL_VIDUPDATE) || (fr->subclass == AST_CONTROL_T38) || - (fr->subclass == AST_CONTROL_SRCUPDATE)) { + (fr->subclass == AST_CONTROL_SRCUPDATE) || + (fr->subclass == AST_CONTROL_T38_PARAMETERS)) { /* If we are going on hold, then break callback mode and P2P bridging */ if (fr->subclass == AST_CONTROL_HOLD) { if (p0_callback)