/src/gstreamer/subprojects/gst-plugins-base/gst-libs/gst/video/video-anc.c
Line | Count | Source |
1 | | /* GStreamer |
2 | | * Copyright (C) 2018 Edward Hervey <edward@centricular.com> |
3 | | * Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com> |
4 | | * |
5 | | * This library is free software; you can redistribute it and/or |
6 | | * modify it under the terms of the GNU Library General Public |
7 | | * License as published by the Free Software Foundation; either |
8 | | * version 2 of the License, or (at your option) any later version. |
9 | | * |
10 | | * This library is distributed in the hope that it will be useful, |
11 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | | * Library General Public License for more details. |
14 | | * |
15 | | * You should have received a copy of the GNU Library General Public |
16 | | * License along with this library; if not, write to the |
17 | | * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
18 | | * Boston, MA 02110-1301, USA. |
19 | | */ |
20 | | |
21 | | #ifdef HAVE_CONFIG_H |
22 | | # include "config.h" |
23 | | #endif |
24 | | |
25 | | #include <string.h> |
26 | | #include <gst/base/gstbytereader.h> |
27 | | #include "video-anc.h" |
28 | | |
29 | | /** |
30 | | * SECTION:gstvideoanc |
31 | | * @title: GstVideo Ancillary |
32 | | * @short_description: Utilities for Ancillary data, VBI and Closed Caption |
33 | | * @private_symbols: |
34 | | * - GST_ANCILLARY_META_INFO |
35 | | * - GST_ANCILLARY_META_API_TYPE |
36 | | * - gst_ancillary_meta_get_info |
37 | | * - gst_ancillary_meta_api_get_type |
38 | | * |
39 | | * A collection of objects and methods to assist with handling Ancillary Data |
40 | | * present in Vertical Blanking Interval as well as Closed Caption. |
41 | | */ |
42 | | |
43 | | #ifndef GST_DISABLE_GST_DEBUG |
44 | | #define GST_CAT_DEFAULT ensure_debug_category() |
45 | | static GstDebugCategory * |
46 | | ensure_debug_category (void) |
47 | 0 | { |
48 | 0 | static gsize cat_gonce = 0; |
49 | |
|
50 | 0 | if (g_once_init_enter (&cat_gonce)) { |
51 | 0 | gsize cat_done; |
52 | |
|
53 | 0 | cat_done = (gsize) _gst_debug_category_new ("video-anc", 0, |
54 | 0 | "Ancillary data, VBI and CC utilities"); |
55 | |
|
56 | 0 | g_once_init_leave (&cat_gonce, cat_done); |
57 | 0 | } |
58 | |
|
59 | 0 | return (GstDebugCategory *) cat_gonce; |
60 | 0 | } |
61 | | #else |
62 | | #define ensure_debug_category() /* NOOP */ |
63 | | #endif /* GST_DISABLE_GST_DEBUG */ |
64 | | |
65 | | struct _GstVideoVBIParser |
66 | | { |
67 | | GstVideoInfo info; /* format of the lines provided */ |
68 | | guint8 *work_data; /* Converted line in planar 16bit format */ |
69 | | guint32 work_data_size; /* Size in bytes of work_data */ |
70 | | guint offset; /* Current offset (in bytes) in work_data */ |
71 | | gboolean bit16; /* Data is stored as 16bit if TRUE. Else 8bit(without parity) */ |
72 | | }; |
73 | | |
74 | 0 | G_DEFINE_BOXED_TYPE (GstVideoVBIParser, gst_video_vbi_parser, |
75 | 0 | (GBoxedCopyFunc) gst_video_vbi_parser_copy, |
76 | 0 | (GBoxedFreeFunc) gst_video_vbi_parser_free); |
77 | 0 |
|
78 | 0 | GstVideoVBIParser * |
79 | 0 | gst_video_vbi_parser_copy (const GstVideoVBIParser * parser) |
80 | 0 | { |
81 | 0 | GstVideoVBIParser *res; |
82 | |
|
83 | 0 | g_return_val_if_fail (parser != NULL, NULL); |
84 | | |
85 | 0 | res = gst_video_vbi_parser_new (GST_VIDEO_INFO_FORMAT (&parser->info), |
86 | 0 | parser->info.width); |
87 | 0 | if (res) { |
88 | 0 | memcpy (res->work_data, parser->work_data, parser->work_data_size); |
89 | 0 | } |
90 | 0 | return res; |
91 | 0 | } |
92 | | |
93 | | /* See SMPTE S291 */ |
94 | | static GstVideoVBIParserResult |
95 | | get_ancillary_16 (GstVideoVBIParser * parser, GstVideoAncillary * anc) |
96 | 0 | { |
97 | 0 | gboolean found = FALSE; |
98 | 0 | const guint16 *data = (const guint16 *) parser->work_data; |
99 | |
|
100 | 0 | g_return_val_if_fail (parser != NULL, GST_VIDEO_VBI_PARSER_RESULT_ERROR); |
101 | 0 | g_return_val_if_fail (anc != NULL, GST_VIDEO_VBI_PARSER_RESULT_ERROR); |
102 | | |
103 | | /* 3 words are needed at least to detect what kind of packet we look at |
104 | | * |
105 | | * - ADF (SMPTE S291 3.2.1) in case of component ancillary format: |
106 | | * 0x000 0x3ff 0x3ff (followed by DID, SDID) |
107 | | * - ADF (SMPTE S291 3.2.2) in case of composite ancillary format: |
108 | | * 0x3fc DID SDID |
109 | | */ |
110 | 0 | while (parser->offset + 3 < parser->work_data_size) { |
111 | 0 | guint8 DID, SDID, DC; |
112 | 0 | guint i = 0, j; |
113 | 0 | guint checksum = 0; |
114 | 0 | gboolean composite; |
115 | | |
116 | | /* Look for ADF */ |
117 | 0 | if (data[parser->offset] == 0x3fc) { |
118 | | /* composite */ |
119 | 0 | i += 1; |
120 | 0 | composite = TRUE; |
121 | 0 | } else if (data[parser->offset] == 0x000 && |
122 | 0 | data[parser->offset + 1] == 0x3ff && |
123 | 0 | data[parser->offset + 2] == 0x3ff) { |
124 | | /* component */ |
125 | 0 | i += 3; |
126 | 0 | composite = FALSE; |
127 | 0 | } else { |
128 | 0 | parser->offset += 1; |
129 | 0 | continue; |
130 | 0 | } |
131 | | |
132 | | /* TODO: Might want to check parity bits here but the checksum in |
133 | | * the end should really be enough */ |
134 | | |
135 | | /* 4 words: DID, SDID, DC, [DATA], checksum */ |
136 | 0 | if (parser->offset + i + 4 >= parser->work_data_size) |
137 | 0 | goto not_enough_data; |
138 | | |
139 | | /* We have a valid ADF */ |
140 | 0 | DID = data[parser->offset + i] & 0xff; |
141 | 0 | SDID = data[parser->offset + i + 1] & 0xff; |
142 | 0 | DC = data[parser->offset + i + 2] & 0xff; |
143 | 0 | i += 3; |
144 | | |
145 | | /* Check if we have enough room to get the User Data and checksum */ |
146 | 0 | if (parser->offset + i + DC + 1 >= parser->work_data_size) |
147 | 0 | goto not_enough_data; |
148 | | |
149 | | /* We found a valid ANC \o/ */ |
150 | 0 | anc->DID = DID; |
151 | 0 | anc->SDID_block_number = SDID; |
152 | 0 | anc->data_count = DC; |
153 | 0 | memset (anc->data, 0, 256); |
154 | | |
155 | | /* FIXME: We assume here the same data format for the user data as for the |
156 | | * DID/SDID: 10 bits with parity in the upper 2 bits. In theory some |
157 | | * standards could define this differently and even have full 10 bits of |
158 | | * user data but there does not seem to be a single such standard after |
159 | | * all these years. |
160 | | */ |
161 | | |
162 | | /* i is at the beginning of the user data now */ |
163 | 0 | for (j = 0; j < anc->data_count; j++) |
164 | 0 | anc->data[j] = data[parser->offset + i + j] & 0xff; |
165 | 0 | i += DC; |
166 | | |
167 | | /* Checksum calculation SMPTE S291 3.2.1 */ |
168 | 0 | for (j = (composite ? 1 : 3); j < i; j++) |
169 | 0 | checksum += data[parser->offset + j] & 0x1ff; |
170 | 0 | checksum &= 0x1ff; |
171 | 0 | checksum |= (!(checksum >> 8)) << 9; |
172 | |
|
173 | 0 | if (checksum != (data[parser->offset + i] & 0x3ff)) { |
174 | 0 | GST_WARNING ("ADF checksum mismatch: expected 0x%03x, got 0x%03x", |
175 | 0 | checksum, (data[parser->offset + i] & 0x3ff)); |
176 | 0 | parser->offset += 1; |
177 | 0 | continue; |
178 | 0 | } |
179 | | |
180 | 0 | i += 1; |
181 | |
|
182 | 0 | found = TRUE; |
183 | 0 | parser->offset += i; |
184 | 0 | break; |
185 | 0 | } |
186 | | |
187 | 0 | if (found) |
188 | 0 | return GST_VIDEO_VBI_PARSER_RESULT_OK; |
189 | | |
190 | 0 | return GST_VIDEO_VBI_PARSER_RESULT_DONE; |
191 | | |
192 | | /* ERRORS */ |
193 | 0 | not_enough_data: |
194 | 0 | { |
195 | 0 | GST_WARNING ("ANC requires more User Data than available line size"); |
196 | | /* Avoid further calls to go in the same error */ |
197 | 0 | parser->offset = parser->work_data_size; |
198 | 0 | return GST_VIDEO_VBI_PARSER_RESULT_ERROR; |
199 | 0 | } |
200 | 0 | } |
201 | | |
202 | | /* See SMPTE S291 */ |
203 | | static GstVideoVBIParserResult |
204 | | get_ancillary_8 (GstVideoVBIParser * parser, GstVideoAncillary * anc) |
205 | 0 | { |
206 | 0 | gboolean found = FALSE; |
207 | 0 | const guint8 *data = parser->work_data; |
208 | |
|
209 | 0 | g_return_val_if_fail (parser != NULL, GST_VIDEO_VBI_PARSER_RESULT_ERROR); |
210 | 0 | g_return_val_if_fail (anc != NULL, GST_VIDEO_VBI_PARSER_RESULT_ERROR); |
211 | | |
212 | | /* 3 words are needed at least to detect what kind of packet we look at |
213 | | * |
214 | | * - ADF (SMPTE S291 3.2.1) in case of component ancillary format: |
215 | | * 0x000 0x3ff 0x3ff (followed by DID, SDID) |
216 | | * - ADF (SMPTE S291 3.2.2) in case of composite ancillary format: |
217 | | * 0x3fc DID SDID |
218 | | */ |
219 | 0 | while (parser->offset + 3 < parser->work_data_size) { |
220 | 0 | guint8 DID, SDID, DC; |
221 | 0 | guint i = 0, j; |
222 | 0 | gboolean composite; |
223 | 0 | guint checksum = 0; |
224 | | |
225 | | /* Look for ADF */ |
226 | 0 | if (data[parser->offset] == 0xfc) { |
227 | | /* composite */ |
228 | 0 | composite = TRUE; |
229 | 0 | i += 1; |
230 | 0 | } else if (data[parser->offset] == 0x00 && |
231 | 0 | data[parser->offset + 1] == 0xff && data[parser->offset + 2] == 0xff) { |
232 | | /* component */ |
233 | 0 | composite = FALSE; |
234 | 0 | i += 3; |
235 | 0 | } else { |
236 | 0 | parser->offset += 1; |
237 | 0 | continue; |
238 | 0 | } |
239 | | |
240 | | /* 4 words: DID, SDID, DC, [DATA], checksum */ |
241 | 0 | if (parser->offset + i + 4 >= parser->work_data_size) |
242 | 0 | goto not_enough_data; |
243 | | |
244 | | /* We have a valid ADF */ |
245 | 0 | DID = data[parser->offset + i]; |
246 | 0 | SDID = data[parser->offset + i + 1]; |
247 | 0 | DC = data[parser->offset + i + 2]; |
248 | 0 | i += 3; |
249 | | |
250 | | /* Check if we have enough room to get the User Data and checksum */ |
251 | 0 | if (parser->offset + i + DC + 1 >= parser->work_data_size) |
252 | 0 | goto not_enough_data; |
253 | | |
254 | | /* We found a valid ANC \o/ */ |
255 | 0 | anc->DID = DID; |
256 | 0 | anc->SDID_block_number = SDID; |
257 | 0 | anc->data_count = DC; |
258 | 0 | memset (anc->data, 0, 256); |
259 | | |
260 | | /* i is at the beginning of the user data now */ |
261 | 0 | for (j = 0; j < anc->data_count; j++) |
262 | 0 | anc->data[j] = data[parser->offset + i + j] & 0xff; |
263 | 0 | i += DC; |
264 | | |
265 | | /* Checksum calculation SMPTE S291 3.2.1 */ |
266 | 0 | for (j = (composite ? 1 : 3); j < i; j++) |
267 | 0 | checksum += data[parser->offset + j]; |
268 | 0 | checksum &= 0xff; |
269 | |
|
270 | 0 | if (checksum != data[parser->offset + i]) { |
271 | 0 | GST_WARNING ("ADF checksum mismatch: expected 0x%02x, got 0x%02x", |
272 | 0 | checksum, data[parser->offset + i]); |
273 | 0 | parser->offset += 1; |
274 | 0 | continue; |
275 | 0 | } |
276 | | |
277 | 0 | i += 1; |
278 | |
|
279 | 0 | found = TRUE; |
280 | 0 | parser->offset += i; |
281 | 0 | break; |
282 | 0 | } |
283 | | |
284 | 0 | if (found) |
285 | 0 | return GST_VIDEO_VBI_PARSER_RESULT_OK; |
286 | | |
287 | 0 | return GST_VIDEO_VBI_PARSER_RESULT_DONE; |
288 | | |
289 | | /* ERRORS */ |
290 | 0 | not_enough_data: |
291 | 0 | { |
292 | 0 | GST_WARNING ("ANC requires more User Data than available line size"); |
293 | | /* Avoid further calls to go in the same error */ |
294 | 0 | parser->offset = parser->work_data_size; |
295 | 0 | return GST_VIDEO_VBI_PARSER_RESULT_ERROR; |
296 | 0 | } |
297 | 0 | } |
298 | | |
299 | | /** |
300 | | * gst_video_vbi_parser_get_ancillary: |
301 | | * @parser: a #GstVideoVBIParser |
302 | | * @anc: (out caller-allocates): a #GstVideoAncillary to start the eventual ancillary data |
303 | | * |
304 | | * Parse the line provided previously by gst_video_vbi_parser_add_line(). |
305 | | * |
306 | | * Since: 1.16 |
307 | | * |
308 | | * Returns: %GST_VIDEO_VBI_PARSER_RESULT_OK if ancillary data was found and |
309 | | * @anc was filled. %GST_VIDEO_VBI_PARSER_RESULT_DONE if there wasn't any |
310 | | * data. |
311 | | */ |
312 | | |
313 | | GstVideoVBIParserResult |
314 | | gst_video_vbi_parser_get_ancillary (GstVideoVBIParser * parser, |
315 | | GstVideoAncillary * anc) |
316 | 0 | { |
317 | 0 | g_return_val_if_fail (parser != NULL, GST_VIDEO_VBI_PARSER_RESULT_ERROR); |
318 | 0 | g_return_val_if_fail (anc != NULL, GST_VIDEO_VBI_PARSER_RESULT_ERROR); |
319 | | |
320 | 0 | if (parser->bit16) |
321 | 0 | return get_ancillary_16 (parser, anc); |
322 | 0 | return get_ancillary_8 (parser, anc); |
323 | 0 | } |
324 | | |
325 | | /** |
326 | | * gst_video_vbi_parser_new: |
327 | | * @format: a #GstVideoFormat |
328 | | * @pixel_width: The width in pixel to use |
329 | | * |
330 | | * Create a new #GstVideoVBIParser for the specified @format and @pixel_width. |
331 | | * |
332 | | * Since: 1.16 |
333 | | * |
334 | | * Returns: (nullable): The new #GstVideoVBIParser or %NULL if the @format and/or @pixel_width |
335 | | * is not supported. |
336 | | */ |
337 | | GstVideoVBIParser * |
338 | | gst_video_vbi_parser_new (GstVideoFormat format, guint32 pixel_width) |
339 | 0 | { |
340 | 0 | GstVideoVBIParser *parser; |
341 | |
|
342 | 0 | g_return_val_if_fail (pixel_width > 0, NULL); |
343 | | |
344 | 0 | switch (format) { |
345 | 0 | case GST_VIDEO_FORMAT_v210: |
346 | 0 | parser = g_new0 (GstVideoVBIParser, 1); |
347 | 0 | parser->bit16 = TRUE; |
348 | 0 | break; |
349 | 0 | case GST_VIDEO_FORMAT_UYVY: |
350 | 0 | parser = g_new0 (GstVideoVBIParser, 1); |
351 | 0 | parser->bit16 = FALSE; |
352 | 0 | break; |
353 | 0 | default: |
354 | 0 | GST_WARNING ("Format not supported by GstVideoVBIParser"); |
355 | 0 | return NULL; |
356 | 0 | } |
357 | | |
358 | 0 | gst_video_info_init (&parser->info); |
359 | 0 | if (!gst_video_info_set_format (&parser->info, format, pixel_width, 1)) { |
360 | 0 | GST_ERROR ("Could not create GstVideoInfo"); |
361 | 0 | g_free (parser); |
362 | 0 | return NULL; |
363 | 0 | } |
364 | | |
365 | | /* Allocate the workspace which is going to be 2 * pixel_width big |
366 | | * 2 : number of pixels per "component" (we only deal with 4:2:2) |
367 | | * We use 1 or 2 bytes per pixel depending on whether we are internally |
368 | | * working in 8 or 16bit */ |
369 | 0 | parser->work_data_size = 2 * pixel_width; |
370 | 0 | if (parser->bit16) |
371 | 0 | parser->work_data = g_malloc0 (parser->work_data_size * 2); |
372 | 0 | else |
373 | 0 | parser->work_data = g_malloc0 (parser->work_data_size); |
374 | 0 | parser->offset = 0; |
375 | |
|
376 | 0 | return parser; |
377 | 0 | } |
378 | | |
379 | | /** |
380 | | * gst_video_vbi_parser_free: |
381 | | * @parser: a #GstVideoVBIParser |
382 | | * |
383 | | * Frees the @parser. |
384 | | * |
385 | | * Since: 1.16 |
386 | | */ |
387 | | void |
388 | | gst_video_vbi_parser_free (GstVideoVBIParser * parser) |
389 | 0 | { |
390 | 0 | g_return_if_fail (parser != NULL); |
391 | | |
392 | 0 | g_free (parser->work_data); |
393 | 0 | g_free (parser); |
394 | 0 | } |
395 | | |
396 | | static void |
397 | | convert_line_from_uyvy (GstVideoVBIParser * parser, const guint8 * data) |
398 | 0 | { |
399 | 0 | guint i; |
400 | 0 | guint8 *y = parser->work_data; |
401 | | |
402 | | /* Data is stored differently in SD, making no distinction between Y and UV */ |
403 | 0 | if (parser->info.width < 1280) { |
404 | 0 | for (i = 0; i < parser->info.width - 3; i += 4) { |
405 | 0 | *y++ = data[(i / 4) * 4 + 0]; |
406 | 0 | *y++ = data[(i / 4) * 4 + 1]; |
407 | 0 | *y++ = data[(i / 4) * 4 + 2]; |
408 | 0 | *y++ = data[(i / 4) * 4 + 3]; |
409 | 0 | } |
410 | 0 | } else { |
411 | 0 | guint8 *uv = y + parser->info.width; |
412 | |
|
413 | 0 | for (i = 0; i < parser->info.width - 3; i += 4) { |
414 | 0 | *uv++ = data[(i / 4) * 4 + 0]; |
415 | 0 | *y++ = data[(i / 4) * 4 + 1]; |
416 | 0 | *uv++ = data[(i / 4) * 4 + 2]; |
417 | 0 | *y++ = data[(i / 4) * 4 + 3]; |
418 | 0 | } |
419 | 0 | } |
420 | 0 | GST_MEMDUMP ("Converted line", parser->work_data, 128); |
421 | 0 | } |
422 | | |
423 | | static void |
424 | | gst_info_dump_mem16_line (gchar * linebuf, gsize linebuf_size, |
425 | | const guint16 * mem, gsize mem_offset, gsize mem_size) |
426 | 0 | { |
427 | 0 | gchar hexstr[50], digitstr[6]; |
428 | 0 |
|
429 | 0 | if (mem_size > 8) |
430 | 0 | mem_size = 8; |
431 | 0 |
|
432 | 0 | hexstr[0] = '\0'; |
433 | 0 |
|
434 | 0 | if (mem != NULL) { |
435 | 0 | guint i = 0; |
436 | 0 |
|
437 | 0 | mem += mem_offset; |
438 | 0 | while (i < mem_size) { |
439 | 0 | g_snprintf (digitstr, sizeof (digitstr), "%04x ", mem[i]); |
440 | 0 | g_strlcat (hexstr, digitstr, sizeof (hexstr)); |
441 | 0 | ++i; |
442 | 0 | } |
443 | 0 | } |
444 | 0 |
|
445 | 0 | g_snprintf (linebuf, linebuf_size, "%08x: %-48.48s", |
446 | 0 | (guint) mem_offset, hexstr); |
447 | 0 | } |
448 | | |
449 | | static void |
450 | | convert_line_from_v210 (GstVideoVBIParser * parser, const guint8 * data) |
451 | 0 | { |
452 | 0 | guint i; |
453 | 0 | guint16 *y = (guint16 *) parser->work_data; |
454 | 0 | guint32 a, b, c, d; |
455 | | |
456 | | /* Data is stored differently in SD, making no distinction between Y and UV */ |
457 | 0 | if (parser->info.width < 1280) { |
458 | | /* Convert the line */ |
459 | 0 | for (i = 0; i < parser->info.width - 5; i += 6) { |
460 | 0 | a = GST_READ_UINT32_LE (data + (i / 6) * 16 + 0); |
461 | 0 | b = GST_READ_UINT32_LE (data + (i / 6) * 16 + 4); |
462 | 0 | c = GST_READ_UINT32_LE (data + (i / 6) * 16 + 8); |
463 | 0 | d = GST_READ_UINT32_LE (data + (i / 6) * 16 + 12); |
464 | |
|
465 | 0 | *y++ = (a >> 0) & 0x3ff; |
466 | 0 | *y++ = (a >> 10) & 0x3ff; |
467 | 0 | *y++ = (a >> 20) & 0x3ff; |
468 | 0 | *y++ = (b >> 0) & 0x3ff; |
469 | |
|
470 | 0 | *y++ = (b >> 10) & 0x3ff; |
471 | 0 | *y++ = (b >> 20) & 0x3ff; |
472 | 0 | *y++ = (c >> 0) & 0x3ff; |
473 | 0 | *y++ = (c >> 10) & 0x3ff; |
474 | |
|
475 | 0 | *y++ = (c >> 20) & 0x3ff; |
476 | 0 | *y++ = (d >> 0) & 0x3ff; |
477 | 0 | *y++ = (d >> 10) & 0x3ff; |
478 | 0 | *y++ = (d >> 20) & 0x3ff; |
479 | 0 | } |
480 | 0 | } else { |
481 | 0 | guint16 *uv = y + parser->info.width; |
482 | | |
483 | | /* Convert the line */ |
484 | 0 | for (i = 0; i < parser->info.width - 5; i += 6) { |
485 | 0 | a = GST_READ_UINT32_LE (data + (i / 6) * 16 + 0); |
486 | 0 | b = GST_READ_UINT32_LE (data + (i / 6) * 16 + 4); |
487 | 0 | c = GST_READ_UINT32_LE (data + (i / 6) * 16 + 8); |
488 | 0 | d = GST_READ_UINT32_LE (data + (i / 6) * 16 + 12); |
489 | |
|
490 | 0 | *uv++ = (a >> 0) & 0x3ff; |
491 | 0 | *y++ = (a >> 10) & 0x3ff; |
492 | 0 | *uv++ = (a >> 20) & 0x3ff; |
493 | 0 | *y++ = (b >> 0) & 0x3ff; |
494 | |
|
495 | 0 | *uv++ = (b >> 10) & 0x3ff; |
496 | 0 | *y++ = (b >> 20) & 0x3ff; |
497 | 0 | *uv++ = (c >> 0) & 0x3ff; |
498 | 0 | *y++ = (c >> 10) & 0x3ff; |
499 | |
|
500 | 0 | *uv++ = (c >> 20) & 0x3ff; |
501 | 0 | *y++ = (d >> 0) & 0x3ff; |
502 | 0 | *uv++ = (d >> 10) & 0x3ff; |
503 | 0 | *y++ = (d >> 20) & 0x3ff; |
504 | 0 | } |
505 | 0 | } |
506 | |
|
507 | 0 | if (0) { |
508 | 0 | guint off = 0; |
509 | 0 | gsize length = parser->info.width * 2; |
510 | |
|
511 | 0 | GST_TRACE ("--------" |
512 | 0 | "-------------------------------------------------------------------"); |
513 | |
|
514 | 0 | while (off < length) { |
515 | 0 | gchar buf[128]; |
516 | | |
517 | | /* gst_info_dump_mem_line will process 16 bytes (8 16bit chunks) at most */ |
518 | 0 | gst_info_dump_mem16_line (buf, sizeof (buf), |
519 | 0 | (guint16 *) parser->work_data, off, length - off); |
520 | 0 | GST_TRACE ("%s", buf); |
521 | 0 | off += 8; |
522 | 0 | } |
523 | 0 | GST_TRACE ("--------" |
524 | 0 | "-------------------------------------------------------------------"); |
525 | 0 | } |
526 | 0 | } |
527 | | |
528 | | /** |
529 | | * gst_video_vbi_parser_add_line: |
530 | | * @parser: a #GstVideoVBIParser |
531 | | * @data: (array) (transfer none): The line of data to parse |
532 | | * |
533 | | * Provide a new line of data to the @parser. Call gst_video_vbi_parser_get_ancillary() |
534 | | * to get the Ancillary data that might be present on that line. |
535 | | * |
536 | | * Since: 1.16 |
537 | | */ |
538 | | void |
539 | | gst_video_vbi_parser_add_line (GstVideoVBIParser * parser, const guint8 * data) |
540 | 0 | { |
541 | 0 | g_return_if_fail (parser != NULL); |
542 | 0 | g_return_if_fail (data != NULL); |
543 | | |
544 | | /* Reset offset */ |
545 | 0 | parser->offset = 0; |
546 | |
|
547 | 0 | switch (GST_VIDEO_INFO_FORMAT (&parser->info)) { |
548 | 0 | case GST_VIDEO_FORMAT_v210: |
549 | 0 | convert_line_from_v210 (parser, data); |
550 | 0 | break; |
551 | 0 | case GST_VIDEO_FORMAT_UYVY: |
552 | 0 | convert_line_from_uyvy (parser, data); |
553 | 0 | break; |
554 | 0 | default: |
555 | 0 | GST_ERROR ("UNSUPPORTED FORMAT !"); |
556 | 0 | g_assert_not_reached (); |
557 | 0 | break; |
558 | 0 | } |
559 | 0 | } |
560 | | |
561 | | struct _GstVideoVBIEncoder |
562 | | { |
563 | | GstVideoInfo info; /* format of the lines provided */ |
564 | | guint8 *work_data; /* Converted line in planar 16bit format */ |
565 | | guint32 work_data_size; /* Size in bytes of work_data */ |
566 | | guint offset; /* Current offset (in bytes) in work_data */ |
567 | | gboolean bit16; /* Data is stored as 16bit if TRUE. Else 8bit(without parity) */ |
568 | | }; |
569 | | |
570 | 0 | G_DEFINE_BOXED_TYPE (GstVideoVBIEncoder, gst_video_vbi_encoder, |
571 | 0 | (GBoxedCopyFunc) gst_video_vbi_encoder_copy, |
572 | 0 | (GBoxedFreeFunc) gst_video_vbi_encoder_free); |
573 | 0 |
|
574 | 0 | GstVideoVBIEncoder * |
575 | 0 | gst_video_vbi_encoder_copy (const GstVideoVBIEncoder * encoder) |
576 | 0 | { |
577 | 0 | GstVideoVBIEncoder *res; |
578 | |
|
579 | 0 | g_return_val_if_fail (encoder != NULL, NULL); |
580 | | |
581 | 0 | res = gst_video_vbi_encoder_new (GST_VIDEO_INFO_FORMAT (&encoder->info), |
582 | 0 | encoder->info.width); |
583 | 0 | if (res) { |
584 | 0 | memcpy (res->work_data, encoder->work_data, encoder->work_data_size); |
585 | 0 | } |
586 | 0 | return res; |
587 | 0 | } |
588 | | |
589 | | /** |
590 | | * gst_video_vbi_encoder_free: |
591 | | * @encoder: a #GstVideoVBIEncoder |
592 | | * |
593 | | * Frees the @encoder. |
594 | | * |
595 | | * Since: 1.16 |
596 | | */ |
597 | | void |
598 | | gst_video_vbi_encoder_free (GstVideoVBIEncoder * encoder) |
599 | 0 | { |
600 | 0 | g_return_if_fail (encoder != NULL); |
601 | | |
602 | 0 | g_free (encoder->work_data); |
603 | 0 | g_free (encoder); |
604 | 0 | } |
605 | | |
606 | | /** |
607 | | * gst_video_vbi_encoder_new: |
608 | | * @format: a #GstVideoFormat |
609 | | * @pixel_width: The width in pixel to use |
610 | | * |
611 | | * Create a new #GstVideoVBIEncoder for the specified @format and @pixel_width. |
612 | | * |
613 | | * Since: 1.16 |
614 | | * |
615 | | * Returns: (nullable): The new #GstVideoVBIEncoder or %NULL if the @format and/or @pixel_width |
616 | | * is not supported. |
617 | | */ |
618 | | GstVideoVBIEncoder * |
619 | | gst_video_vbi_encoder_new (GstVideoFormat format, guint32 pixel_width) |
620 | 0 | { |
621 | 0 | GstVideoVBIEncoder *encoder; |
622 | |
|
623 | 0 | g_return_val_if_fail (pixel_width > 0, NULL); |
624 | | |
625 | 0 | switch (format) { |
626 | 0 | case GST_VIDEO_FORMAT_v210: |
627 | 0 | encoder = g_new0 (GstVideoVBIEncoder, 1); |
628 | 0 | encoder->bit16 = TRUE; |
629 | 0 | break; |
630 | 0 | case GST_VIDEO_FORMAT_UYVY: |
631 | 0 | encoder = g_new0 (GstVideoVBIEncoder, 1); |
632 | 0 | encoder->bit16 = FALSE; |
633 | 0 | break; |
634 | 0 | default: |
635 | 0 | GST_WARNING ("Format not supported by GstVideoVBIEncoder"); |
636 | 0 | return NULL; |
637 | 0 | } |
638 | | |
639 | 0 | gst_video_info_init (&encoder->info); |
640 | 0 | if (!gst_video_info_set_format (&encoder->info, format, pixel_width, 1)) { |
641 | 0 | GST_ERROR ("Could not create GstVideoInfo"); |
642 | 0 | g_free (encoder); |
643 | 0 | return NULL; |
644 | 0 | } |
645 | | |
646 | | /* Allocate the workspace which is going to be 2 * pixel_width big |
647 | | * 2 : number of pixels per "component" (we only deal with 4:2:2) |
648 | | * We use 1 or 2 bytes per pixel depending on whether we are internally |
649 | | * working in 8 or 16bit */ |
650 | 0 | encoder->work_data_size = 2 * pixel_width; |
651 | 0 | if (encoder->bit16) |
652 | 0 | encoder->work_data = g_malloc0 (encoder->work_data_size * 2); |
653 | 0 | else |
654 | 0 | encoder->work_data = g_malloc0 (encoder->work_data_size); |
655 | 0 | encoder->offset = 0; |
656 | |
|
657 | 0 | return encoder; |
658 | 0 | } |
659 | | |
660 | | #if G_GNUC_CHECK_VERSION(3,4) |
661 | | static inline guint |
662 | | parity (guint8 x) |
663 | 0 | { |
664 | 0 | return __builtin_parity (x); |
665 | 0 | } |
666 | | #else |
667 | | static guint |
668 | | parity (guint8 x) |
669 | | { |
670 | | guint count = 0; |
671 | | |
672 | | while (x) { |
673 | | count += x & 1; |
674 | | x >>= 1; |
675 | | } |
676 | | |
677 | | return count & 1; |
678 | | } |
679 | | #endif |
680 | | |
681 | | /* Odd/even parity in the upper two bits */ |
682 | 0 | #define SET_WITH_PARITY(buf, val) G_STMT_START { \ |
683 | 0 | *(buf) = val; \ |
684 | 0 | if (parity (val)) \ |
685 | 0 | *(buf) |= 0x100; \ |
686 | 0 | else \ |
687 | 0 | *(buf) |= 0x200; \ |
688 | 0 | } G_STMT_END; |
689 | | |
690 | | /** |
691 | | * gst_video_vbi_encoder_add_ancillary: |
692 | | * @encoder: a #GstVideoVBIEncoder |
693 | | * @composite: %TRUE if composite ADF should be created, component otherwise |
694 | | * @DID: The Data Identifier |
695 | | * @SDID_block_number: The Secondary Data Identifier (if type 2) or the Data |
696 | | * Block Number (if type 1) |
697 | | * @data_count: The amount of data (in bytes) in @data (max 255 bytes) |
698 | | * @data: (array length=data_count): The user data content of the Ancillary packet. |
699 | | * Does not contain the ADF, DID, SDID nor CS. |
700 | | * |
701 | | * Stores Video Ancillary data, according to SMPTE-291M specification. |
702 | | * |
703 | | * Note that the contents of the data are always read as 8bit data (i.e. do not contain |
704 | | * the parity check bits). |
705 | | * |
706 | | * Since: 1.16 |
707 | | * |
708 | | * Returns: %TRUE if enough space was left in the current line, %FALSE |
709 | | * otherwise. |
710 | | */ |
711 | | gboolean |
712 | | gst_video_vbi_encoder_add_ancillary (GstVideoVBIEncoder * encoder, |
713 | | gboolean composite, guint8 DID, guint8 SDID_block_number, |
714 | | const guint8 * data, guint data_count) |
715 | 0 | { |
716 | 0 | g_return_val_if_fail (encoder != NULL, FALSE); |
717 | 0 | g_return_val_if_fail (data != NULL, FALSE); |
718 | 0 | g_return_val_if_fail (data_count < 256, FALSE); |
719 | | |
720 | | /* Doesn't fit into this line anymore */ |
721 | 0 | if (encoder->offset + data_count + (composite ? 5 : 7) > |
722 | 0 | encoder->work_data_size) |
723 | 0 | return FALSE; |
724 | | |
725 | 0 | if (encoder->bit16) { |
726 | 0 | guint16 *work_data = ((guint16 *) encoder->work_data) + encoder->offset; |
727 | 0 | guint i = 0, j; |
728 | 0 | guint checksum = 0; |
729 | | |
730 | | /* Write ADF */ |
731 | 0 | if (composite) { |
732 | 0 | work_data[i] = 0x3fc; |
733 | 0 | i += 1; |
734 | 0 | } else { |
735 | 0 | work_data[i] = 0x000; |
736 | 0 | work_data[i + 1] = 0x3ff; |
737 | 0 | work_data[i + 2] = 0x3ff; |
738 | 0 | i += 3; |
739 | 0 | } |
740 | |
|
741 | 0 | SET_WITH_PARITY (&work_data[i], DID); |
742 | 0 | SET_WITH_PARITY (&work_data[i + 1], SDID_block_number); |
743 | 0 | SET_WITH_PARITY (&work_data[i + 2], data_count); |
744 | 0 | i += 3; |
745 | |
|
746 | 0 | for (j = 0; j < data_count; j++) |
747 | 0 | SET_WITH_PARITY (&work_data[i + j], data[j]); |
748 | 0 | i += data_count; |
749 | |
|
750 | 0 | for (j = (composite ? 1 : 3); j < i; j++) |
751 | 0 | checksum += work_data[j]; |
752 | 0 | checksum &= 0x1ff; |
753 | 0 | checksum |= (!(checksum >> 8)) << 9; |
754 | |
|
755 | 0 | work_data[i] = checksum; |
756 | 0 | i += 1; |
757 | |
|
758 | 0 | encoder->offset += i; |
759 | 0 | } else { |
760 | 0 | guint8 *work_data = ((guint8 *) encoder->work_data) + encoder->offset; |
761 | 0 | guint i = 0, j; |
762 | 0 | guint checksum = 0; |
763 | | |
764 | | /* Write ADF */ |
765 | 0 | if (composite) { |
766 | 0 | work_data[i] = 0xfc; |
767 | 0 | i += 1; |
768 | 0 | } else { |
769 | 0 | work_data[i] = 0x00; |
770 | 0 | work_data[i + 1] = 0xff; |
771 | 0 | work_data[i + 2] = 0xff; |
772 | 0 | i += 3; |
773 | 0 | } |
774 | |
|
775 | 0 | work_data[i] = DID; |
776 | 0 | work_data[i + 1] = SDID_block_number; |
777 | 0 | work_data[i + 2] = data_count; |
778 | 0 | i += 3; |
779 | |
|
780 | 0 | for (j = 0; j < data_count; j++) |
781 | 0 | work_data[i + j] = data[j]; |
782 | 0 | i += data_count; |
783 | |
|
784 | 0 | for (j = (composite ? 1 : 3); j < i; j++) |
785 | 0 | checksum += work_data[j]; |
786 | 0 | checksum &= 0xff; |
787 | |
|
788 | 0 | work_data[i] = checksum; |
789 | 0 | i += 1; |
790 | |
|
791 | 0 | encoder->offset += i; |
792 | 0 | } |
793 | |
|
794 | 0 | return TRUE; |
795 | 0 | } |
796 | | |
797 | | static void |
798 | | convert_line_to_v210 (GstVideoVBIEncoder * encoder, guint8 * data) |
799 | 0 | { |
800 | 0 | guint i; |
801 | 0 | const guint16 *y = (const guint16 *) encoder->work_data; |
802 | 0 | guint32 a, b, c, d; |
803 | | |
804 | | /* Data is stored differently in SD, making no distinction between Y and UV */ |
805 | 0 | if (encoder->info.width < 1280) { |
806 | | /* Convert the line */ |
807 | 0 | for (i = 0; i < encoder->info.width - 5; i += 6) { |
808 | 0 | a = ((y[0] & 0x3ff) << 0) |
809 | 0 | | ((y[1] & 0x3ff) << 10) |
810 | 0 | | ((y[2] & 0x3ff) << 20); |
811 | 0 | y += 3; |
812 | |
|
813 | 0 | b = ((y[0] & 0x3ff) << 0) |
814 | 0 | | ((y[1] & 0x3ff) << 10) |
815 | 0 | | ((y[2] & 0x3ff) << 20); |
816 | 0 | y += 3; |
817 | |
|
818 | 0 | c = ((y[0] & 0x3ff) << 0) |
819 | 0 | | ((y[1] & 0x3ff) << 10) |
820 | 0 | | ((y[2] & 0x3ff) << 20); |
821 | 0 | y += 3; |
822 | |
|
823 | 0 | d = ((y[0] & 0x3ff) << 0) |
824 | 0 | | ((y[1] & 0x3ff) << 10) |
825 | 0 | | ((y[2] & 0x3ff) << 20); |
826 | 0 | y += 3; |
827 | |
|
828 | 0 | GST_WRITE_UINT32_LE (data + (i / 6) * 16 + 0, a); |
829 | 0 | GST_WRITE_UINT32_LE (data + (i / 6) * 16 + 4, b); |
830 | 0 | GST_WRITE_UINT32_LE (data + (i / 6) * 16 + 8, c); |
831 | 0 | GST_WRITE_UINT32_LE (data + (i / 6) * 16 + 12, d); |
832 | 0 | } |
833 | 0 | } else { |
834 | 0 | const guint16 *uv = y + encoder->info.width; |
835 | | |
836 | | /* Convert the line */ |
837 | 0 | for (i = 0; i < encoder->info.width - 5; i += 6) { |
838 | 0 | a = ((uv[0] & 0x3ff) << 0) |
839 | 0 | | ((y[0] & 0x3ff) << 10) |
840 | 0 | | ((uv[1] & 0x3ff) << 20); |
841 | 0 | uv += 2; |
842 | 0 | y++; |
843 | |
|
844 | 0 | b = ((y[0] & 0x3ff) << 0) |
845 | 0 | | ((uv[0] & 0x3ff) << 10) |
846 | 0 | | ((y[1] & 0x3ff) << 20); |
847 | 0 | y += 2; |
848 | 0 | uv++; |
849 | |
|
850 | 0 | c = ((uv[0] & 0x3ff) << 0) |
851 | 0 | | ((y[0] & 0x3ff) << 10) |
852 | 0 | | ((uv[1] & 0x3ff) << 20); |
853 | 0 | uv += 2; |
854 | 0 | y++; |
855 | |
|
856 | 0 | d = ((y[0] & 0x3ff) << 0) |
857 | 0 | | ((uv[0] & 0x3ff) << 10) |
858 | 0 | | ((y[1] & 0x3ff) << 20); |
859 | 0 | y += 2; |
860 | 0 | uv++; |
861 | |
|
862 | 0 | GST_WRITE_UINT32_LE (data + (i / 6) * 16 + 0, a); |
863 | 0 | GST_WRITE_UINT32_LE (data + (i / 6) * 16 + 4, b); |
864 | 0 | GST_WRITE_UINT32_LE (data + (i / 6) * 16 + 8, c); |
865 | 0 | GST_WRITE_UINT32_LE (data + (i / 6) * 16 + 12, d); |
866 | 0 | } |
867 | 0 | } |
868 | 0 | } |
869 | | |
870 | | static void |
871 | | convert_line_to_uyvy (GstVideoVBIEncoder * encoder, guint8 * data) |
872 | 0 | { |
873 | 0 | guint i; |
874 | 0 | const guint8 *y = encoder->work_data; |
875 | | |
876 | | /* Data is stored differently in SD, making no distinction between Y and UV */ |
877 | 0 | if (encoder->info.width < 1280) { |
878 | 0 | for (i = 0; i < encoder->info.width - 3; i += 4) { |
879 | 0 | data[(i / 4) * 4 + 0] = *y++; |
880 | 0 | data[(i / 4) * 4 + 1] = *y++; |
881 | 0 | data[(i / 4) * 4 + 2] = *y++; |
882 | 0 | data[(i / 4) * 4 + 3] = *y++; |
883 | 0 | } |
884 | 0 | } else { |
885 | 0 | const guint8 *uv = y + encoder->info.width; |
886 | |
|
887 | 0 | for (i = 0; i < encoder->info.width - 3; i += 4) { |
888 | 0 | data[(i / 4) * 4 + 0] = *uv++; |
889 | 0 | data[(i / 4) * 4 + 1] = *y++; |
890 | 0 | data[(i / 4) * 4 + 2] = *uv++; |
891 | 0 | data[(i / 4) * 4 + 3] = *y++; |
892 | 0 | } |
893 | 0 | } |
894 | 0 | } |
895 | | |
896 | | /** |
897 | | * gst_video_vbi_encoder_write_line: |
898 | | * @encoder: a #GstVideoVBIEncoder |
899 | | * @data: (array): The line to write to |
900 | | * |
901 | | * @data needs to have the correct size and alignment for the configured video |
902 | | * format of @encoder. |
903 | | */ |
904 | | void |
905 | | gst_video_vbi_encoder_write_line (GstVideoVBIEncoder * encoder, guint8 * data) |
906 | 0 | { |
907 | 0 | g_return_if_fail (encoder != NULL); |
908 | 0 | g_return_if_fail (data != NULL); |
909 | | |
910 | | /* nothing to write? just exit early */ |
911 | 0 | if (!encoder->offset) |
912 | 0 | return; |
913 | | |
914 | 0 | switch (GST_VIDEO_INFO_FORMAT (&encoder->info)) { |
915 | 0 | case GST_VIDEO_FORMAT_v210: |
916 | 0 | convert_line_to_v210 (encoder, data); |
917 | 0 | break; |
918 | 0 | case GST_VIDEO_FORMAT_UYVY: |
919 | 0 | convert_line_to_uyvy (encoder, data); |
920 | 0 | break; |
921 | 0 | default: |
922 | 0 | GST_ERROR ("UNSUPPORTED FORMAT !"); |
923 | 0 | g_assert_not_reached (); |
924 | 0 | break; |
925 | 0 | } |
926 | | |
927 | 0 | encoder->offset = 0; |
928 | 0 | memset (encoder->work_data, 0, |
929 | 0 | encoder->work_data_size * (encoder->bit16 ? 2 : 1)); |
930 | 0 | } |
931 | | |
932 | | /* Closed Caption Meta implementation *******************************************/ |
933 | | |
934 | | GType |
935 | | gst_video_caption_meta_api_get_type (void) |
936 | 0 | { |
937 | 0 | static GType type = 0; |
938 | |
|
939 | 0 | if (g_once_init_enter (&type)) { |
940 | 0 | static const gchar *tags[] = { NULL }; |
941 | 0 | GType _type = gst_meta_api_type_register ("GstVideoCaptionMetaAPI", tags); |
942 | 0 | GST_INFO ("registering"); |
943 | 0 | g_once_init_leave (&type, _type); |
944 | 0 | } |
945 | 0 | return type; |
946 | 0 | } |
947 | | |
948 | | |
949 | | static gboolean |
950 | | gst_video_caption_meta_transform (GstBuffer * dest, GstMeta * meta, |
951 | | GstBuffer * buffer, GQuark type, gpointer data) |
952 | 0 | { |
953 | 0 | GstVideoCaptionMeta *dmeta, *smeta; |
954 | | |
955 | | /* We always copy over the caption meta */ |
956 | 0 | smeta = (GstVideoCaptionMeta *) meta; |
957 | |
|
958 | 0 | GST_DEBUG ("copy caption metadata"); |
959 | 0 | dmeta = |
960 | 0 | gst_buffer_add_video_caption_meta (dest, smeta->caption_type, |
961 | 0 | smeta->data, smeta->size); |
962 | 0 | if (!dmeta) |
963 | 0 | return FALSE; |
964 | | |
965 | 0 | return TRUE; |
966 | 0 | } |
967 | | |
968 | | static gboolean |
969 | | gst_video_caption_meta_init (GstMeta * meta, gpointer params, |
970 | | GstBuffer * buffer) |
971 | 0 | { |
972 | 0 | GstVideoCaptionMeta *emeta = (GstVideoCaptionMeta *) meta; |
973 | |
|
974 | 0 | emeta->caption_type = GST_VIDEO_CAPTION_TYPE_UNKNOWN; |
975 | 0 | emeta->data = NULL; |
976 | 0 | emeta->size = 0; |
977 | |
|
978 | 0 | return TRUE; |
979 | 0 | } |
980 | | |
981 | | static void |
982 | | gst_video_caption_meta_free (GstMeta * meta, GstBuffer * buffer) |
983 | 0 | { |
984 | 0 | GstVideoCaptionMeta *emeta = (GstVideoCaptionMeta *) meta; |
985 | |
|
986 | 0 | g_free (emeta->data); |
987 | 0 | } |
988 | | |
989 | | const GstMetaInfo * |
990 | | gst_video_caption_meta_get_info (void) |
991 | 0 | { |
992 | 0 | static const GstMetaInfo *meta_info = NULL; |
993 | |
|
994 | 0 | if (g_once_init_enter ((GstMetaInfo **) & meta_info)) { |
995 | 0 | const GstMetaInfo *mi = gst_meta_register (GST_VIDEO_CAPTION_META_API_TYPE, |
996 | 0 | "GstVideoCaptionMeta", |
997 | 0 | sizeof (GstVideoCaptionMeta), |
998 | 0 | gst_video_caption_meta_init, |
999 | 0 | gst_video_caption_meta_free, |
1000 | 0 | gst_video_caption_meta_transform); |
1001 | 0 | g_once_init_leave ((GstMetaInfo **) & meta_info, (GstMetaInfo *) mi); |
1002 | 0 | } |
1003 | 0 | return meta_info; |
1004 | 0 | } |
1005 | | |
1006 | | /** |
1007 | | * gst_buffer_add_video_caption_meta: |
1008 | | * @buffer: a #GstBuffer |
1009 | | * @caption_type: The type of Closed Caption to add |
1010 | | * @data: (array length=size) (transfer none): The Closed Caption data |
1011 | | * @size: The size of @data in bytes |
1012 | | * |
1013 | | * Attaches #GstVideoCaptionMeta metadata to @buffer with the given |
1014 | | * parameters. |
1015 | | * |
1016 | | * Returns: (transfer none): the #GstVideoCaptionMeta on @buffer. |
1017 | | * |
1018 | | * Since: 1.16 |
1019 | | */ |
1020 | | GstVideoCaptionMeta * |
1021 | | gst_buffer_add_video_caption_meta (GstBuffer * buffer, |
1022 | | GstVideoCaptionType caption_type, const guint8 * data, gsize size) |
1023 | 0 | { |
1024 | 0 | GstVideoCaptionMeta *meta; |
1025 | |
|
1026 | 0 | g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL); |
1027 | 0 | g_return_val_if_fail (data != NULL, NULL); |
1028 | 0 | g_return_val_if_fail (size > 0, NULL); |
1029 | | |
1030 | 0 | switch (caption_type) { |
1031 | 0 | case GST_VIDEO_CAPTION_TYPE_CEA608_RAW: |
1032 | 0 | case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A: |
1033 | 0 | case GST_VIDEO_CAPTION_TYPE_CEA708_RAW: |
1034 | 0 | case GST_VIDEO_CAPTION_TYPE_CEA708_CDP: |
1035 | 0 | break; |
1036 | 0 | default: |
1037 | 0 | GST_ERROR ("Unknown caption type !"); |
1038 | 0 | return NULL; |
1039 | 0 | } |
1040 | | /* FIXME : Add checks for content ? */ |
1041 | | |
1042 | 0 | meta = (GstVideoCaptionMeta *) gst_buffer_add_meta (buffer, |
1043 | 0 | GST_VIDEO_CAPTION_META_INFO, NULL); |
1044 | 0 | g_return_val_if_fail (meta != NULL, NULL); |
1045 | | |
1046 | 0 | meta->caption_type = caption_type; |
1047 | 0 | meta->data = g_memdup2 (data, size); |
1048 | 0 | meta->size = size; |
1049 | |
|
1050 | 0 | return meta; |
1051 | 0 | } |
1052 | | |
1053 | | /** |
1054 | | * gst_video_caption_type_from_caps: |
1055 | | * @caps: Fixed #GstCaps to parse |
1056 | | * |
1057 | | * Parses fixed Closed Caption #GstCaps and returns the corresponding caption |
1058 | | * type, or %GST_VIDEO_CAPTION_TYPE_UNKNOWN. |
1059 | | * |
1060 | | * Returns: #GstVideoCaptionType. |
1061 | | * |
1062 | | * Since: 1.16 |
1063 | | */ |
1064 | | GstVideoCaptionType |
1065 | | gst_video_caption_type_from_caps (const GstCaps * caps) |
1066 | 0 | { |
1067 | 0 | const GstStructure *s; |
1068 | 0 | const gchar *format; |
1069 | |
|
1070 | 0 | g_return_val_if_fail (gst_caps_is_fixed (caps), |
1071 | 0 | GST_VIDEO_CAPTION_TYPE_UNKNOWN); |
1072 | | |
1073 | 0 | s = gst_caps_get_structure (caps, 0); |
1074 | 0 | g_return_val_if_fail (s != NULL, GST_VIDEO_CAPTION_TYPE_UNKNOWN); |
1075 | | |
1076 | 0 | format = gst_structure_get_string (s, "format"); |
1077 | 0 | if (gst_structure_has_name (s, "closedcaption/x-cea-608")) { |
1078 | 0 | if (g_strcmp0 (format, "raw") == 0) { |
1079 | 0 | return GST_VIDEO_CAPTION_TYPE_CEA608_RAW; |
1080 | 0 | } else if (g_strcmp0 (format, "s334-1a") == 0) { |
1081 | 0 | return GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A; |
1082 | 0 | } |
1083 | 0 | } else if (gst_structure_has_name (s, "closedcaption/x-cea-708")) { |
1084 | 0 | if (g_strcmp0 (format, "cc_data") == 0) { |
1085 | 0 | return GST_VIDEO_CAPTION_TYPE_CEA708_RAW; |
1086 | 0 | } else if (g_strcmp0 (format, "cdp") == 0) { |
1087 | 0 | return GST_VIDEO_CAPTION_TYPE_CEA708_CDP; |
1088 | 0 | } |
1089 | 0 | } |
1090 | 0 | return GST_VIDEO_CAPTION_TYPE_UNKNOWN; |
1091 | 0 | } |
1092 | | |
1093 | | /** |
1094 | | * gst_video_caption_type_to_caps: |
1095 | | * @type: #GstVideoCaptionType |
1096 | | * |
1097 | | * Creates new caps corresponding to @type. |
1098 | | * |
1099 | | * Returns: (transfer full): new #GstCaps |
1100 | | * |
1101 | | * Since: 1.16 |
1102 | | */ |
1103 | | GstCaps * |
1104 | | gst_video_caption_type_to_caps (GstVideoCaptionType type) |
1105 | 0 | { |
1106 | 0 | GstCaps *caption_caps; |
1107 | |
|
1108 | 0 | g_return_val_if_fail (type != GST_VIDEO_CAPTION_TYPE_UNKNOWN, NULL); |
1109 | | |
1110 | 0 | switch (type) { |
1111 | 0 | case GST_VIDEO_CAPTION_TYPE_CEA608_RAW: |
1112 | 0 | caption_caps = gst_caps_new_simple ("closedcaption/x-cea-608", |
1113 | 0 | "format", G_TYPE_STRING, "raw", NULL); |
1114 | 0 | break; |
1115 | 0 | case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A: |
1116 | 0 | caption_caps = gst_caps_new_simple ("closedcaption/x-cea-608", |
1117 | 0 | "format", G_TYPE_STRING, "s334-1a", NULL); |
1118 | 0 | break; |
1119 | 0 | case GST_VIDEO_CAPTION_TYPE_CEA708_RAW: |
1120 | 0 | caption_caps = gst_caps_new_simple ("closedcaption/x-cea-708", |
1121 | 0 | "format", G_TYPE_STRING, "cc_data", NULL); |
1122 | 0 | break; |
1123 | 0 | case GST_VIDEO_CAPTION_TYPE_CEA708_CDP: |
1124 | 0 | caption_caps = gst_caps_new_simple ("closedcaption/x-cea-708", |
1125 | 0 | "format", G_TYPE_STRING, "cdp", NULL); |
1126 | 0 | break; |
1127 | 0 | default: |
1128 | 0 | g_return_val_if_reached (NULL); |
1129 | 0 | break; |
1130 | 0 | } |
1131 | | |
1132 | 0 | return caption_caps; |
1133 | 0 | } |
1134 | | |
1135 | | /* Ancillary Meta Implementation */ |
1136 | | |
1137 | | GType |
1138 | | gst_ancillary_meta_api_get_type (void) |
1139 | 0 | { |
1140 | 0 | static GType type = 0; |
1141 | |
|
1142 | 0 | if (g_once_init_enter (&type)) { |
1143 | 0 | static const gchar *tags[] = { NULL }; |
1144 | 0 | GType _type = gst_meta_api_type_register ("GstAncillaryMetaAPI", tags); |
1145 | 0 | GST_INFO ("registering"); |
1146 | 0 | g_once_init_leave (&type, _type); |
1147 | 0 | } |
1148 | 0 | return type; |
1149 | 0 | } |
1150 | | |
1151 | | static gboolean |
1152 | | gst_ancillary_meta_init (GstMeta * meta, gpointer params, GstBuffer * buffer) |
1153 | 0 | { |
1154 | 0 | GstAncillaryMeta *ameta = (GstAncillaryMeta *) meta; |
1155 | | |
1156 | | /* Set sensible default values */ |
1157 | 0 | ameta->field = GST_ANCILLARY_META_FIELD_PROGRESSIVE; |
1158 | 0 | ameta->c_not_y_channel = 0; |
1159 | 0 | ameta->line = 0x7ff; |
1160 | 0 | ameta->offset = 0xfff; |
1161 | |
|
1162 | 0 | ameta->DID = ameta->SDID_block_number = ameta->data_count = 0; |
1163 | 0 | ameta->data = NULL; |
1164 | 0 | ameta->checksum = 0; |
1165 | |
|
1166 | 0 | return TRUE; |
1167 | 0 | } |
1168 | | |
1169 | | static void |
1170 | | gst_ancillary_meta_free (GstMeta * meta, GstBuffer * buffer) |
1171 | 0 | { |
1172 | 0 | GstAncillaryMeta *ameta = (GstAncillaryMeta *) meta; |
1173 | |
|
1174 | 0 | g_free (ameta->data); |
1175 | 0 | } |
1176 | | |
1177 | | static gboolean |
1178 | | gst_ancillary_meta_transform (GstBuffer * dest, GstMeta * meta, |
1179 | | GstBuffer * buffer, GQuark type, gpointer data) |
1180 | 0 | { |
1181 | 0 | GstAncillaryMeta *dmeta, *smeta; |
1182 | | |
1183 | | /* We always copy over the ancillary meta */ |
1184 | 0 | smeta = (GstAncillaryMeta *) meta; |
1185 | |
|
1186 | 0 | dmeta = |
1187 | 0 | (GstAncillaryMeta *) gst_buffer_add_meta (dest, GST_ANCILLARY_META_INFO, |
1188 | 0 | NULL); |
1189 | |
|
1190 | 0 | dmeta->field = smeta->field; |
1191 | 0 | dmeta->c_not_y_channel = smeta->c_not_y_channel; |
1192 | 0 | dmeta->line = smeta->line; |
1193 | 0 | dmeta->offset = smeta->offset; |
1194 | 0 | dmeta->DID = smeta->DID; |
1195 | 0 | dmeta->SDID_block_number = smeta->SDID_block_number; |
1196 | 0 | dmeta->data_count = smeta->data_count; |
1197 | 0 | dmeta->data = g_memdup2 (smeta->data, (smeta->data_count & 0xff) * 2); |
1198 | 0 | dmeta->checksum = smeta->checksum; |
1199 | |
|
1200 | 0 | return TRUE; |
1201 | 0 | } |
1202 | | |
1203 | | const GstMetaInfo * |
1204 | | gst_ancillary_meta_get_info (void) |
1205 | 0 | { |
1206 | 0 | static const GstMetaInfo *meta_info = NULL; |
1207 | |
|
1208 | 0 | if (g_once_init_enter ((GstMetaInfo **) & meta_info)) { |
1209 | 0 | const GstMetaInfo *mi = gst_meta_register (GST_ANCILLARY_META_API_TYPE, |
1210 | 0 | "GstAncillaryMeta", |
1211 | 0 | sizeof (GstAncillaryMeta), |
1212 | 0 | gst_ancillary_meta_init, |
1213 | 0 | gst_ancillary_meta_free, |
1214 | 0 | gst_ancillary_meta_transform); |
1215 | 0 | g_once_init_leave ((GstMetaInfo **) & meta_info, (GstMetaInfo *) mi); |
1216 | 0 | } |
1217 | 0 | return meta_info; |
1218 | |
|
1219 | 0 | } |
1220 | | |
1221 | | /** |
1222 | | * gst_buffer_add_ancillary_meta: |
1223 | | * @buffer: A #GstBuffer |
1224 | | * |
1225 | | * Adds a new #GstAncillaryMeta to the @buffer. The caller is responsible for setting the appropriate |
1226 | | * fields. |
1227 | | * |
1228 | | * Since: 1.24 |
1229 | | * |
1230 | | * Returns: (transfer none): A new #GstAncillaryMeta, or %NULL if an error happened. |
1231 | | */ |
1232 | | |
1233 | | GstAncillaryMeta * |
1234 | | gst_buffer_add_ancillary_meta (GstBuffer * buffer) |
1235 | 0 | { |
1236 | 0 | GstAncillaryMeta *meta; |
1237 | |
|
1238 | 0 | meta = |
1239 | 0 | (GstAncillaryMeta *) gst_buffer_add_meta (buffer, GST_ANCILLARY_META_INFO, |
1240 | 0 | NULL); |
1241 | 0 | g_assert (meta != NULL); |
1242 | | |
1243 | 0 | return meta; |
1244 | 0 | } |
1245 | | |
1246 | | /* Active Format Description (AFD) Meta implementation */ |
1247 | | |
1248 | | GType |
1249 | | gst_video_afd_meta_api_get_type (void) |
1250 | 0 | { |
1251 | 0 | static GType type = 0; |
1252 | |
|
1253 | 0 | if (g_once_init_enter (&type)) { |
1254 | 0 | static const gchar *tags[] = { |
1255 | 0 | GST_META_TAG_VIDEO_SIZE_STR, |
1256 | 0 | GST_META_TAG_VIDEO_ORIENTATION_STR, |
1257 | 0 | GST_META_TAG_VIDEO_STR, |
1258 | 0 | NULL |
1259 | 0 | }; |
1260 | 0 | GType _type = gst_meta_api_type_register ("GstVideoAFDMetaAPI", tags); |
1261 | 0 | g_once_init_leave (&type, _type); |
1262 | 0 | } |
1263 | 0 | return type; |
1264 | 0 | } |
1265 | | |
1266 | | static gboolean |
1267 | | gst_video_afd_meta_init (GstMeta * meta, gpointer params, GstBuffer * buffer) |
1268 | 0 | { |
1269 | 0 | GstVideoAFDMeta *emeta = (GstVideoAFDMeta *) meta; |
1270 | |
|
1271 | 0 | emeta->field = 0; |
1272 | 0 | emeta->spec = GST_VIDEO_AFD_SPEC_ATSC_A53; |
1273 | 0 | emeta->afd = GST_VIDEO_AFD_UNAVAILABLE; |
1274 | |
|
1275 | 0 | return TRUE; |
1276 | 0 | } |
1277 | | |
1278 | | static gboolean |
1279 | | gst_video_afd_meta_transform (GstBuffer * dest, GstMeta * meta, |
1280 | | GstBuffer * buffer, GQuark type, gpointer data) |
1281 | 0 | { |
1282 | 0 | GstVideoAFDMeta *smeta = (GstVideoAFDMeta *) meta; |
1283 | |
|
1284 | 0 | if (GST_META_TRANSFORM_IS_COPY (type)) { |
1285 | 0 | GST_DEBUG ("copy AFD metadata"); |
1286 | 0 | gst_buffer_add_video_afd_meta (dest, smeta->field, smeta->spec, smeta->afd); |
1287 | 0 | return TRUE; |
1288 | 0 | } else if (GST_VIDEO_META_TRANSFORM_IS_SCALE (type)) { |
1289 | 0 | GstVideoMetaTransform *trans = data; |
1290 | 0 | gdouble diff; |
1291 | 0 | gint ow, oh, nw, nh; |
1292 | 0 | gint opn, opd, npn, npd; |
1293 | |
|
1294 | 0 | ow = GST_VIDEO_INFO_WIDTH (trans->in_info); |
1295 | 0 | nw = GST_VIDEO_INFO_WIDTH (trans->out_info); |
1296 | 0 | oh = GST_VIDEO_INFO_HEIGHT (trans->in_info); |
1297 | 0 | nh = GST_VIDEO_INFO_HEIGHT (trans->out_info); |
1298 | 0 | opn = GST_VIDEO_INFO_PAR_N (trans->in_info); |
1299 | 0 | opd = GST_VIDEO_INFO_PAR_D (trans->in_info); |
1300 | 0 | npn = GST_VIDEO_INFO_PAR_N (trans->out_info); |
1301 | 0 | npd = GST_VIDEO_INFO_PAR_D (trans->out_info); |
1302 | | |
1303 | | /* if the aspect ratio stays the same we can copy the meta, otherwise |
1304 | | * we can't know if the aspect ratio was changed or black borders were |
1305 | | * introduced. Both would invalidate the AFD meta */ |
1306 | |
|
1307 | 0 | diff = |
1308 | 0 | ABS (((gdouble) ow / (gdouble) oh) * ((gdouble) opn / (gdouble) opd) - |
1309 | 0 | ((gdouble) nw / (gdouble) nh) * ((gdouble) npn / (gdouble) npd)); |
1310 | 0 | if (diff < 0.0001) { |
1311 | 0 | GST_DEBUG ("copying AFD metadata, aspect ratio did not change"); |
1312 | 0 | gst_buffer_add_video_afd_meta (dest, smeta->field, smeta->spec, |
1313 | 0 | smeta->afd); |
1314 | 0 | return TRUE; |
1315 | 0 | } else { |
1316 | 0 | return FALSE; |
1317 | 0 | } |
1318 | 0 | } else { |
1319 | | /* return FALSE, if transform type is not supported */ |
1320 | 0 | return FALSE; |
1321 | 0 | } |
1322 | |
|
1323 | 0 | } |
1324 | | |
1325 | | const GstMetaInfo * |
1326 | | gst_video_afd_meta_get_info (void) |
1327 | 0 | { |
1328 | 0 | static const GstMetaInfo *meta_info = NULL; |
1329 | |
|
1330 | 0 | if (g_once_init_enter ((GstMetaInfo **) & meta_info)) { |
1331 | 0 | const GstMetaInfo *mi = gst_meta_register (GST_VIDEO_AFD_META_API_TYPE, |
1332 | 0 | "GstVideoAFDMeta", |
1333 | 0 | sizeof (GstVideoAFDMeta), |
1334 | 0 | gst_video_afd_meta_init, |
1335 | 0 | NULL, |
1336 | 0 | gst_video_afd_meta_transform); |
1337 | 0 | g_once_init_leave ((GstMetaInfo **) & meta_info, (GstMetaInfo *) mi); |
1338 | 0 | } |
1339 | 0 | return meta_info; |
1340 | 0 | } |
1341 | | |
1342 | | /** |
1343 | | * gst_buffer_add_video_afd_meta: |
1344 | | * @buffer: a #GstBuffer |
1345 | | * @field: 0 for progressive or field 1 and 1 for field 2 |
1346 | | * @spec: #GstVideoAFDSpec that applies to AFD value |
1347 | | * @afd: #GstVideoAFDValue AFD enumeration |
1348 | | * |
1349 | | * Attaches #GstVideoAFDMeta metadata to @buffer with the given |
1350 | | * parameters. |
1351 | | * |
1352 | | * Returns: (transfer none): the #GstVideoAFDMeta on @buffer. |
1353 | | * |
1354 | | * Since: 1.18 |
1355 | | */ |
1356 | | GstVideoAFDMeta * |
1357 | | gst_buffer_add_video_afd_meta (GstBuffer * buffer, guint8 field, |
1358 | | GstVideoAFDSpec spec, GstVideoAFDValue afd) |
1359 | 0 | { |
1360 | 0 | GstVideoAFDMeta *meta; |
1361 | 0 | gint8 afd_data GST_UNUSED_CHECKS = (gint8) afd; |
1362 | 0 | g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL); |
1363 | 0 | g_return_val_if_fail (field <= 1, NULL); |
1364 | 0 | g_return_val_if_fail ((guint8) spec <= 2, NULL); |
1365 | | /* AFD is stored in a nybble */ |
1366 | 0 | g_return_val_if_fail (afd_data <= 0xF, NULL); |
1367 | | /* reserved values for all specifications */ |
1368 | 0 | g_return_val_if_fail (afd_data != 1 && (afd_data < 5 || afd_data > 7) |
1369 | 0 | && afd_data != 12, NULL); |
1370 | | /* reserved for DVB/ETSI */ |
1371 | 0 | g_return_val_if_fail ((spec != GST_VIDEO_AFD_SPEC_DVB_ETSI) |
1372 | 0 | || (afd_data != 0), NULL); |
1373 | | |
1374 | 0 | meta = (GstVideoAFDMeta *) gst_buffer_add_meta (buffer, |
1375 | 0 | GST_VIDEO_AFD_META_INFO, NULL); |
1376 | 0 | g_assert (meta != NULL); |
1377 | | |
1378 | 0 | meta->field = field; |
1379 | 0 | meta->spec = spec; |
1380 | 0 | meta->afd = afd; |
1381 | |
|
1382 | 0 | return meta; |
1383 | 0 | } |
1384 | | |
1385 | | /* Bar Meta implementation */ |
1386 | | |
1387 | | GType |
1388 | | gst_video_bar_meta_api_get_type (void) |
1389 | 0 | { |
1390 | 0 | static GType type = 0; |
1391 | |
|
1392 | 0 | if (g_once_init_enter (&type)) { |
1393 | 0 | static const gchar *tags[] = { |
1394 | 0 | GST_META_TAG_VIDEO_SIZE_STR, |
1395 | 0 | GST_META_TAG_VIDEO_ORIENTATION_STR, |
1396 | 0 | GST_META_TAG_VIDEO_STR, |
1397 | 0 | NULL |
1398 | 0 | }; |
1399 | 0 | GType _type = gst_meta_api_type_register ("GstVideoBarMetaAPI", tags); |
1400 | 0 | g_once_init_leave (&type, _type); |
1401 | 0 | } |
1402 | 0 | return type; |
1403 | 0 | } |
1404 | | |
1405 | | static gboolean |
1406 | | gst_video_bar_meta_init (GstMeta * meta, gpointer params, GstBuffer * buffer) |
1407 | 0 | { |
1408 | 0 | GstVideoBarMeta *emeta = (GstVideoBarMeta *) meta; |
1409 | |
|
1410 | 0 | emeta->field = 0; |
1411 | 0 | emeta->is_letterbox = FALSE; |
1412 | 0 | emeta->bar_data1 = 0; |
1413 | 0 | emeta->bar_data2 = 0; |
1414 | |
|
1415 | 0 | return TRUE; |
1416 | 0 | } |
1417 | | |
1418 | | static gboolean |
1419 | | gst_video_bar_meta_transform (GstBuffer * dest, GstMeta * meta, |
1420 | | GstBuffer * buffer, GQuark type, gpointer data) |
1421 | 0 | { |
1422 | 0 | GstVideoBarMeta *smeta = (GstVideoBarMeta *) meta; |
1423 | |
|
1424 | 0 | if (GST_META_TRANSFORM_IS_COPY (type)) { |
1425 | 0 | GST_DEBUG ("copy Bar metadata"); |
1426 | 0 | gst_buffer_add_video_bar_meta (dest, smeta->field, smeta->is_letterbox, |
1427 | 0 | smeta->bar_data1, smeta->bar_data2); |
1428 | 0 | return TRUE; |
1429 | 0 | } else { |
1430 | | /* return FALSE, if transform type is not supported */ |
1431 | 0 | return FALSE; |
1432 | 0 | } |
1433 | 0 | } |
1434 | | |
1435 | | const GstMetaInfo * |
1436 | | gst_video_bar_meta_get_info (void) |
1437 | 0 | { |
1438 | 0 | static const GstMetaInfo *meta_info = NULL; |
1439 | |
|
1440 | 0 | if (g_once_init_enter ((GstMetaInfo **) & meta_info)) { |
1441 | 0 | const GstMetaInfo *mi = gst_meta_register (GST_VIDEO_BAR_META_API_TYPE, |
1442 | 0 | "GstVideoBarMeta", |
1443 | 0 | sizeof (GstVideoBarMeta), |
1444 | 0 | gst_video_bar_meta_init, |
1445 | 0 | NULL, |
1446 | 0 | gst_video_bar_meta_transform); |
1447 | 0 | g_once_init_leave ((GstMetaInfo **) & meta_info, (GstMetaInfo *) mi); |
1448 | 0 | } |
1449 | 0 | return meta_info; |
1450 | 0 | } |
1451 | | |
1452 | | /** |
1453 | | * gst_buffer_add_video_bar_meta: |
1454 | | * @buffer: a #GstBuffer |
1455 | | * @field: 0 for progressive or field 1 and 1 for field 2 |
1456 | | * @is_letterbox: if true then bar data specifies letterbox, otherwise pillarbox |
1457 | | * @bar_data1: If @is_letterbox is true, then the value specifies the |
1458 | | * last line of a horizontal letterbox bar area at top of reconstructed frame. |
1459 | | * Otherwise, it specifies the last horizontal luminance sample of a vertical pillarbox |
1460 | | * bar area at the left side of the reconstructed frame |
1461 | | * @bar_data2: If @is_letterbox is true, then the value specifies the |
1462 | | * first line of a horizontal letterbox bar area at bottom of reconstructed frame. |
1463 | | * Otherwise, it specifies the first horizontal |
1464 | | * luminance sample of a vertical pillarbox bar area at the right side of the reconstructed frame. |
1465 | | * |
1466 | | * Attaches #GstVideoBarMeta metadata to @buffer with the given |
1467 | | * parameters. |
1468 | | * |
1469 | | * Returns: (transfer none): the #GstVideoBarMeta on @buffer. |
1470 | | * |
1471 | | * See Table 6.11 Bar Data Syntax |
1472 | | * |
1473 | | * https://www.atsc.org/wp-content/uploads/2015/03/a_53-Part-4-2009.pdf |
1474 | | * |
1475 | | * Since: 1.18 |
1476 | | */ |
1477 | | GstVideoBarMeta * |
1478 | | gst_buffer_add_video_bar_meta (GstBuffer * buffer, guint8 field, |
1479 | | gboolean is_letterbox, guint bar_data1, guint bar_data2) |
1480 | 0 | { |
1481 | 0 | GstVideoBarMeta *meta; |
1482 | |
|
1483 | 0 | g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL); |
1484 | 0 | g_return_val_if_fail (field <= 1, NULL); |
1485 | | |
1486 | 0 | meta = (GstVideoBarMeta *) gst_buffer_add_meta (buffer, |
1487 | 0 | GST_VIDEO_BAR_META_INFO, NULL); |
1488 | 0 | g_assert (meta != NULL); |
1489 | | |
1490 | 0 | meta->field = field; |
1491 | 0 | meta->is_letterbox = is_letterbox; |
1492 | 0 | meta->bar_data1 = bar_data1; |
1493 | 0 | meta->bar_data2 = bar_data2; |
1494 | 0 | return meta; |
1495 | 0 | } |