1 /* GStreamer
2 * Copyright (C) <2018> Havard Graff <havard.graff@gmail.com>
3 * Copyright (C) <2020-2021> Guillaume Desmottes <guillaume.desmottes@collabora.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more
14 */
15
16 /**
17 * SECTION:element-rtphdrextclientaudiolevel
18 * @title: rtphdrextclientaudiolevel
19 * @short_description: Client-to-Mixer Audio Level Indication (RFC6464) RTP Header Extension
20 *
21 * Client-to-Mixer Audio Level Indication (RFC6464) RTP Header Extension.
22 * The extension should be automatically created by payloader and depayloaders,
23 * if their `auto-header-extension` property is enabled, if the extension
24 * is part of the RTP caps.
25 *
26 * ## Example pipeline
27 * |[
28 * gst-launch-1.0 pulsesrc ! level audio-level-meta=true ! audiconvert !
29 * rtpL16pay ! application/x-rtp,
30 * extmap-1=(string)\< \"\", urn:ietf:params:rtp-hdrext:ssrc-audio-level,
31 * \"vad=on\" \> ! udpsink
32 * ]|
33 *
34 * Since: 1.20
35 *
36 */
37 #ifdef HAVE_CONFIG_H
38 #include "config.h"
39 #endif
40
41 #include "gstrtphdrext-clientaudiolevel.h"
42
43 #include <gst/audio/audio.h>
44
45 #define CLIENT_AUDIO_LEVEL_HDR_EXT_URI GST_RTP_HDREXT_BASE"ssrc-audio-level"
46
47 GST_DEBUG_CATEGORY_STATIC (rtphdrclient_audio_level_debug);
48 #define GST_CAT_DEFAULT (rtphdrclient_audio_level_debug)
49
50 #define DEFAULT_VAD TRUE
51
52 enum
53 {
54 PROP_0,
55 PROP_VAD,
56 };
57
58 struct _GstRTPHeaderExtensionClientAudioLevel
59 {
60 GstRTPHeaderExtension parent;
61
62 gboolean vad;
63 };
64
65 G_DEFINE_TYPE_WITH_CODE (GstRTPHeaderExtensionClientAudioLevel,
66 gst_rtp_header_extension_client_audio_level, GST_TYPE_RTP_HEADER_EXTENSION,
67 GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "rtphdrextclientaudiolevel", 0,
68 "RTP RFC 6464 Header Extensions"););
69 GST_ELEMENT_REGISTER_DEFINE (rtphdrextclientaudiolevel,
70 "rtphdrextclientaudiolevel", GST_RANK_MARGINAL,
71 GST_TYPE_RTP_HEADER_EXTENSION_CLIENT_AUDIO_LEVEL);
72
73 static void
gst_rtp_header_extension_client_audio_level_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)74 gst_rtp_header_extension_client_audio_level_get_property (GObject * object,
75 guint prop_id, GValue * value, GParamSpec * pspec)
76 {
77 GstRTPHeaderExtensionClientAudioLevel *self =
78 GST_RTP_HEADER_EXTENSION_CLIENT_AUDIO_LEVEL (object);
79
80 switch (prop_id) {
81 case PROP_VAD:
82 g_value_set_boolean (value, self->vad);
83 break;
84 default:
85 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
86 break;
87 }
88 }
89
90 static GstRTPHeaderExtensionFlags
gst_rtp_header_extension_client_audio_level_get_supported_flags(GstRTPHeaderExtension * ext)91 gst_rtp_header_extension_client_audio_level_get_supported_flags
92 (GstRTPHeaderExtension * ext)
93 {
94 return GST_RTP_HEADER_EXTENSION_ONE_BYTE | GST_RTP_HEADER_EXTENSION_TWO_BYTE;
95 }
96
97 static gsize
gst_rtp_header_extension_client_audio_level_get_max_size(GstRTPHeaderExtension * ext,const GstBuffer * input_meta)98 gst_rtp_header_extension_client_audio_level_get_max_size (GstRTPHeaderExtension
99 * ext, const GstBuffer * input_meta)
100 {
101 return 2;
102 }
103
104 static void
set_vad(GstRTPHeaderExtension * ext,gboolean vad)105 set_vad (GstRTPHeaderExtension * ext, gboolean vad)
106 {
107 GstRTPHeaderExtensionClientAudioLevel *self =
108 GST_RTP_HEADER_EXTENSION_CLIENT_AUDIO_LEVEL (ext);
109
110 if (self->vad == vad)
111 return;
112
113 GST_DEBUG_OBJECT (ext, "vad: %d", vad);
114 self->vad = vad;
115 g_object_notify (G_OBJECT (self), "vad");
116 }
117
118 static gboolean
gst_rtp_header_extension_client_audio_level_set_attributes(GstRTPHeaderExtension * ext,GstRTPHeaderExtensionDirection direction,const gchar * attributes)119 gst_rtp_header_extension_client_audio_level_set_attributes
120 (GstRTPHeaderExtension * ext, GstRTPHeaderExtensionDirection direction,
121 const gchar * attributes)
122 {
123 if (g_str_equal (attributes, "vad=on") || g_str_equal (attributes, "")) {
124 set_vad (ext, TRUE);
125 } else if (g_str_equal (attributes, "vad=off")) {
126 set_vad (ext, FALSE);
127 } else {
128 GST_WARNING_OBJECT (ext, "Invalid attribute: %s", attributes);
129 return FALSE;
130 }
131
132 return TRUE;
133 }
134
135 static gboolean
gst_rtp_header_extension_client_audio_level_set_caps_from_attributes(GstRTPHeaderExtension * ext,GstCaps * caps)136 gst_rtp_header_extension_client_audio_level_set_caps_from_attributes
137 (GstRTPHeaderExtension * ext, GstCaps * caps)
138 {
139 GstRTPHeaderExtensionClientAudioLevel *self =
140 GST_RTP_HEADER_EXTENSION_CLIENT_AUDIO_LEVEL (ext);
141 const gchar *vad;
142
143 if (self->vad)
144 vad = "vad=on";
145 else
146 vad = "vad=off";
147
148 return gst_rtp_header_extension_set_caps_from_attributes_helper (ext, caps,
149 vad);
150 }
151
152 static gssize
gst_rtp_header_extension_client_audio_level_write(GstRTPHeaderExtension * ext,const GstBuffer * input_meta,GstRTPHeaderExtensionFlags write_flags,GstBuffer * output,guint8 * data,gsize size)153 gst_rtp_header_extension_client_audio_level_write (GstRTPHeaderExtension * ext,
154 const GstBuffer * input_meta, GstRTPHeaderExtensionFlags write_flags,
155 GstBuffer * output, guint8 * data, gsize size)
156 {
157 GstAudioLevelMeta *meta;
158 guint level;
159
160 g_return_val_if_fail (size >=
161 gst_rtp_header_extension_client_audio_level_get_max_size (ext, NULL), -1);
162 g_return_val_if_fail (write_flags &
163 gst_rtp_header_extension_client_audio_level_get_supported_flags (ext),
164 -1);
165
166 meta = gst_buffer_get_audio_level_meta ((GstBuffer *) input_meta);
167 if (!meta) {
168 GST_LOG_OBJECT (ext, "no meta");
169 return 0;
170 }
171
172 level = meta->level;
173 if (level > 127) {
174 GST_LOG_OBJECT (ext, "level from meta is higher than 127: %d, cropping",
175 meta->level);
176 level = 127;
177 }
178
179 GST_LOG_OBJECT (ext, "writing ext (level: %d voice: %d)", meta->level,
180 meta->voice_activity);
181
182 /* Both one & two byte use the same format, the second byte being padding */
183 data[0] = (meta->level & 0x7F) | (meta->voice_activity << 7);
184 if (write_flags & GST_RTP_HEADER_EXTENSION_ONE_BYTE) {
185 return 1;
186 }
187 data[1] = 0;
188 return 2;
189 }
190
191 static gboolean
gst_rtp_header_extension_client_audio_level_read(GstRTPHeaderExtension * ext,GstRTPHeaderExtensionFlags read_flags,const guint8 * data,gsize size,GstBuffer * buffer)192 gst_rtp_header_extension_client_audio_level_read (GstRTPHeaderExtension * ext,
193 GstRTPHeaderExtensionFlags read_flags, const guint8 * data, gsize size,
194 GstBuffer * buffer)
195 {
196 guint8 level;
197 gboolean voice_activity;
198
199 g_return_val_if_fail (read_flags &
200 gst_rtp_header_extension_client_audio_level_get_supported_flags (ext),
201 -1);
202
203 /* Both one & two byte use the same format, the second byte being padding */
204 level = data[0] & 0x7F;
205 voice_activity = (data[0] & 0x80) >> 7;
206
207 GST_LOG_OBJECT (ext, "reading ext (level: %d voice: %d)", level,
208 voice_activity);
209
210 gst_buffer_add_audio_level_meta (buffer, level, voice_activity);
211
212 return TRUE;
213 }
214
215 static void
gst_rtp_header_extension_client_audio_level_class_init(GstRTPHeaderExtensionClientAudioLevelClass * klass)216 gst_rtp_header_extension_client_audio_level_class_init
217 (GstRTPHeaderExtensionClientAudioLevelClass * klass)
218 {
219 GstRTPHeaderExtensionClass *rtp_hdr_class;
220 GstElementClass *gstelement_class;
221 GObjectClass *gobject_class;
222
223 rtp_hdr_class = GST_RTP_HEADER_EXTENSION_CLASS (klass);
224 gobject_class = (GObjectClass *) klass;
225 gstelement_class = GST_ELEMENT_CLASS (klass);
226
227 gobject_class->get_property =
228 gst_rtp_header_extension_client_audio_level_get_property;
229
230 /**
231 * rtphdrextclientaudiolevel:vad:
232 *
233 * If the vad extension attribute is enabled or not, default to %FALSE.
234 *
235 * Since: 1.20
236 */
237 g_object_class_install_property (gobject_class, PROP_VAD,
238 g_param_spec_boolean ("vad", "vad",
239 "If the vad extension attribute is enabled or not",
240 DEFAULT_VAD, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
241
242 rtp_hdr_class->get_supported_flags =
243 gst_rtp_header_extension_client_audio_level_get_supported_flags;
244 rtp_hdr_class->get_max_size =
245 gst_rtp_header_extension_client_audio_level_get_max_size;
246 rtp_hdr_class->set_attributes =
247 gst_rtp_header_extension_client_audio_level_set_attributes;
248 rtp_hdr_class->set_caps_from_attributes =
249 gst_rtp_header_extension_client_audio_level_set_caps_from_attributes;
250 rtp_hdr_class->write = gst_rtp_header_extension_client_audio_level_write;
251 rtp_hdr_class->read = gst_rtp_header_extension_client_audio_level_read;
252
253 gst_element_class_set_static_metadata (gstelement_class,
254 "Client-to-Mixer Audio Level Indication (RFC6464) RTP Header Extension",
255 GST_RTP_HDREXT_ELEMENT_CLASS,
256 "Client-to-Mixer Audio Level Indication (RFC6464) RTP Header Extension",
257 "Guillaume Desmottes <guillaume.desmottes@collabora.com>");
258 gst_rtp_header_extension_class_set_uri (rtp_hdr_class,
259 CLIENT_AUDIO_LEVEL_HDR_EXT_URI);
260 }
261
262 static void
gst_rtp_header_extension_client_audio_level_init(GstRTPHeaderExtensionClientAudioLevel * self)263 gst_rtp_header_extension_client_audio_level_init
264 (GstRTPHeaderExtensionClientAudioLevel * self)
265 {
266 GST_DEBUG_OBJECT (self, "creating element");
267 self->vad = DEFAULT_VAD;
268 }
269