rtp_engine: add support for multirate RFC2833 digits

Add RFC2833 DTMF support for 16K, 24K, and 32K bitrate codecs.

Asterisk currently treats RFC2833 Digits as a single rtp payload type
with a fixed bitrate of 8K.  This change would expand that to 8, 16,
24 and 32K.

This requires checking the offered rtp types for any of these bitrates
and then adding an offer for each (if configured for RFC2833.)  DTMF
generation must also be changed in order to look at the current outbound
codec in order to generate appropriately timed rtp.

For cases where no outgoing audio has yet been sent prior to digit
generation, Asterisk now has a concept of a 'preferred' codec based on
offer order.

On inbound calls Asterisk will mimic the payload types of the RFC2833
digits.

On outbound calls Asterisk will choose the next free payload types starting
with 101.

UserNote: No change in configuration is required in order to enable this
feature. Endpoints configured to use RFC2833 will automatically have this
enabled. If the endpoint does not support this, it should not include it in
the SDP offer/response.

Resolves: #699
This commit is contained in:
Mike Bradeen
2024-04-08 11:00:14 -06:00
parent 6079ea6d31
commit 182ea91fc5
4 changed files with 361 additions and 36 deletions

View File

@@ -538,6 +538,8 @@ static int set_caps(struct ast_sip_session *session,
ast_codec_media_type2str(session_media->type),
ast_format_cap_get_names(caps, &usbuf),
ast_format_cap_get_names(peer, &thembuf));
} else {
ast_rtp_codecs_set_preferred_format(&codecs, ast_format_cap_get_format(joint, 0));
}
if (is_offer) {
@@ -559,7 +561,7 @@ static int set_caps(struct ast_sip_session *session,
AST_MEDIA_TYPE_UNKNOWN);
ast_format_cap_remove_by_type(caps, media_type);
if (session->endpoint->preferred_codec_only){
if (session->endpoint->preferred_codec_only) {
struct ast_format *preferred_fmt = ast_format_cap_get_format(joint, 0);
ast_format_cap_append(caps, preferred_fmt, 0);
ao2_ref(preferred_fmt, -1);
@@ -650,6 +652,42 @@ static pjmedia_sdp_attr* generate_rtpmap_attr(struct ast_sip_session *session, p
return attr;
}
static pjmedia_sdp_attr* generate_rtpmap_attr2(struct ast_sip_session *session, pjmedia_sdp_media *media, pj_pool_t *pool,
int rtp_code, int asterisk_format, struct ast_format *format, int code, int sample_rate)
{
#ifndef HAVE_PJSIP_ENDPOINT_COMPACT_FORM
extern pj_bool_t pjsip_use_compact_form;
#else
pj_bool_t pjsip_use_compact_form = pjsip_cfg()->endpt.use_compact_form;
#endif
pjmedia_sdp_rtpmap rtpmap;
pjmedia_sdp_attr *attr = NULL;
char tmp[64];
enum ast_rtp_options options = session->endpoint->media.g726_non_standard ?
AST_RTP_OPT_G726_NONSTANDARD : 0;
snprintf(tmp, sizeof(tmp), "%d", rtp_code);
pj_strdup2(pool, &media->desc.fmt[media->desc.fmt_count++], tmp);
if (rtp_code <= AST_RTP_PT_LAST_STATIC && pjsip_use_compact_form) {
return NULL;
}
rtpmap.pt = media->desc.fmt[media->desc.fmt_count - 1];
rtpmap.clock_rate = sample_rate;
pj_strdup2(pool, &rtpmap.enc_name, ast_rtp_lookup_mime_subtype2(asterisk_format, format, code, options));
if (!pj_stricmp2(&rtpmap.enc_name, "opus")) {
pj_cstr(&rtpmap.param, "2");
} else {
pj_cstr(&rtpmap.param, NULL);
}
pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
return attr;
}
static pjmedia_sdp_attr* generate_fmtp_attr(pj_pool_t *pool, struct ast_format *format, int rtp_code)
{
struct ast_str *fmtp0 = ast_str_alloca(256);
@@ -1749,6 +1787,13 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
pj_sockaddr ip;
int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
ast_format_cap_count(session->direct_media_cap);
/* Keep track of the sample rates for offered codecs so we can build matching
RFC 2833/4733 payload offers. */
AST_VECTOR(, int) sample_rates;
/* In case we can't init the sample rates, still try to do the rest. */
int build_dtmf_sample_rates = 1;
SCOPE_ENTER(1, "%s Type: %s %s\n", ast_sip_session_get_name(session),
ast_codec_media_type2str(media_type), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
@@ -1900,6 +1945,12 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
ast_format_cap_append_from_cap(caps, ast_stream_get_formats(stream), media_type);
}
/* Init the sample rates before we start adding them. Assume we will have at least one. */
if (AST_VECTOR_INIT(&sample_rates, 1)) {
ast_log(LOG_ERROR, "Unable to add dtmf formats to SDP!\n");
build_dtmf_sample_rates = 0;
}
for (index = 0; index < ast_format_cap_count(caps); ++index) {
struct ast_format *format = ast_format_cap_get_format(caps, index);
@@ -1938,7 +1989,24 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
}
if ((attr = generate_rtpmap_attr(session, media, pool, rtp_code, 1, format, 0))) {
int newrate = ast_rtp_lookup_sample_rate2(1, format, 0);
int i, added = 0;
media->attr[media->attr_count++] = attr;
if (build_dtmf_sample_rates) {
for (i = 0; i < AST_VECTOR_SIZE(&sample_rates); i++) {
/* Only add if we haven't already processed this sample rate. For instance
A-law and u-law 'share' one 8K DTMF payload type. */
if (newrate == AST_VECTOR_GET(&sample_rates, i)) {
added = 1;
break;
}
}
if (!added) {
AST_VECTOR_APPEND(&sample_rates, newrate);
}
}
}
if ((attr = generate_fmtp_attr(pool, format, rtp_code))) {
@@ -1963,20 +2031,38 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
if (!(noncodec & index)) {
continue;
}
rtp_code = ast_rtp_codecs_payload_code(
ast_rtp_instance_get_codecs(session_media->rtp), 0, NULL, index);
if (rtp_code == -1) {
continue;
}
if ((attr = generate_rtpmap_attr(session, media, pool, rtp_code, 0, NULL, index))) {
media->attr[media->attr_count++] = attr;
}
if (index == AST_RTP_DTMF) {
snprintf(tmp, sizeof(tmp), "%d 0-16", rtp_code);
attr = pjmedia_sdp_attr_create(pool, "fmtp", pj_cstr(&stmp, tmp));
media->attr[media->attr_count++] = attr;
if (index != AST_RTP_DTMF) {
rtp_code = ast_rtp_codecs_payload_code(
ast_rtp_instance_get_codecs(session_media->rtp), 0, NULL, index);
if (rtp_code == -1) {
continue;
} else if ((attr = generate_rtpmap_attr(session, media, pool, rtp_code, 0, NULL, index))) {
media->attr[media->attr_count++] = attr;
}
} else if (build_dtmf_sample_rates) {
/*
* Walk through the possible bitrates for the RFC 2833/4733 digits and generate the rtpmap
* attributes.
*/
int i;
for (i = 0; i < AST_VECTOR_SIZE(&sample_rates); i++) {
rtp_code = ast_rtp_codecs_payload_code_sample_rate(
ast_rtp_instance_get_codecs(session_media->rtp), 0, NULL, index, AST_VECTOR_GET(&sample_rates, i));
if (rtp_code == -1) {
continue;
}
if ((attr = generate_rtpmap_attr2(session, media, pool, rtp_code, 0, NULL, index, AST_VECTOR_GET(&sample_rates, i)))) {
media->attr[media->attr_count++] = attr;
snprintf(tmp, sizeof(tmp), "%d 0-16", (rtp_code));
attr = pjmedia_sdp_attr_create(pool, "fmtp", pj_cstr(&stmp, tmp));
media->attr[media->attr_count++] = attr;
}
}
}
if (media->desc.fmt_count == PJMEDIA_MAX_SDP_FMT) {
@@ -1985,6 +2071,8 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
}
}
/* we are done with the sample rates */
AST_VECTOR_FREE(&sample_rates);
/* If no formats were actually added to the media stream don't add it to the SDP */
if (!media->desc.fmt_count) {

View File

@@ -138,7 +138,6 @@
#define RTCP_PT_PSFB AST_RTP_RTCP_PSFB
#define RTP_MTU 1200
#define DTMF_SAMPLE_RATE_MS 8 /*!< DTMF samples per millisecond */
#define DEFAULT_DTMF_TIMEOUT (150 * (8000 / 1000)) /*!< samples */
@@ -434,6 +433,7 @@ struct ast_rtp {
unsigned int dtmf_timeout; /*!< When this timestamp is reached we consider END frame lost and forcibly abort digit */
unsigned int dtmfsamples;
enum ast_rtp_dtmf_mode dtmfmode; /*!< The current DTMF mode of the RTP stream */
unsigned int dtmf_samplerate_ms; /*!< The sample rate of the current RTP stream in ms (sample rate / 1000) */
/* DTMF Transmission Variables */
unsigned int lastdigitts;
char sending_digit; /*!< boolean - are we sending digits */
@@ -4284,8 +4284,10 @@ static int ast_rtp_dtmf_begin(struct ast_rtp_instance *instance, char digit)
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
struct ast_sockaddr remote_address = { {0,} };
int hdrlen = 12, res = 0, i = 0, payload = 101;
unsigned int sample_rate = 8000;
char data[256];
unsigned int *rtpheader = (unsigned int*)data;
RAII_VAR(struct ast_format *, payload_format, NULL, ao2_cleanup);
ast_rtp_instance_get_remote_address(instance, &remote_address);
@@ -4310,12 +4312,32 @@ static int ast_rtp_dtmf_begin(struct ast_rtp_instance *instance, char digit)
return -1;
}
/* Grab the payload that they expect the RFC2833 packet to be received in */
payload = ast_rtp_codecs_payload_code_tx(ast_rtp_instance_get_codecs(instance), 0, NULL, AST_RTP_DTMF);
if (rtp->lasttxformat == ast_format_none) {
/* No audio frames have been written yet so we have to lookup both the preferred payload type and bitrate. */
payload_format = ast_rtp_codecs_get_preferred_format(ast_rtp_instance_get_codecs(instance));
if (payload_format) {
/* If we have a preferred type, use that. Otherwise default to 8K. */
sample_rate = ast_format_get_sample_rate(payload_format);
}
} else {
sample_rate = ast_format_get_sample_rate(rtp->lasttxformat);
}
/* Grab the matching DTMF type payload */
payload = ast_rtp_codecs_payload_code_tx_sample_rate(ast_rtp_instance_get_codecs(instance), 0, NULL, AST_RTP_DTMF, sample_rate);
/* If this returns -1, we are being asked to send digits for a sample rate that is outside
what was negotiated for. Fall back if possible. */
if (payload == -1) {
return -1;
}
ast_test_suite_event_notify("DTMF_BEGIN", "Digit: %d\r\nPayload: %d\r\nRate: %d\r\n", digit, payload, sample_rate);
ast_debug(1, "Sending digit '%d' at rate %d with payload %d\n", digit, sample_rate, payload);
rtp->dtmfmute = ast_tvadd(ast_tvnow(), ast_tv(0, 500000));
rtp->send_duration = 160;
rtp->lastts += calc_txstamp(rtp, NULL) * DTMF_SAMPLE_RATE_MS;
rtp->dtmf_samplerate_ms = (sample_rate / 1000);
rtp->lastts += calc_txstamp(rtp, NULL) * rtp->dtmf_samplerate_ms;
rtp->lastdigitts = rtp->lastts + rtp->send_duration;
/* Create the actual packet that we will be sending */
@@ -4394,7 +4416,7 @@ static int ast_rtp_dtmf_continuation(struct ast_rtp_instance *instance)
/* And now we increment some values for the next time we swing by */
rtp->seqno++;
rtp->send_duration += 160;
rtp->lastts += calc_txstamp(rtp, NULL) * DTMF_SAMPLE_RATE_MS;
rtp->lastts += calc_txstamp(rtp, NULL) * rtp->dtmf_samplerate_ms;
return 0;
}
@@ -4472,7 +4494,7 @@ static int ast_rtp_dtmf_end_with_duration(struct ast_rtp_instance *instance, cha
res = 0;
/* Oh and we can't forget to turn off the stuff that says we are sending DTMF */
rtp->lastts += calc_txstamp(rtp, NULL) * DTMF_SAMPLE_RATE_MS;
rtp->lastts += calc_txstamp(rtp, NULL) * rtp->dtmf_samplerate_ms;
/* Reset the smoother as the delivery time stored in it is now out of date */
if (rtp->smoother) {