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