diff options
Diffstat (limited to 'gst')
-rw-r--r-- | gst/dvdspu/Makefile.am | 4 | ||||
-rw-r--r-- | gst/dvdspu/gstdvdspu-render.c | 515 | ||||
-rw-r--r-- | gst/dvdspu/gstdvdspu.c | 827 | ||||
-rw-r--r-- | gst/dvdspu/gstdvdspu.h | 132 | ||||
-rw-r--r-- | gst/dvdspu/gstspu-common.h | 56 | ||||
-rw-r--r-- | gst/dvdspu/gstspu-pgs.c | 762 | ||||
-rw-r--r-- | gst/dvdspu/gstspu-pgs.h | 106 | ||||
-rw-r--r-- | gst/dvdspu/gstspu-vobsub-render.c | 536 | ||||
-rw-r--r-- | gst/dvdspu/gstspu-vobsub.c | 513 | ||||
-rw-r--r-- | gst/dvdspu/gstspu-vobsub.h | 110 | ||||
-rw-r--r-- | gst/mpegdemux/gstmpegdefs.h | 2 | ||||
-rw-r--r-- | gst/mpegdemux/gstmpegdemux.c | 134 | ||||
-rw-r--r-- | gst/mpegdemux/gstmpegdemux.h | 5 | ||||
-rw-r--r-- | gst/mpegdemux/gstmpegtsdemux.c | 28 | ||||
-rw-r--r-- | gst/mpegdemux/gstmpegtsdemux.h | 1 | ||||
-rw-r--r-- | gst/mpegdemux/gstpesfilter.h | 2 | ||||
-rw-r--r-- | gst/rtpmanager/rtpsource.c | 5 |
17 files changed, 2584 insertions, 1154 deletions
diff --git a/gst/dvdspu/Makefile.am b/gst/dvdspu/Makefile.am index 4d75f76c..07a66357 100644 --- a/gst/dvdspu/Makefile.am +++ b/gst/dvdspu/Makefile.am @@ -1,13 +1,13 @@ plugin_LTLIBRARIES = libgstdvdspu.la -libgstdvdspu_la_SOURCES = gstdvdspu.c gstdvdspu-render.c +libgstdvdspu_la_SOURCES = gstdvdspu.c gstdvdspu-render.c gstspu-vobsub.c gstspu-vobsub-render.c gstspu-pgs.c libgstdvdspu_la_CFLAGS = $(GST_CFLAGS) libgstdvdspu_la_LIBADD = $(GST_LIBS) libgstdvdspu_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstdvdspu_la_LIBTOOLFLAGS = --tag=disable-static -noinst_HEADERS = gstdvdspu.h +noinst_HEADERS = gstdvdspu.h gstspu-pgs.h gstspu-vobsub.h EXTRA_DIST = Notes.txt diff --git a/gst/dvdspu/gstdvdspu-render.c b/gst/dvdspu/gstdvdspu-render.c index a8dadee2..7731aed4 100644 --- a/gst/dvdspu/gstdvdspu-render.c +++ b/gst/dvdspu/gstdvdspu-render.c @@ -1,5 +1,6 @@ /* GStreamer DVD Sub-Picture Unit * Copyright (C) 2007 Fluendo S.A. <info@fluendo.com> + * 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 @@ -29,338 +30,38 @@ GST_DEBUG_CATEGORY_EXTERN (dvdspu_debug); #define GST_CAT_DEFAULT dvdspu_debug -static void -dvdspu_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->current_clut[idx[i]]; - - dest->Y = (guint16) ((col >> 16) & 0xff) * alpha[i]; - /* U/V are stored as V/U in the clut words, so switch them */ - dest->U = (guint16) (col & 0xff) * alpha[i]; - dest->V = (guint16) ((col >> 8) & 0xff) * alpha[i]; - dest->A = alpha[i]; - } -} - -/* Recalculate the main, HL & ChgCol palettes */ -static void -dvdspu_update_palettes (GstDVDSpu * dvdspu, SpuState * state) -{ - gint16 l, c; - guint8 index[4]; /* Indices for the palette */ - guint8 alpha[4]; /* Alpha values the palette */ - - if (state->main_pal_dirty) { - dvdspu_recalc_palette (dvdspu, state->main_pal, state->main_idx, - state->main_alpha); - - /* Need to refresh the hl_ctrl info copies of the main palette too */ - memcpy (state->hl_ctrl_i.pix_ctrl_i[0].pal_cache, state->main_pal, - 4 * sizeof (SpuColour)); - memcpy (state->hl_ctrl_i.pix_ctrl_i[2].pal_cache, state->main_pal, - 4 * sizeof (SpuColour)); - - state->main_pal_dirty = FALSE; - } - - if (state->hl_pal_dirty) { - dvdspu_recalc_palette (dvdspu, state->hl_ctrl_i.pix_ctrl_i[1].pal_cache, - state->hl_idx, state->hl_alpha); - state->hl_pal_dirty = FALSE; - } - - /* Update the offset positions for the highlight region */ - if (state->hl_rect.top != -1) { - state->hl_ctrl_i.top = state->hl_rect.top; - state->hl_ctrl_i.bottom = state->hl_rect.bottom; - state->hl_ctrl_i.n_changes = 3; - state->hl_ctrl_i.pix_ctrl_i[0].left = 0; - state->hl_ctrl_i.pix_ctrl_i[1].left = state->hl_rect.left; - state->hl_ctrl_i.pix_ctrl_i[2].left = state->hl_rect.right + 1; - } - - if (state->line_ctrl_i_pal_dirty) { - GST_LOG_OBJECT (dvdspu, "Updating chg-col-con palettes"); - for (l = 0; l < state->n_line_ctrl_i; l++) { - SpuLineCtrlI *cur_line_ctrl = state->line_ctrl_i + l; - - for (c = 0; c < cur_line_ctrl->n_changes; c++) { - SpuPixCtrlI *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; - dvdspu_recalc_palette (dvdspu, cur->pal_cache, index, alpha); - } - } - state->line_ctrl_i_pal_dirty = FALSE; - } -} - -static void -dvdspu_clear_comp_buffers (SpuState * state) +void +gstspu_clear_comp_buffers (SpuState * state) { - /* The area to clear is the line inside the disp_rect, each entry 2 bytes, + /* The area to clear is the line inside the disp_rect, each entry 4 bytes, * of the sub-sampled UV planes. */ - gint16 left = state->disp_rect.left / 2; - gint16 right = state->disp_rect.right / 2; - gint16 uv_width = 2 * (right - left + 1); + gint16 left = state->comp_left / 2; + gint16 right = state->comp_right / 2; + gint16 uv_width = sizeof (guint32) * (right - left + 1); memset (state->comp_bufs[0] + left, 0, uv_width); memset (state->comp_bufs[1] + left, 0, uv_width); memset (state->comp_bufs[2] + left, 0, uv_width); - - state->comp_last_x[0] = -1; - state->comp_last_x[1] = -1; -} - -static inline guint8 -dvdspu_get_nibble (SpuState * state, guint16 * rle_offset) -{ - guint8 ret; - - if (G_UNLIKELY (*rle_offset >= state->max_offset)) - return 0; /* Overran the buffer */ - - ret = GST_BUFFER_DATA (state->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 -dvdspu_get_rle_code (SpuState * state, guint16 * rle_offset) -{ - guint16 code; - - code = dvdspu_get_nibble (state, rle_offset); - if (code < 0x4) { /* 4 .. f */ - code = (code << 4) | dvdspu_get_nibble (state, rle_offset); - if (code < 0x10) { /* 1x .. 3x */ - code = (code << 4) | dvdspu_get_nibble (state, rle_offset); - if (code < 0x40) { /* 04x .. 0fx */ - code = (code << 4) | dvdspu_get_nibble (state, rle_offset); - } - } - } - return code; -} - -static inline void -dvdspu_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->cur_Y, x, end, colour->Y, colour->U, colour->V, colour->A); -#endif - - if (colour->A != 0) { - guint8 inv_A = 0xf - colour->A; - - /* FIXME: This could be more efficient */ - while (x < end) { - state->out_Y[x] = (inv_A * state->out_Y[x] + colour->Y) / 0xf; - state->out_U[x / 2] += colour->U; - state->out_V[x / 2] += colour->V; - state->out_A[x / 2] += colour->A; - x++; - } - /* Update the compositing buffer so we know how much to blend later */ - *(state->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 dvdspu_render_line_with_chgcol (SpuState * state, - guint8 * planes[3], guint16 * rle_offset); -static gboolean dvdspu_update_chgcol (SpuState * state); - -static void -dvdspu_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->cur_chg_col != NULL) { - if (dvdspu_update_chgcol (state)) { - /* Check the top & bottom, because we might not be within the region yet */ - if (state->cur_Y >= state->cur_chg_col->top && - state->cur_Y <= state->cur_chg_col->bottom) { - dvdspu_render_line_with_chgcol (state, planes, rle_offset); - return; - } - } - } - - /* No special case. Render as normal */ - - /* Set up our output pointers */ - state->out_Y = planes[0]; - state->out_U = state->comp_bufs[0]; - state->out_V = state->comp_bufs[1]; - state->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->disp_rect.left; - end = state->disp_rect.right + 1; - while (x < end) { - rle_code = dvdspu_get_rle_code (state, rle_offset); - colour = &state->main_pal[rle_code & 3]; - next_x = rle_end_x (rle_code, x, end); - /* Now draw the run between [x,next_x) */ - dvdspu_draw_rle_run (state, x, next_x, colour); - x = next_x; - } -} - -static gboolean -dvdspu_update_chgcol (SpuState * state) -{ - if (state->cur_chg_col == NULL) - return FALSE; - - if (state->cur_Y <= state->cur_chg_col->bottom) - return TRUE; - - while (state->cur_chg_col < state->cur_chg_col_end) { - if (state->cur_Y >= state->cur_chg_col->top && - state->cur_Y <= state->cur_chg_col->bottom) { -#if 0 - g_print ("Stopped @ entry %d with top %d bottom %d, cur_y %d", - (gint16) (state->cur_chg_col - state->line_ctrl_i), - state->cur_chg_col->top, state->cur_chg_col->bottom, y); -#endif - return TRUE; - } - state->cur_chg_col++; - } - - /* Finished all our cur_chg_col entries. Use the main palette from here on */ - state->cur_chg_col = NULL; - return FALSE; -} - -static void -dvdspu_render_line_with_chgcol (SpuState * state, guint8 * planes[3], - guint16 * rle_offset) -{ - SpuLineCtrlI *chg_col = state->cur_chg_col; - - gint16 x, next_x, disp_end, rle_code, run_end; - SpuColour *colour; - SpuPixCtrlI *cur_pix_ctrl; - SpuPixCtrlI *next_pix_ctrl; - SpuPixCtrlI *end_pix_ctrl; - SpuPixCtrlI dummy_pix_ctrl; - gint16 cur_reg_end; - gint i; - - state->out_Y = planes[0]; - state->out_U = state->comp_bufs[0]; - state->out_V = state->comp_bufs[1]; - state->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->disp_rect.left; - disp_end = state->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->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 = dvdspu_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]; - dvdspu_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 -dvdspu_blend_comp_buffers (SpuState * state, guint8 * planes[3]) +void +gstspu_blend_comp_buffers (SpuState * state, guint8 * planes[3]) { gint16 uv_end; gint16 left, x; guint8 *out_U; guint8 *out_V; - guint16 *in_U; - guint16 *in_V; - guint16 *in_A; - gint16 comp_last_x = MAX (state->comp_last_x[0], state->comp_last_x[1]); + guint32 *in_U; + guint32 *in_V; + guint32 *in_A; + gint16 comp_last_x = state->comp_right; - if (comp_last_x < state->disp_rect.left) + if (comp_last_x < state->comp_left) return; /* Didn't draw in the comp buffers, nothing to do... */ #if 0 - GST_LOG ("Blending comp buffers from disp_rect.left %d to x=%d", - state->disp_rect.left, comp_last_x); + GST_LOG ("Blending comp buffers from x=%d to x=%d", + state->comp_left, state->comp_right); #endif /* Set up the output pointers */ @@ -376,188 +77,18 @@ dvdspu_blend_comp_buffers (SpuState * state, guint8 * planes[3]) * drawn in the render_line function, divided by 2 (rounding up) to account * for UV sub-sampling */ uv_end = (comp_last_x + 1) / 2; - left = state->disp_rect.left / 2; + left = state->comp_left / 2; for (x = left; x < uv_end; x++) { - guint16 tmp; - guint16 inv_A = (4 * 0xf) - in_A[x]; - + guint32 tmp; /* Each entry in the compositing buffer is 4 summed pixels, so the - * inverse alpha is (4 * 0x0f) - in_A[x] */ + * inverse alpha is (4 * 0xff) - in_A[x] */ + guint16 inv_A = (4 * 0xff) - in_A[x]; + tmp = in_U[x] + inv_A * out_U[x]; - out_U[x] = (guint8) (tmp / (4 * 0xf)); + out_U[x] = (guint8) (tmp / (4 * 0xff)); tmp = in_V[x] + inv_A * out_V[x]; - out_V[x] = (guint8) (tmp / (4 * 0xf)); - } -} - -void -gst_dvd_spu_render_spu (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->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->disp_rect.left, state->disp_rect.top, - state->disp_rect.right, state->disp_rect.bottom, - state->hl_rect.left, state->hl_rect.top, - state->hl_rect.right, state->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->cur_offsets[0] = state->pix_data[0] * 2; - state->cur_offsets[1] = state->pix_data[1] * 2; - state->max_offset = GST_BUFFER_SIZE (state->pix_buf) * 2; - - /* Update all the palette caches */ - dvdspu_update_palettes (dvdspu, state); - - /* Set up HL or Change Color & Contrast rect tracking */ - if (state->hl_rect.top != -1) { - state->cur_chg_col = &state->hl_ctrl_i; - state->cur_chg_col_end = state->cur_chg_col + 1; - } else if (state->n_line_ctrl_i > 0) { - state->cur_chg_col = state->line_ctrl_i; - state->cur_chg_col_end = state->cur_chg_col + state->n_line_ctrl_i; - } else - state->cur_chg_col = NULL; - - /* We start rendering from the first line of the display rect */ - y = state->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->disp_rect.bottom - 1) & ~(0x01); - - /* center the image when display rectangle exceeds the video width */ - if (state->vid_width < state->disp_rect.right) { - gint diff, disp_width; - - disp_width = state->disp_rect.left - state->disp_rect.right; - diff = (disp_width - state->vid_width) / 2; - - /* fixme, this is not used yet */ - state->clip_rect.left = state->disp_rect.left + diff; - state->clip_rect.right = state->disp_rect.right - diff; - - GST_DEBUG ("clipping width to %d,%d", state->clip_rect.left, - state->clip_rect.right); - } else { - state->clip_rect.left = state->disp_rect.left; - state->clip_rect.right = state->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->clip_rect.bottom = state->disp_rect.bottom; - if (state->vid_height < state->disp_rect.bottom) { - state->clip_rect.top = state->disp_rect.bottom - state->vid_height; - GST_DEBUG ("clipping height to %d,%d", state->clip_rect.top, - state->clip_rect.bottom); - } else { - state->clip_rect.top = state->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->cur_Y = y; state->cur_Y <= last_y; state->cur_Y++) { - gboolean clip; - - clip = (state->cur_Y < state->clip_rect.top - || state->cur_Y > state->clip_rect.bottom); - - /* Reset the compositing buffer */ - dvdspu_clear_comp_buffers (state); - /* Render even line */ - state->comp_last_x_ptr = state->comp_last_x; - dvdspu_render_line (state, planes, &state->cur_offsets[0]); - if (!clip) { - /* Advance the luminance output pointer */ - planes[0] += state->Y_stride; - } - state->cur_Y++; - - /* Render odd line */ - state->comp_last_x_ptr = state->comp_last_x + 1; - dvdspu_render_line (state, planes, &state->cur_offsets[1]); - /* Blend the accumulated UV compositing buffers onto the output */ - dvdspu_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->cur_Y == state->disp_rect.bottom) { - g_assert ((state->disp_rect.bottom & 0x01) == 0); - - /* Render a remaining lone last even line. y already has the correct value - * after the above loop exited. */ - dvdspu_clear_comp_buffers (state); - state->comp_last_x_ptr = state->comp_last_x; - dvdspu_render_line (state, planes, &state->cur_offsets[0]); - dvdspu_blend_comp_buffers (state, planes); + out_V[x] = (guint8) (tmp / (4 * 0xff)); } - - /* 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->disp_rect.top; - for (pos = state->disp_rect.left + 1; pos < state->disp_rect.right; pos++) - cur[pos] = (cur[pos] / 2) + 0x8; - cur = GST_BUFFER_DATA (buf) + state->Y_stride * state->disp_rect.bottom; - for (pos = state->disp_rect.left + 1; pos < state->disp_rect.right; pos++) - cur[pos] = (cur[pos] / 2) + 0x8; - cur = GST_BUFFER_DATA (buf) + state->Y_stride * state->disp_rect.top; - for (pos = state->disp_rect.top; pos <= state->disp_rect.bottom; pos++) { - cur[state->disp_rect.left] = (cur[state->disp_rect.left] / 2) + 0x8; - cur[state->disp_rect.right] = (cur[state->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 } diff --git a/gst/dvdspu/gstdvdspu.c b/gst/dvdspu/gstdvdspu.c index 703c405b..f5fce4bc 100644 --- a/gst/dvdspu/gstdvdspu.c +++ b/gst/dvdspu/gstdvdspu.c @@ -32,22 +32,17 @@ # include <config.h> #endif +#include <gst/gst-i18n-plugin.h> + #include <string.h> #include <gst/gst.h> #include "gstdvdspu.h" -#define DUMP_DCSQ 0 - -extern void gst_dvd_spu_render_spu (GstDVDSpu * dvdspu, GstBuffer * buf); - GST_DEBUG_CATEGORY (dvdspu_debug); #define GST_CAT_DEFAULT dvdspu_debug -/* Convert an STM offset in the SPU sequence to a GStreamer timestamp */ -#define STM_TO_GST(stm) ((GST_MSECOND * 1024 * (stm)) / 90) - /* Filter signals and args */ enum { @@ -73,12 +68,19 @@ static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", ); static GstStaticPadTemplate subpic_sink_factory = -GST_STATIC_PAD_TEMPLATE ("subpicture", + GST_STATIC_PAD_TEMPLATE ("subpicture", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("video/x-dvd-subpicture") + GST_STATIC_CAPS ("video/x-dvd-subpicture; subpicture/x-pgs") ); +static const guint32 default_clut[16] = { + 0xb48080, 0x248080, 0x628080, 0xd78080, + 0x808080, 0x808080, 0x808080, 0x808080, + 0x808080, 0x808080, 0x808080, 0x808080, + 0x808080, 0x808080, 0x808080, 0x808080 +}; + GST_BOILERPLATE (GstDVDSpu, gst_dvd_spu, GstElement, GST_TYPE_ELEMENT); static void gst_dvd_spu_dispose (GObject * object); @@ -99,22 +101,26 @@ static void gst_dvd_spu_redraw_still (GstDVDSpu * dvdspu, gboolean force); static void gst_dvd_spu_check_still_updates (GstDVDSpu * dvdspu); static GstFlowReturn gst_dvd_spu_subpic_chain (GstPad * pad, GstBuffer * buf); static gboolean gst_dvd_spu_subpic_event (GstPad * pad, GstEvent * event); +static gboolean gst_dvd_spu_subpic_set_caps (GstPad * pad, GstCaps * caps); static void gst_dvd_spu_clear (GstDVDSpu * dvdspu); -static void gst_dvd_spu_flush_spu_info (GstDVDSpu * dvdspu); +static void gst_dvd_spu_flush_spu_info (GstDVDSpu * dvdspu, + gboolean process_events); static void gst_dvd_spu_advance_spu (GstDVDSpu * dvdspu, GstClockTime new_ts); +static void gstspu_render (GstDVDSpu * dvdspu, GstBuffer * buf); static GstFlowReturn dvdspu_handle_vid_buffer (GstDVDSpu * dvdspu, GstBuffer * buf); +static void gst_dvd_spu_handle_dvd_event (GstDVDSpu * dvdspu, GstEvent * event); static void gst_dvd_spu_base_init (gpointer gclass) { static GstElementDetails element_details = - GST_ELEMENT_DETAILS ("Fluendo DVD Player Sub-picture Overlay", - "Mixer/Video/Overlay/DVD", - "Parses the DVD Sub-Picture command stream and renders the SPU overlay " + GST_ELEMENT_DETAILS ("GStreamer Sub-picture Overlay", + "Mixer/Video/Overlay/DVD/Bluray", + "Parses Sub-Picture command streams and renders the SPU overlay " "onto the video as it passes through", - "Jan Schmidt <jan@fluendo.com>"); + "Jan Schmidt <thaytan@noraisin.net>"); GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); gst_element_class_add_pad_template (element_class, @@ -164,7 +170,8 @@ gst_dvd_spu_init (GstDVDSpu * dvdspu, GstDVDSpuClass * gclass) gst_pad_new_from_static_template (&subpic_sink_factory, "subpicture"); gst_pad_set_chain_function (dvdspu->subpic_sinkpad, gst_dvd_spu_subpic_chain); gst_pad_set_event_function (dvdspu->subpic_sinkpad, gst_dvd_spu_subpic_event); - gst_pad_use_fixed_caps (dvdspu->subpic_sinkpad); + gst_pad_set_setcaps_function (dvdspu->subpic_sinkpad, + gst_dvd_spu_subpic_set_caps); gst_element_add_pad (GST_ELEMENT (dvdspu), dvdspu->videosinkpad); gst_element_add_pad (GST_ELEMENT (dvdspu), dvdspu->subpic_sinkpad); @@ -179,7 +186,10 @@ gst_dvd_spu_init (GstDVDSpu * dvdspu, GstDVDSpuClass * gclass) static void gst_dvd_spu_clear (GstDVDSpu * dvdspu) { - gst_dvd_spu_flush_spu_info (dvdspu); + gst_dvd_spu_flush_spu_info (dvdspu, FALSE); + gst_segment_init (&dvdspu->subp_seg, GST_FORMAT_UNDEFINED); + + dvdspu->spu_input_type = SPU_INPUT_TYPE_NONE; gst_buffer_replace (&dvdspu->ref_frame, NULL); gst_buffer_replace (&dvdspu->pending_frame, NULL); @@ -223,15 +233,14 @@ gst_dvd_spu_finalize (GObject * object) /* With SPU lock held, clear the queue of SPU packets */ static void -gst_dvd_spu_flush_spu_info (GstDVDSpu * dvdspu) +gst_dvd_spu_flush_spu_info (GstDVDSpu * dvdspu, gboolean keep_events) { SpuPacket *packet; SpuState *state = &dvdspu->spu_state; + GQueue tmp_q = G_QUEUE_INIT; GST_INFO_OBJECT (dvdspu, "Flushing SPU information"); - gst_segment_init (&dvdspu->subp_seg, GST_FORMAT_UNDEFINED); - if (dvdspu->partial_spu) { gst_buffer_unref (dvdspu->partial_spu); dvdspu->partial_spu = NULL; @@ -239,38 +248,37 @@ gst_dvd_spu_flush_spu_info (GstDVDSpu * dvdspu) packet = (SpuPacket *) g_queue_pop_head (dvdspu->pending_spus); while (packet != NULL) { - if (packet->buf) + if (packet->buf) { gst_buffer_unref (packet->buf); - if (packet->event) - gst_event_unref (packet->event); - g_free (packet); + g_assert (packet->event == NULL); + g_free (packet); + } else if (packet->event) { + if (keep_events) { + g_queue_push_tail (&tmp_q, packet); + } else { + gst_event_unref (packet->event); + g_free (packet); + } + } packet = (SpuPacket *) g_queue_pop_head (dvdspu->pending_spus); } + /* Push anything we decided to keep back onto the pending_spus list */ + for (packet = g_queue_pop_head (&tmp_q); packet != NULL; + packet = g_queue_pop_head (&tmp_q)) + g_queue_push_tail (dvdspu->pending_spus, packet); - if (state->buf) { - gst_buffer_unref (state->buf); - state->buf = NULL; - } - if (state->pix_buf) { - gst_buffer_unref (state->pix_buf); - state->pix_buf = NULL; - } - - state->base_ts = state->next_ts = GST_CLOCK_TIME_NONE; state->flags &= ~(SPU_STATE_FLAGS_MASK); - state->pix_data[0] = 0; - state->pix_data[1] = 0; - - state->hl_rect.top = -1; - state->hl_rect.bottom = -1; + state->next_ts = GST_CLOCK_TIME_NONE; - state->disp_rect.top = -1; - state->disp_rect.bottom = -1; - - state->n_line_ctrl_i = 0; - if (state->line_ctrl_i != NULL) { - g_free (state->line_ctrl_i); - state->line_ctrl_i = NULL; + switch (dvdspu->spu_input_type) { + case SPU_INPUT_TYPE_VOBSUB: + gstspu_vobsub_flush (dvdspu); + break; + case SPU_INPUT_TYPE_PGS: + gstspu_pgs_flush (dvdspu); + break; + default: + break; } } @@ -342,7 +350,7 @@ gst_dvd_spu_video_set_caps (GstPad * pad, GstCaps * caps) state->UV_stride = GST_ROUND_UP_4 (state->Y_stride / 2); for (i = 0; i < 3; i++) { state->comp_bufs[i] = g_realloc (state->comp_bufs[i], - sizeof (guint16) * state->UV_stride); + sizeof (guint32) * state->UV_stride); } } DVD_SPU_UNLOCK (dvdspu); @@ -614,7 +622,7 @@ dvdspu_handle_vid_buffer (GstDVDSpu * dvdspu, GstBuffer * buf) /* Render the SPU overlay onto the buffer */ buf = gst_buffer_make_writable (buf); - gst_dvd_spu_render_spu (dvdspu, buf); + gstspu_render (dvdspu, buf); } else { if (using_ref == FALSE) { /* Not going to draw anything on this frame, just store a reference @@ -643,6 +651,22 @@ no_ref_frame: return GST_FLOW_OK; } + +static void +gstspu_render (GstDVDSpu * dvdspu, GstBuffer * buf) +{ + switch (dvdspu->spu_input_type) { + case SPU_INPUT_TYPE_VOBSUB: + gstspu_vobsub_render (dvdspu, buf); + break; + case SPU_INPUT_TYPE_PGS: + gstspu_pgs_render (dvdspu, buf); + break; + default: + break; + } +} + /* With SPU LOCK */ static void gst_dvd_spu_redraw_still (GstDVDSpu * dvdspu, gboolean force) @@ -666,7 +690,7 @@ gst_dvd_spu_redraw_still (GstDVDSpu * dvdspu, gboolean force) GST_BUFFER_DURATION (buf) = GST_CLOCK_TIME_NONE; /* Render the SPU overlay onto the buffer */ - gst_dvd_spu_render_spu (dvdspu, buf); + gstspu_render (dvdspu, buf); gst_buffer_replace (&dvdspu->pending_frame, buf); gst_buffer_unref (buf); } else if (force) { @@ -690,392 +714,47 @@ gst_dvd_spu_redraw_still (GstDVDSpu * dvdspu, gboolean force) } 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->n_line_ctrl_i = 0; - if (state->line_ctrl_i != NULL) { - g_free (state->line_ctrl_i); - state->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->n_line_ctrl_i = n_entries; - state->line_ctrl_i = g_new (SpuLineCtrlI, n_entries); - - cur = data; - for (i = 0; i < n_entries; i++) { - SpuLineCtrlI *cur_line_ctrl = state->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++) { - SpuPixCtrlI *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->main_idx[3] = data[1] >> 4; - state->main_idx[2] = data[1] & 0x0f; - state->main_idx[1] = data[2] >> 4; - state->main_idx[0] = data[2] & 0x0f; - - state->main_pal_dirty = TRUE; - - GST_DEBUG_OBJECT (dvdspu, - " Set Color bg %u pattern %u emph-1 %u emph-2 %u", - state->main_idx[0], state->main_idx[1], state->main_idx[2], - state->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->main_alpha[3] = data[1] >> 4; - state->main_alpha[2] = data[1] & 0x0f; - state->main_alpha[1] = data[2] >> 4; - state->main_alpha[0] = data[2] & 0x0f; - - state->main_pal_dirty = TRUE; - - GST_DEBUG_OBJECT (dvdspu, - " Set Alpha bg %u pattern %u emph-1 %u emph-2 %u", - state->main_alpha[0], state->main_alpha[1], state->main_alpha[2], - state->main_alpha[3]); - data += 3; - break; - } - case SPU_CMD_SET_DAREA:{ - SpuRect *r = &state->disp_rect; - - if (G_UNLIKELY (data + 7 >= end)) - return; /* Invalid SET_DAREA cmd at the end of the blk */ - - r->top = ((data[4] & 0x3f) << 4) | ((data[5] & 0xe0) >> 4); - r->left = ((data[1] & 0x3f) << 4) | ((data[2] & 0xf0) >> 4); - r->right = ((data[2] & 0x03) << 8) | data[3]; - r->bottom = ((data[5] & 0x03) << 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->pix_data[0] = GST_READ_UINT16_BE (data + 1); - state->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->pix_buf, state->buf); - - GST_DEBUG_OBJECT (dvdspu, " Set Pixel Data Offsets top: %u bot: %u", - state->pix_data[0], state->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->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->base_ts = GST_CLOCK_TIME_NONE; - gst_buffer_replace (&state->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)) - return FALSE; /* No valid command block to read */ - - delay = GST_READ_UINT16_BE (cmd_blk); - state->next_ts = state->base_ts + STM_TO_GST (delay); - state->cur_cmd_blk = cmd_blk_offset; - - GST_DEBUG_OBJECT (dvdspu, "Setup CMD Block @ %u with TS %" GST_TIME_FORMAT, - state->cur_cmd_blk, GST_TIME_ARGS (state->next_ts)); - return TRUE; -} - -static void -gst_dvd_spu_handle_new_spu_buf (GstDVDSpu * dvdspu, SpuPacket * packet) -{ - guint8 *start, *end; - SpuState *state = &dvdspu->spu_state; - - if (G_UNLIKELY (GST_BUFFER_SIZE (packet->buf) < 4)) - goto invalid; - - if (state->buf != NULL) { - gst_buffer_unref (state->buf); - state->buf = NULL; - } - state->buf = packet->buf; - state->base_ts = packet->event_ts; - - start = GST_BUFFER_DATA (state->buf); - end = start + GST_BUFFER_SIZE (state->buf); - - /* Configure the first command block in this buffer as our initial blk */ - state->cur_cmd_blk = GST_READ_UINT16_BE (start + 2); - gst_dvd_spu_setup_cmd_blk (dvdspu, state->cur_cmd_blk, start, end); - /* Clear existing chg-colcon info */ - state->n_line_ctrl_i = 0; - if (state->line_ctrl_i != NULL) { - g_free (state->line_ctrl_i); - state->line_ctrl_i = NULL; - } - return; - -invalid: - /* Invalid buffer */ - gst_dvd_spu_finish_spu_buf (dvdspu); -} - -static void gst_dvd_spu_handle_dvd_event (GstDVDSpu * dvdspu, GstEvent * event) { - const gchar *event_type; const GstStructure *structure = gst_event_get_structure (event); - SpuState *state = &dvdspu->spu_state; + const gchar *event_type = gst_structure_get_string (structure, "event"); gboolean hl_change = FALSE; - event_type = gst_structure_get_string (structure, "event"); GST_INFO_OBJECT (dvdspu, "DVD event of type %s on subp pad OOB=%d", event_type, (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_DOWNSTREAM_OOB)); - 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->current_clut[i] = (guint32) entry; - } - - state->main_pal_dirty = TRUE; - state->hl_pal_dirty = TRUE; - state->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->hl_idx[3] = ((guint32) (val) >> 28) & 0x0f; - state->hl_idx[2] = ((guint32) (val) >> 24) & 0x0f; - state->hl_idx[1] = ((guint32) (val) >> 20) & 0x0f; - state->hl_idx[0] = ((guint32) (val) >> 16) & 0x0f; - - state->hl_alpha[3] = ((guint32) (val) >> 12) & 0x0f; - state->hl_alpha[2] = ((guint32) (val) >> 8) & 0x0f; - state->hl_alpha[1] = ((guint32) (val) >> 4) & 0x0f; - state->hl_alpha[0] = ((guint32) (val) >> 0) & 0x0f; - - state->hl_pal_dirty = TRUE; - } - if (gst_structure_get_int (structure, "sx", &val)) - state->hl_rect.left = (gint16) val; - if (gst_structure_get_int (structure, "sy", &val)) - state->hl_rect.top = (gint16) val; - if (gst_structure_get_int (structure, "ex", &val)) - state->hl_rect.right = (gint16) val; - if (gst_structure_get_int (structure, "ey", &val)) - state->hl_rect.bottom = (gint16) val; - - GST_INFO_OBJECT (dvdspu, "Highlight rect is now (%d,%d) to (%d,%d)", - state->hl_rect.left, state->hl_rect.top, - state->hl_rect.right, state->hl_rect.bottom); - hl_change = TRUE; - } else if (strcmp (event_type, "dvd-spu-reset-highlight") == 0) { - if (state->hl_rect.top != -1 || state->hl_rect.bottom != -1) - hl_change = TRUE; - state->hl_rect.top = -1; - state->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) || (!was_forced && forced_only)) - hl_change = TRUE; - } + switch (dvdspu->spu_input_type) { + case SPU_INPUT_TYPE_VOBSUB: + hl_change = gstspu_vobsub_handle_dvd_event (dvdspu, event); + break; + case SPU_INPUT_TYPE_PGS: + hl_change = gstspu_pgs_handle_dvd_event (dvdspu, event); + break; + default: + break; } - if (hl_change && (state->flags & SPU_STATE_STILL_FRAME)) { + if (hl_change && (dvdspu->spu_state.flags & SPU_STATE_STILL_FRAME)) { gst_dvd_spu_redraw_still (dvdspu, FALSE); } - - gst_event_unref (event); } -#if DUMP_DCSQ -static void -gst_dvd_spu_dump_dcsq (GstDVDSpu * dvdspu, - GstClockTime start_ts, GstBuffer * spu_buf) +static gboolean +gstspu_execute_event (GstDVDSpu * dvdspu) { - 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); + switch (dvdspu->spu_input_type) { + case SPU_INPUT_TYPE_VOBSUB: + return gstspu_vobsub_execute_event (dvdspu); + break; + case SPU_INPUT_TYPE_PGS: + return gstspu_pgs_execute_event (dvdspu); + break; + default: + g_assert_not_reached (); + break; } + return FALSE; } -#endif /* Advance the SPU packet/command queue to a time. new_ts is in running time */ static void @@ -1083,11 +762,15 @@ gst_dvd_spu_advance_spu (GstDVDSpu * dvdspu, GstClockTime new_ts) { SpuState *state = &dvdspu->spu_state; + if (G_UNLIKELY (dvdspu->spu_input_type == SPU_INPUT_TYPE_NONE)) + return; + while (state->next_ts == GST_CLOCK_TIME_NONE || state->next_ts <= new_ts) { - guint8 *start, *cmd_blk, *end; - guint16 next_blk; + GST_DEBUG_OBJECT (dvdspu, + "Advancing SPU from TS %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT, + GST_TIME_ARGS (state->next_ts), GST_TIME_ARGS (new_ts)); - if (state->buf == NULL) { + if (!gstspu_execute_event (dvdspu)) { GstClockTime vid_run_ts; /* No current command buffer, try and get one */ @@ -1108,51 +791,25 @@ gst_dvd_spu_advance_spu (GstDVDSpu * dvdspu, GstClockTime new_ts) packet->buf ? "buffer" : "event"); if (packet->buf) { -#if DUMP_DCSQ - gst_dvd_spu_dump_dcsq (dvdspu, packet->event_ts, packet->buf); -#endif - gst_dvd_spu_handle_new_spu_buf (dvdspu, packet); - } - if (packet->event) + switch (dvdspu->spu_input_type) { + case SPU_INPUT_TYPE_VOBSUB: + gstspu_vobsub_handle_new_buf (dvdspu, packet->event_ts, + packet->buf); + break; + case SPU_INPUT_TYPE_PGS: + gstspu_pgs_handle_new_buf (dvdspu, packet->event_ts, packet->buf); + break; + default: + g_assert_not_reached (); + break; + } + g_assert (packet->event == NULL); + } else if (packet->event) gst_dvd_spu_handle_dvd_event (dvdspu, packet->event); g_free (packet); continue; } - - GST_DEBUG_OBJECT (dvdspu, - "Advancing SPU from TS %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT, - GST_TIME_ARGS (state->next_ts), GST_TIME_ARGS (new_ts)); - - /* If we get here, we have an SPU buffer, and it's time to process the - * next cmd */ - g_assert (state->buf != NULL); - - GST_DEBUG_OBJECT (dvdspu, "Executing cmd blk with TS %" GST_TIME_FORMAT - " @ offset %u", GST_TIME_ARGS (state->next_ts), state->cur_cmd_blk); - - start = GST_BUFFER_DATA (state->buf); - end = start + GST_BUFFER_SIZE (state->buf); - - cmd_blk = start + state->cur_cmd_blk; - - if (G_UNLIKELY (cmd_blk + 5 >= end)) { - /* Invalid. Finish the buffer and loop again */ - gst_dvd_spu_finish_spu_buf (dvdspu); - continue; - } - - gst_dvd_spu_exec_cmd_blk (dvdspu, cmd_blk + 4, end); - - next_blk = GST_READ_UINT16_BE (cmd_blk + 2); - if (next_blk != state->cur_cmd_blk) { - /* Advance to the next block of commands */ - gst_dvd_spu_setup_cmd_blk (dvdspu, next_blk, start, end); - } else { - /* Next Block points to the current block, so we're finished with this - * SPU buffer */ - gst_dvd_spu_finish_spu_buf (dvdspu); - } } } @@ -1178,10 +835,66 @@ gst_dvd_spu_check_still_updates (GstDVDSpu * dvdspu) } } +static void +submit_new_spu_packet (GstDVDSpu * dvdspu, GstBuffer * buf) +{ + SpuPacket *spu_packet; + GstClockTime ts; + GstClockTime run_ts = GST_CLOCK_TIME_NONE; + + GST_DEBUG_OBJECT (dvdspu, + "Complete subpicture buffer of %u bytes with TS %" GST_TIME_FORMAT, + GST_BUFFER_SIZE (buf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + + /* Decide whether to pass this buffer through to the rendering code */ + ts = GST_BUFFER_TIMESTAMP (buf); + if (GST_CLOCK_TIME_IS_VALID (ts)) { + if (ts < (GstClockTime) dvdspu->subp_seg.start) { + GstClockTimeDiff diff = dvdspu->subp_seg.start - ts; + + /* Buffer starts before segment, see if we can calculate a running time */ + run_ts = + gst_segment_to_running_time (&dvdspu->subp_seg, GST_FORMAT_TIME, + dvdspu->subp_seg.start); + if (run_ts >= (GstClockTime) diff) + run_ts -= diff; + else + run_ts = GST_CLOCK_TIME_NONE; /* No running time possible for this subpic */ + } else { + /* TS within segment, convert to running time */ + run_ts = + gst_segment_to_running_time (&dvdspu->subp_seg, GST_FORMAT_TIME, ts); + } + } + + if (GST_CLOCK_TIME_IS_VALID (run_ts)) { + /* Complete SPU packet, push it onto the queue for processing when + * video packets come past */ + spu_packet = g_new0 (SpuPacket, 1); + spu_packet->buf = buf; + + /* Store the activation time of this buffer in running time */ + spu_packet->event_ts = run_ts; + GST_INFO_OBJECT (dvdspu, + "Pushing SPU buf with TS %" GST_TIME_FORMAT " running time %" + GST_TIME_FORMAT, GST_TIME_ARGS (ts), + GST_TIME_ARGS (spu_packet->event_ts)); + + g_queue_push_tail (dvdspu->pending_spus, spu_packet); + + /* In a still frame condition, advance the SPU to make sure the state is + * up to date */ + gst_dvd_spu_check_still_updates (dvdspu); + } else { + gst_buffer_unref (buf); + } +} + static GstFlowReturn gst_dvd_spu_subpic_chain (GstPad * pad, GstBuffer * buf) { GstDVDSpu *dvdspu = (GstDVDSpu *) (gst_object_get_parent (GST_OBJECT (pad))); + GstFlowReturn ret = GST_FLOW_OK; g_return_val_if_fail (dvdspu != NULL, GST_FLOW_ERROR); @@ -1196,7 +909,15 @@ gst_dvd_spu_subpic_chain (GstPad * pad, GstBuffer * buf) GST_BUFFER_TIMESTAMP (buf)); } + if (GST_BUFFER_IS_DISCONT (buf) && dvdspu->partial_spu) { + gst_buffer_unref (dvdspu->partial_spu); + dvdspu->partial_spu = NULL; + } + if (dvdspu->partial_spu != NULL) { + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) + GST_WARNING_OBJECT (dvdspu, + "Joining subpicture buffer with timestamp to previous"); dvdspu->partial_spu = gst_buffer_join (dvdspu->partial_spu, buf); } else { /* If we don't yet have a buffer, wait for one with a timestamp, @@ -1207,85 +928,88 @@ gst_dvd_spu_subpic_chain (GstPad * pad, GstBuffer * buf) gst_buffer_unref (buf); } - if (dvdspu->partial_spu != NULL && GST_BUFFER_SIZE (dvdspu->partial_spu) > 4) { - guint16 packet_size; - guint8 *data; - - data = GST_BUFFER_DATA (dvdspu->partial_spu); - packet_size = GST_READ_UINT16_BE (data); - - if (packet_size == GST_BUFFER_SIZE (dvdspu->partial_spu)) { - SpuPacket *spu_packet; - GstClockTime ts; - GstClockTime run_ts = GST_CLOCK_TIME_NONE; - - GST_DEBUG_OBJECT (dvdspu, - "Complete subpicture buffer of %u bytes with TS %" GST_TIME_FORMAT, - GST_BUFFER_SIZE (dvdspu->partial_spu), - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (dvdspu->partial_spu))); - - /* Decide whether to pass this buffer through to the rendering code */ - ts = GST_BUFFER_TIMESTAMP (dvdspu->partial_spu); - if (GST_CLOCK_TIME_IS_VALID (ts)) { - if (ts < (GstClockTime) dvdspu->subp_seg.start) { - GstClockTimeDiff diff = dvdspu->subp_seg.start - ts; - - /* Buffer starts before segment, see if we can calculate a running time */ - run_ts = - gst_segment_to_running_time (&dvdspu->subp_seg, GST_FORMAT_TIME, - dvdspu->subp_seg.start); - if (run_ts >= (GstClockTime) diff) - run_ts -= diff; - else - run_ts = GST_CLOCK_TIME_NONE; /* No running time possible for this subpic */ + if (dvdspu->partial_spu == NULL) + goto done; + + switch (dvdspu->spu_input_type) { + case SPU_INPUT_TYPE_VOBSUB: + if (GST_BUFFER_SIZE (dvdspu->partial_spu) > 4) { + guint16 packet_size; + guint8 *data; + + data = GST_BUFFER_DATA (dvdspu->partial_spu); + packet_size = GST_READ_UINT16_BE (data); + + if (packet_size == GST_BUFFER_SIZE (dvdspu->partial_spu)) { + submit_new_spu_packet (dvdspu, dvdspu->partial_spu); + dvdspu->partial_spu = NULL; + } else if (packet_size < GST_BUFFER_SIZE (dvdspu->partial_spu)) { + /* Somehow we collected too much - something is wrong. Drop the + * packet entirely and wait for a new one */ + GST_DEBUG_OBJECT (dvdspu, "Discarding invalid SPU buffer of size %u", + GST_BUFFER_SIZE (dvdspu->partial_spu)); + + gst_buffer_unref (dvdspu->partial_spu); + dvdspu->partial_spu = NULL; } else { - /* TS within segment, convert to running time */ - run_ts = - gst_segment_to_running_time (&dvdspu->subp_seg, GST_FORMAT_TIME, - ts); + GST_LOG_OBJECT (dvdspu, + "SPU buffer claims to be of size %u. Collected %u so far.", + packet_size, GST_BUFFER_SIZE (dvdspu->partial_spu)); + } + } + break; + case SPU_INPUT_TYPE_PGS:{ + /* Collect until we have a command buffer that ends exactly at the size + * we've collected */ + guint8 packet_type; + guint16 packet_size; + guint8 *data = GST_BUFFER_DATA (dvdspu->partial_spu); + guint8 *end = data + GST_BUFFER_SIZE (dvdspu->partial_spu); + + /* FIXME: There's no need to walk the command set each time. We can set a + * marker and resume where we left off next time */ + /* FIXME: Move the packet parsing and sanity checking into the format-specific modules */ + while (data != end) { + if (data + 3 > end) + break; + packet_type = *data++; + packet_size = GST_READ_UINT16_BE (data); + data += 2; + if (data + packet_size > end) + break; + data += packet_size; + /* 0x80 is the END command for PGS packets */ + if (packet_type == 0x80 && data != end) { + /* Extra cruft on the end of the packet -> assume invalid */ + gst_buffer_unref (dvdspu->partial_spu); + dvdspu->partial_spu = NULL; + break; } } - if (GST_CLOCK_TIME_IS_VALID (run_ts)) { - /* Complete SPU packet, push it onto the queue for processing when - * video packets come past */ - spu_packet = g_new0 (SpuPacket, 1); - spu_packet->buf = dvdspu->partial_spu; - - /* Store the activation time of this buffer in running time */ - spu_packet->event_ts = - gst_segment_to_running_time (&dvdspu->subp_seg, GST_FORMAT_TIME, - ts); - GST_INFO_OBJECT (dvdspu, - "Pushing SPU buf with TS %" GST_TIME_FORMAT " running time %" - GST_TIME_FORMAT, GST_TIME_ARGS (ts), - GST_TIME_ARGS (spu_packet->event_ts)); - - g_queue_push_tail (dvdspu->pending_spus, spu_packet); - dvdspu->partial_spu = NULL; - - /* In a still frame condition, advance the SPU to make sure the state is - * up to date */ - gst_dvd_spu_check_still_updates (dvdspu); - } else { - gst_buffer_unref (dvdspu->partial_spu); + if (dvdspu->partial_spu && data == end) { + GST_DEBUG_OBJECT (dvdspu, + "Have complete PGS packet of size %u. Enqueueing.", + GST_BUFFER_SIZE (dvdspu->partial_spu)); + submit_new_spu_packet (dvdspu, dvdspu->partial_spu); dvdspu->partial_spu = NULL; } - } else if (packet_size < GST_BUFFER_SIZE (dvdspu->partial_spu)) { - /* Somehow we collected too much - something is wrong. Drop the - * packet entirely and wait for a new one */ - GST_DEBUG_OBJECT (dvdspu, "Discarding invalid SPU buffer of size %u", - GST_BUFFER_SIZE (dvdspu->partial_spu)); - - gst_buffer_unref (dvdspu->partial_spu); - dvdspu->partial_spu = NULL; + break; } + default: + GST_ERROR_OBJECT (dvdspu, "Input type not configured before SPU passing"); + goto caps_not_set; } +done: DVD_SPU_UNLOCK (dvdspu); - gst_object_unref (dvdspu); - return GST_FLOW_OK; + return ret; +caps_not_set: + GST_ELEMENT_ERROR (dvdspu, RESOURCE, NO_SPACE_LEFT, + (_("Subpicture format was not configured before data flow")), (NULL)); + ret = GST_FLOW_ERROR; + goto done; } static gboolean @@ -1313,7 +1037,8 @@ gst_dvd_spu_subpic_event (GstPad * pad, GstEvent * event) DVD_SPU_LOCK (dvdspu); if (GST_EVENT_IS_SERIALIZED (event)) { SpuPacket *spu_packet = g_new0 (SpuPacket, 1); - + GST_DEBUG_OBJECT (dvdspu, + "Enqueueing DVD event on subpicture pad for later"); spu_packet->event = event; g_queue_push_tail (dvdspu->pending_spus, spu_packet); } else { @@ -1374,8 +1099,11 @@ gst_dvd_spu_subpic_event (GstPad * pad, GstEvent * event) GST_TIME_ARGS (stop), GST_TIME_ARGS (time)); DVD_SPU_LOCK (dvdspu); + gst_segment_set_newsegment_full (&dvdspu->subp_seg, update, rate, arate, format, start, stop, time); + GST_LOG_OBJECT (dvdspu, "Subpicture segment now: %" GST_SEGMENT_FORMAT, + &dvdspu->subp_seg); DVD_SPU_UNLOCK (dvdspu); gst_event_unref (event); @@ -1385,8 +1113,10 @@ gst_dvd_spu_subpic_event (GstPad * pad, GstEvent * event) gst_event_unref (event); goto done; case GST_EVENT_FLUSH_STOP: + GST_DEBUG_OBJECT (dvdspu, "Have flush-stop event on SPU pad"); DVD_SPU_LOCK (dvdspu); - gst_dvd_spu_flush_spu_info (dvdspu); + gst_segment_init (&dvdspu->subp_seg, GST_FORMAT_UNDEFINED); + gst_dvd_spu_flush_spu_info (dvdspu, TRUE); DVD_SPU_UNLOCK (dvdspu); /* We don't forward flushes on the spu pad */ @@ -1409,6 +1139,39 @@ done: return res; } +static gboolean +gst_dvd_spu_subpic_set_caps (GstPad * pad, GstCaps * caps) +{ + GstDVDSpu *dvdspu = GST_DVD_SPU (gst_pad_get_parent (pad)); + gboolean res = FALSE; + GstStructure *s; + SpuInputType input_type; + + s = gst_caps_get_structure (caps, 0); + + if (gst_structure_has_name (s, "video/x-dvd-subpicture")) { + input_type = SPU_INPUT_TYPE_VOBSUB; + } else if (gst_structure_has_name (s, "subpicture/x-pgs")) { + input_type = SPU_INPUT_TYPE_PGS; + } else { + goto done; + } + + DVD_SPU_LOCK (dvdspu); + if (dvdspu->spu_input_type != input_type) { + GST_INFO_OBJECT (dvdspu, "Incoming SPU packet type changed to %u", + input_type); + dvdspu->spu_input_type = input_type; + gst_dvd_spu_flush_spu_info (dvdspu, TRUE); + } + + DVD_SPU_UNLOCK (dvdspu); + res = TRUE; +done: + gst_object_unref (dvdspu); + return res; +} + static GstStateChangeReturn gst_dvd_spu_change_state (GstElement * element, GstStateChange transition) { @@ -1433,8 +1196,8 @@ gst_dvd_spu_change_state (GstElement * element, GstStateChange transition) gboolean gst_dvd_spu_plugin_init (GstPlugin * plugin) { - GST_DEBUG_CATEGORY_INIT (dvdspu_debug, "gstdvdspu", - 0, "DVD Sub-picture Overlay decoder/renderer"); + GST_DEBUG_CATEGORY_INIT (dvdspu_debug, "gstspu", + 0, "Sub-picture Overlay decoder/renderer"); return gst_element_register (plugin, "dvdspu", GST_RANK_NONE, GST_TYPE_DVD_SPU); diff --git a/gst/dvdspu/gstdvdspu.h b/gst/dvdspu/gstdvdspu.h index dfc51f9e..22b48d1d 100644 --- a/gst/dvdspu/gstdvdspu.h +++ b/gst/dvdspu/gstdvdspu.h @@ -16,11 +16,15 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ -#ifndef __DVD_SPU_H__ -#define __DVD_SPU_H__ +#ifndef __GST_DVD_SPU_H__ +#define __GST_DVD_SPU_H__ #include <gst/gst.h> +#include "gstspu-common.h" +#include "gstspu-vobsub.h" +#include "gstspu-pgs.h" + G_BEGIN_DECLS #define GST_TYPE_DVD_SPU \ @@ -37,63 +41,16 @@ G_BEGIN_DECLS #define DVD_SPU_LOCK(s) g_mutex_lock ((s)->spu_lock); #define DVD_SPU_UNLOCK(s) g_mutex_unlock ((s)->spu_lock); -typedef struct _GstDVDSpu GstDVDSpu; typedef struct _GstDVDSpuClass GstDVDSpuClass; -typedef struct SpuRect SpuRect; -typedef struct SpuPixCtrlI SpuPixCtrlI; -typedef struct SpuLineCtrlI SpuLineCtrlI; -typedef struct SpuColour SpuColour; typedef enum SpuStateFlags SpuStateFlags; -typedef struct SpuState SpuState; +typedef enum SpuInputType SpuInputType; typedef struct SpuPacket SpuPacket; -typedef enum SpuCmd SpuCmd; - -/* Describe the limits of a rectangle */ -struct SpuRect { - gint16 left; - gint16 top; - gint16 right; - gint16 bottom; -}; - -/* Store a pre-multiplied colour value. The YUV fields hold the YUV values - * multiplied by the 8-bit alpha, to save computing it while rendering */ -struct SpuColour { - guint16 Y; - guint16 U; - guint16 V; - guint8 A; -}; - -/* Pixel Control Info from a Change Color Contrast command */ -struct SpuPixCtrlI { - gint16 left; - guint32 palette; - - /* Pre-multiplied palette values, updated as - * needed */ - SpuColour pal_cache[4]; -}; - -struct SpuLineCtrlI { - guint8 n_changes; /* 1 to 8 */ - SpuPixCtrlI pix_ctrl_i[8]; - - gint16 top; - gint16 bottom; -}; -enum SpuCmd { - 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 +enum SpuInputType { + SPU_INPUT_TYPE_NONE = 0x00, + SPU_INPUT_TYPE_VOBSUB = 0x01, + SPU_INPUT_TYPE_PGS = 0x02 }; enum SpuStateFlags { @@ -110,69 +67,19 @@ enum SpuStateFlags { struct SpuState { GstClockTime next_ts; /* Next event TS in running time */ - - GstClockTime base_ts; /* base TS for cmd blk delays in running time */ - GstBuffer *buf; /* Current SPU packet we're executing commands from */ - guint16 cur_cmd_blk; /* Offset into the buf for the current cmd block */ - SpuStateFlags flags; - - /* Top + Bottom field offsets in the buffer. 0 = not set */ - guint16 pix_data[2]; - GstBuffer *pix_buf; /* Current SPU packet the pix_data references */ - - SpuRect disp_rect; - SpuRect clip_rect; - SpuRect hl_rect; - - guint32 current_clut[16]; /* Colour lookup table from incoming events */ - - guint8 main_idx[4]; /* Indices for current main palette */ - guint8 main_alpha[4]; /* Alpha values for main palette */ - - guint8 hl_idx[4]; /* Indices for current highlight palette */ - guint8 hl_alpha[4]; /* Alpha values for highlight palette */ - - /* Pre-multiplied colour palette for the main palette */ - SpuColour main_pal[4]; - gboolean main_pal_dirty; - - /* Line control info for rendering the highlight palette */ - SpuLineCtrlI hl_ctrl_i; - gboolean hl_pal_dirty; /* Indicates that the HL palette info needs refreshing */ - - /* LineCtrlI Info from a Change Color & Contrast command */ - SpuLineCtrlI *line_ctrl_i; - gint16 n_line_ctrl_i; - gboolean line_ctrl_i_pal_dirty; /* Indicates that the palettes for the line_ctrl_i - * need recalculating */ - - /* Rendering state vars below */ - guint16 *comp_bufs[3]; /* Compositing buffers for U+V & A */ - gint16 comp_last_x[2]; /* Maximum X values we rendered into the comp buffer (odd & even) */ - gint16 *comp_last_x_ptr; /* Ptr to the current comp_last_x value to be updated by the render */ + + gint fps_n, fps_d; gint16 vid_width, vid_height; gint16 Y_stride, UV_stride; gint16 Y_height, UV_height; - gint fps_n, fps_d; - - /* Current Y Position */ - gint16 cur_Y; - - /* Current offset in nibbles into the pix_data */ - guint16 cur_offsets[2]; - guint16 max_offset; - - /* current ChgColCon Line Info */ - SpuLineCtrlI *cur_chg_col; - SpuLineCtrlI *cur_chg_col_end; + guint32 *comp_bufs[3]; /* Compositing buffers for U+V & A */ + guint16 comp_left; + guint16 comp_right; - /* Output position tracking */ - guint8 *out_Y; - guint16 *out_U; - guint16 *out_V; - guint16 *out_A; + SpuVobsubState vobsub; + SpuPgsState pgs; }; /* Structure used to store the queue of pending SPU packets. The start_ts is @@ -198,6 +105,7 @@ struct _GstDVDSpu { GstSegment subp_seg; SpuState spu_state; + SpuInputType spu_input_type; /* GQueue of SpuBuf structures */ GQueue *pending_spus; @@ -221,4 +129,4 @@ GType gst_dvd_spu_get_type (void); G_END_DECLS -#endif /* __DVD_SPU_H__ */ +#endif /* __GST_DVD_SPU_H__ */ diff --git a/gst/dvdspu/gstspu-common.h b/gst/dvdspu/gstspu-common.h new file mode 100644 index 00000000..206e8820 --- /dev/null +++ b/gst/dvdspu/gstspu-common.h @@ -0,0 +1,56 @@ +/* GStreamer DVD Sub-Picture Unit + * Copyright (C) 2007 Fluendo S.A. <info@fluendo.com> + * + * 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. + */ +#ifndef __GSTSPU_COMMON_H__ +#define __GSTSPU_COMMON_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +/* FIXME: Move this back to gstdvdspu.h when the renderers no longer use it: */ +typedef struct _GstDVDSpu GstDVDSpu; + +typedef struct SpuState SpuState; +typedef struct SpuColour SpuColour; +typedef struct SpuRect SpuRect; + +/* Describe the limits of a rectangle */ +struct SpuRect { + gint16 left; + gint16 top; + gint16 right; + gint16 bottom; +}; + +/* Store a pre-multiplied colour value. The YUV fields hold the YUV values + * multiplied by the 8-bit alpha, to save computing it while rendering */ +struct SpuColour { + guint16 Y; + guint16 U; + guint16 V; + guint8 A; +}; + +void gstspu_clear_comp_buffers (SpuState * state); +void gstspu_blend_comp_buffers (SpuState * state, guint8 * planes[3]); + + +G_END_DECLS + +#endif /* __GSTSPU_COMMON_H__ */ diff --git a/gst/dvdspu/gstspu-pgs.c b/gst/dvdspu/gstspu-pgs.c new file mode 100644 index 00000000..d1d4de18 --- /dev/null +++ b/gst/dvdspu/gstspu-pgs.c @@ -0,0 +1,762 @@ +/* GStreamer Sub-Picture Unit - PGS 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <string.h> +#include <gst/gst.h> + +#include "gstdvdspu.h" +#include "gstspu-pgs.h" + +const struct PgsFrameRateEntry +{ + guint8 id; + guint fps_n; + guint fps_d; +} PgsFrameRates[] = { + { + 64, 30000, 1001} /* 29.97 FPS */ +}; + +typedef enum PgsCommandType PgsCommandType; + +enum PgsCommandType +{ + PGS_COMMAND_SET_PALETTE = 0x14, + PGS_COMMAND_SET_OBJECT_DATA = 0x15, + PGS_COMMAND_PRESENTATION_SEGMENT = 0x16, + PGS_COMMAND_SET_WINDOW = 0x17, + PGS_COMMAND_INTERACTIVE_SEGMENT = 0x18, + + PGS_COMMAND_END_DISPLAY = 0x80, + + PGS_COMMAND_INVALID = 0xFFFF +}; + +static gint gstspu_exec_pgs_buffer (GstDVDSpu * dvdspu, GstBuffer * buf); + +#define DUMP_CMDS 0 +#define DUMP_FULL_IMAGE 0 +#define DUMP_FULL_PALETTE 0 + +#if DUMP_CMDS +#define PGS_DUMP(...) g_print(__VA_ARGS__) +#else +#define PGS_DUMP(...) +#endif + +static void +dump_bytes (guint8 * data, guint16 len) +{ + gint i; + + /* Dump the numbers */ + for (i = 0; i < len; i++) { + PGS_DUMP ("0x%02x ", data[i]); + if (!((i + 1) % 16)) + PGS_DUMP ("\n"); + } + if (len > 0 && (i % 16)) + PGS_DUMP ("\n"); +} + +static void +dump_rle_data (GstDVDSpu * dvdspu, guint8 * data, guint32 len) +{ + guint8 *end = data + len; + guint16 obj_w, obj_h; + guint x = 0; + + if (data + 4 > end) + return; + + /* RLE data: */ + obj_w = GST_READ_UINT16_BE (data); + obj_h = GST_READ_UINT16_BE (data + 2); + data += 4; + PGS_DUMP ("RLE image is %ux%u\n", obj_w, obj_h); + + while (data < end) { + guint8 pal_id; + guint16 run_len; + + pal_id = *data++; + if (pal_id != 0) { + // PGS_DUMP ("data 0x%02x\n", data[0]); + run_len = 1; + } else { + if (data + 1 > end) + return; + switch (data[0] & 0xC0) { + case 0x00: + //PGS_DUMP ("data 0x%02x\n", data[0]); + run_len = (data[0] & 0x3f); + data++; + break; + case 0x40: + if (data + 2 > end) + return; + //PGS_DUMP ("data 0x%02x 0x%02x\n", data[0], data[1]); + run_len = ((data[0] << 8) | data[1]) & 0x3fff; + data += 2; + break; + case 0x80: + if (data + 2 > end) + return; + //PGS_DUMP ("data 0x%02x 0x%02x\n", data[0], data[1]); + run_len = (data[0] & 0x3f); + pal_id = data[1]; + data += 2; + break; + case 0xC0: + if (data + 3 > end) + return; + //PGS_DUMP ("data 0x%02x 0x%02x 0x%02x\n", data[0], data[1], data[2]); + run_len = ((data[0] << 8) | data[1]) & 0x3fff; + pal_id = data[2]; + data += 3; + break; + } + } + +#if DUMP_FULL_IMAGE + { + gint i; +#if 1 + if (dvdspu->spu_state.pgs.palette[pal_id].A) { + guint8 val = dvdspu->spu_state.pgs.palette[pal_id].A; + for (i = 0; i < run_len; i++) + PGS_DUMP ("%02x ", val); + } else { + for (i = 0; i < run_len; i++) + PGS_DUMP (" "); + } + if (!run_len || (x + run_len) > obj_w) + PGS_DUMP ("\n"); +#else + PGS_DUMP ("Run x: %d pix: %d col: %d\n", x, run_len, pal_id); +#endif + } +#endif + + x += run_len; + if (!run_len || x > obj_w) + x = 0; + }; + + PGS_DUMP ("\n"); +} + +static void +pgs_composition_object_render (PgsCompositionObject * obj, SpuState * state, + GstBuffer * dest_buf) +{ + SpuColour *colour; + guint8 *planes[3]; /* YUV frame pointers */ + guint8 *data, *end; + guint16 obj_w, obj_h; + guint x, y, i, max_x; + + if (G_UNLIKELY (obj->rle_data == NULL || obj->rle_data_size == 0 + || obj->rle_data_used != obj->rle_data_size)) + return; + + data = obj->rle_data; + end = data + obj->rle_data_used; + + if (data + 4 > end) + return; + + /* FIXME: Calculate and use the cropping window for the output, as the + * intersection of the crop rectangle for this object (if any) and the + * window specified by the object's window_id */ + + /* Store the start of each plane */ + planes[0] = GST_BUFFER_DATA (dest_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 (dest_buf) + GST_BUFFER_SIZE (dest_buf)); + + x = obj->x; + y = obj->y; + + planes[0] += state->Y_stride * y; + planes[1] += state->UV_stride * (y / 2); + planes[2] += state->UV_stride * (y / 2); + + /* RLE data: */ + obj_w = GST_READ_UINT16_BE (data); + obj_h = GST_READ_UINT16_BE (data + 2); + data += 4; + + max_x = x + obj_w; + + state->comp_left = x; + state->comp_right = max_x; + + gstspu_clear_comp_buffers (state); + + while (data < end) { + guint8 pal_id; + guint16 run_len; + + pal_id = *data++; + if (pal_id != 0) { + run_len = 1; + } else { + if (data + 1 > end) + return; + switch (data[0] & 0xC0) { + case 0x00: + run_len = (data[0] & 0x3f); + data++; + break; + case 0x40: + if (data + 2 > end) + return; + run_len = ((data[0] << 8) | data[1]) & 0x3fff; + data += 2; + break; + case 0x80: + if (data + 2 > end) + return; + run_len = (data[0] & 0x3f); + pal_id = data[1]; + data += 2; + break; + case 0xC0: + if (data + 3 > end) + return; + run_len = ((data[0] << 8) | data[1]) & 0x3fff; + pal_id = data[2]; + data += 3; + break; + } + } + + colour = &state->pgs.palette[pal_id]; + if (colour->A) { + guint32 inv_A = 0xff - colour->A; + + for (i = 0; i < run_len; i++) { + planes[0][x] = (inv_A * planes[0][x] + colour->Y) / 0xff; + + state->comp_bufs[0][x / 2] += colour->U; + state->comp_bufs[1][x / 2] += colour->V; + state->comp_bufs[2][x / 2] += colour->A; + x++; + } + } else { + x += run_len; + } + + if (!run_len || x > max_x) { + x = state->pgs.win_x; + planes[0] += state->Y_stride; + + if (y % 2) { + gstspu_blend_comp_buffers (state, planes); + gstspu_clear_comp_buffers (state); + + planes[1] += state->UV_stride; + planes[2] += state->UV_stride; + } + y++; + } + } + + if (y % 2) + gstspu_blend_comp_buffers (state, planes); +} + +static void +pgs_composition_object_clear (PgsCompositionObject * obj) +{ + if (obj->rle_data) { + g_free (obj->rle_data); + obj->rle_data = NULL; + } + obj->rle_data_size = obj->rle_data_used = 0; +} + +static void +pgs_presentation_segment_set_object_count (PgsPresentationSegment * ps, + guint8 n_objects) +{ + if (ps->objects == NULL) { + ps->objects = + g_array_sized_new (FALSE, TRUE, sizeof (PgsCompositionObject), + n_objects); + g_array_set_size (ps->objects, n_objects); + return; + } + + /* Clear memory in any extraneous objects */ + if (ps->objects->len > n_objects) { + guint i; + for (i = n_objects; i < ps->objects->len; i++) { + PgsCompositionObject *cur = + &g_array_index (ps->objects, PgsCompositionObject, i); + pgs_composition_object_clear (cur); + } + } + + g_array_set_size (ps->objects, n_objects); + + if (n_objects == 0) { + g_array_free (ps->objects, TRUE); + ps->objects = NULL; + } +} + +static PgsCompositionObject * +pgs_presentation_segment_find_object (PgsPresentationSegment * ps, + guint16 obj_id) +{ + guint i; + if (ps->objects == NULL) + return NULL; + + for (i = 0; i < ps->objects->len; i++) { + PgsCompositionObject *cur = + &g_array_index (ps->objects, PgsCompositionObject, i); + if (cur->id == obj_id) + return cur; + } + + return NULL; +} + +static int +parse_presentation_segment (GstDVDSpu * dvdspu, guint8 type, guint8 * payload, + guint16 len) +{ + guint8 *end = payload + len; + PgsPresentationSegment *ps = &dvdspu->spu_state.pgs.pres_seg; + guint8 n_objects, palette_id; + gint i; + + /* Parse video descriptor */ + if (payload + 5 > end) + return 0; + + ps->vid_w = GST_READ_UINT16_BE (payload); + ps->vid_h = GST_READ_UINT16_BE (payload + 2); + ps->vid_fps_code = payload[4]; + payload += 5; + + /* Parse composition descriptor */ + if (payload + 3 > end) + return 0; + ps->composition_no = GST_READ_UINT16_BE (payload); + ps->composition_state = payload[2]; + payload += 3; + + /* Parse other bits */ + if (payload + 3 > end) + return 0; + + ps->flags = payload[0]; + + palette_id = payload[1]; + n_objects = payload[2]; + payload += 3; + + if (ps->flags & PGS_PRES_SEGMENT_FLAG_UPDATE_PALETTE) + ps->palette_id = palette_id; + + PGS_DUMP ("Video width %u height %u fps code %u\n", ps->vid_w, ps->vid_h, + ps->vid_fps_code); + PGS_DUMP + ("Composition num %u state 0x%02x flags 0x%02x palette id %u n_objects %u\n", + ps->composition_no, ps->composition_state, ps->flags, ps->palette_id, + n_objects); + + pgs_presentation_segment_set_object_count (ps, n_objects); + + for (i = 0; i < (gint) n_objects; i++) { + PgsCompositionObject *obj = + &g_array_index (ps->objects, PgsCompositionObject, i); + + if (payload + 8 > end) + break; + obj->id = GST_READ_UINT16_BE (payload); + obj->win_id = payload[2]; + obj->flags = payload[3]; + obj->x = GST_READ_UINT16_BE (payload + 4); + obj->y = GST_READ_UINT16_BE (payload + 6); + obj->rle_data_size = obj->rle_data_used = 0; + + payload += 8; + + PGS_DUMP ("Composition object %d Object ID %u Window ID %u flags 0x%02x " + "x %u y %u\n", i, obj->id, obj->win_id, obj->flags, obj->x, obj->y); + + if (obj->flags & PGS_COMPOSITION_OBJECT_FLAG_CROPPED) { + if (payload + 8 > end) + break; + + obj->crop_x = GST_READ_UINT16_BE (payload); + obj->crop_y = GST_READ_UINT16_BE (payload + 2); + obj->crop_w = GST_READ_UINT16_BE (payload + 4); + obj->crop_h = GST_READ_UINT16_BE (payload + 6); + + payload += 8; + + PGS_DUMP ("Cropping window x %u y %u w %u h %u\n", + obj->crop_x, obj->crop_y, obj->crop_w, obj->crop_h); + } + + if (obj->flags & ~(PGS_COMPOSITION_OBJECT_FLAG_CROPPED | + PGS_COMPOSITION_OBJECT_FLAG_FORCED)) + g_warning ("PGS Composition Object has unknown flags: 0x%02x", + obj->flags); + } + + if (payload != end) { + g_warning ("PGS Composition Object: %ld bytes not consumed", end - payload); + dump_bytes (payload, end - payload); + } + + return 0; +} + +static int +parse_set_palette (GstDVDSpu * dvdspu, guint8 type, guint8 * payload, + guint16 len) +{ + SpuState *state = &dvdspu->spu_state; + + const gint PGS_PALETTE_ENTRY_SIZE = 5; + guint8 *end = payload + len; + guint8 palette_id; + guint8 palette_version; + gint n_entries, i; + + if (len < 2) /* Palette command too short */ + return 0; + palette_id = payload[0]; + palette_version = payload[1]; + payload += 2; + + n_entries = (len - 2) / PGS_PALETTE_ENTRY_SIZE; + + PGS_DUMP ("Palette ID %u version %u. %d entries\n", + palette_id, palette_version, n_entries); + for (i = 0; i < 256; i++) + state->pgs.palette[i].A = 0; + for (i = 0; i < n_entries; i++) { + guint8 n, Y, U, V, A; + n = payload[0]; + Y = payload[1]; + U = payload[2]; + V = payload[3]; + A = payload[4]; + +#if DUMP_FULL_PALETTE + PGS_DUMP ("Entry %3d: Y %3d U %3d V %3d A %3d ", n, Y, U, V, A); + if (((i + 1) % 2) == 0) + PGS_DUMP ("\n"); +#endif + + /* Premultiply the palette entries by the alpha */ + state->pgs.palette[n].Y = Y * A; + state->pgs.palette[n].U = U * A; + state->pgs.palette[n].V = V * A; + state->pgs.palette[n].A = A; + + payload += PGS_PALETTE_ENTRY_SIZE; + } + +#if DUMP_FULL_PALETTE + if (n_entries > 0 && (i % 2)) + PGS_DUMP ("\n"); +#endif + + if (payload != end) { + g_warning ("PGS Set Palette: %ld bytes not consumed", end - payload); + dump_bytes (payload, end - payload); + } + + return 0; +} + +static int +parse_set_window (GstDVDSpu * dvdspu, guint8 type, guint8 * payload, + guint16 len) +{ + SpuState *state = &dvdspu->spu_state; + guint8 *end = payload + len; + guint8 win_id, win_ver; + + if (payload + 10 > end) + return 0; + + dump_bytes (payload, len); + + /* FIXME: This is just a guess as to what the numbers mean: */ + win_id = payload[0]; + win_ver = payload[1]; + state->pgs.win_x = GST_READ_UINT16_BE (payload + 2); + state->pgs.win_y = GST_READ_UINT16_BE (payload + 4); + state->pgs.win_w = GST_READ_UINT16_BE (payload + 6); + state->pgs.win_h = GST_READ_UINT16_BE (payload + 8); + payload += 10; + + PGS_DUMP ("Win ID %u version %d x %d y %d w %d h %d\n", + win_id, win_ver, state->pgs.win_x, state->pgs.win_y, state->pgs.win_w, + state->pgs.win_h); + + if (payload != end) { + g_warning ("PGS Set Window: %ld bytes not consumed", end - payload); + dump_bytes (payload, end - payload); + } + + return 0; +} + +static int +parse_set_object_data (GstDVDSpu * dvdspu, guint8 type, guint8 * payload, + guint16 len) +{ + SpuPgsState *pgs_state = &dvdspu->spu_state.pgs; + PgsCompositionObject *obj; + guint8 *end = payload + len; + guint16 obj_id; + guint8 obj_ver, flags; + + if (payload + 4 > end) + return 0; + + obj_id = GST_READ_UINT16_BE (payload); + obj_ver = payload[2]; + flags = payload[3]; + payload += 4; + + obj = pgs_presentation_segment_find_object (&(pgs_state->pres_seg), obj_id); + + PGS_DUMP ("Object ID %d ver %u flags 0x%02x\n", obj_id, obj_ver, flags); + + if (flags & PGS_OBJECT_UPDATE_FLAG_START_RLE) { + obj->rle_data_ver = obj_ver; + + if (payload + 3 > end) + return 0; + + obj->rle_data_size = GST_READ_UINT24_BE (payload); + payload += 3; + + PGS_DUMP ("%d bytes of RLE data, of %d bytes total.\n", + end - payload, obj->rle_data_size); + + obj->rle_data = g_realloc (obj->rle_data, obj->rle_data_size); + obj->rle_data_used = end - payload; + memcpy (obj->rle_data, payload, end - payload); + payload = end; + } else { + PGS_DUMP ("%d bytes of additional RLE data\n", end - payload); + /* Check that the data chunk is for this object version, and fits in the buffer */ + if (obj->rle_data_ver == obj_ver && + obj->rle_data_used + end - payload <= obj->rle_data_size) { + + memcpy (obj->rle_data + obj->rle_data_used, payload, end - payload); + obj->rle_data_used += end - payload; + payload = end; + } + } + + if (obj->rle_data_size == obj->rle_data_used) + dump_rle_data (dvdspu, obj->rle_data, obj->rle_data_size); + + if (payload != end) { + g_warning ("PGS Set Object Data: %ld bytes not consumed", end - payload); + dump_bytes (payload, end - payload); + } + + return 0; +} + +static int +parse_pgs_packet (GstDVDSpu * dvdspu, guint8 type, guint8 * payload, + guint16 len) +{ + SpuPgsState *pgs_state = &dvdspu->spu_state.pgs; + int ret = 0; + + if (!pgs_state->in_presentation_segment + && type != PGS_COMMAND_PRESENTATION_SEGMENT) { + PGS_DUMP ("Expected BEGIN PRESENTATION SEGMENT command. " + "Got command type 0x%02x len %u. Skipping\n", type, len); + return 0; + } + + switch (type) { + case PGS_COMMAND_PRESENTATION_SEGMENT: + PGS_DUMP ("*******************************************\n" + "Begin PRESENTATION_SEGMENT (0x%02x) packet len %u\n", type, len); + pgs_state->in_presentation_segment = + pgs_state->have_presentation_segment = TRUE; + ret = parse_presentation_segment (dvdspu, type, payload, len); + break; + case PGS_COMMAND_SET_OBJECT_DATA: + PGS_DUMP ("*** Set Object Data (0x%02x) packet len %u\n", type, len); + ret = parse_set_object_data (dvdspu, type, payload, len); + break; + case PGS_COMMAND_SET_PALETTE: + PGS_DUMP ("*** Set Palette (0x%02x) packet len %u\n", type, len); + ret = parse_set_palette (dvdspu, type, payload, len); + break; + case PGS_COMMAND_SET_WINDOW: + PGS_DUMP ("*** Set Window command (0x%02x) packet len %u\n", type, len); + ret = parse_set_window (dvdspu, type, payload, len); + break; + case PGS_COMMAND_INTERACTIVE_SEGMENT: + PGS_DUMP ("*** Interactive Segment command(0x%02x) packet len %u\n", + type, len); + dump_bytes (payload, len); + break; + case PGS_COMMAND_END_DISPLAY: + PGS_DUMP ("*** End Display command (0x%02x) packet len %u\n", type, + len); + pgs_state->in_presentation_segment = FALSE; + break; + default: + g_warning ("Unknown PGS command: type 0x%02x len %u", type, len); + dump_bytes (payload, len); + break; + } + PGS_DUMP ("\n"); + + return ret; +} + +gint +gstspu_exec_pgs_buffer (GstDVDSpu * dvdspu, GstBuffer * buf) +{ + guint8 *pos, *end; + guint8 type; + guint16 packet_len; + + pos = GST_BUFFER_DATA (buf); + end = pos + GST_BUFFER_SIZE (buf); + + /* Need at least 3 bytes */ + if (pos + 3 > end) { + PGS_DUMP ("Not enough bytes to be a PGS packet\n"); + return -1; + } + + PGS_DUMP ("Begin dumping command buffer of size %u ts %" GST_TIME_FORMAT "\n", + end - pos, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + do { + type = *pos++; + packet_len = GST_READ_UINT16_BE (pos); + pos += 2; + + if (pos + packet_len > end) { + PGS_DUMP ("Invalid packet length %u (only have %u bytes)\n", packet_len, + end - pos); + return -1; + } + + if (parse_pgs_packet (dvdspu, type, pos, packet_len)) + return -1; + + pos += packet_len; + } while (pos + 3 <= end); + + PGS_DUMP ("End dumping command buffer with %u bytes remaining\n", end - pos); + return (pos - GST_BUFFER_DATA (buf)); +} + +void +gstspu_pgs_handle_new_buf (GstDVDSpu * dvdspu, GstClockTime event_ts, + GstBuffer * buf) +{ + SpuState *state = &dvdspu->spu_state; + + state->next_ts = event_ts; + state->pgs.pending_cmd = buf; +} + +gboolean +gstspu_pgs_execute_event (GstDVDSpu * dvdspu) +{ + SpuState *state = &dvdspu->spu_state; + + if (state->pgs.pending_cmd) { + gstspu_exec_pgs_buffer (dvdspu, state->pgs.pending_cmd); + gst_buffer_unref (state->pgs.pending_cmd); + state->pgs.pending_cmd = NULL; + } + + state->next_ts = GST_CLOCK_TIME_NONE; + + state->flags &= ~SPU_STATE_DISPLAY; + if (state->pgs.have_presentation_segment) { + if (state->pgs.pres_seg.objects && state->pgs.pres_seg.objects->len > 0) + state->flags |= SPU_STATE_DISPLAY; + } + return FALSE; +} + +void +gstspu_pgs_render (GstDVDSpu * dvdspu, GstBuffer * buf) +{ + SpuState *state = &dvdspu->spu_state; + PgsPresentationSegment *ps = &state->pgs.pres_seg; + guint i; + + if (ps->objects == NULL) + return; + + for (i = 0; i < ps->objects->len; i++) { + PgsCompositionObject *cur = + &g_array_index (ps->objects, PgsCompositionObject, i); + pgs_composition_object_render (cur, state, buf); + } +} + +gboolean +gstspu_pgs_handle_dvd_event (GstDVDSpu * dvdspu, GstEvent * event) +{ + return FALSE; +} + +void +gstspu_pgs_flush (GstDVDSpu * dvdspu) +{ + SpuPgsState *pgs_state = &dvdspu->spu_state.pgs; + + if (pgs_state->pending_cmd) { + gst_buffer_unref (pgs_state->pending_cmd); + pgs_state->pending_cmd = NULL; + } + + pgs_state->have_presentation_segment = FALSE; + pgs_state->in_presentation_segment = FALSE; + pgs_presentation_segment_set_object_count (&pgs_state->pres_seg, 0); + + pgs_state->win_x = pgs_state->win_y = pgs_state->win_w = pgs_state->win_h = 0; +} diff --git a/gst/dvdspu/gstspu-pgs.h b/gst/dvdspu/gstspu-pgs.h new file mode 100644 index 00000000..164f4d81 --- /dev/null +++ b/gst/dvdspu/gstspu-pgs.h @@ -0,0 +1,106 @@ +/* GStreamer Sub-Picture Unit - PGS 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GSTSPU_PGS_H__ +#define __GSTSPU_PGS_H__ + +#include "gstspu-common.h" + +typedef struct SpuPgsState SpuPgsState; +typedef enum PgsCompositionObjectFlags PgsCompositionObjectFlags; +typedef enum PgsPresentationSegmentFlags PgsPresentationSegmentFlags; +typedef enum PgsObjectUpdateFlags PgsObjectUpdateFlags; + +typedef struct PgsPresentationSegment PgsPresentationSegment; +typedef struct PgsCompositionObject PgsCompositionObject; + +enum PgsPresentationSegmentFlags +{ + PGS_PRES_SEGMENT_FLAG_UPDATE_PALETTE = 0x80 +}; + +enum PgsCompositionObjectFlags +{ + PGS_COMPOSITION_OBJECT_FLAG_CROPPED = 0x80, + PGS_COMPOSITION_OBJECT_FLAG_FORCED = 0x40 +}; + +enum PgsObjectUpdateFlags +{ + /* Set in an object_update if this is the beginning of new RLE data. + * If not set, the data is a continuation to be appended */ + PGS_OBJECT_UPDATE_FLAG_START_RLE = 0x80, + PGS_OBJECT_UPDATE_FLAG_END_RLE = 0x40 /* This one is a guess */ +}; + +struct PgsPresentationSegment +{ + guint16 composition_no; + guint8 composition_state; + + PgsPresentationSegmentFlags flags; + + guint8 palette_id; + + guint16 vid_w, vid_h; + guint8 vid_fps_code; + + GArray *objects; +}; + +struct PgsCompositionObject +{ + guint16 id; + guint8 version; + PgsCompositionObjectFlags flags; + + guint8 win_id; + + guint8 rle_data_ver; + guint8 *rle_data; + guint32 rle_data_size; + guint32 rle_data_used; + + /* Top left corner of this object */ + guint16 x, y; + + /* Only valid if PGS_COMPOSITION_OBJECT_FLAG_CROPPED is set */ + guint16 crop_x, crop_y, crop_w, crop_h; +}; + +struct SpuPgsState { + GstBuffer *pending_cmd; + + gboolean in_presentation_segment; + gboolean have_presentation_segment; + + PgsPresentationSegment pres_seg; + + SpuColour palette[256]; + + guint16 win_x, win_y, win_w, win_h; +}; + +void gstspu_pgs_handle_new_buf (GstDVDSpu * dvdspu, GstClockTime event_ts, GstBuffer *buf); +gboolean gstspu_pgs_execute_event (GstDVDSpu *dvdspu); +void gstspu_pgs_render (GstDVDSpu *dvdspu, GstBuffer *buf); +gboolean gstspu_pgs_handle_dvd_event (GstDVDSpu *dvdspu, GstEvent *event); +void gstspu_pgs_flush (GstDVDSpu *dvdspu); + +#endif 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. <info@fluendo.com> + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <string.h> + +#include <gst/gst.h> + +#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 +} diff --git a/gst/dvdspu/gstspu-vobsub.c b/gst/dvdspu/gstspu-vobsub.c new file mode 100644 index 00000000..1757feb7 --- /dev/null +++ b/gst/dvdspu/gstspu-vobsub.c @@ -0,0 +1,513 @@ +/* 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, 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] & 0x3f) << 4) | ((data[5] & 0xe0) >> 4); + r->left = ((data[1] & 0x3f) << 4) | ((data[2] & 0xf0) >> 4); + r->right = ((data[2] & 0x03) << 8) | data[3]; + r->bottom = ((data[5] & 0x03) << 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)) + 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) +{ + 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_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; + + start = GST_BUFFER_DATA (state->vobsub.buf); + end = start + GST_BUFFER_SIZE (state->vobsub.buf); + + /* 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; + } + return; + +invalid: + /* Invalid buffer */ + gst_dvd_spu_finish_spu_buf (dvdspu); +} + +gboolean +gstspu_vobsub_execute_event (GstDVDSpu * dvdspu) +{ + guint8 *start, *cmd_blk, *end; + guint16 next_blk; + SpuState *state = &dvdspu->spu_state; + + 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); + + start = GST_BUFFER_DATA (state->vobsub.buf); + end = start + GST_BUFFER_SIZE (state->vobsub.buf); + + cmd_blk = start + state->vobsub.cur_cmd_blk; + + if (G_UNLIKELY (cmd_blk + 5 >= end)) { + /* 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 */ + gst_dvd_spu_setup_cmd_blk (dvdspu, next_blk, start, end); + } else { + /* Next Block points to the current block, so we're finished with this + * SPU buffer */ + gst_dvd_spu_finish_spu_buf (dvdspu); + return FALSE; + } + + return TRUE; +} + +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; + } +} diff --git a/gst/dvdspu/gstspu-vobsub.h b/gst/dvdspu/gstspu-vobsub.h new file mode 100644 index 00000000..9b4196bc --- /dev/null +++ b/gst/dvdspu/gstspu-vobsub.h @@ -0,0 +1,110 @@ +/* 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GSTSPU_VOBSUB_H__ +#define __GSTSPU_VOBSUB_H__ + +#include "gstspu-common.h" + +typedef struct SpuVobsubState SpuVobsubState; +typedef struct SpuVobsubPixCtrlI SpuVobsubPixCtrlI; +typedef struct SpuVobsubLineCtrlI SpuVobsubLineCtrlI; + +/* Pixel Control Info from a Change Color Contrast command */ +struct SpuVobsubPixCtrlI { + gint16 left; + guint32 palette; + + /* Pre-multiplied palette values, updated as + * needed */ + SpuColour pal_cache[4]; +}; + +struct SpuVobsubLineCtrlI { + guint8 n_changes; /* 1 to 8 */ + SpuVobsubPixCtrlI pix_ctrl_i[8]; + + gint16 top; + gint16 bottom; +}; + +struct SpuVobsubState { + GstClockTime base_ts; /* base TS for cmd blk delays in running time */ + GstBuffer *buf; /* Current SPU packet we're executing commands from */ + guint16 cur_cmd_blk; /* Offset into the buf for the current cmd block */ + + /* Top + Bottom field offsets in the buffer. 0 = not set */ + guint16 pix_data[2]; + GstBuffer *pix_buf; /* Current SPU packet the pix_data references */ + + SpuRect disp_rect; + SpuRect clip_rect; + SpuRect hl_rect; + + guint32 current_clut[16]; /* Colour lookup table from incoming events */ + + guint8 main_idx[4]; /* Indices for current main palette */ + guint8 main_alpha[4]; /* Alpha values for main palette */ + + guint8 hl_idx[4]; /* Indices for current highlight palette */ + guint8 hl_alpha[4]; /* Alpha values for highlight palette */ + + /* Pre-multiplied colour palette for the main palette */ + SpuColour main_pal[4]; + gboolean main_pal_dirty; + + /* Line control info for rendering the highlight palette */ + SpuVobsubLineCtrlI hl_ctrl_i; + gboolean hl_pal_dirty; /* Indicates that the HL palette info needs refreshing */ + + /* LineCtrlI Info from a Change Color & Contrast command */ + SpuVobsubLineCtrlI *line_ctrl_i; + gint16 n_line_ctrl_i; + gboolean line_ctrl_i_pal_dirty; /* Indicates that the palettes for the line_ctrl_i + * need recalculating */ + + /* Rendering state vars below */ + gint16 comp_last_x[2]; /* Maximum X values we rendered into the comp buffer (odd & even) */ + gint16 *comp_last_x_ptr; /* Ptr to the current comp_last_x value to be updated by the render */ + + /* Current Y Position */ + gint16 cur_Y; + + /* Current offset in nibbles into the pix_data */ + guint16 cur_offsets[2]; + guint16 max_offset; + + /* current ChgColCon Line Info */ + SpuVobsubLineCtrlI *cur_chg_col; + SpuVobsubLineCtrlI *cur_chg_col_end; + + /* Output position tracking */ + guint8 *out_Y; + guint32 *out_U; + guint32 *out_V; + guint32 *out_A; +}; + +void gstspu_vobsub_handle_new_buf (GstDVDSpu * dvdspu, GstClockTime event_ts, GstBuffer *buf); +gboolean gstspu_vobsub_execute_event (GstDVDSpu *dvdspu); +void gstspu_vobsub_render (GstDVDSpu *dvdspu, GstBuffer *buf); +gboolean gstspu_vobsub_handle_dvd_event (GstDVDSpu *dvdspu, GstEvent *event); +void gstspu_vobsub_flush (GstDVDSpu *dvdspu); + +#endif diff --git a/gst/mpegdemux/gstmpegdefs.h b/gst/mpegdemux/gstmpegdefs.h index d63667d8..7ad1e25c 100644 --- a/gst/mpegdemux/gstmpegdefs.h +++ b/gst/mpegdemux/gstmpegdefs.h @@ -173,6 +173,8 @@ #define ST_HDV_PRIVATE_A0 0xa0 #define ST_HDV_PRIVATE_A1 0xa1 #define ST_PS_DVD_SUBPICTURE 0xff +/* Blu-ray PGS subpictures */ +#define ST_BD_PGS_SUBPICTURE 0x90 /* Un-official time-code stream */ #define ST_PS_TIMECODE 0xd2 diff --git a/gst/mpegdemux/gstmpegdemux.c b/gst/mpegdemux/gstmpegdemux.c index 439bb563..ce1d0978 100644 --- a/gst/mpegdemux/gstmpegdemux.c +++ b/gst/mpegdemux/gstmpegdemux.c @@ -39,6 +39,7 @@ * Fluendo, S.L. All Rights Reserved. * * Contributor(s): Wim Taymans <wim@fluendo.com> + * Jan Schmidt <thaytan@noraisin.net> */ #ifdef HAVE_CONFIG_H @@ -56,6 +57,9 @@ #define SCAN_SCR_SZ 12 #define SCAN_PTS_SZ 80 +#define SEGMENT_THRESHOLD (300*GST_MSECOND) +#define VIDEO_SEGMENT_THRESHOLD (500*GST_MSECOND) + typedef enum { SCAN_SCR, @@ -177,6 +181,13 @@ static GstStaticPadTemplate audio_template = "audio/x-private1-ac3;" "audio/x-private1-dts;" "audio/ac3") ); +static GstStaticPadTemplate subpicture_template = +GST_STATIC_PAD_TEMPLATE ("subpicture_%02x", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("video/x-dvd-subpicture") + ); + static GstStaticPadTemplate private_template = GST_STATIC_PAD_TEMPLATE ("private_%d", GST_PAD_SRC, @@ -209,6 +220,10 @@ static inline gboolean gst_flups_demux_scan_forward_ts (GstFluPSDemux * demux, static inline gboolean gst_flups_demux_scan_backward_ts (GstFluPSDemux * demux, guint64 * pos, SCAN_MODE mode, guint64 * rts); +static void gst_flups_demux_send_segment_updates (GstFluPSDemux * demux, + GstClockTime new_time); +static void gst_flups_demux_clear_times (GstFluPSDemux * demux); + static GstElementClass *parent_class = NULL; /*static guint gst_flups_demux_signals[LAST_SIGNAL] = { 0 };*/ @@ -250,10 +265,14 @@ gst_flups_demux_base_init (GstFluPSDemuxClass * klass) klass->sink_template = gst_static_pad_template_get (&sink_template); klass->video_template = gst_static_pad_template_get (&video_template); klass->audio_template = gst_static_pad_template_get (&audio_template); + klass->subpicture_template = + gst_static_pad_template_get (&subpicture_template); klass->private_template = gst_static_pad_template_get (&private_template); gst_element_class_add_pad_template (element_class, klass->video_template); gst_element_class_add_pad_template (element_class, klass->audio_template); + gst_element_class_add_pad_template (element_class, + klass->subpicture_template); gst_element_class_add_pad_template (element_class, klass->private_template); gst_element_class_add_pad_template (element_class, klass->sink_template); @@ -340,6 +359,7 @@ gst_flups_demux_create_stream (GstFluPSDemux * demux, gint id, gint stream_type) gchar *name; GstFluPSDemuxClass *klass = GST_FLUPS_DEMUX_GET_CLASS (demux); GstCaps *caps; + GstClockTime threshold = SEGMENT_THRESHOLD; name = NULL; template = NULL; @@ -368,6 +388,7 @@ gst_flups_demux_create_stream (GstFluPSDemux * demux, gint id, gint stream_type) caps = gst_caps_new_simple ("video/mpeg", "mpegversion", G_TYPE_INT, mpeg_version, "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + threshold = VIDEO_SEGMENT_THRESHOLD; break; } case ST_AUDIO_MPEG1: @@ -387,6 +408,7 @@ gst_flups_demux_create_stream (GstFluPSDemux * demux, gint id, gint stream_type) template = klass->video_template; name = g_strdup_printf ("video_%02x", id); caps = gst_caps_new_simple ("video/x-h264", NULL); + threshold = VIDEO_SEGMENT_THRESHOLD; break; case ST_PS_AUDIO_AC3: template = klass->audio_template; @@ -404,6 +426,9 @@ gst_flups_demux_create_stream (GstFluPSDemux * demux, gint id, gint stream_type) caps = gst_caps_new_simple ("audio/x-private1-lpcm", NULL); break; case ST_PS_DVD_SUBPICTURE: + template = klass->subpicture_template; + name = g_strdup_printf ("subpicture_%02x", id); + caps = gst_caps_new_simple ("video/x-dvd-subpicture", NULL); break; case ST_GST_AUDIO_RAWA52: template = klass->audio_template; @@ -424,6 +449,7 @@ gst_flups_demux_create_stream (GstFluPSDemux * demux, gint id, gint stream_type) stream->notlinked = FALSE; stream->type = stream_type; stream->pad = gst_pad_new_from_template (template, name); + stream->segment_thresh = threshold; gst_pad_set_event_function (stream->pad, GST_DEBUG_FUNCPTR (gst_flups_demux_src_event)); gst_pad_set_query_function (stream->pad, @@ -559,6 +585,20 @@ gst_flups_demux_send_data (GstFluPSDemux * demux, GstFluPSStream * stream, GST_TIME_ARGS (demux->src_segment.last_stop), GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (demux->current_scr))); + if (demux->src_segment.last_stop != GST_CLOCK_TIME_NONE) { + GstClockTime new_time = demux->base_time + demux->src_segment.last_stop; + + if (stream->last_ts == GST_CLOCK_TIME_NONE || stream->last_ts < new_time) { +#if 0 + g_print ("last_ts update on pad %s to time %" GST_TIME_FORMAT "\n", + GST_PAD_NAME (stream->pad), GST_TIME_ARGS (cur_scr_time)); +#endif + stream->last_ts = new_time; + } + + gst_flups_demux_send_segment_updates (demux, new_time); + } + /* Set the buffer discont flag, and clear discont state on the stream */ if (stream->discont) { GST_DEBUG_OBJECT (demux, "marking discont buffer"); @@ -731,12 +771,76 @@ gst_flups_demux_flush (GstFluPSDemux * demux) gst_adapter_clear (demux->adapter); gst_adapter_clear (demux->rev_adapter); gst_pes_filter_drain (&demux->filter); + gst_flups_demux_clear_times (demux); demux->adapter_offset = G_MAXUINT64; demux->current_scr = G_MAXUINT64; demux->bytes_since_scr = 0; } static void +gst_flups_demux_clear_times (GstFluPSDemux * demux) +{ + gint id; + + /* Clear the last ts for all streams */ + for (id = 0; id < GST_FLUPS_DEMUX_MAX_STREAMS; id++) { + GstFluPSStream *stream = demux->streams[id]; + + if (stream) { + stream->last_seg_start = stream->last_ts = GST_CLOCK_TIME_NONE; + } + } +} + +static void +gst_flups_demux_send_segment_updates (GstFluPSDemux * demux, + GstClockTime new_time) +{ + /* Advance all lagging streams by sending a segment update */ + gint id; + GstEvent *event = NULL; + + /* FIXME: Handle reverse playback */ + + if (new_time > demux->src_segment.stop) + return; + + for (id = 0; id < GST_FLUPS_DEMUX_MAX_STREAMS; id++) { + GstFluPSStream *stream = demux->streams[id]; + + if (stream) { + if (stream->last_ts == GST_CLOCK_TIME_NONE || + stream->last_ts < demux->src_segment.start) + stream->last_ts = demux->src_segment.start; + if (stream->last_ts + stream->segment_thresh < new_time) { +#if 0 + g_print ("Segment update to pad %s time %" GST_TIME_FORMAT " stop now %" + GST_TIME_FORMAT "\n", GST_PAD_NAME (stream->pad), + GST_TIME_ARGS (new_time), GST_TIME_ARGS (demux->src_segment.stop)); +#endif + GST_DEBUG_OBJECT (demux, + "Segment update to pad %s time %" GST_TIME_FORMAT, + GST_PAD_NAME (stream->pad), GST_TIME_ARGS (new_time)); + if (event == NULL) { + event = gst_event_new_new_segment_full (TRUE, + demux->src_segment.rate, demux->src_segment.applied_rate, + GST_FORMAT_TIME, new_time, + demux->src_segment.stop, + demux->src_segment.time + (new_time - demux->src_segment.start)); + } + gst_event_ref (event); + gst_pad_push_event (stream->pad, event); + stream->last_seg_start = stream->last_ts = new_time; + stream->need_segment = FALSE; + } + } + } + + if (event) + gst_event_unref (event); +} + +static void gst_flups_demux_close_segment (GstFluPSDemux * demux) { gint id; @@ -747,6 +851,10 @@ gst_flups_demux_close_segment (GstFluPSDemux * demux) &demux->src_segment); #endif + /* FIXME: Need to send a different segment-close to each pad where the + * last_seg_start != clock_time_none, as that indicates a sparse-stream + * event was sent there */ + /* Close the current segment for a linear playback */ if (demux->src_segment.rate >= 0) { /* for forward playback, we played from start to last_stop */ @@ -1306,7 +1414,7 @@ gst_flups_demux_reset_psm (GstFluPSDemux * demux) FILL_TYPE (0x40, 0x7f, -1); FILL_TYPE (0x80, 0x87, ST_PS_AUDIO_AC3); FILL_TYPE (0x88, 0x9f, ST_PS_AUDIO_DTS); - FILL_TYPE (0xa0, 0xbf, ST_PS_AUDIO_LPCM); + FILL_TYPE (0xa0, 0xaf, ST_PS_AUDIO_LPCM); FILL_TYPE (0xbd, 0xbd, -1); FILL_TYPE (0xc0, 0xdf, ST_AUDIO_MPEG1); FILL_TYPE (0xe0, 0xef, ST_GST_VIDEO_MPEG1_OR_2); @@ -1839,18 +1947,26 @@ gst_flups_demux_data_cb (GstPESFilter * filter, gboolean first, } if (G_LIKELY (stream_type == -1)) { - /* new id */ + /* new id is in the first byte */ id = data[offset++]; - /* Number of audio frames in this packet */ - nframes = data[offset++]; - - GST_DEBUG_OBJECT (demux, "private type 0x%02x, %d frames", id, - nframes); - - datalen -= 2; + datalen--; /* and remap */ stream_type = demux->psm[id]; + + /* Now, if it's a subpicture stream - no more, otherwise + * take the first byte too, since it's the frame count in audio + * streams and our backwards compat convention is to strip it off */ + if (stream_type != ST_PS_DVD_SUBPICTURE) { + /* Number of audio frames in this packet */ + nframes = data[offset++]; + datalen--; + GST_DEBUG_OBJECT (demux, "private type 0x%02x, %d frames", id, + nframes); + } else { + GST_DEBUG_OBJECT (demux, "private type 0x%02x, stream type %d", id, + stream_type); + } } } if (stream_type == -1) diff --git a/gst/mpegdemux/gstmpegdemux.h b/gst/mpegdemux/gstmpegdemux.h index ef175f76..29b3d5e5 100644 --- a/gst/mpegdemux/gstmpegdemux.h +++ b/gst/mpegdemux/gstmpegdemux.h @@ -86,6 +86,10 @@ struct _GstFluPSStream gint type; gint size_bound; + GstClockTime segment_thresh; + GstClockTime last_seg_start; + GstClockTime last_ts; + gboolean discont; gboolean notlinked; gboolean need_segment; @@ -149,6 +153,7 @@ struct _GstFluPSDemuxClass GstPadTemplate *sink_template; GstPadTemplate *video_template; GstPadTemplate *audio_template; + GstPadTemplate *subpicture_template; GstPadTemplate *private_template; }; diff --git a/gst/mpegdemux/gstmpegtsdemux.c b/gst/mpegdemux/gstmpegtsdemux.c index cd64a295..ef0de2c8 100644 --- a/gst/mpegdemux/gstmpegtsdemux.c +++ b/gst/mpegdemux/gstmpegtsdemux.c @@ -157,6 +157,10 @@ enum "audio/x-dts" \ ) +/* Can also use the subpicture pads for text subtitles? */ +#define SUBPICTURE_CAPS \ + GST_STATIC_CAPS ("subpicture/x-pgs; video/x-dvd-subpicture") + static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, @@ -175,6 +179,12 @@ GST_STATIC_PAD_TEMPLATE ("audio_%04x", GST_PAD_SOMETIMES, AUDIO_CAPS); +static GstStaticPadTemplate subpicture_template = +GST_STATIC_PAD_TEMPLATE ("subpicture_%04x", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + SUBPICTURE_CAPS); + static GstStaticPadTemplate private_template = GST_STATIC_PAD_TEMPLATE ("private_%04x", GST_PAD_SRC, @@ -250,10 +260,14 @@ gst_mpegts_demux_base_init (GstMpegTSDemuxClass * klass) klass->sink_template = gst_static_pad_template_get (&sink_template); klass->video_template = gst_static_pad_template_get (&video_template); klass->audio_template = gst_static_pad_template_get (&audio_template); + klass->subpicture_template = + gst_static_pad_template_get (&subpicture_template); klass->private_template = gst_static_pad_template_get (&private_template); gst_element_class_add_pad_template (element_class, klass->video_template); gst_element_class_add_pad_template (element_class, klass->audio_template); + gst_element_class_add_pad_template (element_class, + klass->subpicture_template); gst_element_class_add_pad_template (element_class, klass->private_template); gst_element_class_add_pad_template (element_class, klass->sink_template); @@ -675,6 +689,14 @@ gst_mpegts_demux_fill_stream (GstMpegTSStream * stream, guint8 id, caps = gst_caps_new_simple ("audio/x-lpcm", NULL); break; case ST_PS_DVD_SUBPICTURE: + template = klass->subpicture_template; + name = g_strdup_printf ("subpicture_%04x", stream->PID); + caps = gst_caps_new_simple ("video/x-dvd-subpicture", NULL); + break; + case ST_BD_PGS_SUBPICTURE: + template = klass->subpicture_template; + name = g_strdup_printf ("subpicture_%04x", stream->PID); + caps = gst_caps_new_simple ("subpicture/x-pgs", NULL); break; default: break; @@ -992,8 +1014,8 @@ gst_mpegts_demux_data_cb (GstPESFilter * filter, gboolean first, goto unknown_type; GST_DEBUG_OBJECT (demux, - "New stream 0x%04x of type %d with caps %" GST_PTR_FORMAT, stream->PID, - stream->stream_type, GST_PAD_CAPS (stream->pad)); + "New stream 0x%04x of type 0x%02x with caps %" GST_PTR_FORMAT, + stream->PID, stream->stream_type, GST_PAD_CAPS (stream->pad)); srcpad = stream->pad; @@ -1017,7 +1039,7 @@ gst_mpegts_demux_data_cb (GstPESFilter * filter, gboolean first, unknown_type: { GST_DEBUG_OBJECT (demux, "got unknown stream id 0x%02x, type 0x%02x", - filter->id, filter->type); + filter->id, stream->stream_type); gst_buffer_unref (buffer); return gst_mpegts_demux_combine_flows (demux, stream, GST_FLOW_NOT_LINKED); } diff --git a/gst/mpegdemux/gstmpegtsdemux.h b/gst/mpegdemux/gstmpegtsdemux.h index dad2b023..fde68926 100644 --- a/gst/mpegdemux/gstmpegtsdemux.h +++ b/gst/mpegdemux/gstmpegtsdemux.h @@ -228,6 +228,7 @@ struct _GstMpegTSDemuxClass { GstPadTemplate * sink_template; GstPadTemplate * video_template; GstPadTemplate * audio_template; + GstPadTemplate * subpicture_template; GstPadTemplate * private_template; }; diff --git a/gst/mpegdemux/gstpesfilter.h b/gst/mpegdemux/gstpesfilter.h index b35d8746..ccc8461d 100644 --- a/gst/mpegdemux/gstpesfilter.h +++ b/gst/mpegdemux/gstpesfilter.h @@ -84,8 +84,6 @@ struct _GstPESFilter { gboolean unbounded_packet; guint16 length; - guint8 type; - gint64 pts; gint64 dts; }; diff --git a/gst/rtpmanager/rtpsource.c b/gst/rtpmanager/rtpsource.c index 6a3dc604..355526ee 100644 --- a/gst/rtpmanager/rtpsource.c +++ b/gst/rtpmanager/rtpsource.c @@ -201,7 +201,8 @@ make_address_string (GstNetAddress * addr, gchar * dest, gulong n) gst_netaddress_get_ip4_address (addr, &address, &port); g_snprintf (dest, n, "%d.%d.%d.%d:%d", (address >> 24) & 0xff, - (address >> 16) & 0xff, (address >> 8) & 0xff, address & 0xff, port); + (address >> 16) & 0xff, (address >> 8) & 0xff, address & 0xff, + g_ntohs (port)); break; } case GST_NET_TYPE_IP6: @@ -216,7 +217,7 @@ make_address_string (GstNetAddress * addr, gchar * dest, gulong n) (address[4] << 8) | address[5], (address[6] << 8) | address[7], (address[8] << 8) | address[9], (address[10] << 8) | address[11], (address[12] << 8) | address[13], (address[14] << 8) | address[15], - port); + g_ntohs (port)); break; } default: |