/* * Some SDP file parsing... * * (C) 2009-2015 by Holger Hans Peter Freyther * (C) 2009-2014 by On-Waves * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include struct sdp_rtp_map { /* the type */ int payload_type; /* null, static or later dynamic codec name */ char *codec_name; /* A pointer to the original line for later parsing */ char *map_line; int rate; int channels; }; int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec, int payload_type, const char *audio_name) { int rate = codec->rate; int channels = codec->channels; char audio_codec[64]; talloc_free(codec->subtype_name); codec->subtype_name = NULL; talloc_free(codec->audio_name); codec->audio_name = NULL; if (payload_type != PTYPE_UNDEFINED) codec->payload_type = payload_type; if (!audio_name) { switch (payload_type) { case 0: audio_name = "PCMU/8000/1"; break; case 3: audio_name = "GSM/8000/1"; break; case 8: audio_name = "PCMA/8000/1"; break; case 18: audio_name = "G729/8000/1"; break; default: /* Payload type is unknown, don't change rate and * channels. */ /* TODO: return value? */ return 0; } } if (sscanf(audio_name, "%63[^/]/%d/%d", audio_codec, &rate, &channels) < 1) return -EINVAL; codec->rate = rate; codec->channels = channels; codec->subtype_name = talloc_strdup(ctx, audio_codec); codec->audio_name = talloc_strdup(ctx, audio_name); if (!strcmp(audio_codec, "G729")) { codec->frame_duration_num = 10; codec->frame_duration_den = 1000; } else { codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; } if (payload_type < 0) { payload_type = 96; if (rate == 8000 && channels == 1) { if (!strcmp(audio_codec, "GSM")) payload_type = 3; else if (!strcmp(audio_codec, "PCMA")) payload_type = 8; else if (!strcmp(audio_codec, "PCMU")) payload_type = 0; else if (!strcmp(audio_codec, "G729")) payload_type = 18; } codec->payload_type = payload_type; } if (channels != 1) LOGP(DMGCP, LOGL_NOTICE, "Channels != 1 in SDP: '%s'\n", audio_name); return 0; } void codecs_initialize(void *ctx, struct sdp_rtp_map *codecs, int used) { int i; for (i = 0; i < used; ++i) { switch (codecs[i].payload_type) { case 0: codecs[i].codec_name = "PCMU"; codecs[i].rate = 8000; codecs[i].channels = 1; break; case 3: codecs[i].codec_name = "GSM"; codecs[i].rate = 8000; codecs[i].channels = 1; break; case 8: codecs[i].codec_name = "PCMA"; codecs[i].rate = 8000; codecs[i].channels = 1; break; case 18: codecs[i].codec_name = "G729"; codecs[i].rate = 8000; codecs[i].channels = 1; break; } } } void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used, int payload, char *audio_name) { int i; for (i = 0; i < used; ++i) { char audio_codec[64]; int rate = -1; int channels = -1; if (codecs[i].payload_type != payload) continue; if (sscanf(audio_name, "%63[^/]/%d/%d", audio_codec, &rate, &channels) < 1) { LOGP(DMGCP, LOGL_ERROR, "Failed to parse '%s'\n", audio_name); continue; } codecs[i].map_line = talloc_strdup(ctx, audio_name); codecs[i].codec_name = talloc_strdup(ctx, audio_codec); codecs[i].rate = rate; codecs[i].channels = channels; return; } LOGP(DMGCP, LOGL_ERROR, "Unconfigured PT(%d) with %s\n", payload, audio_name); } int is_codec_compatible(struct mgcp_endpoint *endp, struct sdp_rtp_map *codec) { char *bts_codec; char audio_codec[64]; if (!codec->codec_name) return 0; /* * GSM, GSM/8000 and GSM/8000/1 should all be compatible.. let's go * by name first. */ bts_codec = endp->tcfg->audio_name; if (sscanf(bts_codec, "%63[^/]/%*d/%*d", audio_codec) < 1) return 0; return strcasecmp(audio_codec, codec->codec_name) == 0; } int mgcp_parse_sdp_data(struct mgcp_endpoint *endp, struct mgcp_rtp_end *rtp, struct mgcp_parse_data *p) { struct sdp_rtp_map codecs[10]; int codecs_used = 0; char *line; int maxptime = -1; int i; int codecs_assigned = 0; void *tmp_ctx = talloc_new(NULL); memset(&codecs, 0, sizeof(codecs)); for_each_line(line, p->save) { switch (line[0]) { case 'o': case 's': case 't': case 'v': /* skip these SDP attributes */ break; case 'a': { int payload; int ptime, ptime2 = 0; char audio_name[64]; if (sscanf(line, "a=rtpmap:%d %63s", &payload, audio_name) == 2) { codecs_update(tmp_ctx, codecs, codecs_used, payload, audio_name); } else if (sscanf(line, "a=ptime:%d-%d", &ptime, &ptime2) >= 1) { if (ptime2 > 0 && ptime2 != ptime) rtp->packet_duration_ms = 0; else rtp->packet_duration_ms = ptime; } else if (sscanf(line, "a=maxptime:%d", &ptime2) == 1) { maxptime = ptime2; } break; } case 'm': { int port, rc; rc = sscanf(line, "m=audio %d RTP/AVP %d %d %d %d %d %d %d %d %d %d", &port, &codecs[0].payload_type, &codecs[1].payload_type, &codecs[2].payload_type, &codecs[3].payload_type, &codecs[4].payload_type, &codecs[5].payload_type, &codecs[6].payload_type, &codecs[7].payload_type, &codecs[8].payload_type, &codecs[9].payload_type); if (rc >= 2) { rtp->rtp_port = htons(port); rtp->rtcp_port = htons(port + 1); codecs_used = rc - 1; codecs_initialize(tmp_ctx, codecs, codecs_used); } break; } case 'c': { char ipv4[16]; if (sscanf(line, "c=IN IP4 %15s", ipv4) == 1) { inet_aton(ipv4, &rtp->addr); } break; } default: if (p->endp) LOGP(DMGCP, LOGL_NOTICE, "Unhandled SDP option: '%c'/%d on 0x%x\n", line[0], line[0], ENDPOINT_NUMBER(p->endp)); else LOGP(DMGCP, LOGL_NOTICE, "Unhandled SDP option: '%c'/%d\n", line[0], line[0]); break; } } /* Now select the primary and alt_codec */ for (i = 0; i < codecs_used && codecs_assigned < 2; ++i) { struct mgcp_rtp_codec *codec = codecs_assigned == 0 ? &rtp->codec : &rtp->alt_codec; if (endp->tcfg->no_audio_transcoding && !is_codec_compatible(endp, &codecs[i])) { LOGP(DMGCP, LOGL_NOTICE, "Skipping codec %s\n", codecs[i].codec_name); continue; } mgcp_set_audio_info(p->cfg, codec, codecs[i].payload_type, codecs[i].map_line); codecs_assigned += 1; } if (codecs_assigned > 0) { /* TODO/XXX: Store this per codec and derive it on use */ if (maxptime >= 0 && maxptime * rtp->codec.frame_duration_den > rtp->codec.frame_duration_num * 1500) { /* more than 1 frame */ rtp->packet_duration_ms = 0; } LOGP(DMGCP, LOGL_NOTICE, "Got media info via SDP: port %d, payload %d (%s), " "duration %d, addr %s\n", ntohs(rtp->rtp_port), rtp->codec.payload_type, rtp->codec.subtype_name ? rtp->codec.subtype_name : "unknown", rtp->packet_duration_ms, inet_ntoa(rtp->addr)); } talloc_free(tmp_ctx); return codecs_assigned > 0; }