3 * Copyright (C) 2016 Pexip AS
4 * @author Stian Selnes <stian@pexip.com>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
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 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
22 #include <gst/check/check.h>
23 #include <gst/check/gstharness.h>
25 #define RTP_VP8_CAPS_STR \
26 "application/x-rtp,media=video,encoding-name=VP8,clock-rate=90000,payload=96"
28 #define gst_buffer_new_from_array(array) gst_buffer_new_wrapped ( \
29 g_memdup (vp8_bitstream_payload, sizeof (vp8_bitstream_payload)), \
30 sizeof (vp8_bitstream_payload))
33 add_vp8_meta (GstBuffer * buffer, gboolean use_temporal_scaling,
34 gboolean layer_sync, guint layer_id, guint tl0picidx)
39 meta = gst_buffer_add_custom_meta (buffer, "GstVP8Meta");
40 fail_unless (meta != NULL);
41 s = gst_custom_meta_get_structure (meta);
43 "use-temporal-scaling", G_TYPE_BOOLEAN, use_temporal_scaling,
44 "layer-sync", G_TYPE_BOOLEAN, layer_sync,
45 "layer-id", G_TYPE_UINT, layer_id,
46 "tl0picidx", G_TYPE_UINT, tl0picidx, NULL);
49 /* PictureID emum is not exported */
52 VP8_PAY_NO_PICTURE_ID = 0,
53 VP8_PAY_PICTURE_ID_7BITS = 1,
54 VP8_PAY_PICTURE_ID_15BITS = 2,
57 static const struct no_meta_test_data
60 enum PictureID pid; /* picture ID type of test */
61 gboolean vp8_payload_header_m_flag;
63 /* expected outputs */
64 guint vp8_payload_header_size;
65 guint vp8_payload_control_value;
66 } no_meta_test_data[] = {
68 VP8_PAY_NO_PICTURE_ID, FALSE, 1, 0x10}, /* no picture ID single byte header, S set */
70 VP8_PAY_PICTURE_ID_7BITS, FALSE, 3, 0x90}, /* X bit to allow for I bit means header is three bytes, S and X set */
72 VP8_PAY_PICTURE_ID_15BITS, TRUE, 4, 0x90}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */
73 /* repeated with non reference frame */
75 VP8_PAY_NO_PICTURE_ID, FALSE, 1, 0x30}, /* no picture ID single byte header, S set */
77 VP8_PAY_PICTURE_ID_7BITS, FALSE, 3, 0xB0}, /* X bit to allow for I bit means header is three bytes, S and X set */
79 VP8_PAY_PICTURE_ID_15BITS, TRUE, 4, 0xB0}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */
82 GST_START_TEST (test_pay_no_meta)
84 guint8 vp8_bitstream_payload[] = {
85 0x30, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47,
86 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, 0x00
88 const struct no_meta_test_data *test_data = &no_meta_test_data[__i__];
90 GstMapInfo map = GST_MAP_INFO_INIT;
91 GstHarness *h = gst_harness_new ("rtpvp8pay");
92 gst_harness_set_src_caps_str (h, "video/x-vp8");
94 /* check unknown picture id enum value */
95 fail_unless (test_data->pid <= VP8_PAY_PICTURE_ID_15BITS);
97 g_object_set (h->element, "picture-id-mode", test_data->pid,
98 "picture-id-offset", 0x5A5A, NULL);
100 buffer = gst_buffer_new_wrapped (g_memdup (vp8_bitstream_payload,
101 sizeof (vp8_bitstream_payload)), sizeof (vp8_bitstream_payload));
103 /* set droppable if N flag set */
104 if ((test_data->vp8_payload_control_value & 0x20) != 0) {
105 GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DROPPABLE);
108 buffer = gst_harness_push_and_pull (h, buffer);
110 fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
111 fail_unless (map.data != NULL);
113 /* check buffer size and content */
114 fail_unless_equals_int (map.size,
115 12 + test_data->vp8_payload_header_size + sizeof (vp8_bitstream_payload));
117 fail_unless_equals_int (test_data->vp8_payload_control_value, map.data[12]);
119 if (test_data->vp8_payload_header_size > 2) {
120 /* vp8 header extension byte must have I set */
121 fail_unless_equals_int (0x80, map.data[13]);
122 /* check picture id */
123 if (test_data->pid == VP8_PAY_PICTURE_ID_7BITS) {
124 fail_unless_equals_int (0x5a, map.data[14]);
125 } else if (test_data->pid == VP8_PAY_PICTURE_ID_15BITS) {
126 fail_unless_equals_int (0xDA, map.data[14]);
127 fail_unless_equals_int (0x5A, map.data[15]);
131 gst_buffer_unmap (buffer, &map);
132 gst_buffer_unref (buffer);
134 gst_harness_teardown (h);
139 static const struct with_meta_test_data
142 enum PictureID pid; /* picture ID type of test */
143 gboolean vp8_payload_header_m_flag;
144 gboolean use_temporal_scaling;
147 /* expected outputs */
148 guint vp8_payload_header_size;
149 guint vp8_payload_control_value;
150 guint vp8_payload_extended_value;
151 } with_meta_test_data[] = {
153 VP8_PAY_NO_PICTURE_ID, FALSE, FALSE, FALSE, 1, 0x10, 0x80}, /* no picture ID single byte header, S set */
155 VP8_PAY_PICTURE_ID_7BITS, FALSE, FALSE, FALSE, 3, 0x90, 0x80}, /* X bit to allow for I bit means header is three bytes, S and X set */
157 VP8_PAY_PICTURE_ID_15BITS, TRUE, FALSE, FALSE, 4, 0x90, 0x80}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */
159 VP8_PAY_NO_PICTURE_ID, FALSE, TRUE, FALSE, 4, 0x90, 0x60}, /* no picture ID single byte header, S set */
161 VP8_PAY_PICTURE_ID_7BITS, FALSE, TRUE, FALSE, 5, 0x90, 0xE0}, /* X bit to allow for I bit means header is three bytes, S and X set */
163 VP8_PAY_PICTURE_ID_15BITS, TRUE, TRUE, FALSE, 6, 0x90, 0xE0}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */
165 VP8_PAY_NO_PICTURE_ID, FALSE, TRUE, TRUE, 4, 0x90, 0x60}, /* no picture ID single byte header, S set */
167 VP8_PAY_PICTURE_ID_7BITS, FALSE, TRUE, TRUE, 5, 0x90, 0xE0}, /* X bit to allow for I bit means header is three bytes, S and X set */
169 VP8_PAY_PICTURE_ID_15BITS, TRUE, TRUE, TRUE, 6, 0x90, 0xE0}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */
170 /* repeated with non reference frame */
172 VP8_PAY_NO_PICTURE_ID, FALSE, FALSE, FALSE, 1, 0x30, 0x80}, /* no picture ID single byte header, S set */
174 VP8_PAY_PICTURE_ID_7BITS, FALSE, FALSE, FALSE, 3, 0xB0, 0x80}, /* X bit to allow for I bit means header is three bytes, S and X set */
176 VP8_PAY_PICTURE_ID_15BITS, TRUE, FALSE, FALSE, 4, 0xB0, 0x80}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */
178 VP8_PAY_NO_PICTURE_ID, FALSE, TRUE, FALSE, 4, 0xB0, 0x60}, /* no picture ID single byte header, S set */
180 VP8_PAY_PICTURE_ID_7BITS, FALSE, TRUE, FALSE, 5, 0xB0, 0xE0}, /* X bit to allow for I bit means header is three bytes, S and X set */
182 VP8_PAY_PICTURE_ID_15BITS, TRUE, TRUE, FALSE, 6, 0xB0, 0xE0}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */
184 VP8_PAY_NO_PICTURE_ID, FALSE, TRUE, TRUE, 4, 0xB0, 0x60}, /* no picture ID single byte header, S set */
186 VP8_PAY_PICTURE_ID_7BITS, FALSE, TRUE, TRUE, 5, 0xB0, 0xE0}, /* X bit to allow for I bit means header is three bytes, S and X set */
188 VP8_PAY_PICTURE_ID_15BITS, TRUE, TRUE, TRUE, 6, 0xB0, 0xE0}, /* X bit to allow for I bit with M bit means header is four bytes, S, X and M set */
191 GST_START_TEST (test_pay_with_meta)
193 guint8 vp8_bitstream_payload[] = {
194 0x30, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47,
195 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, 0x00
197 const struct with_meta_test_data *test_data = &with_meta_test_data[__i__];
200 GstMapInfo map = GST_MAP_INFO_INIT;
201 GstHarness *h = gst_harness_new ("rtpvp8pay");
202 gst_harness_set_src_caps_str (h, "video/x-vp8");
204 /* check for unknown picture id enum value */
205 fail_unless (test_data->pid <= VP8_PAY_PICTURE_ID_15BITS);
207 g_object_set (h->element, "picture-id-mode", test_data->pid,
208 "picture-id-offset", 0x5A5A, NULL);
210 /* Push a buffer in */
211 buffer = gst_buffer_new_wrapped (g_memdup (vp8_bitstream_payload,
212 sizeof (vp8_bitstream_payload)), sizeof (vp8_bitstream_payload));
213 add_vp8_meta (buffer, test_data->use_temporal_scaling, test_data->y_flag,
215 /* set droppable if N flag set */
216 if ((test_data->vp8_payload_control_value & 0x20) != 0) {
217 GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DROPPABLE);
220 buffer = gst_harness_push_and_pull (h, buffer);
222 fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
223 fail_unless (map.data != NULL);
225 meta = gst_buffer_get_custom_meta (buffer, "GstVP8Meta");
226 fail_unless (meta == NULL);
228 /* check buffer size and content */
229 fail_unless_equals_int (map.size,
230 12 + test_data->vp8_payload_header_size + sizeof (vp8_bitstream_payload));
231 fail_unless_equals_int (test_data->vp8_payload_control_value, map.data[12]);
233 if (test_data->vp8_payload_header_size > 1) {
235 fail_unless_equals_int (test_data->vp8_payload_extended_value,
238 /* check picture ID */
239 if (test_data->pid == VP8_PAY_PICTURE_ID_7BITS) {
240 fail_unless_equals_int (0x5A, map.data[hdridx++]);
241 } else if (test_data->pid == VP8_PAY_PICTURE_ID_15BITS) {
242 fail_unless_equals_int (0xDA, map.data[hdridx++]);
243 fail_unless_equals_int (0x5A, map.data[hdridx++]);
246 if (test_data->use_temporal_scaling) {
247 /* check temporal layer 0 picture ID value */
248 fail_unless_equals_int (255, map.data[hdridx++]);
249 /* check temporal layer ID value */
250 fail_unless_equals_int (2, (map.data[hdridx] >> 6) & 0x3);
252 if (test_data->y_flag) {
253 fail_unless_equals_int (1, (map.data[hdridx] >> 5) & 1);
255 fail_unless_equals_int (0, (map.data[hdridx] >> 5) & 1);
260 gst_buffer_unmap (buffer, &map);
261 gst_buffer_unref (buffer);
263 gst_harness_teardown (h);
268 GST_START_TEST (test_pay_continuous_picture_id_and_tl0picidx)
270 guint8 vp8_bitstream_payload[] = {
271 0x30, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47,
272 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, 0x00
274 GstHarness *h = gst_harness_new ("rtpvp8pay");
275 const gint header_len_without_tl0picidx = 3;
276 const gint header_len_with_tl0picidx = 5;
277 const gint packet_len_without_tl0picidx = 12 + header_len_without_tl0picidx +
278 sizeof (vp8_bitstream_payload);
279 const gint packet_len_with_tl0picidx = 12 + header_len_with_tl0picidx +
280 sizeof (vp8_bitstream_payload);
281 const gint picid_offset = 14;
282 const gint tl0picidx_offset = 15;
286 g_object_set (h->element, "picture-id-mode", VP8_PAY_PICTURE_ID_7BITS,
287 "picture-id-offset", 0, NULL);
288 gst_harness_set_src_caps_str (h, "video/x-vp8");
290 /* First, push a frame without temporal scalability meta */
291 buffer = gst_buffer_new_from_array (vp8_bitstream_payload);
292 buffer = gst_harness_push_and_pull (h, buffer);
293 fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
294 fail_unless_equals_int (map.size, packet_len_without_tl0picidx);
295 fail_unless_equals_int (map.data[picid_offset], 0x00);
296 gst_buffer_unmap (buffer, &map);
297 gst_buffer_unref (buffer);
299 /* Push a frame for temporal layer 0 with meta */
300 buffer = gst_buffer_new_from_array (vp8_bitstream_payload);
301 add_vp8_meta (buffer, TRUE, TRUE, 0, 0);
303 buffer = gst_harness_push_and_pull (h, buffer);
304 fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
305 fail_unless_equals_int (map.size, packet_len_with_tl0picidx);
306 fail_unless_equals_int (map.data[picid_offset], 0x01);
307 fail_unless_equals_int (map.data[tl0picidx_offset], 0x00);
308 gst_buffer_unmap (buffer, &map);
309 gst_buffer_unref (buffer);
311 /* Push a frame for temporal layer 1 with meta */
312 buffer = gst_buffer_new_from_array (vp8_bitstream_payload);
313 add_vp8_meta (buffer, TRUE, TRUE, 1, 0);
314 buffer = gst_harness_push_and_pull (h, buffer);
315 fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
316 fail_unless_equals_int (map.size, packet_len_with_tl0picidx);
317 fail_unless_equals_int (map.data[picid_offset], 0x02);
318 fail_unless_equals_int (map.data[tl0picidx_offset], 0x00);
319 gst_buffer_unmap (buffer, &map);
320 gst_buffer_unref (buffer);
322 /* Push next frame for temporal layer 0 with meta */
323 buffer = gst_buffer_new_from_array (vp8_bitstream_payload);
324 add_vp8_meta (buffer, TRUE, TRUE, 0, 1);
325 buffer = gst_harness_push_and_pull (h, buffer);
326 fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
327 fail_unless_equals_int (map.size, packet_len_with_tl0picidx);
328 fail_unless_equals_int (map.data[picid_offset], 0x03);
329 fail_unless_equals_int (map.data[tl0picidx_offset], 0x01);
330 gst_buffer_unmap (buffer, &map);
331 gst_buffer_unref (buffer);
333 /* Another frame for temporal layer 0, but now the meta->tl0picidx has been
334 * reset to 0 (simulating an encoder reset). Payload must ensure tl0picidx
336 buffer = gst_buffer_new_from_array (vp8_bitstream_payload);
337 add_vp8_meta (buffer, TRUE, TRUE, 0, 0);
338 buffer = gst_harness_push_and_pull (h, buffer);
339 fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
340 fail_unless_equals_int (map.size, packet_len_with_tl0picidx);
341 fail_unless_equals_int (map.data[picid_offset], 0x04);
342 fail_unless_equals_int (map.data[tl0picidx_offset], 0x02);
343 gst_buffer_unmap (buffer, &map);
344 gst_buffer_unref (buffer);
346 /* If we receive a frame without meta, we should continue to increase and
347 * add tl0picidx (assuming TID=0) in order to maximize interop. */
348 buffer = gst_buffer_new_from_array (vp8_bitstream_payload);
349 buffer = gst_harness_push_and_pull (h, buffer);
350 fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
351 fail_unless_equals_int (map.size, packet_len_with_tl0picidx);
352 fail_unless_equals_int (map.data[picid_offset], 0x05);
353 fail_unless_equals_int (map.data[tl0picidx_offset], 0x03);
354 gst_buffer_unmap (buffer, &map);
355 gst_buffer_unref (buffer);
357 gst_harness_teardown (h);
362 GST_START_TEST (test_pay_tl0picidx_split_buffer)
364 guint8 vp8_bitstream_payload[] = {
365 0x30, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0xb0, 0x00, 0x90, 0x00, 0x06, 0x47,
366 0x08, 0x85, 0x85, 0x88, 0x99, 0x84, 0x88, 0x21, 0x00
369 gst_harness_new_parse
370 ("rtpvp8pay mtu=28 picture-id-mode=1 picture-id-offset=0");
371 const gint header_len = 12 + 5; /* RTP + VP8 payload header */
372 const gint picid_offset = 14;
373 const gint tl0picidx_offset = 15;
374 guint output_bytes_left;
378 gst_harness_set_src_caps_str (h, "video/x-vp8");
380 /* Push a frame for temporal layer 0 with meta */
381 buffer = gst_buffer_new_from_array (vp8_bitstream_payload);
382 add_vp8_meta (buffer, TRUE, TRUE, 0, 0);
383 gst_harness_push (h, buffer);
385 /* Expect it to be split into multiple buffers to fit the MTU */
386 output_bytes_left = sizeof (vp8_bitstream_payload);
387 while (output_bytes_left > 0) {
388 const gint expected = MIN (output_bytes_left, 28 - header_len);
389 const gint packet_len = header_len + expected;
390 output_bytes_left -= expected;
392 buffer = gst_harness_pull (h);
393 fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
394 fail_unless_equals_int (map.size, packet_len);
395 fail_unless_equals_int (map.data[picid_offset], 0x00);
396 fail_unless_equals_int (map.data[tl0picidx_offset], 0x00);
397 gst_buffer_unmap (buffer, &map);
398 gst_buffer_unref (buffer);
401 /* Push a frame for temporal layer 1 with meta */
402 buffer = gst_buffer_new_from_array (vp8_bitstream_payload);
403 add_vp8_meta (buffer, TRUE, TRUE, 1, 0);
404 gst_harness_push (h, buffer);
406 /* Expect it to be split into multiple buffers to fit the MTU */
407 output_bytes_left = sizeof (vp8_bitstream_payload);
408 while (output_bytes_left > 0) {
409 const gint expected = MIN (output_bytes_left, 28 - header_len);
410 const gint packet_len = header_len + expected;
411 output_bytes_left -= expected;
413 buffer = gst_harness_pull (h);
414 fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
415 fail_unless_equals_int (map.size, packet_len);
416 fail_unless_equals_int (map.data[picid_offset], 0x01);
417 fail_unless_equals_int (map.data[tl0picidx_offset], 0x00);
418 gst_buffer_unmap (buffer, &map);
419 gst_buffer_unref (buffer);
422 /* Push another frame for temporal layer 0 with meta */
423 buffer = gst_buffer_new_from_array (vp8_bitstream_payload);
424 add_vp8_meta (buffer, TRUE, TRUE, 0, 0);
425 gst_harness_push (h, buffer);
427 /* Expect it to be split into multiple buffers to fit the MTU */
428 output_bytes_left = sizeof (vp8_bitstream_payload);
429 while (output_bytes_left > 0) {
430 const gint expected = MIN (output_bytes_left, 28 - header_len);
431 const gint packet_len = header_len + expected;
432 output_bytes_left -= expected;
434 buffer = gst_harness_pull (h);
435 fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
436 fail_unless_equals_int (map.size, packet_len);
437 fail_unless_equals_int (map.data[picid_offset], 0x02);
438 fail_unless_equals_int (map.data[tl0picidx_offset], 0x01);
439 gst_buffer_unmap (buffer, &map);
440 gst_buffer_unref (buffer);
443 gst_harness_teardown (h);
451 Suite *s = suite_create ("rtpvp8");
453 static const gchar *tags[] = { NULL };
455 /* Register custom GstVP8Meta manually */
456 gst_meta_register_custom ("GstVP8Meta", tags, NULL, NULL, NULL);
458 suite_add_tcase (s, (tc_chain = tcase_create ("vp8pay")));
459 tcase_add_loop_test (tc_chain, test_pay_no_meta, 0,
460 G_N_ELEMENTS (no_meta_test_data));
461 tcase_add_loop_test (tc_chain, test_pay_with_meta, 0,
462 G_N_ELEMENTS (with_meta_test_data));
463 tcase_add_test (tc_chain, test_pay_continuous_picture_id_and_tl0picidx);
464 tcase_add_test (tc_chain, test_pay_tl0picidx_split_buffer);
469 GST_CHECK_MAIN (rtpvp8);