rtp: add rtphdrextrfc6464
authorGuillaume Desmottes <guillaume.desmottes@collabora.com>
Thu, 21 Jan 2021 17:04:58 +0000 (18:04 +0100)
committerGuillaume Desmottes <guillaume.desmottes@collabora.com>
Thu, 4 Feb 2021 10:12:51 +0000 (11:12 +0100)
Header Extension for Client-to-Mixer Audio Level Indication as
defined in RFC 6464.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/630>

docs/gst_plugins_cache.json
gst/rtpmanager/gstrtphdrext-rfc6464.c [new file with mode: 0644]
gst/rtpmanager/gstrtphdrext-rfc6464.h [new file with mode: 0644]
gst/rtpmanager/gstrtpmanager.c
gst/rtpmanager/meson.build
tests/check/elements/rtphdrextrfc6464.c [new file with mode: 0644]
tests/check/meson.build

index 13a57df..691c688 100644 (file)
                 },
                 "rank": "none"
             },
+            "rtphdrextrfc6464": {
+                "RTP-Header-Extension-URI": "urn:ietf:params:rtp-hdrext:ssrc-audio-level",
+                "author": "Guillaume Desmottes <guillaume.desmottes@collabora.com>",
+                "description": "Client-to-Mixer Audio Level Indication (RFC6464) RTP Header Extension",
+                "hierarchy": [
+                    "GstRTPHeaderExtensionRfc6464",
+                    "GstRTPHeaderExtension",
+                    "GstElement",
+                    "GstObject",
+                    "GInitiallyUnowned",
+                    "GObject"
+                ],
+                "klass": "Network/Extension/RTPHeader",
+                "long-name": "Client-to-Mixer Audio Level Indication (RFC6464) RTP Header Extension",
+                "properties": {
+                    "vad": {
+                        "blurb": "If the vad extension attribute is enabled or not",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "true",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gboolean",
+                        "writable": false
+                    }
+                },
+                "rank": "marginal"
+            },
             "rtphdrexttwcc": {
                 "RTP-Header-Extension-URI": "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01",
                 "author": "Matthew Waters <matthew@centricular.com>",
diff --git a/gst/rtpmanager/gstrtphdrext-rfc6464.c b/gst/rtpmanager/gstrtphdrext-rfc6464.c
new file mode 100644 (file)
index 0000000..8f347ea
--- /dev/null
@@ -0,0 +1,324 @@
+/* GStreamer
+ * Copyright (C) <2018> Havard Graff <havard.graff@gmail.com>
+ * Copyright (C) <2020-2021> Guillaume Desmottes <guillaume.desmottes@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more
+ */
+
+/**
+ * SECTION:element-rtphdrextrfc6464
+ * @title: rtphdrextrfc6464
+ * @short_description: Client-to-Mixer Audio Level Indication (RFC6464) RTP Header Extension
+ *
+ * Client-to-Mixer Audio Level Indication (RFC6464) RTP Header Extension.
+ * The extension should be automatically created by payloader and depayloaders,
+ * if their `auto-header-extension` property is enabled, if the extension
+ * is part of the RTP caps.
+ *
+ * Since: 1.20
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstrtphdrext-rfc6464.h"
+
+#include <gst/audio/audio.h>
+
+#define RFC6464_HDR_EXT_URI GST_RTP_HDREXT_BASE"ssrc-audio-level"
+
+GST_DEBUG_CATEGORY_STATIC (rtphdrrfc6464_twcc_debug);
+#define GST_CAT_DEFAULT (rtphdrrfc6464_twcc_debug)
+
+#define DEFAULT_VAD TRUE
+
+enum
+{
+  PROP_0,
+  PROP_VAD,
+};
+
+struct _GstRTPHeaderExtensionRfc6464
+{
+  GstRTPHeaderExtension parent;
+
+  gboolean vad;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GstRTPHeaderExtensionRfc6464,
+    gst_rtp_header_extension_rfc6464, GST_TYPE_RTP_HEADER_EXTENSION,
+    GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "rtphdrextrfc6464", 0,
+        "RTP RFC 6464 Header Extensions");
+    )
+     static void
+         gst_rtp_header_extension_rfc6464_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec)
+{
+  GstRTPHeaderExtensionRfc6464 *self =
+      GST_RTP_HEADER_EXTENSION_RFC6464 (object);
+
+  switch (prop_id) {
+    case PROP_VAD:
+      g_value_set_boolean (value, self->vad);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GstRTPHeaderExtensionFlags
+gst_rtp_header_extension_rfc6464_get_supported_flags (GstRTPHeaderExtension *
+    ext)
+{
+  return GST_RTP_HEADER_EXTENSION_ONE_BYTE | GST_RTP_HEADER_EXTENSION_TWO_BYTE;
+}
+
+static gsize
+gst_rtp_header_extension_rfc6464_get_max_size (GstRTPHeaderExtension * ext,
+    const GstBuffer * input_meta)
+{
+  return 2;
+}
+
+static void
+set_vad (GstRTPHeaderExtension * ext, gboolean vad)
+{
+  GstRTPHeaderExtensionRfc6464 *self = GST_RTP_HEADER_EXTENSION_RFC6464 (ext);
+
+  if (self->vad == vad)
+    return;
+
+  GST_DEBUG_OBJECT (ext, "vad: %d", vad);
+  self->vad = vad;
+  g_object_notify (G_OBJECT (self), "vad");
+}
+
+static gboolean
+gst_rtp_header_extension_rfc6464_set_attributes_from_caps (GstRTPHeaderExtension
+    * ext, const GstCaps * caps)
+{
+  gchar *field_name = gst_rtp_header_extension_get_sdp_caps_field_name (ext);
+  GstStructure *s = gst_caps_get_structure (caps, 0);
+  const gchar *ext_uri;
+  const GValue *arr;
+
+  if (!field_name)
+    return FALSE;
+
+  if ((ext_uri = gst_structure_get_string (s, field_name))) {
+    if (g_strcmp0 (ext_uri, gst_rtp_header_extension_get_uri (ext)) != 0) {
+      /* incompatible extension uri for this instance */
+      goto error;
+    }
+    set_vad (ext, DEFAULT_VAD);
+  } else if ((arr = gst_structure_get_value (s, field_name))
+      && GST_VALUE_HOLDS_ARRAY (arr)
+      && gst_value_array_get_size (arr) == 3) {
+    const GValue *val;
+    const gchar *vad_attr;
+
+    val = gst_value_array_get_value (arr, 1);
+    if (!G_VALUE_HOLDS_STRING (val))
+      goto error;
+    if (g_strcmp0 (g_value_get_string (val),
+            gst_rtp_header_extension_get_uri (ext)) != 0)
+      goto error;
+
+    val = gst_value_array_get_value (arr, 2);
+    if (!G_VALUE_HOLDS_STRING (val))
+      goto error;
+
+    vad_attr = g_value_get_string (val);
+
+    if (g_str_equal (vad_attr, "vad=on"))
+      set_vad (ext, TRUE);
+    else if (g_str_equal (vad_attr, "vad=off"))
+      set_vad (ext, FALSE);
+    else {
+      GST_WARNING_OBJECT (ext, "Invalid attribute: %s", vad_attr);
+      goto error;
+    }
+  } else {
+    /* unknown caps format */
+    goto error;
+  }
+
+  g_free (field_name);
+  return TRUE;
+
+error:
+  g_free (field_name);
+  return FALSE;
+}
+
+static gboolean
+gst_rtp_header_extension_rfc6464_set_caps_from_attributes (GstRTPHeaderExtension
+    * ext, GstCaps * caps)
+{
+  GstRTPHeaderExtensionRfc6464 *self = GST_RTP_HEADER_EXTENSION_RFC6464 (ext);
+  gchar *field_name = gst_rtp_header_extension_get_sdp_caps_field_name (ext);
+  GstStructure *s = gst_caps_get_structure (caps, 0);
+  GValue arr = G_VALUE_INIT;
+  GValue val = G_VALUE_INIT;
+
+  if (!field_name)
+    return FALSE;
+
+  g_value_init (&arr, GST_TYPE_ARRAY);
+  g_value_init (&val, G_TYPE_STRING);
+
+  /* direction */
+  g_value_set_string (&val, "");
+  gst_value_array_append_value (&arr, &val);
+
+  /* uri */
+  g_value_set_string (&val, gst_rtp_header_extension_get_uri (ext));
+  gst_value_array_append_value (&arr, &val);
+
+  /* attributes */
+  if (self->vad)
+    g_value_set_string (&val, "vad=on");
+  else
+    g_value_set_string (&val, "vad=off");
+  gst_value_array_append_value (&arr, &val);
+
+  gst_structure_set_value (s, field_name, &arr);
+
+  GST_DEBUG_OBJECT (self, "%" GST_PTR_FORMAT, caps);
+
+  g_value_unset (&val);
+  g_value_unset (&arr);
+
+  g_free (field_name);
+  return TRUE;
+}
+
+static gsize
+gst_rtp_header_extension_rfc6464_write (GstRTPHeaderExtension * ext,
+    const GstBuffer * input_meta, GstRTPHeaderExtensionFlags write_flags,
+    GstBuffer * output, guint8 * data, gsize size)
+{
+  GstAudioLevelMeta *meta;
+
+  g_return_val_if_fail (size >=
+      gst_rtp_header_extension_rfc6464_get_max_size (ext, NULL), -1);
+  g_return_val_if_fail (write_flags &
+      gst_rtp_header_extension_rfc6464_get_supported_flags (ext), -1);
+
+  meta = gst_buffer_get_audio_level_meta ((GstBuffer *) input_meta);
+  if (!meta) {
+    GST_LOG_OBJECT (ext, "no meta");
+    return 0;
+  }
+
+  if (meta->level > 127) {
+    GST_WARNING_OBJECT (ext, "level from meta is higher than 127: %d",
+        meta->level);
+    return -1;
+  }
+
+  GST_LOG_OBJECT (ext, "writing ext (level: %d voice: %d)", meta->level,
+      meta->voice_activity);
+
+  if (write_flags & GST_RTP_HEADER_EXTENSION_ONE_BYTE) {
+    *data = (meta->level & 0x7F) | (meta->voice_activity << 7);
+    return 1;
+  } else {
+    guint16 payload;
+
+    payload = ((meta->level & 0x7F) | (meta->voice_activity << 7)) << 8;
+    GST_WRITE_UINT16_LE (data, payload);
+    return 2;
+  }
+}
+
+static gboolean
+gst_rtp_header_extension_rfc6464_read (GstRTPHeaderExtension * ext,
+    GstRTPHeaderExtensionFlags read_flags, const guint8 * data, gsize size,
+    GstBuffer * buffer)
+{
+  guint8 val;
+  guint8 level;
+  gboolean voice_activity;
+
+  g_return_val_if_fail (read_flags &
+      gst_rtp_header_extension_rfc6464_get_supported_flags (ext), -1);
+
+  if (read_flags & GST_RTP_HEADER_EXTENSION_ONE_BYTE) {
+    val = data[0];
+  } else {
+    val = *((guint16 *) data) >> 8;
+  }
+
+  level = val & 0x7F;
+  voice_activity = (val & 0x80) >> 7;
+
+  GST_LOG_OBJECT (ext, "reading ext (level: %d voice: %d)", level,
+      voice_activity);
+
+  gst_buffer_add_audio_level_meta (buffer, level, voice_activity);
+
+  return TRUE;
+}
+
+static void
+gst_rtp_header_extension_rfc6464_class_init (GstRTPHeaderExtensionRfc6464Class *
+    klass)
+{
+  GstRTPHeaderExtensionClass *rtp_hdr_class;
+  GstElementClass *gstelement_class;
+  GObjectClass *gobject_class;
+
+  rtp_hdr_class = GST_RTP_HEADER_EXTENSION_CLASS (klass);
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = GST_ELEMENT_CLASS (klass);
+
+  gobject_class->get_property = gst_rtp_header_extension_rfc6464_get_property;
+
+  /**
+   * rtphdrextrfc6464:vad:
+   *
+   * If the vad extension attribute is enabled or not, default to %FALSE.
+   *
+   * Since: 1.20
+   */
+  g_object_class_install_property (gobject_class, PROP_VAD,
+      g_param_spec_boolean ("vad", "vad",
+          "If the vad extension attribute is enabled or not",
+          DEFAULT_VAD, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  rtp_hdr_class->get_supported_flags =
+      gst_rtp_header_extension_rfc6464_get_supported_flags;
+  rtp_hdr_class->get_max_size = gst_rtp_header_extension_rfc6464_get_max_size;
+  rtp_hdr_class->set_attributes_from_caps =
+      gst_rtp_header_extension_rfc6464_set_attributes_from_caps;
+  rtp_hdr_class->set_caps_from_attributes =
+      gst_rtp_header_extension_rfc6464_set_caps_from_attributes;
+  rtp_hdr_class->write = gst_rtp_header_extension_rfc6464_write;
+  rtp_hdr_class->read = gst_rtp_header_extension_rfc6464_read;
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "Client-to-Mixer Audio Level Indication (RFC6464) RTP Header Extension",
+      GST_RTP_HDREXT_ELEMENT_CLASS,
+      "Client-to-Mixer Audio Level Indication (RFC6464) RTP Header Extension",
+      "Guillaume Desmottes <guillaume.desmottes@collabora.com>");
+  gst_rtp_header_extension_class_set_uri (rtp_hdr_class, RFC6464_HDR_EXT_URI);
+}
+
+static void
+gst_rtp_header_extension_rfc6464_init (GstRTPHeaderExtensionRfc6464 * self)
+{
+  GST_DEBUG_OBJECT (self, "creating element");
+  self->vad = DEFAULT_VAD;
+}
diff --git a/gst/rtpmanager/gstrtphdrext-rfc6464.h b/gst/rtpmanager/gstrtphdrext-rfc6464.h
new file mode 100644 (file)
index 0000000..16dc2e6
--- /dev/null
@@ -0,0 +1,30 @@
+/* GStreamer
+ * Copyright (C) <2018> Havard Graff <havard.graff@gmail.com>
+ * Copyright (C) <2020-2021> Guillaume Desmottes <guillaume.desmottes@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more
+ */
+
+#ifndef __GST_RTPHDREXT_RFC6464_H__
+#define __GST_RTPHDREXT_RFC6464_H__
+
+#include <gst/gst.h>
+#include <gst/rtp/gstrtphdrext.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_RTP_HEADER_EXTENSION_RFC6464 (gst_rtp_header_extension_rfc6464_get_type())
+
+G_DECLARE_FINAL_TYPE (GstRTPHeaderExtensionRfc6464, gst_rtp_header_extension_rfc6464, GST, RTP_HEADER_EXTENSION_RFC6464, GstRTPHeaderExtension)
+
+G_END_DECLS
+
+#endif /* __GST_RTPHDREXT_RFC6464_H__ */
index aa7d374..1001334 100644 (file)
@@ -35,6 +35,7 @@
 #include "gstrtpst2022-1-fecdec.h"
 #include "gstrtpst2022-1-fecenc.h"
 #include "gstrtphdrext-twcc.h"
+#include "gstrtphdrext-rfc6464.h"
 
 static gboolean
 plugin_init (GstPlugin * plugin)
@@ -89,6 +90,10 @@ plugin_init (GstPlugin * plugin)
           GST_TYPE_RTP_HEADER_EXTENSION_TWCC))
     return FALSE;
 
+  if (!gst_element_register (plugin, "rtphdrextrfc6464", GST_RANK_MARGINAL,
+          GST_TYPE_RTP_HEADER_EXTENSION_RFC6464))
+    return FALSE;
+
   return TRUE;
 }
 
index ff2c9e8..6f8aee2 100644 (file)
@@ -4,6 +4,7 @@ rtpmanager_sources = [
   'gstrtpdtmfmux.c',
   'gstrtpjitterbuffer.c',
   'gstrtphdrext-twcc.c',
+  'gstrtphdrext-rfc6464.c',
   'gstrtpmux.c',
   'gstrtpptdemux.c',
   'gstrtprtxqueue.c',
@@ -26,7 +27,7 @@ gstrtpmanager = library('gstrtpmanager',
   rtpmanager_sources,
   c_args : gst_plugins_good_args,
   include_directories : [configinc, libsinc],
-  dependencies : [gstbase_dep, gstnet_dep, gstrtp_dep, gio_dep],
+  dependencies : [gstbase_dep, gstnet_dep, gstrtp_dep, gstaudio_dep, gio_dep],
   install : true,
   install_dir : plugins_install_dir,
 )
diff --git a/tests/check/elements/rtphdrextrfc6464.c b/tests/check/elements/rtphdrextrfc6464.c
new file mode 100644 (file)
index 0000000..c0fbcf3
--- /dev/null
@@ -0,0 +1,289 @@
+/* GStreamer
+ *
+ * unit test for RTP RFC 6464 Header Extensions
+ *
+ * Copyright (C) <2020-2021> Guillaume Desmottes <guillaume.desmottes@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <gst/check/gstcheck.h>
+#include <gst/rtp/rtp.h>
+#include <gst/sdp/gstsdpmessage.h>
+#include <gst/audio/audio.h>
+
+#define URN "urn:ietf:params:rtp-hdrext:ssrc-audio-level"
+
+#define SDP "v=0\r\n" \
+    "o=- 123456 2 IN IP4 127.0.0.1 \r\n" \
+    "s=-\r\n" \
+    "t=0 0\r\n" \
+    "a=maxptime:60\r\n" \
+    "a=sendrecv\r\n" \
+    "m=audio 55815 RTP/SAVPF 100\r\n" \
+    "c=IN IP4 1.1.1.1\r\n" \
+    "a=rtpmap:100 opus/48000/2\r\n"
+
+#define SDP_NO_VAD SDP \
+    "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n"
+#define SDP_VAD_ON SDP \
+    "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level vad=on\r\n"
+#define SDP_VAD_OFF SDP \
+    "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level vad=off\r\n"
+#define SDP_VAD_WRONG SDP \
+    "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level vad=badger\r\n"
+
+static GstCaps *
+create_caps (const gchar * sdp)
+{
+  GstSDPMessage *message;
+  glong length = -1;
+  const GstSDPMedia *media;
+  GstCaps *caps;
+
+  gst_sdp_message_new (&message);
+  gst_sdp_message_parse_buffer ((guint8 *) sdp, length, message);
+  media = gst_sdp_message_get_media (message, 0);
+  fail_unless (media != NULL);
+
+  caps = gst_sdp_media_get_caps_from_media (media, 100);
+  gst_sdp_media_attributes_to_caps (media, caps);
+  gst_sdp_message_free (message);
+  return caps;
+}
+
+static void
+check_caps (GstRTPHeaderExtension * ext, gboolean vad)
+{
+  GstCaps *caps;
+  GstStructure *s;
+  const GValue *arr, *val;
+
+  caps = gst_caps_new_empty_simple ("application/x-rtp");
+  fail_unless (gst_rtp_header_extension_set_caps_from_attributes (ext, caps));
+  s = gst_caps_get_structure (caps, 0);
+
+  arr = gst_structure_get_value (s, "extmap-1");
+  fail_unless (arr != NULL);
+  fail_unless (GST_VALUE_HOLDS_ARRAY (arr));
+  fail_unless (gst_value_array_get_size (arr) == 3);
+
+  val = gst_value_array_get_value (arr, 0);
+  fail_unless_equals_string (g_value_get_string (val), "");
+
+  val = gst_value_array_get_value (arr, 1);
+  fail_unless_equals_string (g_value_get_string (val), URN);
+
+  val = gst_value_array_get_value (arr, 2);
+  if (vad) {
+    fail_unless_equals_string (g_value_get_string (val), "vad=on");
+  } else {
+    fail_unless_equals_string (g_value_get_string (val), "vad=off");
+  }
+
+  gst_caps_unref (caps);
+}
+
+GST_START_TEST (rtprfc6464_sdp)
+{
+  GstRTPHeaderExtension *ext;
+  GstCaps *caps;
+  gboolean vad = FALSE;
+
+  ext = gst_rtp_header_extension_create_from_uri (URN);
+  fail_unless (ext != NULL);
+  gst_rtp_header_extension_set_id (ext, 1);
+
+  /* vad default to on */
+  caps = create_caps (SDP_NO_VAD);
+  fail_unless (gst_rtp_header_extension_set_attributes_from_caps (ext, caps));
+  gst_caps_unref (caps);
+  g_object_get (ext, "vad", &vad, NULL);
+  fail_unless (vad);
+  check_caps (ext, TRUE);
+
+  /* vad is disabled */
+  caps = create_caps (SDP_VAD_OFF);
+  fail_unless (gst_rtp_header_extension_set_attributes_from_caps (ext, caps));
+  gst_caps_unref (caps);
+  g_object_get (ext, "vad", &vad, NULL);
+  fail_if (vad);
+
+  /* vad is enabled */
+  caps = create_caps (SDP_VAD_ON);
+  fail_unless (gst_rtp_header_extension_set_attributes_from_caps (ext, caps));
+  gst_caps_unref (caps);
+  g_object_get (ext, "vad", &vad, NULL);
+  fail_unless (vad);
+
+  /* invalid vad */
+  caps = create_caps (SDP_VAD_WRONG);
+  fail_if (gst_rtp_header_extension_set_attributes_from_caps (ext, caps));
+  gst_caps_unref (caps);
+
+  gst_object_unref (ext);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (rtprfc6464_one_byte)
+{
+  GstRTPHeaderExtension *ext;
+  GstRTPHeaderExtensionFlags flags;
+  GstBuffer *buffer;
+  guint8 *data;
+  gsize size, written;
+  GstAudioLevelMeta *meta;
+  guint8 level = 12;
+  gboolean voice = TRUE;
+
+  ext = gst_rtp_header_extension_create_from_uri (URN);
+  fail_unless (ext != NULL);
+  gst_rtp_header_extension_set_id (ext, 1);
+
+  flags = gst_rtp_header_extension_get_supported_flags (ext);
+  fail_unless (flags & GST_RTP_HEADER_EXTENSION_ONE_BYTE);
+
+  buffer = gst_buffer_new ();
+  meta = gst_buffer_add_audio_level_meta (buffer, level, voice);
+
+  size = gst_rtp_header_extension_get_max_size (ext, buffer);
+  fail_unless (size > 0);
+  data = g_malloc0 (size);
+  fail_unless (data != NULL);
+
+  /* Write extension */
+  written =
+      gst_rtp_header_extension_write (ext, buffer,
+      GST_RTP_HEADER_EXTENSION_ONE_BYTE, buffer, data, size);
+  fail_unless (written == 1);
+
+  /* Read it back */
+  fail_unless (gst_buffer_remove_meta (buffer, (GstMeta *) meta));
+  fail_unless (gst_rtp_header_extension_read (ext,
+          GST_RTP_HEADER_EXTENSION_ONE_BYTE, data, size, buffer));
+  meta = gst_buffer_get_audio_level_meta (buffer);
+  fail_unless (meta != NULL);
+  fail_unless_equals_int (meta->level, level);
+  fail_unless (meta->voice_activity == voice);
+
+  g_free (data);
+  gst_buffer_unref (buffer);
+  gst_object_unref (ext);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (rtprfc6464_two_bytes)
+{
+  GstRTPHeaderExtension *ext;
+  GstRTPHeaderExtensionFlags flags;
+  GstBuffer *buffer;
+  guint8 *data;
+  gsize size, written;
+  GstAudioLevelMeta *meta;
+  guint8 level = 12;
+  gboolean voice = TRUE;
+
+  ext = gst_rtp_header_extension_create_from_uri (URN);
+  fail_unless (ext != NULL);
+  gst_rtp_header_extension_set_id (ext, 1);
+
+  flags = gst_rtp_header_extension_get_supported_flags (ext);
+  fail_unless (flags & GST_RTP_HEADER_EXTENSION_TWO_BYTE);
+
+  buffer = gst_buffer_new ();
+  meta = gst_buffer_add_audio_level_meta (buffer, level, voice);
+
+  size = gst_rtp_header_extension_get_max_size (ext, buffer);
+  fail_unless (size > 0);
+  data = g_malloc0 (size);
+  fail_unless (data != NULL);
+
+  /* Write extension */
+  written =
+      gst_rtp_header_extension_write (ext, buffer,
+      GST_RTP_HEADER_EXTENSION_TWO_BYTE, buffer, data, size);
+  fail_unless (written == 2);
+
+  /* Read it back */
+  fail_unless (gst_buffer_remove_meta (buffer, (GstMeta *) meta));
+  fail_unless (gst_rtp_header_extension_read (ext,
+          GST_RTP_HEADER_EXTENSION_TWO_BYTE, data, size, buffer));
+  meta = gst_buffer_get_audio_level_meta (buffer);
+  fail_unless (meta != NULL);
+  fail_unless_equals_int (meta->level, level);
+  fail_unless (meta->voice_activity == voice);
+
+  g_free (data);
+  gst_buffer_unref (buffer);
+  gst_object_unref (ext);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (rtprfc6464_no_meta)
+{
+  GstRTPHeaderExtension *ext;
+  GstBuffer *buffer;
+  guint8 *data;
+  gsize size, written;
+
+  ext = gst_rtp_header_extension_create_from_uri (URN);
+  fail_unless (ext != NULL);
+  gst_rtp_header_extension_set_id (ext, 1);
+
+  buffer = gst_buffer_new ();
+
+  size = gst_rtp_header_extension_get_max_size (ext, buffer);
+  fail_unless (size > 0);
+  data = g_malloc0 (size);
+  fail_unless (data != NULL);
+
+  written =
+      gst_rtp_header_extension_write (ext, buffer,
+      GST_RTP_HEADER_EXTENSION_ONE_BYTE, buffer, data, size);
+  fail_unless (written == 0);
+
+  written =
+      gst_rtp_header_extension_write (ext, buffer,
+      GST_RTP_HEADER_EXTENSION_TWO_BYTE, buffer, data, size);
+  fail_unless (written == 0);
+
+  g_free (data);
+  gst_buffer_unref (buffer);
+  gst_object_unref (ext);
+}
+
+GST_END_TEST;
+
+static Suite *
+rtprfc6464_suite (void)
+{
+  Suite *s = suite_create ("rtprfc6464");
+  TCase *tc_chain = tcase_create ("general");
+
+  suite_add_tcase (s, tc_chain);
+
+  tcase_add_test (tc_chain, rtprfc6464_sdp);
+  tcase_add_test (tc_chain, rtprfc6464_one_byte);
+  tcase_add_test (tc_chain, rtprfc6464_two_bytes);
+  tcase_add_test (tc_chain, rtprfc6464_no_meta);
+
+  return s;
+}
+
+GST_CHECK_MAIN (rtprfc6464)
index c4b7245..0160f09 100644 (file)
@@ -72,6 +72,7 @@ good_tests = [
   [ 'elements/rtpbin_buffer_list' ],
   [ 'elements/rtpcollision' ],
   [ 'elements/rtpfunnel' ],
+  [ 'elements/rtphdrextrfc6464', false, [gstsdp_dep, gstaudio_dep] ],
   [ 'elements/rtpjitterbuffer' ],
   [ 'elements/rtpjpeg' ],