2d2bb290218c469e824e3a5f38d5427a57f6612b
[platform/upstream/gstreamer.git] / gst / dvdspu / gstspu-vobsub.c
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
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
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
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
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     return FALSE;               /* No valid command block to read */
266
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;
270
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));
273   return TRUE;
274 }
275
276 #if DUMP_DCSQ
277 static void
278 gst_dvd_spu_dump_dcsq (GstDVDSpu * dvdspu,
279     GstClockTime start_ts, GstBuffer * spu_buf)
280 {
281   guint16 cmd_blk_offset;
282   guint16 next_blk;
283   guint8 *start, *end;
284
285   start = GST_BUFFER_DATA (spu_buf);
286   end = start + GST_BUFFER_SIZE (spu_buf);
287
288   g_return_if_fail (start != NULL);
289
290   /* First command */
291   next_blk = GST_READ_UINT16_BE (start + 2);
292   cmd_blk_offset = 0;
293
294   /* Loop through all commands */
295   g_print ("SPU begins @ %" GST_TIME_FORMAT " offset %u\n",
296       GST_TIME_ARGS (start_ts), next_blk);
297
298   while (cmd_blk_offset != next_blk) {
299     guint8 *data;
300     GstClockTime cmd_blk_ts;
301
302     cmd_blk_offset = next_blk;
303
304     if (G_UNLIKELY (start + cmd_blk_offset + 5 >= end))
305       break;                    /* No valid command to read */
306
307     data = start + cmd_blk_offset;
308
309     cmd_blk_ts = start_ts + STM_TO_GST (GST_READ_UINT16_BE (data));
310     next_blk = GST_READ_UINT16_BE (data + 2);
311
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));
314
315     data += 4;
316     gst_dvd_spu_exec_cmd_blk (dvdspu, data, end);
317   }
318 }
319 #endif
320
321 void
322 gstspu_vobsub_handle_new_buf (GstDVDSpu * dvdspu, GstClockTime event_ts,
323     GstBuffer * buf)
324 {
325   GstMapInfo map;
326   guint8 *start, *end;
327   SpuState *state = &dvdspu->spu_state;
328
329 #if DUMP_DCSQ
330   gst_dvd_spu_dump_dcsq (dvdspu, event_ts, buf);
331 #endif
332
333   if (G_UNLIKELY (gst_buffer_get_size (buf) < 4))
334     goto invalid;
335
336   if (state->vobsub.buf != NULL) {
337     gst_buffer_unref (state->vobsub.buf);
338     state->vobsub.buf = NULL;
339   }
340   state->vobsub.buf = buf;
341   state->vobsub.base_ts = event_ts;
342
343   gst_buffer_map (state->vobsub.buf, &map, GST_MAP_READ);
344   start = map.data;
345   end = start + map.size;
346
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;
355   }
356   gst_buffer_unmap (state->vobsub.buf, &map);
357   return;
358
359 invalid:
360   /* Invalid buffer */
361   gst_dvd_spu_finish_spu_buf (dvdspu);
362 }
363
364 gboolean
365 gstspu_vobsub_execute_event (GstDVDSpu * dvdspu)
366 {
367   GstMapInfo map;
368   guint8 *start, *cmd_blk, *end;
369   guint16 next_blk;
370   SpuState *state = &dvdspu->spu_state;
371
372   if (state->vobsub.buf == NULL)
373     return FALSE;
374
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);
378
379   gst_buffer_map (state->vobsub.buf, &map, GST_MAP_READ);
380   start = map.data;
381   end = start + map.size;
382
383   cmd_blk = start + state->vobsub.cur_cmd_blk;
384
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);
389     return FALSE;
390   }
391
392   gst_dvd_spu_exec_cmd_blk (dvdspu, cmd_blk + 4, end);
393
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);
398   } else {
399     /* Next Block points to the current block, so we're finished with this
400      * SPU buffer */
401     gst_buffer_unmap (state->vobsub.buf, &map);
402     gst_dvd_spu_finish_spu_buf (dvdspu);
403     return FALSE;
404   }
405   gst_buffer_unmap (state->vobsub.buf, &map);
406
407   return TRUE;
408 }
409
410 gboolean
411 gstspu_vobsub_handle_dvd_event (GstDVDSpu * dvdspu, GstEvent * event)
412 {
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;
417
418   event_type = gst_structure_get_string (structure, "event");
419
420   if (strcmp (event_type, "dvd-spu-clut-change") == 0) {
421     gchar prop_name[32];
422     gint i;
423     gint entry;
424
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))
428         entry = 0;
429       state->vobsub.current_clut[i] = (guint32) entry;
430     }
431
432     state->vobsub.main_pal_dirty = TRUE;
433     state->vobsub.hl_pal_dirty = TRUE;
434     state->vobsub.line_ctrl_i_pal_dirty = TRUE;
435     hl_change = TRUE;
436   } else if (strcmp (event_type, "dvd-spu-highlight") == 0) {
437     gint val;
438
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;
444
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;
449
450       state->vobsub.hl_pal_dirty = TRUE;
451     }
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;
460
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);
464     hl_change = TRUE;
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)
467       hl_change = TRUE;
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;
473
474     if (gst_structure_get_boolean (structure, "forced-only", &forced_only)) {
475       gboolean was_forced = (state->flags & SPU_STATE_FORCED_ONLY);
476
477       if (forced_only)
478         state->flags |= SPU_STATE_FORCED_ONLY;
479       else
480         state->flags &= ~(SPU_STATE_FORCED_ONLY);
481
482       if (was_forced != forced_only)
483         hl_change = TRUE;
484     }
485   }
486
487   gst_event_unref (event);
488
489   return hl_change;
490 }
491
492 void
493 gstspu_vobsub_flush (GstDVDSpu * dvdspu)
494 {
495   SpuState *state = &dvdspu->spu_state;
496
497   if (state->vobsub.buf) {
498     gst_buffer_unref (state->vobsub.buf);
499     state->vobsub.buf = NULL;
500   }
501   if (state->vobsub.pix_buf) {
502     gst_buffer_unref (state->vobsub.pix_buf);
503     state->vobsub.pix_buf = NULL;
504   }
505
506   state->vobsub.base_ts = GST_CLOCK_TIME_NONE;
507   state->vobsub.pix_data[0] = 0;
508   state->vobsub.pix_data[1] = 0;
509
510   state->vobsub.hl_rect.top = -1;
511   state->vobsub.hl_rect.bottom = -1;
512
513   state->vobsub.disp_rect.top = -1;
514   state->vobsub.disp_rect.bottom = -1;
515
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;
520   }
521 }