rtpopuspay: add DTX support
[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 enum
62 {
63   PROP_0,
64   PROP_DTX,
65 };
66
67 #define DEFAULT_DTX FALSE
68
69 static GstStaticPadTemplate gst_rtp_opus_pay_sink_template =
70     GST_STATIC_PAD_TEMPLATE ("sink",
71     GST_PAD_SINK,
72     GST_PAD_ALWAYS,
73     GST_STATIC_CAPS ("audio/x-opus, channel-mapping-family = (int) 0;"
74         "audio/x-opus, channel-mapping-family = (int) 0, channels = (int) [1, 2];"
75         "audio/x-opus, channel-mapping-family = (int) 1, channels = (int) [3, 255]")
76     );
77
78 static GstStaticPadTemplate gst_rtp_opus_pay_src_template =
79 GST_STATIC_PAD_TEMPLATE ("src",
80     GST_PAD_SRC,
81     GST_PAD_ALWAYS,
82     GST_STATIC_CAPS ("application/x-rtp, "
83         "media = (string) \"audio\", "
84         "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
85         "clock-rate = (int) 48000, "
86         "encoding-name = (string) { \"OPUS\", \"X-GST-OPUS-DRAFT-SPITTKA-00\", \"multiopus\" }")
87     );
88
89 static gboolean gst_rtp_opus_pay_setcaps (GstRTPBasePayload * payload,
90     GstCaps * caps);
91 static GstCaps *gst_rtp_opus_pay_getcaps (GstRTPBasePayload * payload,
92     GstPad * pad, GstCaps * filter);
93 static GstFlowReturn gst_rtp_opus_pay_handle_buffer (GstRTPBasePayload *
94     payload, GstBuffer * buffer);
95
96 G_DEFINE_TYPE (GstRtpOPUSPay, gst_rtp_opus_pay, GST_TYPE_RTP_BASE_PAYLOAD);
97 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (rtpopuspay, "rtpopuspay",
98     GST_RANK_PRIMARY, GST_TYPE_RTP_OPUS_PAY, rtp_element_init (plugin));
99
100 #define GST_RTP_OPUS_PAY_CAST(obj) ((GstRtpOPUSPay *)(obj))
101
102 static void
103 gst_rtp_opus_pay_set_property (GObject * object,
104     guint prop_id, const GValue * value, GParamSpec * pspec)
105 {
106   GstRtpOPUSPay *self = GST_RTP_OPUS_PAY (object);
107
108   switch (prop_id) {
109     case PROP_DTX:
110       self->dtx = g_value_get_boolean (value);
111       break;
112     default:
113       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
114       break;
115   }
116 }
117
118 static void
119 gst_rtp_opus_pay_get_property (GObject * object,
120     guint prop_id, GValue * value, GParamSpec * pspec)
121 {
122   GstRtpOPUSPay *self = GST_RTP_OPUS_PAY (object);
123
124   switch (prop_id) {
125     case PROP_DTX:
126       g_value_set_boolean (value, self->dtx);
127       break;
128     default:
129       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
130       break;
131   }
132 }
133
134 static void
135 gst_rtp_opus_pay_class_init (GstRtpOPUSPayClass * klass)
136 {
137   GstRTPBasePayloadClass *gstbasertppayload_class;
138   GstElementClass *element_class;
139   GObjectClass *gobject_class;
140
141   gstbasertppayload_class = (GstRTPBasePayloadClass *) klass;
142   element_class = GST_ELEMENT_CLASS (klass);
143   gobject_class = (GObjectClass *) klass;
144
145   gstbasertppayload_class->set_caps = gst_rtp_opus_pay_setcaps;
146   gstbasertppayload_class->get_caps = gst_rtp_opus_pay_getcaps;
147   gstbasertppayload_class->handle_buffer = gst_rtp_opus_pay_handle_buffer;
148
149   gobject_class->set_property = gst_rtp_opus_pay_set_property;
150   gobject_class->get_property = gst_rtp_opus_pay_get_property;
151
152   gst_element_class_add_static_pad_template (element_class,
153       &gst_rtp_opus_pay_src_template);
154   gst_element_class_add_static_pad_template (element_class,
155       &gst_rtp_opus_pay_sink_template);
156
157   /**
158    * GstRtpOPUSPay:dtx:
159    *
160    * If enabled, the payloader will not transmit empty packets.
161    *
162    * Since: 1.20
163    */
164   g_object_class_install_property (gobject_class, PROP_DTX,
165       g_param_spec_boolean ("dtx", "Discontinuous Transmission",
166           "If enabled, the payloader will not transmit empty packets",
167           DEFAULT_DTX,
168           G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING |
169           G_PARAM_STATIC_STRINGS));
170
171   gst_element_class_set_static_metadata (element_class,
172       "RTP Opus payloader",
173       "Codec/Payloader/Network/RTP",
174       "Puts Opus audio in RTP packets",
175       "Danilo Cesar Lemes de Paula <danilo.cesar@collabora.co.uk>");
176
177   GST_DEBUG_CATEGORY_INIT (rtpopuspay_debug, "rtpopuspay", 0,
178       "Opus RTP Payloader");
179 }
180
181 static void
182 gst_rtp_opus_pay_init (GstRtpOPUSPay * rtpopuspay)
183 {
184   rtpopuspay->dtx = DEFAULT_DTX;
185 }
186
187 static gboolean
188 gst_rtp_opus_pay_setcaps (GstRTPBasePayload * payload, GstCaps * caps)
189 {
190   gboolean res;
191   GstCaps *src_caps;
192   GstStructure *s, *outcaps;
193   const char *encoding_name = "OPUS";
194   gint channels = 2;
195   gint rate;
196   gchar *encoding_params;
197
198   outcaps = gst_structure_new_empty ("unused");
199
200   src_caps = gst_pad_get_allowed_caps (GST_RTP_BASE_PAYLOAD_SRCPAD (payload));
201   if (src_caps) {
202     GstStructure *s;
203     const GValue *value;
204
205     s = gst_caps_get_structure (src_caps, 0);
206
207     if (gst_structure_has_field (s, "encoding-name")) {
208       GValue default_value = G_VALUE_INIT;
209
210       g_value_init (&default_value, G_TYPE_STRING);
211       g_value_set_static_string (&default_value, encoding_name);
212
213       value = gst_structure_get_value (s, "encoding-name");
214       if (!gst_value_can_intersect (&default_value, value))
215         encoding_name = "X-GST-OPUS-DRAFT-SPITTKA-00";
216     }
217     gst_caps_unref (src_caps);
218   }
219
220   s = gst_caps_get_structure (caps, 0);
221   if (gst_structure_get_int (s, "channels", &channels)) {
222     if (channels > 2) {
223       /* Implies channel-mapping-family = 1. */
224
225       gint stream_count, coupled_count;
226       const GValue *channel_mapping_array;
227
228       /* libwebrtc only supports "multiopus" when channels > 2. Mono and stereo
229        * sound must always be payloaded according to RFC 7587. */
230       encoding_name = "multiopus";
231
232       if (gst_structure_get_int (s, "stream-count", &stream_count)) {
233         char *num_streams = g_strdup_printf ("%d", stream_count);
234         gst_structure_set (outcaps, "num_streams", G_TYPE_STRING, num_streams,
235             NULL);
236         g_free (num_streams);
237       }
238       if (gst_structure_get_int (s, "coupled-count", &coupled_count)) {
239         char *coupled_streams = g_strdup_printf ("%d", coupled_count);
240         gst_structure_set (outcaps, "coupled_streams", G_TYPE_STRING,
241             coupled_streams, NULL);
242         g_free (coupled_streams);
243       }
244
245       channel_mapping_array = gst_structure_get_value (s, "channel-mapping");
246       if (GST_VALUE_HOLDS_ARRAY (channel_mapping_array)) {
247         GString *str = g_string_new (NULL);
248         guint i;
249
250         for (i = 0; i < gst_value_array_get_size (channel_mapping_array); ++i) {
251           if (i != 0) {
252             g_string_append_c (str, ',');
253           }
254           g_string_append_printf (str, "%d",
255               g_value_get_int (gst_value_array_get_value (channel_mapping_array,
256                       i)));
257         }
258
259         gst_structure_set (outcaps, "channel_mapping", G_TYPE_STRING, str->str,
260             NULL);
261
262         g_string_free (str, TRUE);
263       }
264     } else {
265       gst_structure_set (outcaps, "sprop-stereo", G_TYPE_STRING,
266           (channels == 2) ? "1" : "0", NULL);
267       /* RFC 7587 requires the number of channels always be 2. */
268       channels = 2;
269     }
270   }
271
272   encoding_params = g_strdup_printf ("%d", channels);
273   gst_structure_set (outcaps, "encoding-params", G_TYPE_STRING,
274       encoding_params, NULL);
275   g_free (encoding_params);
276
277   if (gst_structure_get_int (s, "rate", &rate)) {
278     gchar *sprop_maxcapturerate = g_strdup_printf ("%d", rate);
279
280     gst_structure_set (outcaps, "sprop-maxcapturerate", G_TYPE_STRING,
281         sprop_maxcapturerate, NULL);
282
283     g_free (sprop_maxcapturerate);
284   }
285
286   gst_rtp_base_payload_set_options (payload, "audio", FALSE,
287       encoding_name, 48000);
288
289   res = gst_rtp_base_payload_set_outcaps_structure (payload, outcaps);
290
291   gst_structure_free (outcaps);
292
293   return res;
294 }
295
296 static GstFlowReturn
297 gst_rtp_opus_pay_handle_buffer (GstRTPBasePayload * basepayload,
298     GstBuffer * buffer)
299 {
300   GstRtpOPUSPay *self = GST_RTP_OPUS_PAY_CAST (basepayload);
301   GstBuffer *outbuf;
302   GstClockTime pts, dts, duration;
303
304   /* DTX packets are zero-length frames, with a 1 or 2-bytes header */
305   if (self->dtx && gst_buffer_get_size (buffer) <= 2) {
306     GST_LOG_OBJECT (self,
307         "discard empty buffer as DTX is enabled: %" GST_PTR_FORMAT, buffer);
308     gst_buffer_unref (buffer);
309     return GST_FLOW_OK;
310   }
311
312   pts = GST_BUFFER_PTS (buffer);
313   dts = GST_BUFFER_DTS (buffer);
314   duration = GST_BUFFER_DURATION (buffer);
315
316   outbuf = gst_rtp_base_payload_allocate_output_buffer (basepayload, 0, 0, 0);
317
318   gst_rtp_copy_audio_meta (basepayload, outbuf, buffer);
319
320   outbuf = gst_buffer_append (outbuf, buffer);
321
322   GST_BUFFER_PTS (outbuf) = pts;
323   GST_BUFFER_DTS (outbuf) = dts;
324   GST_BUFFER_DURATION (outbuf) = duration;
325
326   /* Push out */
327   return gst_rtp_base_payload_push (basepayload, outbuf);
328 }
329
330 static GstCaps *
331 gst_rtp_opus_pay_getcaps (GstRTPBasePayload * payload,
332     GstPad * pad, GstCaps * filter)
333 {
334   GstCaps *caps, *peercaps, *tcaps;
335   GstStructure *s;
336   const gchar *stereo;
337
338   if (pad == GST_RTP_BASE_PAYLOAD_SRCPAD (payload))
339     return
340         GST_RTP_BASE_PAYLOAD_CLASS (gst_rtp_opus_pay_parent_class)->get_caps
341         (payload, pad, filter);
342
343   tcaps = gst_pad_get_pad_template_caps (GST_RTP_BASE_PAYLOAD_SRCPAD (payload));
344   peercaps = gst_pad_peer_query_caps (GST_RTP_BASE_PAYLOAD_SRCPAD (payload),
345       tcaps);
346   gst_caps_unref (tcaps);
347   if (!peercaps)
348     return
349         GST_RTP_BASE_PAYLOAD_CLASS (gst_rtp_opus_pay_parent_class)->get_caps
350         (payload, pad, filter);
351
352   if (gst_caps_is_empty (peercaps))
353     return peercaps;
354
355   caps = gst_pad_get_pad_template_caps (GST_RTP_BASE_PAYLOAD_SINKPAD (payload));
356
357   s = gst_caps_get_structure (peercaps, 0);
358   stereo = gst_structure_get_string (s, "stereo");
359   if (stereo != NULL) {
360     caps = gst_caps_make_writable (caps);
361
362     if (!strcmp (stereo, "1")) {
363       GstCaps *caps2 = gst_caps_copy (caps);
364
365       gst_caps_set_simple (caps, "channels", G_TYPE_INT, 2, NULL);
366       gst_caps_set_simple (caps2, "channels", G_TYPE_INT, 1, NULL);
367       caps = gst_caps_merge (caps, caps2);
368     } else if (!strcmp (stereo, "0")) {
369       GstCaps *caps2 = gst_caps_copy (caps);
370
371       gst_caps_set_simple (caps, "channels", G_TYPE_INT, 1, NULL);
372       gst_caps_set_simple (caps2, "channels", G_TYPE_INT, 2, NULL);
373       caps = gst_caps_merge (caps, caps2);
374     }
375   }
376   gst_caps_unref (peercaps);
377
378   if (filter) {
379     GstCaps *tmp = gst_caps_intersect_full (caps, filter,
380         GST_CAPS_INTERSECT_FIRST);
381     gst_caps_unref (caps);
382     caps = tmp;
383   }
384
385   GST_DEBUG_OBJECT (payload, "Returning caps: %" GST_PTR_FORMAT, caps);
386   return caps;
387 }