From b68a05dbfab9bc6c43b6b4744749778bc38d18be Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Sat, 23 May 2009 23:19:05 +0100 Subject: gstspu: Implement PGS rendering and alpha blending Refactor the DVD subpicture compositing, switching it to 8-bit alpha calculations. Reuse some of the resulting code to implement PGS subpicture blending. Implement parsing and collecting of composition objects properly, but assuming a single active window and colour palette for now. I need more PGS samples. --- gst/dvdspu/gstspu-vobsub-render.c | 536 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 536 insertions(+) create mode 100644 gst/dvdspu/gstspu-vobsub-render.c (limited to 'gst/dvdspu/gstspu-vobsub-render.c') diff --git a/gst/dvdspu/gstspu-vobsub-render.c b/gst/dvdspu/gstspu-vobsub-render.c new file mode 100644 index 00000000..07abff22 --- /dev/null +++ b/gst/dvdspu/gstspu-vobsub-render.c @@ -0,0 +1,536 @@ +/* GStreamer DVD Sub-Picture Unit + * Copyright (C) 2007 Fluendo S.A. + * Copyright (C) 2009 Jan Schmidt + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include + +#include "gstdvdspu.h" + +GST_DEBUG_CATEGORY_EXTERN (dvdspu_debug); +#define GST_CAT_DEFAULT dvdspu_debug + +static void +gstspu_vobsub_recalc_palette (GstDVDSpu * dvdspu, + SpuColour * dest, guint8 * idx, guint8 * alpha) +{ + SpuState *state = &dvdspu->spu_state; + gint i; + + for (i = 0; i < 4; i++, dest++) { + guint32 col = state->vobsub.current_clut[idx[i]]; + + /* Convert incoming 4-bit alpha to 8 bit for blending */ + dest->A = (alpha[i] << 4) | alpha[i]; + dest->Y = ((guint16) ((col >> 16) & 0xff)) * dest->A; + /* U/V are stored as V/U in the clut words, so switch them */ + dest->V = ((guint16) ((col >> 8) & 0xff)) * dest->A; + dest->U = ((guint16) (col & 0xff)) * dest->A; + } +} + +/* Recalculate the main, HL & ChgCol palettes */ +static void +gstspu_vobsub_update_palettes (GstDVDSpu * dvdspu, SpuState * state) +{ + guint8 index[4]; /* Indices for the palette */ + guint8 alpha[4]; /* Alpha values the palette */ + + if (state->vobsub.main_pal_dirty) { + gstspu_vobsub_recalc_palette (dvdspu, state->vobsub.main_pal, + state->vobsub.main_idx, state->vobsub.main_alpha); + + /* Need to refresh the hl_ctrl info copies of the main palette too */ + memcpy (state->vobsub.hl_ctrl_i.pix_ctrl_i[0].pal_cache, + state->vobsub.main_pal, 4 * sizeof (SpuColour)); + memcpy (state->vobsub.hl_ctrl_i.pix_ctrl_i[2].pal_cache, + state->vobsub.main_pal, 4 * sizeof (SpuColour)); + + state->vobsub.main_pal_dirty = FALSE; + } + + if (state->vobsub.hl_pal_dirty) { + gstspu_vobsub_recalc_palette (dvdspu, + state->vobsub.hl_ctrl_i.pix_ctrl_i[1].pal_cache, state->vobsub.hl_idx, + state->vobsub.hl_alpha); + state->vobsub.hl_pal_dirty = FALSE; + } + + /* Update the offset positions for the highlight region */ + if (state->vobsub.hl_rect.top != -1) { + state->vobsub.hl_ctrl_i.top = state->vobsub.hl_rect.top; + state->vobsub.hl_ctrl_i.bottom = state->vobsub.hl_rect.bottom; + state->vobsub.hl_ctrl_i.n_changes = 3; + state->vobsub.hl_ctrl_i.pix_ctrl_i[0].left = 0; + state->vobsub.hl_ctrl_i.pix_ctrl_i[1].left = state->vobsub.hl_rect.left; + state->vobsub.hl_ctrl_i.pix_ctrl_i[2].left = + state->vobsub.hl_rect.right + 1; + } + + if (state->vobsub.line_ctrl_i_pal_dirty) { + gint16 l, c; + GST_LOG_OBJECT (dvdspu, "Updating chg-col-con palettes"); + for (l = 0; l < state->vobsub.n_line_ctrl_i; l++) { + SpuVobsubLineCtrlI *cur_line_ctrl = state->vobsub.line_ctrl_i + l; + + for (c = 0; c < cur_line_ctrl->n_changes; c++) { + SpuVobsubPixCtrlI *cur = cur_line_ctrl->pix_ctrl_i + c; + + index[3] = (cur->palette >> 28) & 0x0f; + index[2] = (cur->palette >> 24) & 0x0f; + index[1] = (cur->palette >> 20) & 0x0f; + index[0] = (cur->palette >> 16) & 0x0f; + + alpha[3] = (cur->palette >> 12) & 0x0f; + alpha[2] = (cur->palette >> 8) & 0x0f; + alpha[1] = (cur->palette >> 4) & 0x0f; + alpha[0] = (cur->palette) & 0x0f; + gstspu_vobsub_recalc_palette (dvdspu, cur->pal_cache, index, alpha); + } + } + state->vobsub.line_ctrl_i_pal_dirty = FALSE; + } +} + +static inline guint8 +gstspu_vobsub_get_nibble (SpuState * state, guint16 * rle_offset) +{ + guint8 ret; + + if (G_UNLIKELY (*rle_offset >= state->vobsub.max_offset)) + return 0; /* Overran the buffer */ + + ret = GST_BUFFER_DATA (state->vobsub.pix_buf)[(*rle_offset) / 2]; + + /* If the offset is even, we shift the answer down 4 bits, otherwise not */ + if (*rle_offset & 0x01) + ret &= 0x0f; + else + ret = ret >> 4; + + (*rle_offset)++; + return ret; +} + +static guint16 +gstspu_vobsub_get_rle_code (SpuState * state, guint16 * rle_offset) +{ + guint16 code; + + code = gstspu_vobsub_get_nibble (state, rle_offset); + if (code < 0x4) { /* 4 .. f */ + code = (code << 4) | gstspu_vobsub_get_nibble (state, rle_offset); + if (code < 0x10) { /* 1x .. 3x */ + code = (code << 4) | gstspu_vobsub_get_nibble (state, rle_offset); + if (code < 0x40) { /* 04x .. 0fx */ + code = (code << 4) | gstspu_vobsub_get_nibble (state, rle_offset); + } + } + } + return code; +} + +static inline void +gstspu_vobsub_draw_rle_run (SpuState * state, gint16 x, gint16 end, + SpuColour * colour) +{ +#if 0 + GST_LOG ("Y: %d x: %d end %d col %d %d %d %d", + state->vobsub.cur_Y, x, end, colour->Y, colour->U, colour->V, colour->A); +#endif + + if (colour->A != 0) { + guint32 inv_A = 0xff - colour->A; + + /* FIXME: This could be more efficient */ + while (x < end) { + state->vobsub.out_Y[x] = + (inv_A * state->vobsub.out_Y[x] + colour->Y) / 0xff; + state->vobsub.out_U[x / 2] += colour->U; + state->vobsub.out_V[x / 2] += colour->V; + state->vobsub.out_A[x / 2] += colour->A; + x++; + } + /* Update the compositing buffer so we know how much to blend later */ + *(state->vobsub.comp_last_x_ptr) = end; + } +} + +static inline gint16 +rle_end_x (guint16 rle_code, gint16 x, gint16 end) +{ + /* run length = rle_code >> 2 */ + if (G_UNLIKELY (((rle_code >> 2) == 0))) + return end; + else + return MIN (end, x + (rle_code >> 2)); +} + +static void gstspu_vobsub_render_line_with_chgcol (SpuState * state, + guint8 * planes[3], guint16 * rle_offset); +static gboolean gstspu_vobsub_update_chgcol (SpuState * state); + +static void +gstspu_vobsub_render_line (SpuState * state, guint8 * planes[3], + guint16 * rle_offset) +{ + gint16 x, next_x, end, rle_code; + SpuColour *colour; + + /* Check for special case of chg_col info to use (either highlight or + * ChgCol command */ + if (state->vobsub.cur_chg_col != NULL) { + if (gstspu_vobsub_update_chgcol (state)) { + /* Check the top & bottom, because we might not be within the region yet */ + if (state->vobsub.cur_Y >= state->vobsub.cur_chg_col->top && + state->vobsub.cur_Y <= state->vobsub.cur_chg_col->bottom) { + gstspu_vobsub_render_line_with_chgcol (state, planes, rle_offset); + return; + } + } + } + + /* No special case. Render as normal */ + + /* Set up our output pointers */ + state->vobsub.out_Y = planes[0]; + state->vobsub.out_U = state->comp_bufs[0]; + state->vobsub.out_V = state->comp_bufs[1]; + state->vobsub.out_A = state->comp_bufs[2]; + /* We always need to start our RLE decoding byte_aligned */ + *rle_offset = GST_ROUND_UP_2 (*rle_offset); + + x = state->vobsub.disp_rect.left; + end = state->vobsub.disp_rect.right + 1; + while (x < end) { + rle_code = gstspu_vobsub_get_rle_code (state, rle_offset); + colour = &state->vobsub.main_pal[rle_code & 3]; + next_x = rle_end_x (rle_code, x, end); + /* Now draw the run between [x,next_x) */ + gstspu_vobsub_draw_rle_run (state, x, next_x, colour); + x = next_x; + } +} + +static gboolean +gstspu_vobsub_update_chgcol (SpuState * state) +{ + if (state->vobsub.cur_chg_col == NULL) + return FALSE; + + if (state->vobsub.cur_Y <= state->vobsub.cur_chg_col->bottom) + return TRUE; + + while (state->vobsub.cur_chg_col < state->vobsub.cur_chg_col_end) { + if (state->vobsub.cur_Y >= state->vobsub.cur_chg_col->top && + state->vobsub.cur_Y <= state->vobsub.cur_chg_col->bottom) { +#if 0 + g_print ("Stopped @ entry %d with top %d bottom %d, cur_y %d", + (gint16) (state->vobsub.cur_chg_col - state->vobsub.line_ctrl_i), + state->vobsub.cur_chg_col->top, state->vobsub.cur_chg_col->bottom, y); +#endif + return TRUE; + } + state->vobsub.cur_chg_col++; + } + + /* Finished all our cur_chg_col entries. Use the main palette from here on */ + state->vobsub.cur_chg_col = NULL; + return FALSE; +} + +static void +gstspu_vobsub_render_line_with_chgcol (SpuState * state, guint8 * planes[3], + guint16 * rle_offset) +{ + SpuVobsubLineCtrlI *chg_col = state->vobsub.cur_chg_col; + + gint16 x, next_x, disp_end, rle_code, run_end; + SpuColour *colour; + SpuVobsubPixCtrlI *cur_pix_ctrl; + SpuVobsubPixCtrlI *next_pix_ctrl; + SpuVobsubPixCtrlI *end_pix_ctrl; + SpuVobsubPixCtrlI dummy_pix_ctrl; + gint16 cur_reg_end; + gint i; + + state->vobsub.out_Y = planes[0]; + state->vobsub.out_U = state->comp_bufs[0]; + state->vobsub.out_V = state->comp_bufs[1]; + state->vobsub.out_A = state->comp_bufs[2]; + + /* We always need to start our RLE decoding byte_aligned */ + *rle_offset = GST_ROUND_UP_2 (*rle_offset); + + /* Our run will cover the display rect */ + x = state->vobsub.disp_rect.left; + disp_end = state->vobsub.disp_rect.right + 1; + + /* Work out the first pixel control info, which may point to the dummy entry if + * the global palette/alpha need using initally */ + cur_pix_ctrl = chg_col->pix_ctrl_i; + end_pix_ctrl = chg_col->pix_ctrl_i + chg_col->n_changes; + + if (cur_pix_ctrl->left != 0) { + next_pix_ctrl = cur_pix_ctrl; + cur_pix_ctrl = &dummy_pix_ctrl; + for (i = 0; i < 4; i++) /* Copy the main palette to our dummy entry */ + dummy_pix_ctrl.pal_cache[i] = state->vobsub.main_pal[i]; + } else { + next_pix_ctrl = cur_pix_ctrl + 1; + } + if (next_pix_ctrl < end_pix_ctrl) + cur_reg_end = next_pix_ctrl->left; + else + cur_reg_end = disp_end; + + /* Render stuff */ + while (x < disp_end) { + rle_code = gstspu_vobsub_get_rle_code (state, rle_offset); + next_x = rle_end_x (rle_code, x, disp_end); + + /* Now draw the run between [x,next_x), crossing palette regions as needed */ + while (x < next_x) { + run_end = MIN (next_x, cur_reg_end); + + if (G_LIKELY (x < run_end)) { + colour = &cur_pix_ctrl->pal_cache[rle_code & 3]; + gstspu_vobsub_draw_rle_run (state, x, run_end, colour); + x = run_end; + } + + if (x >= cur_reg_end) { + /* Advance to next region */ + cur_pix_ctrl = next_pix_ctrl; + next_pix_ctrl++; + + if (next_pix_ctrl < end_pix_ctrl) + cur_reg_end = next_pix_ctrl->left; + else + cur_reg_end = disp_end; + } + } + } +} + +static void +gstspu_vobsub_blend_comp_buffers (SpuState * state, guint8 * planes[3]) +{ + state->comp_left = state->vobsub.disp_rect.left; + state->comp_right = + MAX (state->vobsub.comp_last_x[0], state->vobsub.comp_last_x[1]); + + gstspu_blend_comp_buffers (state, planes); +} + +void +gstspu_vobsub_clear_comp_buffers (SpuState * state) +{ + state->comp_left = state->vobsub.disp_rect.left; + state->comp_right = state->vobsub.disp_rect.right; + + gstspu_clear_comp_buffers (state); + + state->vobsub.comp_last_x[0] = -1; + state->vobsub.comp_last_x[1] = -1; +} + +void +gstspu_vobsub_render (GstDVDSpu * dvdspu, GstBuffer * buf) +{ + SpuState *state = &dvdspu->spu_state; + guint8 *planes[3]; /* YUV frame pointers */ + gint y, last_y; + + /* Set up our initial state */ + if (G_UNLIKELY (state->vobsub.pix_buf == NULL)) + return; + + /* Store the start of each plane */ + planes[0] = GST_BUFFER_DATA (buf); + planes[1] = planes[0] + (state->Y_height * state->Y_stride); + planes[2] = planes[1] + (state->UV_height * state->UV_stride); + + /* Sanity check */ + g_return_if_fail (planes[2] + (state->UV_height * state->UV_stride) <= + GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf)); + + GST_DEBUG ("Rendering SPU. disp_rect %d,%d to %d,%d. hl_rect %d,%d to %d,%d", + state->vobsub.disp_rect.left, state->vobsub.disp_rect.top, + state->vobsub.disp_rect.right, state->vobsub.disp_rect.bottom, + state->vobsub.hl_rect.left, state->vobsub.hl_rect.top, + state->vobsub.hl_rect.right, state->vobsub.hl_rect.bottom); + + GST_DEBUG ("vid_disp %d,%d", state->vid_width, state->vid_height); + + /* When reading RLE data, we track the offset in nibbles... */ + state->vobsub.cur_offsets[0] = state->vobsub.pix_data[0] * 2; + state->vobsub.cur_offsets[1] = state->vobsub.pix_data[1] * 2; + state->vobsub.max_offset = GST_BUFFER_SIZE (state->vobsub.pix_buf) * 2; + + /* Update all the palette caches */ + gstspu_vobsub_update_palettes (dvdspu, state); + + /* Set up HL or Change Color & Contrast rect tracking */ + if (state->vobsub.hl_rect.top != -1) { + state->vobsub.cur_chg_col = &state->vobsub.hl_ctrl_i; + state->vobsub.cur_chg_col_end = state->vobsub.cur_chg_col + 1; + } else if (state->vobsub.n_line_ctrl_i > 0) { + state->vobsub.cur_chg_col = state->vobsub.line_ctrl_i; + state->vobsub.cur_chg_col_end = + state->vobsub.cur_chg_col + state->vobsub.n_line_ctrl_i; + } else + state->vobsub.cur_chg_col = NULL; + + /* We start rendering from the first line of the display rect */ + y = state->vobsub.disp_rect.top; + /* start_y is always an even number and we render lines in pairs from there, + * accumulating 2 lines of chroma then blending it. We might need to render a + * single line at the end if the display rect ends on an even line too. */ + last_y = (state->vobsub.disp_rect.bottom - 1) & ~(0x01); + + /* center the image when display rectangle exceeds the video width */ + if (state->vid_width < state->vobsub.disp_rect.right) { + gint diff, disp_width; + + disp_width = state->vobsub.disp_rect.left - state->vobsub.disp_rect.right; + diff = (disp_width - state->vid_width) / 2; + + /* fixme, this is not used yet */ + state->vobsub.clip_rect.left = state->vobsub.disp_rect.left + diff; + state->vobsub.clip_rect.right = state->vobsub.disp_rect.right - diff; + + GST_DEBUG ("clipping width to %d,%d", state->vobsub.clip_rect.left, + state->vobsub.clip_rect.right); + } else { + state->vobsub.clip_rect.left = state->vobsub.disp_rect.left; + state->vobsub.clip_rect.right = state->vobsub.disp_rect.right; + } + + /* for the height, chop off the bottom bits of the diplay rectangle because we + * assume the picture is in the lower part. We should better check where it + * is and do something more clever. */ + state->vobsub.clip_rect.bottom = state->vobsub.disp_rect.bottom; + if (state->vid_height < state->vobsub.disp_rect.bottom) { + state->vobsub.clip_rect.top = + state->vobsub.disp_rect.bottom - state->vid_height; + GST_DEBUG ("clipping height to %d,%d", state->vobsub.clip_rect.top, + state->vobsub.clip_rect.bottom); + } else { + state->vobsub.clip_rect.top = state->vobsub.disp_rect.top; + /* Update our plane references to the first line of the disp_rect */ + planes[0] += state->Y_stride * y; + planes[1] += state->UV_stride * (y / 2); + planes[2] += state->UV_stride * (y / 2); + } + + for (state->vobsub.cur_Y = y; state->vobsub.cur_Y <= last_y; + state->vobsub.cur_Y++) { + gboolean clip; + + clip = (state->vobsub.cur_Y < state->vobsub.clip_rect.top + || state->vobsub.cur_Y > state->vobsub.clip_rect.bottom); + + /* Reset the compositing buffer */ + gstspu_clear_comp_buffers (state); + /* Render even line */ + state->vobsub.comp_last_x_ptr = state->vobsub.comp_last_x; + gstspu_vobsub_render_line (state, planes, &state->vobsub.cur_offsets[0]); + if (!clip) { + /* Advance the luminance output pointer */ + planes[0] += state->Y_stride; + } + state->vobsub.cur_Y++; + + /* Render odd line */ + state->vobsub.comp_last_x_ptr = state->vobsub.comp_last_x + 1; + gstspu_vobsub_render_line (state, planes, &state->vobsub.cur_offsets[1]); + /* Blend the accumulated UV compositing buffers onto the output */ + gstspu_vobsub_blend_comp_buffers (state, planes); + + if (!clip) { + /* Update all the output pointers */ + planes[0] += state->Y_stride; + planes[1] += state->UV_stride; + planes[2] += state->UV_stride; + } + } + if (state->vobsub.cur_Y == state->vobsub.disp_rect.bottom) { + g_assert ((state->vobsub.disp_rect.bottom & 0x01) == 0); + + /* Render a remaining lone last even line. y already has the correct value + * after the above loop exited. */ + gstspu_clear_comp_buffers (state); + state->vobsub.comp_last_x_ptr = state->vobsub.comp_last_x; + gstspu_vobsub_render_line (state, planes, &state->vobsub.cur_offsets[0]); + gstspu_vobsub_blend_comp_buffers (state, planes); + } + + /* for debugging purposes, draw a faint rectangle at the edges of the disp_rect */ +#if 0 + do { + guint8 *cur; + gint16 pos; + + cur = GST_BUFFER_DATA (buf) + state->Y_stride * state->vobsub.disp_rect.top; + for (pos = state->vobsub.disp_rect.left + 1; + pos < state->vobsub.disp_rect.right; pos++) + cur[pos] = (cur[pos] / 2) + 0x8; + cur = + GST_BUFFER_DATA (buf) + + state->Y_stride * state->vobsub.disp_rect.bottom; + for (pos = state->vobsub.disp_rect.left + 1; + pos < state->vobsub.disp_rect.right; pos++) + cur[pos] = (cur[pos] / 2) + 0x8; + cur = GST_BUFFER_DATA (buf) + state->Y_stride * state->vobsub.disp_rect.top; + for (pos = state->vobsub.disp_rect.top; + pos <= state->vobsub.disp_rect.bottom; pos++) { + cur[state->vobsub.disp_rect.left] = + (cur[state->vobsub.disp_rect.left] / 2) + 0x8; + cur[state->vobsub.disp_rect.right] = + (cur[state->vobsub.disp_rect.right] / 2) + 0x8; + cur += state->Y_stride; + } + } while (0); +#endif + /* For debugging purposes, draw a faint rectangle around the highlight rect */ +#if 0 + if (state->hl_rect.top != -1) { + guint8 *cur; + gint16 pos; + + cur = GST_BUFFER_DATA (buf) + state->Y_stride * state->hl_rect.top; + for (pos = state->hl_rect.left + 1; pos < state->hl_rect.right; pos++) + cur[pos] = (cur[pos] / 2) + 0x8; + cur = GST_BUFFER_DATA (buf) + state->Y_stride * state->hl_rect.bottom; + for (pos = state->hl_rect.left + 1; pos < state->hl_rect.right; pos++) + cur[pos] = (cur[pos] / 2) + 0x8; + cur = GST_BUFFER_DATA (buf) + state->Y_stride * state->hl_rect.top; + for (pos = state->hl_rect.top; pos <= state->hl_rect.bottom; pos++) { + cur[state->hl_rect.left] = (cur[state->hl_rect.left] / 2) + 0x8; + cur[state->hl_rect.right] = (cur[state->hl_rect.right] / 2) + 0x8; + cur += state->Y_stride; + } + } +#endif +} -- cgit v1.2.1