rtpvp8pay: payload temporally scaled bitstreams.
[platform/upstream/gstreamer.git] / tests / check / elements / rtpvp8.c
1 /* GStreamer
2  *
3  * Copyright (C) 2016 Pexip AS
4  *   @author Stian Selnes <stian@pexip.com>
5  *
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.
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  * Library General Public License for more details.
15  *
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.
20  */
21
22 #include <gst/check/check.h>
23 #include <gst/check/gstharness.h>
24
25 #define RTP_VP8_CAPS_STR \
26   "application/x-rtp,media=video,encoding-name=VP8,clock-rate=90000,payload=96"
27
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))
31
32 static void
33 add_vp8_meta (GstBuffer * buffer, gboolean use_temporal_scaling,
34     gboolean layer_sync, guint layer_id, guint tl0picidx)
35 {
36   GstCustomMeta *meta;
37   GstStructure *s;
38
39   meta = gst_buffer_add_custom_meta (buffer, "GstVP8Meta");
40   fail_unless (meta != NULL);
41   s = gst_custom_meta_get_structure (meta);
42   gst_structure_set (s,
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);
47 }
48
49 /* PictureID emum is not exported */
50 enum PictureID
51 {
52   VP8_PAY_NO_PICTURE_ID = 0,
53   VP8_PAY_PICTURE_ID_7BITS = 1,
54   VP8_PAY_PICTURE_ID_15BITS = 2,
55 };
56
57 static const struct no_meta_test_data
58 {
59   /* control inputs */
60   enum PictureID pid;           /* picture ID type of test */
61   gboolean vp8_payload_header_m_flag;
62
63   /* expected outputs */
64   guint vp8_payload_header_size;
65   guint vp8_payload_control_value;
66 } no_meta_test_data[] = {
67   {
68   VP8_PAY_NO_PICTURE_ID, FALSE, 1, 0x10},       /* no picture ID single byte header, S set */
69   {
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 */
71   {
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 */
74   {
75   VP8_PAY_NO_PICTURE_ID, FALSE, 1, 0x30},       /* no picture ID single byte header, S set */
76   {
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 */
78   {
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 */
80 };
81
82 GST_START_TEST (test_pay_no_meta)
83 {
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
87   };
88   const struct no_meta_test_data *test_data = &no_meta_test_data[__i__];
89   GstBuffer *buffer;
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");
93
94   /* check unknown picture id enum value */
95   fail_unless (test_data->pid <= VP8_PAY_PICTURE_ID_15BITS);
96
97   g_object_set (h->element, "picture-id-mode", test_data->pid,
98       "picture-id-offset", 0x5A5A, NULL);
99
100   buffer = gst_buffer_new_wrapped (g_memdup (vp8_bitstream_payload,
101           sizeof (vp8_bitstream_payload)), sizeof (vp8_bitstream_payload));
102
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);
106   }
107
108   buffer = gst_harness_push_and_pull (h, buffer);
109
110   fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
111   fail_unless (map.data != NULL);
112
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));
116
117   fail_unless_equals_int (test_data->vp8_payload_control_value, map.data[12]);
118
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]);
128     }
129   }
130
131   gst_buffer_unmap (buffer, &map);
132   gst_buffer_unref (buffer);
133
134   gst_harness_teardown (h);
135 }
136
137 GST_END_TEST;
138
139 static const struct with_meta_test_data
140 {
141   /* control inputs */
142   enum PictureID pid;           /* picture ID type of test */
143   gboolean vp8_payload_header_m_flag;
144   gboolean use_temporal_scaling;
145   gboolean y_flag;
146
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[] = {
152   {
153   VP8_PAY_NO_PICTURE_ID, FALSE, FALSE, FALSE, 1, 0x10, 0x80},   /* no picture ID single byte header, S set */
154   {
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 */
156   {
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 */
158   {
159   VP8_PAY_NO_PICTURE_ID, FALSE, TRUE, FALSE, 4, 0x90, 0x60},    /* no picture ID single byte header, S set */
160   {
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 */
162   {
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 */
164   {
165   VP8_PAY_NO_PICTURE_ID, FALSE, TRUE, TRUE, 4, 0x90, 0x60},     /* no picture ID single byte header, S set */
166   {
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 */
168   {
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 */
171   {
172   VP8_PAY_NO_PICTURE_ID, FALSE, FALSE, FALSE, 1, 0x30, 0x80},   /* no picture ID single byte header, S set */
173   {
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 */
175   {
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 */
177   {
178   VP8_PAY_NO_PICTURE_ID, FALSE, TRUE, FALSE, 4, 0xB0, 0x60},    /* no picture ID single byte header, S set */
179   {
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 */
181   {
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 */
183   {
184   VP8_PAY_NO_PICTURE_ID, FALSE, TRUE, TRUE, 4, 0xB0, 0x60},     /* no picture ID single byte header, S set */
185   {
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 */
187   {
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 */
189 };
190
191 GST_START_TEST (test_pay_with_meta)
192 {
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
196   };
197   const struct with_meta_test_data *test_data = &with_meta_test_data[__i__];
198   GstBuffer *buffer;
199   GstCustomMeta *meta;
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");
203
204   /* check for unknown picture id enum value */
205   fail_unless (test_data->pid <= VP8_PAY_PICTURE_ID_15BITS);
206
207   g_object_set (h->element, "picture-id-mode", test_data->pid,
208       "picture-id-offset", 0x5A5A, NULL);
209
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,
214       2, 255);
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);
218   }
219
220   buffer = gst_harness_push_and_pull (h, buffer);
221
222   fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ));
223   fail_unless (map.data != NULL);
224
225   meta = gst_buffer_get_custom_meta (buffer, "GstVP8Meta");
226   fail_unless (meta == NULL);
227
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]);
232
233   if (test_data->vp8_payload_header_size > 1) {
234     int hdridx = 13;
235     fail_unless_equals_int (test_data->vp8_payload_extended_value,
236         map.data[hdridx++]);
237
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++]);
244     }
245
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);
251
252       if (test_data->y_flag) {
253         fail_unless_equals_int (1, (map.data[hdridx] >> 5) & 1);
254       } else {
255         fail_unless_equals_int (0, (map.data[hdridx] >> 5) & 1);
256       }
257     }
258   }
259
260   gst_buffer_unmap (buffer, &map);
261   gst_buffer_unref (buffer);
262
263   gst_harness_teardown (h);
264 }
265
266 GST_END_TEST;
267
268 GST_START_TEST (test_pay_continuous_picture_id_and_tl0picidx)
269 {
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
273   };
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;
283   GstBuffer *buffer;
284   GstMapInfo map;
285
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");
289
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);
298
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);
302
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);
310
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);
321
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);
332
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
335    * is increasing. */
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);
345
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);
356
357   gst_harness_teardown (h);
358 }
359
360 GST_END_TEST;
361
362 GST_START_TEST (test_pay_tl0picidx_split_buffer)
363 {
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
367   };
368   GstHarness *h =
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;
375   GstBuffer *buffer;
376   GstMapInfo map;
377
378   gst_harness_set_src_caps_str (h, "video/x-vp8");
379
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);
384
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;
391
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);
399   }
400
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);
405
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;
412
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);
420   }
421
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);
426
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;
433
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);
441   }
442
443   gst_harness_teardown (h);
444 }
445
446 GST_END_TEST;
447
448 static Suite *
449 rtpvp8_suite (void)
450 {
451   Suite *s = suite_create ("rtpvp8");
452   TCase *tc_chain;
453   static const gchar *tags[] = { NULL };
454
455   /* Register custom GstVP8Meta manually */
456   gst_meta_register_custom ("GstVP8Meta", tags, NULL, NULL, NULL);
457
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);
465
466   return s;
467 }
468
469 GST_CHECK_MAIN (rtpvp8);