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