summaryrefslogtreecommitdiff
path: root/src/libosmo-mgcp/mgcp_codec.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libosmo-mgcp/mgcp_codec.c')
-rw-r--r--src/libosmo-mgcp/mgcp_codec.c343
1 files changed, 343 insertions, 0 deletions
diff --git a/src/libosmo-mgcp/mgcp_codec.c b/src/libosmo-mgcp/mgcp_codec.c
new file mode 100644
index 0000000..2ce90dd
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_codec.c
@@ -0,0 +1,343 @@
+/*
+ * (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 <osmocom/mgcp/mgcp_internal.h>
+#include <osmocom/mgcp/mgcp_endp.h>
+#include <errno.h>
+
+/* Helper function to dump codec information of a specified codec to a printable
+ * string, used by dump_codec_summary() */
+static char *dump_codec(struct mgcp_rtp_codec *codec)
+{
+ static char str[256];
+ char *pt_str;
+
+ if (codec->payload_type > 76)
+ pt_str = "DYNAMIC";
+ else if (codec->payload_type > 72)
+ pt_str = "RESERVED <!>";
+ else if (codec->payload_type != PTYPE_UNDEFINED)
+ pt_str = codec->subtype_name;
+ else
+ pt_str = "INVALID <!>";
+
+ snprintf(str, sizeof(str), "(pt:%i=%s, audio:%s subt=%s, rate=%u, ch=%i, t=%u/%u)", codec->payload_type, pt_str,
+ codec->audio_name, codec->subtype_name, codec->rate, codec->channels, codec->frame_duration_num,
+ codec->frame_duration_den);
+ return str;
+}
+
+/*! Dump a summary of all negotiated codecs to debug log
+ * \param[in] conn related rtp-connection. */
+void mgcp_codec_summary(struct mgcp_conn_rtp *conn)
+{
+ struct mgcp_rtp_end *rtp;
+ unsigned int i;
+ struct mgcp_rtp_codec *codec;
+ struct mgcp_endpoint *endp;
+
+ rtp = &conn->end;
+ endp = conn->conn->endp;
+
+ if (rtp->codecs_assigned == 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "endpoint:0x%x conn:%s no codecs available\n", ENDPOINT_NUMBER(endp),
+ mgcp_conn_dump(conn->conn));
+ return;
+ }
+
+ /* Store parsed codec information */
+ for (i = 0; i < rtp->codecs_assigned; i++) {
+ codec = &rtp->codecs[i];
+
+ LOGP(DLMGCP, LOGL_DEBUG, "endpoint:0x%x conn:%s codecs[%u]:%s", ENDPOINT_NUMBER(endp),
+ mgcp_conn_dump(conn->conn), i, dump_codec(codec));
+
+ if (codec == rtp->codec)
+ LOGPC(DLMGCP, LOGL_DEBUG, " [selected]");
+
+ LOGPC(DLMGCP, LOGL_DEBUG, "\n");
+ }
+}
+
+/* Initalize or reset codec information with default data. */
+void codec_init(struct mgcp_rtp_codec *codec)
+{
+ if (codec->subtype_name)
+ talloc_free(codec->subtype_name);
+ if (codec->audio_name)
+ talloc_free(codec->audio_name);
+ memset(codec, 0, sizeof(*codec));
+ codec->payload_type = -1;
+ codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM;
+ codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN;
+ codec->rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE;
+ codec->channels = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS;
+}
+
+/*! Initalize or reset codec information with default data.
+ * \param[out] conn related rtp-connection. */
+void mgcp_codec_reset_all(struct mgcp_conn_rtp *conn)
+{
+ memset(conn->end.codecs, 0, sizeof(conn->end.codecs));
+ conn->end.codecs_assigned = 0;
+ conn->end.codec = NULL;
+}
+
+/* Set members of struct mgcp_rtp_codec, extrapolate in missing information */
+static int codec_set(void *ctx, struct mgcp_rtp_codec *codec,
+ int payload_type, const char *audio_name, unsigned int pt_offset)
+{
+ int rate;
+ int channels;
+ char audio_codec[64];
+
+ /* Initalize the codec struct with some default data to begin with */
+ codec_init(codec);
+
+ if (payload_type != PTYPE_UNDEFINED) {
+ /* Make sure we do not get any reserved or undefined type numbers */
+ /* See also: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */
+ if (payload_type == 1 || payload_type == 2 || payload_type == 19)
+ goto error;
+ if (payload_type >= 72 && payload_type <= 76)
+ goto error;
+ if (payload_type >= 127)
+ goto error;
+
+ codec->payload_type = payload_type;
+ }
+
+ /* When no audio name is given, we are forced to use the payload
+ * type to generate the audio name. This is only possible for
+ * non dynamic payload types, which are statically defined */
+ if (!audio_name) {
+ switch (payload_type) {
+ case 0:
+ audio_name = talloc_strdup(ctx, "PCMU/8000/1");
+ break;
+ case 3:
+ audio_name = talloc_strdup(ctx, "GSM/8000/1");
+ break;
+ case 8:
+ audio_name = talloc_strdup(ctx, "PCMA/8000/1");
+ break;
+ case 18:
+ audio_name = talloc_strdup(ctx, "G729/8000/1");
+ break;
+ default:
+ /* The given payload type is not known to us, or it
+ * it is a dynamic payload type for which we do not
+ * know the audio name. We must give up here */
+ goto error;
+ }
+ }
+
+ /* Now we extract the codec subtype name, rate and channels. The latter
+ * two are optional. If they are not present we use the safe defaults
+ * above. */
+ if (strlen(audio_name) > sizeof(audio_codec))
+ goto error;
+ channels = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS;
+ rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE;
+ if (sscanf(audio_name, "%63[^/]/%d/%d", audio_codec, &rate, &channels) < 1)
+ goto error;
+
+ /* Note: We only accept configurations with one audio channel! */
+ if (channels != 1)
+ goto error;
+
+ codec->rate = rate;
+ codec->channels = channels;
+ codec->subtype_name = talloc_strdup(ctx, audio_codec);
+ codec->audio_name = talloc_strdup(ctx, audio_name);
+ codec->payload_type = payload_type;
+
+ 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;
+ }
+
+ /* Derive the payload type if it is unknown */
+ if (codec->payload_type == PTYPE_UNDEFINED) {
+
+ /* For the known codecs from the static range we restore
+ * the IANA or 3GPP assigned payload type number */
+ if (codec->rate == 8000 && codec->channels == 1) {
+ /* See also: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */
+ if (!strcmp(codec->subtype_name, "GSM"))
+ codec->payload_type = 3;
+ else if (!strcmp(codec->subtype_name, "PCMA"))
+ codec->payload_type = 8;
+ else if (!strcmp(codec->subtype_name, "PCMU"))
+ codec->payload_type = 0;
+ else if (!strcmp(codec->subtype_name, "G729"))
+ codec->payload_type = 18;
+
+ /* See also: 3GPP TS 48.103, chapter 5.4.2.2 RTP Payload
+ * Note: These are not fixed payload types as the IANA
+ * defined once, they still remain dymanic payload
+ * types, but with a payload type number preference. */
+ else if (!strcmp(codec->subtype_name, "GSM-EFR"))
+ codec->payload_type = 110;
+ else if (!strcmp(codec->subtype_name, "GSM-HR-08"))
+ codec->payload_type = 111;
+ else if (!strcmp(codec->subtype_name, "AMR"))
+ codec->payload_type = 112;
+ else if (!strcmp(codec->subtype_name, "AMR-WB"))
+ codec->payload_type = 113;
+ }
+
+ /* If we could not determine a payload type we assume that
+ * we are dealing with a codec from the dynamic range. We
+ * choose a fixed identifier from 96-109. (Note: normally,
+ * the dynamic payload type rante is from 96-127, but from
+ * 110 onwards 3gpp defines prefered codec types, which are
+ * also fixed, see above) */
+ if (codec->payload_type < 0) {
+ codec->payload_type = 96 + pt_offset;
+ if (codec->payload_type > 109)
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ /* Make sure we leave a clean codec entry on error. */
+ codec_init(codec);
+ memset(codec, 0, sizeof(*codec));
+ return -EINVAL;
+}
+
+/*! Add codec configuration depending on payload type and/or codec name. This
+ * function uses the input parameters to extrapolate the full codec information.
+ * \param[out] codec configuration (caller provided memory).
+ * \param[out] conn related rtp-connection.
+ * \param[in] payload_type codec type id (e.g. 3 for GSM, -1 when undefined).
+ * \param[in] audio_name audio codec name (e.g. "GSM/8000/1").
+ * \returns 0 on success, -EINVAL on failure. */
+int mgcp_codec_add(struct mgcp_conn_rtp *conn, int payload_type, const char *audio_name)
+{
+ int rc;
+
+ /* The amount of codecs we can store is limited, make sure we do not
+ * overrun this limit. */
+ if (conn->end.codecs_assigned >= MGCP_MAX_CODECS)
+ return -EINVAL;
+
+ rc = codec_set(conn->conn, &conn->end.codecs[conn->end.codecs_assigned], payload_type, audio_name,
+ conn->end.codecs_assigned);
+ if (rc != 0)
+ return -EINVAL;
+
+ conn->end.codecs_assigned++;
+
+ return 0;
+}
+
+/* Check if the given codec is applicable on the specified endpoint
+ * Helper function for mgcp_codec_decide() */
+static bool is_codec_compatible(const struct mgcp_endpoint *endp, const struct mgcp_rtp_codec *codec)
+{
+ char codec_name[64];
+
+ /* A codec name must be set, if not, this might mean that the codec
+ * (payload type) that was assigned is unknown to us so we must stop
+ * here. */
+ if (!codec->subtype_name)
+ return false;
+
+ /* We now extract the codec_name (letters before the /, e.g. "GSM"
+ * from the audio name that is stored in the trunk configuration.
+ * We do not compare to the full audio_name because we expect that
+ * "GSM", "GSM/8000" and "GSM/8000/1" are all compatible when the
+ * audio name of the codec is set to "GSM" */
+ if (sscanf(endp->tcfg->audio_name, "%63[^/]/%*d/%*d", codec_name) < 1)
+ return false;
+
+ /* Finally we check if the subtype_name we have generated from the
+ * audio_name in the trunc struct patches the codec_name of the
+ * given codec */
+ if (strcasecmp(codec_name, codec->subtype_name) == 0)
+ return true;
+
+ /* FIXME: It is questinable that the method to pick a compatible
+ * codec can work properly. Since this useses tcfg->audio_name, as
+ * a reference, which is set to "AMR/8000" permanently.
+ * tcfg->audio_name must be updated by the first connection that
+ * has been made on an endpoint, so that the second connection
+ * can make a meaningful decision here */
+
+ return false;
+}
+
+/*! Decide for one suitable codec
+ * \param[in] conn related rtp-connection.
+ * \returns 0 on success, -EINVAL on failure. */
+int mgcp_codec_decide(struct mgcp_conn_rtp *conn)
+{
+ struct mgcp_rtp_end *rtp;
+ unsigned int i;
+ struct mgcp_endpoint *endp;
+ bool codec_assigned = false;
+
+ endp = conn->conn->endp;
+ rtp = &conn->end;
+
+ /* This function works on the results the SDP/LCO parser has extracted
+ * from the MGCP message. The goal is to select a suitable codec for
+ * the given connection. When transcoding is available, the first codec
+ * from the codec list is taken without further checking. When
+ * transcoding is not available, then the choice must be made more
+ * carefully. Each codec in the list is checked until one is found that
+ * is rated compatible. The rating is done by the helper function
+ * is_codec_compatible(), which does the actual checking. */
+ for (i = 0; i < rtp->codecs_assigned; i++) {
+ /* When no transcoding is available, avoid codecs that would
+ * require transcoding. */
+ if (endp->tcfg->no_audio_transcoding && !is_codec_compatible(endp, &rtp->codecs[i])) {
+ LOGP(DLMGCP, LOGL_NOTICE, "transcoding not available, skipping codec: %d/%s\n",
+ rtp->codecs[i].payload_type, rtp->codecs[i].subtype_name);
+ continue;
+ }
+
+ rtp->codec = &rtp->codecs[i];
+ codec_assigned = true;
+ break;
+ }
+
+ /* FIXME: To the reviewes: This is problematic. I do not get why we
+ * need to reset the packet_duration_ms depending on the codec
+ * selection. I thought it were all 20ms? Is this to address some
+ * cornercase. (This piece of code was in the code path before,
+ * together with the note: "TODO/XXX: Store this per codec and derive
+ * it on use" */
+ if (codec_assigned) {
+ if (rtp->maximum_packet_time >= 0
+ && rtp->maximum_packet_time * rtp->codec->frame_duration_den >
+ rtp->codec->frame_duration_num * 1500)
+ rtp->packet_duration_ms = 0;
+
+ return 0;
+ }
+
+ return -EINVAL;
+}