From: Andreas Eversberg Date: Wed, 1 Feb 2012 16:52:36 +0000 (+0100) Subject: Adding negotiation of speech codecs between GSM and SIP when using rtp-bridge X-Git-Url: http://git.eversberg.eu/gitweb.cgi?p=lcr.git;a=commitdiff_plain;h=f854931ffbee9464b278c433c4fdc7c3ea5af2fb Adding negotiation of speech codecs between GSM and SIP when using rtp-bridge Since LCR does not put hands on any RTP frame when directly bridged between OpenBSC and SIP, it will now allow all speech codecs that are commonly supported by MS and remote SIP endpoint. It must be noted that OpenBSC must support forwarding the codec types that MS and remote SIP endpoints support. Currently LCR negotiates the following codecs for GSM: - Full Rate - EFR - AMR - Half Rate --- diff --git a/gsm.cpp b/gsm.cpp index 63f7545..2c1bace 100644 --- a/gsm.cpp +++ b/gsm.cpp @@ -27,6 +27,10 @@ extern "C" { //struct lcr_gsm *gsm = NULL; +#define RTP_PT_GSM_HALF 96 +#define RTP_PT_GSM_EFR 97 +#define RTP_PT_GSM_AMR 98 + int new_callref = 1; /* names of MNCC-SAP */ @@ -35,6 +39,7 @@ static const struct _value_string { const char *name; } mncc_names[] = { { 0, "New call ref" }, + { 1, "Codec negotiation" }, { MNCC_SETUP_REQ, "MNCC_SETUP_REQ" }, { MNCC_SETUP_IND, "MNCC_SETUP_IND" }, { MNCC_SETUP_RSP, "MNCC_SETUP_RSP" }, @@ -82,6 +87,7 @@ static const struct _value_string { { MNCC_STOP_DTMF_REQ, "MNCC_STOP_DTMF_REQ" }, { MNCC_HOLD_REQ, "MNCC_HOLD_REQ " }, { MNCC_RETRIEVE_REQ, "MNCC_RETRIEVE_REQ" }, + { MNCC_LCHAN_MODIFY, "MNCC_LCHAN_MODIFY" }, { 0, NULL } }; @@ -139,6 +145,7 @@ Pgsm::Pgsm(int type, char *portname, struct port_settings *settings, struct inte p_g_rtp_bridge = 0; if (interface->rtp_bridge) p_g_rtp_bridge = 1; + p_g_rtp_payloads = 0; memset(&p_g_samples, 0, sizeof(p_g_samples)); SCPY(p_g_interface_name, interface->name); p_callerinfo.itype = (interface->extension)?INFO_ITYPE_ISDN_EXTENSION:INFO_ITYPE_ISDN; @@ -161,6 +168,7 @@ Pgsm::Pgsm(int type, char *portname, struct port_settings *settings, struct inte } p_g_rxpos = 0; p_g_tch_connected = 0; + p_g_payload_type = -1; PDEBUG(DEBUG_GSM, "Created new GSMPort(%s).\n", portname); } @@ -315,7 +323,7 @@ void gsm_trace_header(const char *interface_name, class Pgsm *port, unsigned int SCAT(msgtext, " ----"); /* init trace with given values */ - start_trace(0, + start_trace(-1, interface, port?numberrize_callerinfo(port->p_callerinfo.id, port->p_callerinfo.ntype, options.national, options.international):NULL, port?port->p_dialinginfo.id:NULL, @@ -325,31 +333,42 @@ void gsm_trace_header(const char *interface_name, class Pgsm *port, unsigned int msgtext); } -/* PROCEEDING INDICATION */ -void Pgsm::call_conf_ind(unsigned int msg_type, unsigned int callref, struct gsm_mncc *mncc) +/* modify lchan to given payload type */ +void Pgsm::modify_lchan(unsigned char payload_type) { struct gsm_mncc *mode; - gsm_trace_header(p_g_interface_name, this, msg_type, DIRECTION_IN); - if (mncc->fields & MNCC_F_CAUSE) { - add_trace("cause", "coding", "%d", mncc->cause.coding); - add_trace("cause", "location", "%", mncc->cause.location); - add_trace("cause", "value", "%", mncc->cause.value); - } - end_trace(); + /* already modified to that payload type */ + if (p_g_payload_type == payload_type) + return; - /* modify lchan to GSM codec V1 */ + p_g_payload_type = payload_type; gsm_trace_header(p_g_interface_name, this, MNCC_LCHAN_MODIFY, DIRECTION_OUT); mode = create_mncc(MNCC_LCHAN_MODIFY, p_g_callref); - mode->lchan_mode = 0x01; /* GSM V1 */ - mode->lchan_type = 0x02; + switch (payload_type) { + case RTP_PT_GSM_EFR: + add_trace("speech", "version", "EFR given"); + mode->lchan_mode = 0x21; /* GSM V2 */ + break; + case RTP_PT_GSM_AMR: + add_trace("speech", "version", "AMR given"); + mode->lchan_mode = 0x41; /* GSM V3 */ + break; + case RTP_PT_GSM_HALF: + add_trace("speech", "version", "Half Rate given"); + mode->lchan_mode = 0x05; /* GSM V1 */ + break; + default: + add_trace("speech", "version", "Full Rate given"); + mode->lchan_mode = 0x01; /* GSM V1 HR */ + } + mode->lchan_type = 0x02; /* FIXME: unused */ add_trace("mode", NULL, "0x%02x", mode->lchan_mode); end_trace(); send_and_free_mncc(p_g_lcr_gsm, mode->msg_type, mode); - } -/* CALL PROCEEDING INDICATION */ +/* CALL PROCEEDING INDICATION (from network) */ void Pgsm::call_proc_ind(unsigned int msg_type, unsigned int callref, struct gsm_mncc *mncc) { struct lcr_msg *message; @@ -435,7 +454,17 @@ void Pgsm::setup_cnf(unsigned int msg_type, unsigned int callref, struct gsm_mnc message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_CONNECT); memcpy(&message->param.connectinfo, &p_connectinfo, sizeof(struct connect_info)); - message->param.connectinfo.rtpinfo.payload_type = 3; /* FIXME: receive payload type from peer */ + + /* if we have a bridge, but not yet modified, the phone accepts out requested payload. + * we force the first in list */ + if (p_g_rtp_bridge) { + if (p_g_payload_type < 0) { + /* modify to first given payload */ + modify_lchan(p_g_rtp_payload_types[0]); + } + message->param.connectinfo.rtpinfo.payload_types[0] = p_g_payload_type; + message->param.connectinfo.rtpinfo.payloads = 1; + } if (p_g_rtp_bridge) { struct gsm_mncc_rtp *rtp; @@ -641,6 +670,9 @@ void Pgsm::message_progress(unsigned int epoint_id, int message_id, union parame rtp->ip = param->progressinfo.rtpinfo.ip; rtp->port = param->progressinfo.rtpinfo.port; send_and_free_mncc(p_g_lcr_gsm, rtp->msg_type, rtp); + + /* modify channel to accepted payload type */ + modify_lchan(param->progressinfo.rtpinfo.payload_types[0]); } } @@ -748,8 +780,10 @@ void Pgsm::message_connect(unsigned int epoint_id, int message_id, union paramet rtp->ip = param->connectinfo.rtpinfo.ip; rtp->port = param->connectinfo.rtpinfo.port; send_and_free_mncc(p_g_lcr_gsm, rtp->msg_type, rtp); - } + /* modify channel to accepted payload type */ + modify_lchan(param->connectinfo.rtpinfo.payload_types[0]); + } } /* MESSAGE_DISCONNECT */ diff --git a/gsm.h b/gsm.h index 671aab6..421a098 100644 --- a/gsm.h +++ b/gsm.h @@ -53,10 +53,13 @@ class Pgsm : public Port signed short p_g_rxdata[160]; /* receive audio buffer */ int p_g_rxpos; /* position in audio buffer 0..159 */ int p_g_tch_connected; /* indicates if audio is connected */ + int p_g_payload_type; /* current payload type or -1 if not set */ int p_g_rtp_bridge; /* if we use a bridge */ unsigned int p_g_rtp_ip_remote; /* stores ip */ unsigned short p_g_rtp_port_remote; /* stores port */ + int p_g_rtp_payloads; + int p_g_rtp_payload_types[8]; void frame_send(void *_frame); void frame_receive(void *_frame); @@ -64,7 +67,7 @@ class Pgsm : public Port int bridge_rx(unsigned char *data, int len); int hunt_bchannel(void); - void call_conf_ind(unsigned int msg_type, unsigned int callref, struct gsm_mncc *gsm); + void modify_lchan(unsigned char payload_type); void call_proc_ind(unsigned int msg_type, unsigned int callref, struct gsm_mncc *mncc); void alert_ind(unsigned int msg_type, unsigned int callref, struct gsm_mncc *mncc); void setup_cnf(unsigned int msg_type, unsigned int callref, struct gsm_mncc *mncc); diff --git a/gsm_bs.cpp b/gsm_bs.cpp index 29888be..ae763ca 100644 --- a/gsm_bs.cpp +++ b/gsm_bs.cpp @@ -14,6 +14,11 @@ struct lcr_gsm *gsm_bs = NULL; +#define RTP_PT_GSM_FULL 3 +#define RTP_PT_GSM_HALF 96 +#define RTP_PT_GSM_EFR 97 +#define RTP_PT_GSM_AMR 98 + /* * DTMF stuff */ @@ -62,6 +67,74 @@ Pgsm_bs::~Pgsm_bs() PDEBUG(DEBUG_GSM, "Destroyed GSM BS process(%s).\n", p_name); } +/* PROCEEDING INDICATION (from MS) */ +void Pgsm_bs::call_conf_ind(unsigned int msg_type, unsigned int callref, struct gsm_mncc *mncc) +{ + unsigned char payload_types[8]; + int payloads = 0; + + gsm_trace_header(p_g_interface_name, this, msg_type, DIRECTION_IN); + if (mncc->fields & MNCC_F_CAUSE) { + add_trace("cause", "coding", "%d", mncc->cause.coding); + add_trace("cause", "location", "%", mncc->cause.location); + add_trace("cause", "value", "%", mncc->cause.value); + } + end_trace(); + + new_state(PORT_STATE_OUT_PROCEEDING); + + /* get list of offered payload types + * if list ist empty, the FR V1 is selected */ + select_payload_type(mncc, payload_types, &payloads, sizeof(payload_types)); + /* if no given payload type is supported, we assume */ + if (!payloads) { + payload_types[0] = RTP_PT_GSM_FULL; + payloads = 1; + } + + /* select first payload type that matches the rtp list */ + if (p_g_rtp_bridge) { + int i, j; + + for (i = 0; i < p_g_rtp_payloads; i++) { + for (j = 0; j < payloads; j++) { + if (p_g_rtp_payload_types[i] == payload_types[j]) + break; + } + if (j < payloads) + break; + } + if (i == p_g_rtp_payloads) { + struct lcr_msg *message; + + /* payload offered by remote RTP is not supported */ + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_RELEASE); + message->param.disconnectinfo.cause = 65; + message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL; + message_put(message); + /* send release */ + mncc = create_mncc(MNCC_REL_REQ, p_g_callref); + gsm_trace_header(p_g_interface_name, this, MNCC_REL_REQ, DIRECTION_OUT); + mncc->fields |= MNCC_F_CAUSE; + mncc->cause.coding = 3; + mncc->cause.location = LOCATION_PRIVATE_LOCAL; + mncc->cause.value = 65; + add_trace("cause", "coding", "%d", mncc->cause.coding); + add_trace("cause", "location", "%d", mncc->cause.location); + add_trace("cause", "value", "%d", mncc->cause.value); + add_trace("reason", NULL, "None of the payload types are supported by MS"); + end_trace(); + send_and_free_mncc(p_g_lcr_gsm, mncc->msg_type, mncc); + new_state(PORT_STATE_RELEASE); + trigger_work(&p_g_delete); + } + modify_lchan(p_g_rtp_payload_types[i]); + } else { + /* modify to first given payload */ + modify_lchan(payload_types[0]); + } +} + /* DTMF INDICATION */ void Pgsm_bs::start_dtmf_ind(unsigned int msg_type, unsigned int callref, struct gsm_mncc *mncc) { @@ -204,6 +277,73 @@ void Pgsm_bs::retr_ind(unsigned int msg_type, unsigned int callref, struct gsm_m } /* + * select payload type by given list or GSM V1 FR + * return the payload type or 0 if not given + */ + +void Pgsm_bs::select_payload_type(struct gsm_mncc *mncc, unsigned char *payload_types, int *payloads, int max_payloads) +{ + unsigned char payload_type; + + *payloads = 0; + + gsm_trace_header(p_g_interface_name, this, 1 /* codec negotioation */, DIRECTION_NONE); + if ((mncc->fields & MNCC_F_BEARER_CAP)) { + /* select preferred payload type from list */ + int i; + + add_trace("bearer", "capa", "given by MS"); + for (i = 0; mncc->bearer_cap.speech_ver[i] >= 0; i++) { + /* select payload type we support */ + switch (mncc->bearer_cap.speech_ver[i]) { + case 0: + add_trace("speech", "version", "Full Rate given"); + payload_type = RTP_PT_GSM_FULL; + break; + case 2: + add_trace("speech", "version", "EFR given"); + payload_type = RTP_PT_GSM_EFR; + break; + case 4: + add_trace("speech", "version", "AMR given"); + payload_type = RTP_PT_GSM_AMR; + break; + case 1: + add_trace("speech", "version", "Half Rate given"); + payload_type = RTP_PT_GSM_HALF; + break; + default: + add_trace("speech", "version", "%d given", mncc->bearer_cap.speech_ver[i]); + payload_type = 0; + } + /* wen don't support it, so we check the next */ + if (!payload_type) { + add_trace("speech", "ignored", "Not supported by LCR"); + continue; + } + if (!p_g_rtp_bridge) { + if (payload_type != RTP_PT_GSM_FULL) { + add_trace("speech", "ignored", "Not suitable for LCR"); + continue; + } + } + if (*payloads <= max_payloads) { + payload_types[*payloads] = payload_type; + (*payloads)++; + } + } + } else { + add_trace("bearer", "capa", "not given by MS"); + add_trace("speech", "version", "Full Rate given"); + payload_types[0] = RTP_PT_GSM_FULL; + *payloads = 1; + } + if (!(*payloads)) + add_trace("error", "", "All given payload types unsupported"); + end_trace(); +} + +/* * handles all indications */ /* SETUP INDICATION */ @@ -211,8 +351,10 @@ void Pgsm_bs::setup_ind(unsigned int msg_type, unsigned int callref, struct gsm_ { class Endpoint *epoint; struct lcr_msg *message; - struct gsm_mncc *mode, *proceeding, *frame; + struct gsm_mncc *proceeding, *frame; struct interface *interface; + unsigned char payload_types[8]; + int payloads = 0; interface = getinterfacebyname(p_g_interface_name); if (!interface) { @@ -282,13 +424,41 @@ void Pgsm_bs::setup_ind(unsigned int msg_type, unsigned int callref, struct gsm_ p_dialinginfo.sending_complete = 1; /* bearer capability */ - // todo p_capainfo.bearer_capa = INFO_BC_SPEECH; p_capainfo.bearer_info1 = (options.law=='a')?3:2; p_capainfo.bearer_mode = INFO_BMODE_CIRCUIT; p_capainfo.source_mode = B_MODE_TRANSPARENT; p_g_mode = p_capainfo.source_mode; + /* get list of offered payload types + * if list ist empty, the FR V1 is selected */ + select_payload_type(mncc, payload_types, &payloads, sizeof(payload_types)); + /* if no given payload type is supported, we assume */ + if (!payloads) { + payload_types[0] = RTP_PT_GSM_FULL; + payloads = 1; + } +#if 0 + /* if no given payload type is supported, we reject the call */ + if (!payloads) { + mncc = create_mncc(MNCC_REJ_REQ, callref); + gsm_trace_header(p_g_interface_name, this, MNCC_REJ_REQ, DIRECTION_OUT); + mncc->fields |= MNCC_F_CAUSE; + mncc->cause.coding = 3; + mncc->cause.location = 1; + mncc->cause.value = 65; + add_trace("cause", "coding", "%d", mncc->cause.coding); + add_trace("cause", "location", "%d", mncc->cause.location); + add_trace("cause", "value", "%d", mncc->cause.value); + add_trace("reason", NULL, "Given speech codec(s) not supported"); + end_trace(); + send_and_free_mncc(p_g_lcr_gsm, mncc->msg_type, mncc); + new_state(PORT_STATE_RELEASE); + trigger_work(&p_g_delete); + return; + } +#endif + /* useruser */ /* what infos did we got ... */ @@ -309,14 +479,9 @@ void Pgsm_bs::setup_ind(unsigned int msg_type, unsigned int callref, struct gsm_ epoint->ep_app = new_endpointapp(epoint, 0, interface->app); //incoming epointlist_new(epoint->ep_serial); - /* modify lchan to GSM codec V1 */ - gsm_trace_header(p_g_interface_name, this, MNCC_LCHAN_MODIFY, DIRECTION_OUT); - mode = create_mncc(MNCC_LCHAN_MODIFY, p_g_callref); - mode->lchan_mode = 0x01; /* GSM V1 */ - mode->lchan_type = 0x02; - add_trace("mode", NULL, "0x%02x", mode->lchan_mode); - end_trace(); - send_and_free_mncc(p_g_lcr_gsm, mode->msg_type, mode); + /* modify lchan in case of no rtp bridge */ + if (!p_g_rtp_bridge) + modify_lchan(payload_types[0]); /* send call proceeding */ gsm_trace_header(p_g_interface_name, this, MNCC_CALL_PROC_REQ, DIRECTION_OUT); @@ -353,15 +518,20 @@ void Pgsm_bs::setup_ind(unsigned int msg_type, unsigned int callref, struct gsm_ SCPY((char *)message->param.setup.useruser.data, (char *)mncc->useruser.info); message->param.setup.useruser.len = strlen(mncc->useruser.info); message->param.setup.useruser.protocol = mncc->useruser.proto; - message->param.setup.rtpinfo.payload_type = 3; /* FIXME: receive payload type from peer */ - if (p_g_rtp_bridge) { struct gsm_mncc_rtp *rtp; + int i; PDEBUG(DEBUG_GSM, "Request RTP peer info, before forwarding setup\n"); p_g_setup_pending = message; rtp = (struct gsm_mncc_rtp *) create_mncc(MNCC_RTP_CREATE, p_g_callref); send_and_free_mncc(p_g_lcr_gsm, rtp->msg_type, rtp); + + for (i = 0; i < (int)sizeof(message->param.setup.rtpinfo.payload_types) && i < payloads; i++) { + message->param.setup.rtpinfo.payload_types[i] = payload_types[i]; + message->param.setup.rtpinfo.payloads++; + } + } else message_put(message); @@ -562,6 +732,43 @@ void Pgsm_bs::message_setup(unsigned int epoint_id, int message_id, union parame return; } + /* unsupported codec for RTP bridge */ + if (param->setup.rtpinfo.port) { + int i; + + p_g_rtp_payloads = 0; + gsm_trace_header(p_g_interface_name, this, 1 /* codec negotioation */, DIRECTION_NONE); + for (i = 0; i < param->setup.rtpinfo.payloads; i++) { + switch (param->setup.rtpinfo.payload_types[i]) { + case RTP_PT_GSM_FULL: + case RTP_PT_GSM_EFR: + case RTP_PT_GSM_AMR: + case RTP_PT_GSM_HALF: + add_trace("rtp", "payload", "%d supported", param->setup.rtpinfo.payload_types[i]); + if (p_g_rtp_payloads < (int)sizeof(p_g_rtp_payload_types)) { + p_g_rtp_payload_types[p_g_rtp_payloads++] = param->setup.rtpinfo.payload_types[i]; + p_g_rtp_payloads++; + } + break; + default: + add_trace("rtp", "payload", "%d unsupported", param->setup.rtpinfo.payload_types[i]); + } + } + end_trace(); + if (!p_g_rtp_payloads) { + gsm_trace_header(p_g_interface_name, this, MNCC_SETUP_REQ, DIRECTION_OUT); + add_trace("failure", NULL, "No payload given that is supported by GSM"); + end_trace(); + message = message_create(p_serial, epoint_id, PORT_TO_EPOINT, MESSAGE_RELEASE); + message->param.disconnectinfo.cause = 65; + message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL; + message_put(message); + new_state(PORT_STATE_RELEASE); + trigger_work(&p_g_delete); + return; + } + } + // SCPY(&p_m_tones_dir, param->setup.ext.tones_dir); /* screen outgoing caller id */ do_screen(1, p_callerinfo.id, sizeof(p_callerinfo.id), &p_callerinfo.ntype, &p_callerinfo.present, p_g_interface_name); @@ -691,8 +898,6 @@ void Pgsm_bs::message_setup(unsigned int epoint_id, int message_id, union parame add_trace("redir", "screen", "%d", mncc->redirecting.screen); add_trace("redir", "number", "%s", mncc->redirecting.number); } - /* bearer capability */ - //todo end_trace(); send_and_free_mncc(p_g_lcr_gsm, mncc->msg_type, mncc); @@ -702,8 +907,6 @@ void Pgsm_bs::message_setup(unsigned int epoint_id, int message_id, union parame message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_PROCEEDING); message_put(message); - new_state(PORT_STATE_OUT_PROCEEDING); - /* RTP bridge */ if (param->setup.rtpinfo.port) { p_g_rtp_bridge = 1; diff --git a/gsm_bs.h b/gsm_bs.h index 81e1f13..b60c02c 100644 --- a/gsm_bs.h +++ b/gsm_bs.h @@ -9,7 +9,9 @@ class Pgsm_bs : public Pgsm unsigned char *p_g_dtmf; /* DTMF tone generation (MS only) */ int p_g_dtmf_index; /* DTMF tone generation index */ + void select_payload_type(struct gsm_mncc *mncc, unsigned char *payload_types, int *payloads, int max_payloads); void setup_ind(unsigned int msg_type, unsigned int callref, struct gsm_mncc *mncc); + void call_conf_ind(unsigned int msg_type, unsigned int callref, struct gsm_mncc *gsm); void start_dtmf_ind(unsigned int msg_type, unsigned int callref, struct gsm_mncc *mncc); void stop_dtmf_ind(unsigned int msg_type, unsigned int callref, struct gsm_mncc *mncc); void hold_ind(unsigned int msg_type, unsigned int callref, struct gsm_mncc *mncc); diff --git a/gsm_ms.cpp b/gsm_ms.cpp index bb2ad67..0cbcca1 100644 --- a/gsm_ms.cpp +++ b/gsm_ms.cpp @@ -23,6 +23,8 @@ static int dtmf_timeout(struct lcr_timer *timer, void *instance, int index); #define DTMF_ST_STOP 3 /* DTMF stopped, waiting for resp. */ #define DTMF_ST_SPACE 4 /* wait space between tones */ +#define RTP_PT_GSM_FULL 3 + /* * constructor */ @@ -67,7 +69,7 @@ void Pgsm_ms::setup_ind(unsigned int msg_type, unsigned int callref, struct gsm_ { class Endpoint *epoint; struct lcr_msg *message; - struct gsm_mncc *mode, *proceeding, *frame; + struct gsm_mncc *proceeding, *frame; struct interface *interface; interface = getinterfacebyname(p_g_interface_name); @@ -275,13 +277,7 @@ void Pgsm_ms::setup_ind(unsigned int msg_type, unsigned int callref, struct gsm_ epointlist_new(epoint->ep_serial); /* modify lchan to GSM codec V1 */ - gsm_trace_header(p_g_interface_name, this, MNCC_LCHAN_MODIFY, DIRECTION_OUT); - mode = create_mncc(MNCC_LCHAN_MODIFY, p_g_callref); - mode->lchan_mode = 0x01; /* GSM V1 */ - mode->lchan_type = 0x02; - add_trace("mode", NULL, "0x%02x", mode->lchan_mode); - end_trace(); - send_and_free_mncc(p_g_lcr_gsm, mode->msg_type, mode); + modify_lchan(RTP_PT_GSM_FULL); /* send call proceeding */ gsm_trace_header(p_g_interface_name, this, MNCC_CALL_CONF_REQ, DIRECTION_OUT); diff --git a/interface.c b/interface.c index 7a00735..365e843 100644 --- a/interface.c +++ b/interface.c @@ -981,10 +981,53 @@ static int inter_sip(struct interface *interface, char *filename, int line, char } static int inter_rtp_bridge(struct interface *interface, char *filename, int line, char *parameter, char *value) { + int supported = 0; + +#ifdef WITH_GSM_BS + if (interface->gsm_bs) + supported = 1; +#endif +#ifdef WITH_SIP + if (interface->sip) + supported = 1; +#endif + if (!supported) { + SPRINT(interface_error, "Error in %s (line %d): Interface does not support RTP\n", filename, line); + return(-1); + } interface->rtp_bridge = 1; return(0); } +#if 0 +static int inter_rtp_payload(struct interface *interface, char *filename, int line, char *parameter, char *value) +{ +#ifndef WITH_GSM_BS + SPRINT(interface_error, "Error in %s (line %d): GSM BS side not compiled in.\n", filename, line); + return(-1); +#else + if (!interface->gsm_bs) { + SPRINT(interface_error, "Error in %s (line %d): This parameter only works for GSM BS side interface\n", filename, line); + return(-1); + } + if (!interface->rtp_bridge) { + SPRINT(interface_error, "Error in %s (line %d): This parameter only works here, if RTP bridging is enabled\n", filename, line); + return(-1); + } + if (!value[0]) { + SPRINT(interface_error, "Error in %s (line %d): parameter '%s' expects one payload type\n", filename, line, parameter); + return(-1); + } + if (interface->gsm_bs_payloads == sizeof(interface->gsm_bs_payload_types)) { + SPRINT(interface_error, "Error in %s (line %d): Too many payload types defined\n", filename, line); + return(-1); + } + interface->gsm_bs_payload_types[interface->gsm_bs_payloads++] = atoi(value); + + return(0); +#endif +} +#endif static int inter_nonotify(struct interface *interface, char *filename, int line, char *parameter, char *value) { struct interface_port *ifport; @@ -1245,8 +1288,15 @@ struct interface_param interface_param[] = { "Sets up SIP interface that represents one SIP endpoint.\n" "Give SIP configuration file."}, {"rtp-bridge", &inter_rtp_bridge, "", - "Sets up SIP interface that represents one SIP endpoint.\n" - "Give SIP configuration file."}, + "Enables RTP bridging directly from this interface.\n" + "This only works, if both ends support RTP. (like gsm-bs and sip)"}, +#if 0 + not needed, since ms defines what is supports and remote (sip) tells what is selected + {"rtp-payload", &inter_rtp_payload, "", + "Define RTP payload to use. Only valid in conjuntion with gsm-bs!\n" + "If multiple payloads are defined, the first has highest priority.\n" + "If none are defined, GSM fullrate V1 (type 3) is assumed.\n"}, +#endif {"nonotify", &inter_nonotify, "", "Prevents sending notify messages to this interface. A call placed on hold will\n" "Not affect the remote end (phone or telcom switch).\n" diff --git a/interface.h b/interface.h index 93fb1b6..cf1b192 100644 --- a/interface.h +++ b/interface.h @@ -108,6 +108,10 @@ struct interface { int bf_len; /* filter length of blowfish */ #ifdef WITH_GSM_BS int gsm_bs; /* interface is an GSM BS interface */ +#if 0 + int gsm_bs_payloads; + unsigned char gsm_bs_payload_types[8]; +#endif #endif #ifdef WITH_GSM_MS int gsm_ms; /* interface is an GSM MS interface */ diff --git a/macro.h b/macro.h index 5d2bce5..e4a2f2e 100644 --- a/macro.h +++ b/macro.h @@ -22,10 +22,10 @@ static inline void scpy(char *dst, const char *src, unsigned int siz) /* safe strcat/strncat */ -#define SCAT(dst, src) scat(dst, src, sizeof(dst)-strlen(dst)-1) +#define SCAT(dst, src) scat(dst, src, sizeof(dst)) static inline void scat(char *dst, const char *src, unsigned int siz) { - strncat(dst, src, siz); + strncat(dst, src, siz-strlen(dst)-1); dst[siz-1] = '\0'; } diff --git a/main.h b/main.h index 5fac1ef..8d8b448 100644 --- a/main.h +++ b/main.h @@ -118,6 +118,8 @@ void debug(const char *file, const char *function, int line, const char *prefix, */ //#define BUDETECT_DEF +/* internal limit of payload type in a message */ + #ifdef BUDETECT_DEF #define BUDETECT budetect(__FILE__, __LINE__, __FUNCTION__); void budetect(const char *file, int line, const char *function); diff --git a/message.h b/message.h index 6330654..ce6ed03 100644 --- a/message.h +++ b/message.h @@ -150,9 +150,10 @@ enum { /* rtp-info structure */ struct rtp_info { - int payload_type; - unsigned int ip; - unsigned short port; + int payloads; /* number of payloads offered */ + unsigned char payload_types[32];/* rtp payload types */ + unsigned int ip; /* peer's IP */ + unsigned short port; /* peer's port */ }; /* call-info structure CALLER */ diff --git a/sip.cpp b/sip.cpp index cd03566..92f11b2 100644 --- a/sip.cpp +++ b/sip.cpp @@ -51,7 +51,6 @@ Psip::Psip(int type, char *portname, struct port_settings *settings, struct inte memset(&p_s_rtcp_sin_local, 0, sizeof(p_s_rtcp_sin_local)); memset(&p_s_rtp_sin_remote, 0, sizeof(p_s_rtp_sin_remote)); memset(&p_s_rtcp_sin_remote, 0, sizeof(p_s_rtcp_sin_remote)); - p_s_rtp_payload_type = 0; p_s_rtp_ip_local = 0; p_s_rtp_ip_remote = 0; p_s_rtp_port_local = 0; @@ -145,7 +144,7 @@ struct rtp_x_hdr { #define RTP_PT_GSM_FULL 3 #define RTP_PT_GSM_HALF 96 #define RTP_PT_GSM_EFR 97 -#define RTP_PT_AMR 98 +#define RTP_PT_GSM_AMR 98 /* decode an rtp frame */ static int rtp_decode(class Psip *psip, unsigned char *data, int len) @@ -220,6 +219,14 @@ static int rtp_decode(class Psip *psip, unsigned char *data, int len) return -EINVAL; } break; + case RTP_PT_GSM_HALF: + if (payload_len != 14) { + PDEBUG(DEBUG_SIP, "received RTP half rate frame with " + "payload length != 14 (len = %d)\n", + payload_len); + return -EINVAL; + } + break; case RTP_PT_ALAW: if (options.law != 'a') { PDEBUG(DEBUG_SIP, "received Alaw, but we don't do Alaw\n"); @@ -469,6 +476,10 @@ int Psip::rtp_send_frame(unsigned char *data, unsigned int len, uint8_t payload_ payload_len = 31; duration = 160; break; + case RTP_PT_GSM_HALF: + payload_len = 14; + duration = 160; + break; case RTP_PT_ALAW: case RTP_PT_ULAW: payload_len = len; @@ -722,18 +733,20 @@ int Psip::message_connect(unsigned int epoint_id, int message_id, union paramete char sdp_str[256]; struct in_addr ia; struct lcr_msg *message; + unsigned char payload_type; if (param->connectinfo.rtpinfo.port) { PDEBUG(DEBUG_SIP, "RTP info given by remote, forward that\n"); p_s_rtp_bridge = 1; - p_s_rtp_payload_type = param->connectinfo.rtpinfo.payload_type; + payload_type = param->connectinfo.rtpinfo.payload_types[0]; p_s_rtp_ip_local = param->connectinfo.rtpinfo.ip; p_s_rtp_port_local = param->connectinfo.rtpinfo.port; + PDEBUG(DEBUG_SIP, "payload type %d\n", payload_type); PDEBUG(DEBUG_SIP, "local ip %08x port %d\n", p_s_rtp_ip_local, p_s_rtp_port_local); PDEBUG(DEBUG_SIP, "remote ip %08x port %d\n", p_s_rtp_ip_remote, p_s_rtp_port_remote); } else { PDEBUG(DEBUG_SIP, "RTP info not given by remote, so we do our own RTP\n"); - p_s_rtp_payload_type = (options.law=='a') ? RTP_PT_ALAW : RTP_PT_ULAW; + payload_type = (options.law=='a') ? RTP_PT_ALAW : RTP_PT_ULAW; /* open local RTP peer (if not bridging) */ if (rtp_connect() < 0) { nua_cancel(p_s_handle, TAG_END()); @@ -762,7 +775,7 @@ int Psip::message_connect(unsigned int epoint_id, int message_id, union paramete "t=0 0\n" "m=audio %d RTP/AVP %d\n" "a=rtpmap:%d %s/8000\n" - , inet_ntoa(ia), inet_ntoa(ia), p_s_rtp_port_local, p_s_rtp_payload_type, p_s_rtp_payload_type, payload_type2name(p_s_rtp_payload_type)); + , inet_ntoa(ia), inet_ntoa(ia), p_s_rtp_port_local, payload_type, payload_type, payload_type2name(payload_type)); PDEBUG(DEBUG_SIP, "Using SDP response: %s\n", sdp_str); nua_respond(p_s_handle, SIP_200_OK, @@ -773,6 +786,9 @@ int Psip::message_connect(unsigned int epoint_id, int message_id, union paramete sip_trace_header(this, "RESPOND", DIRECTION_OUT); add_trace("respond", "value", "200 OK"); add_trace("reason", NULL, "call connected"); + add_trace("rtp", "ip", "%s", inet_ntoa(ia)); + add_trace("rtp", "port", "%d,%d", p_s_rtp_port_local, p_s_rtp_port_local + 1); + add_trace("rtp", "payload", "%d", payload_type); end_trace(); return 0; @@ -849,11 +865,15 @@ int Psip::message_setup(unsigned int epoint_id, int message_id, union parameter char to[128]; const char *local = inst->local_ip; const char *remote = inst->remote_ip; - char sdp_str[256]; + char sdp_str[256], pt_str[32]; struct in_addr ia; struct epoint_list *epointlist; sip_cseq_t *cseq = NULL; struct lcr_msg *message; + unsigned char lcr_payload = { (options.law=='a') ? RTP_PT_ALAW : RTP_PT_ULAW }; + unsigned char *payload_types; + int payloads = 0; + int i; PDEBUG(DEBUG_SIP, "Doing Setup (inst %p)\n", inst); @@ -864,7 +884,8 @@ int Psip::message_setup(unsigned int epoint_id, int message_id, union parameter if (param->setup.rtpinfo.port) { PDEBUG(DEBUG_SIP, "RTP info given by remote, forward that\n"); p_s_rtp_bridge = 1; - p_s_rtp_payload_type = param->setup.rtpinfo.payload_type; + payload_types = param->setup.rtpinfo.payload_types; + payloads = param->setup.rtpinfo.payloads; p_s_rtp_ip_local = param->setup.rtpinfo.ip; p_s_rtp_port_local = param->setup.rtpinfo.port; PDEBUG(DEBUG_SIP, "local ip %08x port %d\n", p_s_rtp_ip_local, p_s_rtp_port_local); @@ -872,7 +893,8 @@ int Psip::message_setup(unsigned int epoint_id, int message_id, union parameter } else { PDEBUG(DEBUG_SIP, "RTP info not given by remote, so we do our own RTP\n"); p_s_rtp_bridge = 0; - p_s_rtp_payload_type = (options.law=='a') ? RTP_PT_ALAW : RTP_PT_ULAW; + payload_types = &lcr_payload; + payloads = 1; /* open local RTP peer (if not bridging) */ if (rtp_open() < 0) { @@ -887,14 +909,6 @@ int Psip::message_setup(unsigned int epoint_id, int message_id, union parameter return 0; } } - SPRINT(from, "sip:%s@%s", param->setup.callerinfo.id, local); - SPRINT(to, "sip:%s@%s", param->setup.dialinginfo.id, remote); - - sip_trace_header(this, "INVITE", DIRECTION_OUT); - add_trace("from", "uri", "%s", from); - add_trace("to", "uri", "%s", to); - add_trace("rtp", "port", "%d,%d", p_s_rtp_port_local, p_s_rtp_port_local + 1); - end_trace(); p_s_handle = nua_handle(inst->nua, NULL, TAG_END()); if (!p_s_handle) { @@ -925,11 +939,31 @@ int Psip::message_setup(unsigned int epoint_id, int message_id, union parameter "s=SIP Call\n" "c=IN IP4 %s\n" "t=0 0\n" - "m=audio %d RTP/AVP %d\n" - "a=rtpmap:%d %s/8000\n" - , inet_ntoa(ia), inet_ntoa(ia), p_s_rtp_port_local, p_s_rtp_payload_type, p_s_rtp_payload_type, payload_type2name(p_s_rtp_payload_type)); + "m=audio %d RTP/AVP" + , inet_ntoa(ia), inet_ntoa(ia), p_s_rtp_port_local); + for (i = 0; i < payloads; i++) { + SPRINT(pt_str, " %d", payload_types[i]); + SCAT(sdp_str, pt_str); + } + SCAT(sdp_str, "\n"); + for (i = 0; i < payloads; i++) { + SPRINT(pt_str, "a=rtpmap:%d %s/8000\n", payload_types[i], payload_type2name(payload_types[i])); + SCAT(sdp_str, pt_str); + } PDEBUG(DEBUG_SIP, "Using SDP for invite: %s\n", sdp_str); + SPRINT(from, "sip:%s@%s", param->setup.callerinfo.id, local); + SPRINT(to, "sip:%s@%s", param->setup.dialinginfo.id, remote); + + sip_trace_header(this, "INVITE", DIRECTION_OUT); + add_trace("from", "uri", "%s", from); + add_trace("to", "uri", "%s", to); + add_trace("rtp", "ip", "%s", inet_ntoa(ia)); + add_trace("rtp", "port", "%d,%d", p_s_rtp_port_local, p_s_rtp_port_local + 1); + for (i = 0; i < payloads; i++) + add_trace("rtp", "payload", "%d", payload_types[i]); + end_trace(); + // cseq = sip_cseq_create(sip_home, 123, SIP_METHOD_INVITE); nua_invite(p_s_handle, @@ -947,7 +981,7 @@ int Psip::message_setup(unsigned int epoint_id, int message_id, union parameter message = message_create(p_serial, epoint_id, PORT_TO_EPOINT, MESSAGE_OVERLAP); message_put(message); #else - PDEBUG(DEBUG_SIP, "do overlap\n"); + PDEBUG(DEBUG_SIP, "do proceeding\n"); new_state(PORT_STATE_OUT_PROCEEDING); message = message_create(p_serial, epoint_id, PORT_TO_EPOINT, MESSAGE_PROCEEDING); message_put(message); @@ -1112,9 +1146,9 @@ int Psip::message_epoint(unsigned int epoint_id, int message_id, union parameter return 0; } -int Psip::parse_sdp(sip_t const *sip, unsigned int *ip, unsigned short *port, uint8_t payload_type) +int Psip::parse_sdp(sip_t const *sip, unsigned int *ip, unsigned short *port, uint8_t *payload_types, int *payloads, int max_payloads) { - int codec_supported = 0; + *payloads = 0; if (!sip->sip_payload) { PDEBUG(DEBUG_SIP, "no payload given\n"); @@ -1173,20 +1207,17 @@ int Psip::parse_sdp(sip_t const *sip, unsigned int *ip, unsigned short *port, ui } for (map = m->m_rtpmaps; map; map = map->rm_next) { PDEBUG(DEBUG_SIP, "RTPMAP: coding:'%s' rate='%d' pt='%d'\n", map->rm_encoding, map->rm_rate, map->rm_pt); - if (map->rm_pt == payload_type) { - PDEBUG(DEBUG_SIP, "supported codec found\n"); - codec_supported = 1; - goto done_codec; + /* append to payload list, if there is space */ + add_trace("rtp", "payload", "%d", map->rm_pt); + if (*payloads <= max_payloads) { + *payload_types++ = map->rm_pt; + (*payloads)++; } } } - done_codec: sdp_parser_free(parser); - if (!codec_supported) - return 415; - return 0; } @@ -1197,8 +1228,9 @@ void Psip::i_invite(int status, char const *phrase, nua_t *nua, nua_magic_t *mag int ret; class Endpoint *epoint; struct lcr_msg *message; - uint8_t payload_type; struct interface *interface; + uint8_t payload_types[32]; + int payloads = 0; interface = getinterfacebyname(inst->interface_name); if (!interface) { @@ -1212,11 +1244,25 @@ void Psip::i_invite(int status, char const *phrase, nua_t *nua, nua_magic_t *mag to = sip->sip_to->a_url->url_user; PDEBUG(DEBUG_SIP, "invite received (%s->%s)\n", from, to); - if (p_s_rtp_bridge) - payload_type = 3; // FIXME: use list and forward list to remote side - else - payload_type = (options.law=='a')?RTP_PT_ALAW:RTP_PT_ULAW; - ret = parse_sdp(sip, &p_s_rtp_ip_remote, &p_s_rtp_port_remote, payload_type); + sip_trace_header(this, "Payload received", DIRECTION_NONE); + ret = parse_sdp(sip, &p_s_rtp_ip_remote, &p_s_rtp_port_remote, payload_types, &payloads, sizeof(payload_types)); + if (!ret) { + /* if no RTP bridge, we must support LAW codec, otherwise we forward what we have */ + if (!p_s_rtp_bridge) { + int i; + + /* check if supported payload type exists */ + for (i = 0; i < payloads; i++) { + if (payload_types[i] == ((options.law=='a') ? RTP_PT_ALAW : RTP_PT_ULAW)) + break; + } + if (i == payloads) { + add_trace("error", NULL, "Expected LAW payload type (not bridged)"); + ret = 415; + } + } + } + end_trace(); if (ret) { if (ret == 400) nua_respond(nh, SIP_400_BAD_REQUEST, TAG_END()); @@ -1317,10 +1363,17 @@ void Psip::i_invite(int status, char const *phrase, nua_t *nua, nua_magic_t *mag // message->param.setup.useruser.len = strlen(mncc->useruser.info); // message->param.setup.useruser.protocol = mncc->useruser.proto; if (p_s_rtp_bridge) { + int i; + PDEBUG(DEBUG_SIP, "sending setup with RTP info\n"); - message->param.setup.rtpinfo.payload_type = payload_type; message->param.setup.rtpinfo.ip = p_s_rtp_ip_remote; message->param.setup.rtpinfo.port = p_s_rtp_port_remote; + /* add codecs to setup message */ + for (i = 0; i < payloads; i++) { + message->param.setup.rtpinfo.payload_types[message->param.setup.rtpinfo.payloads++] = payload_types[i]; + if (message->param.setup.rtpinfo.payloads == sizeof(message->param.setup.rtpinfo.payload_types)) + break; + } } message_put(message); } @@ -1417,6 +1470,8 @@ void Psip::r_invite(int status, char const *phrase, nua_t *nua, nua_magic_t *mag { struct lcr_msg *message; int cause = 0, location = 0; + uint8_t payload_types[32]; + int payloads = 0; PDEBUG(DEBUG_SIP, "response to invite received (status = %d)\n", status); @@ -1427,20 +1482,24 @@ void Psip::r_invite(int status, char const *phrase, nua_t *nua, nua_magic_t *mag /* connect audio */ if (status == 183 || (status >= 200 && status <= 299)) { int ret; - uint8_t payload_type; - if (p_s_rtp_bridge) - payload_type = p_s_rtp_payload_type; - else - payload_type = (options.law=='a')?RTP_PT_ALAW:RTP_PT_ULAW; - ret = parse_sdp(sip, &p_s_rtp_ip_remote, &p_s_rtp_port_remote, payload_type); + sip_trace_header(this, "Payload received", DIRECTION_NONE); + ret = parse_sdp(sip, &p_s_rtp_ip_remote, &p_s_rtp_port_remote, payload_types, &payloads, sizeof(payload_types)); + if (!ret) { + if (payloads != 1) + ret = 415; + else if (!p_s_rtp_bridge) { + if (payload_types[0] != ((options.law=='a') ? RTP_PT_ALAW : RTP_PT_ULAW)) { + add_trace("error", NULL, "Expected LAW payload type (not bridged)"); + ret = 415; + } + } + } + end_trace(); if (ret) { - if (ret == 400) - nua_cancel(nh, TAG_END()); - else - nua_cancel(nh, TAG_END()); + nua_cancel(nh, TAG_END()); sip_trace_header(this, "CANCEL", DIRECTION_OUT); - add_trace("reason", NULL, "offered codec does not match"); + add_trace("reason", NULL, "accepted codec does not match"); end_trace(); cause = 88; location = LOCATION_PRIVATE_LOCAL; @@ -1481,9 +1540,10 @@ void Psip::r_invite(int status, char const *phrase, nua_t *nua, nua_magic_t *mag message->param.progressinfo.progress = 8; message->param.progressinfo.location = 10; if (p_s_rtp_bridge) { - message->param.progressinfo.rtpinfo.payload_type = p_s_rtp_payload_type; message->param.progressinfo.rtpinfo.ip = p_s_rtp_ip_remote; message->param.progressinfo.rtpinfo.port = p_s_rtp_port_remote; + message->param.progressinfo.rtpinfo.payload_types[0] = payload_types[0]; + message->param.progressinfo.rtpinfo.payloads = 1; } message_put(message); return; @@ -1502,9 +1562,10 @@ void Psip::r_invite(int status, char const *phrase, nua_t *nua, nua_magic_t *mag new_state(PORT_STATE_CONNECT); message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_CONNECT); if (p_s_rtp_bridge) { - message->param.connectinfo.rtpinfo.payload_type = p_s_rtp_payload_type; message->param.connectinfo.rtpinfo.ip = p_s_rtp_ip_remote; message->param.connectinfo.rtpinfo.port = p_s_rtp_port_remote; + message->param.connectinfo.rtpinfo.payload_types[0] = payload_types[0]; + message->param.connectinfo.rtpinfo.payloads = 1; } message_put(message); return; diff --git a/sip.h b/sip.h index 8fd420b..9e8194e 100644 --- a/sip.h +++ b/sip.h @@ -35,7 +35,6 @@ class Psip : public Port nua_handle_t *p_s_handle; nua_magic_t *p_s_magic; int p_s_rtp_bridge; /* bridge RTP instead of having a local RTP peer */ - uint8_t p_s_rtp_payload_type; unsigned short p_s_rtp_port_local; unsigned short p_s_rtp_port_remote; unsigned int p_s_rtp_ip_local; @@ -63,7 +62,7 @@ class Psip : public Port unsigned char p_s_rxdata[160]; /* receive audio buffer */ int p_s_rxpos; /* position in audio buffer 0..159 */ int bridge_rx(unsigned char *data, int len); - int parse_sdp(sip_t const *sip, unsigned int *ip, unsigned short *port, uint8_t payload_type); + int parse_sdp(sip_t const *sip, unsigned int *ip, unsigned short *port, uint8_t *payload_types, int *payloads, int max_payloads); void rtp_shutdown(void); };