/* * Asterisk -- A telephony toolkit for Linux. * * Real-time Protocol Support * * Copyright (C) 1999, Mark Spencer * * Mark Spencer * * This program is free software, distributed under the terms of * the GNU General Public License */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int dtmftimeout = 300; /* 300 samples */ struct ast_rtp { int s; char resp; struct ast_frame f; unsigned char rawdata[1024 + AST_FRIENDLY_OFFSET]; unsigned int ssrc; unsigned int lastts; unsigned int lastrxts; int dtmfcount; struct sockaddr_in us; struct sockaddr_in them; struct timeval rxcore; struct timeval txcore; struct ast_smoother *smoother; int *ioid; unsigned short seqno; struct sched_context *sched; struct io_context *io; void *data; ast_rtp_callback callback; }; void ast_rtp_set_data(struct ast_rtp *rtp, void *data) { rtp->data = data; } void ast_rtp_set_callback(struct ast_rtp *rtp, ast_rtp_callback callback) { rtp->callback = callback; } static void send_dtmf(struct ast_rtp *rtp) { printf("Sending dtmf: %d (%c)\n", rtp->resp, rtp->resp); rtp->f.frametype = AST_FRAME_DTMF; rtp->f.subclass = rtp->resp; rtp->f.datalen = 0; rtp->f.timelen = 0; rtp->f.mallocd = 0; rtp->f.src = "RTP"; rtp->resp = 0; if (rtp->callback) rtp->callback(rtp, &rtp->f, rtp->data); } static void process_rfc2833(struct ast_rtp *rtp, unsigned char *data, int len) { unsigned int event; char resp = 0; event = ntohl(*((unsigned int *)(data))); event >>= 24; #if 0 printf("Event: %08x (len = %d)\n", event, len); #endif if (event < 10) { resp = '0' + event; } else if (event < 11) { resp = '*'; } else if (event < 12) { resp = '#'; } else if (event < 16) { resp = 'A' + (event - 12); } if (rtp->resp && (rtp->resp != resp)) { send_dtmf(rtp); } rtp->resp = resp; rtp->dtmfcount = dtmftimeout; } static void process_type121(struct ast_rtp *rtp, unsigned char *data, int len) { char resp = 0; unsigned char b0,b1,b2,b3,b4,b5,b6,b7; b0=*(data+0);b1=*(data+1);b2=*(data+2);b3=*(data+3); b4=*(data+4);b5=*(data+5);b6=*(data+6);b7=*(data+7); // printf("%u %u %u %u %u %u %u %u\n",b0,b1,b2,b3,b4,b5,b6,b7); if (b2==32) { // printf("Start %d\n",b3); if (b4==0) { // printf("Detection point for DTMF %d\n",b3); if (b3<10) { resp='0'+b3; } else if (b3<11) { resp='*'; } else if (b3<12) { resp='#'; } else if (b3<16) { resp='A'+(b3-12); } rtp->resp=resp; send_dtmf(rtp); } } if (b2==3) { // printf("Stop(3) %d\n",b3); } if (b2==0) { // printf("Stop(0) %d\n",b3); } } static int rtpread(int *id, int fd, short events, void *cbdata) { struct ast_rtp *rtp = cbdata; int res; struct sockaddr_in sin; int len; unsigned int seqno; int payloadtype; int hdrlen = 12; unsigned int timestamp; unsigned int *rtpheader; len = sizeof(sin); res = recvfrom(rtp->s, rtp->rawdata + AST_FRIENDLY_OFFSET, sizeof(rtp->rawdata) - AST_FRIENDLY_OFFSET, 0, (struct sockaddr *)&sin, &len); rtpheader = (unsigned int *)(rtp->rawdata + AST_FRIENDLY_OFFSET); if (res < 0) { ast_log(LOG_WARNING, "RTP Read error: %s\n", strerror(errno)); if (errno == EBADF) CRASH; return 1; } if (res < hdrlen) { ast_log(LOG_WARNING, "RTP Read too short\n"); return 1; } /* Get fields */ seqno = ntohl(rtpheader[0]); payloadtype = (seqno & 0x7f0000) >> 16; seqno &= 0xffff; timestamp = ntohl(rtpheader[1]); #if 0 printf("Got RTP packet from %s:%d (type %d, seq %d, ts %d, len = %d)\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), payloadtype, seqno, timestamp,res - hdrlen); #endif rtp->f.frametype = AST_FRAME_VOICE; rtp->f.subclass = rtp2ast(payloadtype); if (rtp->f.subclass < 0) { if (payloadtype == 101) { /* It's special -- rfc2833 process it */ process_rfc2833(rtp, rtp->rawdata + AST_FRIENDLY_OFFSET + hdrlen, res - hdrlen); } else if (payloadtype == 121) { /* CISCO proprietary DTMF bridge */ process_type121(rtp, rtp->rawdata + AST_FRIENDLY_OFFSET + hdrlen, res - hdrlen); } else { ast_log(LOG_NOTICE, "Unknown RTP codec %d received\n", payloadtype); } return 1; } if (!rtp->lastrxts) rtp->lastrxts = timestamp; if (rtp->dtmfcount) { #if 0 printf("dtmfcount was %d\n", rtp->dtmfcount); #endif rtp->dtmfcount -= (timestamp - rtp->lastrxts); if (rtp->dtmfcount < 0) rtp->dtmfcount = 0; #if 0 if (dtmftimeout != rtp->dtmfcount) printf("dtmfcount is %d\n", rtp->dtmfcount); #endif } rtp->lastrxts = timestamp; /* Send any pending DTMF */ if (rtp->resp && !rtp->dtmfcount) { send_dtmf(rtp); /* Setup the voice frame again */ rtp->f.frametype = AST_FRAME_VOICE; rtp->f.subclass = rtp2ast(payloadtype); } rtp->f.mallocd = 0; rtp->f.datalen = res - hdrlen; rtp->f.data = rtp->rawdata + hdrlen + AST_FRIENDLY_OFFSET; rtp->f.offset = hdrlen + AST_FRIENDLY_OFFSET; switch(rtp->f.subclass) { case AST_FORMAT_ULAW: case AST_FORMAT_ALAW: rtp->f.timelen = rtp->f.datalen / 8; break; case AST_FORMAT_SLINEAR: rtp->f.timelen = rtp->f.datalen / 16; break; case AST_FORMAT_GSM: rtp->f.timelen = 20 * (rtp->f.datalen / 33); break; case AST_FORMAT_ADPCM: rtp->f.timelen = rtp->f.datalen / 4; break; case AST_FORMAT_G729A: rtp->f.timelen = rtp->f.datalen; break; default: ast_log(LOG_NOTICE, "Unable to calculate timelen for format %d\n", rtp->f.subclass); break; } rtp->f.src = "RTP"; if (rtp->callback) rtp->callback(rtp, &rtp->f, rtp->data); return 1; } static struct { int rtp; int ast; char *label; } cmap[] = { { 0, AST_FORMAT_ULAW, "PCMU" }, { 3, AST_FORMAT_GSM, "GSM" }, { 4, AST_FORMAT_G723_1, "G723" }, { 5, AST_FORMAT_ADPCM, "ADPCM" }, { 8, AST_FORMAT_ALAW, "PCMA" }, { 18, AST_FORMAT_G729A, "G729" }, }; int rtp2ast(int id) { int x; for (x=0;xthem.sin_family = AF_INET; rtp->us.sin_family = AF_INET; rtp->s = socket(AF_INET, SOCK_DGRAM, 0); rtp->ssrc = rand(); rtp->seqno = rand() & 0xffff; if (rtp->s < 0) { free(rtp); ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno)); return NULL; } flags = fcntl(rtp->s, F_GETFL); fcntl(rtp->s, F_SETFL, flags | O_NONBLOCK); for (;;) { /* Find us a place */ x = (rand() % (65000-1025)) + 1025; /* Must be an even port number by RTP spec */ x = x & ~1; rtp->us.sin_port = htons(x); if (!bind(rtp->s, &rtp->us, sizeof(rtp->us))) break; if (errno != EADDRINUSE) { ast_log(LOG_WARNING, "Unexpected bind error: %s\n", strerror(errno)); close(rtp->s); free(rtp); return NULL; } } rtp->io = io; rtp->sched = sched; rtp->ioid = ast_io_add(rtp->io, rtp->s, rtpread, AST_IO_IN, rtp); return rtp; } void ast_rtp_set_peer(struct ast_rtp *rtp, struct sockaddr_in *them) { rtp->them.sin_port = them->sin_port; rtp->them.sin_addr = them->sin_addr; } void ast_rtp_get_us(struct ast_rtp *rtp, struct sockaddr_in *us) { memcpy(us, &rtp->us, sizeof(rtp->us)); } void ast_rtp_destroy(struct ast_rtp *rtp) { if (rtp->smoother) ast_smoother_free(rtp->smoother); if (rtp->ioid) ast_io_remove(rtp->io, rtp->ioid); if (rtp->s > -1) close(rtp->s); free(rtp); } static unsigned int calc_txstamp(struct ast_rtp *rtp) { struct timeval now; unsigned int ms; if (!rtp->txcore.tv_sec && !rtp->txcore.tv_usec) { gettimeofday(&rtp->txcore, NULL); } gettimeofday(&now, NULL); ms = (now.tv_sec - rtp->txcore.tv_sec) * 1000; ms += (now.tv_usec - rtp->txcore.tv_usec) / 1000; return ms; } static int ast_rtp_raw_write(struct ast_rtp *rtp, struct ast_frame *f, int codec) { unsigned int *rtpheader; int hdrlen = 12; int res; int ms; int pred; ms = calc_txstamp(rtp); /* Default prediction */ pred = ms * 8; switch(f->subclass) { case AST_FORMAT_ULAW: case AST_FORMAT_ALAW: /* If we're within +/- 20ms from when where we predict we should be, use that */ pred = rtp->lastts + f->datalen; break; case AST_FORMAT_G729A: pred = rtp->lastts + f->datalen * 8; break; default: ast_log(LOG_WARNING, "Not sure about timestamp format for codec format %d\n", f->subclass); } /* Re-calculate last TS */ rtp->lastts = ms * 8; /* If it's close to ou prediction, go for it */ if (abs(rtp->lastts - pred) < 640) rtp->lastts = pred; #if 0 else printf("Difference is %d, ms is %d\n", abs(rtp->lastts - pred), ms); #endif /* Get a pointer to the header */ rtpheader = (unsigned int *)(f->data - hdrlen); rtpheader[0] = htonl((2 << 30) | (codec << 16) | (rtp->seqno++)); rtpheader[1] = htonl(rtp->lastts); rtpheader[2] = htonl(rtp->ssrc); if (rtp->them.sin_port && rtp->them.sin_addr.s_addr) { res = sendto(rtp->s, (void *)rtpheader, f->datalen + hdrlen, 0, &rtp->them, sizeof(rtp->them)); if (res <0) ast_log(LOG_NOTICE, "RTP Transmission error to %s:%d: %s\n", inet_ntoa(rtp->them.sin_addr), ntohs(rtp->them.sin_port), strerror(errno)); #if 0 printf("Sent %d bytes of RTP data to %s:%d\n", res, inet_ntoa(rtp->them.sin_addr), ntohs(rtp->them.sin_port)); #endif } return 0; } int ast_rtp_write(struct ast_rtp *rtp, struct ast_frame *_f) { struct ast_frame *f; int codec; int hdrlen = 12; /* Make sure we have enough space for RTP header */ if (_f->frametype != AST_FRAME_VOICE) { ast_log(LOG_WARNING, "RTP can only send voice\n"); return -1; } codec = ast2rtp(_f->subclass); if (codec < 0) { ast_log(LOG_WARNING, "Don't know how to send format %d packets with RTP\n", _f->subclass); return -1; } switch(_f->subclass) { case AST_FORMAT_ULAW: case AST_FORMAT_ALAW: if (!rtp->smoother) { rtp->smoother = ast_smoother_new(160); } if (!rtp->smoother) { ast_log(LOG_WARNING, "Unable to create smoother :(\n"); return -1; } ast_smoother_feed(rtp->smoother, _f); while((f = ast_smoother_read(rtp->smoother))) ast_rtp_raw_write(rtp, f, codec); break; case AST_FORMAT_G729A: if (!rtp->smoother) { rtp->smoother = ast_smoother_new(20); } if (!rtp->smoother) { ast_log(LOG_WARNING, "Unable to create g729 smoother :(\n"); return -1; } ast_smoother_feed(rtp->smoother, _f); while((f = ast_smoother_read(rtp->smoother))) ast_rtp_raw_write(rtp, f, codec); break; default: ast_log(LOG_WARNING, "Not sure about sending format %d packets\n", _f->subclass); if (_f->offset < hdrlen) { f = ast_frdup(_f); } else { f = _f; } ast_rtp_raw_write(rtp, f, codec); } return 0; }