1 /* GStreamer Sub-Picture Unit - VobSub/DVD handling
2 * Copyright (C) 2009 Jan Schmidt <thaytan@noraisin.net>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 #include <string.h>
24
25 #include <gst/gst.h>
26
27 #include "gstdvdspu.h"
28 #include "gstspu-vobsub.h"
29
30 GST_DEBUG_CATEGORY_EXTERN (dvdspu_debug);
31 #define GST_CAT_DEFAULT dvdspu_debug
32
33 /* Define to dump out a text description of the incoming SPU commands */
34 #define DUMP_DCSQ 0
35
36 /* Convert an STM offset in the SPU sequence to a GStreamer timestamp */
37 #define STM_TO_GST(stm) ((GST_MSECOND * 1024 * (stm)) / 90)
38
39 typedef enum SpuVobsubCmd SpuVobsubCmd;
40
41 enum SpuVobsubCmd
42 {
43 SPU_CMD_FSTA_DSP = 0x00, /* Forced Display */
44 SPU_CMD_DSP = 0x01, /* Display Start */
45 SPU_CMD_STP_DSP = 0x02, /* Display Off */
46 SPU_CMD_SET_COLOR = 0x03, /* Set the color indexes for the palette */
47 SPU_CMD_SET_ALPHA = 0x04, /* Set the alpha indexes for the palette */
48 SPU_CMD_SET_DAREA = 0x05, /* Set the display area for the SPU */
49 SPU_CMD_DSPXA = 0x06, /* Pixel data addresses */
50 SPU_CMD_CHG_COLCON = 0x07, /* Change Color & Contrast */
51 SPU_CMD_END = 0xff
52 };
53
54 static void
gst_dvd_spu_parse_chg_colcon(GstDVDSpu * dvdspu,guint8 * data,guint8 * end)55 gst_dvd_spu_parse_chg_colcon (GstDVDSpu * dvdspu, guint8 * data, guint8 * end)
56 {
57 SpuState *state = &dvdspu->spu_state;
58 guint8 *cur;
59 gint16 n_entries;
60 gint16 i;
61
62 /* Clear any existing chg colcon info */
63 state->vobsub.n_line_ctrl_i = 0;
64 if (state->vobsub.line_ctrl_i != NULL) {
65 g_free (state->vobsub.line_ctrl_i);
66 state->vobsub.line_ctrl_i = NULL;
67 }
68 GST_DEBUG_OBJECT (dvdspu, "Change Color & Contrast. Pixel data = %d bytes",
69 (gint16) (end - data));
70
71 /* Count the number of entries we'll need */
72 n_entries = 0;
73 for (cur = data; cur < end;) {
74 guint8 n_changes;
75 guint32 code;
76
77 if (cur + 4 > end)
78 break;
79
80 code = GST_READ_UINT32_BE (cur);
81 if (code == 0x0fffffff)
82 break; /* Termination code */
83
84 n_changes = CLAMP ((cur[2] >> 4), 1, 8);
85 cur += 4 + (6 * n_changes);
86
87 if (cur > end)
88 break; /* Invalid entry overrunning buffer */
89
90 n_entries++;
91 }
92
93 state->vobsub.n_line_ctrl_i = n_entries;
94 state->vobsub.line_ctrl_i = g_new (SpuVobsubLineCtrlI, n_entries);
95
96 cur = data;
97 for (i = 0; i < n_entries; i++) {
98 SpuVobsubLineCtrlI *cur_line_ctrl = state->vobsub.line_ctrl_i + i;
99 guint8 n_changes = CLAMP ((cur[2] >> 4), 1, 8);
100 guint8 c;
101
102 cur_line_ctrl->n_changes = n_changes;
103 cur_line_ctrl->top = ((cur[0] << 8) & 0x300) | cur[1];
104 cur_line_ctrl->bottom = ((cur[2] << 8) & 0x300) | cur[3];
105
106 GST_LOG_OBJECT (dvdspu, "ChgColcon Entry %d Top: %d Bottom: %d Changes: %d",
107 i, cur_line_ctrl->top, cur_line_ctrl->bottom, n_changes);
108 cur += 4;
109
110 for (c = 0; c < n_changes; c++) {
111 SpuVobsubPixCtrlI *cur_pix_ctrl = cur_line_ctrl->pix_ctrl_i + c;
112
113 cur_pix_ctrl->left = ((cur[0] << 8) & 0x300) | cur[1];
114 cur_pix_ctrl->palette = GST_READ_UINT32_BE (cur + 2);
115 GST_LOG_OBJECT (dvdspu, " %d: left: %d palette 0x%x", c,
116 cur_pix_ctrl->left, cur_pix_ctrl->palette);
117 cur += 6;
118 }
119 }
120 }
121
122 static void
gst_dvd_spu_exec_cmd_blk(GstDVDSpu * dvdspu,guint8 * data,guint8 * end)123 gst_dvd_spu_exec_cmd_blk (GstDVDSpu * dvdspu, guint8 * data, guint8 * end)
124 {
125 SpuState *state = &dvdspu->spu_state;
126
127 while (data < end) {
128 guint8 cmd;
129
130 cmd = data[0];
131
132 switch (cmd) {
133 case SPU_CMD_FSTA_DSP:
134 GST_DEBUG_OBJECT (dvdspu, " Forced Display");
135 state->flags |= SPU_STATE_FORCED_DSP;
136 data += 1;
137 break;
138 case SPU_CMD_DSP:
139 GST_DEBUG_OBJECT (dvdspu, " Display On");
140 state->flags |= SPU_STATE_DISPLAY;
141 data += 1;
142 break;
143 case SPU_CMD_STP_DSP:
144 GST_DEBUG_OBJECT (dvdspu, " Display Off");
145 state->flags &= ~(SPU_STATE_FORCED_DSP | SPU_STATE_DISPLAY);
146 data += 1;
147 break;
148 case SPU_CMD_SET_COLOR:{
149 if (G_UNLIKELY (data + 3 >= end))
150 return; /* Invalid SET_COLOR cmd at the end of the blk */
151
152 state->vobsub.main_idx[3] = data[1] >> 4;
153 state->vobsub.main_idx[2] = data[1] & 0x0f;
154 state->vobsub.main_idx[1] = data[2] >> 4;
155 state->vobsub.main_idx[0] = data[2] & 0x0f;
156
157 state->vobsub.main_pal_dirty = TRUE;
158
159 GST_DEBUG_OBJECT (dvdspu,
160 " Set Color bg %u pattern %u emph-1 %u emph-2 %u",
161 state->vobsub.main_idx[0], state->vobsub.main_idx[1],
162 state->vobsub.main_idx[2], state->vobsub.main_idx[3]);
163 data += 3;
164 break;
165 }
166 case SPU_CMD_SET_ALPHA:{
167 if (G_UNLIKELY (data + 3 >= end))
168 return; /* Invalid SET_ALPHA cmd at the end of the blk */
169
170 state->vobsub.main_alpha[3] = data[1] >> 4;
171 state->vobsub.main_alpha[2] = data[1] & 0x0f;
172 state->vobsub.main_alpha[1] = data[2] >> 4;
173 state->vobsub.main_alpha[0] = data[2] & 0x0f;
174
175 state->vobsub.main_pal_dirty = TRUE;
176
177 GST_DEBUG_OBJECT (dvdspu,
178 " Set Alpha bg %u pattern %u emph-1 %u emph-2 %u",
179 state->vobsub.main_alpha[0], state->vobsub.main_alpha[1],
180 state->vobsub.main_alpha[2], state->vobsub.main_alpha[3]);
181 data += 3;
182 break;
183 }
184 case SPU_CMD_SET_DAREA:{
185 SpuRect *r = &state->vobsub.disp_rect;
186
187 if (G_UNLIKELY (data + 7 >= end))
188 return; /* Invalid SET_DAREA cmd at the end of the blk */
189
190 r->top = ((data[4] & 0xff) << 4) | ((data[5] & 0xf0) >> 4);
191 r->left = ((data[1] & 0xff) << 4) | ((data[2] & 0xf0) >> 4);
192 r->right = ((data[2] & 0x0f) << 8) | data[3];
193 r->bottom = ((data[5] & 0x0f) << 8) | data[6];
194
195 GST_DEBUG_OBJECT (dvdspu,
196 " Set Display Area top %u left %u bottom %u right %u", r->top,
197 r->left, r->bottom, r->right);
198
199 data += 7;
200 break;
201 }
202 case SPU_CMD_DSPXA:{
203 if (G_UNLIKELY (data + 5 >= end))
204 return; /* Invalid SET_DSPXE cmd at the end of the blk */
205
206 state->vobsub.pix_data[0] = GST_READ_UINT16_BE (data + 1);
207 state->vobsub.pix_data[1] = GST_READ_UINT16_BE (data + 3);
208 /* Store a reference to the current command buffer, as that's where
209 * we'll need to take our pixel data from */
210 gst_buffer_replace (&state->vobsub.pix_buf, state->vobsub.buf);
211
212 GST_DEBUG_OBJECT (dvdspu, " Set Pixel Data Offsets top: %u bot: %u",
213 state->vobsub.pix_data[0], state->vobsub.pix_data[1]);
214
215 data += 5;
216 break;
217 }
218 case SPU_CMD_CHG_COLCON:{
219 guint16 field_size;
220
221 GST_DEBUG_OBJECT (dvdspu, " Set Color & Contrast Change");
222 if (G_UNLIKELY (data + 3 >= end))
223 return; /* Invalid CHG_COLCON cmd at the end of the blk */
224
225 data++;
226 field_size = GST_READ_UINT16_BE (data);
227
228 if (G_UNLIKELY (data + field_size >= end))
229 return; /* Invalid CHG_COLCON cmd at the end of the blk */
230
231 gst_dvd_spu_parse_chg_colcon (dvdspu, data + 2, data + field_size);
232 state->vobsub.line_ctrl_i_pal_dirty = TRUE;
233 data += field_size;
234 break;
235 }
236 case SPU_CMD_END:
237 default:
238 GST_DEBUG_OBJECT (dvdspu, " END");
239 data = end;
240 break;
241 }
242 }
243 }
244
245 static void
gst_dvd_spu_finish_spu_buf(GstDVDSpu * dvdspu)246 gst_dvd_spu_finish_spu_buf (GstDVDSpu * dvdspu)
247 {
248 SpuState *state = &dvdspu->spu_state;
249
250 state->next_ts = state->vobsub.base_ts = GST_CLOCK_TIME_NONE;
251 gst_buffer_replace (&state->vobsub.buf, NULL);
252
253 GST_DEBUG_OBJECT (dvdspu, "Finished SPU buffer");
254 }
255
256 static gboolean
gst_dvd_spu_setup_cmd_blk(GstDVDSpu * dvdspu,guint16 cmd_blk_offset,guint8 * start,guint8 * end)257 gst_dvd_spu_setup_cmd_blk (GstDVDSpu * dvdspu, guint16 cmd_blk_offset,
258 guint8 * start, guint8 * end)
259 {
260 SpuState *state = &dvdspu->spu_state;
261 guint16 delay;
262 guint8 *cmd_blk = start + cmd_blk_offset;
263
264 if (G_UNLIKELY (cmd_blk + 5 >= end)) {
265 GST_DEBUG_OBJECT (dvdspu, "No valid command block");
266 return FALSE; /* No valid command block to read */
267 }
268
269 delay = GST_READ_UINT16_BE (cmd_blk);
270 state->next_ts = state->vobsub.base_ts + STM_TO_GST (delay);
271 state->vobsub.cur_cmd_blk = cmd_blk_offset;
272
273 GST_DEBUG_OBJECT (dvdspu, "Setup CMD Block @ %u with TS %" GST_TIME_FORMAT,
274 state->vobsub.cur_cmd_blk, GST_TIME_ARGS (state->next_ts));
275 return TRUE;
276 }
277
278 #if DUMP_DCSQ
279 static void
gst_dvd_spu_dump_dcsq(GstDVDSpu * dvdspu,GstClockTime start_ts,GstBuffer * spu_buf)280 gst_dvd_spu_dump_dcsq (GstDVDSpu * dvdspu,
281 GstClockTime start_ts, GstBuffer * spu_buf)
282 {
283 guint16 cmd_blk_offset;
284 guint16 next_blk;
285 guint8 *start, *end;
286
287 start = GST_BUFFER_DATA (spu_buf);
288 end = start + GST_BUFFER_SIZE (spu_buf);
289
290 g_return_if_fail (start != NULL);
291
292 /* First command */
293 next_blk = GST_READ_UINT16_BE (start + 2);
294 cmd_blk_offset = 0;
295
296 /* Loop through all commands */
297 g_print ("SPU begins @ %" GST_TIME_FORMAT " offset %u\n",
298 GST_TIME_ARGS (start_ts), next_blk);
299
300 while (cmd_blk_offset != next_blk) {
301 guint8 *data;
302 GstClockTime cmd_blk_ts;
303
304 cmd_blk_offset = next_blk;
305
306 if (G_UNLIKELY (start + cmd_blk_offset + 5 >= end))
307 break; /* No valid command to read */
308
309 data = start + cmd_blk_offset;
310
311 cmd_blk_ts = start_ts + STM_TO_GST (GST_READ_UINT16_BE (data));
312 next_blk = GST_READ_UINT16_BE (data + 2);
313
314 g_print ("Cmd Blk @ offset %u next %u ts %" GST_TIME_FORMAT "\n",
315 cmd_blk_offset, next_blk, GST_TIME_ARGS (cmd_blk_ts));
316
317 data += 4;
318 gst_dvd_spu_exec_cmd_blk (dvdspu, data, end);
319 }
320 }
321 #endif
322
323 void
gstspu_vobsub_handle_new_buf(GstDVDSpu * dvdspu,GstClockTime event_ts,GstBuffer * buf)324 gstspu_vobsub_handle_new_buf (GstDVDSpu * dvdspu, GstClockTime event_ts,
325 GstBuffer * buf)
326 {
327 GstMapInfo map;
328 guint8 *start, *end;
329 SpuState *state = &dvdspu->spu_state;
330
331 #if DUMP_DCSQ
332 gst_dvd_spu_dump_dcsq (dvdspu, event_ts, buf);
333 #endif
334
335 if (G_UNLIKELY (gst_buffer_get_size (buf) < 4))
336 goto invalid;
337
338 if (state->vobsub.buf != NULL) {
339 gst_buffer_unref (state->vobsub.buf);
340 state->vobsub.buf = NULL;
341 }
342 state->vobsub.buf = buf;
343 state->vobsub.base_ts = event_ts;
344
345 gst_buffer_map (state->vobsub.buf, &map, GST_MAP_READ);
346 start = map.data;
347 end = start + map.size;
348
349 /* Configure the first command block in this buffer as our initial blk */
350 state->vobsub.cur_cmd_blk = GST_READ_UINT16_BE (start + 2);
351 gst_dvd_spu_setup_cmd_blk (dvdspu, state->vobsub.cur_cmd_blk, start, end);
352 /* Clear existing chg-colcon info */
353 state->vobsub.n_line_ctrl_i = 0;
354 if (state->vobsub.line_ctrl_i != NULL) {
355 g_free (state->vobsub.line_ctrl_i);
356 state->vobsub.line_ctrl_i = NULL;
357 }
358 gst_buffer_unmap (state->vobsub.buf, &map);
359 return;
360
361 invalid:
362 /* Invalid buffer */
363 gst_dvd_spu_finish_spu_buf (dvdspu);
364 }
365
366 gboolean
gstspu_vobsub_execute_event(GstDVDSpu * dvdspu)367 gstspu_vobsub_execute_event (GstDVDSpu * dvdspu)
368 {
369 GstMapInfo map;
370 guint8 *start, *cmd_blk, *end;
371 guint16 next_blk;
372 SpuState *state = &dvdspu->spu_state;
373 gboolean ret = TRUE;
374
375 if (state->vobsub.buf == NULL)
376 return FALSE;
377
378 GST_DEBUG_OBJECT (dvdspu, "Executing cmd blk with TS %" GST_TIME_FORMAT
379 " @ offset %u", GST_TIME_ARGS (state->next_ts),
380 state->vobsub.cur_cmd_blk);
381
382 gst_buffer_map (state->vobsub.buf, &map, GST_MAP_READ);
383 start = map.data;
384 end = start + map.size;
385
386 cmd_blk = start + state->vobsub.cur_cmd_blk;
387
388 if (G_UNLIKELY (cmd_blk + 5 >= end)) {
389 gst_buffer_unmap (state->vobsub.buf, &map);
390 /* Invalid. Finish the buffer and loop again */
391 gst_dvd_spu_finish_spu_buf (dvdspu);
392 return FALSE;
393 }
394
395 gst_dvd_spu_exec_cmd_blk (dvdspu, cmd_blk + 4, end);
396
397 next_blk = GST_READ_UINT16_BE (cmd_blk + 2);
398 if (next_blk != state->vobsub.cur_cmd_blk) {
399 /* Advance to the next block of commands */
400 ret = gst_dvd_spu_setup_cmd_blk (dvdspu, next_blk, start, end);
401 gst_buffer_unmap (state->vobsub.buf, &map);
402 } else {
403 /* Next Block points to the current block, so we're finished with this
404 * SPU buffer */
405 gst_buffer_unmap (state->vobsub.buf, &map);
406 gst_dvd_spu_finish_spu_buf (dvdspu);
407 ret = FALSE;
408 }
409
410 return ret;
411 }
412
413 gboolean
gstspu_vobsub_handle_dvd_event(GstDVDSpu * dvdspu,GstEvent * event)414 gstspu_vobsub_handle_dvd_event (GstDVDSpu * dvdspu, GstEvent * event)
415 {
416 const gchar *event_type;
417 const GstStructure *structure = gst_event_get_structure (event);
418 SpuState *state = &dvdspu->spu_state;
419 gboolean hl_change = FALSE;
420
421 event_type = gst_structure_get_string (structure, "event");
422
423 if (strcmp (event_type, "dvd-spu-clut-change") == 0) {
424 gchar prop_name[32];
425 gint i;
426 gint entry;
427
428 for (i = 0; i < 16; i++) {
429 g_snprintf (prop_name, 32, "clut%02d", i);
430 if (!gst_structure_get_int (structure, prop_name, &entry))
431 entry = 0;
432 state->vobsub.current_clut[i] = (guint32) entry;
433 }
434
435 state->vobsub.main_pal_dirty = TRUE;
436 state->vobsub.hl_pal_dirty = TRUE;
437 state->vobsub.line_ctrl_i_pal_dirty = TRUE;
438 hl_change = TRUE;
439 } else if (strcmp (event_type, "dvd-spu-highlight") == 0) {
440 gint val;
441
442 if (gst_structure_get_int (structure, "palette", &val)) {
443 state->vobsub.hl_idx[3] = ((guint32) (val) >> 28) & 0x0f;
444 state->vobsub.hl_idx[2] = ((guint32) (val) >> 24) & 0x0f;
445 state->vobsub.hl_idx[1] = ((guint32) (val) >> 20) & 0x0f;
446 state->vobsub.hl_idx[0] = ((guint32) (val) >> 16) & 0x0f;
447
448 state->vobsub.hl_alpha[3] = ((guint32) (val) >> 12) & 0x0f;
449 state->vobsub.hl_alpha[2] = ((guint32) (val) >> 8) & 0x0f;
450 state->vobsub.hl_alpha[1] = ((guint32) (val) >> 4) & 0x0f;
451 state->vobsub.hl_alpha[0] = ((guint32) (val) >> 0) & 0x0f;
452
453 state->vobsub.hl_pal_dirty = TRUE;
454 }
455 if (gst_structure_get_int (structure, "sx", &val))
456 state->vobsub.hl_rect.left = (gint16) val;
457 if (gst_structure_get_int (structure, "sy", &val))
458 state->vobsub.hl_rect.top = (gint16) val;
459 if (gst_structure_get_int (structure, "ex", &val))
460 state->vobsub.hl_rect.right = (gint16) val;
461 if (gst_structure_get_int (structure, "ey", &val))
462 state->vobsub.hl_rect.bottom = (gint16) val;
463
464 GST_INFO_OBJECT (dvdspu, "Highlight rect is now (%d,%d) to (%d,%d)",
465 state->vobsub.hl_rect.left, state->vobsub.hl_rect.top,
466 state->vobsub.hl_rect.right, state->vobsub.hl_rect.bottom);
467 hl_change = TRUE;
468 } else if (strcmp (event_type, "dvd-spu-reset-highlight") == 0) {
469 if (state->vobsub.hl_rect.top != -1 || state->vobsub.hl_rect.bottom != -1)
470 hl_change = TRUE;
471 state->vobsub.hl_rect.top = -1;
472 state->vobsub.hl_rect.bottom = -1;
473 GST_INFO_OBJECT (dvdspu, "Highlight off");
474 } else if (strcmp (event_type, "dvd-set-subpicture-track") == 0) {
475 gboolean forced_only;
476
477 if (gst_structure_get_boolean (structure, "forced-only", &forced_only)) {
478 gboolean was_forced = (state->flags & SPU_STATE_FORCED_ONLY);
479
480 if (forced_only)
481 state->flags |= SPU_STATE_FORCED_ONLY;
482 else
483 state->flags &= ~(SPU_STATE_FORCED_ONLY);
484
485 if (was_forced != forced_only)
486 hl_change = TRUE;
487 }
488 }
489
490 gst_event_unref (event);
491
492 return hl_change;
493 }
494
495 void
gstspu_vobsub_flush(GstDVDSpu * dvdspu)496 gstspu_vobsub_flush (GstDVDSpu * dvdspu)
497 {
498 SpuState *state = &dvdspu->spu_state;
499
500 if (state->vobsub.buf) {
501 gst_buffer_unref (state->vobsub.buf);
502 state->vobsub.buf = NULL;
503 }
504 if (state->vobsub.pix_buf) {
505 gst_buffer_unref (state->vobsub.pix_buf);
506 state->vobsub.pix_buf = NULL;
507 }
508
509 state->vobsub.base_ts = GST_CLOCK_TIME_NONE;
510 state->vobsub.pix_data[0] = 0;
511 state->vobsub.pix_data[1] = 0;
512
513 state->vobsub.hl_rect.top = -1;
514 state->vobsub.hl_rect.bottom = -1;
515
516 state->vobsub.disp_rect.top = -1;
517 state->vobsub.disp_rect.bottom = -1;
518
519 state->vobsub.n_line_ctrl_i = 0;
520 if (state->vobsub.line_ctrl_i != NULL) {
521 g_free (state->vobsub.line_ctrl_i);
522 state->vobsub.line_ctrl_i = NULL;
523 }
524 }
525