1 /*
2 * Opus Depayloader Gst Element
3 *
4 * @author: Danilo Cesar Lemes de Paula <danilo.cesar@collabora.co.uk>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25
26 #include <string.h>
27 #include <stdlib.h>
28 #include <gst/rtp/gstrtpbuffer.h>
29 #include <gst/audio/audio.h>
30 #include "gstrtpelements.h"
31 #include "gstrtpopusdepay.h"
32 #include "gstrtputils.h"
33
34 GST_DEBUG_CATEGORY_STATIC (rtpopusdepay_debug);
35 #define GST_CAT_DEFAULT (rtpopusdepay_debug)
36
37 static GstStaticPadTemplate gst_rtp_opus_depay_sink_template =
38 GST_STATIC_PAD_TEMPLATE ("sink",
39 GST_PAD_SINK,
40 GST_PAD_ALWAYS,
41 GST_STATIC_CAPS ("application/x-rtp, "
42 "media = (string) \"audio\", "
43 "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ","
44 "clock-rate = (int) 48000, "
45 "encoding-name = (string) { \"OPUS\", \"X-GST-OPUS-DRAFT-SPITTKA-00\", \"multiopus\" }")
46 );
47
48 static GstStaticPadTemplate gst_rtp_opus_depay_src_template =
49 GST_STATIC_PAD_TEMPLATE ("src",
50 GST_PAD_SRC,
51 GST_PAD_ALWAYS,
52 GST_STATIC_CAPS ("audio/x-opus, channel-mapping-family = (int) [ 0, 1 ]")
53 );
54
55 static GstBuffer *gst_rtp_opus_depay_process (GstRTPBaseDepayload * depayload,
56 GstRTPBuffer * rtp_buffer);
57 static gboolean gst_rtp_opus_depay_setcaps (GstRTPBaseDepayload * depayload,
58 GstCaps * caps);
59
60 G_DEFINE_TYPE (GstRTPOpusDepay, gst_rtp_opus_depay,
61 GST_TYPE_RTP_BASE_DEPAYLOAD);
62 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (rtpopusdepay, "rtpopusdepay",
63 GST_RANK_PRIMARY, GST_TYPE_RTP_OPUS_DEPAY, rtp_element_init (plugin));
64
65 static void
gst_rtp_opus_depay_class_init(GstRTPOpusDepayClass * klass)66 gst_rtp_opus_depay_class_init (GstRTPOpusDepayClass * klass)
67 {
68 GstRTPBaseDepayloadClass *gstbasertpdepayload_class;
69 GstElementClass *element_class;
70
71 element_class = GST_ELEMENT_CLASS (klass);
72 gstbasertpdepayload_class = (GstRTPBaseDepayloadClass *) klass;
73
74 gst_element_class_add_static_pad_template (element_class,
75 &gst_rtp_opus_depay_src_template);
76 gst_element_class_add_static_pad_template (element_class,
77 &gst_rtp_opus_depay_sink_template);
78 gst_element_class_set_static_metadata (element_class,
79 "RTP Opus packet depayloader", "Codec/Depayloader/Network/RTP",
80 "Extracts Opus audio from RTP packets",
81 "Danilo Cesar Lemes de Paula <danilo.cesar@collabora.co.uk>");
82
83 gstbasertpdepayload_class->process_rtp_packet = gst_rtp_opus_depay_process;
84 gstbasertpdepayload_class->set_caps = gst_rtp_opus_depay_setcaps;
85
86 GST_DEBUG_CATEGORY_INIT (rtpopusdepay_debug, "rtpopusdepay", 0,
87 "Opus RTP Depayloader");
88 }
89
90 static void
gst_rtp_opus_depay_init(GstRTPOpusDepay * rtpopusdepay)91 gst_rtp_opus_depay_init (GstRTPOpusDepay * rtpopusdepay)
92 {
93
94 }
95
96 static gboolean
gst_rtp_opus_depay_setcaps(GstRTPBaseDepayload * depayload,GstCaps * caps)97 gst_rtp_opus_depay_setcaps (GstRTPBaseDepayload * depayload, GstCaps * caps)
98 {
99 GstCaps *srccaps;
100 GstStructure *s;
101 gboolean ret;
102 const gchar *sprop_maxcapturerate;
103
104 srccaps = gst_caps_new_empty_simple ("audio/x-opus");
105
106 s = gst_caps_get_structure (caps, 0);
107
108 if (g_str_equal (gst_structure_get_string (s, "encoding-name"), "multiopus")) {
109 gint channels;
110 gint stream_count;
111 gint coupled_count;
112 const gchar *encoding_params;
113 const gchar *num_streams;
114 const gchar *coupled_streams;
115 const gchar *channel_mapping;
116 gchar *endptr;
117
118 if (!gst_structure_has_field_typed (s, "encoding-params", G_TYPE_STRING) ||
119 !gst_structure_has_field_typed (s, "num_streams", G_TYPE_STRING) ||
120 !gst_structure_has_field_typed (s, "coupled_streams", G_TYPE_STRING) ||
121 !gst_structure_has_field_typed (s, "channel_mapping", G_TYPE_STRING)) {
122 GST_WARNING_OBJECT (depayload, "Encoding name 'multiopus' requires "
123 "encoding-params, num_streams, coupled_streams and channel_mapping "
124 "as string fields in caps.");
125 goto reject_caps;
126 }
127
128 gst_caps_set_simple (srccaps, "channel-mapping-family", G_TYPE_INT, 1,
129 NULL);
130
131 encoding_params = gst_structure_get_string (s, "encoding-params");
132 channels = g_ascii_strtoull (encoding_params, &endptr, 10);
133 if (*endptr != '\0' || channels > 255) {
134 GST_WARNING_OBJECT (depayload, "Invalid encoding-params value '%s'",
135 encoding_params);
136 goto reject_caps;
137 }
138 gst_caps_set_simple (srccaps, "channels", G_TYPE_INT, channels, NULL);
139
140 num_streams = gst_structure_get_string (s, "num_streams");
141 stream_count = g_ascii_strtoull (num_streams, &endptr, 10);
142 if (*endptr != '\0' || stream_count > channels) {
143 GST_WARNING_OBJECT (depayload, "Invalid num_streams value '%s'",
144 num_streams);
145 goto reject_caps;
146 }
147 gst_caps_set_simple (srccaps, "stream-count", G_TYPE_INT, stream_count,
148 NULL);
149
150 coupled_streams = gst_structure_get_string (s, "coupled_streams");
151 coupled_count = g_ascii_strtoull (coupled_streams, &endptr, 10);
152 if (*endptr != '\0' || coupled_count > stream_count) {
153 GST_WARNING_OBJECT (depayload, "Invalid coupled_streams value '%s'",
154 coupled_streams);
155 goto reject_caps;
156 }
157 gst_caps_set_simple (srccaps, "coupled-count", G_TYPE_INT, coupled_count,
158 NULL);
159
160 channel_mapping = gst_structure_get_string (s, "channel_mapping");
161 {
162 gchar **split;
163 gchar **ptr;
164 GValue mapping = G_VALUE_INIT;
165 GValue v = G_VALUE_INIT;
166
167 split = g_strsplit (channel_mapping, ",", -1);
168
169 g_value_init (&mapping, GST_TYPE_ARRAY);
170 g_value_init (&v, G_TYPE_INT);
171
172 for (ptr = split; *ptr; ++ptr) {
173 gint channel = g_ascii_strtoull (*ptr, &endptr, 10);
174 if (*endptr != '\0' || channel > channels) {
175 GST_WARNING_OBJECT (depayload, "Invalid channel_mapping value '%s'",
176 channel_mapping);
177 g_value_unset (&mapping);
178 break;
179 }
180 g_value_set_int (&v, channel);
181 gst_value_array_append_value (&mapping, &v);
182 }
183
184 g_value_unset (&v);
185 g_strfreev (split);
186
187 if (G_IS_VALUE (&mapping)) {
188 gst_caps_set_value (srccaps, "channel-mapping", &mapping);
189 g_value_unset (&mapping);
190 } else {
191 goto reject_caps;
192 }
193 }
194 } else {
195 const gchar *sprop_stereo;
196
197 gst_caps_set_simple (srccaps, "channel-mapping-family", G_TYPE_INT, 0,
198 NULL);
199
200 if ((sprop_stereo = gst_structure_get_string (s, "sprop-stereo"))) {
201 if (strcmp (sprop_stereo, "0") == 0)
202 gst_caps_set_simple (srccaps, "channels", G_TYPE_INT, 1, NULL);
203 else if (strcmp (sprop_stereo, "1") == 0)
204 gst_caps_set_simple (srccaps, "channels", G_TYPE_INT, 2, NULL);
205 else
206 GST_WARNING_OBJECT (depayload, "Unknown sprop-stereo value '%s'",
207 sprop_stereo);
208 } else {
209 /* Although sprop-stereo defaults to mono as per RFC 7587, this just means
210 that the signal is likely mono and can be safely downmixed, it may
211 still be stereo at times. */
212 gst_caps_set_simple (srccaps, "channels", G_TYPE_INT, 2, NULL);
213 }
214 }
215
216 if ((sprop_maxcapturerate =
217 gst_structure_get_string (s, "sprop-maxcapturerate"))) {
218 gulong rate;
219 gchar *tailptr;
220
221 rate = strtoul (sprop_maxcapturerate, &tailptr, 10);
222 if (rate > INT_MAX || *tailptr != '\0') {
223 GST_WARNING_OBJECT (depayload,
224 "Failed to parse sprop-maxcapturerate value '%s'",
225 sprop_maxcapturerate);
226 } else {
227 gst_caps_set_simple (srccaps, "rate", G_TYPE_INT, rate, NULL);
228 }
229 }
230
231 ret = gst_pad_set_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (depayload), srccaps);
232
233 GST_DEBUG_OBJECT (depayload,
234 "set caps on source: %" GST_PTR_FORMAT " (ret=%d)", srccaps, ret);
235 gst_caps_unref (srccaps);
236
237 depayload->clock_rate = 48000;
238
239 return ret;
240
241 reject_caps:
242 gst_caps_unref (srccaps);
243
244 return FALSE;
245 }
246
247 static GstBuffer *
gst_rtp_opus_depay_process(GstRTPBaseDepayload * depayload,GstRTPBuffer * rtp_buffer)248 gst_rtp_opus_depay_process (GstRTPBaseDepayload * depayload,
249 GstRTPBuffer * rtp_buffer)
250 {
251 GstBuffer *outbuf;
252
253 outbuf = gst_rtp_buffer_get_payload_buffer (rtp_buffer);
254
255 gst_rtp_drop_non_audio_meta (depayload, outbuf);
256
257 return outbuf;
258 }
259