1 /* GStreamer Sub-Picture Unit - VobSub/DVD handling
2 * Copyright (C) 2009 Jan Schmidt <thaytan@noraisin.net>
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.
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.
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.
27 #include "gstdvdspu.h"
28 #include "gstspu-vobsub.h"
30 GST_DEBUG_CATEGORY_EXTERN (dvdspu_debug);
31 #define GST_CAT_DEFAULT dvdspu_debug
33 /* Define to dump out a text description of the incoming SPU commands */
36 /* Convert an STM offset in the SPU sequence to a GStreamer timestamp */
37 #define STM_TO_GST(stm) ((GST_MSECOND * 1024 * (stm)) / 90)
39 typedef enum SpuVobsubCmd SpuVobsubCmd;
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 */
55 gst_dvd_spu_parse_chg_colcon (GstDVDSpu * dvdspu, guint8 * data, guint8 * end)
57 SpuState *state = &dvdspu->spu_state;
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;
68 GST_DEBUG_OBJECT (dvdspu, "Change Color & Contrast. Pixel data = %d bytes",
69 (gint16) (end - data));
71 /* Count the number of entries we'll need */
73 for (cur = data; cur < end;) {
80 code = GST_READ_UINT32_BE (cur);
81 if (code == 0x0fffffff)
82 break; /* Termination code */
84 n_changes = CLAMP ((cur[2] >> 4), 1, 8);
85 cur += 4 + (6 * n_changes);
88 break; /* Invalid entry overrunning buffer */
93 state->vobsub.n_line_ctrl_i = n_entries;
94 state->vobsub.line_ctrl_i = g_new (SpuVobsubLineCtrlI, n_entries);
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);
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];
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);
110 for (c = 0; c < n_changes; c++) {
111 SpuVobsubPixCtrlI *cur_pix_ctrl = cur_line_ctrl->pix_ctrl_i + c;
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);
123 gst_dvd_spu_exec_cmd_blk (GstDVDSpu * dvdspu, guint8 * data, guint8 * end)
125 SpuState *state = &dvdspu->spu_state;
133 case SPU_CMD_FSTA_DSP:
134 GST_DEBUG_OBJECT (dvdspu, " Forced Display");
135 state->flags |= SPU_STATE_FORCED_DSP;
139 GST_DEBUG_OBJECT (dvdspu, " Display On");
140 state->flags |= SPU_STATE_DISPLAY;
143 case SPU_CMD_STP_DSP:
144 GST_DEBUG_OBJECT (dvdspu, " Display Off");
145 state->flags &= ~(SPU_STATE_FORCED_DSP | SPU_STATE_DISPLAY);
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 */
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;
157 state->vobsub.main_pal_dirty = TRUE;
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]);
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 */
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;
175 state->vobsub.main_pal_dirty = TRUE;
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]);
184 case SPU_CMD_SET_DAREA:{
185 SpuRect *r = &state->vobsub.disp_rect;
187 if (G_UNLIKELY (data + 7 >= end))
188 return; /* Invalid SET_DAREA cmd at the end of the blk */
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];
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);
203 if (G_UNLIKELY (data + 5 >= end))
204 return; /* Invalid SET_DSPXE cmd at the end of the blk */
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);
212 GST_DEBUG_OBJECT (dvdspu, " Set Pixel Data Offsets top: %u bot: %u",
213 state->vobsub.pix_data[0], state->vobsub.pix_data[1]);
218 case SPU_CMD_CHG_COLCON:{
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 */
226 field_size = GST_READ_UINT16_BE (data);
228 if (G_UNLIKELY (data + field_size >= end))
229 return; /* Invalid CHG_COLCON cmd at the end of the blk */
231 gst_dvd_spu_parse_chg_colcon (dvdspu, data + 2, data + field_size);
232 state->vobsub.line_ctrl_i_pal_dirty = TRUE;
238 GST_DEBUG_OBJECT (dvdspu, " END");
246 gst_dvd_spu_finish_spu_buf (GstDVDSpu * dvdspu)
248 SpuState *state = &dvdspu->spu_state;
250 state->next_ts = state->vobsub.base_ts = GST_CLOCK_TIME_NONE;
251 gst_buffer_replace (&state->vobsub.buf, NULL);
253 GST_DEBUG_OBJECT (dvdspu, "Finished SPU buffer");
257 gst_dvd_spu_setup_cmd_blk (GstDVDSpu * dvdspu, guint16 cmd_blk_offset,
258 guint8 * start, guint8 * end)
260 SpuState *state = &dvdspu->spu_state;
262 guint8 *cmd_blk = start + cmd_blk_offset;
264 if (G_UNLIKELY (cmd_blk + 5 >= end))
265 return FALSE; /* No valid command block to read */
267 delay = GST_READ_UINT16_BE (cmd_blk);
268 state->next_ts = state->vobsub.base_ts + STM_TO_GST (delay);
269 state->vobsub.cur_cmd_blk = cmd_blk_offset;
271 GST_DEBUG_OBJECT (dvdspu, "Setup CMD Block @ %u with TS %" GST_TIME_FORMAT,
272 state->vobsub.cur_cmd_blk, GST_TIME_ARGS (state->next_ts));
278 gst_dvd_spu_dump_dcsq (GstDVDSpu * dvdspu,
279 GstClockTime start_ts, GstBuffer * spu_buf)
281 guint16 cmd_blk_offset;
285 start = GST_BUFFER_DATA (spu_buf);
286 end = start + GST_BUFFER_SIZE (spu_buf);
288 g_return_if_fail (start != NULL);
291 next_blk = GST_READ_UINT16_BE (start + 2);
294 /* Loop through all commands */
295 g_print ("SPU begins @ %" GST_TIME_FORMAT " offset %u\n",
296 GST_TIME_ARGS (start_ts), next_blk);
298 while (cmd_blk_offset != next_blk) {
300 GstClockTime cmd_blk_ts;
302 cmd_blk_offset = next_blk;
304 if (G_UNLIKELY (start + cmd_blk_offset + 5 >= end))
305 break; /* No valid command to read */
307 data = start + cmd_blk_offset;
309 cmd_blk_ts = start_ts + STM_TO_GST (GST_READ_UINT16_BE (data));
310 next_blk = GST_READ_UINT16_BE (data + 2);
312 g_print ("Cmd Blk @ offset %u next %u ts %" GST_TIME_FORMAT "\n",
313 cmd_blk_offset, next_blk, GST_TIME_ARGS (cmd_blk_ts));
316 gst_dvd_spu_exec_cmd_blk (dvdspu, data, end);
322 gstspu_vobsub_handle_new_buf (GstDVDSpu * dvdspu, GstClockTime event_ts,
327 SpuState *state = &dvdspu->spu_state;
330 gst_dvd_spu_dump_dcsq (dvdspu, event_ts, buf);
333 if (G_UNLIKELY (gst_buffer_get_size (buf) < 4))
336 if (state->vobsub.buf != NULL) {
337 gst_buffer_unref (state->vobsub.buf);
338 state->vobsub.buf = NULL;
340 state->vobsub.buf = buf;
341 state->vobsub.base_ts = event_ts;
343 gst_buffer_map (state->vobsub.buf, &map, GST_MAP_READ);
345 end = start + map.size;
347 /* Configure the first command block in this buffer as our initial blk */
348 state->vobsub.cur_cmd_blk = GST_READ_UINT16_BE (start + 2);
349 gst_dvd_spu_setup_cmd_blk (dvdspu, state->vobsub.cur_cmd_blk, start, end);
350 /* Clear existing chg-colcon info */
351 state->vobsub.n_line_ctrl_i = 0;
352 if (state->vobsub.line_ctrl_i != NULL) {
353 g_free (state->vobsub.line_ctrl_i);
354 state->vobsub.line_ctrl_i = NULL;
356 gst_buffer_unmap (state->vobsub.buf, &map);
361 gst_dvd_spu_finish_spu_buf (dvdspu);
365 gstspu_vobsub_execute_event (GstDVDSpu * dvdspu)
368 guint8 *start, *cmd_blk, *end;
370 SpuState *state = &dvdspu->spu_state;
372 if (state->vobsub.buf == NULL)
375 GST_DEBUG_OBJECT (dvdspu, "Executing cmd blk with TS %" GST_TIME_FORMAT
376 " @ offset %u", GST_TIME_ARGS (state->next_ts),
377 state->vobsub.cur_cmd_blk);
379 gst_buffer_map (state->vobsub.buf, &map, GST_MAP_READ);
381 end = start + map.size;
383 cmd_blk = start + state->vobsub.cur_cmd_blk;
385 if (G_UNLIKELY (cmd_blk + 5 >= end)) {
386 gst_buffer_unmap (state->vobsub.buf, &map);
387 /* Invalid. Finish the buffer and loop again */
388 gst_dvd_spu_finish_spu_buf (dvdspu);
392 gst_dvd_spu_exec_cmd_blk (dvdspu, cmd_blk + 4, end);
394 next_blk = GST_READ_UINT16_BE (cmd_blk + 2);
395 if (next_blk != state->vobsub.cur_cmd_blk) {
396 /* Advance to the next block of commands */
397 gst_dvd_spu_setup_cmd_blk (dvdspu, next_blk, start, end);
399 /* Next Block points to the current block, so we're finished with this
401 gst_buffer_unmap (state->vobsub.buf, &map);
402 gst_dvd_spu_finish_spu_buf (dvdspu);
405 gst_buffer_unmap (state->vobsub.buf, &map);
411 gstspu_vobsub_handle_dvd_event (GstDVDSpu * dvdspu, GstEvent * event)
413 const gchar *event_type;
414 const GstStructure *structure = gst_event_get_structure (event);
415 SpuState *state = &dvdspu->spu_state;
416 gboolean hl_change = FALSE;
418 event_type = gst_structure_get_string (structure, "event");
420 if (strcmp (event_type, "dvd-spu-clut-change") == 0) {
425 for (i = 0; i < 16; i++) {
426 g_snprintf (prop_name, 32, "clut%02d", i);
427 if (!gst_structure_get_int (structure, prop_name, &entry))
429 state->vobsub.current_clut[i] = (guint32) entry;
432 state->vobsub.main_pal_dirty = TRUE;
433 state->vobsub.hl_pal_dirty = TRUE;
434 state->vobsub.line_ctrl_i_pal_dirty = TRUE;
436 } else if (strcmp (event_type, "dvd-spu-highlight") == 0) {
439 if (gst_structure_get_int (structure, "palette", &val)) {
440 state->vobsub.hl_idx[3] = ((guint32) (val) >> 28) & 0x0f;
441 state->vobsub.hl_idx[2] = ((guint32) (val) >> 24) & 0x0f;
442 state->vobsub.hl_idx[1] = ((guint32) (val) >> 20) & 0x0f;
443 state->vobsub.hl_idx[0] = ((guint32) (val) >> 16) & 0x0f;
445 state->vobsub.hl_alpha[3] = ((guint32) (val) >> 12) & 0x0f;
446 state->vobsub.hl_alpha[2] = ((guint32) (val) >> 8) & 0x0f;
447 state->vobsub.hl_alpha[1] = ((guint32) (val) >> 4) & 0x0f;
448 state->vobsub.hl_alpha[0] = ((guint32) (val) >> 0) & 0x0f;
450 state->vobsub.hl_pal_dirty = TRUE;
452 if (gst_structure_get_int (structure, "sx", &val))
453 state->vobsub.hl_rect.left = (gint16) val;
454 if (gst_structure_get_int (structure, "sy", &val))
455 state->vobsub.hl_rect.top = (gint16) val;
456 if (gst_structure_get_int (structure, "ex", &val))
457 state->vobsub.hl_rect.right = (gint16) val;
458 if (gst_structure_get_int (structure, "ey", &val))
459 state->vobsub.hl_rect.bottom = (gint16) val;
461 GST_INFO_OBJECT (dvdspu, "Highlight rect is now (%d,%d) to (%d,%d)",
462 state->vobsub.hl_rect.left, state->vobsub.hl_rect.top,
463 state->vobsub.hl_rect.right, state->vobsub.hl_rect.bottom);
465 } else if (strcmp (event_type, "dvd-spu-reset-highlight") == 0) {
466 if (state->vobsub.hl_rect.top != -1 || state->vobsub.hl_rect.bottom != -1)
468 state->vobsub.hl_rect.top = -1;
469 state->vobsub.hl_rect.bottom = -1;
470 GST_INFO_OBJECT (dvdspu, "Highlight off");
471 } else if (strcmp (event_type, "dvd-set-subpicture-track") == 0) {
472 gboolean forced_only;
474 if (gst_structure_get_boolean (structure, "forced-only", &forced_only)) {
475 gboolean was_forced = (state->flags & SPU_STATE_FORCED_ONLY);
478 state->flags |= SPU_STATE_FORCED_ONLY;
480 state->flags &= ~(SPU_STATE_FORCED_ONLY);
482 if (was_forced != forced_only)
487 gst_event_unref (event);
493 gstspu_vobsub_flush (GstDVDSpu * dvdspu)
495 SpuState *state = &dvdspu->spu_state;
497 if (state->vobsub.buf) {
498 gst_buffer_unref (state->vobsub.buf);
499 state->vobsub.buf = NULL;
501 if (state->vobsub.pix_buf) {
502 gst_buffer_unref (state->vobsub.pix_buf);
503 state->vobsub.pix_buf = NULL;
506 state->vobsub.base_ts = GST_CLOCK_TIME_NONE;
507 state->vobsub.pix_data[0] = 0;
508 state->vobsub.pix_data[1] = 0;
510 state->vobsub.hl_rect.top = -1;
511 state->vobsub.hl_rect.bottom = -1;
513 state->vobsub.disp_rect.top = -1;
514 state->vobsub.disp_rect.bottom = -1;
516 state->vobsub.n_line_ctrl_i = 0;
517 if (state->vobsub.line_ctrl_i != NULL) {
518 g_free (state->vobsub.line_ctrl_i);
519 state->vobsub.line_ctrl_i = NULL;