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