bee7bb5b4c540a826ddca9703d3dbcba4e13156a
[platform/upstream/gstreamer.git] / gst / rtp / gstrtpopuspay.c
1 /*
2  * Opus Payloader 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 /**
23  * SECTION:element-rtpopuspay
24  * @title: rtpopuspay
25  *
26  * rtpopuspay encapsulates Opus-encoded audio data into RTP packets following
27  * the payload format described in RFC 7587.
28  *
29  * In addition to the RFC, which assumes only mono and stereo payload,
30  * the element supports multichannel Opus audio streams using a non-standardized
31  * SDP config and "multiopus" codec developed by Google for libwebrtc. When the
32  * input data have more than 2 channels, rtpopuspay will add extra fields to
33  * output caps that can be used to generate SDP in the syntax understood by
34  * libwebrtc. For example in the case of 5.1 audio:
35  *
36  * |[
37  *  a=rtpmap:96 multiopus/48000/6
38  *  a=fmtp:96 num_streams=4;coupled_streams=2;channel_mapping=0,4,1,2,3,5
39  * ]|
40  *
41  * See https://webrtc-review.googlesource.com/c/src/+/129768 for more details on
42  * multichannel Opus in libwebrtc.
43  */
44
45 #ifdef HAVE_CONFIG_H
46 #  include "config.h"
47 #endif
48
49 #include <string.h>
50
51 #include <gst/rtp/gstrtpbuffer.h>
52 #include <gst/audio/audio.h>
53
54 #include "gstrtpelements.h"
55 #include "gstrtpopuspay.h"
56 #include "gstrtputils.h"
57
58 GST_DEBUG_CATEGORY_STATIC (rtpopuspay_debug);
59 #define GST_CAT_DEFAULT (rtpopuspay_debug)
60
61
62 static GstStaticPadTemplate gst_rtp_opus_pay_sink_template =
63     GST_STATIC_PAD_TEMPLATE ("sink",
64     GST_PAD_SINK,
65     GST_PAD_ALWAYS,
66     GST_STATIC_CAPS ("audio/x-opus, channel-mapping-family = (int) 0;"
67         "audio/x-opus, channel-mapping-family = (int) 0, channels = (int) [1, 2];"
68         "audio/x-opus, channel-mapping-family = (int) 1, channels = (int) [3, 255]")
69     );
70
71 static GstStaticPadTemplate gst_rtp_opus_pay_src_template =
72 GST_STATIC_PAD_TEMPLATE ("src",
73     GST_PAD_SRC,
74     GST_PAD_ALWAYS,
75     GST_STATIC_CAPS ("application/x-rtp, "
76         "media = (string) \"audio\", "
77         "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
78         "clock-rate = (int) 48000, "
79         "encoding-name = (string) { \"OPUS\", \"X-GST-OPUS-DRAFT-SPITTKA-00\", \"multiopus\" }")
80     );
81
82 static gboolean gst_rtp_opus_pay_setcaps (GstRTPBasePayload * payload,
83     GstCaps * caps);
84 static GstCaps *gst_rtp_opus_pay_getcaps (GstRTPBasePayload * payload,
85     GstPad * pad, GstCaps * filter);
86 static GstFlowReturn gst_rtp_opus_pay_handle_buffer (GstRTPBasePayload *
87     payload, GstBuffer * buffer);
88
89 G_DEFINE_TYPE (GstRtpOPUSPay, gst_rtp_opus_pay, GST_TYPE_RTP_BASE_PAYLOAD);
90 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (rtpopuspay, "rtpopuspay",
91     GST_RANK_PRIMARY, GST_TYPE_RTP_OPUS_PAY, rtp_element_init (plugin));
92
93 static void
94 gst_rtp_opus_pay_class_init (GstRtpOPUSPayClass * klass)
95 {
96   GstRTPBasePayloadClass *gstbasertppayload_class;
97   GstElementClass *element_class;
98
99   gstbasertppayload_class = (GstRTPBasePayloadClass *) klass;
100   element_class = GST_ELEMENT_CLASS (klass);
101
102   gstbasertppayload_class->set_caps = gst_rtp_opus_pay_setcaps;
103   gstbasertppayload_class->get_caps = gst_rtp_opus_pay_getcaps;
104   gstbasertppayload_class->handle_buffer = gst_rtp_opus_pay_handle_buffer;
105
106   gst_element_class_add_static_pad_template (element_class,
107       &gst_rtp_opus_pay_src_template);
108   gst_element_class_add_static_pad_template (element_class,
109       &gst_rtp_opus_pay_sink_template);
110
111   gst_element_class_set_static_metadata (element_class,
112       "RTP Opus payloader",
113       "Codec/Payloader/Network/RTP",
114       "Puts Opus audio in RTP packets",
115       "Danilo Cesar Lemes de Paula <danilo.cesar@collabora.co.uk>");
116
117   GST_DEBUG_CATEGORY_INIT (rtpopuspay_debug, "rtpopuspay", 0,
118       "Opus RTP Payloader");
119 }
120
121 static void
122 gst_rtp_opus_pay_init (GstRtpOPUSPay * rtpopuspay)
123 {
124 }
125
126 static gboolean
127 gst_rtp_opus_pay_setcaps (GstRTPBasePayload * payload, GstCaps * caps)
128 {
129   gboolean res;
130   GstCaps *src_caps;
131   GstStructure *s, *outcaps;
132   const char *encoding_name = "OPUS";
133   gint channels = 2;
134   gint rate;
135   gchar *encoding_params;
136
137   outcaps = gst_structure_new_empty ("unused");
138
139   src_caps = gst_pad_get_allowed_caps (GST_RTP_BASE_PAYLOAD_SRCPAD (payload));
140   if (src_caps) {
141     GstStructure *s;
142     const GValue *value;
143
144     s = gst_caps_get_structure (src_caps, 0);
145
146     if (gst_structure_has_field (s, "encoding-name")) {
147       GValue default_value = G_VALUE_INIT;
148
149       g_value_init (&default_value, G_TYPE_STRING);
150       g_value_set_static_string (&default_value, encoding_name);
151
152       value = gst_structure_get_value (s, "encoding-name");
153       if (!gst_value_can_intersect (&default_value, value))
154         encoding_name = "X-GST-OPUS-DRAFT-SPITTKA-00";
155     }
156     gst_caps_unref (src_caps);
157   }
158
159   s = gst_caps_get_structure (caps, 0);
160   if (gst_structure_get_int (s, "channels", &channels)) {
161     if (channels > 2) {
162       /* Implies channel-mapping-family = 1. */
163
164       gint stream_count, coupled_count;
165       const GValue *channel_mapping_array;
166
167       /* libwebrtc only supports "multiopus" when channels > 2. Mono and stereo
168        * sound must always be payloaded according to RFC 7587. */
169       encoding_name = "multiopus";
170
171       if (gst_structure_get_int (s, "stream-count", &stream_count)) {
172         char *num_streams = g_strdup_printf ("%d", stream_count);
173         gst_structure_set (outcaps, "num_streams", G_TYPE_STRING, num_streams,
174             NULL);
175         g_free (num_streams);
176       }
177       if (gst_structure_get_int (s, "coupled-count", &coupled_count)) {
178         char *coupled_streams = g_strdup_printf ("%d", coupled_count);
179         gst_structure_set (outcaps, "coupled_streams", G_TYPE_STRING,
180             coupled_streams, NULL);
181         g_free (coupled_streams);
182       }
183
184       channel_mapping_array = gst_structure_get_value (s, "channel-mapping");
185       if (GST_VALUE_HOLDS_ARRAY (channel_mapping_array)) {
186         GString *str = g_string_new (NULL);
187         guint i;
188
189         for (i = 0; i < gst_value_array_get_size (channel_mapping_array); ++i) {
190           if (i != 0) {
191             g_string_append_c (str, ',');
192           }
193           g_string_append_printf (str, "%d",
194               g_value_get_int (gst_value_array_get_value (channel_mapping_array,
195                       i)));
196         }
197
198         gst_structure_set (outcaps, "channel_mapping", G_TYPE_STRING, str->str,
199             NULL);
200
201         g_string_free (str, TRUE);
202       }
203     } else {
204       gst_structure_set (outcaps, "sprop-stereo", G_TYPE_STRING,
205           (channels == 2) ? "1" : "0", NULL);
206       /* RFC 7587 requires the number of channels always be 2. */
207       channels = 2;
208     }
209   }
210
211   encoding_params = g_strdup_printf ("%d", channels);
212   gst_structure_set (outcaps, "encoding-params", G_TYPE_STRING,
213       encoding_params, NULL);
214   g_free (encoding_params);
215
216   if (gst_structure_get_int (s, "rate", &rate)) {
217     gchar *sprop_maxcapturerate = g_strdup_printf ("%d", rate);
218
219     gst_structure_set (outcaps, "sprop-maxcapturerate", G_TYPE_STRING,
220         sprop_maxcapturerate, NULL);
221
222     g_free (sprop_maxcapturerate);
223   }
224
225   gst_rtp_base_payload_set_options (payload, "audio", FALSE,
226       encoding_name, 48000);
227
228   res = gst_rtp_base_payload_set_outcaps_structure (payload, outcaps);
229
230   gst_structure_free (outcaps);
231
232   return res;
233 }
234
235 static GstFlowReturn
236 gst_rtp_opus_pay_handle_buffer (GstRTPBasePayload * basepayload,
237     GstBuffer * buffer)
238 {
239   GstBuffer *outbuf;
240   GstClockTime pts, dts, duration;
241
242   pts = GST_BUFFER_PTS (buffer);
243   dts = GST_BUFFER_DTS (buffer);
244   duration = GST_BUFFER_DURATION (buffer);
245
246   outbuf = gst_rtp_base_payload_allocate_output_buffer (basepayload, 0, 0, 0);
247
248   gst_rtp_copy_audio_meta (basepayload, outbuf, buffer);
249
250   outbuf = gst_buffer_append (outbuf, buffer);
251
252   GST_BUFFER_PTS (outbuf) = pts;
253   GST_BUFFER_DTS (outbuf) = dts;
254   GST_BUFFER_DURATION (outbuf) = duration;
255
256   /* Push out */
257   return gst_rtp_base_payload_push (basepayload, outbuf);
258 }
259
260 static GstCaps *
261 gst_rtp_opus_pay_getcaps (GstRTPBasePayload * payload,
262     GstPad * pad, GstCaps * filter)
263 {
264   GstCaps *caps, *peercaps, *tcaps;
265   GstStructure *s;
266   const gchar *stereo;
267
268   if (pad == GST_RTP_BASE_PAYLOAD_SRCPAD (payload))
269     return
270         GST_RTP_BASE_PAYLOAD_CLASS (gst_rtp_opus_pay_parent_class)->get_caps
271         (payload, pad, filter);
272
273   tcaps = gst_pad_get_pad_template_caps (GST_RTP_BASE_PAYLOAD_SRCPAD (payload));
274   peercaps = gst_pad_peer_query_caps (GST_RTP_BASE_PAYLOAD_SRCPAD (payload),
275       tcaps);
276   gst_caps_unref (tcaps);
277   if (!peercaps)
278     return
279         GST_RTP_BASE_PAYLOAD_CLASS (gst_rtp_opus_pay_parent_class)->get_caps
280         (payload, pad, filter);
281
282   if (gst_caps_is_empty (peercaps))
283     return peercaps;
284
285   caps = gst_pad_get_pad_template_caps (GST_RTP_BASE_PAYLOAD_SINKPAD (payload));
286
287   s = gst_caps_get_structure (peercaps, 0);
288   stereo = gst_structure_get_string (s, "stereo");
289   if (stereo != NULL) {
290     caps = gst_caps_make_writable (caps);
291
292     if (!strcmp (stereo, "1")) {
293       GstCaps *caps2 = gst_caps_copy (caps);
294
295       gst_caps_set_simple (caps, "channels", G_TYPE_INT, 2, NULL);
296       gst_caps_set_simple (caps2, "channels", G_TYPE_INT, 1, NULL);
297       caps = gst_caps_merge (caps, caps2);
298     } else if (!strcmp (stereo, "0")) {
299       GstCaps *caps2 = gst_caps_copy (caps);
300
301       gst_caps_set_simple (caps, "channels", G_TYPE_INT, 1, NULL);
302       gst_caps_set_simple (caps2, "channels", G_TYPE_INT, 2, NULL);
303       caps = gst_caps_merge (caps, caps2);
304     }
305   }
306   gst_caps_unref (peercaps);
307
308   if (filter) {
309     GstCaps *tmp = gst_caps_intersect_full (caps, filter,
310         GST_CAPS_INTERSECT_FIRST);
311     gst_caps_unref (caps);
312     caps = tmp;
313   }
314
315   GST_DEBUG_OBJECT (payload, "Returning caps: %" GST_PTR_FORMAT, caps);
316   return caps;
317 }