From f3a2d1775a50b17e39cb135d2858bb14b604d846 Mon Sep 17 00:00:00 2001 From: oej Date: Wed, 14 May 2008 13:37:07 +0000 Subject: [PATCH] Adding spport for T.140 RED - Simple RTP redundancy to prevent packet loss in text stream Work sponsored by Omnitor AB, Stockholm, Sweden (http://www.omnitor.se) git-svn-id: http://svn.digium.com/svn/asterisk/trunk@116237 f38db490-d61c-443f-a65b-d21fe96a405b --- CHANGES | 2 + CREDITS | 3 + channels/chan_sip.c | 69 +++++++++++-- include/asterisk/frame.h | 8 +- include/asterisk/rtp.h | 21 ++++ main/frame.c | 3 +- main/rtp.c | 203 ++++++++++++++++++++++++++++++++++++++- 7 files changed, 295 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index 45d1a2457..9526521b6 100644 --- a/CHANGES +++ b/CHANGES @@ -77,6 +77,8 @@ SIP Changes testing and problem reporting! * Added ability to specify registration expiry time on a per registration basis in the register line. + * Added support for T140 RED - redundancy in T.140 to prevent text loss due to + lost packets. IAX Changes ----------- diff --git a/CREDITS b/CREDITS index 2b8ae738e..004ba6334 100644 --- a/CREDITS +++ b/CREDITS @@ -20,6 +20,9 @@ Paul Bagyenda, Digital Solutions - for initial Voicetronix driver development John Todd, TalkPlus, Inc. and JR Richardson, Ntegrated Solutions. - for funding the development of SIP Session Timers support. +Omnitor AB, Gunnar Hellström, for funding work with videocaps, T.140 RED, +originate with video/text and many more contributions. + === WISHLIST CONTRIBUTERS === Jeremy McNamara - SpeeX support Nick Seraphin - RDNIS support diff --git a/channels/chan_sip.c b/channels/chan_sip.c index dca3740a1..1b767c890 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -1323,6 +1323,8 @@ struct sip_pvt { (A bit unsure of this, please correct if you know more) */ struct sip_st_dlg *stimer; /*!< SIP Session-Timers */ + + int red; }; /*! Max entires in the history list for a sip_pvt */ @@ -5208,16 +5210,20 @@ static int sip_write(struct ast_channel *ast, struct ast_frame *frame) case AST_FRAME_TEXT: if (p) { sip_pvt_lock(p); - if (p->trtp) { - /* Activate text early media */ - if ((ast->_state != AST_STATE_UP) && - !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) && - !ast_test_flag(&p->flags[0], SIP_OUTGOING)) { - transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE); - ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT); + if (p->red) { + red_buffer_t140(p->trtp, frame); + } else { + if (p->trtp) { + /* Activate text early media */ + if ((ast->_state != AST_STATE_UP) && + !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) && + !ast_test_flag(&p->flags[0], SIP_OUTGOING)) { + transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE); + ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT); + } + p->lastrtptx = time(NULL); + res = ast_rtp_write(p->trtp, frame); } - p->lastrtptx = time(NULL); - res = ast_rtp_write(p->trtp, frame); } sip_pvt_unlock(p); } @@ -6651,6 +6657,13 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action char buf[SIPBUFSIZE]; int rua_version; + + int red_data_pt[10]; + int red_num_gen = 0; + int red_pt = 0; + + char *red_cp; /* For T.140 red */ + char red_fmtp[100] = "empty"; /* For T.140 red */ if (!p->rtp) { ast_log(LOG_ERROR, "Got SDP but have no RTP session allocated.\n"); @@ -6993,6 +7006,20 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action memset(&found_rtpmap_codecs, 0, sizeof(found_rtpmap_codecs)); last_rtpmap_codec = 0; continue; + + } else if (!strncmp(a, red_fmtp, strlen(red_fmtp))) { + /* count numbers of generations in fmtp */ + red_cp = &red_fmtp[strlen(red_fmtp)]; + strncpy(red_fmtp, a, 100); + + sscanf(red_cp, "%u", &red_data_pt[red_num_gen]); + red_cp = strtok(red_cp, "/"); + while (red_cp && red_num_gen++ < RED_MAX_GENERATION) { + sscanf(red_cp, "%u", &red_data_pt[red_num_gen]); + red_cp = strtok(NULL, "/"); + } + red_cp = red_fmtp; + } else if (sscanf(a, "rtpmap: %u %[^/]/", &codec, mimeSubtype) == 2) { /* We have a rtpmap to handle */ @@ -7015,6 +7042,15 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action /* ast_verbose("Adding t140 mimeSubtype to textrtp struct\n"); */ ast_rtp_set_rtpmap_type(newtextrtp, codec, "text", mimeSubtype, 0); } + } else if (!strncasecmp(mimeSubtype, "RED", 3)) { /* Text with Redudancy */ + if (p->trtp) { + ast_rtp_set_rtpmap_type(newtextrtp, codec, "text", mimeSubtype, 0); + red_pt = codec; + sprintf(red_fmtp, "fmtp:%d ", red_pt); + + if (debug) + ast_verbose("Red submimetype has payload type: %d\n", red_pt); + } } else { /* Must be audio?? */ if(ast_rtp_set_rtpmap_type(newaudiortp, codec, "audio", mimeSubtype, ast_test_flag(&p->flags[0], SIP_G726_NONSTANDARD) ? AST_RTP_OPT_G726_NONSTANDARD : 0) != -1) { @@ -7191,6 +7227,13 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action p->peercapability = newpeercapability; /* The other sides capability in latest offer */ p->jointnoncodeccapability = newnoncodeccapability; /* DTMF capabilities */ + if (p->jointcapability & AST_FORMAT_T140RED) { + p->red = 1; + rtp_red_init(p->trtp, 300, red_data_pt, 2); + } else { + p->red = 0; + } + ast_rtp_pt_copy(p->rtp, newaudiortp); if (p->vrtp) ast_rtp_pt_copy(p->vrtp, newvideortp); @@ -8103,6 +8146,14 @@ static void add_tcodec_to_sdp(const struct sip_pvt *p, int codec, int sample_rat ast_str_append(a_buf, 0, "a=rtpmap:%d %s/%d\r\n", rtp_code, ast_rtp_lookup_mime_subtype(1, codec, 0), sample_rate); /* Add fmtp code here */ + + if (codec == AST_FORMAT_T140RED) { + ast_str_append(a_buf, 0, "a=fmtp:%d %d/%d/%d\r\n", rtp_code, + ast_rtp_lookup_code(p->trtp, 1, AST_FORMAT_T140), + ast_rtp_lookup_code(p->trtp, 1, AST_FORMAT_T140), + ast_rtp_lookup_code(p->trtp, 1, AST_FORMAT_T140)); + + } } diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h index 719048b02..0cd748c03 100644 --- a/include/asterisk/frame.h +++ b/include/asterisk/frame.h @@ -274,8 +274,12 @@ extern struct ast_frame ast_null_frame; /*! MPEG4 Video */ #define AST_FORMAT_MP4_VIDEO (1 << 22) #define AST_FORMAT_VIDEO_MASK (((1 << 25)-1) & ~(AST_FORMAT_AUDIO_MASK)) -/*! T.140 Text format - ITU T.140, RFC 4351*/ -#define AST_FORMAT_T140 (1 << 25) +/*! T.140 Text format - ITU T.140, RFC 4103 */ +#define AST_FORMAT_T140 (1 << 26) +/*! T.140 RED Text format RFC 4103 */ +#define AST_FORMAT_T140RED (1 << 27) +/*! Maximum text mask */ +#define AST_FORMAT_MAX_TEXT (1 << 28)) #define AST_FORMAT_TEXT_MASK (((1 << 30)-1) & ~(AST_FORMAT_AUDIO_MASK) & ~(AST_FORMAT_VIDEO_MASK)) enum ast_control_frame_type { diff --git a/include/asterisk/rtp.h b/include/asterisk/rtp.h index 5a857f016..bb8358feb 100644 --- a/include/asterisk/rtp.h +++ b/include/asterisk/rtp.h @@ -51,6 +51,9 @@ extern "C" { /*! Maxmum number of payload defintions for a RTP session */ #define MAX_RTP_PT 256 +/*! T.140 Redundancy Maxium number of generations */ +#define RED_MAX_GENERATION 5 + #define FLAG_3389_WARNING (1 << 0) enum ast_rtp_options { @@ -67,6 +70,8 @@ enum ast_rtp_get_result { }; struct ast_rtp; +/*! T.140 Redundancy structure*/ +struct rtp_red; /*! \brief This is the structure that binds a channel (SIP/Jingle/H.323) to the RTP subsystem */ @@ -288,6 +293,22 @@ int ast_rtp_get_rtptimeout(struct ast_rtp *rtp); /* \brief Put RTP timeout timers on hold during another transaction, like T.38 */ void ast_rtp_set_rtptimers_onhold(struct ast_rtp *rtp); +/*! \brief Initalize t.140 redudancy + * \param ti time between each t140red frame is sent + * \param red_pt payloadtype for RTP packet + * \param pt payloadtype numbers for each generation including primary data + * \param num_gen number of redundant generations, primary data excluded + */ +int rtp_red_init(struct ast_rtp *rtp, int ti, int *pt, int num_gen); + +void red_init(struct rtp_red *red, const struct ast_frame *f); + + +/*! \brief Buffer t.140 data */ +void red_buffer_t140(struct ast_rtp *rtp, struct ast_frame *f); + + + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/main/frame.c b/main/frame.c index c49e8e661..75c6f91cc 100644 --- a/main/frame.c +++ b/main/frame.c @@ -119,6 +119,7 @@ static struct ast_format_list AST_FORMAT_LIST[] = { { AST_FORMAT_H263_PLUS, "h263p", 0, "H.263+ Video" }, /*!< H.263plus passthrough support See format_h263.c */ { AST_FORMAT_H264, "h264", 0, "H.264 Video" }, /*!< Passthrough support, see format_h263.c */ { AST_FORMAT_MP4_VIDEO, "mpeg4", 0, "MPEG4 Video" }, /*!< Passthrough support for MPEG4 */ + { AST_FORMAT_T140RED, "red", 1, "T.140 Realtime Text with redundancy"}, /*!< Redundant T.140 Realtime Text */ { AST_FORMAT_T140, "t140", 0, "Passthrough T.140 Realtime Text" }, /*!< Passthrough support for T.140 Realtime Text */ }; @@ -575,7 +576,7 @@ int ast_getformatbyname(const char *name) for (x = 0; x < sizeof(AST_FORMAT_LIST) / sizeof(AST_FORMAT_LIST[0]); x++) { if (all || !strcasecmp(AST_FORMAT_LIST[x].name,name) || - !strcasecmp(AST_FORMAT_LIST[x].name,ast_expand_codec_alias(name))) { + !strcasecmp(AST_FORMAT_LIST[x].name, ast_expand_codec_alias(name))) { format |= AST_FORMAT_LIST[x].bits; if (!all) break; diff --git a/main/rtp.c b/main/rtp.c index 64080a92d..e7f849686 100644 --- a/main/rtp.c +++ b/main/rtp.c @@ -177,6 +177,25 @@ struct ast_rtp { struct sockaddr_in strict_rtp_address; /*!< Remote address information for strict RTP purposes */ int set_marker_bit:1; /*!< Whether to set the marker bit or not */ + struct rtp_red *red; +}; + +static struct ast_frame *red_t140_to_red(struct rtp_red *red); +static int red_write(const void *data); + +struct rtp_red { + struct ast_frame t140; /*!< Primary data */ + struct ast_frame t140red; /*!< Redundant t140*/ + unsigned char pt[RED_MAX_GENERATION]; /*!< Payload types for redundancy data */ + unsigned char ts[RED_MAX_GENERATION]; /*!< Time stamps */ + unsigned char len[RED_MAX_GENERATION]; /*!< length of each generation */ + int num_gen; /*!< Number of generations */ + int schedid; /*!< Timer id */ + int ti; /*!< How long to buffer data before send */ + unsigned char t140red_data[64000]; + unsigned char buf_data[64000]; /*!< buffered primary data */ + int hdrlen; + long int prev_ts; }; /* Forward declarations */ @@ -1392,6 +1411,7 @@ struct ast_frame *ast_rtp_read(struct ast_rtp *rtp) unsigned int *rtpheader; struct rtpPayloadType rtpPT; struct ast_rtp *bridged = NULL; + int prev_seqno; /* If time is up, kill it */ if (rtp->sending_digit) @@ -1541,6 +1561,8 @@ struct ast_frame *ast_rtp_read(struct ast_rtp *rtp) } if ( (int)rtp->lastrxseqno - (int)seqno > 100) /* if so it would indicate that the sender cycled; allow for misordering */ rtp->cycles += RTP_SEQ_MOD; + + prev_seqno = rtp->lastrxseqno; rtp->lastrxseqno = seqno; @@ -1604,6 +1626,61 @@ struct ast_frame *ast_rtp_read(struct ast_rtp *rtp) rtp->f.data = rtp->rawdata + hdrlen + AST_FRIENDLY_OFFSET; rtp->f.offset = hdrlen + AST_FRIENDLY_OFFSET; rtp->f.seqno = seqno; + + if (rtp->f.subclass == AST_FORMAT_T140 && (int)seqno - (prev_seqno+1) > 0 && (int)seqno - (prev_seqno+1) < 10) { + unsigned char *data = rtp->f.data; + + memmove(rtp->f.data+3, rtp->f.data, rtp->f.datalen); + rtp->f.datalen +=3; + *data++ = 0xEF; + *data++ = 0xBF; + *data = 0xBD; + } + + if (rtp->f.subclass == AST_FORMAT_T140RED) { + unsigned char *data = rtp->f.data; + unsigned char *header_end; + int num_generations; + int header_length; + int len; + int diff =(int)seqno - (prev_seqno+1); /* if diff = 0, no drop*/ + int x; + + rtp->f.subclass = AST_FORMAT_T140; + header_end = memchr(data, ((*data) & 0x7f), rtp->f.datalen); + header_end++; + + header_length = header_end - data; + num_generations = header_length / 4; + len = header_length; + + if (!diff) { + for (x = 0; x < num_generations; x++) + len += data[x * 4 + 3]; + + if (!(rtp->f.datalen - len)) + return &ast_null_frame; + + rtp->f.data += len; + rtp->f.datalen -= len; + } else if (diff > num_generations && diff < 10) { + len -= 3; + rtp->f.data += len; + rtp->f.datalen -= len; + + data = rtp->f.data; + *data++ = 0xEF; + *data++ = 0xBF; + *data = 0xBD; + } else { + for ( x = 0; x < num_generations - diff; x++) + len += data[x * 4 + 3]; + + rtp->f.data += len; + rtp->f.datalen -= len; + } + } + if (rtp->f.subclass & AST_FORMAT_AUDIO_MASK) { rtp->f.samples = ast_codec_get_samples(&rtp->f); if (rtp->f.subclass == AST_FORMAT_SLINEAR) @@ -1674,6 +1751,7 @@ static struct { {{1, AST_FORMAT_H263_PLUS}, "video", "h263-1998"}, {{1, AST_FORMAT_H264}, "video", "H264"}, {{1, AST_FORMAT_MP4_VIDEO}, "video", "MP4V-ES"}, + {{1, AST_FORMAT_T140RED}, "text", "RED"}, {{1, AST_FORMAT_T140}, "text", "T140"}, }; @@ -1713,9 +1791,10 @@ static struct rtpPayloadType static_RTP_PT[MAX_RTP_PT] = { [98] = {1, AST_FORMAT_H263_PLUS}, [99] = {1, AST_FORMAT_H264}, [101] = {0, AST_RTP_DTMF}, - [102] = {1, AST_FORMAT_T140}, /* Real time text chat */ [103] = {1, AST_FORMAT_H263_PLUS}, [104] = {1, AST_FORMAT_MP4_VIDEO}, + [105] = {1, AST_FORMAT_T140RED}, /* Real time text chat (with redundancy encoding) */ + [106] = {1, AST_FORMAT_T140}, /* Real time text chat */ [110] = {1, AST_FORMAT_SPEEX}, [111] = {1, AST_FORMAT_G726}, [112] = {1, AST_FORMAT_G726_AAL2}, @@ -2382,6 +2461,11 @@ struct ast_rtp *ast_rtp_get_bridged(struct ast_rtp *rtp) void ast_rtp_stop(struct ast_rtp *rtp) { AST_SCHED_DEL(rtp->sched, rtp->rtcp->schedid); + if (rtp->red) { + AST_SCHED_DEL(rtp->sched, rtp->red->schedid); + free(rtp->red); + rtp->red = NULL; + } memset(&rtp->them.sin_addr, 0, sizeof(rtp->them.sin_addr)); memset(&rtp->them.sin_port, 0, sizeof(rtp->them.sin_port)); @@ -3141,7 +3225,7 @@ int ast_rtp_write(struct ast_rtp *rtp, struct ast_frame *_f) return 0; /* If there is no data length, return immediately */ - if (!_f->datalen) + if(!_f->datalen && !rtp->red) return 0; /* Make sure we have enough space for RTP header */ @@ -3150,6 +3234,13 @@ int ast_rtp_write(struct ast_rtp *rtp, struct ast_frame *_f) return -1; } + if (rtp->red) { + /* return 0; */ + /* no primary data or generations to send */ + if ((_f = red_t140_to_red(rtp->red)) == NULL) + return 0; + } + /* The bottom bit of a video subclass contains the marker bit */ subclass = _f->subclass; if (_f->frametype == AST_FRAME_VIDEO) @@ -4267,3 +4358,111 @@ void ast_rtp_init(void) __ast_rtp_reload(0); } +/*! \brief Write t140 redundacy frame + * \param data primary data to be buffered + */ +static int red_write(const void *data) +{ + struct ast_rtp *rtp = (struct ast_rtp*) data; + + ast_rtp_write(rtp, &rtp->red->t140); + + return 1; +} + +/*! \brief Construct a redundant frame + * \param red redundant data structure + */ +static struct ast_frame *red_t140_to_red(struct rtp_red *red) { + unsigned char *data = red->t140red.data; + int len = 0; + int i; + + /* replace most aged generation */ + if (red->len[0]) { + for (i = 1; i < red->num_gen+1; i++) + len += red->len[i]; + + memmove(&data[red->hdrlen], &data[red->hdrlen+red->len[0]], len); + } + + /* Store length of each generation and primary data length*/ + for (i = 0; i < red->num_gen; i++) + red->len[i] = red->len[i+1]; + red->len[i] = red->t140.datalen; + + /* write each generation length in red header */ + len = red->hdrlen; + for (i = 0; i < red->num_gen; i++) + len += data[i*4+3] = red->len[i]; + + /* add primary data to buffer */ + memcpy(&data[len], red->t140.data, red->t140.datalen); + red->t140red.datalen = len + red->t140.datalen; + + /* no primary data and no generations to send */ + if (len == red->hdrlen && !red->t140.datalen) + return NULL; + + /* reset t.140 buffer */ + red->t140.datalen = 0; + + return &red->t140red; +} + +/*! \brief Initialize t140 redundancy + * \param rtp + * \param ti buffer t140 for ti (msecs) before sending redundant frame + * \param red_data_pt Payloadtypes for primary- and generation-data + * \param num_gen numbers of generations (primary generation not encounted) + * +*/ +int rtp_red_init(struct ast_rtp *rtp, int ti, int *red_data_pt, int num_gen) +{ + struct rtp_red *r; + int x; + + if (!(r = ast_calloc(1, sizeof(struct rtp_red)))) + return -1; + + r->t140.frametype = AST_FRAME_TEXT; + r->t140.subclass = AST_FORMAT_T140RED; + r->t140.data = &r->buf_data; + + r->t140.ts = 0; + r->t140red = r->t140; + r->t140red.data = &r->t140red_data; + r->t140red.datalen = 0; + r->ti = ti; + r->num_gen = num_gen; + r->hdrlen = num_gen * 4 + 1; + r->prev_ts = 0; + + for (x = 0; x < num_gen; x++) { + r->pt[x] = red_data_pt[x]; + r->pt[x] |= 1 << 7; /* mark redundant generations pt */ + r->t140red_data[x*4] = r->pt[x]; + } + r->t140red_data[x*4] = r->pt[x] = red_data_pt[x]; /* primary pt */ + r->schedid = ast_sched_add(rtp->sched, ti, red_write, rtp); + rtp->red = r; + + r->t140.datalen = 0; + + return 0; +} + +/*! \brief Buffer t140 from chan_sip + * \param rtp + * \param f frame + */ +void red_buffer_t140(struct ast_rtp *rtp, struct ast_frame *f) +{ + if( f->datalen > -1 ) { + struct rtp_red *red = rtp->red; + memcpy(&red->buf_data[red->t140.datalen], f->data, f->datalen); + red->t140.datalen += f->datalen; + red->t140.ts = f->ts; + } +} +