Add support for telephone events

Not much tested yet, except for sending telephone events via SIP.
This commit is contained in:
Andreas Eversberg 2023-11-12 17:45:29 +01:00
parent 25a675a4ce
commit a138935937
4 changed files with 156 additions and 15 deletions

View File

@ -139,7 +139,7 @@ void receive_originator(struct osmo_cc_session_codec *codec, uint8_t marker, uin
if (codec->decoder == decode_te) {
struct telephone_event *te = (struct telephone_event *)data;
telephone_event(relation, marker, te);
rx_telephone_event(relation, marker, te);
return;
}
@ -204,6 +204,20 @@ void receive_terminator(struct osmo_cc_session_codec *codec, uint8_t __attribute
jitter_save(&call->term_dejitter, samples, len, 1, sequence, timestamp, ssrc);
}
void tx_telephone_event(call_relation_t *relation, uint8_t marker, struct telephone_event *te)
{
/* only if codec was negotiated */
if (!relation->telephone_event)
return;
/* there should be a codec at this point; just to be safe */
if (!relation->codec)
return;
/* encode and send via RTP */
osmo_cc_rtp_send_ts(relation->telephone_event, (uint8_t *)te, sizeof(*te), marker, relation->codec->media->tx_sequence, relation->codec->media->tx_timestamp, relation);
}
void call_clock(int len)
{
call_t *call;
@ -303,9 +317,23 @@ void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len
void encode_te(uint8_t __attribute__((unused)) *src_data, int __attribute__((unused)) src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
{
/* FIXME: TBD... */
*dst_data = NULL;
*dst_len = 0;
struct telephone_event *te = (void *)src_data;
uint8_t *dst;
if (src_len != sizeof(*te))
return;
dst = calloc(1, 4);
if (!dst)
return;
dst[0] = te->event;
dst[1] = te->e << 7;
dst[1] |= te->r << 6;
dst[1] |= (te->volume < 0x3f) ? te->volume : 0x3f;
dst[2] |= te->duration >> 8;
dst[3] |= te->duration;
*dst_data = dst;
*dst_len = 4;
}
void decode_te(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void __attribute__((unused)) *priv)
@ -323,7 +351,8 @@ void decode_te(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len,
te->e = src[1] >> 7;
te->r = (src[1] >> 6) & 0x1;
te->volume = src[1] & 0x3f;
te->duration = (src[2] << 16) | src[3];
te->duration = (src[2] << 8) | src[3];
*dst_data = (uint8_t *)te;
*dst_len = sizeof(*te);
}

View File

@ -2,6 +2,7 @@
void audio_init(void);
void receive_originator(struct osmo_cc_session_codec *codec, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len);
void receive_terminator(struct osmo_cc_session_codec *codec, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len);
void tx_telephone_event(call_relation_t *relation, uint8_t marker, struct telephone_event *te);
void call_clock(int len);
void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);

View File

@ -412,7 +412,7 @@ static void proxy_send_sdp_answer(call_relation_t *relation, osmo_cc_msg_t *msg)
// if (!relation->codec_negotiated || msg->type == OSMO_CC_MSG_SETUP_CNF) { gibt einen crash, da codec vor der antwort schon gesetzt ist. warum sollten wir nach einer antwort denn nochmal den codec schicken?
if (!relation->codec_negotiated) {
sdp = osmo_cc_helper_audio_accept(&relation->cc_ep->session_config, relation, relation->orig_codecs, receive_originator, relation->call->setup_msg, &relation->cc_session, &relation->codec, 0);
sdp = osmo_cc_helper_audio_accept_te(&relation->cc_ep->session_config, relation, relation->orig_codecs, receive_originator, relation->call->setup_msg, &relation->cc_session, &relation->codec, &relation->telephone_event, 0);
if (sdp) {
relation->codec_negotiated = 1;
PDEBUG(DROUTER, DEBUG_DEBUG, "Sending SDP answer to originator with supported codecs.\n");
@ -1110,7 +1110,7 @@ void cc_message(osmo_cc_endpoint_t *cc_ep, uint32_t callref, osmo_cc_msg_t *msg)
/* remove progress, since it will be added with the SDP answer */
osmo_cc_remove_ie(msg, OSMO_CC_IE_PROGRESS, 0);
/* negotiate codec */
osmo_cc_helper_audio_negotiate(msg, &relation->cc_session, &relation->codec);
osmo_cc_helper_audio_negotiate_te(msg, &relation->cc_session, &relation->codec, &relation->telephone_event);
if (relation->codec)
relation->codec_negotiated = 1;
}
@ -1184,6 +1184,9 @@ static struct param {
/* disc/rel */
int isdn_cause, sip_cause;
/* telephone-event */
char *event;
/* error */
char *error;
} param;
@ -1344,8 +1347,9 @@ static void routing_rtp_proxy(call_t *call, int argc, char *argv[], struct comma
if (!param.term_given) {
memcpy(&relation->term_codecs[0], &codecs_def[0], sizeof(relation->term_codecs[0]));
memcpy(&relation->term_codecs[1], &codecs_def[1], sizeof(relation->term_codecs[1]));
memset(&relation->term_codecs[2], 0, sizeof(relation->term_codecs[2]));
PDEBUG(DROUTER, DEBUG_INFO, "No terminating codeds given, using default '%s' and '%s'.\n", relation->term_codecs[0].payload_name, relation->term_codecs[1].payload_name);
memcpy(&relation->term_codecs[2], &codecs_def[2], sizeof(relation->term_codecs[2]));
memset(&relation->term_codecs[3], 0, sizeof(relation->term_codecs[3]));
PDEBUG(DROUTER, DEBUG_INFO, "No terminating codeds given, using default '%s' and '%s' and '%s'.\n", relation->term_codecs[0].payload_name, relation->term_codecs[1].payload_name, relation->term_codecs[2].payload_name);
} else
memcpy(relation->term_codecs, param.term_codecs, sizeof(param.term_codecs));
@ -1964,13 +1968,16 @@ static void routing_release(call_t *call, int argc, char *argv[], struct command
void recv_dtmf(void *priv, char digit, dtmf_meas_t __attribute__((unused)) *meas)
{
call_relation_t *relation = (call_relation_t *)priv;
char digit_string[7] = "dtmf x";
char digit_string[7+6+1] = "called-dtmf x";
if (!relation->call->routing.routing)
return;
digit_string[5] = digit;
routing_send(&relation->call->routing, digit_string);
digit_string[7+5] = digit;
if (relation == relation->call->relation_list)
routing_send(&relation->call->routing, digit_string + 7);
else
routing_send(&relation->call->routing, digit_string);
}
/* routing orders us to enable DTMF decoding */
@ -1978,6 +1985,15 @@ static void routing_dtmf(call_t *call, int __attribute__((unused)) argc, char __
{
call_relation_t *relation = call->relation_list;
if (!strcmp(command_def->name, "called-dtmf")) {
relation = relation->next;
if (!relation || relation->next) {
PDEBUG(DROUTER, DEBUG_NOTICE, "Cannot enable DTMF detection on the called side, because there is no single terminating callee.\n");
return;
}
}
dtmf_decode_exit(&relation->dtmf_dec);
/* we add 13, because we working at speech level and not at 0dBm, which is -13 dBm */
@ -1991,11 +2007,97 @@ static void routing_dtmf_stop(call_t *call, int __attribute__((unused)) argc, ch
{
call_relation_t *relation = call->relation_list;
if (!strcmp(command_def->name, "called-dtmf-stop")) {
relation = relation->next;
if (!relation || relation->next)
return;
}
dtmf_decode_exit(&relation->dtmf_dec);
relation->dtmf_dec_enable = 0;
}
/* routing orders us to send a telephone event */
struct param_def param_event[] = {
{ .n = "event", .s = &param.event, .d = "0..9|*|#|A..D", .mandatory = 1 },
{ .n = NULL }
};
static void routing_te(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def)
{
call_relation_t *relation = call->relation_list;
struct telephone_event te;
uint8_t marker;
uint8_t event;
if (!strcmp(command_def->name, "called-telephone-event")) {
relation = relation->next;
if (!relation || relation->next) {
PDEBUG(DROUTER, DEBUG_NOTICE, "Cannot send telephone event, because there is no single terminating callee.\n");
return;
}
}
if (!relation->telephone_event) {
PDEBUG(DROUTER, DEBUG_NOTICE, "Cannot send telephone event, because the codec was not negotiated.\n");
return;
}
parse_params(argc, argv, command_def);
if (!param.event || !param.event[0] || param.event[1]) {
PDEBUG(DROUTER, DEBUG_ERROR, "'telephone-event' command reqires an event character as parameter.\n");
return;
}
switch (param.event[0]) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
event = param.event[0] - '0';
break;
case '*':
event = 10;
break;
case '#':
event = 11;
break;
case 'a':
case 'b':
case 'c':
case 'd':
event = param.event[0] - 'a' + 12;
break;
case 'A':
case 'B':
case 'C':
case 'D':
event = param.event[0] - 'A' + 12;
break;
default:
PDEBUG(DROUTER, DEBUG_ERROR, "'telephone-event' with illegal event character '%c'.\n", param.event[0]);
return;
}
memset(&te, 0, sizeof(te));
marker = 1;
te.event = event;
te.e = 1;
te.volume = 0;
te.duration = 800; /* 100 ms */
tx_telephone_event(relation, marker, &te);
}
/* routing failed, release the call */
struct param_def param_error[] = {
{ .n = "string", .s = &param.error, .d = "error string to be displayed", .mandatory = 1 },
@ -2049,6 +2151,10 @@ struct command_def command_def[] = {
{ "release", routing_release, "Release call towards caller and release callee, if any.", param_disc_rel },
{ "dtmf", routing_dtmf, "Turn on DTMF detection.", NULL },
{ "dtmf-stop", routing_dtmf_stop, "Turn off DTMF detection.", NULL },
{ "called-dtmf", routing_dtmf, "Turn on DTMF detection at callee.", NULL },
{ "called-dtmf-stop", routing_dtmf_stop, "Turn off DTMF detection at callee.", NULL },
{ "telephone-event", routing_te, "Send digit as telephone event to caller.", param_event },
{ "called-telephone-event", routing_te, "Send digit as telephone event to callee.", param_event },
{ "error", routing_error, "Display error message.", param_error },
{ NULL, NULL, NULL, NULL }
};
@ -2060,6 +2166,11 @@ void routing_receive_stdout(routing_t *routing, const char *string)
char *argv[256], *token;
int i;
if (routing->call->relation_list)
PDEBUG(DSTDERR, DEBUG_INFO, "(call #%d) Command from routing: '%s'\n", routing->call->num, string);
else
PDEBUG(DSTDERR, DEBUG_INFO, "Command from routing: '%s'\n", string);
/* convert string into tokens */
while ((token = osmo_cc_strtok_quotes(&string)))
argv[argc++] = strdup(token);
@ -2123,7 +2234,7 @@ void routing_close(routing_t *routing)
call_destroy(call);
}
void telephone_event(call_relation_t *relation, uint8_t marker, struct telephone_event *te)
void rx_telephone_event(call_relation_t *relation, uint8_t marker, struct telephone_event *te)
{
char digit_string[7] = "dtmf x";

View File

@ -46,7 +46,7 @@ typedef struct call_relation {
int codec_negotiated;
struct osmo_cc_helper_audio_codecs orig_codecs[MAX_CODECS + 1]; /* codecs for originator */
struct osmo_cc_helper_audio_codecs term_codecs[MAX_CODECS + 1]; /* codecs for terminator, stored at relation of originator */
osmo_cc_session_codec_t *codec;
osmo_cc_session_codec_t *codec, *telephone_event; /* codec and optional telephone event codec */
dtmf_dec_t dtmf_dec; /* dtmf decoder */
int dtmf_dec_enable;/* feed decoder with data */
@ -105,7 +105,7 @@ int call_init(osmo_cc_endpoint_t *cc_ep1, osmo_cc_endpoint_t *cc_ep2, const char
void call_exit(void);
int call_handle(void);
void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg);
void telephone_event(call_relation_t *relation, uint8_t marker, struct telephone_event *te);
void rx_telephone_event(call_relation_t *relation, uint8_t marker, struct telephone_event *te);
void routing_help(void);