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