parent
f2fca2a2b1
commit
9e63d47df4
|
@ -915,7 +915,7 @@ static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg)
|
|||
} else {
|
||||
LOG_TRANS(trans, LOGL_INFO,
|
||||
"Got no information of remote Bearer Capability. Trying anyway.\n");
|
||||
sdp_audio_codecs_set_csd(&trans->cc.codecs.ms);
|
||||
sdp_audio_codecs_set_csd(trans, &trans->cc.codecs.ms);
|
||||
}
|
||||
trans_cc_filter_run(trans);
|
||||
break;
|
||||
|
|
|
@ -21,794 +21,14 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
#include <osmocom/mgcp_client/fmtp.h>
|
||||
|
||||
#include <osmocom/msc/debug.h>
|
||||
#include <osmocom/msc/sdp_msg.h>
|
||||
|
||||
bool sdp_audio_codec_is_set(const struct sdp_audio_codec *a)
|
||||
{
|
||||
return a && a->subtype_name[0];
|
||||
}
|
||||
|
||||
static void strip_whitespace(char *str)
|
||||
{
|
||||
char *i = str;
|
||||
char *o = str;
|
||||
for (; *i; i++, o++) {
|
||||
while (isspace(*i))
|
||||
i++;
|
||||
*o = *i;
|
||||
if (!*i)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool amr_fmtp_match(const char *a, const char *b)
|
||||
{
|
||||
char a_modeset[32] = {};
|
||||
char b_modeset[32] = {};
|
||||
|
||||
/* octet-align=1. Omission means octet-align=0 */
|
||||
if (osmo_sdp_fmtp_amr_is_octet_aligned(a) != osmo_sdp_fmtp_amr_is_octet_aligned(b))
|
||||
return false;
|
||||
|
||||
/* mode-set=0,1,2,3,4,5,6,7. Omission of 'mode-set' means, match any and all codec modes. So if either a or b
|
||||
* have no 'mode-set', it's a match. If both have one, they must be identical to match. We expect the
|
||||
* mode-set string to be in ascending order. */
|
||||
if (osmo_sdp_fmtp_get_val(a_modeset, sizeof(a_modeset), a, "mode-set")
|
||||
&& osmo_sdp_fmtp_get_val(b_modeset, sizeof(b_modeset), b, "mode-set")) {
|
||||
/* Strip whitespace: We don't know what remote SDP peers may throw at us. There could be whitespace
|
||||
* around the separators like 'mode-set=2,3 ; octet-align=1', which may show up here as whitespace in
|
||||
* the value string as "2,3 ", which would mismatch "2,3". */
|
||||
strip_whitespace(a_modeset);
|
||||
strip_whitespace(b_modeset);
|
||||
if (strcmp(a_modeset, b_modeset))
|
||||
return false;
|
||||
}
|
||||
|
||||
/* TODO: treat other AMR traits, see RFC4867 8.1. Maybe generically match all values that are present?
|
||||
* So far we have not seen any peer use other values than octet-align and mode-set. */
|
||||
|
||||
/* No mismatch found, it's a match */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Compare name, rate and fmtp, returning typical cmp result: 0 on match, and -1 / 1 on mismatch.
|
||||
* If cmp_fmtp is false, do *not* compare the fmtp string; if true, compare fmtp 1:1 as strings.
|
||||
* If cmp_payload_type is false, do *not* compare the payload_type number.
|
||||
* The fmtp is only string-compared -- e.g. if AMR parameters appear in a different order, it amounts to a mismatch even
|
||||
* though all parameters are the same. */
|
||||
int sdp_audio_codec_cmp(const struct sdp_audio_codec *a, const struct sdp_audio_codec *b,
|
||||
bool cmp_fmtp, bool cmp_payload_type)
|
||||
{
|
||||
int cmp;
|
||||
if (a == b)
|
||||
return 0;
|
||||
if (!a)
|
||||
return -1;
|
||||
if (!b)
|
||||
return 1;
|
||||
cmp = strncmp(a->subtype_name, b->subtype_name, sizeof(a->subtype_name));
|
||||
if (cmp)
|
||||
return cmp;
|
||||
cmp = OSMO_CMP(a->rate, b->rate);
|
||||
if (cmp)
|
||||
return cmp;
|
||||
if (cmp_fmtp) {
|
||||
cmp = strncmp(a->fmtp, b->fmtp, sizeof(a->fmtp));
|
||||
/* In case of AMR, allow logical matching; we only need to do that if the strings differ. */
|
||||
if (cmp
|
||||
&& !strcmp("AMR", a->subtype_name)
|
||||
&& amr_fmtp_match(a->fmtp, b->fmtp))
|
||||
cmp = 0;
|
||||
if (cmp)
|
||||
return cmp;
|
||||
}
|
||||
if (cmp_payload_type) {
|
||||
cmp = OSMO_CMP(a->payload_type, b->payload_type);
|
||||
if (cmp)
|
||||
return cmp;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Compare two lists of audio codecs, returning typical cmp result: 0 on match, and -1 / 1 on mismatch.
|
||||
* The ordering in the two lists may differ, except that the first codec in 'a' must also be the first codec in 'b'.
|
||||
* This is because the first codec typically expresses the preferred codec to use.
|
||||
* If cmp_fmtp is false, do *not* compare the fmtp strings; if true, compare fmtp 1:1 as strings.
|
||||
* If cmp_payload_type is false, do *not* compare the payload_type numbers.
|
||||
* The fmtp is only string-compared -- e.g. if AMR parameters appear in a different order, it amounts to a mismatch even
|
||||
* though all parameters are the same. */
|
||||
int sdp_audio_codecs_cmp(const struct sdp_audio_codecs *a, const struct sdp_audio_codecs *b,
|
||||
bool cmp_fmtp, bool cmp_payload_type)
|
||||
{
|
||||
const struct sdp_audio_codec *codec_a;
|
||||
const struct sdp_audio_codec *codec_b;
|
||||
int cmp;
|
||||
if (a == b)
|
||||
return 0;
|
||||
if (!a)
|
||||
return -1;
|
||||
if (!b)
|
||||
return 1;
|
||||
|
||||
cmp = OSMO_CMP(a->count, b->count);
|
||||
if (cmp)
|
||||
return cmp;
|
||||
|
||||
if (!a->count)
|
||||
return 0;
|
||||
|
||||
/* The first codec is the "chosen" codec and should match. The others may appear in different order. */
|
||||
cmp = sdp_audio_codec_cmp(&a->codec[0], &b->codec[0], cmp_fmtp, cmp_payload_type);
|
||||
if (cmp)
|
||||
return cmp;
|
||||
|
||||
/* See if each codec in a is also present in b */
|
||||
sdp_audio_codecs_foreach(codec_a, a) {
|
||||
bool match_found = false;
|
||||
sdp_audio_codecs_foreach(codec_b, b) {
|
||||
if (!sdp_audio_codec_cmp(codec_a, codec_b, cmp_fmtp, cmp_payload_type)) {
|
||||
match_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match_found)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add an SDP audio codec entry.
|
||||
* If 'once' == true, do not add an entry if an identical entry (except for the payload_type number) already exists.
|
||||
* If 'pick_unused_pt_nr' == true, pick an unused PT nr in case 'payload_type' is already taken in 'ac'.
|
||||
* Return the new entry, or NULL if there is no more space in the list.
|
||||
*/
|
||||
struct sdp_audio_codec *sdp_audio_codecs_add(struct sdp_audio_codecs *ac, unsigned int payload_type,
|
||||
const char *subtype_name, unsigned int rate, const char *fmtp,
|
||||
bool once, bool pick_unused_pt_nr)
|
||||
{
|
||||
struct sdp_audio_codec c = {
|
||||
.payload_type = payload_type,
|
||||
.rate = rate,
|
||||
};
|
||||
OSMO_STRLCPY_ARRAY(c.subtype_name, subtype_name);
|
||||
if (fmtp && *fmtp)
|
||||
OSMO_STRLCPY_ARRAY(c.fmtp, fmtp);
|
||||
return sdp_audio_codecs_add_copy(ac, &c, once, pick_unused_pt_nr);
|
||||
}
|
||||
|
||||
struct sdp_audio_codec *sdp_audio_codecs_add_copy(struct sdp_audio_codecs *ac,
|
||||
const struct sdp_audio_codec *codec,
|
||||
bool once, bool pick_unused_pt_nr)
|
||||
{
|
||||
struct sdp_audio_codec *new_entry;
|
||||
|
||||
if (once) {
|
||||
struct sdp_audio_codec *exists;
|
||||
exists = sdp_audio_codecs_by_descr(ac, codec);
|
||||
if (exists)
|
||||
return exists;
|
||||
}
|
||||
|
||||
if (ac->count >= ARRAY_SIZE(ac->codec))
|
||||
return NULL;
|
||||
|
||||
/* Compose new_entry in an unused entry, and increment 'count' further below. */
|
||||
new_entry = &ac->codec[ac->count];
|
||||
|
||||
/* Take provided entry as-is. */
|
||||
*new_entry = *codec;
|
||||
|
||||
/* Adjust payload_type number? */
|
||||
if (pick_unused_pt_nr) {
|
||||
const struct sdp_audio_codec *c;
|
||||
bool exists = false;
|
||||
unsigned int existing_pt_max = 96;
|
||||
|
||||
sdp_audio_codecs_foreach(c, ac) {
|
||||
if (c->payload_type == new_entry->payload_type)
|
||||
exists = true;
|
||||
|
||||
existing_pt_max = OSMO_MAX(existing_pt_max, c->payload_type);
|
||||
|
||||
/* For dynamic allocations, skip these predefined numbers, taken from enum mgcp_codecs:
|
||||
* CODEC_GSMEFR_8000_1 = 110, 3GPP TS 48.103 table 5.4.2.2.1
|
||||
* CODEC_GSMHR_8000_1 = 111, 3GPP TS 48.103 table 5.4.2.2.1
|
||||
* CODEC_AMR_8000_1 = 112, 3GPP TS 48.103 table 5.4.2.2.1
|
||||
* CODEC_AMRWB_16000_1 = 113, 3GPP TS 48.103 table 5.4.2.2.1
|
||||
* CODEC_IUFP = 96,
|
||||
* CODEC_CLEARMODE = 120, 3GPP TS 48.103 table 5.4.2.2.1
|
||||
*/
|
||||
if (existing_pt_max >= 110 && existing_pt_max < 113)
|
||||
existing_pt_max = 113;
|
||||
else if (existing_pt_max == 120)
|
||||
existing_pt_max++;
|
||||
}
|
||||
|
||||
if (exists)
|
||||
new_entry->payload_type = existing_pt_max + 1;
|
||||
}
|
||||
|
||||
/* new_entry is complete, increment list count. */
|
||||
ac->count++;
|
||||
|
||||
return new_entry;
|
||||
}
|
||||
|
||||
/* Find or create an entry for the given payload_type number in the given list of codecs.
|
||||
* If the given payload_type number is already present in ac, return the first matching entry.
|
||||
* If no such payload_type number is present: a) return NULL if create == false;
|
||||
* b) If create == true, add a mostly empty codec entry to the end of ac with the given payload_type number, and return
|
||||
* the created entry.
|
||||
* If create == true, a NULL return value means that there was no unused entry left in ac to add this payload_type.
|
||||
*/
|
||||
struct sdp_audio_codec *sdp_audio_codecs_by_payload_type(struct sdp_audio_codecs *ac, unsigned int payload_type,
|
||||
bool create)
|
||||
{
|
||||
struct sdp_audio_codec *codec;
|
||||
sdp_audio_codecs_foreach(codec, ac) {
|
||||
if (codec->payload_type == payload_type)
|
||||
return codec;
|
||||
}
|
||||
if (!create)
|
||||
return NULL;
|
||||
|
||||
if (ac->count >= ARRAY_SIZE(ac->codec))
|
||||
return NULL;
|
||||
|
||||
codec = &ac->codec[ac->count];
|
||||
*codec = (struct sdp_audio_codec){
|
||||
.payload_type = payload_type,
|
||||
.rate = 8000,
|
||||
};
|
||||
ac->count++;
|
||||
return codec;
|
||||
}
|
||||
|
||||
/* Return a given sdp_msg's codec entry that matches the subtype_name and rate of the given codec, or NULL if no
|
||||
* match is found. Comparison is made by sdp_audio_codec_cmp(cmp_payload_type=false). */
|
||||
struct sdp_audio_codec *sdp_audio_codecs_by_descr(struct sdp_audio_codecs *ac, const struct sdp_audio_codec *codec)
|
||||
{
|
||||
struct sdp_audio_codec *i;
|
||||
sdp_audio_codecs_foreach(i, ac) {
|
||||
if (!sdp_audio_codec_cmp(i, codec, true, false))
|
||||
return i;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Remove the codec entry pointed at by 'codec'. 'codec' must point at an entry of 'ac'.
|
||||
* To use any external codec instance, use sdp_audio_codecs_remove(ac, sdp_audio_codecs_by_descr(ac, codec)).
|
||||
* Return 0 on success, -ENOENT if codec does not point at the sdp->codec array. */
|
||||
int sdp_audio_codecs_remove(struct sdp_audio_codecs *ac, const struct sdp_audio_codec *codec)
|
||||
{
|
||||
struct sdp_audio_codec *i;
|
||||
if ((codec < ac->codec)
|
||||
|| ((codec - ac->codec) >= OSMO_MIN(ac->count, ARRAY_SIZE(ac->codec))))
|
||||
return -ENOENT;
|
||||
|
||||
/* Move all following entries one up */
|
||||
ac->count--;
|
||||
sdp_audio_codecs_foreach(i, ac) {
|
||||
if (i < codec)
|
||||
continue;
|
||||
*i = *(i+1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char * const sdp_mode_str[] = {
|
||||
[SDP_MODE_UNSET] = "-",
|
||||
[SDP_MODE_SENDONLY] = "sendonly",
|
||||
[SDP_MODE_RECVONLY] = "recvonly",
|
||||
[SDP_MODE_SENDRECV] = "sendrecv",
|
||||
[SDP_MODE_INACTIVE] = "inactive",
|
||||
static const struct osmo_sdp_codec codec_csd = {
|
||||
.payload_type = 120,
|
||||
.encoding_name = "CLEARMODE",
|
||||
.rate = 8000,
|
||||
};
|
||||
|
||||
/* Convert struct sdp_msg to the actual SDP protocol representation */
|
||||
int sdp_msg_to_sdp_str_buf(char *dst, size_t dst_size, const struct sdp_msg *sdp)
|
||||
void sdp_codecs_set_csd(void *ctx, struct osmo_sdp_codec_list *codecs)
|
||||
{
|
||||
const struct sdp_audio_codec *codec;
|
||||
struct osmo_strbuf sb = { .buf = dst, .len = dst_size };
|
||||
const char *ip;
|
||||
char ipv;
|
||||
|
||||
if (!sdp) {
|
||||
OSMO_STRBUF_PRINTF(sb, "%s", "");
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
ip = sdp->rtp.ip[0] ? sdp->rtp.ip : "0.0.0.0";
|
||||
ipv = (osmo_ip_str_type(ip) == AF_INET6) ? '6' : '4';
|
||||
|
||||
OSMO_STRBUF_PRINTF(sb,
|
||||
"v=0\r\n"
|
||||
"o=OsmoMSC 0 0 IN IP%c %s\r\n"
|
||||
"s=GSM Call\r\n"
|
||||
"c=IN IP%c %s\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio %d RTP/AVP",
|
||||
ipv, ip, ipv, ip,
|
||||
sdp->rtp.port);
|
||||
|
||||
/* Append all payload type numbers to 'm=audio <port> RTP/AVP 3 4 112' line */
|
||||
sdp_audio_codecs_foreach(codec, &sdp->audio_codecs)
|
||||
OSMO_STRBUF_PRINTF(sb, " %d", codec->payload_type);
|
||||
OSMO_STRBUF_PRINTF(sb, "\r\n");
|
||||
|
||||
/* Add details for all codecs */
|
||||
sdp_audio_codecs_foreach(codec, &sdp->audio_codecs) {
|
||||
if (!sdp_audio_codec_is_set(codec))
|
||||
continue;
|
||||
OSMO_STRBUF_PRINTF(sb, "a=rtpmap:%d %s/%d\r\n", codec->payload_type, codec->subtype_name,
|
||||
codec->rate > 0 ? codec->rate : 8000);
|
||||
if (codec->fmtp[0])
|
||||
OSMO_STRBUF_PRINTF(sb, OSMO_SDP_PREFIX_A_FMTP "%d %s\r\n", codec->payload_type, codec->fmtp);
|
||||
}
|
||||
|
||||
OSMO_STRBUF_PRINTF(sb, "a=ptime:%d\r\n", sdp->ptime > 0? sdp->ptime : 20);
|
||||
|
||||
if (sdp->mode != SDP_MODE_UNSET && sdp->mode < ARRAY_SIZE(sdp_mode_str))
|
||||
OSMO_STRBUF_PRINTF(sb, "a=%s\r\n", sdp_mode_str[sdp->mode]);
|
||||
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
/* Return the first line ending (or the end of the string) at or after the given string position. */
|
||||
const char *sdp_msg_line_end(const char *src)
|
||||
{
|
||||
const char *line_end = strchr(src, '\r');
|
||||
if (!line_end)
|
||||
line_end = strchr(src, '\n');
|
||||
if (!line_end)
|
||||
line_end = src + strlen(src);
|
||||
return line_end;
|
||||
}
|
||||
|
||||
/* parse a line like 'a=rtpmap:0 PCMU/8000', 'a=fmtp:112 octet-align=1; mode-set=4', 'a=ptime:20'.
|
||||
* The src should point at the character after 'a=', e.g. at the start of 'rtpmap', 'fmtp', 'ptime'
|
||||
*/
|
||||
int sdp_parse_attrib(struct sdp_msg *sdp, const char *src)
|
||||
{
|
||||
unsigned int payload_type;
|
||||
struct sdp_audio_codec *codec;
|
||||
#define A_RTPMAP "rtpmap:"
|
||||
#define A_PTIME "ptime:"
|
||||
#define A_RTCP "rtcp:"
|
||||
|
||||
if (osmo_str_startswith(src, A_RTPMAP)) {
|
||||
/* "a=rtpmap:3 GSM/8000" */
|
||||
char *audio_name;
|
||||
unsigned int channels = 1;
|
||||
if (sscanf(src, A_RTPMAP "%u", &payload_type) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
audio_name = strchr(src, ' ');
|
||||
if (!audio_name || audio_name >= sdp_msg_line_end(src))
|
||||
return -EINVAL;
|
||||
|
||||
codec = sdp_audio_codecs_by_payload_type(&sdp->audio_codecs, payload_type, true);
|
||||
if (!codec)
|
||||
return -ENOSPC;
|
||||
|
||||
if (sscanf(audio_name, " %31[^/]/%u/%u", codec->subtype_name, &codec->rate, &channels) < 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (channels != 1)
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
else if (osmo_str_startswith(src, OSMO_SDP_PREFIX_FMTP)) {
|
||||
/* "a=fmtp:112 octet-align=1;mode-set=0,1,2,3" */
|
||||
char *fmtp_str;
|
||||
const char *line_end = sdp_msg_line_end(src);
|
||||
if (sscanf(src, OSMO_SDP_PREFIX_FMTP "%u", &payload_type) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
fmtp_str = strchr(src, ' ');
|
||||
if (!fmtp_str)
|
||||
return -EINVAL;
|
||||
fmtp_str++;
|
||||
if (fmtp_str >= line_end)
|
||||
return -EINVAL;
|
||||
|
||||
codec = sdp_audio_codecs_by_payload_type(&sdp->audio_codecs, payload_type, true);
|
||||
if (!codec)
|
||||
return -ENOSPC;
|
||||
|
||||
/* (+1 because osmo_strlcpy() interprets it as size including the '\0') */
|
||||
osmo_strlcpy(codec->fmtp, fmtp_str, line_end - fmtp_str + 1);
|
||||
}
|
||||
|
||||
else if (osmo_str_startswith(src, A_PTIME)) {
|
||||
/* "a=ptime:20" */
|
||||
if (sscanf(src, A_PTIME "%u", &sdp->ptime) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
}
|
||||
|
||||
else if (osmo_str_startswith(src, A_RTCP)) {
|
||||
/* TODO? */
|
||||
}
|
||||
|
||||
else if (osmo_str_startswith(src, sdp_mode_str[SDP_MODE_SENDRECV])) {
|
||||
/* "a=sendrecv" */
|
||||
sdp->mode = SDP_MODE_SENDRECV;
|
||||
}
|
||||
|
||||
else if (osmo_str_startswith(src, sdp_mode_str[SDP_MODE_SENDONLY])) {
|
||||
/* "a=sendonly" */
|
||||
sdp->mode = SDP_MODE_SENDONLY;
|
||||
}
|
||||
|
||||
else if (osmo_str_startswith(src, sdp_mode_str[SDP_MODE_RECVONLY])) {
|
||||
/* "a=recvonly" */
|
||||
sdp->mode = SDP_MODE_RECVONLY;
|
||||
}
|
||||
|
||||
else if (osmo_str_startswith(src, sdp_mode_str[SDP_MODE_INACTIVE])) {
|
||||
/* "a=inactive" */
|
||||
sdp->mode = SDP_MODE_INACTIVE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct value_string sdp_msg_payload_type_names[] = {
|
||||
{ 0, "PCMU" },
|
||||
{ 3, "GSM" },
|
||||
{ 8, "PCMA" },
|
||||
{ 18, "G729" },
|
||||
{ 110, "GSM-EFR" },
|
||||
{ 111, "GSM-HR-08" },
|
||||
{ 112, "AMR" },
|
||||
{ 113, "AMR-WB" },
|
||||
{}
|
||||
};
|
||||
|
||||
/* Return payload type number matching given string ("AMR", "GSM", ...) or negative if not found. */
|
||||
int sdp_subtype_name_to_payload_type(const char *subtype_name)
|
||||
{
|
||||
return get_string_value(sdp_msg_payload_type_names, subtype_name);
|
||||
}
|
||||
|
||||
/* Parse a line like 'm=audio 16398 RTP/AVP 0 3 8 96 112', starting after the '=' */
|
||||
static int sdp_parse_media_description(struct sdp_msg *sdp, const char *src)
|
||||
{
|
||||
unsigned int port;
|
||||
int i;
|
||||
const char *payload_type_str;
|
||||
const char *line_end = sdp_msg_line_end(src);
|
||||
if (sscanf(src, "audio %u RTP/AVP", &port) < 1)
|
||||
return -ENOTSUP;
|
||||
|
||||
if (port > 0xffff)
|
||||
return -EINVAL;
|
||||
|
||||
sdp->rtp.port = port;
|
||||
|
||||
/* skip "audio 12345 RTP/AVP ", i.e. 3 spaces on */
|
||||
payload_type_str = src;
|
||||
for (i = 0; i < 3; i++) {
|
||||
payload_type_str = strchr(payload_type_str, ' ');
|
||||
if (!payload_type_str)
|
||||
return -EINVAL;
|
||||
while (*payload_type_str == ' ')
|
||||
payload_type_str++;
|
||||
if (payload_type_str >= line_end)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Parse listing of payload type numbers after "RTP/AVP" */
|
||||
while (payload_type_str < line_end) {
|
||||
unsigned int payload_type;
|
||||
struct sdp_audio_codec *codec;
|
||||
const char *subtype_name;
|
||||
if (sscanf(payload_type_str, "%u", &payload_type) < 1)
|
||||
return -EINVAL;
|
||||
|
||||
codec = sdp_audio_codecs_by_payload_type(&sdp->audio_codecs, payload_type, true);
|
||||
if (!codec)
|
||||
return -ENOSPC;
|
||||
|
||||
/* Fill in subtype name for fixed payload types */
|
||||
subtype_name = get_value_string_or_null(sdp_msg_payload_type_names, codec->payload_type);
|
||||
if (subtype_name)
|
||||
OSMO_STRLCPY_ARRAY(codec->subtype_name, subtype_name);
|
||||
|
||||
payload_type_str = strchr(payload_type_str, ' ');
|
||||
if (!payload_type_str)
|
||||
payload_type_str = line_end;
|
||||
while (*payload_type_str == ' ')
|
||||
payload_type_str++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* parse a line like 'c=IN IP4 192.168.11.151' starting after the '=' */
|
||||
static int sdp_parse_connection_info(struct sdp_msg *sdp, const char *src)
|
||||
{
|
||||
char ipv[10];
|
||||
char addr_str[INET6_ADDRSTRLEN];
|
||||
if (sscanf(src, "IN %s %s", ipv, addr_str) < 2)
|
||||
return -EINVAL;
|
||||
|
||||
/* supporting only IPv4 */
|
||||
if (strcmp(ipv, "IP4"))
|
||||
return -ENOTSUP;
|
||||
|
||||
osmo_sockaddr_str_from_str(&sdp->rtp, addr_str, sdp->rtp.port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse SDP string into struct sdp_msg. Return 0 on success, negative on error. */
|
||||
int sdp_msg_from_sdp_str(struct sdp_msg *sdp, const char *src)
|
||||
{
|
||||
const char *pos;
|
||||
*sdp = (struct sdp_msg){};
|
||||
|
||||
for (pos = src; pos && *pos; pos++) {
|
||||
char attrib;
|
||||
int rc = 0;
|
||||
|
||||
if (*pos == '\r' || *pos == '\n')
|
||||
continue;
|
||||
|
||||
/* Expecting only lines starting with 'X='. Not being too strict about it is probably alright. */
|
||||
if (pos[1] != '=')
|
||||
goto next_line;
|
||||
|
||||
attrib = *pos;
|
||||
pos += 2;
|
||||
switch (attrib) {
|
||||
/* a=... */
|
||||
case 'a':
|
||||
rc = sdp_parse_attrib(sdp, pos);
|
||||
break;
|
||||
case 'm':
|
||||
rc = sdp_parse_media_description(sdp, pos);
|
||||
break;
|
||||
case 'c':
|
||||
rc = sdp_parse_connection_info(sdp, pos);
|
||||
break;
|
||||
default:
|
||||
/* ignore any other parameters */
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
size_t line_len;
|
||||
const char *line_end = sdp_msg_line_end(pos);
|
||||
pos -= 2;
|
||||
line_len = line_end - pos;
|
||||
switch (rc) {
|
||||
case -EINVAL:
|
||||
LOGP(DMNCC, LOGL_ERROR,
|
||||
"Failed to parse SDP: invalid line: %s\n", osmo_quote_str(pos, line_len));
|
||||
break;
|
||||
case -ENOSPC:
|
||||
LOGP(DMNCC, LOGL_ERROR,
|
||||
"Failed to parse SDP: no more space for: %s\n", osmo_quote_str(pos, line_len));
|
||||
break;
|
||||
case -ENOTSUP:
|
||||
LOGP(DMNCC, LOGL_ERROR,
|
||||
"Failed to parse SDP: not supported: %s\n", osmo_quote_str(pos, line_len));
|
||||
break;
|
||||
default:
|
||||
LOGP(DMNCC, LOGL_ERROR,
|
||||
"Failed to parse SDP: %s\n", osmo_quote_str(pos, line_len));
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
next_line:
|
||||
pos = strstr(pos, "\r\n");
|
||||
if (!pos)
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Leave only those codecs in 'ac_dest' that are also present in 'ac_other'.
|
||||
* The matching is made by sdp_audio_codec_cmp(cmp_payload_type=false), i.e. payload_type numbers are not compared and
|
||||
* fmtp parameters are compared 1:1 as plain strings.
|
||||
* If translate_payload_type_numbers has an effect if ac_dest and ac_other have mismatching payload_type numbers for the
|
||||
* same SDP codec descriptions. If translate_payload_type_numbers is true, take the payload_type numbers from ac_other.
|
||||
* If false, keep payload_type numbers in ac_dest unchanged. */
|
||||
void sdp_audio_codecs_intersection(struct sdp_audio_codecs *ac_dest, const struct sdp_audio_codecs *ac_other,
|
||||
bool translate_payload_type_numbers)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < ac_dest->count; i++) {
|
||||
struct sdp_audio_codec *codec = &ac_dest->codec[i];
|
||||
struct sdp_audio_codec *other;
|
||||
OSMO_ASSERT(i < ARRAY_SIZE(ac_dest->codec));
|
||||
|
||||
other = sdp_audio_codecs_by_descr((struct sdp_audio_codecs *)ac_other, codec);
|
||||
|
||||
if (!other) {
|
||||
OSMO_ASSERT(sdp_audio_codecs_remove(ac_dest, codec) == 0);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Doing payload_type number translation as part of the intersection algorithm is a good match:
|
||||
* we already know ac_dest will be a subset of ac_other, and there is no need to resolve payload type
|
||||
* number conflicts. */
|
||||
if (translate_payload_type_numbers)
|
||||
codec->payload_type = other->payload_type;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure the given codec is listed as the first codec. 'codec' must be an actual codec entry of the given audio
|
||||
* codecs list. */
|
||||
void sdp_audio_codecs_select_member(struct sdp_audio_codecs *ac, struct sdp_audio_codec *codec)
|
||||
{
|
||||
struct sdp_audio_codec tmp;
|
||||
struct sdp_audio_codec *pos;
|
||||
OSMO_ASSERT((codec >= ac->codec)
|
||||
&& ((codec - ac->codec) < OSMO_MIN(ac->count, ARRAY_SIZE(ac->codec))));
|
||||
|
||||
/* Already the first? */
|
||||
if (codec == ac->codec)
|
||||
return;
|
||||
|
||||
tmp = *codec;
|
||||
for (pos = codec - 1; pos >= ac->codec; pos--)
|
||||
pos[1] = pos[0];
|
||||
|
||||
ac->codec[0] = tmp;
|
||||
return;
|
||||
}
|
||||
|
||||
bool sdp_audio_codecs_select(struct sdp_audio_codecs *ac, const struct sdp_audio_codec *codec)
|
||||
{
|
||||
struct sdp_audio_codec *select = sdp_audio_codecs_by_descr(ac, codec);
|
||||
if (!select)
|
||||
return false;
|
||||
sdp_audio_codecs_select_member(ac, select);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Short single-line representation of an SDP audio codec, convenient for logging.
|
||||
* Like "AMR/8000:octet-align=1#122" */
|
||||
int sdp_audio_codec_to_str_buf(char *buf, size_t buflen, const struct sdp_audio_codec *codec)
|
||||
{
|
||||
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
|
||||
OSMO_STRBUF_PRINTF(sb, "%s", codec->subtype_name);
|
||||
if (codec->rate != 8000)
|
||||
OSMO_STRBUF_PRINTF(sb, "/%u", codec->rate);
|
||||
if (codec->fmtp[0])
|
||||
OSMO_STRBUF_PRINTF(sb, ":%s", codec->fmtp);
|
||||
OSMO_STRBUF_PRINTF(sb, "#%d", codec->payload_type);
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
char *sdp_audio_codec_to_str_c(void *ctx, const struct sdp_audio_codec *codec)
|
||||
{
|
||||
OSMO_NAME_C_IMPL(ctx, 32, "sdp_audio_codec_to_str_c-ERROR", sdp_audio_codec_to_str_buf, codec)
|
||||
}
|
||||
|
||||
const char *sdp_audio_codec_to_str(const struct sdp_audio_codec *codec)
|
||||
{
|
||||
return sdp_audio_codec_to_str_c(OTC_SELECT, codec);
|
||||
}
|
||||
|
||||
/* Short single-line representation of a list of SDP audio codecs, convenient for logging */
|
||||
int sdp_audio_codecs_to_str_buf(char *buf, size_t buflen, const struct sdp_audio_codecs *ac)
|
||||
{
|
||||
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
|
||||
const struct sdp_audio_codec *codec;
|
||||
if (!ac->count)
|
||||
OSMO_STRBUF_PRINTF(sb, "(no-codecs)");
|
||||
sdp_audio_codecs_foreach(codec, ac) {
|
||||
bool first = (codec == ac->codec);
|
||||
if (!first)
|
||||
OSMO_STRBUF_PRINTF(sb, ",");
|
||||
OSMO_STRBUF_APPEND(sb, sdp_audio_codec_to_str_buf, codec);
|
||||
}
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
char *sdp_audio_codecs_to_str_c(void *ctx, const struct sdp_audio_codecs *ac)
|
||||
{
|
||||
OSMO_NAME_C_IMPL(ctx, 128, "sdp_audio_codecs_to_str_c-ERROR", sdp_audio_codecs_to_str_buf, ac)
|
||||
}
|
||||
|
||||
const char *sdp_audio_codecs_to_str(const struct sdp_audio_codecs *ac)
|
||||
{
|
||||
return sdp_audio_codecs_to_str_c(OTC_SELECT, ac);
|
||||
}
|
||||
|
||||
/* Short single-line representation of an SDP message, convenient for logging */
|
||||
int sdp_msg_to_str_buf(char *buf, size_t buflen, const struct sdp_msg *sdp)
|
||||
{
|
||||
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
|
||||
if (!sdp) {
|
||||
OSMO_STRBUF_PRINTF(sb, "NULL");
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
OSMO_STRBUF_PRINTF(sb, OSMO_SOCKADDR_STR_FMT, OSMO_SOCKADDR_STR_FMT_ARGS(&sdp->rtp));
|
||||
OSMO_STRBUF_PRINTF(sb, "{");
|
||||
OSMO_STRBUF_APPEND(sb, sdp_audio_codecs_to_str_buf, &sdp->audio_codecs);
|
||||
if (sdp->bearer_services.count) {
|
||||
OSMO_STRBUF_PRINTF(sb, ",");
|
||||
OSMO_STRBUF_APPEND(sb, csd_bs_list_to_str_buf, &sdp->bearer_services);
|
||||
}
|
||||
OSMO_STRBUF_PRINTF(sb, "}");
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
char *sdp_msg_to_str_c(void *ctx, const struct sdp_msg *sdp)
|
||||
{
|
||||
OSMO_NAME_C_IMPL(ctx, 128, "sdp_msg_to_str_c-ERROR", sdp_msg_to_str_buf, sdp)
|
||||
}
|
||||
|
||||
const char *sdp_msg_to_str(const struct sdp_msg *sdp)
|
||||
{
|
||||
return sdp_msg_to_str_c(OTC_SELECT, sdp);
|
||||
}
|
||||
|
||||
void sdp_audio_codecs_set_csd(struct sdp_audio_codecs *ac)
|
||||
{
|
||||
*ac = (struct sdp_audio_codecs){
|
||||
.count = 1,
|
||||
.codec = {{
|
||||
.payload_type = 120,
|
||||
.subtype_name = "CLEARMODE",
|
||||
.rate = 8000,
|
||||
}},
|
||||
};
|
||||
}
|
||||
|
||||
static void token_copy(char *dst, size_t dst_size, const char *start, const char *end1, const char *end2)
|
||||
{
|
||||
const char *end;
|
||||
if (end1 && end2)
|
||||
end = OSMO_MIN(end1, end2);
|
||||
else
|
||||
end = end1 ? : end2;
|
||||
if (!end)
|
||||
end = start + strlen(start);
|
||||
osmo_strlcpy(dst, start, OSMO_MIN(dst_size, end - start + 1));
|
||||
}
|
||||
#define TOKEN_COPY_ARRAY(DST, START, END1, END2) \
|
||||
token_copy(DST, sizeof(DST), START, END1, END2)
|
||||
|
||||
/* Parse a codec string as from sdp_audio_codec_to_str_buf() back to an sdp_audio_codec struct.
|
||||
* Write the parsed result to *dst.
|
||||
* The input string is like <subtype_name>[:<fmtp-string>][#<payload-type-nr>],
|
||||
* for example:
|
||||
* "FOO:my-fmtp=val;my-other-fmtp=val2#96"
|
||||
* Note that ';' are separators only within the fmtp string. This function does not separate those. In above example,
|
||||
* the fmtp string part is "my-fmtp=val;my-other-fmtp=val2" and ends up in dst->ftmp as-is.
|
||||
* Return 0 on success, negative on failure. */
|
||||
int sdp_audio_codec_from_str(struct sdp_audio_codec *dst, const char *str)
|
||||
{
|
||||
const char *colon = strchr(str, ':');
|
||||
const char *hash = strchr(str, '#');
|
||||
|
||||
*dst = (struct sdp_audio_codec){
|
||||
.rate = 8000,
|
||||
};
|
||||
|
||||
TOKEN_COPY_ARRAY(dst->subtype_name, str, colon, hash);
|
||||
if (colon)
|
||||
TOKEN_COPY_ARRAY(dst->fmtp, colon + 1, hash > colon ? hash : NULL, NULL);
|
||||
if (hash)
|
||||
dst->payload_type = atoi(hash + 1);
|
||||
return 0;
|
||||
osmo_sdp_codec_list_free(codecs);
|
||||
osmo_sdp_codec_list_add(ctx, &codec_csd);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue