| /* GStreamer Sub-Picture Unit - VobSub/DVD handling |
| * Copyright (C) 2009 Jan Schmidt <thaytan@noraisin.net> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| #ifdef HAVE_CONFIG_H |
| # include <config.h> |
| #endif |
| |
| #include <string.h> |
| |
| #include <gst/gst.h> |
| |
| #include "gstdvdspu.h" |
| #include "gstspu-vobsub.h" |
| |
| GST_DEBUG_CATEGORY_EXTERN (dvdspu_debug); |
| #define GST_CAT_DEFAULT dvdspu_debug |
| |
| /* Define to dump out a text description of the incoming SPU commands */ |
| #define DUMP_DCSQ 0 |
| |
| /* Convert an STM offset in the SPU sequence to a GStreamer timestamp */ |
| #define STM_TO_GST(stm) ((GST_MSECOND * 1024 * (stm)) / 90) |
| |
| typedef enum SpuVobsubCmd SpuVobsubCmd; |
| |
| enum SpuVobsubCmd |
| { |
| SPU_CMD_FSTA_DSP = 0x00, /* Forced Display */ |
| SPU_CMD_DSP = 0x01, /* Display Start */ |
| SPU_CMD_STP_DSP = 0x02, /* Display Off */ |
| SPU_CMD_SET_COLOR = 0x03, /* Set the color indexes for the palette */ |
| SPU_CMD_SET_ALPHA = 0x04, /* Set the alpha indexes for the palette */ |
| SPU_CMD_SET_DAREA = 0x05, /* Set the display area for the SPU */ |
| SPU_CMD_DSPXA = 0x06, /* Pixel data addresses */ |
| SPU_CMD_CHG_COLCON = 0x07, /* Change Color & Contrast */ |
| SPU_CMD_END = 0xff |
| }; |
| |
| static void |
| gst_dvd_spu_parse_chg_colcon (GstDVDSpu * dvdspu, guint8 * data, guint8 * end) |
| { |
| SpuState *state = &dvdspu->spu_state; |
| guint8 *cur; |
| gint16 n_entries; |
| gint16 i; |
| |
| /* Clear any existing chg colcon info */ |
| state->vobsub.n_line_ctrl_i = 0; |
| if (state->vobsub.line_ctrl_i != NULL) { |
| g_free (state->vobsub.line_ctrl_i); |
| state->vobsub.line_ctrl_i = NULL; |
| } |
| GST_DEBUG_OBJECT (dvdspu, "Change Color & Contrast. Pixel data = %d bytes", |
| (gint16) (end - data)); |
| |
| /* Count the number of entries we'll need */ |
| n_entries = 0; |
| for (cur = data; cur < end;) { |
| guint8 n_changes; |
| guint32 code; |
| |
| if (cur + 4 > end) |
| break; |
| |
| code = GST_READ_UINT32_BE (cur); |
| if (code == 0x0fffffff) |
| break; /* Termination code */ |
| |
| n_changes = CLAMP ((cur[2] >> 4), 1, 8); |
| cur += 4 + (6 * n_changes); |
| |
| if (cur > end) |
| break; /* Invalid entry overrunning buffer */ |
| |
| n_entries++; |
| } |
| |
| state->vobsub.n_line_ctrl_i = n_entries; |
| state->vobsub.line_ctrl_i = g_new (SpuVobsubLineCtrlI, n_entries); |
| |
| cur = data; |
| for (i = 0; i < n_entries; i++) { |
| SpuVobsubLineCtrlI *cur_line_ctrl = state->vobsub.line_ctrl_i + i; |
| guint8 n_changes = CLAMP ((cur[2] >> 4), 1, 8); |
| guint8 c; |
| |
| cur_line_ctrl->n_changes = n_changes; |
| cur_line_ctrl->top = ((cur[0] << 8) & 0x300) | cur[1]; |
| cur_line_ctrl->bottom = ((cur[2] << 8) & 0x300) | cur[3]; |
| |
| GST_LOG_OBJECT (dvdspu, "ChgColcon Entry %d Top: %d Bottom: %d Changes: %d", |
| i, cur_line_ctrl->top, cur_line_ctrl->bottom, n_changes); |
| cur += 4; |
| |
| for (c = 0; c < n_changes; c++) { |
| SpuVobsubPixCtrlI *cur_pix_ctrl = cur_line_ctrl->pix_ctrl_i + c; |
| |
| cur_pix_ctrl->left = ((cur[0] << 8) & 0x300) | cur[1]; |
| cur_pix_ctrl->palette = GST_READ_UINT32_BE (cur + 2); |
| GST_LOG_OBJECT (dvdspu, " %d: left: %d palette 0x%x", c, |
| cur_pix_ctrl->left, cur_pix_ctrl->palette); |
| cur += 6; |
| } |
| } |
| } |
| |
| static void |
| gst_dvd_spu_exec_cmd_blk (GstDVDSpu * dvdspu, guint8 * data, guint8 * end) |
| { |
| SpuState *state = &dvdspu->spu_state; |
| |
| while (data < end) { |
| guint8 cmd; |
| |
| cmd = data[0]; |
| |
| switch (cmd) { |
| case SPU_CMD_FSTA_DSP: |
| GST_DEBUG_OBJECT (dvdspu, " Forced Display"); |
| state->flags |= SPU_STATE_FORCED_DSP; |
| data += 1; |
| break; |
| case SPU_CMD_DSP: |
| GST_DEBUG_OBJECT (dvdspu, " Display On"); |
| state->flags |= SPU_STATE_DISPLAY; |
| data += 1; |
| break; |
| case SPU_CMD_STP_DSP: |
| GST_DEBUG_OBJECT (dvdspu, " Display Off"); |
| state->flags &= ~(SPU_STATE_FORCED_DSP | SPU_STATE_DISPLAY); |
| data += 1; |
| break; |
| case SPU_CMD_SET_COLOR:{ |
| if (G_UNLIKELY (data + 3 >= end)) |
| return; /* Invalid SET_COLOR cmd at the end of the blk */ |
| |
| state->vobsub.main_idx[3] = data[1] >> 4; |
| state->vobsub.main_idx[2] = data[1] & 0x0f; |
| state->vobsub.main_idx[1] = data[2] >> 4; |
| state->vobsub.main_idx[0] = data[2] & 0x0f; |
| |
| state->vobsub.main_pal_dirty = TRUE; |
| |
| GST_DEBUG_OBJECT (dvdspu, |
| " Set Color bg %u pattern %u emph-1 %u emph-2 %u", |
| state->vobsub.main_idx[0], state->vobsub.main_idx[1], |
| state->vobsub.main_idx[2], state->vobsub.main_idx[3]); |
| data += 3; |
| break; |
| } |
| case SPU_CMD_SET_ALPHA:{ |
| if (G_UNLIKELY (data + 3 >= end)) |
| return; /* Invalid SET_ALPHA cmd at the end of the blk */ |
| |
| state->vobsub.main_alpha[3] = data[1] >> 4; |
| state->vobsub.main_alpha[2] = data[1] & 0x0f; |
| state->vobsub.main_alpha[1] = data[2] >> 4; |
| state->vobsub.main_alpha[0] = data[2] & 0x0f; |
| |
| state->vobsub.main_pal_dirty = TRUE; |
| |
| GST_DEBUG_OBJECT (dvdspu, |
| " Set Alpha bg %u pattern %u emph-1 %u emph-2 %u", |
| state->vobsub.main_alpha[0], state->vobsub.main_alpha[1], |
| state->vobsub.main_alpha[2], state->vobsub.main_alpha[3]); |
| data += 3; |
| break; |
| } |
| case SPU_CMD_SET_DAREA:{ |
| SpuRect *r = &state->vobsub.disp_rect; |
| |
| if (G_UNLIKELY (data + 7 >= end)) |
| return; /* Invalid SET_DAREA cmd at the end of the blk */ |
| |
| r->top = ((data[4] & 0xff) << 4) | ((data[5] & 0xf0) >> 4); |
| r->left = ((data[1] & 0xff) << 4) | ((data[2] & 0xf0) >> 4); |
| r->right = ((data[2] & 0x0f) << 8) | data[3]; |
| r->bottom = ((data[5] & 0x0f) << 8) | data[6]; |
| |
| GST_DEBUG_OBJECT (dvdspu, |
| " Set Display Area top %u left %u bottom %u right %u", r->top, |
| r->left, r->bottom, r->right); |
| |
| data += 7; |
| break; |
| } |
| case SPU_CMD_DSPXA:{ |
| if (G_UNLIKELY (data + 5 >= end)) |
| return; /* Invalid SET_DSPXE cmd at the end of the blk */ |
| |
| state->vobsub.pix_data[0] = GST_READ_UINT16_BE (data + 1); |
| state->vobsub.pix_data[1] = GST_READ_UINT16_BE (data + 3); |
| /* Store a reference to the current command buffer, as that's where |
| * we'll need to take our pixel data from */ |
| gst_buffer_replace (&state->vobsub.pix_buf, state->vobsub.buf); |
| |
| GST_DEBUG_OBJECT (dvdspu, " Set Pixel Data Offsets top: %u bot: %u", |
| state->vobsub.pix_data[0], state->vobsub.pix_data[1]); |
| |
| data += 5; |
| break; |
| } |
| case SPU_CMD_CHG_COLCON:{ |
| guint16 field_size; |
| |
| GST_DEBUG_OBJECT (dvdspu, " Set Color & Contrast Change"); |
| if (G_UNLIKELY (data + 3 >= end)) |
| return; /* Invalid CHG_COLCON cmd at the end of the blk */ |
| |
| data++; |
| field_size = GST_READ_UINT16_BE (data); |
| |
| if (G_UNLIKELY (data + field_size >= end)) |
| return; /* Invalid CHG_COLCON cmd at the end of the blk */ |
| |
| gst_dvd_spu_parse_chg_colcon (dvdspu, data + 2, data + field_size); |
| state->vobsub.line_ctrl_i_pal_dirty = TRUE; |
| data += field_size; |
| break; |
| } |
| case SPU_CMD_END: |
| default: |
| GST_DEBUG_OBJECT (dvdspu, " END"); |
| data = end; |
| break; |
| } |
| } |
| } |
| |
| static void |
| gst_dvd_spu_finish_spu_buf (GstDVDSpu * dvdspu) |
| { |
| SpuState *state = &dvdspu->spu_state; |
| |
| state->next_ts = state->vobsub.base_ts = GST_CLOCK_TIME_NONE; |
| gst_buffer_replace (&state->vobsub.buf, NULL); |
| |
| GST_DEBUG_OBJECT (dvdspu, "Finished SPU buffer"); |
| } |
| |
| static gboolean |
| gst_dvd_spu_setup_cmd_blk (GstDVDSpu * dvdspu, guint16 cmd_blk_offset, |
| guint8 * start, guint8 * end) |
| { |
| SpuState *state = &dvdspu->spu_state; |
| guint16 delay; |
| guint8 *cmd_blk = start + cmd_blk_offset; |
| |
| if (G_UNLIKELY (cmd_blk + 5 >= end)) { |
| GST_DEBUG_OBJECT (dvdspu, "No valid command block"); |
| return FALSE; /* No valid command block to read */ |
| } |
| |
| delay = GST_READ_UINT16_BE (cmd_blk); |
| state->next_ts = state->vobsub.base_ts + STM_TO_GST (delay); |
| state->vobsub.cur_cmd_blk = cmd_blk_offset; |
| |
| GST_DEBUG_OBJECT (dvdspu, "Setup CMD Block @ %u with TS %" GST_TIME_FORMAT, |
| state->vobsub.cur_cmd_blk, GST_TIME_ARGS (state->next_ts)); |
| return TRUE; |
| } |
| |
| #if DUMP_DCSQ |
| static void |
| gst_dvd_spu_dump_dcsq (GstDVDSpu * dvdspu, |
| GstClockTime start_ts, GstBuffer * spu_buf) |
| { |
| guint16 cmd_blk_offset; |
| guint16 next_blk; |
| guint8 *start, *end; |
| |
| start = GST_BUFFER_DATA (spu_buf); |
| end = start + GST_BUFFER_SIZE (spu_buf); |
| |
| g_return_if_fail (start != NULL); |
| |
| /* First command */ |
| next_blk = GST_READ_UINT16_BE (start + 2); |
| cmd_blk_offset = 0; |
| |
| /* Loop through all commands */ |
| g_print ("SPU begins @ %" GST_TIME_FORMAT " offset %u\n", |
| GST_TIME_ARGS (start_ts), next_blk); |
| |
| while (cmd_blk_offset != next_blk) { |
| guint8 *data; |
| GstClockTime cmd_blk_ts; |
| |
| cmd_blk_offset = next_blk; |
| |
| if (G_UNLIKELY (start + cmd_blk_offset + 5 >= end)) |
| break; /* No valid command to read */ |
| |
| data = start + cmd_blk_offset; |
| |
| cmd_blk_ts = start_ts + STM_TO_GST (GST_READ_UINT16_BE (data)); |
| next_blk = GST_READ_UINT16_BE (data + 2); |
| |
| g_print ("Cmd Blk @ offset %u next %u ts %" GST_TIME_FORMAT "\n", |
| cmd_blk_offset, next_blk, GST_TIME_ARGS (cmd_blk_ts)); |
| |
| data += 4; |
| gst_dvd_spu_exec_cmd_blk (dvdspu, data, end); |
| } |
| } |
| #endif |
| |
| void |
| gstspu_vobsub_handle_new_buf (GstDVDSpu * dvdspu, GstClockTime event_ts, |
| GstBuffer * buf) |
| { |
| GstMapInfo map; |
| guint8 *start, *end; |
| SpuState *state = &dvdspu->spu_state; |
| |
| #if DUMP_DCSQ |
| gst_dvd_spu_dump_dcsq (dvdspu, event_ts, buf); |
| #endif |
| |
| if (G_UNLIKELY (gst_buffer_get_size (buf) < 4)) |
| goto invalid; |
| |
| if (state->vobsub.buf != NULL) { |
| gst_buffer_unref (state->vobsub.buf); |
| state->vobsub.buf = NULL; |
| } |
| state->vobsub.buf = buf; |
| state->vobsub.base_ts = event_ts; |
| |
| gst_buffer_map (state->vobsub.buf, &map, GST_MAP_READ); |
| start = map.data; |
| end = start + map.size; |
| |
| /* Configure the first command block in this buffer as our initial blk */ |
| state->vobsub.cur_cmd_blk = GST_READ_UINT16_BE (start + 2); |
| gst_dvd_spu_setup_cmd_blk (dvdspu, state->vobsub.cur_cmd_blk, start, end); |
| /* Clear existing chg-colcon info */ |
| state->vobsub.n_line_ctrl_i = 0; |
| if (state->vobsub.line_ctrl_i != NULL) { |
| g_free (state->vobsub.line_ctrl_i); |
| state->vobsub.line_ctrl_i = NULL; |
| } |
| gst_buffer_unmap (state->vobsub.buf, &map); |
| return; |
| |
| invalid: |
| /* Invalid buffer */ |
| gst_dvd_spu_finish_spu_buf (dvdspu); |
| } |
| |
| gboolean |
| gstspu_vobsub_execute_event (GstDVDSpu * dvdspu) |
| { |
| GstMapInfo map; |
| guint8 *start, *cmd_blk, *end; |
| guint16 next_blk; |
| SpuState *state = &dvdspu->spu_state; |
| gboolean ret = TRUE; |
| |
| if (state->vobsub.buf == NULL) |
| return FALSE; |
| |
| GST_DEBUG_OBJECT (dvdspu, "Executing cmd blk with TS %" GST_TIME_FORMAT |
| " @ offset %u", GST_TIME_ARGS (state->next_ts), |
| state->vobsub.cur_cmd_blk); |
| |
| gst_buffer_map (state->vobsub.buf, &map, GST_MAP_READ); |
| start = map.data; |
| end = start + map.size; |
| |
| cmd_blk = start + state->vobsub.cur_cmd_blk; |
| |
| if (G_UNLIKELY (cmd_blk + 5 >= end)) { |
| gst_buffer_unmap (state->vobsub.buf, &map); |
| /* Invalid. Finish the buffer and loop again */ |
| gst_dvd_spu_finish_spu_buf (dvdspu); |
| return FALSE; |
| } |
| |
| gst_dvd_spu_exec_cmd_blk (dvdspu, cmd_blk + 4, end); |
| |
| next_blk = GST_READ_UINT16_BE (cmd_blk + 2); |
| if (next_blk != state->vobsub.cur_cmd_blk) { |
| /* Advance to the next block of commands */ |
| ret = gst_dvd_spu_setup_cmd_blk (dvdspu, next_blk, start, end); |
| gst_buffer_unmap (state->vobsub.buf, &map); |
| } else { |
| /* Next Block points to the current block, so we're finished with this |
| * SPU buffer */ |
| gst_buffer_unmap (state->vobsub.buf, &map); |
| gst_dvd_spu_finish_spu_buf (dvdspu); |
| ret = FALSE; |
| } |
| |
| return ret; |
| } |
| |
| gboolean |
| gstspu_vobsub_handle_dvd_event (GstDVDSpu * dvdspu, GstEvent * event) |
| { |
| const gchar *event_type; |
| const GstStructure *structure = gst_event_get_structure (event); |
| SpuState *state = &dvdspu->spu_state; |
| gboolean hl_change = FALSE; |
| |
| event_type = gst_structure_get_string (structure, "event"); |
| |
| if (strcmp (event_type, "dvd-spu-clut-change") == 0) { |
| gchar prop_name[32]; |
| gint i; |
| gint entry; |
| |
| for (i = 0; i < 16; i++) { |
| g_snprintf (prop_name, 32, "clut%02d", i); |
| if (!gst_structure_get_int (structure, prop_name, &entry)) |
| entry = 0; |
| state->vobsub.current_clut[i] = (guint32) entry; |
| } |
| |
| state->vobsub.main_pal_dirty = TRUE; |
| state->vobsub.hl_pal_dirty = TRUE; |
| state->vobsub.line_ctrl_i_pal_dirty = TRUE; |
| hl_change = TRUE; |
| } else if (strcmp (event_type, "dvd-spu-highlight") == 0) { |
| gint val; |
| |
| if (gst_structure_get_int (structure, "palette", &val)) { |
| state->vobsub.hl_idx[3] = ((guint32) (val) >> 28) & 0x0f; |
| state->vobsub.hl_idx[2] = ((guint32) (val) >> 24) & 0x0f; |
| state->vobsub.hl_idx[1] = ((guint32) (val) >> 20) & 0x0f; |
| state->vobsub.hl_idx[0] = ((guint32) (val) >> 16) & 0x0f; |
| |
| state->vobsub.hl_alpha[3] = ((guint32) (val) >> 12) & 0x0f; |
| state->vobsub.hl_alpha[2] = ((guint32) (val) >> 8) & 0x0f; |
| state->vobsub.hl_alpha[1] = ((guint32) (val) >> 4) & 0x0f; |
| state->vobsub.hl_alpha[0] = ((guint32) (val) >> 0) & 0x0f; |
| |
| state->vobsub.hl_pal_dirty = TRUE; |
| } |
| if (gst_structure_get_int (structure, "sx", &val)) |
| state->vobsub.hl_rect.left = (gint16) val; |
| if (gst_structure_get_int (structure, "sy", &val)) |
| state->vobsub.hl_rect.top = (gint16) val; |
| if (gst_structure_get_int (structure, "ex", &val)) |
| state->vobsub.hl_rect.right = (gint16) val; |
| if (gst_structure_get_int (structure, "ey", &val)) |
| state->vobsub.hl_rect.bottom = (gint16) val; |
| |
| GST_INFO_OBJECT (dvdspu, "Highlight rect is now (%d,%d) to (%d,%d)", |
| state->vobsub.hl_rect.left, state->vobsub.hl_rect.top, |
| state->vobsub.hl_rect.right, state->vobsub.hl_rect.bottom); |
| hl_change = TRUE; |
| } else if (strcmp (event_type, "dvd-spu-reset-highlight") == 0) { |
| if (state->vobsub.hl_rect.top != -1 || state->vobsub.hl_rect.bottom != -1) |
| hl_change = TRUE; |
| state->vobsub.hl_rect.top = -1; |
| state->vobsub.hl_rect.bottom = -1; |
| GST_INFO_OBJECT (dvdspu, "Highlight off"); |
| } else if (strcmp (event_type, "dvd-set-subpicture-track") == 0) { |
| gboolean forced_only; |
| |
| if (gst_structure_get_boolean (structure, "forced-only", &forced_only)) { |
| gboolean was_forced = (state->flags & SPU_STATE_FORCED_ONLY); |
| |
| if (forced_only) |
| state->flags |= SPU_STATE_FORCED_ONLY; |
| else |
| state->flags &= ~(SPU_STATE_FORCED_ONLY); |
| |
| if (was_forced != forced_only) |
| hl_change = TRUE; |
| } |
| } |
| |
| gst_event_unref (event); |
| |
| return hl_change; |
| } |
| |
| void |
| gstspu_vobsub_flush (GstDVDSpu * dvdspu) |
| { |
| SpuState *state = &dvdspu->spu_state; |
| |
| if (state->vobsub.buf) { |
| gst_buffer_unref (state->vobsub.buf); |
| state->vobsub.buf = NULL; |
| } |
| if (state->vobsub.pix_buf) { |
| gst_buffer_unref (state->vobsub.pix_buf); |
| state->vobsub.pix_buf = NULL; |
| } |
| |
| state->vobsub.base_ts = GST_CLOCK_TIME_NONE; |
| state->vobsub.pix_data[0] = 0; |
| state->vobsub.pix_data[1] = 0; |
| |
| state->vobsub.hl_rect.top = -1; |
| state->vobsub.hl_rect.bottom = -1; |
| |
| state->vobsub.disp_rect.top = -1; |
| state->vobsub.disp_rect.bottom = -1; |
| |
| state->vobsub.n_line_ctrl_i = 0; |
| if (state->vobsub.line_ctrl_i != NULL) { |
| g_free (state->vobsub.line_ctrl_i); |
| state->vobsub.line_ctrl_i = NULL; |
| } |
| } |