1 /* gstrtpvp8depay.c - Source for GstRtpVP8Depay
2 * Copyright (C) 2011 Sjoerd Simons <sjoerd@luon.net>
3 * Copyright (C) 2011 Collabora Ltd.
4 * Contact: Youness Alaoui <youness.alaoui@collabora.co.uk>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include "gstrtpelements.h"
26 #include "gstrtpvp8depay.h"
27 #include "gstrtputils.h"
28
29 #include <gst/video/video.h>
30
31 #include <stdio.h>
32
33 GST_DEBUG_CATEGORY_STATIC (gst_rtp_vp8_depay_debug);
34 #define GST_CAT_DEFAULT gst_rtp_vp8_depay_debug
35
36 static void gst_rtp_vp8_depay_dispose (GObject * object);
37 static void gst_rtp_vp8_depay_get_property (GObject * object, guint prop_id,
38 GValue * value, GParamSpec * pspec);
39 static void gst_rtp_vp8_depay_set_property (GObject * object, guint prop_id,
40 const GValue * value, GParamSpec * pspec);
41 static GstBuffer *gst_rtp_vp8_depay_process (GstRTPBaseDepayload * depayload,
42 GstRTPBuffer * rtp);
43 static GstStateChangeReturn gst_rtp_vp8_depay_change_state (GstElement *
44 element, GstStateChange transition);
45 static gboolean gst_rtp_vp8_depay_handle_event (GstRTPBaseDepayload * depay,
46 GstEvent * event);
47 static gboolean gst_rtp_vp8_depay_packet_lost (GstRTPBaseDepayload * depay,
48 GstEvent * event);
49
50 G_DEFINE_TYPE (GstRtpVP8Depay, gst_rtp_vp8_depay, GST_TYPE_RTP_BASE_DEPAYLOAD);
51 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (rtpvp8depay, "rtpvp8depay",
52 GST_RANK_MARGINAL, GST_TYPE_RTP_VP8_DEPAY, rtp_element_init (plugin));
53
54 static GstStaticPadTemplate gst_rtp_vp8_depay_src_template =
55 GST_STATIC_PAD_TEMPLATE ("src",
56 GST_PAD_SRC,
57 GST_PAD_ALWAYS,
58 GST_STATIC_CAPS ("video/x-vp8"));
59
60 static GstStaticPadTemplate gst_rtp_vp8_depay_sink_template =
61 GST_STATIC_PAD_TEMPLATE ("sink",
62 GST_PAD_SINK,
63 GST_PAD_ALWAYS,
64 GST_STATIC_CAPS ("application/x-rtp, "
65 "clock-rate = (int) 90000,"
66 "media = (string) \"video\","
67 "encoding-name = (string) { \"VP8\", \"VP8-DRAFT-IETF-01\" }"));
68
69 #define DEFAULT_WAIT_FOR_KEYFRAME FALSE
70 #define DEFAULT_REQUEST_KEYFRAME FALSE
71
72 enum
73 {
74 PROP_0,
75 PROP_WAIT_FOR_KEYFRAME,
76 PROP_REQUEST_KEYFRAME,
77 };
78
79 #define PICTURE_ID_NONE (UINT_MAX)
80 #define IS_PICTURE_ID_15BITS(pid) (((guint)(pid) & 0x8000) != 0)
81
82 static void
gst_rtp_vp8_depay_init(GstRtpVP8Depay * self)83 gst_rtp_vp8_depay_init (GstRtpVP8Depay * self)
84 {
85 self->adapter = gst_adapter_new ();
86 self->started = FALSE;
87 self->wait_for_keyframe = DEFAULT_WAIT_FOR_KEYFRAME;
88 self->request_keyframe = DEFAULT_REQUEST_KEYFRAME;
89 self->last_pushed_was_lost_event = FALSE;
90 }
91
92 static void
gst_rtp_vp8_depay_class_init(GstRtpVP8DepayClass * gst_rtp_vp8_depay_class)93 gst_rtp_vp8_depay_class_init (GstRtpVP8DepayClass * gst_rtp_vp8_depay_class)
94 {
95 GObjectClass *object_class = G_OBJECT_CLASS (gst_rtp_vp8_depay_class);
96 GstElementClass *element_class = GST_ELEMENT_CLASS (gst_rtp_vp8_depay_class);
97 GstRTPBaseDepayloadClass *depay_class =
98 (GstRTPBaseDepayloadClass *) (gst_rtp_vp8_depay_class);
99
100 gst_element_class_add_static_pad_template (element_class,
101 &gst_rtp_vp8_depay_sink_template);
102 gst_element_class_add_static_pad_template (element_class,
103 &gst_rtp_vp8_depay_src_template);
104
105 gst_element_class_set_static_metadata (element_class, "RTP VP8 depayloader",
106 "Codec/Depayloader/Network/RTP",
107 "Extracts VP8 video from RTP packets)",
108 "Sjoerd Simons <sjoerd@luon.net>");
109
110 object_class->dispose = gst_rtp_vp8_depay_dispose;
111 object_class->set_property = gst_rtp_vp8_depay_set_property;
112 object_class->get_property = gst_rtp_vp8_depay_get_property;
113
114 g_object_class_install_property (object_class, PROP_WAIT_FOR_KEYFRAME,
115 g_param_spec_boolean ("wait-for-keyframe", "Wait for Keyframe",
116 "Wait for the next keyframe after packet loss",
117 DEFAULT_WAIT_FOR_KEYFRAME,
118 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
119
120 /**
121 * GstRtpVP8Depay:request-keyframe:
122 *
123 * Request new keyframe when packet loss is detected
124 *
125 * Since: 1.20
126 */
127 g_object_class_install_property (object_class, PROP_REQUEST_KEYFRAME,
128 g_param_spec_boolean ("request-keyframe", "Request Keyframe",
129 "Request new keyframe when packet loss is detected",
130 DEFAULT_REQUEST_KEYFRAME,
131 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
132
133 element_class->change_state = gst_rtp_vp8_depay_change_state;
134
135 depay_class->process_rtp_packet = gst_rtp_vp8_depay_process;
136 depay_class->handle_event = gst_rtp_vp8_depay_handle_event;
137 depay_class->packet_lost = gst_rtp_vp8_depay_packet_lost;
138
139 GST_DEBUG_CATEGORY_INIT (gst_rtp_vp8_depay_debug, "rtpvp8depay", 0,
140 "VP8 Video RTP Depayloader");
141 }
142
143 static void
gst_rtp_vp8_depay_dispose(GObject * object)144 gst_rtp_vp8_depay_dispose (GObject * object)
145 {
146 GstRtpVP8Depay *self = GST_RTP_VP8_DEPAY (object);
147
148 if (self->adapter != NULL)
149 g_object_unref (self->adapter);
150 self->adapter = NULL;
151
152 /* release any references held by the object here */
153
154 if (G_OBJECT_CLASS (gst_rtp_vp8_depay_parent_class)->dispose)
155 G_OBJECT_CLASS (gst_rtp_vp8_depay_parent_class)->dispose (object);
156 }
157
158 static void
gst_rtp_vp8_depay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)159 gst_rtp_vp8_depay_set_property (GObject * object, guint prop_id,
160 const GValue * value, GParamSpec * pspec)
161 {
162 GstRtpVP8Depay *self = GST_RTP_VP8_DEPAY (object);
163
164 switch (prop_id) {
165 case PROP_WAIT_FOR_KEYFRAME:
166 self->wait_for_keyframe = g_value_get_boolean (value);
167 break;
168 case PROP_REQUEST_KEYFRAME:
169 self->request_keyframe = g_value_get_boolean (value);
170 break;
171 default:
172 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
173 break;
174 }
175 }
176
177 static void
gst_rtp_vp8_depay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)178 gst_rtp_vp8_depay_get_property (GObject * object, guint prop_id,
179 GValue * value, GParamSpec * pspec)
180 {
181 GstRtpVP8Depay *self = GST_RTP_VP8_DEPAY (object);
182
183 switch (prop_id) {
184 case PROP_WAIT_FOR_KEYFRAME:
185 g_value_set_boolean (value, self->wait_for_keyframe);
186 break;
187 case PROP_REQUEST_KEYFRAME:
188 g_value_set_boolean (value, self->request_keyframe);
189 break;
190 default:
191 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
192 break;
193 }
194 }
195
196 static gint
picture_id_compare(guint16 id0,guint16 id1)197 picture_id_compare (guint16 id0, guint16 id1)
198 {
199 guint shift = 16 - (IS_PICTURE_ID_15BITS (id1) ? 15 : 7);
200 id0 = id0 << shift;
201 id1 = id1 << shift;
202 return ((gint16) (id1 - id0)) >> shift;
203 }
204
205 static void
send_last_lost_event(GstRtpVP8Depay * self)206 send_last_lost_event (GstRtpVP8Depay * self)
207 {
208 if (self->last_lost_event) {
209 GST_ERROR_OBJECT (self,
210 "Sending the last stopped lost event: %" GST_PTR_FORMAT,
211 self->last_lost_event);
212 GST_RTP_BASE_DEPAYLOAD_CLASS (gst_rtp_vp8_depay_parent_class)
213 ->packet_lost (GST_RTP_BASE_DEPAYLOAD_CAST (self),
214 self->last_lost_event);
215 gst_event_replace (&self->last_lost_event, NULL);
216 self->last_pushed_was_lost_event = TRUE;
217 }
218 }
219
220 static void
send_new_lost_event(GstRtpVP8Depay * self,GstClockTime timestamp,guint new_picture_id,const gchar * reason)221 send_new_lost_event (GstRtpVP8Depay * self, GstClockTime timestamp,
222 guint new_picture_id, const gchar * reason)
223 {
224 GstEvent *event;
225
226 if (!GST_CLOCK_TIME_IS_VALID (timestamp)) {
227 GST_WARNING_OBJECT (self, "Can't create lost event with invalid timestmap");
228 return;
229 }
230
231 event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
232 gst_structure_new ("GstRTPPacketLost",
233 "timestamp", G_TYPE_UINT64, timestamp,
234 "duration", G_TYPE_UINT64, G_GUINT64_CONSTANT (0), NULL));
235
236 GST_DEBUG_OBJECT (self, "Pushing lost event "
237 "(picids 0x%x 0x%x, reason \"%s\"): %" GST_PTR_FORMAT,
238 self->last_picture_id, new_picture_id, reason, event);
239
240 GST_RTP_BASE_DEPAYLOAD_CLASS (gst_rtp_vp8_depay_parent_class)
241 ->packet_lost (GST_RTP_BASE_DEPAYLOAD_CAST (self), event);
242
243 gst_event_unref (event);
244 }
245
246 static void
send_last_lost_event_if_needed(GstRtpVP8Depay * self,guint new_picture_id)247 send_last_lost_event_if_needed (GstRtpVP8Depay * self, guint new_picture_id)
248 {
249 if (self->last_picture_id == PICTURE_ID_NONE)
250 return;
251
252 if (self->last_lost_event) {
253 gboolean send_lost_event = FALSE;
254 if (new_picture_id == PICTURE_ID_NONE) {
255 GST_DEBUG_OBJECT (self, "Dropping the last stopped lost event "
256 "(picture id does not exist): %" GST_PTR_FORMAT,
257 self->last_lost_event);
258 } else if (IS_PICTURE_ID_15BITS (self->last_picture_id) &&
259 !IS_PICTURE_ID_15BITS (new_picture_id)) {
260 GST_DEBUG_OBJECT (self, "Dropping the last stopped lost event "
261 "(picture id has less bits than before): %" GST_PTR_FORMAT,
262 self->last_lost_event);
263 } else if (picture_id_compare (self->last_picture_id, new_picture_id) != 1) {
264 GstStructure *s = gst_event_writable_structure (self->last_lost_event);
265
266 GST_DEBUG_OBJECT (self, "Sending the last stopped lost event "
267 "(gap in picture id %u %u): %" GST_PTR_FORMAT,
268 self->last_picture_id, new_picture_id, self->last_lost_event);
269 send_lost_event = TRUE;
270 /* Prevent rtpbasedepayload from dropping the event now
271 * that we have made sure the lost packet was not FEC */
272 gst_structure_remove_field (s, "might-have-been-fec");
273 }
274 if (send_lost_event)
275 GST_RTP_BASE_DEPAYLOAD_CLASS (gst_rtp_vp8_depay_parent_class)
276 ->packet_lost (GST_RTP_BASE_DEPAYLOAD_CAST (self),
277 self->last_lost_event);
278
279 gst_event_replace (&self->last_lost_event, NULL);
280 }
281 }
282
283 static GstBuffer *
gst_rtp_vp8_depay_process(GstRTPBaseDepayload * depay,GstRTPBuffer * rtp)284 gst_rtp_vp8_depay_process (GstRTPBaseDepayload * depay, GstRTPBuffer * rtp)
285 {
286 GstRtpVP8Depay *self = GST_RTP_VP8_DEPAY (depay);
287 GstBuffer *payload;
288 guint8 *data;
289 guint hdrsize = 1;
290 guint picture_id = PICTURE_ID_NONE;
291 guint size = gst_rtp_buffer_get_payload_len (rtp);
292 guint s_bit;
293 guint part_id;
294 gboolean frame_start;
295 gboolean sent_lost_event = FALSE;
296
297 if (G_UNLIKELY (GST_BUFFER_IS_DISCONT (rtp->buffer))) {
298 GST_DEBUG_OBJECT (self, "Discontinuity, flushing adapter");
299 gst_adapter_clear (self->adapter);
300 self->started = FALSE;
301
302 if (self->wait_for_keyframe)
303 self->waiting_for_keyframe = TRUE;
304
305 if (self->request_keyframe)
306 gst_pad_push_event (GST_RTP_BASE_DEPAYLOAD_SINKPAD (depay),
307 gst_video_event_new_upstream_force_key_unit (GST_CLOCK_TIME_NONE,
308 TRUE, 0));
309 }
310
311 /* At least one header and one vp8 byte */
312 if (G_UNLIKELY (size < 2))
313 goto too_small;
314
315 data = gst_rtp_buffer_get_payload (rtp);
316
317 s_bit = (data[0] >> 4) & 0x1;
318 part_id = (data[0] >> 0) & 0x7;
319
320 /* Check X optional header */
321 if ((data[0] & 0x80) != 0) {
322 hdrsize++;
323 /* Check I optional header */
324 if ((data[1] & 0x80) != 0) {
325 if (G_UNLIKELY (size < 3))
326 goto too_small;
327 hdrsize++;
328 /* Check for 16 bits PictureID */
329 picture_id = data[2];
330 if ((data[2] & 0x80) != 0) {
331 if (G_UNLIKELY (size < 4))
332 goto too_small;
333 hdrsize++;
334 picture_id = (picture_id << 8) | data[3];
335 }
336 }
337 /* Check L optional header */
338 if ((data[1] & 0x40) != 0)
339 hdrsize++;
340 /* Check T or K optional headers */
341 if ((data[1] & 0x20) != 0 || (data[1] & 0x10) != 0)
342 hdrsize++;
343 }
344 GST_LOG_OBJECT (depay,
345 "hdrsize %u, size %u, picture id 0x%x, s %u, part_id %u", hdrsize, size,
346 picture_id, s_bit, part_id);
347 if (G_UNLIKELY (hdrsize >= size))
348 goto too_small;
349
350 frame_start = (s_bit == 1) && (part_id == 0);
351 if (frame_start) {
352 if (G_UNLIKELY (self->started)) {
353 GST_DEBUG_OBJECT (depay, "Incomplete frame, flushing adapter");
354 gst_adapter_clear (self->adapter);
355 self->started = FALSE;
356
357 send_new_lost_event (self, GST_BUFFER_PTS (rtp->buffer), picture_id,
358 "Incomplete frame detected");
359 sent_lost_event = TRUE;
360 }
361 }
362
363 if (!self->started) {
364 if (G_UNLIKELY (!frame_start)) {
365 GST_DEBUG_OBJECT (depay,
366 "The frame is missing the first packet, ignoring the packet");
367 if (self->stop_lost_events && !sent_lost_event) {
368 send_last_lost_event (self);
369 self->stop_lost_events = FALSE;
370 }
371 goto done;
372 }
373
374 GST_LOG_OBJECT (depay, "Found the start of the frame");
375
376 if (self->stop_lost_events && !sent_lost_event) {
377 send_last_lost_event_if_needed (self, picture_id);
378 self->stop_lost_events = FALSE;
379 }
380
381 self->started = TRUE;
382 }
383
384 payload = gst_rtp_buffer_get_payload_subbuffer (rtp, hdrsize, -1);
385 gst_adapter_push (self->adapter, payload);
386 self->last_picture_id = picture_id;
387
388 /* Marker indicates that it was the last rtp packet for this frame */
389 if (gst_rtp_buffer_get_marker (rtp)) {
390 GstBuffer *out;
391 guint8 header[10];
392
393 GST_LOG_OBJECT (depay,
394 "Found the end of the frame (%" G_GSIZE_FORMAT " bytes)",
395 gst_adapter_available (self->adapter));
396 if (gst_adapter_available (self->adapter) < 10)
397 goto too_small;
398 gst_adapter_copy (self->adapter, &header, 0, 10);
399
400 out = gst_adapter_take_buffer (self->adapter,
401 gst_adapter_available (self->adapter));
402
403 self->started = FALSE;
404
405 /* mark keyframes */
406 out = gst_buffer_make_writable (out);
407 /* Filter away all metas that are not sensible to copy */
408 gst_rtp_drop_non_video_meta (self, out);
409 if ((header[0] & 0x01)) {
410 GST_BUFFER_FLAG_SET (out, GST_BUFFER_FLAG_DELTA_UNIT);
411
412 if (self->waiting_for_keyframe) {
413 gst_buffer_unref (out);
414 out = NULL;
415 GST_INFO_OBJECT (self, "Dropping inter-frame before intra-frame");
416 gst_pad_push_event (GST_RTP_BASE_DEPAYLOAD_SINKPAD (depay),
417 gst_video_event_new_upstream_force_key_unit (GST_CLOCK_TIME_NONE,
418 TRUE, 0));
419 }
420 } else {
421 guint profile, width, height;
422
423 GST_BUFFER_FLAG_UNSET (out, GST_BUFFER_FLAG_DELTA_UNIT);
424 GST_DEBUG_OBJECT (self, "Processed keyframe");
425
426 profile = (header[0] & 0x0e) >> 1;
427 width = GST_READ_UINT16_LE (header + 6) & 0x3fff;
428 height = GST_READ_UINT16_LE (header + 8) & 0x3fff;
429
430 if (G_UNLIKELY (self->last_width != width ||
431 self->last_height != height || self->last_profile != profile)) {
432 gchar profile_str[3];
433 GstCaps *srccaps;
434
435 snprintf (profile_str, 3, "%u", profile);
436 srccaps = gst_caps_new_simple ("video/x-vp8",
437 "framerate", GST_TYPE_FRACTION, 0, 1,
438 "height", G_TYPE_INT, height,
439 "width", G_TYPE_INT, width,
440 "profile", G_TYPE_STRING, profile_str, NULL);
441
442 gst_pad_set_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (depay), srccaps);
443 gst_caps_unref (srccaps);
444
445 self->last_width = width;
446 self->last_height = height;
447 self->last_profile = profile;
448 }
449 self->waiting_for_keyframe = FALSE;
450 }
451
452 if (picture_id != PICTURE_ID_NONE)
453 self->stop_lost_events = TRUE;
454
455 self->last_pushed_was_lost_event = FALSE;
456
457 return out;
458 }
459
460 done:
461 return NULL;
462
463 too_small:
464 GST_DEBUG_OBJECT (self, "Invalid rtp packet (too small), ignoring");
465 gst_adapter_clear (self->adapter);
466 self->started = FALSE;
467
468 goto done;
469 }
470
471 static GstStateChangeReturn
gst_rtp_vp8_depay_change_state(GstElement * element,GstStateChange transition)472 gst_rtp_vp8_depay_change_state (GstElement * element, GstStateChange transition)
473 {
474 GstRtpVP8Depay *self = GST_RTP_VP8_DEPAY (element);
475
476 switch (transition) {
477 case GST_STATE_CHANGE_READY_TO_PAUSED:
478 self->last_profile = -1;
479 self->last_height = -1;
480 self->last_width = -1;
481 self->waiting_for_keyframe = TRUE;
482 self->caps_sent = FALSE;
483 self->last_picture_id = PICTURE_ID_NONE;
484 gst_event_replace (&self->last_lost_event, NULL);
485 self->stop_lost_events = FALSE;
486 break;
487 default:
488 break;
489 }
490
491 return
492 GST_ELEMENT_CLASS (gst_rtp_vp8_depay_parent_class)->change_state (element,
493 transition);
494 }
495
496 static gboolean
gst_rtp_vp8_depay_handle_event(GstRTPBaseDepayload * depay,GstEvent * event)497 gst_rtp_vp8_depay_handle_event (GstRTPBaseDepayload * depay, GstEvent * event)
498 {
499 GstRtpVP8Depay *self = GST_RTP_VP8_DEPAY (depay);
500
501 switch (GST_EVENT_TYPE (event)) {
502 case GST_EVENT_FLUSH_STOP:
503 self->last_profile = -1;
504 self->last_height = -1;
505 self->last_width = -1;
506 self->last_picture_id = PICTURE_ID_NONE;
507 gst_event_replace (&self->last_lost_event, NULL);
508 self->stop_lost_events = FALSE;
509 break;
510 default:
511 break;
512 }
513
514 return
515 GST_RTP_BASE_DEPAYLOAD_CLASS
516 (gst_rtp_vp8_depay_parent_class)->handle_event (depay, event);
517 }
518
519 static gboolean
gst_rtp_vp8_depay_packet_lost(GstRTPBaseDepayload * depay,GstEvent * event)520 gst_rtp_vp8_depay_packet_lost (GstRTPBaseDepayload * depay, GstEvent * event)
521 {
522 GstRtpVP8Depay *self = GST_RTP_VP8_DEPAY (depay);
523 const GstStructure *s;
524 gboolean might_have_been_fec;
525 gboolean unref_event = FALSE;
526 gboolean ret;
527
528 s = gst_event_get_structure (event);
529
530 if (self->stop_lost_events) {
531 if (gst_structure_get_boolean (s, "might-have-been-fec",
532 &might_have_been_fec)
533 && might_have_been_fec) {
534 GST_DEBUG_OBJECT (depay, "Stopping lost event %" GST_PTR_FORMAT, event);
535 gst_event_replace (&self->last_lost_event, event);
536 return TRUE;
537 }
538 } else if (self->last_picture_id != PICTURE_ID_NONE) {
539 GstStructure *s;
540
541 if (!gst_event_is_writable (event)) {
542 event = gst_event_copy (event);
543 unref_event = TRUE;
544 }
545
546 s = gst_event_writable_structure (event);
547
548 /* We are currently processing a picture, let's make sure the
549 * base depayloader doesn't drop this lost event */
550 gst_structure_remove_field (s, "might-have-been-fec");
551 }
552
553 self->last_pushed_was_lost_event = TRUE;
554
555 ret =
556 GST_RTP_BASE_DEPAYLOAD_CLASS
557 (gst_rtp_vp8_depay_parent_class)->packet_lost (depay, event);
558
559 if (unref_event)
560 gst_event_unref (event);
561
562 return ret;
563 }
564