From 077f84ac1f89770faf8b8af7c0856ec765906a89 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Mon, 27 Aug 2007 14:33:59 +0000 Subject: dvdspu element donated by Fluendo. It implements a DVD Sub-Picture Original commit message from CVS: * configure.ac: * gst/dvdspu/.cvsignore: * gst/dvdspu/Notes.txt: * gst/dvdspu/gstdvdspu-render.c: (dvdspu_recalc_palette), (dvdspu_update_palettes), (dvdspu_clear_comp_buffers), (dvdspu_get_nibble), (dvdspu_get_rle_code), (dvdspu_draw_rle_run), (rle_end_x), (dvdspu_render_line), (dvdspu_update_chgcol), (dvdspu_render_line_with_chgcol), (dvdspu_blend_comp_buffers), (gstdvdspu_render_spu): * gst/dvdspu/gstdvdspu.c: (dvdspu_base_init), (dvdspu_class_init), (dvdspu_init), (dvdspu_clear), (dvdspu_dispose), (dvdspu_finalize), (dvdspu_flush_spu_info), (dvdspu_buffer_alloc), (dvdspu_src_event), (dvdspu_video_set_caps), (dvdspu_video_proxy_getcaps), (dvdspu_video_event), (dvdspu_video_chain), (dvspu_handle_vid_buffer), (dvdspu_redraw_still), (gstdvdspu_parse_chg_colcon), (dvdspu_exec_cmd_blk), (dvdspu_finish_spu_buf), (dvdspu_setup_cmd_blk), (dvdspu_handle_new_spu_buf), (dvdspu_handle_dvd_event), (dvdspu_dump_dcsq), (dvdspu_advance_spu), (dvdspu_check_still_updates), (dvdspu_subpic_chain), (dvdspu_subpic_event), (dvdspu_change_state), (gstdvdspu_plugin_init): * gst/dvdspu/gstdvdspu.h: dvdspu element donated by Fluendo. It implements a DVD Sub-Picture Unit, decoding and overlaying DVD subtitles and menu graphics. * gst/mpeg2sub/.cvsignore: * gst/mpeg2sub/Makefile.am: * gst/mpeg2sub/Notes.txt: * gst/mpeg2sub/gstmpeg2subt.c: * gst/mpeg2sub/gstmpeg2subt.h: * gst/mpeg2sub/mpeg2subt.vcproj: Delete old and broken mpeg2subt element that was never ported from 0.8 --- ChangeLog | 37 ++ configure.ac | 2 + gst/dvdspu/.gitignore | 7 + gst/dvdspu/Notes.txt | 324 ++++++++++ gst/dvdspu/gstdvdspu-render.c | 521 ++++++++++++++++ gst/dvdspu/gstdvdspu.c | 1376 +++++++++++++++++++++++++++++++++++++++++ gst/dvdspu/gstdvdspu.h | 226 +++++++ gst/mpeg2sub/.gitignore | 7 - gst/mpeg2sub/Makefile.am | 12 - gst/mpeg2sub/Notes.txt | 324 ---------- gst/mpeg2sub/gstmpeg2subt.c | 1049 ------------------------------- gst/mpeg2sub/gstmpeg2subt.h | 114 ---- gst/mpeg2sub/mpeg2subt.vcproj | 148 ----- 13 files changed, 2493 insertions(+), 1654 deletions(-) create mode 100644 gst/dvdspu/.gitignore create mode 100644 gst/dvdspu/Notes.txt create mode 100644 gst/dvdspu/gstdvdspu-render.c create mode 100644 gst/dvdspu/gstdvdspu.c create mode 100644 gst/dvdspu/gstdvdspu.h delete mode 100644 gst/mpeg2sub/.gitignore delete mode 100644 gst/mpeg2sub/Makefile.am delete mode 100644 gst/mpeg2sub/Notes.txt delete mode 100644 gst/mpeg2sub/gstmpeg2subt.c delete mode 100644 gst/mpeg2sub/gstmpeg2subt.h delete mode 100644 gst/mpeg2sub/mpeg2subt.vcproj diff --git a/ChangeLog b/ChangeLog index a9cb8991..80fea138 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,40 @@ +2007-08-27 Jan Schmidt + + * configure.ac: + * gst/dvdspu/.cvsignore: + * gst/dvdspu/Notes.txt: + * gst/dvdspu/gstdvdspu-render.c: (dvdspu_recalc_palette), + (dvdspu_update_palettes), (dvdspu_clear_comp_buffers), + (dvdspu_get_nibble), (dvdspu_get_rle_code), (dvdspu_draw_rle_run), + (rle_end_x), (dvdspu_render_line), (dvdspu_update_chgcol), + (dvdspu_render_line_with_chgcol), (dvdspu_blend_comp_buffers), + (gstdvdspu_render_spu): + * gst/dvdspu/gstdvdspu.c: (dvdspu_base_init), (dvdspu_class_init), + (dvdspu_init), (dvdspu_clear), (dvdspu_dispose), (dvdspu_finalize), + (dvdspu_flush_spu_info), (dvdspu_buffer_alloc), (dvdspu_src_event), + (dvdspu_video_set_caps), (dvdspu_video_proxy_getcaps), + (dvdspu_video_event), (dvdspu_video_chain), + (dvspu_handle_vid_buffer), (dvdspu_redraw_still), + (gstdvdspu_parse_chg_colcon), (dvdspu_exec_cmd_blk), + (dvdspu_finish_spu_buf), (dvdspu_setup_cmd_blk), + (dvdspu_handle_new_spu_buf), (dvdspu_handle_dvd_event), + (dvdspu_dump_dcsq), (dvdspu_advance_spu), + (dvdspu_check_still_updates), (dvdspu_subpic_chain), + (dvdspu_subpic_event), (dvdspu_change_state), + (gstdvdspu_plugin_init): + * gst/dvdspu/gstdvdspu.h: + + dvdspu element donated by Fluendo. It implements a DVD Sub-Picture + Unit, decoding and overlaying DVD subtitles and menu graphics. + + * gst/mpeg2sub/.cvsignore: + * gst/mpeg2sub/Makefile.am: + * gst/mpeg2sub/Notes.txt: + * gst/mpeg2sub/gstmpeg2subt.c: + * gst/mpeg2sub/gstmpeg2subt.h: + * gst/mpeg2sub/mpeg2subt.vcproj: + Delete old and broken mpeg2subt element that was never ported from 0.8 + 2007-08-24 Julien MOUTTE * gst/flv/gstflvdemux.c: (gst_flv_demux_flush), diff --git a/configure.ac b/configure.ac index 1c8030ff..e9e5cfdd 100644 --- a/configure.ac +++ b/configure.ac @@ -83,6 +83,7 @@ GST_PLUGINS_ALL="\ bayer \ cdxaparse \ deinterlace \ + dvdspu \ equalizer \ festival \ filter \ @@ -989,6 +990,7 @@ gst/app/Makefile gst/bayer/Makefile gst/cdxaparse/Makefile gst/deinterlace/Makefile +gst/dvdspu/Makefile gst/equalizer/Makefile gst/festival/Makefile gst/filter/Makefile diff --git a/gst/dvdspu/.gitignore b/gst/dvdspu/.gitignore new file mode 100644 index 00000000..08f5ed37 --- /dev/null +++ b/gst/dvdspu/.gitignore @@ -0,0 +1,7 @@ +Makefile +Makefile.in +*.o +*.lo +*.la +.deps +.libs diff --git a/gst/dvdspu/Notes.txt b/gst/dvdspu/Notes.txt new file mode 100644 index 00000000..a0e56e3c --- /dev/null +++ b/gst/dvdspu/Notes.txt @@ -0,0 +1,324 @@ + + DVD subtitles + --------------- + + + 0. Introduction + 1. Basics + 2. The data structure + 3. Reading the control header + 4. Decoding the graphics + 5. What I do not know yet / What I need + 6. Thanks + 7. Changes + + + + + +The latest version of this document can be found here: +http://www.via.ecp.fr/~sam/doc/dvd/ + + + + + +0. Introduction + + One of the last things we missed in DVD decoding under my system was the +decoding of subtitles. I found no information on the web or Usenet about them, +apart from a few words on them being run-length encoded in the DVD FAQ. + + So we decided to reverse-engineer their format (it's completely legal in +France, since we did it on interoperability purposes), and managed to get +almost all of it. + + + + + +1. Basics + + DVD subtitles are hidden in private PS packets (0x000001ba), just like AC3 +streams are. + + Within the PS packet, there are PES packets, and like AC3, the header for the +ones containing subtitles have a 0x000001bd header. + As for AC3, where there's an ID like (0x80 + x), there's a subtitle ID equal +to (0x20 + x), where x is the subtitle ID. Thus there seems to be only +16 possible different subtitles on a DVD (my Taxi Driver copy has 16). + + I'll suppose you know how to extract AC3 from a DVD, and jump to the +interesting part of this documentation. Anyway you're unlikely to have +understood what I said without already being familiar with MPEG2. + + + + + +2. The data structure + +A subtitle packet, after its parts have been collected and appended, looks +like this : + + +----------------------------------------------------------+ + | | + | 0 2 size | + | +----+------------------------+-----------------+ | + | |size| data packet | control | | + | +----+------------------------+-----------------+ | + | | + | a subtitle packet | + | | + +----------------------------------------------------------+ + +size is a 2 bytes word, and data packet and control may have any size. + + +Here is the structure of the data packet : + + +----------------------------------------------------------+ + | | + | 2 4 S0+2 | + | +----+------------------------------------------+ | + | | S0 | data | | + | +----+------------------------------------------+ | + | | + | the data packet | + | | + +----------------------------------------------------------+ + +S0, the data packet size, is a 2 bytes word. + + +Finally, here's the structure of the control packet : + + +----------------------------------------------------------+ + | | + | S0+2 S0+4 S1 size | + | +----+---------+---------+--+---------+--+---------+ | + | | S1 |ctrl seq |ctrl seq |..|ctrl seq |ff| end seq | | + | +----+---------+---------+--+---------+--+---------+ | + | | + | the control packet | + | | + +----------------------------------------------------------+ + +To summarize : + + - S1, at offset S0+2, the position of the end sequence + - several control sequences + - the 'ff' byte + - the end sequence + + + + + +3. Reading the control header + +The first thing to read is the control sequences. There are several +types of them, and each type is determined by its first byte. As far +as I know, each type has a fixed length. + + * type 0x01 : '01' - 1 byte + it seems to be an empty control sequence. + + * type 0x03 : '03wxyz' - 3 bytes + this one has the palette information ; it basically says 'encoded color 0 + is the wth color of the palette, encoded color 1 is the xth color, aso. + + * type 0x04 : '04wxyz' - 3 bytes + I *think* this is the alpha channel information ; I only saw values of 0 or f + for those nibbles, so I can't really be sure, but it seems plausable. + + * type 0x05 : '05xxxXXXyyyYYY' - 7 bytes + the coordinates of the subtitle on the screen : + xxx is the first column of the subtitle + XXX is the last column of the subtitle + yyy is the first line of the subtitle + YYY is the last line of the subtitle + thus the subtitle's size is (XXX-xxx+1) x (YYY-yyy+1) + + * type 0x06 : '06xxxxyyyy' - 5 bytes + xxxx is the position of the first graphic line, and yyyy is the position of + the second one (the graphics are interlaced, so it helps a lot :p) + +The end sequence has this structure: + + xxxx yyyy 02 ff (ff) + + it ends with 'ff' or 'ffff', to make the whole packet have an even length. + +FIXME: I absolutely don't know what xxxx is. I suppose it may be some date +information since I found it nowhere else, but I can't be sure. + + yyyy is equal to S1 (see picture). + + +Example of a control header : +---- +0A 0C 01 03 02 31 04 0F F0 05 00 02 CF 00 22 3E 06 00 06 04 E9 FF 00 93 0A 0C 02 FF +---- +Let's decode it. First of all, S1 = 0x0a0c. + +The control sequences are : + 01 + Nothing to say about this one + 03 02 31 + Color 0 is 0, color 1 is 2, color 2 is 3, and color 3 is 1. + 04 0F F0 + Colors 0 and 3 are transparent, and colors 2 and 3 are opaque (not sure of this one) + 05 00 02 CF 00 22 3E + The first column is 0x000, the last one is 0x2cf, the first line is 0x002, and + the last line is 0x23e. Thus the subtitle's size is 0x2d0 x 0x23d. + 06 00 06 04 E9 + The first encoded image starts at offset 0x006, and the second one starts at 0x04e9. + +And the end sequence is : + 00 93 0A 0C 02 FF + Which means... well, not many things now. We can at least verify that S1 (0x0a0c) is + there. + + + + + +4. Decoding the graphics + + The graphics are rather easy to decode (at least, when you know how to do it - it + took us one whole week to figure out what the encoding was :p). + + The picture is interlaced, for instance for a 40 lines picture : + + line 0 ---------------#---------- + line 2 ------#------------------- + ... + line 38 ------------#------------- + line 1 ------------------#------- + line 3 --------#----------------- + ... + line 39 -------------#------------ + + When decoding you should get: + + line 0 ---------------#---------- + line 1 ------------------#------- + line 2 ------#------------------- + line 3 --------#----------------- + ... + line 38 ------------#------------- + line 39 -------------#------------ + + Computers with weak processors could choose only to decode even lines + in order to gain some time, for instance. + + + The encoding is run-length encoded, with the following alphabet: + + 0xf + 0xe + 0xd + 0xc + 0xb + 0xa + 0x9 + 0x8 + 0x7 + 0x6 + 0x5 + 0x4 + 0x3- + 0x2- + 0x1- + 0x0f- + 0x0e- + 0x0d- + 0x0c- + 0x0b- + 0x0a- + 0x09- + 0x08- + 0x07- + 0x06- + 0x05- + 0x04- + 0x03-- + 0x02-- + 0x01-- + 0x0000 + + '-' stands for any other nibble. Once a sequence X of this alphabet has + been read, the pixels can be displayed : (X >> 2) is the number of pixels + to display, and (X & 0x3) is the color of the pixel. + + For instance, 0x23 means "8 pixels of color 3". + + "0000" has a special meaning : it's a carriage return. The decoder should + do a carriage return when reaching the end of the line, or when encountering + this "0000" sequence. When doing a carriage return, the parser should be + reset to the next even position (it cannot be nibble-aligned at the start + of a line). + + After a carriage return, the parser should read a line on the other + interlaced picture, and swap like this after each carriage return. + + Perhaps I don't explain this very well, so you'd better have a look at + the enclosed source. + + + + + +5. What I do not know yet / What I need + +I don't know what's in the end sequence yet. + +Also, I don't know exactly when to display subtitles, and when to remove them. + +I don't know if there are other types of control sequences (in my programs I consider +0xff as a control sequence type, as well as 0x02. I don't know if it's correct or not, +so please comment on this). + +I don't know what the "official" color palette is. + +I don't know how to handle transparency information. + +I don't know if this document is generic enough. + +So what I need is you : + + - if you can, patch this document or my programs to fix strange behaviour with your subtitles. + + - send me your subtitles (there's a program to extract them enclosed) ; the first 10 KB + of subtitles in a VOB should be enough, but it would be cool if you sent me one subtitle + file per language. + + + + + +6. Thanks + + Thanks to Michel Lespinasse for his great help on understanding +the RLE stuff, and for all the ideas he had. + + Thanks to mass (David Waite) and taaz (David I. Lehn) from irc at +openprojects.net for sending me their subtitles. + + + + + +7. Changes + + 20000116: added the 'changes' section. + 20000116: added David Waite's and David I. Lehn's name. + 20000116: changed "x0" and "x1" to "S0" and "S1" to make it less confusing. + + + + +-- +Paris, January 16th 2000 +Samuel Hocevar diff --git a/gst/dvdspu/gstdvdspu-render.c b/gst/dvdspu/gstdvdspu-render.c new file mode 100644 index 00000000..1825712d --- /dev/null +++ b/gst/dvdspu/gstdvdspu-render.c @@ -0,0 +1,521 @@ +/* GStreamer DVD Sub-Picture Unit + * Copyright (C) 2007 Fluendo S.A. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include + +#include "gstdvdspu.h" + +GST_DEBUG_CATEGORY_EXTERN (dvdspu_debug); +#define GST_CAT_DEFAULT dvdspu_debug + +static void +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) +{ + /* The area to clear is the line inside the disp_rect, each entry 2 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); + + 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]) +{ + 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]); + + if (comp_last_x < state->disp_rect.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); +#endif + + /* Set up the output pointers */ + out_U = planes[1]; /* U plane */ + out_V = planes[2]; /* V plane */ + + /* Input starts at the first pixel of the compositing buffer */ + in_U = state->comp_bufs[0]; /* U comp buffer */ + in_V = state->comp_bufs[1]; /* V comp buffer */ + in_A = state->comp_bufs[2]; /* A comp buffer */ + + /* Calculate how many pixels to blend based on the maximum X value that was + * 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; + + for (x = left; x < uv_end; x++) { + guint16 tmp; + guint16 inv_A = (4 * 0xf) - in_A[x]; + + /* Each entry in the compositing buffer is 4 summed pixels, so the + * inverse alpha is (4 * 0x0f) - in_A[x] */ + tmp = in_U[x] + inv_A * out_U[x]; + out_U[x] = (guint8) (tmp / (4 * 0xf)); + + tmp = in_V[x] + inv_A * out_V[x]; + out_V[x] = (guint8) (tmp / (4 * 0xf)); + } +} + +void +gstdvdspu_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 */ + + /* 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); + + /* We start rendering from the first line of the display rect */ + y = 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); + + /* 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; + + /* 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); + for (state->cur_Y = y; state->cur_Y <= last_y; state->cur_Y++) { + /* 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]); + /* 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); + + /* 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); + } + + /* 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 new file mode 100644 index 00000000..85d80f5a --- /dev/null +++ b/gst/dvdspu/gstdvdspu.c @@ -0,0 +1,1376 @@ +/* GStreamer DVD Sub-Picture Unit + * Copyright (C) 2007 Fluendo S.A. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include +#include + +#include "gstdvdspu.h" + +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 +{ + /* FILL ME */ + LAST_SIGNAL +}; + +static GstStaticPadTemplate video_sink_factory = +GST_STATIC_PAD_TEMPLATE ("video", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw-yuv, " "format = (fourcc) { I420 }, " + "width = (int) [ 16, 4096 ], " "height = (int) [ 16, 4096 ]") + /* FIXME: Can support YV12 one day too */ + ); + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw-yuv, " "format = (fourcc) { I420 }, " + "width = (int) [ 16, 4096 ], " "height = (int) [ 16, 4096 ]") + /* FIXME: Can support YV12 one day too */ + ); + +static GstStaticPadTemplate subpic_sink_factory = +GST_STATIC_PAD_TEMPLATE ("subpicture", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-dvd-subpicture") + ); + +GST_BOILERPLATE (GstDVDSpu, dvdspu, GstElement, GST_TYPE_ELEMENT); + +static void dvdspu_dispose (GstDVDSpu * dvdspu); +static void dvdspu_finalize (GstDVDSpu * dvdspu); +static GstStateChangeReturn dvdspu_change_state (GstElement * element, + GstStateChange transition); + +static gboolean dvdspu_src_event (GstPad * pad, GstEvent * event); + +static GstCaps *dvdspu_video_proxy_getcaps (GstPad * pad); +static gboolean dvdspu_video_set_caps (GstPad * pad, GstCaps * caps); +static GstFlowReturn dvdspu_video_chain (GstPad * pad, GstBuffer * buf); +static gboolean dvdspu_video_event (GstPad * pad, GstEvent * event); +static GstFlowReturn dvdspu_buffer_alloc (GstPad * sinkpad, guint64 offset, + guint size, GstCaps * caps, GstBuffer ** buf); +static void dvdspu_redraw_still (GstDVDSpu * dvdspu); + +static void dvdspu_check_still_updates (GstDVDSpu * dvdspu); +static GstFlowReturn dvdspu_subpic_chain (GstPad * pad, GstBuffer * buf); +static gboolean dvdspu_subpic_event (GstPad * pad, GstEvent * event); + +static void dvdspu_clear (GstDVDSpu * dvdspu); +static void dvdspu_flush_spu_info (GstDVDSpu * dvdspu); +static void dvdspu_advance_spu (GstDVDSpu * dvdspu, GstClockTime new_ts); +static GstFlowReturn +dvspu_handle_vid_buffer (GstDVDSpu * dvdspu, GstBuffer * buf); + +static void +dvdspu_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 " + "onto the video as it passes through", + "Jan Schmidt "); + GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&video_sink_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&subpic_sink_factory)); + gst_element_class_set_details (element_class, &element_details); + + element_class->change_state = dvdspu_change_state; +} + +static void +dvdspu_class_init (GstDVDSpuClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->dispose = (GObjectFinalizeFunc) dvdspu_dispose; + gobject_class->finalize = (GObjectFinalizeFunc) dvdspu_finalize; +} + +static void +dvdspu_init (GstDVDSpu * dvdspu, GstDVDSpuClass * gclass) +{ + dvdspu->videosinkpad = + gst_pad_new_from_static_template (&video_sink_factory, "video"); + gst_pad_set_setcaps_function (dvdspu->videosinkpad, dvdspu_video_set_caps); + gst_pad_set_getcaps_function (dvdspu->videosinkpad, + dvdspu_video_proxy_getcaps); + gst_pad_set_chain_function (dvdspu->videosinkpad, dvdspu_video_chain); + gst_pad_set_event_function (dvdspu->videosinkpad, dvdspu_video_event); + gst_pad_set_bufferalloc_function (dvdspu->videosinkpad, dvdspu_buffer_alloc); + + dvdspu->srcpad = gst_pad_new_from_static_template (&src_factory, "src"); + gst_pad_set_event_function (dvdspu->srcpad, dvdspu_src_event); + gst_pad_set_getcaps_function (dvdspu->srcpad, dvdspu_video_proxy_getcaps); + + dvdspu->subpic_sinkpad = + gst_pad_new_from_static_template (&subpic_sink_factory, "subpicture"); + gst_pad_set_chain_function (dvdspu->subpic_sinkpad, dvdspu_subpic_chain); + gst_pad_set_event_function (dvdspu->subpic_sinkpad, dvdspu_subpic_event); + gst_pad_use_fixed_caps (dvdspu->subpic_sinkpad); + + gst_element_add_pad (GST_ELEMENT (dvdspu), dvdspu->videosinkpad); + gst_element_add_pad (GST_ELEMENT (dvdspu), dvdspu->subpic_sinkpad); + gst_element_add_pad (GST_ELEMENT (dvdspu), dvdspu->srcpad); + + dvdspu->spu_lock = g_mutex_new (); + dvdspu->pending_spus = g_queue_new (); + + dvdspu_clear (dvdspu); +} + +static void +dvdspu_clear (GstDVDSpu * dvdspu) +{ + dvdspu_flush_spu_info (dvdspu); + + gst_buffer_replace (&dvdspu->ref_frame, NULL); + gst_buffer_replace (&dvdspu->pending_frame, NULL); + + dvdspu->spu_state.fps_n = 25; + dvdspu->spu_state.fps_d = 1; + + gst_segment_init (&dvdspu->video_seg, GST_FORMAT_UNDEFINED); +} + +static void +dvdspu_dispose (GstDVDSpu * dvdspu) +{ + /* need to hold the SPU lock in case other stuff is still running... */ + GSTDVDSPU_LOCK (dvdspu); + dvdspu_clear (dvdspu); + GSTDVDSPU_UNLOCK (dvdspu); +} + +static void +dvdspu_finalize (GstDVDSpu * dvdspu) +{ + gint i; + + for (i = 0; i < 3; i++) { + if (dvdspu->spu_state.comp_bufs[i] != NULL) { + g_free (dvdspu->spu_state.comp_bufs[i]); + dvdspu->spu_state.comp_bufs[i] = NULL; + } + } + g_queue_free (dvdspu->pending_spus); + g_mutex_free (dvdspu->spu_lock); +} + +/* With SPU lock held, clear the queue of SPU packets */ +static void +dvdspu_flush_spu_info (GstDVDSpu * dvdspu) +{ + SpuPacket *packet; + SpuState *state = &dvdspu->spu_state; + + 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; + } + + packet = (SpuPacket *) g_queue_pop_head (dvdspu->pending_spus); + while (packet != NULL) { + if (packet->buf) + gst_buffer_unref (packet->buf); + if (packet->event) + gst_event_unref (packet->event); + g_free (packet); + packet = (SpuPacket *) g_queue_pop_head (dvdspu->pending_spus); + } + + 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->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; + } +} + +/* Proxy buffer allocations on the video sink pad downstream */ +static GstFlowReturn +dvdspu_buffer_alloc (GstPad * sinkpad, guint64 offset, guint size, + GstCaps * caps, GstBuffer ** buf) +{ + GstDVDSpu *dvdspu = GSTDVDSPU (gst_pad_get_parent (sinkpad)); + GstFlowReturn ret = GST_FLOW_OK; + + ret = gst_pad_alloc_buffer (dvdspu->srcpad, offset, size, caps, buf); + + gst_object_unref (dvdspu); + + return ret; +} + +static gboolean +dvdspu_src_event (GstPad * pad, GstEvent * event) +{ + GstDVDSpu *dvdspu = GSTDVDSPU (gst_pad_get_parent (pad)); + GstPad *peer; + gboolean res = TRUE; + + peer = gst_pad_get_peer (dvdspu->videosinkpad); + if (peer) { + res = gst_pad_send_event (peer, event); + gst_object_unref (peer); + } + + gst_object_unref (dvdspu); + return res; +} + +static gboolean +dvdspu_video_set_caps (GstPad * pad, GstCaps * caps) +{ + GstDVDSpu *dvdspu = GSTDVDSPU (gst_pad_get_parent (pad)); + gboolean res = FALSE; + GstStructure *s; + gint w, h; + gint i; + gint fps_n, fps_d; + SpuState *state; + + s = gst_caps_get_structure (caps, 0); + + if (!gst_structure_get_int (s, "width", &w) || + !gst_structure_get_int (s, "height", &h) || + !gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d)) { + goto done; + } + + GSTDVDSPU_LOCK (dvdspu); + + state = &dvdspu->spu_state; + + state->fps_n = fps_n; + state->fps_d = fps_d; + + state->vid_height = h; + state->Y_height = GST_ROUND_UP_2 (h); + state->UV_height = state->Y_height / 2; + + if (state->vid_width != w) { + state->vid_width = w; + state->Y_stride = GST_ROUND_UP_4 (w); + 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); + } + } + GSTDVDSPU_UNLOCK (dvdspu); + + res = TRUE; +done: + gst_object_unref (dvdspu); + return res; +} + +static GstCaps * +dvdspu_video_proxy_getcaps (GstPad * pad) +{ + GstDVDSpu *dvdspu = GSTDVDSPU (gst_pad_get_parent (pad)); + GstCaps *caps; + GstPad *otherpad; + + /* Proxy the getcaps between videosink and the srcpad, ignoring the + * subpicture sink pad */ + otherpad = (pad == dvdspu->srcpad) ? dvdspu->videosinkpad : dvdspu->srcpad; + + caps = gst_pad_peer_get_caps (otherpad); + if (caps) { + GstCaps *temp; + const GstCaps *templ; + + templ = gst_pad_get_pad_template_caps (otherpad); + temp = gst_caps_intersect (caps, templ); + gst_caps_unref (caps); + caps = temp; + } else { + caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); + } + + gst_object_unref (dvdspu); + return caps; +} + +static gboolean +dvdspu_video_event (GstPad * pad, GstEvent * event) +{ + GstDVDSpu *dvdspu = (GstDVDSpu *) (gst_object_get_parent (GST_OBJECT (pad))); + SpuState *state = &dvdspu->spu_state; + gboolean res = TRUE; + + g_return_val_if_fail (dvdspu != NULL, FALSE); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_DOWNSTREAM: + case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: + { + const GstStructure *structure = gst_event_get_structure (event); + const char *event_type; + + if (!gst_structure_has_name (structure, "application/x-gst-dvd")) { + res = gst_pad_event_default (pad, event); + break; + } + + event_type = gst_structure_get_string (structure, "event"); + GST_DEBUG_OBJECT (dvdspu, + "DVD event of type %s on video pad", event_type); + + if (strcmp (event_type, "dvd-still") == 0) { + gboolean in_still; + + if (gst_structure_get_boolean (structure, "still-state", &in_still)) { + GSTDVDSPU_LOCK (dvdspu); + if (in_still) { + state->flags |= SPU_STATE_STILL_FRAME; + /* Entering still. Advance the SPU to make sure the state is + * up to date */ + dvdspu_check_still_updates (dvdspu); + /* And re-draw the still frame to make sure it appears on + * screen, otherwise the last frame might have been discarded + * by QoS */ + dvdspu_redraw_still (dvdspu); + } else + state->flags &= ~(SPU_STATE_STILL_FRAME); + GSTDVDSPU_UNLOCK (dvdspu); + } + gst_event_unref (event); + res = TRUE; + } else + res = gst_pad_event_default (pad, event); + break; + } + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + gdouble rate, arate; + GstFormat format; + gint64 start, stop, time; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + if (format != GST_FORMAT_TIME) + return FALSE; + + /* Only print updates if they have an end time (don't print start_time + * updates */ + GST_DEBUG_OBJECT (dvdspu, "video pad NewSegment:" + " Update %d, rate %g arate %g format %d start %" GST_TIME_FORMAT + " %" GST_TIME_FORMAT " position %" GST_TIME_FORMAT, + update, rate, arate, format, GST_TIME_ARGS (start), + GST_TIME_ARGS (stop), GST_TIME_ARGS (time)); + + GSTDVDSPU_LOCK (dvdspu); + + if (update && start > dvdspu->video_seg.last_stop) { +#if 0 + g_print ("Segment update for video. Advancing from %" GST_TIME_FORMAT + " to %" GST_TIME_FORMAT "\n", + GST_TIME_ARGS (dvdspu->video_seg.last_stop), GST_TIME_ARGS (start)); +#endif + while (dvdspu->video_seg.last_stop < start && + !(state->flags & SPU_STATE_STILL_FRAME)) { + GSTDVDSPU_UNLOCK (dvdspu); + if (dvspu_handle_vid_buffer (dvdspu, NULL) != GST_FLOW_OK) { + GSTDVDSPU_LOCK (dvdspu); + break; + } + GSTDVDSPU_LOCK (dvdspu); + } + } + + gst_segment_set_newsegment_full (&dvdspu->video_seg, update, rate, arate, + format, start, stop, time); + + GSTDVDSPU_UNLOCK (dvdspu); + + res = gst_pad_event_default (pad, event); + break; + } + case GST_EVENT_FLUSH_START: + res = gst_pad_event_default (pad, event); + goto done; + case GST_EVENT_FLUSH_STOP: + res = gst_pad_event_default (pad, event); + + GSTDVDSPU_LOCK (dvdspu); + gst_segment_init (&dvdspu->video_seg, GST_FORMAT_UNDEFINED); + gst_buffer_replace (&dvdspu->ref_frame, NULL); + gst_buffer_replace (&dvdspu->pending_frame, NULL); + + GSTDVDSPU_UNLOCK (dvdspu); + goto done; + default: + res = gst_pad_event_default (pad, event); + break; + } + +done: + gst_object_unref (dvdspu); + return res; +#if 0 +error: + gst_event_unref (event); + return FALSE; +#endif +} + +static GstFlowReturn +dvdspu_video_chain (GstPad * pad, GstBuffer * buf) +{ + GstDVDSpu *dvdspu = (GstDVDSpu *) (gst_object_get_parent (GST_OBJECT (pad))); + GstFlowReturn ret; + + g_return_val_if_fail (dvdspu != NULL, GST_FLOW_ERROR); + + GST_LOG_OBJECT (dvdspu, "video buffer %p with TS %" GST_TIME_FORMAT, + buf, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + + ret = dvspu_handle_vid_buffer (dvdspu, buf); + + gst_object_unref (dvdspu); + + return ret; +} + +static GstFlowReturn +dvspu_handle_vid_buffer (GstDVDSpu * dvdspu, GstBuffer * buf) +{ + GstClockTime new_ts; + GstFlowReturn ret; + gboolean using_ref = FALSE; + + GSTDVDSPU_LOCK (dvdspu); + + if (buf == NULL) { + GstClockTime next_ts = dvdspu->video_seg.last_stop; + + next_ts += gst_util_uint64_scale_int (GST_SECOND, + dvdspu->spu_state.fps_d, dvdspu->spu_state.fps_n); + + /* NULL buffer was passed - use the reference frame and update the timestamp, + * or else there's nothing to draw, and just return GST_FLOW_OK */ + if (dvdspu->ref_frame == NULL) { + gst_segment_set_last_stop (&dvdspu->video_seg, GST_FORMAT_TIME, next_ts); + goto no_ref_frame; + } + + buf = gst_buffer_copy (dvdspu->ref_frame); + +#if 0 + g_print ("Duping frame %" GST_TIME_FORMAT " with new TS %" GST_TIME_FORMAT + "\n", GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), + GST_TIME_ARGS (next_ts)); +#endif + + GST_BUFFER_TIMESTAMP (buf) = next_ts; + using_ref = TRUE; + } + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + gst_segment_set_last_stop (&dvdspu->video_seg, GST_FORMAT_TIME, + GST_BUFFER_TIMESTAMP (buf)); + } + + new_ts = gst_segment_to_running_time (&dvdspu->video_seg, GST_FORMAT_TIME, + dvdspu->video_seg.last_stop); + +#if 0 + g_print ("TS %" GST_TIME_FORMAT " running: %" GST_TIME_FORMAT "\n", + GST_TIME_ARGS (dvdspu->video_seg.last_stop), GST_TIME_ARGS (new_ts)); +#endif + + dvdspu_advance_spu (dvdspu, new_ts); + + /* If we have an active SPU command set, we store a copy of the frame in case + * we hit a still and need to draw on it. Otherwise, a reference is + * sufficient in case we later encounter a still */ + if ((dvdspu->spu_state.flags & SPU_STATE_FORCED_DSP) || + ((dvdspu->spu_state.flags & SPU_STATE_FORCED_ONLY) == 0 && + (dvdspu->spu_state.flags & SPU_STATE_DISPLAY))) { + if (using_ref == FALSE) { + GstBuffer *copy; + + /* Take a copy in case we hit a still frame and need the pristine + * frame around */ + copy = gst_buffer_copy (buf); + gst_buffer_replace (&dvdspu->ref_frame, copy); + gst_buffer_unref (copy); + } + + /* Render the SPU overlay onto the buffer */ + buf = gst_buffer_make_writable (buf); + + gstdvdspu_render_spu (dvdspu, buf); + } else { + if (using_ref == FALSE) { + /* Not going to draw anything on this frame, just store a reference + * in case we hit a still frame and need it */ + gst_buffer_replace (&dvdspu->ref_frame, buf); + } + } + + if (dvdspu->spu_state.flags & SPU_STATE_STILL_FRAME) { + GST_DEBUG_OBJECT (dvdspu, "Outputting buffer with TS %" GST_TIME_FORMAT + "from chain while in still", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + } + + GSTDVDSPU_UNLOCK (dvdspu); + + /* just push out the incoming buffer without touching it */ + ret = gst_pad_push (dvdspu->srcpad, buf); + + return ret; + +no_ref_frame: + + GSTDVDSPU_UNLOCK (dvdspu); + + return GST_FLOW_OK; +} + +/* With SPU LOCK */ +static void +dvdspu_redraw_still (GstDVDSpu * dvdspu) +{ + /* If we have an active SPU command set and a reference frame, copy the + * frame, redraw the SPU and store it as the pending frame for output */ + if (dvdspu->ref_frame) { + if ((dvdspu->spu_state.flags & SPU_STATE_FORCED_DSP) || + ((dvdspu->spu_state.flags & SPU_STATE_FORCED_ONLY) == 0 && + (dvdspu->spu_state.flags & SPU_STATE_DISPLAY))) { + GstBuffer *buf = gst_buffer_copy (dvdspu->ref_frame); + + buf = gst_buffer_make_writable (buf); + + GST_LOG_OBJECT (dvdspu, "Redraw due to Still Frame with ref %p", + dvdspu->ref_frame); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + GST_BUFFER_TIMESTAMP (buf) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (buf) = GST_CLOCK_TIME_NONE; + + /* Render the SPU overlay onto the buffer */ + gstdvdspu_render_spu (dvdspu, buf); + gst_buffer_replace (&dvdspu->pending_frame, buf); + } else { + GST_LOG_OBJECT (dvdspu, + "Redraw due to Still Frame skipped - no SPU to draw"); + } + } else { + GST_LOG_OBJECT (dvdspu, "Not redrawing still frame - no ref frame"); + } +} + +static void +gstdvdspu_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 +dvdspu_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 */ + + gstdvdspu_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 +dvdspu_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 +dvdspu_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 +dvdspu_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); + dvdspu_setup_cmd_blk (dvdspu, state->cur_cmd_blk, start, end); + /* Clear existing chg-colcon info */ + if (state->line_ctrl_i != NULL) { + g_free (state->line_ctrl_i); + state->line_ctrl_i = NULL; + } + return; + +invalid: + /* Invalid buffer */ + dvdspu_finish_spu_buf (dvdspu); +} + +static void +dvdspu_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"); + 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; + } + } + + if (hl_change && (state->flags & SPU_STATE_STILL_FRAME)) { + dvdspu_redraw_still (dvdspu); + } + + gst_event_unref (event); +} + +#if 0 +static void +dvdspu_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; + dvdspu_exec_cmd_blk (dvdspu, data, end); + } +} +#endif + +/* Advance the SPU packet/command queue to a time. new_ts is in running time */ +static void +dvdspu_advance_spu (GstDVDSpu * dvdspu, GstClockTime new_ts) +{ + SpuState *state = &dvdspu->spu_state; + + while (state->next_ts == GST_CLOCK_TIME_NONE || state->next_ts <= new_ts) { + guint8 *start, *cmd_blk, *end; + guint16 next_blk; + + if (state->buf == NULL) { + GstClockTime vid_run_ts; + + /* No current command buffer, try and get one */ + SpuPacket *packet = (SpuPacket *) g_queue_pop_head (dvdspu->pending_spus); + + if (packet == NULL) + return; /* No SPU packets available */ + + vid_run_ts = + gst_segment_to_running_time (&dvdspu->video_seg, GST_FORMAT_TIME, + dvdspu->video_seg.last_stop); + GST_LOG_OBJECT (dvdspu, + "Popped new SPU packet with TS %" GST_TIME_FORMAT + ". Video last_stop=%" GST_TIME_FORMAT " (%" GST_TIME_FORMAT ")", + GST_TIME_ARGS (packet->event_ts), GST_TIME_ARGS (vid_run_ts), + GST_TIME_ARGS (dvdspu->video_seg.last_stop)); + + if (packet->buf) { + // dvdspu_dump_dcsq (dvdspu, packet->event_ts, packet->buf); + dvdspu_handle_new_spu_buf (dvdspu, packet); + } + if (packet->event) + dvdspu_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 && state->next_ts <= new_ts); + + 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 */ + dvdspu_finish_spu_buf (dvdspu); + continue; + } + + dvdspu_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 */ + dvdspu_setup_cmd_blk (dvdspu, next_blk, start, end); + } else { + /* Next Block points to the current block, so we're finished with this + * SPU buffer */ + dvdspu_finish_spu_buf (dvdspu); + } + } +} + +static void +dvdspu_check_still_updates (GstDVDSpu * dvdspu) +{ + GstClockTime sub_ts; + GstClockTime vid_ts; + + if (dvdspu->spu_state.flags & SPU_STATE_STILL_FRAME) { + + vid_ts = gst_segment_to_running_time (&dvdspu->video_seg, + GST_FORMAT_TIME, dvdspu->video_seg.last_stop); + sub_ts = gst_segment_to_running_time (&dvdspu->subp_seg, + GST_FORMAT_TIME, dvdspu->subp_seg.last_stop); + + vid_ts = MAX (vid_ts, sub_ts); + + GST_DEBUG_OBJECT (dvdspu, + "In still frame - advancing TS to %" GST_TIME_FORMAT + " to process SPU buffer", GST_TIME_ARGS (vid_ts)); + dvdspu_advance_spu (dvdspu, vid_ts); + } +} + +static GstFlowReturn +dvdspu_subpic_chain (GstPad * pad, GstBuffer * buf) +{ + GstDVDSpu *dvdspu = (GstDVDSpu *) (gst_object_get_parent (GST_OBJECT (pad))); + + g_return_val_if_fail (dvdspu != NULL, GST_FLOW_ERROR); + + GST_INFO_OBJECT (dvdspu, "Have subpicture buffer with timestamp %" + GST_TIME_FORMAT " and size %u", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), GST_BUFFER_SIZE (buf)); + + GSTDVDSPU_LOCK (dvdspu); + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + gst_segment_set_last_stop (&dvdspu->subp_seg, GST_FORMAT_TIME, + GST_BUFFER_TIMESTAMP (buf)); + } + + if (dvdspu->partial_spu != NULL) { + 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, + * since that will avoid collecting the 2nd half of a partial buf */ + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) + dvdspu->partial_spu = buf; + else + 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 */ + } 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 = 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 */ + dvdspu_check_still_updates (dvdspu); + } else { + gst_buffer_unref (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; + } + } + + GSTDVDSPU_UNLOCK (dvdspu); + + gst_object_unref (dvdspu); + return GST_FLOW_OK; +} + +static gboolean +dvdspu_subpic_event (GstPad * pad, GstEvent * event) +{ + GstDVDSpu *dvdspu = (GstDVDSpu *) (gst_object_get_parent (GST_OBJECT (pad))); + gboolean res = TRUE; + + g_return_val_if_fail (dvdspu != NULL, FALSE); + + /* Some events on the subpicture sink pad just get ignored, like + * FLUSH_START */ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_DOWNSTREAM: + case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: + { + const GstStructure *structure = gst_event_get_structure (event); + gboolean need_push; + + if (!gst_structure_has_name (structure, "application/x-gst-dvd")) { + res = gst_pad_event_default (pad, event); + break; + } + + GSTDVDSPU_LOCK (dvdspu); + if (GST_EVENT_IS_SERIALIZED (event)) { + SpuPacket *spu_packet = g_new0 (SpuPacket, 1); + + spu_packet->event = event; + g_queue_push_tail (dvdspu->pending_spus, spu_packet); + } else { + dvdspu_handle_dvd_event (dvdspu, event); + } + + /* If the handle_dvd_event generated a pending frame, we + * need to synchronise with the video pad's stream lock and push it. + * This requires some dancing to preserve locking order and handle + * flushes correctly */ + need_push = (dvdspu->pending_frame != NULL); + GSTDVDSPU_UNLOCK (dvdspu); + if (need_push) { + GstBuffer *to_push = NULL; + gboolean flushing; + + GST_LOG_OBJECT (dvdspu, "Going for stream lock"); + GST_PAD_STREAM_LOCK (dvdspu->videosinkpad); + GST_LOG_OBJECT (dvdspu, "Got stream lock"); + GST_OBJECT_LOCK (dvdspu->videosinkpad); + flushing = GST_PAD_IS_FLUSHING (dvdspu->videosinkpad); + GST_OBJECT_UNLOCK (dvdspu->videosinkpad); + + GSTDVDSPU_LOCK (dvdspu); + if (dvdspu->pending_frame == NULL || flushing) { + /* Got flushed while waiting for the stream lock */ + GSTDVDSPU_UNLOCK (dvdspu); + } else { + to_push = dvdspu->pending_frame; + dvdspu->pending_frame = NULL; + + GSTDVDSPU_UNLOCK (dvdspu); + gst_pad_push (dvdspu->srcpad, to_push); + } + GST_LOG_OBJECT (dvdspu, "Dropping stream lock"); + GST_PAD_STREAM_UNLOCK (dvdspu->videosinkpad); + } + + break; + } + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + gdouble rate; + GstFormat format; + gint64 start, stop, time; + gdouble arate; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + /* Only print updates if they have an end time (don't print start_time + * updates */ + GST_DEBUG_OBJECT (dvdspu, "subpic pad NewSegment:" + " Update %d, rate %g arate %g format %d start %" GST_TIME_FORMAT + " %" GST_TIME_FORMAT " position %" GST_TIME_FORMAT, + update, rate, arate, format, GST_TIME_ARGS (start), + GST_TIME_ARGS (stop), GST_TIME_ARGS (time)); + + GSTDVDSPU_LOCK (dvdspu); + gst_segment_set_newsegment_full (&dvdspu->subp_seg, update, rate, arate, + format, start, stop, time); + GSTDVDSPU_UNLOCK (dvdspu); + + gst_event_unref (event); + break; + } + case GST_EVENT_FLUSH_START: + gst_event_unref (event); + goto done; + case GST_EVENT_FLUSH_STOP: + GSTDVDSPU_LOCK (dvdspu); + dvdspu_flush_spu_info (dvdspu); + GSTDVDSPU_UNLOCK (dvdspu); + + /* We don't forward flushes on the spu pad */ + gst_event_unref (event); + goto done; + default: + res = gst_pad_event_default (pad, event); + break; + } + +done: + gst_object_unref (dvdspu); + + return res; +} + +static GstStateChangeReturn +dvdspu_change_state (GstElement * element, GstStateChange transition) +{ + GstDVDSpu *dvdspu = (GstDVDSpu *) element; + GstStateChangeReturn ret; + + ret = parent_class->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + GSTDVDSPU_LOCK (dvdspu); + dvdspu_clear (dvdspu); + GSTDVDSPU_UNLOCK (dvdspu); + break; + default: + break; + } + + return ret; +} + +gboolean +gstdvdspu_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (dvdspu_debug, "gstdvdspu", + 0, "DVD Sub-picture Overlay decoder/renderer"); + + return gst_element_register (plugin, "dvdspu", + GST_RANK_NONE, GST_TYPE_GSTDVDSPU); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "dvdspu", + "DVD Sub-picture Overlay element", + gstdvdspu_plugin_init, + VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/gst/dvdspu/gstdvdspu.h b/gst/dvdspu/gstdvdspu.h new file mode 100644 index 00000000..46923a58 --- /dev/null +++ b/gst/dvdspu/gstdvdspu.h @@ -0,0 +1,226 @@ +/* GStreamer DVD Sub-Picture Unit + * Copyright (C) 2007 Fluendo S.A. + * + * 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 __GSTDVDSPU_H__ +#define __GSTDVDSPU_H__ + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_GSTDVDSPU \ + (dvdspu_get_type()) +#define GSTDVDSPU(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GSTDVDSPU,GstDVDSpu)) +#define GSTDVDSPU_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GSTDVDSPU,GstDVDSpuClass)) +#define GST_IS_PLUGIN_TEMPLATE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GSTDVDSPU)) +#define GST_IS_PLUGIN_TEMPLATE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GSTDVDSPU)) + +#define GSTDVDSPU_LOCK(s) g_mutex_lock ((s)->spu_lock); +#define GSTDVDSPU_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 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 SpuStateFlags { + SPU_STATE_NONE = 0x00, + /* Flags cleared on a flush */ + SPU_STATE_DISPLAY = 0x01, + SPU_STATE_FORCED_DSP = 0x02, + SPU_STATE_STILL_FRAME = 0x04, + /* Persistent flags */ + SPU_STATE_FORCED_ONLY = 0x100 +}; + +#define SPU_STATE_FLAGS_MASK (0xff) + +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 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 */ + 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; + + /* Output position tracking */ + guint8 *out_Y; + guint16 *out_U; + guint16 *out_V; + guint16 *out_A; +}; + +/* Structure used to store the queue of pending SPU packets. The start_ts is + * stored in running time... + * Also used to carry in-band events so they remain serialised properly */ +struct SpuPacket { + GstClockTime event_ts; + GstBuffer *buf; + GstEvent *event; +}; + +struct GstDVDSpu +{ + GstElement element; + + GstPad *videosinkpad; + GstPad *subpic_sinkpad; + GstPad *srcpad; + + /* Mutex to protect state we access from different chain funcs */ + GMutex *spu_lock; + + GstSegment video_seg; + GstSegment subp_seg; + + SpuState spu_state; + + /* GQueue of SpuBuf structures */ + GQueue *pending_spus; + + /* Accumulator for collecting partial SPU buffers until they're complete */ + GstBuffer *partial_spu; + + /* Store either a reference or a copy of the last video frame for duplication + * during still-frame conditions */ + GstBuffer *ref_frame; + + /* Buffer to push after handling a DVD event, if any */ + GstBuffer *pending_frame; +}; + +struct GstDVDSpuClass +{ + GstElementClass parent_class; +}; + +GType gstdvdspu_get_type (void); +void gstdvdspu_render_spu (GstDVDSpu *dvdspu, GstBuffer *buf); + +G_END_DECLS + +#endif /* __GSTDVDSPU_H__ */ diff --git a/gst/mpeg2sub/.gitignore b/gst/mpeg2sub/.gitignore deleted file mode 100644 index 08f5ed37..00000000 --- a/gst/mpeg2sub/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -Makefile -Makefile.in -*.o -*.lo -*.la -.deps -.libs diff --git a/gst/mpeg2sub/Makefile.am b/gst/mpeg2sub/Makefile.am deleted file mode 100644 index ff2ad1a6..00000000 --- a/gst/mpeg2sub/Makefile.am +++ /dev/null @@ -1,12 +0,0 @@ - -plugin_LTLIBRARIES = libgstmpeg2subt.la - -libgstmpeg2subt_la_SOURCES = gstmpeg2subt.c - -libgstmpeg2subt_la_CFLAGS = $(GST_CFLAGS) -libgstmpeg2subt_la_LIBADD = -libgstmpeg2subt_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) - -noinst_HEADERS = gstmpeg2subt.h - -EXTRA_DIST = Notes.txt diff --git a/gst/mpeg2sub/Notes.txt b/gst/mpeg2sub/Notes.txt deleted file mode 100644 index a0e56e3c..00000000 --- a/gst/mpeg2sub/Notes.txt +++ /dev/null @@ -1,324 +0,0 @@ - - DVD subtitles - --------------- - - - 0. Introduction - 1. Basics - 2. The data structure - 3. Reading the control header - 4. Decoding the graphics - 5. What I do not know yet / What I need - 6. Thanks - 7. Changes - - - - - -The latest version of this document can be found here: -http://www.via.ecp.fr/~sam/doc/dvd/ - - - - - -0. Introduction - - One of the last things we missed in DVD decoding under my system was the -decoding of subtitles. I found no information on the web or Usenet about them, -apart from a few words on them being run-length encoded in the DVD FAQ. - - So we decided to reverse-engineer their format (it's completely legal in -France, since we did it on interoperability purposes), and managed to get -almost all of it. - - - - - -1. Basics - - DVD subtitles are hidden in private PS packets (0x000001ba), just like AC3 -streams are. - - Within the PS packet, there are PES packets, and like AC3, the header for the -ones containing subtitles have a 0x000001bd header. - As for AC3, where there's an ID like (0x80 + x), there's a subtitle ID equal -to (0x20 + x), where x is the subtitle ID. Thus there seems to be only -16 possible different subtitles on a DVD (my Taxi Driver copy has 16). - - I'll suppose you know how to extract AC3 from a DVD, and jump to the -interesting part of this documentation. Anyway you're unlikely to have -understood what I said without already being familiar with MPEG2. - - - - - -2. The data structure - -A subtitle packet, after its parts have been collected and appended, looks -like this : - - +----------------------------------------------------------+ - | | - | 0 2 size | - | +----+------------------------+-----------------+ | - | |size| data packet | control | | - | +----+------------------------+-----------------+ | - | | - | a subtitle packet | - | | - +----------------------------------------------------------+ - -size is a 2 bytes word, and data packet and control may have any size. - - -Here is the structure of the data packet : - - +----------------------------------------------------------+ - | | - | 2 4 S0+2 | - | +----+------------------------------------------+ | - | | S0 | data | | - | +----+------------------------------------------+ | - | | - | the data packet | - | | - +----------------------------------------------------------+ - -S0, the data packet size, is a 2 bytes word. - - -Finally, here's the structure of the control packet : - - +----------------------------------------------------------+ - | | - | S0+2 S0+4 S1 size | - | +----+---------+---------+--+---------+--+---------+ | - | | S1 |ctrl seq |ctrl seq |..|ctrl seq |ff| end seq | | - | +----+---------+---------+--+---------+--+---------+ | - | | - | the control packet | - | | - +----------------------------------------------------------+ - -To summarize : - - - S1, at offset S0+2, the position of the end sequence - - several control sequences - - the 'ff' byte - - the end sequence - - - - - -3. Reading the control header - -The first thing to read is the control sequences. There are several -types of them, and each type is determined by its first byte. As far -as I know, each type has a fixed length. - - * type 0x01 : '01' - 1 byte - it seems to be an empty control sequence. - - * type 0x03 : '03wxyz' - 3 bytes - this one has the palette information ; it basically says 'encoded color 0 - is the wth color of the palette, encoded color 1 is the xth color, aso. - - * type 0x04 : '04wxyz' - 3 bytes - I *think* this is the alpha channel information ; I only saw values of 0 or f - for those nibbles, so I can't really be sure, but it seems plausable. - - * type 0x05 : '05xxxXXXyyyYYY' - 7 bytes - the coordinates of the subtitle on the screen : - xxx is the first column of the subtitle - XXX is the last column of the subtitle - yyy is the first line of the subtitle - YYY is the last line of the subtitle - thus the subtitle's size is (XXX-xxx+1) x (YYY-yyy+1) - - * type 0x06 : '06xxxxyyyy' - 5 bytes - xxxx is the position of the first graphic line, and yyyy is the position of - the second one (the graphics are interlaced, so it helps a lot :p) - -The end sequence has this structure: - - xxxx yyyy 02 ff (ff) - - it ends with 'ff' or 'ffff', to make the whole packet have an even length. - -FIXME: I absolutely don't know what xxxx is. I suppose it may be some date -information since I found it nowhere else, but I can't be sure. - - yyyy is equal to S1 (see picture). - - -Example of a control header : ----- -0A 0C 01 03 02 31 04 0F F0 05 00 02 CF 00 22 3E 06 00 06 04 E9 FF 00 93 0A 0C 02 FF ----- -Let's decode it. First of all, S1 = 0x0a0c. - -The control sequences are : - 01 - Nothing to say about this one - 03 02 31 - Color 0 is 0, color 1 is 2, color 2 is 3, and color 3 is 1. - 04 0F F0 - Colors 0 and 3 are transparent, and colors 2 and 3 are opaque (not sure of this one) - 05 00 02 CF 00 22 3E - The first column is 0x000, the last one is 0x2cf, the first line is 0x002, and - the last line is 0x23e. Thus the subtitle's size is 0x2d0 x 0x23d. - 06 00 06 04 E9 - The first encoded image starts at offset 0x006, and the second one starts at 0x04e9. - -And the end sequence is : - 00 93 0A 0C 02 FF - Which means... well, not many things now. We can at least verify that S1 (0x0a0c) is - there. - - - - - -4. Decoding the graphics - - The graphics are rather easy to decode (at least, when you know how to do it - it - took us one whole week to figure out what the encoding was :p). - - The picture is interlaced, for instance for a 40 lines picture : - - line 0 ---------------#---------- - line 2 ------#------------------- - ... - line 38 ------------#------------- - line 1 ------------------#------- - line 3 --------#----------------- - ... - line 39 -------------#------------ - - When decoding you should get: - - line 0 ---------------#---------- - line 1 ------------------#------- - line 2 ------#------------------- - line 3 --------#----------------- - ... - line 38 ------------#------------- - line 39 -------------#------------ - - Computers with weak processors could choose only to decode even lines - in order to gain some time, for instance. - - - The encoding is run-length encoded, with the following alphabet: - - 0xf - 0xe - 0xd - 0xc - 0xb - 0xa - 0x9 - 0x8 - 0x7 - 0x6 - 0x5 - 0x4 - 0x3- - 0x2- - 0x1- - 0x0f- - 0x0e- - 0x0d- - 0x0c- - 0x0b- - 0x0a- - 0x09- - 0x08- - 0x07- - 0x06- - 0x05- - 0x04- - 0x03-- - 0x02-- - 0x01-- - 0x0000 - - '-' stands for any other nibble. Once a sequence X of this alphabet has - been read, the pixels can be displayed : (X >> 2) is the number of pixels - to display, and (X & 0x3) is the color of the pixel. - - For instance, 0x23 means "8 pixels of color 3". - - "0000" has a special meaning : it's a carriage return. The decoder should - do a carriage return when reaching the end of the line, or when encountering - this "0000" sequence. When doing a carriage return, the parser should be - reset to the next even position (it cannot be nibble-aligned at the start - of a line). - - After a carriage return, the parser should read a line on the other - interlaced picture, and swap like this after each carriage return. - - Perhaps I don't explain this very well, so you'd better have a look at - the enclosed source. - - - - - -5. What I do not know yet / What I need - -I don't know what's in the end sequence yet. - -Also, I don't know exactly when to display subtitles, and when to remove them. - -I don't know if there are other types of control sequences (in my programs I consider -0xff as a control sequence type, as well as 0x02. I don't know if it's correct or not, -so please comment on this). - -I don't know what the "official" color palette is. - -I don't know how to handle transparency information. - -I don't know if this document is generic enough. - -So what I need is you : - - - if you can, patch this document or my programs to fix strange behaviour with your subtitles. - - - send me your subtitles (there's a program to extract them enclosed) ; the first 10 KB - of subtitles in a VOB should be enough, but it would be cool if you sent me one subtitle - file per language. - - - - - -6. Thanks - - Thanks to Michel Lespinasse for his great help on understanding -the RLE stuff, and for all the ideas he had. - - Thanks to mass (David Waite) and taaz (David I. Lehn) from irc at -openprojects.net for sending me their subtitles. - - - - - -7. Changes - - 20000116: added the 'changes' section. - 20000116: added David Waite's and David I. Lehn's name. - 20000116: changed "x0" and "x1" to "S0" and "S1" to make it less confusing. - - - - --- -Paris, January 16th 2000 -Samuel Hocevar diff --git a/gst/mpeg2sub/gstmpeg2subt.c b/gst/mpeg2sub/gstmpeg2subt.c deleted file mode 100644 index c338853b..00000000 --- a/gst/mpeg2sub/gstmpeg2subt.c +++ /dev/null @@ -1,1049 +0,0 @@ -/* GStreamer - * Copyright (C) <1999> Erik Walthinsen - * - * 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. - */ - - -/*#define DEBUG_ENABLED */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif -#include "gstmpeg2subt.h" -#include - -static void gst_mpeg2subt_class_init (GstMpeg2SubtClass * klass); -static void gst_mpeg2subt_base_init (GstMpeg2SubtClass * klass); -static void gst_mpeg2subt_init (GstMpeg2Subt * mpeg2subt); -static void gst_mpeg2subt_loop (GstElement * element); - -static GstCaps *gst_mpeg2subt_getcaps_video (GstPad * pad); -static GstPadLinkReturn gst_mpeg2subt_link_video (GstPad * pad, - const GstCaps * caps); -static void gst_mpeg2subt_handle_video (GstMpeg2Subt * mpeg2subt, - GstData * _data); -static gboolean gst_mpeg2subt_src_event (GstPad * pad, GstEvent * event); -static void gst_mpeg2subt_handle_subtitle (GstMpeg2Subt * mpeg2subt, - GstData * _data); - -static void gst_mpeg2subt_merge_title (GstMpeg2Subt * mpeg2subt, - GstBuffer * buf); -static void gst_mpeg2subt_handle_dvd_event (GstMpeg2Subt * mpeg2subt, - GstEvent * event, gboolean from_sub_pad); -static void gst_mpeg2subt_finalize (GObject * gobject); -static void gst_mpeg2subt_set_property (GObject * object, guint prop_id, - const GValue * value, GParamSpec * pspec); -static void gst_mpeg2subt_get_property (GObject * object, guint prop_id, - GValue * value, GParamSpec * pspec); -static void gst_setup_palette (GstMpeg2Subt * mpeg2subt, guchar * indexes, - guchar * alpha); -static void gst_update_still_frame (GstMpeg2Subt * mpeg2subt); - -/* elementfactory information */ -static const GstElementDetails mpeg2subt_details = -GST_ELEMENT_DETAILS ("MPEG-2 subtitle decoder", - "Codec/Decoder/Video", - "Decodes and merges MPEG2 subtitles into a video frame", - "Wim Taymans \n" - "Jan Schmidt "); - -static GstStaticPadTemplate video_template = GST_STATIC_PAD_TEMPLATE ("video", - GST_PAD_SINK, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("video/x-raw-yuv, " "format = (fourcc) { I420 }, " /* YV12 later */ - "width = (int) [ 16, 4096 ], " "height = (int) [ 16, 4096 ]") - ); - -static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", - GST_PAD_SRC, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("video/x-raw-yuv, " "format = (fourcc) { I420 }, " /* YV12 later */ - "width = (int) [ 16, 4096 ], " "height = (int) [ 16, 4096 ]") - ); - -static GstStaticPadTemplate subtitle_template = -GST_STATIC_PAD_TEMPLATE ("subtitle", - GST_PAD_SINK, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("video/x-dvd-subpicture") - ); - -GST_DEBUG_CATEGORY_STATIC (mpeg2subt_debug); -#define GST_CAT_DEFAULT (mpeg2subt_debug) - -/* GstMpeg2Subt signals and args */ -enum -{ - /* FILL ME */ - LAST_SIGNAL -}; - -enum -{ - ARG_0, - ARG_SKIP - /* FILL ME */ -}; - -enum -{ - SPU_FORCE_DISPLAY = 0x00, - SPU_SHOW = 0x01, - SPU_HIDE = 0x02, - SPU_SET_PALETTE = 0x03, - SPU_SET_ALPHA = 0x04, - SPU_SET_SIZE = 0x05, - SPU_SET_OFFSETS = 0x06, - SPU_WIPE = 0x07, - SPU_END = 0xff -}; - -static guint32 default_clut[16] = { - 0xb48080, 0x248080, 0x628080, 0xd78080, - 0x808080, 0x808080, 0x808080, 0x808080, - 0x808080, 0x808080, 0x808080, 0x808080, - 0x808080, 0x808080, 0x808080, 0x808080 -}; - -typedef struct RLE_state -{ - gint id; - gint aligned; - gint offset[2]; - gint clip_left; - gint clip_right; - - guchar *target_Y; - guchar *target_U; - guchar *target_V; - guchar *target_A; - - guchar next; -} -RLE_state; - -static GstElementClass *parent_class = NULL; - -/*static guint gst_mpeg2subt_signals[LAST_SIGNAL] = { 0 };*/ - -GType -gst_mpeg2subt_get_type (void) -{ - static GType mpeg2subt_type = 0; - - if (!mpeg2subt_type) { - static const GTypeInfo mpeg2subt_info = { - sizeof (GstMpeg2SubtClass), - (GBaseInitFunc) gst_mpeg2subt_base_init, - NULL, - (GClassInitFunc) gst_mpeg2subt_class_init, - NULL, - NULL, - sizeof (GstMpeg2Subt), - 0, - (GInstanceInitFunc) gst_mpeg2subt_init, - }; - - mpeg2subt_type = - g_type_register_static (GST_TYPE_ELEMENT, "GstMpeg2Subt", - &mpeg2subt_info, 0); - - GST_DEBUG_CATEGORY_INIT (mpeg2subt_debug, "mpeg2subt", 0, - "MPEG2 subtitle overlay element"); - } - - return mpeg2subt_type; -} - -static void -gst_mpeg2subt_base_init (GstMpeg2SubtClass * klass) -{ - GstElementClass *element_class = GST_ELEMENT_CLASS (klass); - - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&src_template)); - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&video_template)); - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&subtitle_template)); - - gst_element_class_set_details (element_class, &mpeg2subt_details); -} - -static void -gst_mpeg2subt_class_init (GstMpeg2SubtClass * klass) -{ - GObjectClass *gobject_class; - GstElementClass *gstelement_class; - - gobject_class = (GObjectClass *) klass; - gstelement_class = (GstElementClass *) klass; - - g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SKIP, g_param_spec_int ("skip", "skip", "skip", G_MININT, G_MAXINT, 0, G_PARAM_READWRITE)); /* CHECKME */ - - parent_class = g_type_class_peek_parent (klass); - - gobject_class->set_property = gst_mpeg2subt_set_property; - gobject_class->get_property = gst_mpeg2subt_get_property; - gobject_class->finalize = gst_mpeg2subt_finalize; -} - -static void -gst_mpeg2subt_init (GstMpeg2Subt * mpeg2subt) -{ - mpeg2subt->videopad = - gst_pad_new_from_static_template (&video_template, "video"); - gst_element_add_pad (GST_ELEMENT (mpeg2subt), mpeg2subt->videopad); - gst_pad_set_link_function (mpeg2subt->videopad, - GST_DEBUG_FUNCPTR (gst_mpeg2subt_link_video)); - gst_pad_set_getcaps_function (mpeg2subt->videopad, - GST_DEBUG_FUNCPTR (gst_mpeg2subt_getcaps_video)); - - mpeg2subt->subtitlepad = - gst_pad_new_from_static_template (&subtitle_template, "subtitle"); - gst_element_add_pad (GST_ELEMENT (mpeg2subt), mpeg2subt->subtitlepad); - - mpeg2subt->srcpad = gst_pad_new_from_static_template (&src_template, "src"); - gst_element_add_pad (GST_ELEMENT (mpeg2subt), mpeg2subt->srcpad); - gst_pad_set_getcaps_function (mpeg2subt->srcpad, - GST_DEBUG_FUNCPTR (gst_mpeg2subt_getcaps_video)); - gst_pad_set_link_function (mpeg2subt->srcpad, - GST_DEBUG_FUNCPTR (gst_mpeg2subt_link_video)); - gst_pad_set_event_function (mpeg2subt->srcpad, - GST_DEBUG_FUNCPTR (gst_mpeg2subt_src_event)); - - gst_element_set_loop_function (GST_ELEMENT (mpeg2subt), gst_mpeg2subt_loop); - GST_OBJECT_FLAG_SET (GST_ELEMENT (mpeg2subt), GST_ELEMENT_EVENT_AWARE); - - mpeg2subt->partialbuf = NULL; - mpeg2subt->hold_frame = NULL; - mpeg2subt->still_frame = NULL; - mpeg2subt->have_title = FALSE; - mpeg2subt->start_display_time = GST_CLOCK_TIME_NONE; - mpeg2subt->end_display_time = GST_CLOCK_TIME_NONE; - mpeg2subt->forced_display = FALSE; - - memcpy (mpeg2subt->current_clut, default_clut, sizeof (guint32) * 16); - gst_setup_palette (mpeg2subt, mpeg2subt->menu_index, mpeg2subt->menu_alpha); - gst_setup_palette (mpeg2subt, mpeg2subt->subtitle_index, - mpeg2subt->subtitle_alpha); - - memset (mpeg2subt->out_buffers, 0, sizeof (mpeg2subt->out_buffers)); - mpeg2subt->pending_video_buffer = NULL; - mpeg2subt->next_video_time = GST_CLOCK_TIME_NONE; - mpeg2subt->pending_subtitle_buffer = NULL; - mpeg2subt->next_subtitle_time = GST_CLOCK_TIME_NONE; -} - -static void -gst_mpeg2subt_finalize (GObject * gobject) -{ - GstMpeg2Subt *mpeg2subt = GST_MPEG2SUBT (gobject); - gint i; - - for (i = 0; i < 3; i++) { - if (mpeg2subt->out_buffers[i]) - g_free (mpeg2subt->out_buffers[i]); - } - - if (mpeg2subt->partialbuf) - gst_buffer_unref (mpeg2subt->partialbuf); -} - -static GstCaps * -gst_mpeg2subt_getcaps_video (GstPad * pad) -{ - GstMpeg2Subt *mpeg2subt = GST_MPEG2SUBT (gst_pad_get_parent (pad)); - GstPad *otherpad; - - otherpad = - (pad == mpeg2subt->srcpad) ? mpeg2subt->videopad : mpeg2subt->srcpad; - - return gst_pad_get_allowed_caps (otherpad); -} - -static GstPadLinkReturn -gst_mpeg2subt_link_video (GstPad * pad, const GstCaps * caps) -{ - GstMpeg2Subt *mpeg2subt = GST_MPEG2SUBT (gst_pad_get_parent (pad)); - GstPad *otherpad; - GstPadLinkReturn ret; - GstStructure *structure; - gint width, height; - gint i; - - otherpad = - (pad == mpeg2subt->srcpad) ? mpeg2subt->videopad : mpeg2subt->srcpad; - - ret = gst_pad_try_set_caps (otherpad, caps); - if (GST_PAD_LINK_FAILED (ret)) { - return ret; - } - - structure = gst_caps_get_structure (caps, 0); - - if (!gst_structure_get_int (structure, "width", &width) || - !gst_structure_get_int (structure, "height", &height)) { - return GST_PAD_LINK_REFUSED; - } - - mpeg2subt->in_width = width; - mpeg2subt->in_height = height; - - /* Allocate compositing buffers */ - for (i = 0; i < 3; i++) { - if (mpeg2subt->out_buffers[i]) - g_free (mpeg2subt->out_buffers[i]); - mpeg2subt->out_buffers[i] = g_malloc (sizeof (guint16) * width); - } - - return GST_PAD_LINK_OK; -} - -static void -gst_mpeg2subt_handle_video (GstMpeg2Subt * mpeg2subt, GstData * _data) -{ - if (GST_IS_BUFFER (_data)) { - GstBuffer *buf = GST_BUFFER (_data); - guchar *data; - glong size; - - data = GST_BUFFER_DATA (buf); - size = GST_BUFFER_SIZE (buf); - - if (mpeg2subt->still_frame) { - gst_buffer_unref (mpeg2subt->still_frame); - mpeg2subt->still_frame = NULL; - } - - if (!mpeg2subt->hold_frame) { - mpeg2subt->hold_frame = buf; - } else { - GstBuffer *out_buf; - - out_buf = mpeg2subt->hold_frame; - mpeg2subt->hold_frame = buf; - - if (mpeg2subt->have_title) { - if ((mpeg2subt->forced_display && (mpeg2subt->current_button != 0)) - || - ((mpeg2subt->start_display_time <= GST_BUFFER_TIMESTAMP (out_buf)) - && (mpeg2subt->end_display_time >= - GST_BUFFER_TIMESTAMP (out_buf)))) { - out_buf = gst_buffer_copy_on_write (out_buf); - gst_mpeg2subt_merge_title (mpeg2subt, out_buf); - } - } - - gst_pad_push (mpeg2subt->srcpad, GST_DATA (out_buf)); - } - } else if (GST_IS_EVENT (_data)) { - switch (GST_EVENT_TYPE (GST_EVENT (_data))) { - case GST_EVENT_ANY: - gst_mpeg2subt_handle_dvd_event (mpeg2subt, GST_EVENT (_data), FALSE); - gst_data_unref (_data); - break; - case GST_EVENT_DISCONTINUOUS: - /* Turn off forced highlight display */ - mpeg2subt->forced_display = 0; - if (mpeg2subt->still_frame) { - gst_buffer_unref (mpeg2subt->still_frame); - mpeg2subt->still_frame = NULL; - } - if (mpeg2subt->hold_frame) { - gst_buffer_unref (mpeg2subt->hold_frame); - mpeg2subt->hold_frame = NULL; - } - gst_pad_push (mpeg2subt->srcpad, _data); - break; - default: - gst_pad_push (mpeg2subt->srcpad, _data); - break; - } - } else - gst_data_unref (_data); -} - -static gboolean -gst_mpeg2subt_src_event (GstPad * pad, GstEvent * event) -{ - GstMpeg2Subt *mpeg2subt = GST_MPEG2SUBT (gst_pad_get_parent (pad)); - - return gst_pad_send_event (GST_PAD_PEER (mpeg2subt->videopad), event); -} - -static void -gst_mpeg2subt_parse_header (GstMpeg2Subt * mpeg2subt) -{ -#define PARSE_BYTES_NEEDED(x) if ((buf+(x)) >= end) \ - { GST_WARNING("Subtitle stream broken parsing %d", *buf); \ - broken = TRUE; break; } - - guchar *buf; - guchar *start = GST_BUFFER_DATA (mpeg2subt->partialbuf); - guchar *end; - gboolean broken = FALSE; - gboolean last_seq = FALSE; - guchar *next_seq = NULL; - guint event_time; - - mpeg2subt->forced_display = FALSE; - g_return_if_fail (mpeg2subt->packet_size >= 4); - - buf = start + mpeg2subt->data_size; - end = buf + mpeg2subt->packet_size; - event_time = GUINT16_FROM_BE (*(guint16 *) (buf)); - next_seq = start + GUINT16_FROM_BE (*(guint16 *) (buf + 2)); - /* If the next control sequence is at the current offset, this is - * the last one */ - last_seq = (next_seq == buf); - buf += 4; - - while ((buf < end) && (!broken)) { - switch (*buf) { - case SPU_FORCE_DISPLAY: /* Forced display menu subtitle */ - mpeg2subt->forced_display = TRUE; - buf++; - break; - case SPU_SHOW: /* Show the subtitle in this packet */ - mpeg2subt->start_display_time = - GST_BUFFER_TIMESTAMP (mpeg2subt->partialbuf) + - ((GST_SECOND * event_time) / 90); - GST_DEBUG ("Subtitle starts at %" G_GUINT64_FORMAT, - mpeg2subt->end_display_time); - buf++; - break; - case SPU_HIDE: /* 02 ff (ff) is the end of the packet, hide the */ - mpeg2subt->end_display_time = - GST_BUFFER_TIMESTAMP (mpeg2subt->partialbuf) + - ((GST_SECOND * event_time) / 90); - GST_DEBUG ("Subtitle ends at %" G_GUINT64_FORMAT, - mpeg2subt->end_display_time); - buf++; - break; - case SPU_SET_PALETTE: /* palette */ - PARSE_BYTES_NEEDED (3); - - mpeg2subt->subtitle_index[3] = buf[1] >> 4; - mpeg2subt->subtitle_index[2] = buf[1] & 0xf; - mpeg2subt->subtitle_index[1] = buf[2] >> 4; - mpeg2subt->subtitle_index[0] = buf[2] & 0xf; - buf += 3; - break; - case SPU_SET_ALPHA: /* transparency palette */ - PARSE_BYTES_NEEDED (3); - - mpeg2subt->subtitle_alpha[3] = buf[1] >> 4; - mpeg2subt->subtitle_alpha[2] = buf[1] & 0xf; - mpeg2subt->subtitle_alpha[1] = buf[2] >> 4; - mpeg2subt->subtitle_alpha[0] = buf[2] & 0xf; - buf += 3; - break; - case SPU_SET_SIZE: /* image coordinates */ - PARSE_BYTES_NEEDED (7); - - mpeg2subt->left = - CLAMP ((((unsigned int) buf[1]) << 4) | (buf[2] >> 4), 0, - (mpeg2subt->in_width - 1)); - mpeg2subt->top = - CLAMP ((((unsigned int) buf[4]) << 4) | (buf[5] >> 4), 0, - (mpeg2subt->in_height - 1)); - mpeg2subt->right = - CLAMP ((((buf[2] & 0x0f) << 8) | buf[3]), 0, - (mpeg2subt->in_width - 1)); - mpeg2subt->bottom = - CLAMP ((((buf[5] & 0x0f) << 8) | buf[6]), 0, - (mpeg2subt->in_height - 1)); - - GST_DEBUG ("left %d, top %d, right %d, bottom %d", mpeg2subt->left, - mpeg2subt->top, mpeg2subt->right, mpeg2subt->bottom); - buf += 7; - break; - case SPU_SET_OFFSETS: /* image 1 / image 2 offsets */ - PARSE_BYTES_NEEDED (5); - mpeg2subt->offset[0] = (((unsigned int) buf[1]) << 8) | buf[2]; - mpeg2subt->offset[1] = (((unsigned int) buf[3]) << 8) | buf[4]; - GST_DEBUG ("Offset1 %d, Offset2 %d", mpeg2subt->offset[0], - mpeg2subt->offset[1]); - buf += 5; - break; - case SPU_WIPE: - { - guint length; - - GST_WARNING ("SPU_WIPE not yet implemented"); - PARSE_BYTES_NEEDED (3); - - length = (buf[1] << 8) | (buf[2]); - buf += 1 + length; - } - break; - case SPU_END: - buf = (last_seq) ? end : next_seq; - - /* Start a new control sequence */ - if (buf + 4 < end) { - event_time = GUINT16_FROM_BE (*(guint16 *) (buf)); - next_seq = start + GUINT16_FROM_BE (*(guint16 *) (buf + 2)); - last_seq = (next_seq == buf); - } - buf += 4; - break; - default: - GST_ERROR - ("Invalid sequence in subtitle packet header (%.2x). Skipping", - *buf); - broken = TRUE; - break; - } - } - - if (!mpeg2subt->forced_display) - gst_setup_palette (mpeg2subt, mpeg2subt->subtitle_index, - mpeg2subt->subtitle_alpha); -} - -inline int -gst_get_nibble (guchar * buffer, RLE_state * state) -{ - if (state->aligned) { - state->next = buffer[state->offset[state->id]++]; - state->aligned = 0; - return state->next >> 4; - } else { - state->aligned = 1; - return state->next & 0xf; - } -} - -/* Premultiply the current lookup table into the palette_cache */ -static void -gst_setup_palette (GstMpeg2Subt * mpeg2subt, guchar * indexes, guchar * alpha) -{ - gint i; - YUVA_val *target = mpeg2subt->palette_cache; - - for (i = 0; i < 4; i++, target++) { - guint32 col = mpeg2subt->current_clut[indexes[i]]; - - target->Y = (guint16) ((col >> 16) & 0xff) * alpha[i]; - target->U = (guint16) ((col >> 8) & 0xff) * alpha[i]; - target->V = (guint16) (col & 0xff) * alpha[i]; - target->A = alpha[i]; - } -} - -inline guint -gst_get_rle_code (guchar * buffer, RLE_state * state) -{ - gint code; - - code = gst_get_nibble (buffer, state); - if (code < 0x4) { /* 4 .. f */ - code = (code << 4) | gst_get_nibble (buffer, state); - if (code < 0x10) { /* 1x .. 3x */ - code = (code << 4) | gst_get_nibble (buffer, state); - if (code < 0x40) { /* 04x .. 0fx */ - code = (code << 4) | gst_get_nibble (buffer, state); - } - } - } - return code; -} - -/* - * This function steps over each run-length segment, drawing - * into the YUVA buffers as it goes. UV are composited and then output - * at half width/height - */ -static void -gst_draw_rle_line (GstMpeg2Subt * mpeg2subt, guchar * buffer, RLE_state * state) -{ - gint length, colourid; - gint right = mpeg2subt->right + 1; - YUVA_val *colour_entry; - guint code; - gint x; - gboolean in_clip = FALSE; - guchar *target_Y; - guint16 *target_U; - guint16 *target_V; - guint16 *target_A; - - target_Y = state->target_Y; - target_U = mpeg2subt->out_buffers[0]; - target_V = mpeg2subt->out_buffers[1]; - target_A = mpeg2subt->out_buffers[2]; - x = mpeg2subt->left; - while (x < right) { - code = gst_get_rle_code (buffer, state); - length = code >> 2; - colourid = code & 3; - colour_entry = mpeg2subt->palette_cache + colourid; - - /* Length = 0 implies fill to the end of the line */ - if (length == 0) - length = right - x; - else { - /* Restrict the colour run to the end of the line */ - length = length < (right - x) ? length : (right - x); - } - - /* Check if this run of colour crosses into the clip region */ - in_clip = (((x + length) >= state->clip_left) && (x <= state->clip_right)); - - /* Draw YA onto the frame via target_Y, UVA into the composite buffers */ - if ((in_clip) && (colour_entry->A)) { - guint16 inv_alpha = 0xf - colour_entry->A; - gint i; - - for (i = 0; i < length; i++) { - *target_Y = ((inv_alpha * (*target_Y)) + colour_entry->Y) / 0xf; - *target_U += colour_entry->U; - *target_V += colour_entry->V; - *target_A += colour_entry->A; - target_Y++; - target_U++; - target_V++; - target_A++; - } - } else { - target_Y += length; - target_U += length; - target_V += length; - target_A += length; - } - x += length; - } -} - -inline void -gst_merge_uv_data (GstMpeg2Subt * mpeg2subt, guchar * buffer, RLE_state * state) -{ - gint x; - guchar *target_V; - guchar *target_U; - gint width = mpeg2subt->right - mpeg2subt->left + 1; - - guint16 *comp_U; - guint16 *comp_V; - guint16 *comp_A; - - /* The compositing buffers should contain the results of accumulating 2 scanlines of - * U, V (premultiplied) and A data. Merge them back into their output buffers at - * half width/height. - */ - target_U = state->target_U; - target_V = state->target_V; - comp_U = mpeg2subt->out_buffers[0]; - comp_V = mpeg2subt->out_buffers[1]; - comp_A = mpeg2subt->out_buffers[2]; - - for (x = 0; x < width; x += 2) { - guint16 temp1, temp2; - - /* Average out the alpha accumulated to compute transparency */ - guint16 alpha = (comp_A[0] + comp_A[1]); - - if (alpha > 0) { - temp1 = (*target_U) * ((4 * 0xf) - alpha) + comp_U[0] + comp_U[1]; - temp2 = (*target_V) * ((4 * 0xf) - alpha) + comp_V[0] + comp_V[1]; - *target_U = temp1 / (4 * 0xf); - *target_V = temp2 / (4 * 0xf); - }; - comp_U += 2; - comp_V += 2; - comp_A += 2; - target_U++; - target_V++; - } -} - -/* - * Decode the RLE subtitle image and blend with the current - * frame buffer. - */ -static void -gst_mpeg2subt_merge_title (GstMpeg2Subt * mpeg2subt, GstBuffer * buf) -{ - gint y; - gint width = mpeg2subt->right - mpeg2subt->left + 1; - gint Y_stride; - gint UV_stride; - - guchar *buffer = GST_BUFFER_DATA (mpeg2subt->partialbuf); - gint last_y; - gint first_y; - RLE_state state; - - /* Set up the initial offsets, remembering the half-res size for UV in I420 packing - * see http://www.fourcc.org for details - */ - Y_stride = mpeg2subt->in_width; - UV_stride = (mpeg2subt->in_width + 1) / 2; - - GST_DEBUG ("Merging subtitle on frame at time %" G_GUINT64_FORMAT - " using %s colour table", GST_BUFFER_TIMESTAMP (buf), - mpeg2subt->forced_display ? "menu" : "subtitle"); - - state.id = 0; - state.aligned = 1; - state.offset[0] = mpeg2subt->offset[0]; - state.offset[1] = mpeg2subt->offset[1]; - - /* skip over lines until we hit the clip region */ - if (mpeg2subt->forced_display) { - state.clip_right = mpeg2subt->clip_right; - state.clip_left = mpeg2subt->clip_left; - last_y = mpeg2subt->clip_bottom; - first_y = mpeg2subt->clip_top; - } else { - state.clip_right = mpeg2subt->right; - state.clip_left = mpeg2subt->left; - last_y = mpeg2subt->bottom; - first_y = mpeg2subt->top; - } - - for (y = mpeg2subt->top; y < first_y; y++) { - /* Skip a line of RLE data */ - gint length; - guint code; - gint x = 0; - - while (x < width) { - code = gst_get_rle_code (buffer, &state); - length = code >> 2; - - /* Length = 0 implies fill to the end of the line so we're done */ - if (length == 0) - break; - - x += length; - } - if (!state.aligned) - gst_get_nibble (buffer, &state); - state.id = !state.id; - } - - state.target_Y = GST_BUFFER_DATA (buf) + mpeg2subt->left + (y * Y_stride); - state.target_V = GST_BUFFER_DATA (buf) + (Y_stride * mpeg2subt->in_height) - + ((mpeg2subt->left) / 2) + ((y / 2) * UV_stride); - state.target_U = - state.target_V + UV_stride * ((mpeg2subt->in_height + 1) / 2); - - memset (mpeg2subt->out_buffers[0], 0, sizeof (guint16) * Y_stride); - memset (mpeg2subt->out_buffers[1], 0, sizeof (guint16) * Y_stride); - memset (mpeg2subt->out_buffers[2], 0, sizeof (guint16) * Y_stride); - - /* Now draw scanlines until we hit last_y or end of RLE data */ - for (; ((state.offset[1] < mpeg2subt->data_size + 2) && (y <= last_y)); y++) { - gst_draw_rle_line (mpeg2subt, buffer, &state); - if (state.id) { - gst_merge_uv_data (mpeg2subt, buffer, &state); - - /* Clear the compositing buffers */ - memset (mpeg2subt->out_buffers[0], 0, sizeof (guint16) * Y_stride); - memset (mpeg2subt->out_buffers[1], 0, sizeof (guint16) * Y_stride); - memset (mpeg2subt->out_buffers[2], 0, sizeof (guint16) * Y_stride); - - state.target_U += UV_stride; - state.target_V += UV_stride; - } - state.target_Y += Y_stride; - - /* Realign the RLE state for the next line */ - if (!state.aligned) - gst_get_nibble (buffer, &state); - state.id = !state.id; - } -} - -static void -gst_update_still_frame (GstMpeg2Subt * mpeg2subt) -{ - GstBuffer *out_buf; - - if ((mpeg2subt->still_frame) && - (mpeg2subt->have_title) && - ((mpeg2subt->forced_display && (mpeg2subt->current_button != 0)))) { - gst_buffer_ref (mpeg2subt->still_frame); - out_buf = gst_buffer_copy_on_write (mpeg2subt->still_frame); - gst_mpeg2subt_merge_title (mpeg2subt, out_buf); - gst_pad_push (mpeg2subt->srcpad, GST_DATA (out_buf)); - } -} - -static void -gst_mpeg2subt_handle_subtitle (GstMpeg2Subt * mpeg2subt, GstData * _data) -{ - g_return_if_fail (_data != NULL); - - if (GST_IS_BUFFER (_data)) { - GstBuffer *buf = GST_BUFFER (_data); - guchar *data; - glong size = 0; - - if (mpeg2subt->have_title) { - gst_buffer_unref (mpeg2subt->partialbuf); - mpeg2subt->partialbuf = NULL; - mpeg2subt->have_title = FALSE; - } - - GST_DEBUG ("Got subtitle buffer, pts %" G_GUINT64_FORMAT, - GST_BUFFER_TIMESTAMP (buf)); - - /* deal with partial frame from previous buffer */ - if (mpeg2subt->partialbuf) { - GstBuffer *merge; - - merge = gst_buffer_merge (mpeg2subt->partialbuf, buf); - gst_buffer_unref (mpeg2subt->partialbuf); - gst_buffer_unref (buf); - mpeg2subt->partialbuf = merge; - } else { - mpeg2subt->partialbuf = buf; - } - - data = GST_BUFFER_DATA (mpeg2subt->partialbuf); - size = GST_BUFFER_SIZE (mpeg2subt->partialbuf); - - if (size > 4) { - mpeg2subt->packet_size = GST_READ_UINT16_BE (data); - - if (mpeg2subt->packet_size == size) { - GST_LOG ("Subtitle packet size %d, current size %ld", - mpeg2subt->packet_size, size); - - mpeg2subt->data_size = GST_READ_UINT16_BE (data + 2); - mpeg2subt->have_title = TRUE; - - gst_mpeg2subt_parse_header (mpeg2subt); - } - } - } else if (GST_IS_EVENT (_data)) { - switch (GST_EVENT_TYPE (GST_EVENT (_data))) { - case GST_EVENT_ANY: - GST_LOG ("DVD event on subtitle pad with timestamp %llu", - GST_EVENT_TIMESTAMP (GST_EVENT (_data))); - gst_mpeg2subt_handle_dvd_event (mpeg2subt, GST_EVENT (_data), TRUE); - break; - case GST_EVENT_EMPTY: - if (GST_CLOCK_TIME_IS_VALID (mpeg2subt->next_video_time) && - (mpeg2subt->next_video_time > 0)) { - mpeg2subt->next_subtitle_time = mpeg2subt->next_video_time + 1; - GST_LOG ("Forwarding subtitle time to %llu", - mpeg2subt->next_subtitle_time); - } - gst_update_still_frame (mpeg2subt); - break; - default: - GST_LOG ("Got event of type %d on subtitle pad", - GST_EVENT_TYPE (GST_EVENT (_data))); - break; - } - gst_data_unref (_data); - } else - gst_data_unref (_data); -} - -static void -gst_mpeg2subt_handle_dvd_event (GstMpeg2Subt * mpeg2subt, GstEvent * event, - gboolean from_sub_pad) -{ - GstStructure *structure; - const gchar *event_type; - - structure = event->event_data.structure.structure; - - event_type = gst_structure_get_string (structure, "event"); - g_return_if_fail (event_type != NULL); - - if (from_sub_pad && !strcmp (event_type, "dvd-spu-highlight")) { - gint button; - gint palette, sx, sy, ex, ey; - gint i; - - /* Details for the highlight region to display */ - if (!gst_structure_get_int (structure, "button", &button) || - !gst_structure_get_int (structure, "palette", &palette) || - !gst_structure_get_int (structure, "sx", &sx) || - !gst_structure_get_int (structure, "sy", &sy) || - !gst_structure_get_int (structure, "ex", &ex) || - !gst_structure_get_int (structure, "ey", &ey)) { - GST_ERROR ("Invalid dvd-spu-highlight event received"); - return; - } - mpeg2subt->current_button = button; - mpeg2subt->clip_left = sx; - mpeg2subt->clip_top = sy; - mpeg2subt->clip_right = ex; - mpeg2subt->clip_bottom = ey; - for (i = 0; i < 4; i++) { - mpeg2subt->menu_alpha[i] = ((guint32) (palette) >> (i * 4)) & 0x0f; - mpeg2subt->menu_index[i] = ((guint32) (palette) >> (16 + (i * 4))) & 0x0f; - } - - GST_DEBUG ("New button activated clip=(%d,%d) to (%d,%d) palette 0x%x", sx, - sy, ex, ey, palette); - gst_setup_palette (mpeg2subt, mpeg2subt->menu_index, mpeg2subt->menu_alpha); - - gst_update_still_frame (mpeg2subt); - } else if (from_sub_pad && !strcmp (event_type, "dvd-spu-clut-change")) { - /* Take a copy of the colour table */ - guchar name[16]; - int i; - gint value; - - GST_LOG ("New colour table recieved"); - for (i = 0; i < 16; i++) { - sprintf (name, "clut%02d", i); - if (!gst_structure_get_int (structure, name, &value)) { - GST_ERROR ("dvd-spu-clut-change event did not contain %s field", name); - break; - } - mpeg2subt->current_clut[i] = (guint32) (value); - } - - if (mpeg2subt->forced_display) - gst_setup_palette (mpeg2subt, mpeg2subt->menu_index, - mpeg2subt->menu_alpha); - else - gst_setup_palette (mpeg2subt, mpeg2subt->subtitle_index, - mpeg2subt->subtitle_alpha); - - gst_update_still_frame (mpeg2subt); - } else if ((from_sub_pad && !strcmp (event_type, "dvd-spu-stream-change")) - || (from_sub_pad && !strcmp (event_type, "dvd-spu-reset-highlight"))) { - /* Turn off forced highlight display */ - mpeg2subt->current_button = 0; - mpeg2subt->clip_left = mpeg2subt->left; - mpeg2subt->clip_top = mpeg2subt->top; - mpeg2subt->clip_right = mpeg2subt->right; - mpeg2subt->clip_bottom = mpeg2subt->bottom; - GST_LOG ("Clearing button state"); - gst_update_still_frame (mpeg2subt); - } else if (!from_sub_pad && !strcmp (event_type, "dvd-spu-still-frame")) { - /* Handle a still frame */ - GST_LOG ("Received still frame notification"); - if (mpeg2subt->still_frame) - gst_buffer_unref (mpeg2subt->still_frame); - mpeg2subt->still_frame = mpeg2subt->hold_frame; - mpeg2subt->hold_frame = NULL; - gst_update_still_frame (mpeg2subt); - } else { - /* Ignore all other unknown events */ - GST_LOG ("Ignoring DVD event %s from %s pad", event_type, - from_sub_pad ? "sub" : "video"); - } -} - -static void -gst_mpeg2subt_loop (GstElement * element) -{ - GstMpeg2Subt *mpeg2subt = GST_MPEG2SUBT (element); - GstData *data; - GstClockTime timestamp = 0; - - /* Process any pending video buffer */ - if (mpeg2subt->pending_video_buffer) { - gst_mpeg2subt_handle_video (mpeg2subt, mpeg2subt->pending_video_buffer); - mpeg2subt->pending_video_buffer = NULL; - } - data = mpeg2subt->pending_video_buffer = gst_pad_pull (mpeg2subt->videopad); - if (!data) - return; - - if (GST_IS_BUFFER (data)) { - timestamp = GST_BUFFER_TIMESTAMP (GST_BUFFER (data)); - } else if (GST_IS_EVENT (data)) { - timestamp = GST_EVENT_TIMESTAMP (GST_EVENT (data)); - } else { - GST_WARNING ("Got GstData of unknown type %d", GST_DATA_TYPE (data)); - } - if (timestamp && GST_CLOCK_TIME_IS_VALID (timestamp) && (timestamp > 0)) { - mpeg2subt->next_video_time = timestamp; - GST_LOG ("next_video_time = %llu, next_subtitle_time = %llu", - mpeg2subt->next_video_time, mpeg2subt->next_subtitle_time); - } - - /* Process subtitle buffers until we get one beyond 'next_video_time' */ - if (mpeg2subt->pending_subtitle_buffer) { - gst_mpeg2subt_handle_subtitle (mpeg2subt, - mpeg2subt->pending_subtitle_buffer); - mpeg2subt->pending_subtitle_buffer = NULL; - } - data = mpeg2subt->pending_subtitle_buffer = - gst_pad_pull (mpeg2subt->subtitlepad); - if (!data) { - return; - } - - if (GST_IS_BUFFER (data)) { - timestamp = GST_BUFFER_TIMESTAMP (GST_BUFFER (data)); - } else if (GST_IS_EVENT (data)) { - timestamp = GST_EVENT_TIMESTAMP (GST_EVENT (data)); - } else { - GST_WARNING ("Got GstData of unknown type %d", GST_DATA_TYPE (data)); - } - if (GST_CLOCK_TIME_IS_VALID (timestamp) && (timestamp > 0)) { - mpeg2subt->next_subtitle_time = timestamp; - GST_LOG ("next_subtitle_time = %llu, next_video_time = %llu", - mpeg2subt->next_subtitle_time, mpeg2subt->next_video_time); - } -} - -static void -gst_mpeg2subt_set_property (GObject * object, guint prop_id, - const GValue * value, GParamSpec * pspec) -{ - GstMpeg2Subt *src; - - g_return_if_fail (GST_IS_MPEG2SUBT (object)); - src = GST_MPEG2SUBT (object); - - switch (prop_id) { - default: - break; - } -} - -static void -gst_mpeg2subt_get_property (GObject * object, guint prop_id, GValue * value, - GParamSpec * pspec) -{ - GstMpeg2Subt *src; - - g_return_if_fail (GST_IS_MPEG2SUBT (object)); - src = GST_MPEG2SUBT (object); - - switch (prop_id) { - default: - break; - } -} - -static gboolean -plugin_init (GstPlugin * plugin) -{ - return gst_element_register (plugin, "mpeg2subt", - GST_RANK_NONE, GST_TYPE_MPEG2SUBT); -} - -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, - GST_VERSION_MINOR, - "mpeg2sub", - "MPEG-2 video subtitle parser", - plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/gst/mpeg2sub/gstmpeg2subt.h b/gst/mpeg2sub/gstmpeg2subt.h deleted file mode 100644 index a257f55d..00000000 --- a/gst/mpeg2sub/gstmpeg2subt.h +++ /dev/null @@ -1,114 +0,0 @@ -/* GStreamer - * Copyright (C) <1999> Erik Walthinsen - * - * 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 __GST_MPEG2SUBT_H__ -#define __GST_MPEG2SUBT_H__ - - -#include - - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - - -#define GST_TYPE_MPEG2SUBT \ - (gst_mpeg2subt_get_type()) -#define GST_MPEG2SUBT(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MPEG2SUBT,GstMpeg2Subt)) -#define GST_MPEG2SUBT_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MPEG2SUBT,GstMpeg2SubtClass)) -#define GST_IS_MPEG2SUBT(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MPEG2SUBT)) -#define GST_IS_MPEG2SUBT_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MPEG2SUBT)) - -typedef struct _GstMpeg2Subt GstMpeg2Subt; -typedef struct _GstMpeg2SubtClass GstMpeg2SubtClass; - -/* Hold premultimplied colour values */ -typedef struct YUVA_val { - guint16 Y; - guint16 U; - guint16 V; - guint16 A; -} YUVA_val; - -struct _GstMpeg2Subt { - GstElement element; - - GstPad *videopad,*subtitlepad,*srcpad; - - GstBuffer *partialbuf; /* Collect together subtitle buffers until we have a full control sequence */ - GstBuffer *hold_frame; /* Hold back one frame of video */ - GstBuffer *still_frame; - - guint16 packet_size; - guint16 data_size; - - gint offset[2]; - - YUVA_val palette_cache[4]; - - /* - * Store 1 line width of U, V and A respectively. - * Y is composited direct onto the frame. - */ - guint16 *out_buffers[3]; - guchar subtitle_index[4]; - guchar menu_index[4]; - guchar subtitle_alpha[4]; - guchar menu_alpha[4]; - - guint32 current_clut[16]; - - gboolean have_title; - gboolean forced_display; - - GstClockTime start_display_time; - GstClockTime end_display_time; - gint left, top, - right, bottom; - gint clip_left, clip_top, - clip_right, clip_bottom; - - gint in_width, in_height; - gint current_button; - - GstData *pending_video_buffer; - GstClockTime next_video_time; - GstData *pending_subtitle_buffer; - GstClockTime next_subtitle_time; -}; - -struct _GstMpeg2SubtClass { - GstElementClass parent_class; -}; - -GType gst_mpeg2subt_get_type(void); - - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - - -#endif /* __GST_MPEG2SUBT_H__ */ diff --git a/gst/mpeg2sub/mpeg2subt.vcproj b/gst/mpeg2sub/mpeg2subt.vcproj deleted file mode 100644 index a356f88b..00000000 --- a/gst/mpeg2sub/mpeg2subt.vcproj +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- cgit v1.2.1