Merge branch 'zecke/features/sdp-codec-handling'

Move forward while preserving the legacy handling. Beging to
extract SDP rtpmap information and select codecs atfer this.
It is a foundation we can now build further and better check
ons.
This commit is contained in:
Holger Hans Peter Freyther 2015-08-14 15:48:54 +02:00
commit a334e90ddf
5 changed files with 421 additions and 221 deletions

View File

@ -22,6 +22,8 @@
#pragma once
#include <string.h>
#include <osmocom/core/select.h>
#define CI_UNUSED 0
@ -203,11 +205,51 @@ struct mgcp_endpoint {
} osmux;
};
#define for_each_line(line, save) \
for (line = strline_r(NULL, &save); line;\
line = strline_r(NULL, &save))
static inline char *strline_r(char *str, char **saveptr)
{
char *result;
if (str)
*saveptr = str;
result = *saveptr;
if (*saveptr != NULL) {
*saveptr = strpbrk(*saveptr, "\r\n");
if (*saveptr != NULL) {
char *eos = *saveptr;
if ((*saveptr)[0] == '\r' && (*saveptr)[1] == '\n')
(*saveptr)++;
(*saveptr)++;
if ((*saveptr)[0] == '\0')
*saveptr = NULL;
*eos = '\0';
}
}
return result;
}
#define ENDPOINT_NUMBER(endp) abs((int)(endp - endp->tcfg->endpoints))
struct mgcp_msg_ptr {
unsigned int start;
unsigned int length;
/**
* Internal structure while parsing a request
*/
struct mgcp_parse_data {
struct mgcp_config *cfg;
struct mgcp_endpoint *endp;
char *trans;
char *save;
int found;
};
int mgcp_send_dummy(struct mgcp_endpoint *endp);
@ -260,5 +302,21 @@ enum {
MGCP_DEST_BTS,
};
#define MGCP_DUMMY_LOAD 0x23
/**
* SDP related information
*/
/* Assume audio frame length of 20ms */
#define DEFAULT_RTP_AUDIO_FRAME_DUR_NUM 20
#define DEFAULT_RTP_AUDIO_FRAME_DUR_DEN 1000
#define DEFAULT_RTP_AUDIO_PACKET_DURATION_MS 20
#define DEFAULT_RTP_AUDIO_DEFAULT_RATE 8000
#define DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS 1
#define PTYPE_UNDEFINED (-1)
int mgcp_parse_sdp_data(struct mgcp_endpoint *endp, struct mgcp_rtp_end *rtp, struct mgcp_parse_data *p);
int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec,
int payload_type, const char *audio_name);

View File

@ -8,7 +8,8 @@ noinst_LIBRARIES = libmgcp.a
noinst_HEADERS = g711common.h
libmgcp_a_SOURCES = mgcp_protocol.c mgcp_network.c mgcp_vty.c mgcp_osmux.c
libmgcp_a_SOURCES = mgcp_protocol.c mgcp_network.c mgcp_vty.c mgcp_osmux.c \
mgcp_sdp.c
if BUILD_MGCP_TRANSCODING
libmgcp_a_SOURCES += mgcp_transcode.c

View File

@ -24,7 +24,6 @@
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <limits.h>
#include <unistd.h>
@ -41,57 +40,9 @@
for (line = strtok_r(NULL, "\r\n", &save); line;\
line = strtok_r(NULL, "\r\n", &save))
#define for_each_line(line, save) \
for (line = strline_r(NULL, &save); line;\
line = strline_r(NULL, &save))
char *strline_r(char *str, char **saveptr)
{
char *result;
if (str)
*saveptr = str;
result = *saveptr;
if (*saveptr != NULL) {
*saveptr = strpbrk(*saveptr, "\r\n");
if (*saveptr != NULL) {
char *eos = *saveptr;
if ((*saveptr)[0] == '\r' && (*saveptr)[1] == '\n')
(*saveptr)++;
(*saveptr)++;
if ((*saveptr)[0] == '\0')
*saveptr = NULL;
*eos = '\0';
}
}
return result;
}
/* Assume audio frame length of 20ms */
#define DEFAULT_RTP_AUDIO_FRAME_DUR_NUM 20
#define DEFAULT_RTP_AUDIO_FRAME_DUR_DEN 1000
#define DEFAULT_RTP_AUDIO_PACKET_DURATION_MS 20
#define DEFAULT_RTP_AUDIO_DEFAULT_RATE 8000
#define DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS 1
#define PTYPE_UNDEFINED (-1)
static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end);
struct mgcp_parse_data {
struct mgcp_config *cfg;
struct mgcp_endpoint *endp;
char *trans;
char *save;
int found;
};
struct mgcp_request {
char *name;
struct msgb *(*handle_request) (struct mgcp_parse_data *data);
@ -599,72 +550,6 @@ static int parse_conn_mode(const char *msg, struct mgcp_endpoint *endp)
return ret;
}
static int 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 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, "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;
}
static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_rtp_end *end,
struct mgcp_port_range *range,
int (*alloc)(struct mgcp_endpoint *endp, int port))
@ -735,103 +620,6 @@ static int allocate_ports(struct mgcp_endpoint *endp)
return 0;
}
static int parse_sdp_data(struct mgcp_rtp_end *rtp, struct mgcp_parse_data *p)
{
char *line;
int found_media = 0;
/* TODO/XXX make it more generic */
int audio_payload = -1;
int audio_payload_alt = -1;
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 (audio_payload == -1)
break;
if (sscanf(line, "a=rtpmap:%d %63s",
&payload, audio_name) == 2) {
if (payload == audio_payload)
set_audio_info(p->cfg, &rtp->codec,
payload, audio_name);
else if (payload == audio_payload_alt)
set_audio_info(p->cfg, &rtp->alt_codec,
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) {
/* TODO/XXX: Store this per codec and derive it on use */
if (ptime2 * rtp->codec.frame_duration_den >
rtp->codec.frame_duration_num * 1500)
/* more than 1 frame */
rtp->packet_duration_ms = 0;
}
break;
}
case 'm': {
int port, rc;
audio_payload = -1;
audio_payload_alt = -1;
rc = sscanf(line, "m=audio %d RTP/AVP %d %d",
&port, &audio_payload, &audio_payload_alt);
if (rc >= 2) {
rtp->rtp_port = htons(port);
rtp->rtcp_port = htons(port + 1);
found_media = 1;
set_audio_info(p->cfg, &rtp->codec, audio_payload, NULL);
if (rc == 3)
set_audio_info(p->cfg, &rtp->alt_codec,
audio_payload_alt, NULL);
}
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;
}
}
if (found_media)
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));
return found_media;
}
/* Set the LCO from a string (see RFC 3435).
* The string is stored in the 'string' field. A NULL string is handled excatly
* like an empty string, the 'string' field is never NULL after this function
@ -1036,13 +824,13 @@ mgcp_header_done:
endp->allocated = 1;
/* set up RTP media parameters */
set_audio_info(p->cfg, &endp->bts_end.codec, tcfg->audio_payload, tcfg->audio_name);
mgcp_set_audio_info(p->cfg, &endp->bts_end.codec, tcfg->audio_payload, tcfg->audio_name);
endp->bts_end.fmtp_extra = talloc_strdup(tcfg->endpoints,
tcfg->audio_fmtp_extra);
if (have_sdp)
parse_sdp_data(&endp->net_end, p);
mgcp_parse_sdp_data(endp, &endp->net_end, p);
else if (endp->local_options.codec)
set_audio_info(p->cfg, &endp->net_end.codec,
mgcp_set_audio_info(p->cfg, &endp->net_end.codec,
PTYPE_UNDEFINED, endp->local_options.codec);
if (p->cfg->bts_force_ptime) {
@ -1143,7 +931,7 @@ static struct msgb *handle_modify_con(struct mgcp_parse_data *p)
case '\0':
/* SDP file begins */
have_sdp = 1;
parse_sdp_data(&endp->net_end, p);
mgcp_parse_sdp_data(endp, &endp->net_end, p);
/* This will exhaust p->save, so the loop will
* terminate next time.
*/
@ -1159,7 +947,7 @@ static struct msgb *handle_modify_con(struct mgcp_parse_data *p)
local_options);
if (!have_sdp && endp->local_options.codec)
set_audio_info(p->cfg, &endp->net_end.codec,
mgcp_set_audio_info(p->cfg, &endp->net_end.codec,
PTYPE_UNDEFINED, endp->local_options.codec);
if (setup_rtp_processing(endp) != 0)

View File

@ -0,0 +1,294 @@
/*
* Some SDP file parsing...
*
* (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
* (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 <http://www.gnu.org/licenses/>.
*
*/
#include <openbsc/mgcp.h>
#include <openbsc/mgcp_internal.h>
#include <errno.h>
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 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, "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 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];
/*
* 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;
}

View File

@ -340,6 +340,31 @@ static void test_strline(void)
"a=rtpmap:101 FOO/8000\r\n" \
"a=ptime:40\r\n"
#define CRCX_MULT_GSM_EXACT \
"CRCX 259260421 5@mgw MGCP 1.0\r\n" \
"C: 1355c6041e\r\n" \
"I: 3\r\n" \
"L: p:20, a:GSM, nt:IN\r\n" \
"M: recvonly\r\n" \
"\r\n" \
"v=0\r\n" \
"o=- 1439038275 1439038275 IN IP4 192.168.181.247\r\n" \
"s=-\r\nc=IN IP4 192.168.181.247\r\n" \
"t=0 0\r\nm=audio 29084 RTP/AVP 0 8 3 18 4 96 97 101\r\n" \
"a=rtpmap:0 PCMU/8000\r\n" \
"a=rtpmap:8 PCMA/8000\r\n" \
"a=rtpmap:3 gsm/8000\r\n" \
"a=rtpmap:18 G729/8000\r\n" \
"a=fmtp:18 annexb=no\r\n" \
"a=rtpmap:4 G723/8000\r\n" \
"a=rtpmap:96 iLBC/8000\r\n" \
"a=fmtp:96 mode=20\r\n" \
"a=rtpmap:97 iLBC/8000\r\n" \
"a=fmtp:97 mode=30\r\n" \
"a=rtpmap:101 telephone-event/8000\r\n" \
"a=fmtp:101 0-15\r\n" \
"a=recvonly\r\n"
struct mgcp_test {
const char *name;
const char *req;
@ -1011,6 +1036,40 @@ static void test_multilple_codec(void)
OSMO_ASSERT(endp->net_end.codec.payload_type == 18);
OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1);
/* Allocate 5@mgw at select GSM.. */
last_endpoint = -1;
inp = create_msg(CRCX_MULT_GSM_EXACT);
talloc_free(cfg->trunk.audio_name);
cfg->trunk.audio_name = "GSM/8000";
cfg->trunk.no_audio_transcoding = 1;
resp = mgcp_handle_message(cfg, inp);
msgb_free(inp);
msgb_free(resp);
OSMO_ASSERT(last_endpoint == 5);
endp = &cfg->trunk.endpoints[last_endpoint];
OSMO_ASSERT(endp->net_end.codec.payload_type == 3);
OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1);
/* Check what happens without that flag */
/* Free the previous endpoint and the data ... */
mgcp_release_endp(endp);
talloc_free(endp->last_response);
talloc_free(endp->last_trans);
endp->last_response = endp->last_trans = NULL;
last_endpoint = -1;
inp = create_msg(CRCX_MULT_GSM_EXACT);
cfg->trunk.no_audio_transcoding = 0;
resp = mgcp_handle_message(cfg, inp);
msgb_free(inp);
msgb_free(resp);
OSMO_ASSERT(last_endpoint == 5);
endp = &cfg->trunk.endpoints[last_endpoint];
OSMO_ASSERT(endp->net_end.codec.payload_type == 0);
OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 8);
talloc_free(cfg);
}