1 /* GStreamer
2 * Copyright (C) <2006> 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 <string.h>
25
26 #include <gst/rtp/gstrtpbuffer.h>
27 #include <gst/video/video.h>
28
29 #include "fnv1hash.h"
30 #include "gstrtptheorapay.h"
31 #include "gstrtputils.h"
32
33 #define THEORA_ID_LEN 42
34
35 GST_DEBUG_CATEGORY_STATIC (rtptheorapay_debug);
36 #define GST_CAT_DEFAULT (rtptheorapay_debug)
37
38 /* references:
39 * http://svn.xiph.org/trunk/theora/doc/draft-ietf-avt-rtp-theora-01.txt
40 */
41
42 static GstStaticPadTemplate gst_rtp_theora_pay_src_template =
43 GST_STATIC_PAD_TEMPLATE ("src",
44 GST_PAD_SRC,
45 GST_PAD_ALWAYS,
46 GST_STATIC_CAPS ("application/x-rtp, "
47 "media = (string) \"video\", "
48 "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
49 "clock-rate = (int) 90000, " "encoding-name = (string) \"THEORA\""
50 /* All required parameters
51 *
52 * "sampling = (string) { "YCbCr-4:2:0", "YCbCr-4:2:2", "YCbCr-4:4:4" } "
53 * "width = (string) [1, 1048561] (multiples of 16) "
54 * "height = (string) [1, 1048561] (multiples of 16) "
55 * "configuration = (string) ANY"
56 */
57 /* All optional parameters
58 *
59 * "configuration-uri ="
60 * "delivery-method = (string) { inline, in_band, out_band/<specific_name> } "
61 */
62 )
63 );
64
65 static GstStaticPadTemplate gst_rtp_theora_pay_sink_template =
66 GST_STATIC_PAD_TEMPLATE ("sink",
67 GST_PAD_SINK,
68 GST_PAD_ALWAYS,
69 GST_STATIC_CAPS ("video/x-theora")
70 );
71
72 #define DEFAULT_CONFIG_INTERVAL 0
73
74 enum
75 {
76 PROP_0,
77 PROP_CONFIG_INTERVAL
78 };
79
80 #define gst_rtp_theora_pay_parent_class parent_class
81 G_DEFINE_TYPE (GstRtpTheoraPay, gst_rtp_theora_pay, GST_TYPE_RTP_BASE_PAYLOAD);
82
83 static gboolean gst_rtp_theora_pay_setcaps (GstRTPBasePayload * basepayload,
84 GstCaps * caps);
85 static GstStateChangeReturn gst_rtp_theora_pay_change_state (GstElement *
86 element, GstStateChange transition);
87 static GstFlowReturn gst_rtp_theora_pay_handle_buffer (GstRTPBasePayload * pad,
88 GstBuffer * buffer);
89 static gboolean gst_rtp_theora_pay_sink_event (GstRTPBasePayload * payload,
90 GstEvent * event);
91
92 static gboolean gst_rtp_theora_pay_parse_id (GstRTPBasePayload * basepayload,
93 guint8 * data, guint size);
94 static gboolean gst_rtp_theora_pay_finish_headers (GstRTPBasePayload *
95 basepayload);
96
97 static void gst_rtp_theora_pay_set_property (GObject * object, guint prop_id,
98 const GValue * value, GParamSpec * pspec);
99 static void gst_rtp_theora_pay_get_property (GObject * object, guint prop_id,
100 GValue * value, GParamSpec * pspec);
101
102 static void
gst_rtp_theora_pay_class_init(GstRtpTheoraPayClass * klass)103 gst_rtp_theora_pay_class_init (GstRtpTheoraPayClass * klass)
104 {
105 GObjectClass *gobject_class;
106 GstElementClass *gstelement_class;
107 GstRTPBasePayloadClass *gstrtpbasepayload_class;
108
109 gobject_class = (GObjectClass *) klass;
110 gstelement_class = (GstElementClass *) klass;
111 gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
112
113 gstelement_class->change_state = gst_rtp_theora_pay_change_state;
114
115 gstrtpbasepayload_class->set_caps = gst_rtp_theora_pay_setcaps;
116 gstrtpbasepayload_class->handle_buffer = gst_rtp_theora_pay_handle_buffer;
117 gstrtpbasepayload_class->sink_event = gst_rtp_theora_pay_sink_event;
118
119 gobject_class->set_property = gst_rtp_theora_pay_set_property;
120 gobject_class->get_property = gst_rtp_theora_pay_get_property;
121
122 gst_element_class_add_static_pad_template (gstelement_class,
123 &gst_rtp_theora_pay_src_template);
124 gst_element_class_add_static_pad_template (gstelement_class,
125 &gst_rtp_theora_pay_sink_template);
126
127 gst_element_class_set_static_metadata (gstelement_class,
128 "RTP Theora payloader", "Codec/Payloader/Network/RTP",
129 "Payload-encode Theora video into RTP packets (draft-01 RFC XXXX)",
130 "Wim Taymans <wim.taymans@gmail.com>");
131
132 GST_DEBUG_CATEGORY_INIT (rtptheorapay_debug, "rtptheorapay", 0,
133 "Theora RTP Payloader");
134
135 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CONFIG_INTERVAL,
136 g_param_spec_uint ("config-interval", "Config Send Interval",
137 "Send Config Insertion Interval in seconds (configuration headers "
138 "will be multiplexed in the data stream when detected.) (0 = disabled)",
139 0, 3600, DEFAULT_CONFIG_INTERVAL,
140 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
141 );
142 }
143
144 static void
gst_rtp_theora_pay_init(GstRtpTheoraPay * rtptheorapay)145 gst_rtp_theora_pay_init (GstRtpTheoraPay * rtptheorapay)
146 {
147 rtptheorapay->last_config = GST_CLOCK_TIME_NONE;
148 }
149
150 static void
gst_rtp_theora_pay_clear_packet(GstRtpTheoraPay * rtptheorapay)151 gst_rtp_theora_pay_clear_packet (GstRtpTheoraPay * rtptheorapay)
152 {
153 if (rtptheorapay->packet)
154 gst_buffer_unref (rtptheorapay->packet);
155 rtptheorapay->packet = NULL;
156 g_list_free_full (rtptheorapay->packet_buffers,
157 (GDestroyNotify) gst_buffer_unref);
158 rtptheorapay->packet_buffers = NULL;
159 }
160
161 static void
gst_rtp_theora_pay_cleanup(GstRtpTheoraPay * rtptheorapay)162 gst_rtp_theora_pay_cleanup (GstRtpTheoraPay * rtptheorapay)
163 {
164 gst_rtp_theora_pay_clear_packet (rtptheorapay);
165 g_list_free_full (rtptheorapay->headers, (GDestroyNotify) gst_buffer_unref);
166 rtptheorapay->headers = NULL;
167 g_free (rtptheorapay->config_data);
168 rtptheorapay->config_data = NULL;
169 rtptheorapay->last_config = GST_CLOCK_TIME_NONE;
170 }
171
172 static gboolean
gst_rtp_theora_pay_setcaps(GstRTPBasePayload * basepayload,GstCaps * caps)173 gst_rtp_theora_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
174 {
175 GstRtpTheoraPay *rtptheorapay;
176 GstStructure *s;
177 const GValue *array;
178 gint asize, i;
179 GstBuffer *buf;
180 GstMapInfo map;
181
182 rtptheorapay = GST_RTP_THEORA_PAY (basepayload);
183
184 s = gst_caps_get_structure (caps, 0);
185
186 rtptheorapay->need_headers = TRUE;
187
188 if ((array = gst_structure_get_value (s, "streamheader")) == NULL)
189 goto done;
190
191 if (G_VALUE_TYPE (array) != GST_TYPE_ARRAY)
192 goto done;
193
194 if ((asize = gst_value_array_get_size (array)) < 3)
195 goto done;
196
197 for (i = 0; i < asize; i++) {
198 const GValue *value;
199
200 value = gst_value_array_get_value (array, i);
201 if ((buf = gst_value_get_buffer (value)) == NULL)
202 goto null_buffer;
203
204 gst_buffer_map (buf, &map, GST_MAP_READ);
205 /* no data packets allowed */
206 if (map.size < 1)
207 goto invalid_streamheader;
208
209 /* we need packets with id 0x80, 0x81, 0x82 */
210 if (map.data[0] != 0x80 + i)
211 goto invalid_streamheader;
212
213 if (i == 0) {
214 /* identification, we need to parse this in order to get the clock rate. */
215 if (G_UNLIKELY (!gst_rtp_theora_pay_parse_id (basepayload, map.data,
216 map.size)))
217 goto parse_id_failed;
218 }
219 GST_DEBUG_OBJECT (rtptheorapay, "collecting header %d", i);
220 rtptheorapay->headers =
221 g_list_append (rtptheorapay->headers, gst_buffer_ref (buf));
222 gst_buffer_unmap (buf, &map);
223 }
224 if (!gst_rtp_theora_pay_finish_headers (basepayload))
225 goto finish_failed;
226
227 done:
228 return TRUE;
229
230 /* ERRORS */
231 null_buffer:
232 {
233 GST_WARNING_OBJECT (rtptheorapay, "streamheader with null buffer received");
234 return FALSE;
235 }
236 invalid_streamheader:
237 {
238 GST_WARNING_OBJECT (rtptheorapay, "unable to parse initial header");
239 gst_buffer_unmap (buf, &map);
240 return FALSE;
241 }
242 parse_id_failed:
243 {
244 GST_WARNING_OBJECT (rtptheorapay, "unable to parse initial header");
245 gst_buffer_unmap (buf, &map);
246 return FALSE;
247 }
248 finish_failed:
249 {
250 GST_WARNING_OBJECT (rtptheorapay, "unable to finish headers");
251 return FALSE;
252 }
253 }
254
255 static void
gst_rtp_theora_pay_reset_packet(GstRtpTheoraPay * rtptheorapay,guint8 TDT)256 gst_rtp_theora_pay_reset_packet (GstRtpTheoraPay * rtptheorapay, guint8 TDT)
257 {
258 guint payload_len;
259 GstRTPBuffer rtp = { NULL };
260
261 GST_DEBUG_OBJECT (rtptheorapay, "reset packet");
262
263 rtptheorapay->payload_pos = 4;
264 gst_rtp_buffer_map (rtptheorapay->packet, GST_MAP_READ, &rtp);
265 payload_len = gst_rtp_buffer_get_payload_len (&rtp);
266 gst_rtp_buffer_unmap (&rtp);
267 rtptheorapay->payload_left = payload_len - 4;
268 rtptheorapay->payload_duration = 0;
269 rtptheorapay->payload_F = 0;
270 rtptheorapay->payload_TDT = TDT;
271 rtptheorapay->payload_pkts = 0;
272 }
273
274 static void
gst_rtp_theora_pay_init_packet(GstRtpTheoraPay * rtptheorapay,guint8 TDT,GstClockTime timestamp)275 gst_rtp_theora_pay_init_packet (GstRtpTheoraPay * rtptheorapay, guint8 TDT,
276 GstClockTime timestamp)
277 {
278 GST_DEBUG_OBJECT (rtptheorapay, "starting new packet, TDT: %d", TDT);
279
280 gst_rtp_theora_pay_clear_packet (rtptheorapay);
281
282 /* new packet allocate max packet size */
283 rtptheorapay->packet =
284 gst_rtp_buffer_new_allocate_len (GST_RTP_BASE_PAYLOAD_MTU
285 (rtptheorapay), 0, 0);
286 gst_rtp_theora_pay_reset_packet (rtptheorapay, TDT);
287
288 GST_BUFFER_PTS (rtptheorapay->packet) = timestamp;
289 }
290
291 static GstFlowReturn
gst_rtp_theora_pay_flush_packet(GstRtpTheoraPay * rtptheorapay)292 gst_rtp_theora_pay_flush_packet (GstRtpTheoraPay * rtptheorapay)
293 {
294 GstFlowReturn ret;
295 guint8 *payload;
296 guint hlen;
297 GstRTPBuffer rtp = { NULL };
298 GList *l;
299
300 /* check for empty packet */
301 if (!rtptheorapay->packet || rtptheorapay->payload_pos <= 4)
302 return GST_FLOW_OK;
303
304 GST_DEBUG_OBJECT (rtptheorapay, "flushing packet");
305
306 gst_rtp_buffer_map (rtptheorapay->packet, GST_MAP_WRITE, &rtp);
307
308 /* fix header */
309 payload = gst_rtp_buffer_get_payload (&rtp);
310 /*
311 * 0 1 2 3
312 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
313 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
314 * | Ident | F |TDT|# pkts.|
315 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
316 *
317 * F: Fragment type (0=none, 1=start, 2=cont, 3=end)
318 * TDT: Theora data type (0=theora, 1=config, 2=comment, 3=reserved)
319 * pkts: number of packets.
320 */
321 payload[0] = (rtptheorapay->payload_ident >> 16) & 0xff;
322 payload[1] = (rtptheorapay->payload_ident >> 8) & 0xff;
323 payload[2] = (rtptheorapay->payload_ident) & 0xff;
324 payload[3] = (rtptheorapay->payload_F & 0x3) << 6 |
325 (rtptheorapay->payload_TDT & 0x3) << 4 |
326 (rtptheorapay->payload_pkts & 0xf);
327
328 gst_rtp_buffer_unmap (&rtp);
329
330 /* shrink the buffer size to the last written byte */
331 hlen = gst_rtp_buffer_calc_header_len (0);
332 gst_buffer_resize (rtptheorapay->packet, 0, hlen + rtptheorapay->payload_pos);
333
334 GST_BUFFER_DURATION (rtptheorapay->packet) = rtptheorapay->payload_duration;
335
336 for (l = g_list_last (rtptheorapay->packet_buffers); l; l = l->prev) {
337 GstBuffer *buf = GST_BUFFER_CAST (l->data);
338 gst_rtp_copy_video_meta (rtptheorapay, rtptheorapay->packet, buf);
339 gst_buffer_unref (buf);
340 }
341 g_list_free (rtptheorapay->packet_buffers);
342 rtptheorapay->packet_buffers = NULL;
343
344 /* push, this gives away our ref to the packet, so clear it. */
345 ret =
346 gst_rtp_base_payload_push (GST_RTP_BASE_PAYLOAD (rtptheorapay),
347 rtptheorapay->packet);
348 rtptheorapay->packet = NULL;
349
350 return ret;
351 }
352
353 static gboolean
gst_rtp_theora_pay_finish_headers(GstRTPBasePayload * basepayload)354 gst_rtp_theora_pay_finish_headers (GstRTPBasePayload * basepayload)
355 {
356 GstRtpTheoraPay *rtptheorapay = GST_RTP_THEORA_PAY (basepayload);
357 GList *walk;
358 guint length, size, n_headers, configlen, extralen;
359 gchar *wstr, *hstr, *configuration;
360 guint8 *data, *config;
361 guint32 ident;
362 gboolean res;
363 const gchar *sampling = NULL;
364
365 GST_DEBUG_OBJECT (rtptheorapay, "finish headers");
366
367 if (!rtptheorapay->headers) {
368 GST_DEBUG_OBJECT (rtptheorapay, "We need 2 headers but have none");
369 goto no_headers;
370 }
371
372 /* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
373 * | Number of packed headers |
374 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
375 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
376 * | Packed header |
377 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
378 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
379 * | Packed header |
380 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
381 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
382 * | .... |
383 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
384 *
385 * We only construct a config containing 1 packed header like this:
386 *
387 * 0 1 2 3
388 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
389 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
390 * | Ident | length ..
391 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
392 * .. | n. of headers | length1 | length2 ..
393 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
394 * .. | Identification Header ..
395 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
396 * .................................................................
397 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
398 * .. | Comment Header ..
399 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
400 * .................................................................
401 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
402 * .. Comment Header |
403 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
404 * | Setup Header ..
405 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
406 * .................................................................
407 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
408 * .. Setup Header |
409 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
410 */
411
412 /* we need 4 bytes for the number of headers (which is always 1), 3 bytes for
413 * the ident, 2 bytes for length, 1 byte for n. of headers. */
414 size = 4 + 3 + 2 + 1;
415
416 /* count the size of the headers first and update the hash */
417 length = 0;
418 n_headers = 0;
419 ident = fnv1_hash_32_new ();
420 extralen = 1;
421 for (walk = rtptheorapay->headers; walk; walk = g_list_next (walk)) {
422 GstBuffer *buf = GST_BUFFER_CAST (walk->data);
423 GstMapInfo map;
424 guint bsize;
425
426 bsize = gst_buffer_get_size (buf);
427 length += bsize;
428 n_headers++;
429
430 /* count number of bytes needed for length fields, we don't need this for
431 * the last header. */
432 if (g_list_next (walk)) {
433 do {
434 size++;
435 extralen++;
436 bsize >>= 7;
437 } while (bsize);
438 }
439 /* update hash */
440 gst_buffer_map (buf, &map, GST_MAP_READ);
441 ident = fnv1_hash_32_update (ident, map.data, map.size);
442 gst_buffer_unmap (buf, &map);
443 }
444
445 /* packet length is header size + packet length */
446 configlen = size + length;
447 config = data = g_malloc (configlen);
448
449 /* number of packed headers, we only pack 1 header */
450 data[0] = 0;
451 data[1] = 0;
452 data[2] = 0;
453 data[3] = 1;
454
455 ident = fnv1_hash_32_to_24 (ident);
456 rtptheorapay->payload_ident = ident;
457 GST_DEBUG_OBJECT (rtptheorapay, "ident 0x%08x", ident);
458
459 /* take lower 3 bytes */
460 data[4] = (ident >> 16) & 0xff;
461 data[5] = (ident >> 8) & 0xff;
462 data[6] = ident & 0xff;
463
464 /* store length of all theora headers */
465 data[7] = ((length) >> 8) & 0xff;
466 data[8] = (length) & 0xff;
467
468 /* store number of headers minus one. */
469 data[9] = n_headers - 1;
470 data += 10;
471
472 /* store length for each header */
473 for (walk = rtptheorapay->headers; walk; walk = g_list_next (walk)) {
474 GstBuffer *buf = GST_BUFFER_CAST (walk->data);
475 guint bsize, size, temp;
476 guint flag;
477
478 /* only need to store the length when it's not the last header */
479 if (!g_list_next (walk))
480 break;
481
482 bsize = gst_buffer_get_size (buf);
483
484 /* calc size */
485 size = 0;
486 do {
487 size++;
488 bsize >>= 7;
489 } while (bsize);
490 temp = size;
491
492 bsize = gst_buffer_get_size (buf);
493 /* write the size backwards */
494 flag = 0;
495 while (size) {
496 size--;
497 data[size] = (bsize & 0x7f) | flag;
498 bsize >>= 7;
499 flag = 0x80; /* Flag bit on all bytes of the length except the last */
500 }
501 data += temp;
502 }
503
504 /* copy header data */
505 for (walk = rtptheorapay->headers; walk; walk = g_list_next (walk)) {
506 GstBuffer *buf = GST_BUFFER_CAST (walk->data);
507
508 gst_buffer_extract (buf, 0, data, gst_buffer_get_size (buf));
509 data += gst_buffer_get_size (buf);
510 }
511 rtptheorapay->need_headers = FALSE;
512
513 /* serialize to base64 */
514 configuration = g_base64_encode (config, configlen);
515
516 /* store for later re-sending */
517 g_free (rtptheorapay->config_data);
518 rtptheorapay->config_size = configlen - 4 - 3 - 2;
519 rtptheorapay->config_data = g_malloc (rtptheorapay->config_size);
520 rtptheorapay->config_extra_len = extralen;
521 memcpy (rtptheorapay->config_data, config + 4 + 3 + 2,
522 rtptheorapay->config_size);
523
524 g_free (config);
525
526 /* configure payloader settings */
527 switch (rtptheorapay->pixel_format) {
528 case 2:
529 sampling = "YCbCr-4:2:2";
530 break;
531 case 3:
532 sampling = "YCbCr-4:4:4";
533 break;
534 case 0:
535 default:
536 sampling = "YCbCr-4:2:0";
537 break;
538 }
539
540
541 wstr = g_strdup_printf ("%d", rtptheorapay->width);
542 hstr = g_strdup_printf ("%d", rtptheorapay->height);
543 gst_rtp_base_payload_set_options (basepayload, "video", TRUE, "THEORA",
544 90000);
545 res =
546 gst_rtp_base_payload_set_outcaps (basepayload, "sampling", G_TYPE_STRING,
547 sampling, "width", G_TYPE_STRING, wstr, "height", G_TYPE_STRING,
548 hstr, "configuration", G_TYPE_STRING, configuration, "delivery-method",
549 G_TYPE_STRING, "inline",
550 /* don't set the other defaults
551 */
552 NULL);
553 g_free (wstr);
554 g_free (hstr);
555 g_free (configuration);
556
557 return res;
558
559 /* ERRORS */
560 no_headers:
561 {
562 GST_DEBUG_OBJECT (rtptheorapay, "finish headers");
563 return FALSE;
564 }
565 }
566
567 static gboolean
gst_rtp_theora_pay_parse_id(GstRTPBasePayload * basepayload,guint8 * data,guint size)568 gst_rtp_theora_pay_parse_id (GstRTPBasePayload * basepayload, guint8 * data,
569 guint size)
570 {
571 GstRtpTheoraPay *rtptheorapay;
572 gint width, height, pixel_format;
573
574 rtptheorapay = GST_RTP_THEORA_PAY (basepayload);
575
576 if (G_UNLIKELY (size < 42))
577 goto too_short;
578
579 if (G_UNLIKELY (memcmp (data, "\200theora", 7)))
580 goto invalid_start;
581 data += 7;
582
583 if (G_UNLIKELY (data[0] != 3))
584 goto invalid_version;
585 if (G_UNLIKELY (data[1] != 2))
586 goto invalid_version;
587 data += 3;
588
589 width = GST_READ_UINT16_BE (data) << 4;
590 data += 2;
591 height = GST_READ_UINT16_BE (data) << 4;
592 data += 29;
593
594 pixel_format = (GST_READ_UINT8 (data) >> 3) & 0x03;
595
596 /* store values */
597 rtptheorapay->pixel_format = pixel_format;
598 rtptheorapay->width = width;
599 rtptheorapay->height = height;
600
601 return TRUE;
602
603 /* ERRORS */
604 too_short:
605 {
606 GST_ELEMENT_ERROR (basepayload, STREAM, DECODE,
607 (NULL),
608 ("Identification packet is too short, need at least 42, got %d", size));
609 return FALSE;
610 }
611 invalid_start:
612 {
613 GST_ELEMENT_ERROR (basepayload, STREAM, DECODE,
614 (NULL), ("Invalid header start in identification packet"));
615 return FALSE;
616 }
617 invalid_version:
618 {
619 GST_ELEMENT_ERROR (basepayload, STREAM, DECODE,
620 (NULL), ("Invalid version"));
621 return FALSE;
622 }
623 }
624
625 static GstFlowReturn
gst_rtp_theora_pay_payload_buffer(GstRtpTheoraPay * rtptheorapay,guint8 TDT,GstBuffer * buffer,guint8 * data,guint size,GstClockTime timestamp,GstClockTime duration,guint not_in_length)626 gst_rtp_theora_pay_payload_buffer (GstRtpTheoraPay * rtptheorapay, guint8 TDT,
627 GstBuffer * buffer, guint8 * data, guint size, GstClockTime timestamp,
628 GstClockTime duration, guint not_in_length)
629 {
630 GstFlowReturn ret = GST_FLOW_OK;
631 guint newsize;
632 guint packet_len;
633 GstClockTime newduration;
634 gboolean flush;
635 guint plen;
636 guint8 *ppos, *payload;
637 gboolean fragmented;
638 GstRTPBuffer rtp = { NULL };
639
640 /* size increases with packet length and 2 bytes size eader. */
641 newduration = rtptheorapay->payload_duration;
642 if (duration != GST_CLOCK_TIME_NONE)
643 newduration += duration;
644
645 newsize = rtptheorapay->payload_pos + 2 + size;
646 packet_len = gst_rtp_buffer_calc_packet_len (newsize, 0, 0);
647
648 /* check buffer filled against length and max latency */
649 flush = gst_rtp_base_payload_is_filled (GST_RTP_BASE_PAYLOAD (rtptheorapay),
650 packet_len, newduration);
651 /* we can store up to 15 theora packets in one RTP packet. */
652 flush |= (rtptheorapay->payload_pkts == 15);
653 /* flush if we have a new TDT */
654 if (rtptheorapay->packet)
655 flush |= (rtptheorapay->payload_TDT != TDT);
656 if (flush)
657 ret = gst_rtp_theora_pay_flush_packet (rtptheorapay);
658
659 if (ret != GST_FLOW_OK)
660 goto done;
661
662 /* create new packet if we must */
663 if (!rtptheorapay->packet) {
664 gst_rtp_theora_pay_init_packet (rtptheorapay, TDT, timestamp);
665 }
666
667 gst_rtp_buffer_map (rtptheorapay->packet, GST_MAP_WRITE, &rtp);
668 payload = gst_rtp_buffer_get_payload (&rtp);
669 ppos = payload + rtptheorapay->payload_pos;
670 fragmented = FALSE;
671
672 /* put buffer in packet, it either fits completely or needs to be fragmented
673 * over multiple RTP packets. */
674 do {
675 plen = MIN (rtptheorapay->payload_left - 2, size);
676
677 GST_DEBUG_OBJECT (rtptheorapay, "append %u bytes", plen);
678
679 /* data is copied in the payload with a 2 byte length header */
680 ppos[0] = ((plen - not_in_length) >> 8) & 0xff;
681 ppos[1] = ((plen - not_in_length) & 0xff);
682 if (plen)
683 memcpy (&ppos[2], data, plen);
684
685 if (buffer) {
686 if (!rtptheorapay->packet_buffers
687 || rtptheorapay->packet_buffers->data != (gpointer) buffer)
688 rtptheorapay->packet_buffers =
689 g_list_prepend (rtptheorapay->packet_buffers,
690 gst_buffer_ref (buffer));
691 } else {
692 GList *l;
693
694 for (l = rtptheorapay->headers; l; l = l->next)
695 rtptheorapay->packet_buffers =
696 g_list_prepend (rtptheorapay->packet_buffers,
697 gst_buffer_ref (l->data));
698 }
699
700 /* only first (only) configuration cuts length field */
701 /* NOTE: spec (if any) is not clear on this ... */
702 not_in_length = 0;
703
704 size -= plen;
705 data += plen;
706
707 rtptheorapay->payload_pos += plen + 2;
708 rtptheorapay->payload_left -= plen + 2;
709
710 if (fragmented) {
711 if (size == 0)
712 /* last fragment, set F to 0x3. */
713 rtptheorapay->payload_F = 0x3;
714 else
715 /* fragment continues, set F to 0x2. */
716 rtptheorapay->payload_F = 0x2;
717 } else {
718 if (size > 0) {
719 /* fragmented packet starts, set F to 0x1, mark ourselves as
720 * fragmented. */
721 rtptheorapay->payload_F = 0x1;
722 fragmented = TRUE;
723 }
724 }
725 if (fragmented) {
726 gst_rtp_buffer_unmap (&rtp);
727 /* fragmented packets are always flushed and have ptks of 0 */
728 rtptheorapay->payload_pkts = 0;
729 ret = gst_rtp_theora_pay_flush_packet (rtptheorapay);
730
731 if (size > 0) {
732 /* start new packet and get pointers. TDT stays the same. */
733 gst_rtp_theora_pay_init_packet (rtptheorapay,
734 rtptheorapay->payload_TDT, timestamp);
735 gst_rtp_buffer_map (rtptheorapay->packet, GST_MAP_WRITE, &rtp);
736 payload = gst_rtp_buffer_get_payload (&rtp);
737 ppos = payload + rtptheorapay->payload_pos;
738 }
739 } else {
740 /* unfragmented packet, update stats for next packet, size == 0 and we
741 * exit the while loop */
742 rtptheorapay->payload_pkts++;
743 if (duration != GST_CLOCK_TIME_NONE)
744 rtptheorapay->payload_duration += duration;
745 }
746 } while (size && ret == GST_FLOW_OK);
747
748 if (rtp.buffer)
749 gst_rtp_buffer_unmap (&rtp);
750 done:
751
752 return ret;
753 }
754
755 static GstFlowReturn
gst_rtp_theora_pay_handle_buffer(GstRTPBasePayload * basepayload,GstBuffer * buffer)756 gst_rtp_theora_pay_handle_buffer (GstRTPBasePayload * basepayload,
757 GstBuffer * buffer)
758 {
759 GstRtpTheoraPay *rtptheorapay;
760 GstFlowReturn ret;
761 GstMapInfo map;
762 gsize size;
763 guint8 *data;
764 GstClockTime duration, timestamp;
765 guint8 TDT;
766 gboolean keyframe = FALSE;
767
768 rtptheorapay = GST_RTP_THEORA_PAY (basepayload);
769
770 gst_buffer_map (buffer, &map, GST_MAP_READ);
771 data = map.data;
772 size = map.size;
773 duration = GST_BUFFER_DURATION (buffer);
774 timestamp = GST_BUFFER_PTS (buffer);
775
776 GST_DEBUG_OBJECT (rtptheorapay, "size %" G_GSIZE_FORMAT
777 ", duration %" GST_TIME_FORMAT, size, GST_TIME_ARGS (duration));
778
779 /* find packet type */
780 if (size == 0) {
781 TDT = 0;
782 keyframe = FALSE;
783 } else if (data[0] & 0x80) {
784 /* header */
785 if (data[0] == 0x80) {
786 /* identification, we need to parse this in order to get the clock rate.
787 */
788 if (G_UNLIKELY (!gst_rtp_theora_pay_parse_id (basepayload, data, size)))
789 goto parse_id_failed;
790 TDT = 1;
791 } else if (data[0] == 0x81) {
792 /* comment */
793 TDT = 2;
794 } else if (data[0] == 0x82) {
795 /* setup */
796 TDT = 1;
797 } else
798 goto unknown_header;
799 } else {
800 /* data */
801 TDT = 0;
802 keyframe = ((data[0] & 0x40) == 0);
803 }
804
805 /* we need to collect the headers and construct a config string from them */
806 if (TDT != 0) {
807 GST_DEBUG_OBJECT (rtptheorapay, "collecting header, buffer %p", buffer);
808 /* append header to the list of headers */
809 gst_buffer_unmap (buffer, &map);
810 rtptheorapay->headers = g_list_append (rtptheorapay->headers, buffer);
811 ret = GST_FLOW_OK;
812 goto done;
813 } else if (rtptheorapay->headers && rtptheorapay->need_headers) {
814 if (!gst_rtp_theora_pay_finish_headers (basepayload))
815 goto header_error;
816 }
817
818 /* there is a config request, see if we need to insert it */
819 if (keyframe && (rtptheorapay->config_interval > 0) &&
820 rtptheorapay->config_data) {
821 gboolean send_config = FALSE;
822 GstClockTime running_time =
823 gst_segment_to_running_time (&basepayload->segment, GST_FORMAT_TIME,
824 timestamp);
825
826 if (rtptheorapay->last_config != -1) {
827 guint64 diff;
828
829 GST_LOG_OBJECT (rtptheorapay,
830 "now %" GST_TIME_FORMAT ", last VOP-I %" GST_TIME_FORMAT,
831 GST_TIME_ARGS (running_time),
832 GST_TIME_ARGS (rtptheorapay->last_config));
833
834 /* calculate diff between last config in milliseconds */
835 if (running_time > rtptheorapay->last_config) {
836 diff = running_time - rtptheorapay->last_config;
837 } else {
838 diff = 0;
839 }
840
841 GST_DEBUG_OBJECT (rtptheorapay,
842 "interval since last config %" GST_TIME_FORMAT, GST_TIME_ARGS (diff));
843
844 /* bigger than interval, queue config */
845 if (GST_TIME_AS_SECONDS (diff) >= rtptheorapay->config_interval) {
846 GST_DEBUG_OBJECT (rtptheorapay, "time to send config");
847 send_config = TRUE;
848 }
849 } else {
850 /* no known previous config time, send now */
851 GST_DEBUG_OBJECT (rtptheorapay, "no previous config time, send now");
852 send_config = TRUE;
853 }
854
855 if (send_config) {
856 /* we need to send config now first */
857 /* different TDT type forces flush */
858 gst_rtp_theora_pay_payload_buffer (rtptheorapay, 1,
859 NULL, rtptheorapay->config_data, rtptheorapay->config_size,
860 timestamp, GST_CLOCK_TIME_NONE, rtptheorapay->config_extra_len);
861
862 if (running_time != -1) {
863 rtptheorapay->last_config = running_time;
864 }
865 }
866 }
867
868 ret =
869 gst_rtp_theora_pay_payload_buffer (rtptheorapay, TDT, buffer, data, size,
870 timestamp, duration, 0);
871
872 gst_buffer_unmap (buffer, &map);
873 gst_buffer_unref (buffer);
874
875 done:
876 return ret;
877
878 /* ERRORS */
879 parse_id_failed:
880 {
881 gst_buffer_unmap (buffer, &map);
882 gst_buffer_unref (buffer);
883 return GST_FLOW_ERROR;
884 }
885 unknown_header:
886 {
887 GST_ELEMENT_WARNING (rtptheorapay, STREAM, DECODE,
888 (NULL), ("Ignoring unknown header received"));
889 gst_buffer_unmap (buffer, &map);
890 gst_buffer_unref (buffer);
891 return GST_FLOW_OK;
892 }
893 header_error:
894 {
895 GST_ELEMENT_WARNING (rtptheorapay, STREAM, DECODE,
896 (NULL), ("Error initializing header config"));
897 gst_buffer_unmap (buffer, &map);
898 gst_buffer_unref (buffer);
899 return GST_FLOW_OK;
900 }
901 }
902
903 static gboolean
gst_rtp_theora_pay_sink_event(GstRTPBasePayload * payload,GstEvent * event)904 gst_rtp_theora_pay_sink_event (GstRTPBasePayload * payload, GstEvent * event)
905 {
906 GstRtpTheoraPay *rtptheorapay = GST_RTP_THEORA_PAY (payload);
907
908 switch (GST_EVENT_TYPE (event)) {
909 case GST_EVENT_FLUSH_STOP:
910 gst_rtp_theora_pay_clear_packet (rtptheorapay);
911 break;
912 default:
913 break;
914 }
915 /* false to let parent handle event as well */
916 return GST_RTP_BASE_PAYLOAD_CLASS (parent_class)->sink_event (payload, event);
917 }
918
919 static GstStateChangeReturn
gst_rtp_theora_pay_change_state(GstElement * element,GstStateChange transition)920 gst_rtp_theora_pay_change_state (GstElement * element,
921 GstStateChange transition)
922 {
923 GstRtpTheoraPay *rtptheorapay;
924
925 GstStateChangeReturn ret;
926
927 rtptheorapay = GST_RTP_THEORA_PAY (element);
928
929 switch (transition) {
930 default:
931 break;
932 }
933
934 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
935
936 switch (transition) {
937 case GST_STATE_CHANGE_PAUSED_TO_READY:
938 gst_rtp_theora_pay_cleanup (rtptheorapay);
939 break;
940 default:
941 break;
942 }
943 return ret;
944 }
945
946 static void
gst_rtp_theora_pay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)947 gst_rtp_theora_pay_set_property (GObject * object, guint prop_id,
948 const GValue * value, GParamSpec * pspec)
949 {
950 GstRtpTheoraPay *rtptheorapay;
951
952 rtptheorapay = GST_RTP_THEORA_PAY (object);
953
954 switch (prop_id) {
955 case PROP_CONFIG_INTERVAL:
956 rtptheorapay->config_interval = g_value_get_uint (value);
957 break;
958 default:
959 break;
960 }
961 }
962
963 static void
gst_rtp_theora_pay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)964 gst_rtp_theora_pay_get_property (GObject * object, guint prop_id,
965 GValue * value, GParamSpec * pspec)
966 {
967 GstRtpTheoraPay *rtptheorapay;
968
969 rtptheorapay = GST_RTP_THEORA_PAY (object);
970
971 switch (prop_id) {
972 case PROP_CONFIG_INTERVAL:
973 g_value_set_uint (value, rtptheorapay->config_interval);
974 break;
975 default:
976 break;
977 }
978 }
979
980 gboolean
gst_rtp_theora_pay_plugin_init(GstPlugin * plugin)981 gst_rtp_theora_pay_plugin_init (GstPlugin * plugin)
982 {
983 return gst_element_register (plugin, "rtptheorapay",
984 GST_RANK_SECONDARY, GST_TYPE_RTP_THEORA_PAY);
985 }
986