1 /*
2  * GStreamer RTP SBC depayloader
3  *
4  * Copyright (C) 2012  Collabora Ltd.
5  *   @author: Arun Raghavan <arun.raghavan@collabora.co.uk>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #include <gst/rtp/gstrtpbuffer.h>
28 #include <gst/audio/audio.h>
29 #include "gstrtpsbcdepay.h"
30 #include "gstrtputils.h"
31 
32 GST_DEBUG_CATEGORY_STATIC (rtpsbcdepay_debug);
33 #define GST_CAT_DEFAULT (rtpsbcdepay_debug)
34 
35 static GstStaticPadTemplate gst_rtp_sbc_depay_src_template =
36 GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
37     GST_STATIC_CAPS ("audio/x-sbc, "
38         "rate = (int) { 16000, 32000, 44100, 48000 }, "
39         "channels = (int) [ 1, 2 ], "
40         "mode = (string) { mono, dual, stereo, joint }, "
41         "blocks = (int) { 4, 8, 12, 16 }, "
42         "subbands = (int) { 4, 8 }, "
43         "allocation-method = (string) { snr, loudness }, "
44         "bitpool = (int) [ 2, 64 ]")
45     );
46 
47 static GstStaticPadTemplate gst_rtp_sbc_depay_sink_template =
48 GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
49     GST_STATIC_CAPS ("application/x-rtp, "
50         "media = (string) audio,"
51         "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
52         "clock-rate = (int) { 16000, 32000, 44100, 48000 },"
53         "encoding-name = (string) SBC")
54     );
55 
56 enum
57 {
58   PROP_0,
59   PROP_IGNORE_TIMESTAMPS,
60   PROP_LAST
61 };
62 
63 #define DEFAULT_IGNORE_TIMESTAMPS FALSE
64 
65 #define gst_rtp_sbc_depay_parent_class parent_class
66 G_DEFINE_TYPE (GstRtpSbcDepay, gst_rtp_sbc_depay, GST_TYPE_RTP_BASE_DEPAYLOAD);
67 
68 static void gst_rtp_sbc_depay_set_property (GObject * object,
69     guint prop_id, const GValue * value, GParamSpec * pspec);
70 static void gst_rtp_sbc_depay_get_property (GObject * object,
71     guint prop_id, GValue * value, GParamSpec * pspec);
72 static void gst_rtp_sbc_depay_finalize (GObject * object);
73 
74 static gboolean gst_rtp_sbc_depay_setcaps (GstRTPBaseDepayload * base,
75     GstCaps * caps);
76 static GstBuffer *gst_rtp_sbc_depay_process (GstRTPBaseDepayload * base,
77     GstRTPBuffer * rtp);
78 
79 static void
gst_rtp_sbc_depay_class_init(GstRtpSbcDepayClass * klass)80 gst_rtp_sbc_depay_class_init (GstRtpSbcDepayClass * klass)
81 {
82   GstRTPBaseDepayloadClass *gstbasertpdepayload_class =
83       GST_RTP_BASE_DEPAYLOAD_CLASS (klass);
84   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
85   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
86 
87   gobject_class->finalize = gst_rtp_sbc_depay_finalize;
88   gobject_class->set_property = gst_rtp_sbc_depay_set_property;
89   gobject_class->get_property = gst_rtp_sbc_depay_get_property;
90 
91   g_object_class_install_property (gobject_class, PROP_IGNORE_TIMESTAMPS,
92       g_param_spec_boolean ("ignore-timestamps", "Ignore Timestamps",
93           "Various statistics", DEFAULT_IGNORE_TIMESTAMPS,
94           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
95 
96   gstbasertpdepayload_class->set_caps = gst_rtp_sbc_depay_setcaps;
97   gstbasertpdepayload_class->process_rtp_packet = gst_rtp_sbc_depay_process;
98 
99   gst_element_class_add_static_pad_template (element_class,
100       &gst_rtp_sbc_depay_src_template);
101   gst_element_class_add_static_pad_template (element_class,
102       &gst_rtp_sbc_depay_sink_template);
103 
104   GST_DEBUG_CATEGORY_INIT (rtpsbcdepay_debug, "rtpsbcdepay", 0,
105       "SBC Audio RTP Depayloader");
106 
107   gst_element_class_set_static_metadata (element_class,
108       "RTP SBC audio depayloader",
109       "Codec/Depayloader/Network/RTP",
110       "Extracts SBC audio from RTP packets",
111       "Arun Raghavan <arun.raghavan@collabora.co.uk>");
112 }
113 
114 static void
gst_rtp_sbc_depay_init(GstRtpSbcDepay * rtpsbcdepay)115 gst_rtp_sbc_depay_init (GstRtpSbcDepay * rtpsbcdepay)
116 {
117   rtpsbcdepay->adapter = gst_adapter_new ();
118   rtpsbcdepay->stream_align =
119       gst_audio_stream_align_new (48000, 40 * GST_MSECOND, 1 * GST_SECOND);
120   rtpsbcdepay->ignore_timestamps = DEFAULT_IGNORE_TIMESTAMPS;
121 }
122 
123 static void
gst_rtp_sbc_depay_finalize(GObject * object)124 gst_rtp_sbc_depay_finalize (GObject * object)
125 {
126   GstRtpSbcDepay *depay = GST_RTP_SBC_DEPAY (object);
127 
128   gst_audio_stream_align_free (depay->stream_align);
129   gst_object_unref (depay->adapter);
130 
131   G_OBJECT_CLASS (parent_class)->finalize (object);
132 }
133 
134 static void
gst_rtp_sbc_depay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)135 gst_rtp_sbc_depay_set_property (GObject * object,
136     guint prop_id, const GValue * value, GParamSpec * pspec)
137 {
138   GstRtpSbcDepay *depay = GST_RTP_SBC_DEPAY (object);
139 
140   switch (prop_id) {
141     case PROP_IGNORE_TIMESTAMPS:
142       depay->ignore_timestamps = g_value_get_boolean (value);
143       break;
144     default:
145       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
146       break;
147   }
148 }
149 
150 static void
gst_rtp_sbc_depay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)151 gst_rtp_sbc_depay_get_property (GObject * object,
152     guint prop_id, GValue * value, GParamSpec * pspec)
153 {
154   GstRtpSbcDepay *depay = GST_RTP_SBC_DEPAY (object);
155 
156   switch (prop_id) {
157     case PROP_IGNORE_TIMESTAMPS:
158       g_value_set_boolean (value, depay->ignore_timestamps);
159       break;
160     default:
161       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
162       break;
163   }
164 }
165 
166 /* FIXME: This duplicates similar functionality rtpsbcpay, but there isn't a
167  * simple way to consolidate the two. This is best done by moving the function
168  * to the codec-utils library in gst-plugins-base when these elements move to
169  * GStreamer. */
170 static int
gst_rtp_sbc_depay_get_params(GstRtpSbcDepay * depay,const guint8 * data,gint size,int * framelen,int * samples)171 gst_rtp_sbc_depay_get_params (GstRtpSbcDepay * depay, const guint8 * data,
172     gint size, int *framelen, int *samples)
173 {
174   int blocks, channel_mode, channels, subbands, bitpool;
175   int length;
176 
177   if (size < 3) {
178     /* Not enough data for the header */
179     return -1;
180   }
181 
182   /* Sanity check */
183   if (data[0] != 0x9c) {
184     GST_WARNING_OBJECT (depay, "Bad packet: couldn't find syncword");
185     return -2;
186   }
187 
188   blocks = (data[1] >> 4) & 0x3;
189   blocks = (blocks + 1) * 4;
190   channel_mode = (data[1] >> 2) & 0x3;
191   channels = channel_mode ? 2 : 1;
192   subbands = (data[1] & 0x1);
193   subbands = (subbands + 1) * 4;
194   bitpool = data[2];
195 
196   length = 4 + ((4 * subbands * channels) / 8);
197 
198   if (channel_mode == 0 || channel_mode == 1) {
199     /* Mono || Dual channel */
200     length += ((blocks * channels * bitpool)
201         + 4 /* round up */ ) / 8;
202   } else {
203     /* Stereo || Joint stereo */
204     gboolean joint = (channel_mode == 3);
205 
206     length += ((joint * subbands) + (blocks * bitpool)
207         + 4 /* round up */ ) / 8;
208   }
209 
210   *framelen = length;
211   *samples = blocks * subbands;
212 
213   return 0;
214 }
215 
216 static gboolean
gst_rtp_sbc_depay_setcaps(GstRTPBaseDepayload * base,GstCaps * caps)217 gst_rtp_sbc_depay_setcaps (GstRTPBaseDepayload * base, GstCaps * caps)
218 {
219   GstRtpSbcDepay *depay = GST_RTP_SBC_DEPAY (base);
220   GstStructure *structure;
221   GstCaps *outcaps, *oldcaps;
222 
223   structure = gst_caps_get_structure (caps, 0);
224 
225   if (!gst_structure_get_int (structure, "clock-rate", &depay->rate))
226     goto bad_caps;
227 
228   outcaps = gst_caps_new_simple ("audio/x-sbc", "rate", G_TYPE_INT,
229       depay->rate, NULL);
230 
231   gst_pad_set_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (base), outcaps);
232 
233   oldcaps = gst_pad_get_current_caps (GST_RTP_BASE_DEPAYLOAD_SINKPAD (base));
234   if (oldcaps && !gst_caps_can_intersect (oldcaps, caps)) {
235     /* Caps have changed, flush old data */
236     gst_adapter_clear (depay->adapter);
237   }
238 
239   gst_caps_unref (outcaps);
240   if (oldcaps)
241     gst_caps_unref (oldcaps);
242 
243   /* Reset when the caps are changing */
244   gst_audio_stream_align_set_rate (depay->stream_align, depay->rate);
245 
246   return TRUE;
247 
248 bad_caps:
249   GST_WARNING_OBJECT (depay, "Can't support the caps we got: %"
250       GST_PTR_FORMAT, caps);
251   return FALSE;
252 }
253 
254 static GstBuffer *
gst_rtp_sbc_depay_process(GstRTPBaseDepayload * base,GstRTPBuffer * rtp)255 gst_rtp_sbc_depay_process (GstRTPBaseDepayload * base, GstRTPBuffer * rtp)
256 {
257   GstRtpSbcDepay *depay = GST_RTP_SBC_DEPAY (base);
258   GstBuffer *data = NULL;
259 
260   gboolean fragment, start, last;
261   guint8 nframes;
262   guint8 *payload;
263   guint payload_len;
264   gint samples = 0;
265 
266   GstClockTime timestamp;
267 
268   GST_LOG_OBJECT (depay, "Got %" G_GSIZE_FORMAT " bytes",
269       gst_buffer_get_size (rtp->buffer));
270 
271   if (gst_rtp_buffer_get_marker (rtp)) {
272     /* Marker isn't supposed to be set */
273     GST_WARNING_OBJECT (depay, "Marker bit was set");
274     goto bad_packet;
275   }
276 
277   timestamp = GST_BUFFER_DTS_OR_PTS (rtp->buffer);
278   if (depay->ignore_timestamps && timestamp == GST_CLOCK_TIME_NONE) {
279     GstClockTime initial_timestamp;
280     guint64 n_samples;
281 
282     initial_timestamp =
283         gst_audio_stream_align_get_timestamp_at_discont (depay->stream_align);
284     n_samples =
285         gst_audio_stream_align_get_samples_since_discont (depay->stream_align);
286 
287     if (initial_timestamp == GST_CLOCK_TIME_NONE) {
288       GST_ERROR_OBJECT (depay,
289           "Can only ignore timestamps on streams without valid initial timestamp");
290       return NULL;
291     }
292 
293     timestamp =
294         initial_timestamp + gst_util_uint64_scale (n_samples, GST_SECOND,
295         depay->rate);
296   }
297 
298   payload = gst_rtp_buffer_get_payload (rtp);
299   payload_len = gst_rtp_buffer_get_payload_len (rtp);
300 
301   fragment = payload[0] & 0x80;
302   start = payload[0] & 0x40;
303   last = payload[0] & 0x20;
304   nframes = payload[0] & 0x0f;
305 
306   payload += 1;
307   payload_len -= 1;
308 
309   data = gst_rtp_buffer_get_payload_subbuffer (rtp, 1, -1);
310 
311   if (fragment) {
312     /* Got a packet with a fragment */
313     GST_LOG_OBJECT (depay, "Got fragment");
314 
315     if (start && gst_adapter_available (depay->adapter)) {
316       GST_WARNING_OBJECT (depay, "Missing last fragment");
317       gst_adapter_clear (depay->adapter);
318 
319     } else if (!start && !gst_adapter_available (depay->adapter)) {
320       GST_WARNING_OBJECT (depay, "Missing start fragment");
321       gst_buffer_unref (data);
322       data = NULL;
323       goto out;
324     }
325 
326     gst_adapter_push (depay->adapter, data);
327 
328     if (last) {
329       gint framelen, samples;
330       guint8 header[4];
331 
332       data = gst_adapter_take_buffer (depay->adapter,
333           gst_adapter_available (depay->adapter));
334       gst_rtp_drop_non_audio_meta (depay, data);
335 
336       if (gst_buffer_extract (data, 0, &header, 4) != 4 ||
337           gst_rtp_sbc_depay_get_params (depay, header,
338               payload_len, &framelen, &samples) < 0) {
339         gst_buffer_unref (data);
340         goto bad_packet;
341       }
342     } else {
343       data = NULL;
344     }
345   } else {
346     /* !fragment */
347     gint framelen;
348 
349     GST_LOG_OBJECT (depay, "Got %d frames", nframes);
350 
351     if (gst_rtp_sbc_depay_get_params (depay, payload,
352             payload_len, &framelen, &samples) < 0) {
353       gst_adapter_clear (depay->adapter);
354       goto bad_packet;
355     }
356 
357     samples *= nframes;
358 
359     GST_LOG_OBJECT (depay, "Got payload of %d", payload_len);
360 
361     if (nframes * framelen > (gint) payload_len) {
362       GST_WARNING_OBJECT (depay, "Short packet");
363       goto bad_packet;
364     } else if (nframes * framelen < (gint) payload_len) {
365       GST_WARNING_OBJECT (depay, "Junk at end of packet");
366     }
367   }
368 
369   if (depay->ignore_timestamps && data) {
370     GstClockTime duration;
371 
372     gst_audio_stream_align_process (depay->stream_align,
373         GST_BUFFER_IS_DISCONT (rtp->buffer), timestamp, samples, &timestamp,
374         &duration, NULL);
375 
376     GST_BUFFER_PTS (data) = timestamp;
377     GST_BUFFER_DTS (data) = GST_CLOCK_TIME_NONE;
378     GST_BUFFER_DURATION (data) = duration;
379   }
380 
381 out:
382   return data;
383 
384 bad_packet:
385   GST_ELEMENT_WARNING (depay, STREAM, DECODE,
386       ("Received invalid RTP payload, dropping"), (NULL));
387   goto out;
388 }
389 
390 gboolean
gst_rtp_sbc_depay_plugin_init(GstPlugin * plugin)391 gst_rtp_sbc_depay_plugin_init (GstPlugin * plugin)
392 {
393   return gst_element_register (plugin, "rtpsbcdepay", GST_RANK_SECONDARY,
394       GST_TYPE_RTP_SBC_DEPAY);
395 }
396