1 /* GStreamer
2  * Copyright (C) <2009> Wim Taymans <wim.taymans@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23 
24 #include <stdlib.h>
25 #include <string.h>
26 #include <gst/rtp/gstrtpbuffer.h>
27 #include <gst/audio/audio.h>
28 
29 #include "gstrtpceltpay.h"
30 #include "gstrtputils.h"
31 
32 GST_DEBUG_CATEGORY_STATIC (rtpceltpay_debug);
33 #define GST_CAT_DEFAULT (rtpceltpay_debug)
34 
35 static GstStaticPadTemplate gst_rtp_celt_pay_sink_template =
36 GST_STATIC_PAD_TEMPLATE ("sink",
37     GST_PAD_SINK,
38     GST_PAD_ALWAYS,
39     GST_STATIC_CAPS ("audio/x-celt, "
40         "rate = (int) [ 32000, 64000 ], "
41         "channels = (int) [1, 2], " "frame-size = (int) [ 64, 512 ]")
42     );
43 
44 static GstStaticPadTemplate gst_rtp_celt_pay_src_template =
45 GST_STATIC_PAD_TEMPLATE ("src",
46     GST_PAD_SRC,
47     GST_PAD_ALWAYS,
48     GST_STATIC_CAPS ("application/x-rtp, "
49         "media = (string) \"audio\", "
50         "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
51         "clock-rate =  (int) [ 32000, 48000 ], "
52         "encoding-name = (string) \"CELT\"")
53     );
54 
55 static void gst_rtp_celt_pay_finalize (GObject * object);
56 
57 static GstStateChangeReturn gst_rtp_celt_pay_change_state (GstElement *
58     element, GstStateChange transition);
59 
60 static gboolean gst_rtp_celt_pay_setcaps (GstRTPBasePayload * payload,
61     GstCaps * caps);
62 static GstCaps *gst_rtp_celt_pay_getcaps (GstRTPBasePayload * payload,
63     GstPad * pad, GstCaps * filter);
64 static GstFlowReturn gst_rtp_celt_pay_handle_buffer (GstRTPBasePayload *
65     payload, GstBuffer * buffer);
66 
67 #define gst_rtp_celt_pay_parent_class parent_class
68 G_DEFINE_TYPE (GstRtpCELTPay, gst_rtp_celt_pay, GST_TYPE_RTP_BASE_PAYLOAD);
69 
70 static void
gst_rtp_celt_pay_class_init(GstRtpCELTPayClass * klass)71 gst_rtp_celt_pay_class_init (GstRtpCELTPayClass * klass)
72 {
73   GObjectClass *gobject_class;
74   GstElementClass *gstelement_class;
75   GstRTPBasePayloadClass *gstrtpbasepayload_class;
76 
77   GST_DEBUG_CATEGORY_INIT (rtpceltpay_debug, "rtpceltpay", 0,
78       "CELT RTP Payloader");
79 
80   gobject_class = (GObjectClass *) klass;
81   gstelement_class = (GstElementClass *) klass;
82   gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
83 
84   gobject_class->finalize = gst_rtp_celt_pay_finalize;
85 
86   gstelement_class->change_state = gst_rtp_celt_pay_change_state;
87 
88   gst_element_class_add_static_pad_template (gstelement_class,
89       &gst_rtp_celt_pay_sink_template);
90   gst_element_class_add_static_pad_template (gstelement_class,
91       &gst_rtp_celt_pay_src_template);
92 
93   gst_element_class_set_static_metadata (gstelement_class, "RTP CELT payloader",
94       "Codec/Payloader/Network/RTP",
95       "Payload-encodes CELT audio into a RTP packet",
96       "Wim Taymans <wim.taymans@gmail.com>");
97 
98   gstrtpbasepayload_class->set_caps = gst_rtp_celt_pay_setcaps;
99   gstrtpbasepayload_class->get_caps = gst_rtp_celt_pay_getcaps;
100   gstrtpbasepayload_class->handle_buffer = gst_rtp_celt_pay_handle_buffer;
101 }
102 
103 static void
gst_rtp_celt_pay_init(GstRtpCELTPay * rtpceltpay)104 gst_rtp_celt_pay_init (GstRtpCELTPay * rtpceltpay)
105 {
106   rtpceltpay->queue = g_queue_new ();
107 }
108 
109 static void
gst_rtp_celt_pay_finalize(GObject * object)110 gst_rtp_celt_pay_finalize (GObject * object)
111 {
112   GstRtpCELTPay *rtpceltpay;
113 
114   rtpceltpay = GST_RTP_CELT_PAY (object);
115 
116   g_queue_free (rtpceltpay->queue);
117 
118   G_OBJECT_CLASS (parent_class)->finalize (object);
119 }
120 
121 static void
gst_rtp_celt_pay_clear_queued(GstRtpCELTPay * rtpceltpay)122 gst_rtp_celt_pay_clear_queued (GstRtpCELTPay * rtpceltpay)
123 {
124   GstBuffer *buf;
125 
126   while ((buf = g_queue_pop_head (rtpceltpay->queue)))
127     gst_buffer_unref (buf);
128 
129   rtpceltpay->bytes = 0;
130   rtpceltpay->sbytes = 0;
131   rtpceltpay->qduration = 0;
132 }
133 
134 static void
gst_rtp_celt_pay_add_queued(GstRtpCELTPay * rtpceltpay,GstBuffer * buffer,guint ssize,guint size,GstClockTime duration)135 gst_rtp_celt_pay_add_queued (GstRtpCELTPay * rtpceltpay, GstBuffer * buffer,
136     guint ssize, guint size, GstClockTime duration)
137 {
138   g_queue_push_tail (rtpceltpay->queue, buffer);
139   rtpceltpay->sbytes += ssize;
140   rtpceltpay->bytes += size;
141   /* only add durations when we have a valid previous duration */
142   if (rtpceltpay->qduration != -1) {
143     if (duration != -1)
144       /* only add valid durations */
145       rtpceltpay->qduration += duration;
146     else
147       /* if we add a buffer without valid duration, our total queued duration
148        * becomes unknown */
149       rtpceltpay->qduration = -1;
150   }
151 }
152 
153 static gboolean
gst_rtp_celt_pay_setcaps(GstRTPBasePayload * payload,GstCaps * caps)154 gst_rtp_celt_pay_setcaps (GstRTPBasePayload * payload, GstCaps * caps)
155 {
156   /* don't configure yet, we wait for the ident packet */
157   return TRUE;
158 }
159 
160 
161 static GstCaps *
gst_rtp_celt_pay_getcaps(GstRTPBasePayload * payload,GstPad * pad,GstCaps * filter)162 gst_rtp_celt_pay_getcaps (GstRTPBasePayload * payload, GstPad * pad,
163     GstCaps * filter)
164 {
165   GstCaps *otherpadcaps;
166   GstCaps *caps;
167   const gchar *params;
168 
169   caps = gst_pad_get_pad_template_caps (pad);
170 
171   otherpadcaps = gst_pad_get_allowed_caps (payload->srcpad);
172   if (otherpadcaps) {
173     if (!gst_caps_is_empty (otherpadcaps)) {
174       GstStructure *ps;
175       GstStructure *s;
176       gint clock_rate = 0, frame_size = 0, channels = 1;
177 
178       caps = gst_caps_make_writable (caps);
179 
180       ps = gst_caps_get_structure (otherpadcaps, 0);
181       s = gst_caps_get_structure (caps, 0);
182 
183       if (gst_structure_get_int (ps, "clock-rate", &clock_rate)) {
184         gst_structure_fixate_field_nearest_int (s, "rate", clock_rate);
185       }
186 
187       if ((params = gst_structure_get_string (ps, "frame-size")))
188         frame_size = atoi (params);
189       if (frame_size)
190         gst_structure_set (s, "frame-size", G_TYPE_INT, frame_size, NULL);
191 
192       if ((params = gst_structure_get_string (ps, "encoding-params"))) {
193         channels = atoi (params);
194         gst_structure_fixate_field_nearest_int (s, "channels", channels);
195       }
196 
197       GST_DEBUG_OBJECT (payload, "clock-rate=%d frame-size=%d channels=%d",
198           clock_rate, frame_size, channels);
199     }
200     gst_caps_unref (otherpadcaps);
201   }
202 
203   if (filter) {
204     GstCaps *tmp;
205 
206     GST_DEBUG_OBJECT (payload, "Intersect %" GST_PTR_FORMAT " and filter %"
207         GST_PTR_FORMAT, caps, filter);
208     tmp = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
209     gst_caps_unref (caps);
210     caps = tmp;
211   }
212 
213   return caps;
214 }
215 
216 static gboolean
gst_rtp_celt_pay_parse_ident(GstRtpCELTPay * rtpceltpay,const guint8 * data,guint size)217 gst_rtp_celt_pay_parse_ident (GstRtpCELTPay * rtpceltpay,
218     const guint8 * data, guint size)
219 {
220   guint32 version, header_size, rate, nb_channels, frame_size, overlap;
221   guint32 bytes_per_packet;
222   GstRTPBasePayload *payload;
223   gchar *cstr, *fsstr;
224   gboolean res;
225 
226   /* we need the header string (8), the version string (20), the version
227    * and the header length. */
228   if (size < 36)
229     goto too_small;
230 
231   if (!g_str_has_prefix ((const gchar *) data, "CELT    "))
232     goto wrong_header;
233 
234   /* skip header and version string */
235   data += 28;
236 
237   version = GST_READ_UINT32_LE (data);
238   GST_DEBUG_OBJECT (rtpceltpay, "version %08x", version);
239 #if 0
240   if (version != 1)
241     goto wrong_version;
242 #endif
243 
244   data += 4;
245   /* ensure sizes */
246   header_size = GST_READ_UINT32_LE (data);
247   if (header_size < 56)
248     goto header_too_small;
249 
250   if (size < header_size)
251     goto payload_too_small;
252 
253   data += 4;
254   rate = GST_READ_UINT32_LE (data);
255   data += 4;
256   nb_channels = GST_READ_UINT32_LE (data);
257   data += 4;
258   frame_size = GST_READ_UINT32_LE (data);
259   data += 4;
260   overlap = GST_READ_UINT32_LE (data);
261   data += 4;
262   bytes_per_packet = GST_READ_UINT32_LE (data);
263 
264   GST_DEBUG_OBJECT (rtpceltpay, "rate %d, nb_channels %d, frame_size %d",
265       rate, nb_channels, frame_size);
266   GST_DEBUG_OBJECT (rtpceltpay, "overlap %d, bytes_per_packet %d",
267       overlap, bytes_per_packet);
268 
269   payload = GST_RTP_BASE_PAYLOAD (rtpceltpay);
270 
271   gst_rtp_base_payload_set_options (payload, "audio", FALSE, "CELT", rate);
272   cstr = g_strdup_printf ("%d", nb_channels);
273   fsstr = g_strdup_printf ("%d", frame_size);
274   res = gst_rtp_base_payload_set_outcaps (payload, "encoding-params",
275       G_TYPE_STRING, cstr, "frame-size", G_TYPE_STRING, fsstr, NULL);
276   g_free (cstr);
277   g_free (fsstr);
278 
279   return res;
280 
281   /* ERRORS */
282 too_small:
283   {
284     GST_DEBUG_OBJECT (rtpceltpay,
285         "ident packet too small, need at least 32 bytes");
286     return FALSE;
287   }
288 wrong_header:
289   {
290     GST_DEBUG_OBJECT (rtpceltpay,
291         "ident packet does not start with \"CELT    \"");
292     return FALSE;
293   }
294 #if 0
295 wrong_version:
296   {
297     GST_DEBUG_OBJECT (rtpceltpay, "can only handle version 1, have version %d",
298         version);
299     return FALSE;
300   }
301 #endif
302 header_too_small:
303   {
304     GST_DEBUG_OBJECT (rtpceltpay,
305         "header size too small, need at least 80 bytes, " "got only %d",
306         header_size);
307     return FALSE;
308   }
309 payload_too_small:
310   {
311     GST_DEBUG_OBJECT (rtpceltpay,
312         "payload too small, need at least %d bytes, got only %d", header_size,
313         size);
314     return FALSE;
315   }
316 }
317 
318 static GstFlowReturn
gst_rtp_celt_pay_flush_queued(GstRtpCELTPay * rtpceltpay)319 gst_rtp_celt_pay_flush_queued (GstRtpCELTPay * rtpceltpay)
320 {
321   GstFlowReturn ret;
322   GstBuffer *buf, *outbuf;
323   guint8 *payload, *spayload;
324   guint payload_len;
325   GstClockTime duration;
326   GstRTPBuffer rtp = { NULL, };
327 
328   payload_len = rtpceltpay->bytes + rtpceltpay->sbytes;
329   duration = rtpceltpay->qduration;
330 
331   GST_DEBUG_OBJECT (rtpceltpay, "flushing out %u, duration %" GST_TIME_FORMAT,
332       payload_len, GST_TIME_ARGS (rtpceltpay->qduration));
333 
334   /* get a big enough packet for the sizes + payloads */
335   outbuf = gst_rtp_buffer_new_allocate (payload_len, 0, 0);
336 
337   GST_BUFFER_DURATION (outbuf) = duration;
338 
339   gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
340 
341   /* point to the payload for size headers and data */
342   spayload = gst_rtp_buffer_get_payload (&rtp);
343   payload = spayload + rtpceltpay->sbytes;
344 
345   while ((buf = g_queue_pop_head (rtpceltpay->queue))) {
346     guint size;
347 
348     /* copy first timestamp to output */
349     if (GST_BUFFER_PTS (outbuf) == -1)
350       GST_BUFFER_PTS (outbuf) = GST_BUFFER_PTS (buf);
351 
352     /* write the size to the header */
353     size = gst_buffer_get_size (buf);
354     while (size > 0xff) {
355       *spayload++ = 0xff;
356       size -= 0xff;
357     }
358     *spayload++ = size;
359 
360     /* copy payload */
361     size = gst_buffer_get_size (buf);
362     gst_buffer_extract (buf, 0, payload, size);
363     payload += size;
364 
365     gst_rtp_copy_audio_meta (rtpceltpay, outbuf, buf);
366 
367     gst_buffer_unref (buf);
368   }
369   gst_rtp_buffer_unmap (&rtp);
370 
371   /* we consumed it all */
372   rtpceltpay->bytes = 0;
373   rtpceltpay->sbytes = 0;
374   rtpceltpay->qduration = 0;
375 
376   ret = gst_rtp_base_payload_push (GST_RTP_BASE_PAYLOAD (rtpceltpay), outbuf);
377 
378   return ret;
379 }
380 
381 static GstFlowReturn
gst_rtp_celt_pay_handle_buffer(GstRTPBasePayload * basepayload,GstBuffer * buffer)382 gst_rtp_celt_pay_handle_buffer (GstRTPBasePayload * basepayload,
383     GstBuffer * buffer)
384 {
385   GstFlowReturn ret;
386   GstRtpCELTPay *rtpceltpay;
387   gsize payload_len;
388   GstMapInfo map;
389   GstClockTime duration, packet_dur;
390   guint i, ssize, packet_len;
391 
392   rtpceltpay = GST_RTP_CELT_PAY (basepayload);
393 
394   ret = GST_FLOW_OK;
395 
396   gst_buffer_map (buffer, &map, GST_MAP_READ);
397 
398   switch (rtpceltpay->packet) {
399     case 0:
400       /* ident packet. We need to parse the headers to construct the RTP
401        * properties. */
402       if (!gst_rtp_celt_pay_parse_ident (rtpceltpay, map.data, map.size))
403         goto parse_error;
404 
405       goto cleanup;
406     case 1:
407       /* comment packet, we ignore it */
408       goto cleanup;
409     default:
410       /* other packets go in the payload */
411       break;
412   }
413   gst_buffer_unmap (buffer, &map);
414 
415   duration = GST_BUFFER_DURATION (buffer);
416 
417   GST_LOG_OBJECT (rtpceltpay,
418       "got buffer of duration %" GST_TIME_FORMAT ", size %" G_GSIZE_FORMAT,
419       GST_TIME_ARGS (duration), map.size);
420 
421   /* calculate the size of the size field and the payload */
422   ssize = 1;
423   for (i = map.size; i > 0xff; i -= 0xff)
424     ssize++;
425 
426   GST_DEBUG_OBJECT (rtpceltpay, "bytes for size %u", ssize);
427 
428   /* calculate what the new size and duration would be of the packet */
429   payload_len = ssize + map.size + rtpceltpay->bytes + rtpceltpay->sbytes;
430   if (rtpceltpay->qduration != -1 && duration != -1)
431     packet_dur = rtpceltpay->qduration + duration;
432   else
433     packet_dur = 0;
434 
435   packet_len = gst_rtp_buffer_calc_packet_len (payload_len, 0, 0);
436 
437   if (gst_rtp_base_payload_is_filled (basepayload, packet_len, packet_dur)) {
438     /* size or duration would overflow the packet, flush the queued data */
439     ret = gst_rtp_celt_pay_flush_queued (rtpceltpay);
440   }
441 
442   /* queue the packet */
443   gst_rtp_celt_pay_add_queued (rtpceltpay, buffer, ssize, map.size, duration);
444 
445 done:
446   rtpceltpay->packet++;
447 
448   return ret;
449 
450   /* ERRORS */
451 cleanup:
452   {
453     gst_buffer_unmap (buffer, &map);
454     goto done;
455   }
456 parse_error:
457   {
458     GST_ELEMENT_ERROR (rtpceltpay, STREAM, DECODE, (NULL),
459         ("Error parsing first identification packet."));
460     gst_buffer_unmap (buffer, &map);
461     return GST_FLOW_ERROR;
462   }
463 }
464 
465 static GstStateChangeReturn
gst_rtp_celt_pay_change_state(GstElement * element,GstStateChange transition)466 gst_rtp_celt_pay_change_state (GstElement * element, GstStateChange transition)
467 {
468   GstRtpCELTPay *rtpceltpay;
469   GstStateChangeReturn ret;
470 
471   rtpceltpay = GST_RTP_CELT_PAY (element);
472 
473   switch (transition) {
474     case GST_STATE_CHANGE_NULL_TO_READY:
475       break;
476     case GST_STATE_CHANGE_READY_TO_PAUSED:
477       rtpceltpay->packet = 0;
478       break;
479     default:
480       break;
481   }
482 
483   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
484 
485   switch (transition) {
486     case GST_STATE_CHANGE_PAUSED_TO_READY:
487       gst_rtp_celt_pay_clear_queued (rtpceltpay);
488       break;
489     case GST_STATE_CHANGE_READY_TO_NULL:
490       break;
491     default:
492       break;
493   }
494   return ret;
495 }
496 
497 gboolean
gst_rtp_celt_pay_plugin_init(GstPlugin * plugin)498 gst_rtp_celt_pay_plugin_init (GstPlugin * plugin)
499 {
500   return gst_element_register (plugin, "rtpceltpay",
501       GST_RANK_SECONDARY, GST_TYPE_RTP_CELT_PAY);
502 }
503