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
This commit is contained in:
parent
7dd656be48
commit
f3a2d1775a
2
CHANGES
2
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
|
||||
-----------
|
||||
|
|
3
CREDITS
3
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
|
||||
|
|
|
@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
203
main/rtp.c
203
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Reference in New Issue