Commit 2cb7e016 authored by Gildas Bazin's avatar Gildas Bazin

* modules/codec/cvdsub.c: ported cvd subtitles decoder to the new subpicture architecture.

* modules/codec/svcdsub.c: ported svcd subtitles decoder to the new subpicture architecture.
parent 805a60e6
......@@ -2464,8 +2464,6 @@ AC_CHECK_HEADERS(png.h, [
AC_CHECK_LIB(png, png_set_rows, [
VLC_ADD_LDFLAGS([png],[-lpng -lz])
VLC_ADD_PLUGINS([png])
VLC_ADD_LDFLAGS([svcdsub],[-lpng -lz])
VLC_ADD_LDFLAGS([cvdsub],[-lpng -lz -lm])
AC_DEFINE(HAVE_LIBPNG, [], [Define if you have the PNG library: libpng])],
[],[-lz])
LDFLAGS="${LDFLAGS_save}"
......@@ -4169,7 +4167,6 @@ AC_CONFIG_FILES([
modules/codec/cmml/Makefile
modules/codec/dmo/Makefile
modules/codec/ffmpeg/Makefile
modules/codec/ogt/Makefile
modules/codec/spudec/Makefile
modules/control/Makefile
modules/control/corba/Makefile
......
......@@ -23,3 +23,5 @@ SOURCES_x264 = x264.c
SOURCES_toolame = toolame.c
SOURCES_dirac = dirac.c
SOURCES_png = png.c
SOURCES_svcdsub = svcdsub.c
SOURCES_cvdsub = cvdsub.c
/*****************************************************************************
* cvd.c : CVD Subtitle decoder
*****************************************************************************
* Copyright (C) 2003, 2004 VideoLAN
* $Id$
*
* Authors: Rocky Bernstein
* Gildas Bazin <gbazin@videolan.org>
* Julio Sanchez Fernandez (http://subhandler.sourceforge.net)
* Laurent Aimar <fenrir@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/vout.h>
#include <vlc/decoder.h>
#include "vlc_bits.h"
#define DEBUG_CVDSUB 1
/*****************************************************************************
* Module descriptor.
*****************************************************************************/
static int DecoderOpen ( vlc_object_t * );
static int PacketizerOpen( vlc_object_t * );
static void DecoderClose ( vlc_object_t * );
vlc_module_begin();
set_description( _("CVD subtitle decoder") );
set_capability( "decoder", 50 );
set_callbacks( DecoderOpen, DecoderClose );
add_submodule();
set_description( _("Chaoji VCD subtitle packetizer") );
set_capability( "packetizer", 50 );
set_callbacks( PacketizerOpen, DecoderClose );
vlc_module_end();
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static subpicture_t *Decode( decoder_t *, block_t ** );
static block_t *Packetize ( decoder_t *, block_t ** );
static block_t *Reassemble ( decoder_t *, block_t * );
static void ParseMetaInfo ( decoder_t *, block_t * );
static void ParseHeader ( decoder_t *, block_t * );
static subpicture_t *DecodePacket( decoder_t *, block_t * );
static void RenderImage( decoder_t *, block_t *, subpicture_region_t * );
#define SUBTITLE_BLOCK_EMPTY 0
#define SUBTITLE_BLOCK_PARTIAL 1
#define SUBTITLE_BLOCK_COMPLETE 2
struct decoder_sys_t
{
int b_packetizer;
int i_state; /* data-gathering state for this subtitle */
block_t *p_spu; /* Bytes of the packet. */
int i_spu_size; /* goal for subtitle_data_pos while gathering,
size of used subtitle_data later */
uint16_t i_image_offset; /* offset from subtitle_data to compressed
image data */
int i_image_length; /* size of the compressed image data */
int first_field_offset; /* offset of even raster lines */
int second_field_offset; /* offset of odd raster lines */
int metadata_offset; /* offset to data describing the image */
int metadata_length; /* length of metadata */
mtime_t i_duration; /* how long to display the image, 0 stands
for "until next subtitle" */
uint16_t i_x_start, i_y_start; /* position of top leftmost pixel of
image when displayed */
uint16_t i_width, i_height; /* dimensions in pixels of image */
uint8_t p_palette[4][4]; /* Palette of colors used in subtitle */
uint8_t p_palette_highlight[4][4];
};
/*****************************************************************************
* DecoderOpen: open/initialize the cvdsub decoder.
*****************************************************************************/
static int DecoderOpen( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
decoder_sys_t *p_sys;
if( p_dec->fmt_in.i_codec != VLC_FOURCC( 'c','v','d',' ' ) )
{
return VLC_EGENERIC;
}
p_dec->p_sys = p_sys = malloc( sizeof( decoder_sys_t ) );
p_sys->b_packetizer = VLC_FALSE;
p_sys->i_state = SUBTITLE_BLOCK_EMPTY;
p_sys->p_spu = NULL;
es_format_Init( &p_dec->fmt_out, SPU_ES, VLC_FOURCC( 'c','v','d',' ' ) );
p_dec->pf_decode_sub = Decode;
p_dec->pf_packetize = Packetize;
return VLC_SUCCESS;
}
/*****************************************************************************
* PacketizerOpen: open/initialize the cvdsub packetizer.
*****************************************************************************/
static int PacketizerOpen( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
if( DecoderOpen( p_this ) != VLC_SUCCESS ) return VLC_EGENERIC;
p_dec->p_sys->b_packetizer = VLC_TRUE;
return VLC_SUCCESS;
}
/*****************************************************************************
* DecoderClose: closes the cvdsub decoder/packetizer.
*****************************************************************************/
void DecoderClose( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
decoder_sys_t *p_sys = p_dec->p_sys;
if( p_sys->p_spu ) block_ChainRelease( p_sys->p_spu );
free( p_sys );
}
/*****************************************************************************
* Decode:
*****************************************************************************/
static subpicture_t *Decode( decoder_t *p_dec, block_t **pp_block )
{
block_t *p_block, *p_spu;
if( pp_block == NULL || *pp_block == NULL ) return NULL;
p_block = *pp_block;
*pp_block = NULL;
if( !(p_spu = Reassemble( p_dec, p_block )) ) return NULL;
/* Parse and decode */
return DecodePacket( p_dec, p_spu );
}
/*****************************************************************************
* Packetize:
*****************************************************************************/
static block_t *Packetize( decoder_t *p_dec, block_t **pp_block )
{
block_t *p_block, *p_spu;
if( pp_block == NULL || *pp_block == NULL ) return NULL;
p_block = *pp_block;
*pp_block = NULL;
if( !(p_spu = Reassemble( p_dec, p_block )) ) return NULL;
p_spu->i_dts = p_spu->i_pts;
p_spu->i_length = 0;
return p_spu;
}
/*****************************************************************************
Reassemble:
Data for single screen subtitle may come in several non-contiguous
packets of a stream. This routine is called when the next packet in
the stream comes in. The job of this routine is to parse the header,
if this is the beginning, and combine the packets into one complete
subtitle unit.
If everything is complete, we will return a block. Otherwise return
NULL.
*****************************************************************************/
#define SPU_HEADER_LEN 1
static block_t *Reassemble( decoder_t *p_dec, block_t *p_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t *p_buffer;
if( p_block->i_buffer < SPU_HEADER_LEN )
{
msg_Dbg( p_dec, "invalid packet header (size %d < %d)" ,
p_block->i_buffer, SPU_HEADER_LEN );
block_Release( p_block );
return NULL;
}
p_buffer = p_block->p_buffer;
/* From the scant data on the format, there is only only way known
* to detect the first packet in a subtitle. The first packet
* seems to have a valid PTS while later packets for the same
* image don't. */
if( p_sys->i_state == SUBTITLE_BLOCK_EMPTY && p_block->i_pts == 0 )
{
msg_Warn( p_dec, "first packet expected but no PTS present");
return NULL;
}
p_block->p_buffer += SPU_HEADER_LEN;
p_block->i_buffer -= SPU_HEADER_LEN;
/* First packet in the subtitle block */
if( p_sys->i_state == SUBTITLE_BLOCK_EMPTY ) ParseHeader( p_dec, p_block );
block_ChainAppend( &p_sys->p_spu, p_block );
p_sys->p_spu = block_ChainGather( p_sys->p_spu );
if( p_sys->p_spu->i_buffer >= p_sys->i_spu_size )
{
block_t *p_spu = p_sys->p_spu;
if( p_spu->i_buffer != p_sys->i_spu_size )
{
msg_Warn( p_dec, "SPU packets size=%d should be %d",
p_spu->i_buffer, p_sys->i_spu_size );
}
msg_Dbg( p_dec, "subtitle packet complete, size=%d", p_spu->i_buffer);
ParseMetaInfo( p_dec, p_spu );
p_sys->i_state = SUBTITLE_BLOCK_EMPTY;
p_sys->p_spu = 0;
return p_spu;
}
else
{
/* Not last block in subtitle, so wait for another. */
p_sys->i_state = SUBTITLE_BLOCK_PARTIAL;
}
return NULL;
}
/*
We do not have information on the subtitle format used on CVD's
except the submux sample code and a couple of samples of dubious
origin. Thus, this is the result of reading some code whose
correctness is not known and some experimentation.
CVD subtitles are different in several ways from SVCD OGT subtitles.
Image comes first and metadata is at the end. So that the metadata
can be found easily, the subtitle packet starts with two bytes
(everything is big-endian again) that give the total size of the
subtitle data and the offset to the metadata - i.e. size of the
image data plus the four bytes at the beginning.
Image data comes interlaced is run-length encoded. Each field is a
four-bit nibble. Each nibble contains a two-bit repeat count and a
two-bit color number so that up to three pixels can be described in
four bits. The function of a 0 repeat count is unknown; it might be
used for RLE extension. However when the full nibble is zero, the
rest of the line is filled with the color value in the next nibble.
It is unknown what happens if the color value is greater than three.
The rest seems to use a 4-entries palette. It is not impossible
that the fill-line complete case above is not as described and the
zero repeat count means fill line. The sample code never produces
this, so it may be untested.
*/
static void ParseHeader( decoder_t *p_dec, block_t *p_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t *p = p_block->p_buffer;
p_sys->i_spu_size = (p[0] << 8) + p[1] + 4; p += 2;
/* FIXME: check data sanity */
p_sys->metadata_offset = (p[0] << 8) + p[1]; p +=2;
p_sys->metadata_length = p_sys->i_spu_size - p_sys->metadata_offset;
p_sys->i_image_offset = 4;
p_sys->i_image_length = p_sys->metadata_offset - p_sys->i_image_offset;
#ifdef DEBUG_CVDSUB
msg_Dbg( p_dec, "total size: %d image size: %d",
p_sys->i_spu_size, p_sys->i_image_length );
#endif
}
/*
We parse the metadata information here.
Although metadata information does not have to come in a fixed field
order, every metadata field consists of a tag byte followed by
parameters. In all cases known, the size including tag byte is
exactly four bytes in length.
*/
#define ExtractXY(x, y) x = ((p[1]&0x0f)<<6) + (p[2]>>2); \
y = ((p[2]&0x03)<<8) + p[3];
static void ParseMetaInfo( decoder_t *p_dec, block_t *p_spu )
{
/* Last packet in subtitle block. */
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t *p = p_spu->p_buffer + p_sys->metadata_offset;
uint8_t *p_end = p + p_sys->metadata_length;
for( ; p < p_end; p += 4 )
{
switch( p[0] )
{
case 0x04: /* subtitle duration in 1/90000ths of a second */
p_sys->i_duration = (p[1]<<16) + (p[2]<<8) + p[3];
#ifdef DEBUG_CVDSUB
msg_Dbg( p_dec, "subtitle display duration %lu secs",
(long unsigned int)(p_sys->i_duration / 90000) );
#endif
p_sys->i_duration *= 100 / 9;
break;
case 0x0c: /* unknown */
#ifdef DEBUG_CVDSUB
msg_Dbg( p_dec, "subtitle command unknown 0x%0x 0x%0x 0x%0x 0x%0x",
(int)p[0], (int)p[1], (int)p[2], (int)p[3] );
#endif
break;
case 0x17: /* coordinates of subtitle upper left x, y position */
ExtractXY(p_sys->i_x_start, p_sys->i_y_start);
#ifdef DEBUG_CVDSUB
msg_Dbg( p_dec, "start position (%d,%d)",
p_sys->i_x_start, p_sys->i_y_start );
#endif
break;
case 0x1f: /* coordinates of subtitle bottom right x, y position */
{
int lastx;
int lasty;
ExtractXY(lastx, lasty);
p_sys->i_width = lastx - p_sys->i_x_start + 1;
p_sys->i_height = lasty - p_sys->i_y_start + 1;
#ifdef DEBUG_CVDSUB
msg_Dbg( p_dec, "end position (%d,%d), w x h: %dx%d",
lastx, lasty, p_sys->i_width, p_sys->i_height );
#endif
break;
}
case 0x24:
case 0x25:
case 0x26:
case 0x27:
{
uint8_t v = p[0] - 0x24;
#ifdef DEBUG_CVDSUB
/* Primary Palette */
msg_Dbg( p_dec, "primary palette %d (y,u,v): (0x%0x,0x%0x,0x%0x)",
(int)v, (int)p[1], (int)p[2], (int)p[3] );
#endif
p_sys->p_palette[v][0] = p[1];
p_sys->p_palette[v][1] = p[2];
p_sys->p_palette[v][2] = p[3];
break;
}
case 0x2c:
case 0x2d:
case 0x2e:
case 0x2f:
{
uint8_t v = p[0] - 0x2c;
#ifdef DEBUG_CVDSUB
msg_Dbg( p_dec,"highlight palette %d (y,u,v): (0x%0x,0x%0x,0x%0x)",
(int)v, (int)p[1], (int)p[2], (int)p[3] );
#endif
/* Highlight Palette */
p_sys->p_palette_highlight[v][0] = p[1];
p_sys->p_palette_highlight[v][1] = p[2];
p_sys->p_palette_highlight[v][2] = p[3];
break;
}
case 0x37:
/* transparency for primary palette */
p_sys->p_palette[0][3] = (p[3] & 0x0f) << 4;
p_sys->p_palette[1][3] = (p[3] >> 4) << 4;
p_sys->p_palette[2][3] = (p[2] & 0x0f) << 4;
p_sys->p_palette[3][3] = (p[2] >> 4) << 4;
#ifdef DEBUG_CVDSUB
msg_Dbg( p_dec, "transparency for primary palette 0..3: "
"0x%0x 0x%0x 0x%0x 0x%0x",
(int)p_sys->p_palette[0][3], (int)p_sys->p_palette[1][3],
(int)p_sys->p_palette[2][3], (int)p_sys->p_palette[3][3]);
#endif
break;
case 0x3f:
/* transparency for highlight palette */
p_sys->p_palette_highlight[0][3] = (p[2] & 0x0f) << 4;
p_sys->p_palette_highlight[1][3] = (p[2] >> 4) << 4;
p_sys->p_palette_highlight[2][3] = (p[1] & 0x0f) << 4;
p_sys->p_palette_highlight[3][3] = (p[1] >> 4) << 4;
#ifdef DEBUG_CVDSUB
msg_Dbg( p_dec, "transparency for highlight palette 0..3: "
"0x%0x 0x%0x 0x%0x 0x%0x",
(int)p_sys->p_palette_highlight[0][3],
(int)p_sys->p_palette_highlight[1][3],
(int)p_sys->p_palette_highlight[2][3],
(int)p_sys->p_palette_highlight[3][3] );
#endif
break;
case 0x47:
/* offset to start of even rows of interlaced image, we correct
* to make it relative to i_image_offset (usually 4) */
p_sys->first_field_offset =
(p[2] << 8) + p[3] - p_sys->i_image_offset;
#ifdef DEBUG_CVDSUB
msg_Dbg( p_dec, "1st_field_offset %d", p_sys->first_field_offset );
#endif
break;
case 0x4f:
/* offset to start of odd rows of interlaced image, we correct
* to make it relative to i_image_offset (usually 4) */
p_sys->second_field_offset =
(p[2] << 8) + p[3] - p_sys->i_image_offset;
#ifdef DEBUG_CVDSUB
msg_Dbg( p_dec, "2nd_field_offset %d", p_sys->second_field_offset);
#endif
break;
default:
#ifdef DEBUG_CVDSUB
msg_Warn( p_dec, "unknown sequence in control header "
"0x%0x 0x%0x 0x%0x 0x%0x", p[0], p[1], p[2], p[3]);
#endif
}
}
}
/*****************************************************************************
* DecodePacket: parse and decode an SPU packet
*****************************************************************************
* This function parses and decodes an SPU packet and, if valid, returns a
* subpicture.
*****************************************************************************/
static subpicture_t *DecodePacket( decoder_t *p_dec, block_t *p_data )
{
decoder_sys_t *p_sys = p_dec->p_sys;
subpicture_t *p_spu;
subpicture_region_t *p_region;
video_format_t fmt;
int i;
/* Allocate the subpicture internal data. */
p_spu = p_dec->pf_spu_buffer_new( p_dec );
if( !p_spu ) return NULL;
p_spu->i_x = p_sys->i_x_start;
p_spu->i_x = p_spu->i_x * 3 / 4; /* FIXME: use aspect ratio for x? */
p_spu->i_y = p_sys->i_y_start;
p_spu->i_start = p_data->i_pts;
p_spu->i_stop = p_data->i_pts + p_sys->i_duration;
p_spu->b_ephemer = VLC_TRUE;
/* Create new SPU region */
memset( &fmt, 0, sizeof(video_format_t) );
fmt.i_chroma = VLC_FOURCC('Y','U','V','P');
fmt.i_aspect = VOUT_ASPECT_FACTOR;
fmt.i_width = fmt.i_visible_width = p_sys->i_width;
fmt.i_height = fmt.i_visible_height = p_sys->i_height;
fmt.i_x_offset = fmt.i_y_offset = 0;
p_region = p_spu->pf_create_region( VLC_OBJECT(p_dec), &fmt );
if( !p_region )
{
msg_Err( p_dec, "cannot allocate SPU region" );
//goto error;
}
p_spu->p_region = p_region;
p_region->i_x = p_region->i_y = 0;
/* Build palette */
fmt.p_palette->i_entries = 4;
for( i = 0; i < fmt.p_palette->i_entries; i++ )
{
fmt.p_palette->palette[i][0] = p_sys->p_palette[i][0];
fmt.p_palette->palette[i][1] = p_sys->p_palette[i][1];
fmt.p_palette->palette[i][2] = p_sys->p_palette[i][2];
fmt.p_palette->palette[i][3] = p_sys->p_palette[i][3];
}
RenderImage( p_dec, p_data, p_region );
return p_spu;
}
/*****************************************************************************
* ParseImage: parse and render the image part of the subtitle
*****************************************************************************
This part parses the subtitle graphical data and renders it.
Image data comes interlaced and is run-length encoded (RLE). Each
field is a four-bit nibbles that is further subdivided in a two-bit
repeat count and a two-bit color number - up to three pixels can be
described in four bits. What a 0 repeat count means is unknown. It
might be used for RLE extension. There is a special case of a 0
repeat count though. When the full nibble is zero, the rest of the
line is filled with the color value in the next nibble. It is
unknown what happens if the color value is greater than three. The
rest seems to use a 4-entries palette. It is not impossible that the
fill-line complete case above is not as described and the zero repeat
count means fill line. The sample code never produces this, so it
may be untested.
However we'll transform this so that that the RLE is expanded and
interlacing will also be removed. On output each pixel entry will by
a 4-bit alpha (filling 8 bits), and 8-bit y, u, and v entry.
*****************************************************************************/
static void RenderImage( decoder_t *p_dec, block_t *p_data,
subpicture_region_t *p_region )
{
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t *p_dest = p_region->picture.Y_PIXELS;
int i_field; /* The subtitles are interlaced */
int i_row, i_column; /* scanline row/column number */
uint8_t i_color, i_count;
bs_t bs;
bs_init( &bs, p_data->p_buffer + p_sys->i_image_offset,
p_data->i_buffer - p_sys->i_image_offset );
for( i_field = 0; i_field < 2; i_field++ )
{
for( i_row = i_field; i_row < p_sys->i_height; i_row += 2 )
{
for( i_column = 0; i_column < p_sys->i_width; i_column++ )
{
uint8_t i_val = bs_read( &bs, 4 );
if( i_val == 0 )
{
/* Fill the rest of the line with next color */
i_color = bs_read( &bs, 4 );
memset( &p_dest[i_row * p_region->picture.Y_PITCH +
i_column], i_color,
p_sys->i_width - i_column );
i_column = p_sys->i_width;
continue;
}
else
{
/* Normal case: get color and repeat count */
i_count = (i_val >> 2);
i_color = i_val & 0x3;
i_count = __MIN( i_count, p_sys->i_width - i_column );
memset( &p_dest[i_row * p_region->picture.Y_PITCH +
i_column], i_color, i_count );
i_column += i_count - 1;
continue;
}
}
bs_align( &bs );
}
}
}
SOURCES_svcdsub = \
common.c \
common.h \
subtitle.h \
ogt.c \
ogt.h \
ogt_parse.c \
pixmap.c \
pixmap.h \
render.c \
render.h \
write_png.c \
write_png.h \
$(NULL)
SOURCES_cvdsub = \
common.c \
common.h \
cvd.c \
cvd.h \
subtitle.h \
cvd_parse.c \
pixmap.c \
pixmap.h \
render.c \
render.h \
write_png.c \
write_png.h \
$(NULL)
/*****************************************************************************
* Common SVCD and CVD subtitle routines.
*****************************************************************************
* Copyright (C) 2003, 2004 VideoLAN
* $Id$
*
* Author: Rocky Bernstein <rocky@panix.com>
* based on code from:
* Julio Sanchez Fernandez (http://subhandler.sourceforge.net)
* Samuel Hocevar <sam@zoy.org>
* Laurent Aimar <fenrir@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/vout.h>
#include <vlc/decoder.h>
#include "subtitle.h"
#include "pixmap.h"
#include "common.h"
#ifdef HAVE_LIBPNG
#include "write_png.h"
#endif
/*****************************************************************************
Free Resources associated with subtitle packet.
*****************************************************************************/
void VCDSubClose( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
decoder_sys_t *p_sys = p_dec->p_sys;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_EXT) , "");
if( !p_sys->b_packetizer && p_sys->p_vout )
{
/* FIXME check if it's ok to not lock vout */
spu_Control( p_sys->p_vout->p_spu, SPU_CHANNEL_CLEAR,
p_sys->i_subpic_channel );
}
if( p_sys->p_block )
{
block_ChainRelease( p_sys->p_block );
}
free(p_sys->subtitle_data);
free( p_sys );
}
/*****************************************************************************
Initialize so the next packet will start off a new one.
*****************************************************************************/
void
VCDSubInitSubtitleBlock( decoder_sys_t * p_sys )
{
p_sys->i_spu_size = 0;
p_sys->state = SUBTITLE_BLOCK_EMPTY;
p_sys->i_spu = 0;
p_sys->p_block = NULL;
p_sys->subtitle_data_pos = 0;
}
void
VCDSubInitSubtitleData(decoder_sys_t *p_sys)
{
if ( p_sys->subtitle_data ) {
if ( p_sys->subtitle_data_size < p_sys->i_spu_size ) {
p_sys->subtitle_data = realloc(p_sys->subtitle_data,
p_sys->i_spu_size);
p_sys->subtitle_data_size = p_sys->i_spu_size;
}
} else {
p_sys->subtitle_data = malloc(p_sys->i_spu_size);
p_sys->subtitle_data_size = p_sys->i_spu_size;
/* FIXME: wrong place to get p_sys */
p_sys->i_image = 0;
}
p_sys->subtitle_data_pos = 0;
}
void
VCDSubAppendData ( decoder_t *p_dec, uint8_t *buffer, uint32_t buf_len )
{
decoder_sys_t *p_sys = p_dec->p_sys;
int chunk_length = buf_len;
if ( chunk_length > p_sys->i_spu_size - p_sys->subtitle_data_pos ) {
msg_Warn( p_dec, "too much data (%d) expecting at most %u",
chunk_length, p_sys->i_spu_size - p_sys->subtitle_data_pos );
chunk_length = p_sys->i_spu_size - p_sys->subtitle_data_pos;
}
if ( chunk_length > 0 ) {
#if 0
int i;
int8_t *b=buffer;
for (i=0; i<chunk_length; i++)
printf ("%02x", b[i]);
printf("\n");
#endif
memcpy(p_sys->subtitle_data + p_sys->subtitle_data_pos,
buffer, chunk_length);
p_sys->subtitle_data_pos += chunk_length;
dbg_print(DECODE_DBG_PACKET, "%d bytes appended, pointer now %d",
chunk_length, p_sys->subtitle_data_pos);
}
}
/*****************************************************************************
* FindVout: Find a vout or wait for one to be created.
*****************************************************************************/
vout_thread_t *VCDSubFindVout( decoder_t *p_dec )
{
vout_thread_t *p_vout = NULL;
/* Find an available video output */
do
{
if( p_dec->b_die || p_dec->b_error )
{
break;
}
p_vout = vlc_object_find( p_dec, VLC_OBJECT_VOUT, FIND_ANYWHERE );
if( p_vout )
{
break;
}
msleep( VOUT_OUTMEM_SLEEP );
}
while( 1 );
return p_vout;
}
/**
Remove color palette by expanding pixel entries to contain the
palette values. We work from the free space at the end to the
beginning so we can expand inline.
*/
static void
InlinePalette ( /*inout*/ uint8_t *p_dest, decoder_sys_t *p_sys )
{
const unsigned int i_width = p_sys->i_width;
const unsigned int i_height = p_sys->i_height;
int n = (i_height * i_width) - 1;
uint8_t *p_from = p_dest;
ogt_yuvt_t *p_to = (ogt_yuvt_t *) p_dest;
for ( ; n >= 0 ; n-- ) {
p_to[n] = p_sys->p_palette[p_from[n]];
/*p_to[n] = p_sys->p_palette[p_from[3]];*/
}
}
/**
Check to see if user has overridden subtitle aspect ratio.
0 is returned for no override which means just counteract any
scaling effects.
*/
unsigned int
VCDSubGetAROverride(vlc_object_t * p_input, vout_thread_t *p_vout)
{
char *psz_string = config_GetPsz( p_input, MODULE_STRING "-aspect-ratio" );
/* Check whether the user tried to override aspect ratio */
if( !psz_string ) return 0;
{
unsigned int i_new_aspect = 0;
char *psz_parser = strchr( psz_string, ':' );
if( psz_parser )
{
*psz_parser++ = '\0';
i_new_aspect = atoi( psz_string ) * VOUT_ASPECT_FACTOR
/ atoi( psz_parser );
}
else
{
i_new_aspect = p_vout->output.i_width * VOUT_ASPECT_FACTOR
* atof( psz_string )
/ p_vout->output.i_height;
}
return i_new_aspect;
}
}
/**
Scales down (reduces size) of p_dest in the x direction as
determined through aspect ratio x_scale by y_scale. Scaling
is done in place. p_spu->i_width, is updated to new width
The aspect ratio is assumed to be between 1/2 and 1.
Note: the scaling truncates the new width rather than rounds it.
Perhaps something one might want to address.
*/
void
VCDSubScaleX( decoder_t *p_dec, subpicture_t *p_spu,
unsigned int i_scale_x, unsigned int i_scale_y )
{
int i_row, i_col;
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t *p_src1 = p_spu->p_sys->p_data;
uint8_t *p_src2 = p_src1 + PIXEL_SIZE;
uint8_t *p_dst = p_src1;
unsigned int i_new_width = (p_spu->i_width * i_scale_x) / i_scale_y ;
unsigned int i_used=0; /* Number of bytes used up in p_src1. */
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_TRANSFORM) ,
"aspect ratio %i:%i, Old width: %d, new width: %d",
i_scale_x, i_scale_y, p_spu->i_width, i_new_width);
if (! (i_scale_x < i_scale_y && i_scale_y < i_scale_x+i_scale_x) )
{
msg_Warn( p_dec, "Need x < y < 2x. x: %i, y: %i", i_scale_x, i_scale_y );
return;
}
for ( i_row=0; i_row <= p_spu->i_height - 1; i_row++ ) {
if (i_used != 0) {
/* Discard the remaining piece of the column of the previous line*/
i_used=0;
p_src1 = p_src2;
p_src2 += PIXEL_SIZE;
}
for ( i_col=0; i_col <= p_spu->i_width - 2; i_col++ ) {
unsigned int i;
unsigned int w1= i_scale_x - i_used;
unsigned int w2;
if ( i_scale_y - w1 <= i_scale_x ) {
/* Average spans 2 pixels. */
w2 = i_scale_y - w1;
for (i = 0; i < PIXEL_SIZE; i++ ) {
*p_dst = ( (*p_src1 * w1) + (*p_src2 * w2) ) / i_scale_y;
p_src1++; p_src2++; p_dst++;
}
} else {
/* Average spans 3 pixels. */
unsigned int w0 = w1;
unsigned int w1 = i_scale_x;
uint8_t *p_src0 = p_src1;
w2 = i_scale_y - w0 - w1;
p_src1 = p_src2;
p_src2 += PIXEL_SIZE;
for (i = 0; i < PIXEL_SIZE; i++ ) {
*p_dst = ( (*p_src0 * w0) + (*p_src1 * w1) + (*p_src2 * w2) )
/ i_scale_y;
p_src0++; p_src1++; p_src2++; p_dst++;
}
i_col++;
}
i_used = w2;
if (i_scale_x == i_used) {
/* End of last pixel was end of p_src2. */
p_src1 = p_src2;
p_src2 += PIXEL_SIZE;
i_col++;
i_used = 0;
}
}
}
p_spu->i_width = i_new_width;
if ( p_sys && p_sys->i_debug & DECODE_DBG_TRANSFORM )
{
ogt_yuvt_t *p_source = (ogt_yuvt_t *) p_spu->p_sys->p_data;
for ( i_row=0; i_row < p_spu->i_height; i_row++ ) {
for ( i_col=0; i_col < p_spu->i_width; i_col++ ) {
printf("%1x", p_source->s.t);
p_source++;
}
printf("\n");
}
}
}
/**
The video may be scaled. However subtitle bitmaps assume an 1:1
aspect ratio. So unless the user has specified otherwise, we
need to scale to compensate for or undo the effects of video
output scaling.
Perhaps this should go in the Render routine? The advantage would
be that it will deal with a dynamically changing aspect ratio.
The downside is having to scale many times for each render call.
We also expand palette entries here, unless we are dealing with a
palettized chroma (e.g. RGB2).
*/
void
VCDSubHandleScaling( subpicture_t *p_spu, decoder_t *p_dec )
{
vlc_object_t * p_input = p_spu->p_sys->p_input;
vout_thread_t *p_vout = vlc_object_find( p_input, VLC_OBJECT_VOUT,
FIND_CHILD );
int i_aspect_x, i_aspect_y;
uint8_t *p_dest = (uint8_t *)p_spu->p_sys->p_data;
if (p_vout) {
/* Check for user-configuration override. */
unsigned int i_new_aspect;
if ( p_vout->output.i_chroma == VLC_FOURCC('R','G','B','2') ) {
/* This is an unscaled palettized format. We don't allow
user scaling here. And to make the render process faster,
we don't expand the palette entries into a color value.
*/
return;
}
InlinePalette( p_dest, p_dec->p_sys );
i_new_aspect = VCDSubGetAROverride( p_input, p_vout );
if (i_new_aspect == VOUT_ASPECT_FACTOR) {
/* For scaling 1:1, nothing needs to be done. Note this means
subtitles will get scaled the same way the video does.
*/
;
} else {
if (0 == i_new_aspect) {
/* Counteract the effects of background video scaling when
there is scaling. That's why x and y are reversed from
the else branch in the call below.
*/
switch( p_vout->output.i_chroma )
{
/* chromas in which scaling is done outside of our
blending routine, so we need to compensate for those
effects before blending gets called: */
case VLC_FOURCC('I','4','2','0'):
case VLC_FOURCC('I','Y','U','V'):
case VLC_FOURCC('Y','V','1','2'):
case VLC_FOURCC('Y','U','Y','2'):
break;
/* chromas in which scaling is done in our blending
routine and thus we don't do it here: */
case VLC_FOURCC('R','V','1','6'):
case VLC_FOURCC('R','V','2','4'):
case VLC_FOURCC('R','V','3','2'):
case VLC_FOURCC('R','G','B','2'):
return;
break;
default:
msg_Err( p_vout, "unknown chroma %x",
p_vout->output.i_chroma );
return;
break;
}
/* We get here only for scaled chromas. */
vlc_reduce( &i_aspect_x, &i_aspect_y, p_vout->render.i_aspect,
VOUT_ASPECT_FACTOR, 0 );
} else {
/* User knows best? */
vlc_reduce( &i_aspect_x, &i_aspect_y, p_vout->render.i_aspect,
VOUT_ASPECT_FACTOR, 0 );
}
VCDSubScaleX( p_dec, p_spu, i_aspect_x, i_aspect_y );
}
}
}
/**
* DestroySPU: subpicture destructor
*/
void VCDSubDestroySPU( subpicture_t *p_spu )
{
if( p_spu->p_sys->p_input )
{
/* Detach from our input thread */
vlc_object_release( p_spu->p_sys->p_input );
}
vlc_mutex_destroy( &p_spu->p_sys->lock );
free( p_spu->p_sys );
}
/*****************************************************************************
This callback is called from the input thread when we need cropping
*****************************************************************************/
int VCDSubCropCallback( vlc_object_t *p_object, char const *psz_var,
vlc_value_t oldval, vlc_value_t newval, void *p_data )
{
VCDSubUpdateSPU( (subpicture_t *)p_data, p_object );
return VLC_SUCCESS;
}
/*****************************************************************************
update subpicture settings
*****************************************************************************
This function is called from CropCallback and at initialization time, to
retrieve crop information from the input.
*****************************************************************************/
void VCDSubUpdateSPU( subpicture_t *p_spu, vlc_object_t *p_object )
{
vlc_value_t val;
p_spu->p_sys->b_crop = val.b_bool;
if( !p_spu->p_sys->b_crop )
{
return;
}
if ( VLC_SUCCESS == var_Get( p_object, "x-start", &val ) )
p_spu->p_sys->i_x_start = val.i_int;
if ( VLC_SUCCESS == var_Get( p_object, "y-start", &val ) )
p_spu->p_sys->i_y_start = val.i_int;
if ( VLC_SUCCESS == var_Get( p_object, "x-end", &val ) )
p_spu->p_sys->i_x_end = val.i_int;
if ( VLC_SUCCESS == var_Get( p_object, "y-end", &val ) )
p_spu->p_sys->i_y_end = val.i_int;
}
/*
Dump an a subtitle image to standard output - for debugging.
*/
void VCDSubDumpImage( uint8_t *p_image, uint32_t i_height, uint32_t i_width )
{
uint8_t *p = p_image;
unsigned int i_row; /* scanline row number */
unsigned int i_column; /* scanline column number */
printf("-------------------------------------\n++");
for ( i_row=0; i_row < i_height; i_row ++ ) {
for ( i_column=0; i_column<i_width; i_column++ ) {
printf("%1d", *p++ & 0x03);
}
printf("\n++");
}
printf("\n-------------------------------------\n");
}
#ifdef HAVE_LIBPNG
#define PALETTE_SIZE 4
/* Note the below assumes the above is a power of 2 */
#define PALETTE_SIZE_MASK (PALETTE_SIZE-1)
/*
Dump an a subtitle image to a Portable Network Graphics (PNG) file.
All we do here is convert YUV palette entries to RGB, expand
the image into a linear RGB pixel array, and call the routine
that does the PNG writing.
*/
void
VCDSubDumpPNG( uint8_t *p_image, decoder_t *p_dec,
uint32_t i_height, uint32_t i_width, const char *filename,
png_text *text_ptr, int i_text_count )
{
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t *p = p_image;
uint8_t *image_data = malloc(RGB_SIZE * i_height * i_width );
uint8_t *q = image_data;
unsigned int i_row; /* scanline row number */
unsigned int i_column; /* scanline column number */
uint8_t rgb_palette[PALETTE_SIZE * RGB_SIZE];
int i;
dbg_print( (DECODE_DBG_CALL), "%s", filename);
if (NULL == image_data) return;
/* Convert palette YUV into RGB. */
for (i=0; i<PALETTE_SIZE; i++) {
ogt_yuvt_t *p_yuv = &(p_sys->p_palette[i]);
uint8_t *p_rgb_out = &(rgb_palette[i*RGB_SIZE]);
yuv2rgb( p_yuv, p_rgb_out );
}
/* Convert palette entries into linear RGB array. */
for ( i_row=0; i_row < i_height; i_row ++ ) {
for ( i_column=0; i_column<i_width; i_column++ ) {
uint8_t *p_rgb = &rgb_palette[ ((*p)&PALETTE_SIZE_MASK)*RGB_SIZE ];
*q++ = p_rgb[0];
*q++ = p_rgb[1];
*q++ = p_rgb[2];
p++;
}
}
write_png( filename, i_height, i_width, image_data, text_ptr, i_text_count );
free(image_data);
}
#endif /*HAVE_LIBPNG*/
/*
* Local variables:
* c-file-style: "gnu"
* tab-width: 8
* indent-tabs-mode: nil
* End:
*/
/*****************************************************************************
* Header for Common SVCD and VCD subtitle routines.
*****************************************************************************
* Copyright (C) 2003, 2004 VideoLAN
* $Id: common.h,v 1.6 2004/01/16 13:32:37 rocky Exp $
*
* Author: Rocky Bernstein
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
void VCDSubClose ( vlc_object_t * );
void VCDSubInitSubtitleBlock( decoder_sys_t * p_sys );
void VCDSubInitSubtitleData(decoder_sys_t *p_sys);
void VCDSubAppendData( decoder_t *p_dec, uint8_t *buffer,
uint32_t buf_len );
vout_thread_t *VCDSubFindVout( decoder_t *p_dec );
void VCDSubHandleScaling( subpicture_t *p_spu, decoder_t *p_dec ) ;
void VCDSubScaleX( decoder_t *p_dec, subpicture_t *p_spu,
unsigned int i_scale_x, unsigned int i_scale_y );
void VCDSubDestroySPU( subpicture_t *p_spu );
int VCDSubCropCallback( vlc_object_t *p_object, char const *psz_var,
vlc_value_t oldval, vlc_value_t newval,
void *p_data );
void VCDSubUpdateSPU( subpicture_t *p_spu, vlc_object_t *p_object );
void VCDSubDumpImage( uint8_t *p_image, uint32_t i_height,
uint32_t i_width );
unsigned int VCDSubGetAROverride(vlc_object_t * p_input,
vout_thread_t *p_vout);
#ifdef HAVE_LIBPNG
#include <png.h>
void VCDSubDumpPNG( uint8_t *p_image, decoder_t *p_dec,
uint32_t i_height, uint32_t i_width,
const char *filename, /*in*/ png_text *text_ptr,
int i_text_count );
#endif /*HAVE_LIBPNG*/
/*****************************************************************************
* cvd.c : CVD Subtitle decoder thread
*****************************************************************************
* Copyright (C) 2003, 2004 VideoLAN
* $Id$
*
* Authors: Rocky Bernstein
* based on code from:
* Julio Sanchez Fernandez (http://subhandler.sourceforge.net)
* Samuel Hocevar <sam@zoy.org>
* Laurent Aimar <fenrir@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/vout.h>
#include <vlc/decoder.h>
#include "subtitle.h"
#include "cvd.h"
#include "common.h"
/*****************************************************************************
* Module descriptor.
*****************************************************************************/
static int VCDSubOpen ( vlc_object_t * );
static int PacketizerOpen( vlc_object_t * );
vlc_module_begin();
set_description( _("CVD subtitle decoder") );
set_capability( "decoder", 50 );
set_callbacks( VCDSubOpen, VCDSubClose );
set_category( CAT_INPUT );
set_subcategory( SUBCAT_INPUT_SCODEC );
add_integer ( MODULE_STRING "-debug", 0, NULL,
DEBUG_TEXT, DEBUG_LONGTEXT, VLC_TRUE );
add_integer ( MODULE_STRING "-horizontal-correct", 0, NULL,
HORIZONTAL_CORRECT, HORIZONTAL_CORRECT_LONGTEXT, VLC_FALSE );
add_integer ( MODULE_STRING "-vertical-correct", 0, NULL,
VERTICAL_CORRECT, VERTICAL_CORRECT_LONGTEXT, VLC_FALSE );
add_string( MODULE_STRING "-aspect-ratio", "", NULL,
SUB_ASPECT_RATIO_TEXT, SUB_ASPECT_RATIO_LONGTEXT,
VLC_TRUE );
add_integer( MODULE_STRING "-duration-scaling", 3, NULL,
DURATION_SCALE_TEXT, DURATION_SCALE_LONGTEXT,
VLC_TRUE );
add_submodule();
set_description( _("Chaoji VCD subtitle packetizer") );
set_capability( "packetizer", 50 );
set_callbacks( PacketizerOpen, VCDSubClose );
vlc_module_end();
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static block_t *Reassemble( decoder_t *, block_t ** );
static subpicture_t *Decode( decoder_t *, block_t ** );
static block_t *Packetize( decoder_t *, block_t ** );
/*****************************************************************************
* VCDSubOpen
*****************************************************************************
* Tries to launch a decoder and return score so that the interface is able
* to chose.
*****************************************************************************/
static int
VCDSubOpen( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
decoder_sys_t *p_sys;
if( p_dec->fmt_in.i_codec != VLC_FOURCC( 'c','v','d',' ' ) )
{
return VLC_EGENERIC;
}
p_dec->p_sys = p_sys = malloc( sizeof( decoder_sys_t ) );
p_sys->i_debug = config_GetInt( p_this, MODULE_STRING "-debug" );
p_sys->b_packetizer = VLC_FALSE;
p_sys->p_vout = NULL;
p_sys->i_image = -1;
p_sys->subtitle_data = NULL;
VCDSubInitSubtitleBlock( p_sys );
es_format_Init( &p_dec->fmt_out, SPU_ES, VLC_FOURCC( 'c','v','d',' ' ) );
p_dec->pf_decode_sub = Decode;
p_dec->pf_packetize = Packetize;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_EXT) , "");
return VLC_SUCCESS;
}
/*****************************************************************************
* PacketizerOpen
*****************************************************************************
* Tries to launch a decoder and return score so that the interface is able
* to chose.
*****************************************************************************/
static int PacketizerOpen( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
if( VCDSubOpen( p_this ) )
{
return VLC_EGENERIC;
}
p_dec->p_sys->b_packetizer = VLC_TRUE;
return VLC_SUCCESS;
}
/*****************************************************************************
* Decode:
*****************************************************************************/
static subpicture_t *
Decode ( decoder_t *p_dec, block_t **pp_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
block_t *p_spu = Reassemble( p_dec, pp_block );
vout_thread_t *p_last_vout = p_dec->p_sys->p_vout;
dbg_print( (DECODE_DBG_CALL) , "");
if( p_spu )
{
p_sys->i_spu = block_ChainExtract( p_spu, p_sys->buffer, 65536 );
p_sys->i_pts = p_spu->i_pts;
block_ChainRelease( p_spu );
if( ( p_sys->p_vout = VCDSubFindVout( p_dec ) ) )
{
if( p_last_vout != p_sys->p_vout )
{
spu_Control( p_sys->p_vout->p_spu, SPU_CHANNEL_REGISTER,
&p_sys->i_subpic_channel );
}
/* Parse and decode */
E_(ParsePacket)( p_dec );
vlc_object_release( p_sys->p_vout );
}
VCDSubInitSubtitleBlock ( p_sys );
}
return NULL;
}
/*****************************************************************************
* Packetize:
*****************************************************************************/
static block_t *
Packetize( decoder_t *p_dec, block_t **pp_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
block_t *p_spu = Reassemble( p_dec, pp_block );
if( p_spu )
{
p_spu->i_dts = p_spu->i_pts;
p_spu->i_length = 0;
VCDSubInitSubtitleBlock( p_sys );
return block_ChainGather( p_spu );
}
return NULL;
}
/* following functions are local */
#define SPU_HEADER_LEN 1
/*****************************************************************************
Reassemble:
Data for single screen subtitle may come in several non-contiguous
packets of a stream. This routine is called when the next packet in
the stream comes in. The job of this routine is to parse the header,
if this is the beginning, and combine the packets into one complete
subtitle unit.
If everything is complete, we will return a block. Otherwise return
NULL.
*****************************************************************************/
static block_t *
Reassemble( decoder_t *p_dec, block_t **pp_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
block_t *p_block;
uint8_t *p_buffer;
if( pp_block == NULL || *pp_block == NULL )
{
return NULL;
}
p_block = *pp_block;
*pp_block = NULL;
if( p_block->i_buffer < SPU_HEADER_LEN )
{
msg_Dbg( p_dec, "invalid packet header (size %d < %d)" ,
p_block->i_buffer, SPU_HEADER_LEN );
block_Release( p_block );
return NULL;
}
p_buffer = p_block->p_buffer;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_PACKET),
"header: 0x%02x 0x%02x 0x%02x 0x%02x, 0x%02x, 0x%02x, size: %i",
p_buffer[1], p_buffer[2], p_buffer[3], p_buffer[4],
p_buffer[5], p_buffer[6],
p_block->i_buffer);
/* Attach to our input thread and see if subtitle is selected. */
{
vlc_object_t * p_input;
vlc_value_t val;
p_input = vlc_object_find( p_dec, VLC_OBJECT_INPUT, FIND_PARENT );
if( !p_input ) return NULL;
if( var_Get( p_input, "spu-channel", &val ) )
{
vlc_object_release( p_input );
return NULL;
}
vlc_object_release( p_input );
/* Number could be 0bd, 1bd, 2bd, 3bd for 0..3. If so
reduce it to 0..3.
*/
if ( (val.i_int & 0xff) == 0xbd ) val.i_int >>= 8;
if( val.i_int == -1 || val.i_int != p_buffer[0] )
return NULL;
}
/* From the scant data on the format, there is only only way known
to detect the first packet in a subtitle. The first packet
seems to have a valid PTS while later packets for the same
image don't. */
if ( p_sys->state == SUBTITLE_BLOCK_EMPTY && p_block->i_pts == 0 ) {
msg_Warn( p_dec,
"first packet expected but no PTS present -- skipped\n");
return NULL;
}
if ( p_sys->subtitle_data_pos == 0 ) {
/* First packet in the subtitle block */
E_(ParseHeader)( p_dec, p_buffer, p_block );
VCDSubInitSubtitleData(p_sys);
}
/* FIXME - remove append_data and use chainappend */
VCDSubAppendData( p_dec, p_buffer + SPU_HEADER_LEN,
p_block->i_buffer - SPU_HEADER_LEN );
block_ChainAppend( &p_sys->p_block, p_block );
p_sys->i_spu += p_block->i_buffer - SPU_HEADER_LEN;
if ( p_sys->subtitle_data_pos == p_sys->i_spu_size ) {
E_(ParseMetaInfo)( p_dec );
return p_sys->p_block;
} else {
/* Not last block in subtitle, so wait for another. */
p_sys->state = SUBTITLE_BLOCK_PARTIAL;
}
return NULL;
}
/*****************************************************************************
* cvd.h : CVD subtitles decoder thread interface
*****************************************************************************
* Copyright (C) 2003 VideoLAN
* $Id: cvd.h,v 1.2 2004/01/04 04:56:21 rocky Exp $
*
* Author: Rocky Bernstein
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Prototypes
*****************************************************************************/
void E_(ParseHeader)( decoder_t *, uint8_t *, block_t * );
void E_(ParsePacket)( decoder_t * );
void E_(ParseMetaInfo)( decoder_t *p_dec );
/*****************************************************************************
* parse.c: Philips OGT (SVCD subtitle) packet parser
*****************************************************************************
* Copyright (C) 2003, 2004 VideoLAN
* $Id$
*
* Authors: Rocky Bernstein
* based on code from:
* Julio Sanchez Fernandez (http://subhandler.sourceforge.net)
* Sam Hocevar <sam@zoy.org>
* Laurent Aimar <fenrir@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/vout.h>
#include <vlc/decoder.h>
#include "subtitle.h"
#include "render.h"
#include "cvd.h"
#include "common.h"
#ifdef HAVE_LIBPNG
#include <png.h>
#endif
/* An image color is a two-bit palette entry: 0..3 */
typedef uint8_t ogt_color_t;
/*****************************************************************************
* Local prototypes.
*****************************************************************************/
static int ParseImage ( decoder_t *, subpicture_t * );
/*
We do not have information on the subtitle format used on CVD's
except the submux sample code and a couple of samples of dubious
origin. Thus, this is the result of reading some code whose
correctness is not known and some experimentation.
CVD subtitles are different in several ways from SVCD OGT subtitles.
Image comes first and metadata is at the end. So that the metadata
can be found easily, the subtitle packet starts with two bytes
(everything is big-endian again) that give the total size of the
subtitle data and the offset to the metadata - i.e. size of the
image data plus the four bytes at the beginning.
Image data comes interlaced is run-length encoded. Each field is a
four-bit nibble. Each nibble contains a two-bit repeat count and a
two-bit color number so that up to three pixels can be described in
four bits. The function of a 0 repeat count is unknown; it might be
used for RLE extension. However when the full nibble is zero, the
rest of the line is filled with the color value in the next nibble.
It is unknown what happens if the color value is greater than three.
The rest seems to use a 4-entries palette. It is not impossible
that the fill-line complete case above is not as described and the
zero repeat count means fill line. The sample code never produces
this, so it may be untested.
*/
void E_(ParseHeader)( decoder_t *p_dec, uint8_t *p_buffer, block_t *p_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t *p = p_buffer+1;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_PACKET),
"header: 0x%02x 0x%02x 0x%02x 0x%02x, 0x%02x, 0x%02x, size: %i",
p_buffer[0], p_buffer[1], p_buffer[2], p_buffer[3],
p_buffer[4], p_buffer[5],
p_block->i_buffer);
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_EXT) , "");
p_sys->i_pts = p_block->i_pts;
p_sys->i_spu_size = (p[0] << 8) + p[1] + 4; p += 2;
/* FIXME: check data sanity */
p_sys->metadata_offset = GETINT16(p);
p_sys->metadata_length = p_sys->i_spu_size - p_sys->metadata_offset;
p_sys->i_image_offset = 4;
p_sys->i_image_length = p_sys->metadata_offset - p_sys->i_image_offset;
dbg_print(DECODE_DBG_PACKET, "total size: %d image size: %d\n",
p_sys->i_spu_size, p_sys->i_image_length);
}
#define ExtractXY(x, y) \
x = ((p[1]&0x0f)<<6) + (p[2]>>2); \
y = ((p[2]&0x03)<<8) + p[3];
/*
We parse the metadata information here.
Although metadata information does not have to come in a fixed field
order, every metadata field consists of a tag byte followed by
parameters. In all cases known, the size including tag byte is
exactly four bytes in length.
*/
void E_(ParseMetaInfo)( decoder_t *p_dec )
{
/* last packet in subtitle block. */
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t *p = p_sys->subtitle_data + p_sys->metadata_offset;
uint8_t *p_end = p + p_sys->metadata_length;
dbg_print( (DECODE_DBG_PACKET),
"subtitle packet complete, size=%d", p_sys->i_spu );
p_sys->state = SUBTITLE_BLOCK_COMPLETE;
p_sys->i_image++;
for ( ; p < p_end; p += 4 ) {
switch ( p[0] ) {
case 0x04: /* subtitle duration in 1/90000ths of a second */
{
mtime_t i_duration = (p[1]<<16) + (p[2]<<8) + p[3];
mtime_t i_duration_scale = config_GetInt( p_dec, MODULE_STRING
"-duration-scaling" );
dbg_print( DECODE_DBG_PACKET,
"subtitle display duration %lu secs (scaled %lu secs)",
(long unsigned int) (i_duration / 90000),
(long unsigned int) (i_duration * i_duration_scale / 90000)
);
p_sys->i_duration = i_duration * i_duration_scale ;
break;
}
case 0x0c: /* unknown */
dbg_print( DECODE_DBG_PACKET,
"subtitle command unknown 0x%0x 0x%0x 0x%0x 0x%0x\n",
p[0], p[1], p[2], p[3]);
break;
case 0x17: /* coordinates of subtitle upper left x, y position */
ExtractXY(p_sys->i_x_start, p_sys->i_y_start);
break;
case 0x1f: /* coordinates of subtitle bottom right x, y position */
{
int lastx;
int lasty;
ExtractXY(lastx, lasty);
p_sys->i_width = lastx - p_sys->i_x_start + 1;
p_sys->i_height = lasty - p_sys->i_y_start + 1;
dbg_print( DECODE_DBG_PACKET,
"end position: (%d,%d): %.2x %.2x %.2x, w x h: %dx%d",
lastx, lasty, p[1], p[2], p[3],
p_sys->i_width, p_sys->i_height );
break;
}
case 0x24:
case 0x25:
case 0x26:
case 0x27:
{
uint8_t v = p[0]-0x24;
/* Primary Palette */
dbg_print( DECODE_DBG_PACKET,
"primary palette %d (y,u,v): (0x%0x,0x%0x,0x%0x)",
v, p[1], p[2], p[3]);
p_sys->p_palette[v].s.y = p[1];
p_sys->p_palette[v].s.u = p[2];
p_sys->p_palette[v].s.v = p[3];
break;
}
case 0x2c:
case 0x2d:
case 0x2e:
case 0x2f:
{
uint8_t v = p[0]-0x2c;
dbg_print( DECODE_DBG_PACKET,
"highlight palette %d (y,u,v): (0x%0x,0x%0x,0x%0x)",
v, p[1], p[2], p[3]);
/* Highlight Palette */
p_sys->p_palette_highlight[v].s.y = p[1];
p_sys->p_palette_highlight[v].s.u = p[2];
p_sys->p_palette_highlight[v].s.v = p[3];
break;
}
case 0x37:
/* transparency for primary palette */
p_sys->p_palette[0].s.t = p[3] & 0x0f;
p_sys->p_palette[1].s.t = p[3] >> 4;
p_sys->p_palette[2].s.t = p[2] & 0x0f;
p_sys->p_palette[3].s.t = p[2] >> 4;
dbg_print( DECODE_DBG_PACKET,
"transparency for primary palette 0..3: "
"0x%0x 0x%0x 0x%0x 0x%0x",
p_sys->p_palette[0].s.t,
p_sys->p_palette[1].s.t,
p_sys->p_palette[2].s.t,
p_sys->p_palette[3].s.t );
break;
case 0x3f:
/* transparency for highlight palette */
p_sys->p_palette_highlight[0].s.t = p[2] & 0x0f;
p_sys->p_palette_highlight[1].s.t = p[2] >> 4;
p_sys->p_palette_highlight[2].s.t = p[1] & 0x0f;
p_sys->p_palette_highlight[3].s.t = p[1] >> 4;
dbg_print( DECODE_DBG_PACKET,
"transparency for primary palette 0..3: "
"0x%0x 0x%0x 0x%0x 0x%0x",
p_sys->p_palette_highlight[0].s.t,
p_sys->p_palette_highlight[1].s.t,
p_sys->p_palette_highlight[2].s.t,
p_sys->p_palette_highlight[3].s.t );
break;
case 0x47:
/* offset to start of even rows of interlaced image, we correct
to make it relative to i_image_offset (usually 4) */
p_sys->first_field_offset =
(p[2] << 8) + p[3] - p_sys->i_image_offset;
dbg_print( DECODE_DBG_PACKET,
"first_field_offset %d", p_sys->first_field_offset);
break;
case 0x4f:
/* offset to start of odd rows of interlaced image, we correct
to make it relative to i_image_offset (usually 4) */
p_sys->second_field_offset =
(p[2] << 8) + p[3] - p_sys->i_image_offset;
dbg_print( DECODE_DBG_PACKET,
"second_field_offset %d", p_sys->second_field_offset);
break;
default:
msg_Warn( p_dec,
"unknown sequence in control header "
"0x%0x 0x%0x 0x%0x 0x%0x",
p[0], p[1], p[2], p[3]);
p_sys->subtitle_data_pos = 0;
}
}
}
/*****************************************************************************
* ParsePacket: parse an SPU packet and send it to the video output
*****************************************************************************
* This function parses the SPU packet and, if valid, sends it to the
* video output.
*****************************************************************************/
void
E_(ParsePacket)( decoder_t *p_dec)
{
decoder_sys_t *p_sys = p_dec->p_sys;
subpicture_t *p_spu;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_EXT) , "");
/* Allocate the subpicture internal data. */
p_spu = spu_CreateSubpicture( p_sys->p_vout->p_spu );
if( p_spu == NULL ) return;
p_spu->i_channel = p_sys->i_subpic_channel;
/* In ParseImage we expand the run-length encoded color 0's; also
we expand pixels and remove the color palette. This should
facilitate scaling and antialiasing and speed up rendering.
*/
p_spu->p_sys = malloc( sizeof( subpicture_sys_t )
+ PIXEL_SIZE * (p_sys->i_width * p_sys->i_height) );
/* Fill the p_spu structure */
vlc_mutex_init( p_dec, &p_spu->p_sys->lock );
p_spu->pf_render = VCDSubBlend;
p_spu->pf_destroy = VCDSubDestroySPU;
p_spu->p_sys->p_data = (uint8_t*)p_spu->p_sys + sizeof( subpicture_sys_t );
p_spu->p_sys->i_x_end = p_sys->i_x_start + p_sys->i_width - 1;
p_spu->p_sys->i_y_end = p_sys->i_y_start + p_sys->i_height - 1;
p_spu->i_x = p_sys->i_x_start
+ config_GetInt( p_dec, MODULE_STRING "-horizontal-correct" );
p_spu->p_sys->p_palette[0] = p_sys->p_palette[0];
p_spu->p_sys->p_palette[1] = p_sys->p_palette[1];
p_spu->p_sys->p_palette[2] = p_sys->p_palette[2];
p_spu->p_sys->p_palette[3] = p_sys->p_palette[3];
/* FIXME: use aspect ratio for x? */
p_spu->i_x = (p_spu->i_x * 3) / 4;
p_spu->i_y = p_sys->i_y_start
+ config_GetInt( p_dec, MODULE_STRING "-vertical-correct" );
p_spu->i_width = p_sys->i_width;
p_spu->i_height = p_sys->i_height;
p_spu->i_start = p_sys->i_pts;
p_spu->i_stop = p_sys->i_pts + (p_sys->i_duration);
p_spu->p_sys->b_crop = VLC_FALSE;
p_spu->p_sys->i_debug = p_sys->i_debug;
/* Get display time now. If we do it later, we may miss the PTS. */
p_spu->p_sys->i_pts = p_sys->i_pts;
/* Attach to our input thread */
p_spu->p_sys->p_input = vlc_object_find( p_dec,
VLC_OBJECT_INPUT, FIND_PARENT );
/* We try to display it */
if( ParseImage( p_dec, p_spu ) )
{
/* There was a parse error, delete the subpicture */
spu_DestroySubpicture( p_sys->p_vout->p_spu, p_spu );
return;
}
/* SPU is finished - we can ask the video output to display it */
spu_DisplaySubpicture( p_sys->p_vout->p_spu, p_spu );
}
#define advance_color_byte_pointer \
p++; \
i_nibble_field = 2; \
/* \
* This is wrong, it may exceed maxp if it is the last, check \
* should be moved to use location or the algorithm changed to \
* that in vob2sub \
*/ \
if (p >= maxp) { \
msg_Warn( p_dec, \
"broken subtitle - overflow while decoding " \
" padding (%d,%d,%d)\n", \
i_field, i_row, i_column ); \
return VLC_EGENERIC; \
}
#define CVD_FIELD_BITS (4)
#define CVD_FIELD_MASK ((1<<CVD_FIELD_BITS) - 1)
/* Get the next field - a 2-bit palette index and a run count. To do
this we use byte image pointer p, and i_nibble_field which
indicates where we are in the byte.
*/
static inline uint8_t
ExtractField(uint8_t *p, uint8_t i_nibble_field)
{
return ( ( *p >> (CVD_FIELD_BITS*(i_nibble_field-1)) ) & CVD_FIELD_MASK );
}
/*****************************************************************************
* ParseImage: parse the image part of the subtitle
*****************************************************************************
This part parses the subtitle graphical data and stores it in a more
convenient structure for later rendering.
Image data comes interlaced and is run-length encoded (RLE). Each
field is a four-bit nibbles that is further subdivided in a two-bit
repeat count and a two-bit color number - up to three pixels can be
described in four bits. What a 0 repeat count means is unknown. It
might be used for RLE extension. There is a special case of a 0
repeat count though. When the full nibble is zero, the rest of the
line is filled with the color value in the next nibble. It is
unknown what happens if the color value is greater than three. The
rest seems to use a 4-entries palette. It is not impossible that the
fill-line complete case above is not as described and the zero repeat
count means fill line. The sample code never produces this, so it
may be untested.
However we'll transform this so that that the RLE is expanded and
interlacing will also be removed. On output each pixel entry will by
a 4-bit alpha (filling 8 bits), and 8-bit y, u, and v entry.
*****************************************************************************/
static int
ParseImage( decoder_t *p_dec, subpicture_t * p_spu )
{
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t i_field; /* The subtitles are interlaced, are we on an
even or odd scanline? */
unsigned int i_row; /* scanline row number */
unsigned int i_column; /* scanline column number */
unsigned int i_width = p_sys->i_width;
unsigned int i_height = p_sys->i_height;
uint8_t *p_dest = (uint8_t *)p_spu->p_sys->p_data;
uint8_t i_nibble_field; /* The 2-bit pixels remaining in byte of *p.
Has value 0..2. */
vlc_bool_t b_filling; /* Filling i_color to the of the line. */
uint8_t i_pending = 0; /* number of pixels to fill with
color zero 0..3 */
ogt_color_t i_color=0; /* current pixel color: 0..3 */
uint8_t *p = p_sys->subtitle_data + p_sys->i_image_offset;
uint8_t *maxp = p + p_sys->i_image_length;
dbg_print( (DECODE_DBG_CALL) , "width x height: %dx%d",
i_width, i_height);
if (p_sys && p_sys->i_debug & DECODE_DBG_IMAGE)
printf("\n");
i_pending = 0;
for ( i_field=0; i_field < 2; i_field++ ) {
i_nibble_field = 2; /* 4-bit pieces available in *p */
#if 0
unsigned int i;
int8_t *b=p;
for (i=0; i< i_width * i_height; i++)
printf ("%02x", b[i]);
printf("\n");
#endif
for ( i_row=i_field; i_row < i_height; i_row += 2 ) {
b_filling = VLC_FALSE;
for ( i_column=0; i_column<i_width; i_column++ ) {
if ( i_pending ) {
/* We are in the middle of a RLE expansion, just decrement and
fall through with current color value */
i_pending--;
} else if ( b_filling ) {
/* We are just filling to the end of line with one color, just
reuse current color value */
} else {
uint8_t i_val = ExtractField(p, i_nibble_field--);
if ( i_nibble_field == 0 ) {
advance_color_byte_pointer;
}
if ( i_val == 0 ) {
/* fill the rest of the line with next color */
i_color = ExtractField( p, i_nibble_field-- );
if ( i_nibble_field == 0 ) {
p++;
i_nibble_field=2;
/*
This is wrong, it may exceed maxp if it is the
last, check should be moved to use location or the
algorithm changed to that in vob2sub
*/
if (p >= maxp) {
msg_Warn( p_dec,
"broken subtitle - overflow while decoding "
" filling (%d,%d,%d)",
i_field, i_row, i_column);
/* return VLC_EGENERIC; */
}
}
b_filling = VLC_TRUE;
} else {
/* Normal case: get color and repeat count,
this iteration will output the first (or only)
instance */
i_pending = (i_val >> 2);
i_color = i_val & 0x3;
/* This time counts against the total */
i_pending--;
}
}
/* Color is 0-3. */
p_dest[i_row*i_width+i_column] = i_color;
if (p_sys && p_sys->i_debug & DECODE_DBG_IMAGE)
printf("%1d", i_color);
}
if ( i_nibble_field == 1 ) {
advance_color_byte_pointer;
}
if (p_sys && p_sys->i_debug & DECODE_DBG_IMAGE)
printf("\n");
}
}
if (p_sys && (p_sys->i_debug & DECODE_DBG_IMAGE)) {
/* Dump out image not interlaced... */
VCDSubDumpImage( p_dest, i_height, i_width );
}
#ifdef HAVE_LIBPNG
if (p_sys && (p_sys->i_debug & DECODE_DBG_PNG)) {
#define TEXT_COUNT 2
/* Dump image to a file in PNG format. */
char filename[300];
png_text text_ptr[TEXT_COUNT];
text_ptr[0].key = "Preparer";
text_ptr[0].text = "VLC";
text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
text_ptr[1].key = "Description";
text_ptr[1].text = "CVD Subtitle";
text_ptr[1].compression = PNG_TEXT_COMPRESSION_NONE;
snprintf(filename, 300, "%s%d.png", "/tmp/vlc-cvd-sub", p_sys->i_image);
VCDSubDumpPNG( p_dest, p_dec, i_height, i_width, filename,
text_ptr, TEXT_COUNT );
}
#endif /*HAVE_LIBPNG*/
VCDSubHandleScaling( p_spu, p_dec );
return VLC_SUCCESS;
}
/*****************************************************************************
* ogt.c : Overlay Graphics Text (SVCD subtitles) decoder thread
*****************************************************************************
* Copyright (C) 2003, 2004 VideoLAN
* $Id$
*
* Author: Rocky Bernstein
* based on code from:
* Julio Sanchez Fernandez (http://subhandler.sourceforge.net)
* Samuel Hocevar <sam@zoy.org>
* Laurent Aimar <fenrir@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/vout.h>
#include <vlc/decoder.h>
#include "subtitle.h"
#include "ogt.h"
#include "common.h"
/*****************************************************************************
* Module descriptor.
*****************************************************************************/
static int VCDSubOpen ( vlc_object_t * );
static int PacketizerOpen( vlc_object_t * );
vlc_module_begin();
set_description( _("Philips OGT (SVCD subtitle) decoder") );
set_capability( "decoder", 50 );
set_callbacks( VCDSubOpen, VCDSubClose );
add_integer ( MODULE_STRING "-debug", 0, NULL,
DEBUG_TEXT, DEBUG_LONGTEXT, VLC_TRUE );
add_integer ( MODULE_STRING "-horizontal-correct", 0, NULL,
HORIZONTAL_CORRECT, HORIZONTAL_CORRECT_LONGTEXT, VLC_FALSE );
add_integer ( MODULE_STRING "-vertical-correct", 0, NULL,
VERTICAL_CORRECT, VERTICAL_CORRECT_LONGTEXT, VLC_FALSE );
add_string( MODULE_STRING "-aspect-ratio", "", NULL,
SUB_ASPECT_RATIO_TEXT, SUB_ASPECT_RATIO_LONGTEXT, VLC_TRUE );
add_integer( MODULE_STRING "-duration-scaling", 9, NULL,
DURATION_SCALE_TEXT, DURATION_SCALE_LONGTEXT, VLC_TRUE );
add_submodule();
set_description( _("Philips OGT (SVCD subtitle) packetizer") );
set_capability( "packetizer", 50 );
set_callbacks( PacketizerOpen, VCDSubClose );
vlc_module_end();
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static block_t *Reassemble ( decoder_t *, block_t ** );
static subpicture_t *Decode( decoder_t *, block_t ** );
static block_t *Packetize ( decoder_t *, block_t ** );
/*****************************************************************************
* VCDSubOpen
*****************************************************************************
* Tries to launch a decoder and return score so that the interface is able
* to chose.
*****************************************************************************/
static int
VCDSubOpen( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
decoder_sys_t *p_sys;
if( p_dec->fmt_in.i_codec != VLC_FOURCC( 'o','g','t',' ' ) )
{
return VLC_EGENERIC;
}
p_dec->p_sys = p_sys = malloc( sizeof( decoder_sys_t ) );
p_sys->i_debug = config_GetInt( p_this, MODULE_STRING "-debug" );
p_sys->b_packetizer = VLC_FALSE;
p_sys->p_vout = NULL;
p_sys->i_image = -1;
p_sys->subtitle_data = NULL;
VCDSubInitSubtitleBlock( p_sys );
es_format_Init( &p_dec->fmt_out, SPU_ES, VLC_FOURCC( 'o','g','t',' ' ) );
p_dec->pf_decode_sub = Decode;
p_dec->pf_packetize = Packetize;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_EXT) , "");
return VLC_SUCCESS;
}
/*****************************************************************************
* PacketizerOpen
*****************************************************************************
* Tries to launch a decoder and return score so that the interface is able
* to chose.
*****************************************************************************/
static int PacketizerOpen( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
if( VCDSubOpen( p_this ) )
{
return VLC_EGENERIC;
}
p_dec->p_sys->b_packetizer = VLC_TRUE;
return VLC_SUCCESS;
}
/*****************************************************************************
* Decode:
*****************************************************************************/
static subpicture_t *
Decode ( decoder_t *p_dec, block_t **pp_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
block_t *p_spu = Reassemble( p_dec, pp_block );
vout_thread_t *p_last_vout = p_dec->p_sys->p_vout;
dbg_print( (DECODE_DBG_CALL) , "");
if( p_spu )
{
p_sys->i_spu = block_ChainExtract( p_spu, p_sys->buffer, 65536 );
p_sys->i_pts = p_spu->i_pts;
block_ChainRelease( p_spu );
if( ( p_sys->p_vout = VCDSubFindVout( p_dec ) ) )
{
if( p_last_vout != p_sys->p_vout )
{
spu_Control( p_sys->p_vout->p_spu, SPU_CHANNEL_REGISTER,
&p_sys->i_subpic_channel );
}
/* Parse and decode */
E_(ParsePacket)( p_dec );
vlc_object_release( p_sys->p_vout );
}
VCDSubInitSubtitleBlock ( p_sys );
}
return NULL;
}
/*****************************************************************************
* Packetize:
*****************************************************************************/
static block_t *
Packetize( decoder_t *p_dec, block_t **pp_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
block_t *p_spu = Reassemble( p_dec, pp_block );
if( p_spu )
{
p_spu->i_dts = p_spu->i_pts;
p_spu->i_length = 0;
VCDSubInitSubtitleBlock( p_sys );
return block_ChainGather( p_spu );
}
return NULL;
}
#define SPU_HEADER_LEN 5
/*****************************************************************************
Reassemble:
The data for single screen subtitle may come in one of many
non-contiguous packets of a stream. This routine is called when the
next packet in the stream comes in. The job of this routine is to
parse the header, if this is the beginning, and combine the packets
into one complete subtitle unit.
If everything is complete, we will return a block. Otherwise return
NULL.
The format of the beginning of the subtitle packet that is used here.
size description
-------------------------------------------
byte subtitle channel (0..7) in bits 0-3
byte subtitle packet number of this subtitle image 0-N,
if the subtitle packet is complete, the top bit of the byte is 1.
uint16 subtitle image number
*****************************************************************************/
static block_t *
Reassemble( decoder_t *p_dec, block_t **pp_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
block_t *p_block;
uint8_t *p_buffer;
uint16_t i_expected_image;
uint8_t i_packet, i_expected_packet;
if( pp_block == NULL || *pp_block == NULL )
{
return NULL;
}
p_block = *pp_block;
*pp_block = NULL;
if( p_block->i_buffer < SPU_HEADER_LEN )
{
msg_Dbg( p_dec, "invalid packet header (size %d < %d)" ,
p_block->i_buffer, SPU_HEADER_LEN );
block_Release( p_block );
return NULL;
}
p_buffer = p_block->p_buffer;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_PACKET),
"header: 0x%02x 0x%02x 0x%02x 0x%02x, size: %i",
p_buffer[1], p_buffer[2], p_buffer[3], p_buffer[4],
p_block->i_buffer);
/* Attach to our input thread and see if subtitle is selected. */
{
vlc_object_t * p_input;
vlc_value_t val;
p_input = vlc_object_find( p_dec, VLC_OBJECT_INPUT, FIND_PARENT );
if( !p_input ) return NULL;
if( var_Get( p_input, "spu-channel", &val ) )
{
vlc_object_release( p_input );
return NULL;
}
vlc_object_release( p_input );
dbg_print( (DECODE_DBG_PACKET),
"val.i_int %x p_buffer[i] %x", val.i_int, p_buffer[1]);
/* The dummy ES that the menu selection uses has an 0x70 at
the head which we need to strip off. */
if( val.i_int == -1 || (val.i_int & 0x03) != p_buffer[1] )
{
dbg_print( DECODE_DBG_PACKET, "subtitle not for us.\n");
return NULL;
}
}
if ( p_sys->state == SUBTITLE_BLOCK_EMPTY ) {
i_expected_image = p_sys->i_image+1;
i_expected_packet = 0;
} else {
i_expected_image = p_sys->i_image;
i_expected_packet = p_sys->i_packet+1;
}
p_buffer += 2;
if ( *p_buffer & 0x80 ) {
p_sys->state = SUBTITLE_BLOCK_COMPLETE;
i_packet = ( *p_buffer++ & 0x7F );
} else {
p_sys->state = SUBTITLE_BLOCK_PARTIAL;
i_packet = *p_buffer++;
}
p_sys->i_image = GETINT16(p_buffer);
if ( p_sys->i_image != i_expected_image ) {
msg_Warn( p_dec, "expecting subtitle image %u but found %u",
i_expected_image, p_sys->i_image );
}
if ( i_packet != i_expected_packet ) {
msg_Warn( p_dec, "expecting subtitle image packet %u but found %u",
i_expected_packet, i_packet);
}
p_sys->i_packet = i_packet;
if ( p_sys->i_packet == 0 ) {
/* First packet in the subtitle block */
E_(ParseHeader)( p_dec, p_buffer, p_block );
VCDSubInitSubtitleData(p_sys);
}
/* FIXME - remove append_data and use chainappend */
VCDSubAppendData( p_dec, p_buffer, p_block->i_buffer - SPU_HEADER_LEN );
block_ChainAppend( &p_sys->p_block, p_block );
p_sys->i_spu += p_block->i_buffer - SPU_HEADER_LEN;
if (p_sys->state == SUBTITLE_BLOCK_COMPLETE)
{
if( p_sys->i_spu != p_sys->i_spu_size )
{
msg_Warn( p_dec, "SPU packets size=%d should be %d",
p_sys->i_spu, p_sys->i_spu_size );
}
dbg_print( (DECODE_DBG_PACKET),
"subtitle packet complete, size=%d", p_sys->i_spu );
return p_sys->p_block;
}
return NULL;
}
/*
* Local variables:
* c-file-style: "gnu"
* tab-width: 8
* indent-tabs-mode: nil
* End:
*/
/*****************************************************************************
* ogt.h : Overlay Graphics Text (SVCD subtitles) decoder thread interface
*****************************************************************************
* Copyright (C) 2003 VideoLAN
* $Id: ogt.h,v 1.6 2003/12/28 02:01:11 rocky Exp $
*
* Author: Rocky Bernstein
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Prototypes
*****************************************************************************/
void E_(ParseHeader)( decoder_t *, uint8_t *, block_t * );
void E_(ParsePacket)( decoder_t * );
/*****************************************************************************
* Philips OGT (SVCD subtitle) packet parser
*****************************************************************************
* Copyright (C) 2003, 2004 VideoLAN
* $Id$
*
* Author: Rocky Bernstein
* based on code from:
* Julio Sanchez Fernandez (http://subhandler.sourceforge.net)
* Sam Hocevar <sam@zoy.org>
* Laurent Aimar <fenrir@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/vout.h>
#include <vlc/decoder.h>
#include "subtitle.h"
#include "common.h"
#include "render.h"
#include "ogt.h"
#ifdef HAVE_LIBPNG
#include <png.h>
#endif
/* An image color is a two-bit palette entry: 0..3 */
typedef uint8_t ogt_color_t;
/*****************************************************************************
* Local prototypes.
*****************************************************************************/
static int ParseImage ( decoder_t *, subpicture_t * );
/*
The format is roughly as follows (everything is big-endian):
size description
-------------------------------------------
byte subtitle channel (0..7) in bits 0-3
byte subtitle packet number of this subtitle image 0-N,
if the subtitle packet is complete, the top bit of the byte is 1.
u_int16 subtitle image number
u_int16 length in bytes of the rest
byte option flags, unknown meaning except bit 3 (0x08) indicates
presence of the duration field
byte unknown
u_int32 duration in 1/90000ths of a second (optional), start time
is as indicated by the PTS in the PES header
u_int32 xpos
u_int32 ypos
u_int32 width (must be even)
u_int32 height (must be even)
byte[16] palette, 4 palette entries, each contains values for
Y, U, V and transparency, 0 standing for transparent
byte command,
cmd>>6==1 indicates shift
(cmd>>4)&3 is direction from, (0=top,1=left,2=right,3=bottom)
u_int32 shift duration in 1/90000ths of a second
u_int16 offset of odd-numbered scanlines - subtitle images are
given in interlace order
byte[] limited RLE image data in interlace order (0,2,4... 1,3,5) with
2-bits per palette number
*/
/* FIXME: do we really need p_buffer and p?
Can't all of thes _offset's and _lengths's get removed?
*/
void E_(ParseHeader)( decoder_t *p_dec, uint8_t *p_buffer, block_t *p_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t *p = p_buffer;
int i;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_EXT) , "");
p_sys->i_pts = p_block->i_pts;
p_sys->i_spu_size = GETINT16(p);
p_sys->i_options = *p++;
p_sys->i_options2 = *p++;
if ( p_sys->i_options & 0x08 ) {
p_sys->i_duration = GETINT32(p);
p_sys->i_duration *= config_GetInt( p_dec, MODULE_STRING
"-duration-scaling" );
} else {
/* 0 means display until next subtitle comes in. */
p_sys->i_duration = 0;
}
p_sys->i_x_start= GETINT16(p);
p_sys->i_y_start= GETINT16(p);
p_sys->i_width = GETINT16(p);
p_sys->i_height = GETINT16(p);
for (i=0; i<4; i++) {
p_sys->p_palette[i].s.y = *p++;
p_sys->p_palette[i].s.u = *p++;
p_sys->p_palette[i].s.v = *p++;
/* OGT has 8-bit resolution for alpha, but DVD's and CVDS use 4-bits.
Since we want to use the same render routine, rather than scale up
CVD (and DVD) subtitles, we'll scale down ours.
*/
p_sys->p_palette[i].s.t = (*p++) >> 4;
}
p_sys->i_cmd = *p++;
/* We do not really know this, FIXME */
if ( p_sys->i_cmd ) {
p_sys->i_cmd_arg = GETINT32(p);
}
/* Actually, this is measured against a different origin, so we have to
adjust it */
p_sys->second_field_offset = GETINT16(p);
p_sys->i_image_offset = p - p_buffer;
p_sys->i_image_length = p_sys->i_spu_size - p_sys->i_image_offset;
p_sys->metadata_length = p_sys->i_image_offset;
if (p_sys && p_sys->i_debug & DECODE_DBG_PACKET) {
msg_Dbg( p_dec, "x-start: %d, y-start: %d, width: %d, height %d, "
"spu size: %d, duration: %lu (d:%d p:%d)",
p_sys->i_x_start, p_sys->i_y_start,
p_sys->i_width, p_sys->i_height,
p_sys->i_spu_size, (long unsigned int) p_sys->i_duration,
p_sys->i_image_length, p_sys->i_image_offset);
for (i=0; i<4; i++) {
msg_Dbg( p_dec, "palette[%d]= T: %2x, Y: %2x, u: %2x, v: %2x", i,
p_sys->p_palette[i].s.t, p_sys->p_palette[i].s.y,
p_sys->p_palette[i].s.u, p_sys->p_palette[i].s.v );
}
}
}
/*****************************************************************************
* ParsePacket: parse an SPU packet and send it to the video output
*****************************************************************************
* This function parses the SPU packet and, if valid, sends it to the
* video output.
*****************************************************************************/
void
E_(ParsePacket)( decoder_t *p_dec)
{
decoder_sys_t *p_sys = p_dec->p_sys;
subpicture_t *p_spu;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_EXT) , "");
/* Allocate the subpicture internal data. */
p_spu = spu_CreateSubpicture( p_sys->p_vout->p_spu );
if( p_spu == NULL ) return;
p_spu->i_channel = p_sys->i_subpic_channel;
/* In ParseImage we expand the run-length encoded color 0's; also
we expand pixels and remove the color palette. This should
facilitate scaling and antialiasing and speed up rendering.
*/
p_spu->p_sys = malloc( sizeof( subpicture_sys_t )
+ PIXEL_SIZE * (p_sys->i_width * p_sys->i_height) );
/* Fill the p_spu structure */
vlc_mutex_init( p_dec, &p_spu->p_sys->lock );
p_spu->pf_render = VCDSubBlend;
p_spu->pf_destroy = VCDSubDestroySPU;
p_spu->p_sys->p_data = (uint8_t*)p_spu->p_sys + sizeof( subpicture_sys_t );
p_spu->p_sys->i_x_end = p_sys->i_x_start + p_sys->i_width - 1;
p_spu->p_sys->i_y_end = p_sys->i_y_start + p_sys->i_height - 1;
p_spu->i_x = p_sys->i_x_start
+ config_GetInt( p_dec, MODULE_STRING "-horizontal-correct" );
p_spu->p_sys->p_palette[0] = p_sys->p_palette[0];
p_spu->p_sys->p_palette[1] = p_sys->p_palette[1];
p_spu->p_sys->p_palette[2] = p_sys->p_palette[2];
p_spu->p_sys->p_palette[3] = p_sys->p_palette[3];
/* FIXME: use aspect ratio for x? */
p_spu->i_x = (p_spu->i_x * 3) / 4;
p_spu->i_y = p_sys->i_y_start
+ config_GetInt( p_dec, MODULE_STRING "-vertical-correct" );
p_spu->i_width = p_sys->i_width;
p_spu->i_height = p_sys->i_height;
p_spu->i_start = p_sys->i_pts;
p_spu->i_stop = p_sys->i_pts + p_sys->i_duration;
p_spu->p_sys->b_crop = VLC_FALSE;
p_spu->p_sys->i_debug = p_sys->i_debug;
/* Get display time now. If we do it later, we may miss the PTS. */
p_spu->p_sys->i_pts = p_sys->i_pts;
/* Attach to our input thread */
p_spu->p_sys->p_input = vlc_object_find( p_dec,
VLC_OBJECT_INPUT, FIND_PARENT );
/* We try to display it */
if( ParseImage( p_dec, p_spu ) )
{
/* There was a parse error, delete the subpicture */
spu_DestroySubpicture( p_sys->p_vout->p_spu, p_spu );
return;
}
/* SPU is finished - we can ask the video output to display it */
spu_DisplaySubpicture( p_sys->p_vout->p_spu, p_spu );
}
/* Advance pointer to image pointer, update internal i_2bit_field counter
and check that we haven't goine too far in the image data. */
#define advance_color_pointer_byte \
p++; \
i_2bit_field=4; \
if (p >= maxp) { \
msg_Warn( p_dec, \
"broken subtitle - tried to access beyond end " \
"in image extraction"); \
return VLC_EGENERIC; \
} \
#define advance_color_pointer \
i_2bit_field--; \
if ( i_2bit_field == 0 ) { \
advance_color_pointer_byte; \
}
#define OGT_FIELD_BITS (2)
#define OGT_FIELD_MASK ((1<<OGT_FIELD_BITS) - 1)
/* Get the next field - either a palette index or a RLE count for
color 0. To do this we use byte image pointer p, and i_2bit_field
which indicates where we are in the byte.
*/
static inline ogt_color_t
ExtractField(uint8_t *p, unsigned int i_2bit_field)
{
return ( ( *p >> (OGT_FIELD_BITS*(i_2bit_field-1)) ) & OGT_FIELD_MASK );
}
/*****************************************************************************
* ParseImage: parse the image part of the subtitle
*****************************************************************************
This part parses the subtitle graphical data and stores it in a more
convenient structure for later rendering.
The image is encoded using two bits per pixel that select a palette
entry except that value 0 starts a limited run-length encoding for
color 0. When 0 is seen, the next two bits encode one less than the
number of pixels, so we can encode run lengths from 1 to 4. These get
filled with the color in palette entry 0.
The encoding of each line is padded to a whole number of bytes. The
first field is padded to an even byte length and the complete subtitle
is padded to a 4-byte multiple that always include one zero byte at
the end.
However we'll transform this so that that the RLE is expanded and
interlacing will also be removed. On output each pixel entry will by
an 4-bit alpha (filling 8 bits), and 8-bit y, u, and v entry.
*****************************************************************************/
static int
ParseImage( decoder_t *p_dec, subpicture_t * p_spu )
{
decoder_sys_t *p_sys = p_dec->p_sys;
unsigned int i_field; /* The subtitles are interlaced, are we on an
even or odd scanline? */
unsigned int i_row; /* scanline row number */
unsigned int i_column; /* scanline column number */
unsigned int i_width = p_sys->i_width;
unsigned int i_height = p_sys->i_height;
uint8_t *p_dest = (uint8_t *)p_spu->p_sys->p_data;
uint8_t i_2bit_field; /* The 2-bit field to sue in byte of *p.
Has value 0..4. */
uint8_t i_pending_zero = 0; /* number of pixels to fill with
color zero 0..3 */
ogt_color_t i_color; /* current pixel color: 0..3 */
uint8_t *p = p_sys->subtitle_data + p_sys->i_image_offset;
uint8_t *maxp = p + p_sys->i_image_length;
dbg_print( (DECODE_DBG_CALL) , "width x height: %dx%d ",
i_width, i_height);
if (p_sys && p_sys->i_debug & DECODE_DBG_IMAGE)
printf("\n");
for ( i_field=0; i_field < 2; i_field++ ) {
i_2bit_field = 4;
for ( i_row=i_field; i_row < i_height; i_row += 2 ) {
for ( i_column=0; i_column<i_width; i_column++ ) {
if ( i_pending_zero ) {
/* We are in the middle of a RLE expansion, just decrement and
fall through with current color value */
i_pending_zero--;
i_color = 0;
} else {
i_color = ExtractField( p, i_2bit_field );
advance_color_pointer;
if ( i_color == 0 ) {
i_pending_zero = ExtractField( p, i_2bit_field );
advance_color_pointer;
/* Fall through with i_color == 0 to output the first cell */
}
}
/* Color is 0-3. */
p_dest[i_row*i_width+i_column] = i_color;
if (p_sys && p_sys->i_debug & DECODE_DBG_IMAGE)
printf("%1d", i_color);
}
if (p_sys && (p_sys->i_debug & DECODE_DBG_IMAGE))
printf("\n");
if ( i_2bit_field != 4 ) {
/* Lines are padded to complete bytes, ignore padding */
advance_color_pointer_byte;
}
}
p = p_sys->subtitle_data + p_sys->i_image_offset
+ p_sys->second_field_offset;
}
if (p_sys && (p_sys->i_debug & DECODE_DBG_IMAGE)) {
/* Dump out image not interlaced... */
VCDSubDumpImage( p_dest, i_height, i_width );
}
#ifdef HAVE_LIBPNG
if (p_sys && (p_sys->i_debug & DECODE_DBG_PNG)) {
#define TEXT_COUNT 2
/* Dump image to a file in PNG format. */
char filename[300];
png_text text_ptr[TEXT_COUNT];
text_ptr[0].key = "Preparer";
text_ptr[0].text = "VLC";
text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
text_ptr[1].key = "Description";
text_ptr[1].text = "SVCD Subtitle";
text_ptr[1].compression = PNG_TEXT_COMPRESSION_NONE;
snprintf(filename, 300, "%s%d.png", "/tmp/vlc-svcd-sub", p_sys->i_image);
VCDSubDumpPNG( p_dest, p_dec, i_height, i_width, filename,
text_ptr, TEXT_COUNT );
}
#endif /*HAVE_LIBPNG*/
VCDSubHandleScaling( p_spu, p_dec );
return VLC_SUCCESS;
}
/*
* Local variables:
* c-file-style: "gnu"
* tab-width: 8
* indent-tabs-mode: nil
* End:
*/
/*****************************************************************************
* Common pixel/chroma manipulation routines.
*****************************************************************************
* Copyright (C) 2003, 2004 VideoLAN
* $Id: pixmap.c,v 1.3 2004/01/31 05:53:35 rocky Exp $
*
* Author: Rocky Bernstein
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/vout.h>
#include "pixmap.h"
/* FIXME: This is copied from modules/video_chroma/i420_rgb.h.
Include from a more common location.
*/
/*****************************************************************************
* chroma_sys_t: chroma method descriptor
*****************************************************************************
* This structure is part of the chroma transformation descriptor, it
* describes the yuv2rgb specific properties.
*****************************************************************************/
struct chroma_sys_t
{
uint8_t *p_buffer;
int *p_offset;
/* Pre-calculated conversion tables */
void *p_base; /* base for all conversion tables */
uint8_t *p_rgb8; /* RGB 8 bits table */
uint16_t *p_rgb16; /* RGB 16 bits table */
uint32_t *p_rgb32; /* RGB 32 bits table */
/* To get RGB value for palette entry i, use (p_rgb_r[i], p_rgb_g[i],
p_rgb_b[i])
*/
uint16_t p_rgb_r[CMAP_RGB2_SIZE]; /* Red values of palette */
uint16_t p_rgb_g[CMAP_RGB2_SIZE]; /* Green values of palette */
uint16_t p_rgb_b[CMAP_RGB2_SIZE]; /* Blue values of palette */
};
/*
From
http://www.inforamp.net/~poynton/notes/colour_and_gamma/ColorFAQ.html#RTFToC11
http://people.ee.ethz.ch/~buc/brechbuehler/mirror/color/ColorFAQ.html#RTFToC1
11. What is "luma"?
It is useful in a video system to convey a component representative of
luminance and two other components representative of colour. It is
important to convey the component representative of luminance in such
a way that noise (or quantization) introduced in transmission,
processing and storage has a perceptually similar effect across the
entire tone scale from black to white. The ideal way to accomplish
these goals would be to form a luminance signal by matrixing RGB, then
subjecting luminance to a nonlinear transfer function similar to the
L* function.
There are practical reasons in video to perform these operations
in the opposite order. First a nonlinear transfer function - gamma
correction - is applied to each of the linear R, G and B. Then a
weighted sum of the nonlinear components is computed to form a
signal representative of luminance. The resulting component is
related to brightness but is not CIE luminance. Many video
engineers call it luma and give it the symbol Y'. It is often
carelessly called luminance and given the symbol Y. You must be
careful to determine whether a particular author assigns a linear
or nonlinear interpretation to the term luminance and the symbol
Y.
The coefficients that correspond to the "NTSC" red, green and blue
CRT phosphors of 1953 are standardized in ITU-R Recommendation BT.
601-2 (formerly CCIR Rec. 601-2). I call it Rec. 601. To compute
nonlinear video luma from nonlinear red, green and blue:
Y'601 = 0.299R' 0.587G' + 0.114B'
We will use the integer scaled versions of these numbers below
as RED_COEF, GREEN_COEF and BLUE_COEF.
*/
/* 19 = round(0.299 * 64) */
#define RED_COEF ((int32_t) 19)
/* 38 = round(0.587 * 64) */
#define GREEN_COEF ((int32_t) 37)
/* 7 = round(0.114 * 64) */
#define BLUE_COEF ((int32_t) 7)
/**
Find the nearest colormap entry in p_vout (assumed to have RGB2
chroma, i.e. 256 RGB 8bpp entries) that is closest in color to p_rgb. Set
out_rgb to the color found and return the colormap index.
INVALID_CMAP_ENTRY is returned if there is some error.
The closest match is determined by the the Euclidean distance
using integer-scaled 601-2 coefficients described above.
Actually, we use the square of the Euclidean distance; but in
comparisons it amounts to the same thing.
*/
cmap_t
find_cmap_rgb8_nearest(const vout_thread_t *p_vout, const uint8_t *rgb,
uint8_t *out_rgb)
{
uint16_t *p_cmap_r;
uint16_t *p_cmap_g;
uint16_t *p_cmap_b;
int i;
cmap_t i_bestmatch = INVALID_CMAP_ENTRY;
uint32_t i_mindist = 0xFFFFFFFF; /* The largest number here. */
/* Check that we really have RGB2. */
if ( !p_vout && p_vout->output.i_chroma != VLC_FOURCC('R','G','B','2') )
return INVALID_CMAP_ENTRY;
p_cmap_r=p_vout->chroma.p_sys->p_rgb_r;
p_cmap_g=p_vout->chroma.p_sys->p_rgb_g;
p_cmap_b=p_vout->chroma.p_sys->p_rgb_b;
for (i = 0; i < CMAP_RGB2_SIZE; i++) {
/* Interval range calculations to show that we don't overflow the
word sizes below. pixels component values start out 8
bits. When we subtract two components we get 9 bits, then
square to 10 bits. Next we scale by 6 to give 16
bits. XXX_COEF all fit into 5 bits, so when we multiply we
should have 21 bits maximum. So computations can be done using
32-bit precision. However before storing back distance
components we scale back down by 12 bits making the precision 9
bits. (This checks out since it is basically the range of the
square of the initial 8-bit value.)
The squared distance is the sum of three of the 9-bit components
described above. This then uses 27-bits and also fits in a
32-bit word.
*/
/* We use in integer fixed-point fractions rather than floating
point for speed. We multiply by 64 (= 1 << 6) before computing
the product, and divide the result by 64*64 (= 1 >> (6*2)).
*/
#define SCALEBITS 6
#define int32_sqr(x) ( ((int32_t) (x)) * ((int32_t) x) )
/* colormap entires are scaled to 16 bits, so we need to shift
them back down to 8. */
#define CMAP8_RED(i) (p_cmap_r[i]>>8)
#define CMAP8_GREEN(i) (p_cmap_g[i]>>8)
#define CMAP8_BLUE(i) (p_cmap_b[i]>>8)
uint32_t dr = ( RED_COEF * ( int32_sqr(rgb[RED_PIXEL] - CMAP8_RED(i))
<< SCALEBITS ) ) >> (SCALEBITS*2);
uint32_t dg = ( GREEN_COEF * ( int32_sqr(rgb[GREEN_PIXEL] - CMAP8_GREEN(i))
<< SCALEBITS ) ) >> (SCALEBITS*2);
uint32_t db = ( BLUE_COEF * ( int32_sqr(rgb[BLUE_PIXEL] - CMAP8_BLUE(i))
<< SCALEBITS ) ) >> (SCALEBITS*2);
uint32_t i_dist = dr + dg + db;
if (i_dist < i_mindist) {
i_bestmatch = i;
i_mindist = i_dist;
#if 0
printf("+++Change dist to %d RGB cmap %d (%0x, %0x, %0x)\n",
i_dist, i, p_cmap_r[ i ], p_cmap_g[ i ], p_cmap_b[ i ]);
#endif
}
}
if (out_rgb)
{
out_rgb[RED_PIXEL] = CMAP8_RED(i_bestmatch);
out_rgb[GREEN_PIXEL] = CMAP8_GREEN(i_bestmatch);
out_rgb[BLUE_PIXEL] = CMAP8_BLUE(i_bestmatch);
}
return i_bestmatch;
}
/**
Get the the rgb value for a given colormap entry for p_vout (which is'
assumed to have RGB2 chroma).
VLC_FALSE is returned if there was some error.
*/
vlc_bool_t
query_color(const vout_thread_t *p_vout, cmap_t i_cmap,
/*out*/ uint8_t *out_rgb)
{
uint16_t *p_cmap_r;
uint16_t *p_cmap_g;
uint16_t *p_cmap_b;
/* Check that we really have RGB2. */
if ( !p_vout && p_vout->output.i_chroma != VLC_FOURCC('R','G','B','2') )
return VLC_FALSE;
if ( !out_rgb )
return VLC_FALSE;
p_cmap_r=p_vout->chroma.p_sys->p_rgb_r;
p_cmap_g=p_vout->chroma.p_sys->p_rgb_g;
p_cmap_b=p_vout->chroma.p_sys->p_rgb_b;
out_rgb[RED_PIXEL] = CMAP8_RED(i_cmap);
out_rgb[GREEN_PIXEL] = CMAP8_GREEN(i_cmap);
out_rgb[BLUE_PIXEL] = CMAP8_BLUE(i_cmap);
return VLC_TRUE;
}
/*
* Local variables:
* c-file-style: "gnu"
* tab-width: 8
* indent-tabs-mode: nil
* End:
*/
/*****************************************************************************
* Common pixel/chroma manipulation routines.
*****************************************************************************
* Copyright (C) 2003, 2004 VideoLAN
* $Id: pixmap.h,v 1.6 2004/01/31 23:33:02 rocky Exp $
*
* Author: Rocky Bernstein
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
#ifndef PIXMAP_H
#define PIXMAP_H
/** Color and transparency of a pixel or a palette (CLUT) entry
*/
typedef union {
uint8_t plane[4];
struct {
uint8_t y;
uint8_t v;
uint8_t u;
uint8_t t;
} s;
} ogt_yuvt_t;
/** An undefined or invalid colormap index. */
#define INVALID_CMAP_ENTRY -1
/** Type of a palette/colormap index*/
typedef int16_t cmap_t;
/** Number of entries in RGB palette/colormap*/
#define CMAP_RGB2_SIZE 256
/**
Force v in the range 0..255. In video_chroma/i420_rgb.c, this
is macro is called CLIP. FIXME: Combine with that.
*/
#define clip_8_bit(v) \
((v < 0) ? 0 : (v > 255) ? 255 : v)
/**
Color conversion from
http://www.inforamp.net/~poynton/notes/colour_and_gamma/ColorFAQ.html#RTFToC30
http://people.ee.ethz.ch/~buc/brechbuehler/mirror/color/ColorFAQ.html
Thanks to Billy Biggs <vektor@dumbterm.net> for the pointer and
the following conversion.
R' = [ 1.1644 0 1.5960 ] ([ Y' ] [ 16 ])
G' = [ 1.1644 -0.3918 -0.8130 ] * ([ Cb ] - [ 128 ])
B' = [ 1.1644 2.0172 0 ] ([ Cr ] [ 128 ])
See also vlc/modules/video_chroma/i420_rgb.h and
vlc/modules/video_chroma/i420_rgb_c.h for a way to do this in a way
more optimized for integer arithmetic. Would be nice to merge the
two routines.
*/
/**
Convert a YUV pixel into an RGB pixel.
*/
static inline void
yuv2rgb(const ogt_yuvt_t *p_yuv, uint8_t *p_rgb_out )
{
int i_Y = p_yuv->s.y - 16;
int i_Cb = p_yuv->s.v - 128;
int i_Cr = p_yuv->s.u - 128;
int i_red = (1.1644 * i_Y) + (1.5960 * i_Cr);
int i_green = (1.1644 * i_Y) - (0.3918 * i_Cb) - (0.8130 * i_Cr);
int i_blue = (1.1644 * i_Y) + (2.0172 * i_Cb);
i_red = clip_8_bit( i_red );
i_green = clip_8_bit( i_green );
i_blue = clip_8_bit( i_blue );
#ifdef WORDS_BIGENDIAN
*p_rgb_out++ = i_red;
*p_rgb_out++ = i_green;
*p_rgb_out++ = i_blue;
#else
*p_rgb_out++ = i_blue;
*p_rgb_out++ = i_green;
*p_rgb_out++ = i_red;
#endif
}
/* The byte storage of an RGB pixel. */
#define RGB_SIZE 3
#define GREEN_PIXEL 1
#ifdef WORDS_BIGENDIAN
#define RED_PIXEL 0
#define BLUE_PIXEL 2
#else
#define RED_PIXEL 2
#define BLUE_PIXEL 0
#endif
/**
Store an RGB pixel into the location of p_pixel, taking into
account the "Endian"-ness of the underlying machine.
(N.B. Not sure if I've got this right or this is the right thing
to do.)
*/
static inline void
put_rgb24_pixel(const uint8_t *rgb, /*out*/ uint8_t *p_pixel)
{
#ifdef WORDS_BIGENDIAN
*p_pixel++;
#endif
*p_pixel++ = rgb[RED_PIXEL];
*p_pixel++ = rgb[GREEN_PIXEL];
*p_pixel++ = rgb[BLUE_PIXEL];
}
/**
Find the nearest colormap entry in p_vout (assumed to have RGB2
chroma, i.e. 256 RGB 8bpp entries) that is closest in color to p_rgb. Set
out_rgb to the color found and return the colormap index.
INVALID_CMAP_ENTRY is returned if there is some error.
*/
cmap_t
find_cmap_rgb8_nearest(const vout_thread_t *p_vout, const uint8_t *p_rgb,
/*out*/ uint8_t *out_rgb);
/**
Get the the rgb value for a given colormap entry for p_vout (which is'
assumed to have RGB2 chroma).
VLC_FALSE is returned if there was some error.
*/
vlc_bool_t
query_color(const vout_thread_t *p_vout, cmap_t i_cmap,
/*out*/ uint8_t *rgb);
#endif /* PIXMAP_H */
/*
* Local variables:
* c-file-style: "gnu"
* tab-width: 8
* indent-tabs-mode: nil
* End:
*/
/*****************************************************************************
* render.c : Philips OGT and CVD (VCD Subtitle) blending routines.
* stuff from here might be pulled out, abstracted or used
* by DVD subtitles.
*****************************************************************************
* Copyright (C) 2003, 2004 VideoLAN
* $Id$
*
* Author: Rocky Bernstein <rocky@panix.com>
* based on code from:
* Sam Hocevar <sam@zoy.org>
* Rudolf Cornelissen <rag.cornelissen@inter.nl.net>
* Roine Gustafsson <roine@popstar.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*#define TESTING_TRANSPARENCY 1*/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/vout.h>
#include <vlc/decoder.h>
#include "pixmap.h"
#include "subtitle.h"
#include "render.h"
/* We use 4 bits for an transparency value: 0..15, 15 is completely
transparent and 0 completely opaque. Note that although SVCD allow
8-bits, for these routines pixels should previously have been be
scaled down to 4 bits (the range used by DVDs).
*/
#define TRANS_BITS (4)
#define MAX_TRANS ((1<<TRANS_BITS) - 1)
#define TRANS_SCALEDOWN (8-TRANS_BITS)
/* We use a fixed-point arithmetic in scaling ratios so that we
can use integer arithmetic and still get fairly precise
results. ASCALE is a left shift amount.
*/
#define ASCALE 6 /* 2^6 = 32 */
/* Horrible hack to get dbg_print to do the right thing */
#define p_dec p_vout
/**
Take two 8-bit RGB values and a transparency and do a weighted
average of them. The "weight" comes from i_trans which is in the
range 0..MAX_TRANS. To have greater precision using integer
arithmetic, the RGB values are scaled. The uint16_t cast below is
to make sure we don't overflow the product in the multiplication
(MAX_TRANS - i_trans) is the additive "inverse" of i_trans, i.e.
i_trans + (MAX_TRANS - i_trans) = MAX_TRANS. So the resulting sum
of rgb1*i_trans + rgb2*(MAX_TRANS-i_trans) will be scaled by
MAX_TRANS. To reduce the value back down to 8 bits, we shift by
TRANS_BITS, noting that 1 << TRANS_BITS is roughly
MAX_TRANS. (Actually it is MAX_TRANS - 1).
*/
#define avg_8bit_rgb(rgb_vout, rgb_sub, i_trans) \
{ \
int i; \
for (i=0; i < RGB_SIZE; i++) { \
rgb_vout[i] = ( (uint16_t) rgb_vout[i]*(MAX_TRANS-i_trans) + \
(uint16_t) rgb_sub [i]*i_trans ) >> TRANS_BITS; \
} \
}
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static void BlendI420( vout_thread_t *, picture_t *, const subpicture_t *,
vlc_bool_t );
static void BlendYUY2( vout_thread_t *p_vout, picture_t *p_pic,
const subpicture_t *p_spu, vlc_bool_t b_crop );
static void BlendRV16( vout_thread_t *p_vout, picture_t *p_pic,
const subpicture_t *p_spu, vlc_bool_t b_crop,
vlc_bool_t b_15bpp );
static void BlendRV24( vout_thread_t *p_vout, picture_t *p_pic,
const subpicture_t *p_spu, vlc_bool_t b_crop );
static void BlendRV32( vout_thread_t *p_vout, picture_t *p_pic,
const subpicture_t *p_spu, vlc_bool_t b_crop );
static void BlendRGB2( vout_thread_t *p_vout, picture_t *p_pic,
const subpicture_t *p_spu, vlc_bool_t b_crop );
/*****************************************************************************
* BlendSPU: blend a subtitle into a picture
*****************************************************************************
This blends subtitles (a subpicture) into the underlying
picture. Subtitle data has been preprocessed as YUV + transparancy
or 4 bytes per pixel with interleaving of rows in the subtitle
removed.
*****************************************************************************/
void VCDSubBlend( vout_thread_t *p_vout, picture_t *p_pic,
const subpicture_t *p_spu )
{
struct subpicture_sys_t *p_sys = p_spu->p_sys;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_RENDER),
"chroma %x", p_vout->output.i_chroma );
switch( p_vout->output.i_chroma )
{
/* I420 target, no scaling */
case VLC_FOURCC('I','4','2','0'):
case VLC_FOURCC('I','Y','U','V'):
case VLC_FOURCC('Y','V','1','2'):
BlendI420( p_vout, p_pic, p_spu, p_spu->p_sys->b_crop );
break;
/* RGB 555 - scaled */
case VLC_FOURCC('R','V','1','5'):
BlendRV16( p_vout, p_pic, p_spu, p_spu->p_sys->b_crop,
VLC_TRUE );
break;
case VLC_FOURCC('R','V','1','6'):
BlendRV16( p_vout, p_pic, p_spu, p_spu->p_sys->b_crop,
/* Not sure under what conditions RV16 is really
RV16 and not RV15.
*/
#if 0
VLC_FALSE );
#else
VLC_TRUE );
#endif
break;
/* RV24 target, scaling */
case VLC_FOURCC('R','V','2','4'):
BlendRV24( p_vout, p_pic, p_spu, p_spu->p_sys->b_crop );
break;
/* RV32 target, scaling */
case VLC_FOURCC('R','V','3','2'):
BlendRV32( p_vout, p_pic, p_spu, p_spu->p_sys->b_crop );
break;
/* NVidia overlay, no scaling */
case VLC_FOURCC('Y','U','Y','2'):
BlendYUY2( p_vout, p_pic, p_spu, p_spu->p_sys->b_crop );
break;
/* Palettized 8 bits per pixel (256 colors). Each
pixel is an uint8_t index in the palette
Used in ASCII Art.
*/
case VLC_FOURCC('R','G','B','2'):
BlendRGB2( p_vout, p_pic, p_spu, p_spu->p_sys->b_crop );
break;
default:
msg_Err( p_vout, "unknown chroma, can't render SPU" );
break;
}
}
/* Following functions are local */
/*
YV12 format:
All Y samples are found first in memory as an array of bytes
(possibly with a larger stride for memory alignment), followed
immediately by all Cr (=U) samples (with half the stride of the Y
lines, and half the number of lines), then followed immediately by
all Cb (=V) samples in a similar fashion.
*/
static void BlendI420( vout_thread_t *p_vout, picture_t *p_pic,
const subpicture_t *p_spu, vlc_bool_t b_crop )
{
/* Common variables */
uint8_t *p_pixel_base_Y, *p_pixel_base_V, *p_pixel_base_U;
ogt_yuvt_t *p_source; /* This is the where the subtitle pixels come from */
int i_x, i_y;
vlc_bool_t even_scanline = VLC_FALSE;
/* Crop-specific */
int i_x_start, i_y_start, i_x_end, i_y_end;
/* int i=0; */
const struct subpicture_sys_t *p_sys = p_spu->p_sys;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_RENDER),
"spu width x height: (%dx%d), (x,y)=(%d,%d), yuv pitch (%d,%d,%d)",
p_spu->i_width, p_spu->i_height, p_spu->i_x, p_spu->i_y,
p_pic->Y_PITCH, p_pic->U_PITCH, p_pic->V_PITCH );
p_pixel_base_Y = p_pic->p[Y_PLANE].p_pixels + p_spu->i_x
+ p_pic->p[Y_PLANE].i_pitch * p_spu->i_y;
p_pixel_base_U = p_pic->p[U_PLANE].p_pixels + p_spu->i_x/2
+ p_pic->p[U_PLANE].i_pitch * p_spu->i_y/2;
p_pixel_base_V = p_pic->p[V_PLANE].p_pixels + p_spu->i_x/2
+ p_pic->p[V_PLANE].i_pitch * p_spu->i_y/2;
i_x_start = p_sys->i_x_start;
i_y_start = p_pic->p[Y_PLANE].i_pitch * p_sys->i_y_start;
i_x_end = p_sys->i_x_end;
i_y_end = p_pic->p[Y_PLANE].i_pitch * p_sys->i_y_end;
p_source = (ogt_yuvt_t *)p_sys->p_data;
/* Draw until we reach the bottom of the subtitle */
for( i_y = 0;
i_y < p_spu->i_height * p_pic->p[Y_PLANE].i_pitch ;
i_y += p_pic->p[Y_PLANE].i_pitch )
{
uint8_t *p_pixel_base_Y_y = p_pixel_base_Y + i_y;
uint8_t *p_pixel_base_U_y = p_pixel_base_U + i_y/4;
uint8_t *p_pixel_base_V_y = p_pixel_base_V + i_y/4;
i_x = 0;
if ( b_crop ) {
if ( i_y > i_y_end ) break;
if (i_x_start) {
i_x = i_x_start;
p_source += i_x_start;
}
}
even_scanline = !even_scanline;
/* Draw until we reach the end of the line */
for( ; i_x < p_spu->i_width; i_x++, p_source++ )
{
if( b_crop ) {
/* FIXME: y cropping should be dealt with outside of this loop.*/
if ( i_y < i_y_start) continue;
if ( i_x > i_x_end )
{
p_source += p_spu->i_width - i_x;
break;
}
}
#ifdef TESTING_TRANSPARENCY
if (p_source->s.t == MAX_TRANS) p_source->s.t >>= 1;
#endif
switch( p_source->s.t )
{
case 0:
/* Completely transparent. Don't change pixel. */
break;
case MAX_TRANS:
{
/* Completely opaque. Completely overwrite underlying
pixel with subtitle pixel. */
/* This is the location that's going to get changed.*/
uint8_t *p_pixel_Y = p_pixel_base_Y_y + i_x;
*p_pixel_Y = p_source->plane[Y_PLANE];
if ( even_scanline && i_x % 2 == 0 ) {
uint8_t *p_pixel_U = p_pixel_base_U_y + i_x/2;
uint8_t *p_pixel_V = p_pixel_base_V_y + i_x/2;
*p_pixel_U = p_source->plane[U_PLANE];
*p_pixel_V = p_source->plane[V_PLANE];
}
break;
}
default:
{
/* Blend in underlying subtitle pixel. */
/* This is the location that's going to get changed. */
uint8_t *p_pixel_Y = p_pixel_base_Y_y + i_x;
/* This is the weighted part of the subtitle. The
color plane is 8 bits and transparancy is 4 bits so
when multiplied we get up to 12 bits.
*/
uint16_t i_sub_color_Y =
(uint16_t) ( p_source->plane[Y_PLANE] *
(uint16_t) (p_source->s.t) );
/* This is the weighted part of the underlying pixel.
For the same reasons above, the result is up to 12
bits. However since the transparancies are
inverses, the sum of i_sub_color and i_pixel_color
will not exceed 12 bits.
*/
uint16_t i_pixel_color_Y =
(uint16_t) ( *p_pixel_Y *
(uint16_t) (MAX_TRANS - p_source->s.t) ) ;
/* Scale the 12-bit result back down to 8 bits. A
precise scaling after adding the two components,
would divide by one less than a power of 2. However
to simplify and speed things we use a power of
2. This means the boundaries (either all
transparent and all opaque) aren't handled properly.
But we deal with them in special cases above. */
*p_pixel_Y = ( i_sub_color_Y + i_pixel_color_Y ) >> TRANS_BITS;
if ( even_scanline && i_x % 2 == 0 ) {
uint8_t *p_pixel_U = p_pixel_base_U_y + i_x/2
- p_pic->p[U_PLANE].i_pitch / 2;
uint8_t *p_pixel_V = p_pixel_base_V_y + i_x/2
- p_pic->p[V_PLANE].i_pitch / 2;
uint16_t i_sub_color_U =
(uint16_t) ( p_source->plane[U_PLANE] *
(uint16_t) (p_source->s.t) );
uint16_t i_sub_color_V =
(uint16_t) ( p_source->plane[V_PLANE] *
(uint16_t) (p_source->s.t) );
uint16_t i_pixel_color_U =
(uint16_t) ( *p_pixel_U *
(uint16_t) (MAX_TRANS - p_source->s.t) ) ;
uint16_t i_pixel_color_V =
(uint16_t) ( *p_pixel_V *
(uint16_t) (MAX_TRANS - p_source->s.t) ) ;
*p_pixel_U = ( i_sub_color_U + i_pixel_color_U )>>TRANS_BITS;
*p_pixel_V = ( i_sub_color_V + i_pixel_color_V )>>TRANS_BITS;
}
break;
}
}
}
}
}
/*
YUY2 Format:
Data is found in memory as an array of bytes in which the first byte
contains the first sample of Y, the second byte contains the first
sample of Cb (=U), the third byte contains the second sample of Y,
the fourth byte contains the first sample of Cr (=V); and so
on. Each 32-bit word then contains information for two contiguous
horizontal pixels, two 8-bit Y values plus a single Cb and Cr which
spans the two pixels.
*/
#define BYTES_PER_PIXEL 4
static void BlendYUY2( vout_thread_t *p_vout, picture_t *p_pic,
const subpicture_t *p_spu, vlc_bool_t b_crop )
{
/* Common variables */
uint8_t *p_pixel_base;
/* This is the where the subtitle pixels come from */
ogt_yuvt_t *p_source = (ogt_yuvt_t *) p_spu->p_sys->p_data;
ogt_yuvt_t *p_source_end = (ogt_yuvt_t *)p_spu->p_sys->p_data +
(p_spu->i_width * p_spu->i_height);
uint16_t i_x, i_y;
/* Make sure we start on a word (4-byte) boundary. */
uint16_t i_spu_x = (p_spu->i_x & 0xFFFE) * 2;
/* Crop-specific */
int i_x_start, i_y_start, i_x_end, i_y_end;
const struct subpicture_sys_t *p_sys = p_spu->p_sys;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_RENDER),
"spu width x height: (%dx%d), (x,y)=(%d,%d), pitch: %d",
p_spu->i_width, p_spu->i_height, p_spu->i_x, p_spu->i_y,
p_pic->p->i_pitch );
p_pixel_base = p_pic->p->p_pixels +
+ ( p_spu->i_y * p_pic->p->i_pitch ) + i_spu_x;
i_x_start = p_sys->i_x_start;
i_y_start = p_sys->i_y_start * p_pic->p->i_pitch;
i_x_end = p_sys->i_x_end;
i_y_end = p_sys->i_y_end * p_pic->p->i_pitch;
/* Draw until we reach the bottom of the subtitle */
for( i_y = 0;
i_y < p_spu->i_height * p_pic->p[Y_PLANE].i_pitch ;
i_y += p_pic->p[Y_PLANE].i_pitch )
{
uint8_t *p_pixel_base_y = p_pixel_base + i_y;
i_x = 0;
if ( b_crop ) {
if ( i_y > i_y_end ) break;
if (i_x_start) {
i_x = i_x_start;
p_source += (i_x_start*2);
}
}
/* Draw until we reach the end of the line. Each output pixel
is a combination of two source pixels.
*/
for( i_x = 0; i_x < p_spu->i_width / 2; i_x++, p_source +=2 )
{
uint16_t i_avg_tr; /* transparancy sort of averaged over 2 pixels*/
if (p_source > p_source_end-1) {
msg_Err( p_vout, "Trying to access beyond subtitle x: %d y: %d",
i_x, i_y);
return;
}
if( b_crop ) {
/* FIXME: y cropping should be dealt with outside of this loop.*/
if ( i_y < i_y_start) continue;
if ( i_x > i_x_end )
{
p_source += p_spu->i_width - (i_x*2);
break;
}
}
/* Favor opaque subtitle pixels. */
if ( (p_source->s.t == 0) && (p_source+1)->s.t == MAX_TRANS )
i_avg_tr = (p_source+1)->s.t;
else if ( (p_source->s.t == MAX_TRANS) && (p_source+1)->s.t == 0 )
i_avg_tr = p_source->s.t;
else
i_avg_tr = ( p_source->s.t + (p_source+1)->s.t ) / 2;
#ifdef TESTING_TRANSPARENCY
if (i_avg_tr == MAX_TRANS) i_avg_tr >>= 1;
#endif
switch( i_avg_tr )
{
case 0:
/* Completely transparent. Don't change pixel. */
break;
case MAX_TRANS:
{
/* Completely opaque. Completely overwrite underlying
pixel with subtitle pixel. */
/* This is the location that's going to get changed. */
uint8_t *p_pixel = p_pixel_base_y + i_x * BYTES_PER_PIXEL;
uint8_t i_avg_u;
uint8_t i_avg_v;
/* Favor opaque subtitle pixel. */
if (p_source->s.t == MAX_TRANS ) {
i_avg_u = p_source->plane[U_PLANE] ;
i_avg_v = p_source->plane[V_PLANE] ;
} else if ( (p_source+1)->s.t == MAX_TRANS ) {
i_avg_u = (p_source+1)->plane[U_PLANE] ;
i_avg_v = (p_source+1)->plane[V_PLANE] ;
} else {
i_avg_u = ( p_source->plane[U_PLANE]
+ (p_source+1)->plane[U_PLANE] ) / 2;
i_avg_v = ( p_source->plane[V_PLANE]
+ (p_source+1)->plane[V_PLANE] ) / 2;
}
/* draw a two contiguous pixels: 2 Y values, 1 U, and 1 V. */
*p_pixel++ = p_source->plane[Y_PLANE] ;
*p_pixel++ = i_avg_u;
*p_pixel++ = (p_source+1)->plane[Y_PLANE] ;
*p_pixel++ = i_avg_v;
break;
}
default:
{
/* Blend in underlying subtitle pixels. */
/* This is the location that's going to get changed. */
uint8_t *p_pixel = p_pixel_base_y + i_x * BYTES_PER_PIXEL;
uint8_t i_avg_u = ( p_source->plane[U_PLANE]
+ (p_source+1)->plane[U_PLANE] ) / 2;
uint8_t i_avg_v = ( p_source->plane[V_PLANE]
+ (p_source+1)->plane[V_PLANE] ) / 2;
/* This is the weighted part of the two subtitle
pixels. The color plane is 8 bits and transparancy
is 4 bits so when multiplied we get up to 12 bits.
*/
uint16_t i_sub_color_Y1 =
(uint16_t) ( p_source->plane[Y_PLANE] *
(uint16_t) (p_source->s.t) );
uint16_t i_sub_color_Y2 =
(uint16_t) ( (p_source+1)->plane[Y_PLANE] *
(uint16_t) ((p_source+1)->s.t) );
/* This is the weighted part of the underlying pixels.
For the same reasons above, the result is up to 12
bits. However since the transparancies are
inverses, the sum of i_sub_color and i_pixel_color
will not exceed 12 bits.
*/
uint16_t i_sub_color_U =
(uint16_t) ( i_avg_u * (uint16_t) i_avg_tr );
uint16_t i_sub_color_V =
(uint16_t) ( i_avg_v * (uint16_t) i_avg_tr );
uint16_t i_pixel_color_Y1 =
(uint16_t) ( *(p_pixel) *
(uint16_t) (MAX_TRANS - i_avg_tr) ) ;
uint16_t i_pixel_color_Y2 =
(uint16_t) ( *(p_pixel+2) *
(uint16_t) (MAX_TRANS - i_avg_tr) ) ;
uint16_t i_pixel_color_U =
(uint16_t) ( *(p_pixel+1) *
(uint16_t) (MAX_TRANS - i_avg_tr) ) ;
uint16_t i_pixel_color_V =
(uint16_t) ( *(p_pixel+3) *
(uint16_t) (MAX_TRANS - i_avg_tr) ) ;
/* draw a two contiguous pixels: 2 Y values, 1 U, and 1 V. */
/* Scale the 12-bit result back down to 8 bits. A
precise scaling after adding the two components,
would divide by one less than a power of 2. However
to simplify and speed things we use a power of
2. This means the boundaries (either all
transparent and all opaque) aren't handled properly.
But we deal with them in special cases above. */
*p_pixel++ = ( i_sub_color_Y1 + i_pixel_color_Y1 )>>TRANS_BITS;
*p_pixel++ = ( i_sub_color_U + i_pixel_color_U ) >>TRANS_BITS;
*p_pixel++ = ( i_sub_color_Y2 + i_pixel_color_Y2 )>>TRANS_BITS;
*p_pixel++ = ( i_sub_color_V + i_pixel_color_V ) >>TRANS_BITS;
break;
}
}
}
/* For an odd width source, we'll just have to drop off a pixel. */
if (p_spu->i_width % 2) p_source++;
}
}
/**
Convert a YUV pixel into a 16-bit RGB 5-5-5 pixel.
A RGB 5-5-5 pixel looks like this:
RGB 5-5-5 bit (MSB) 7 6 5 4 3 2 1 0 (LSB)
p ? B4 B3 B2 B1 B0 R4 R3
q R2 R1 R0 G4 G3 G2 G1 G0
**/
static inline void
yuv2rgb555(ogt_yuvt_t *p_yuv, uint8_t *p_rgb1, uint8_t *p_rgb2 )
{
uint8_t rgb[RGB_SIZE];
yuv2rgb(p_yuv, rgb);
/* Scale RGB from 8 bits down to 5. */
rgb[RED_PIXEL] >>= (8-5);
rgb[GREEN_PIXEL] >>= (8-5);
rgb[BLUE_PIXEL] >>= (8-5);
*p_rgb1 = ( (rgb[BLUE_PIXEL] << 2)&0x7c ) | ( (rgb[RED_PIXEL]>>3) & 0x03 );
*p_rgb2 = ( (rgb[RED_PIXEL] << 5)&0xe0 ) | ( rgb[GREEN_PIXEL]&0x1f );
#if 0
printf("Y,Cb,Cr,T=(%02x,%02x,%02x,%02x), r,g,b=(%d,%d,%d), "
"rgb1: %02x, rgb2 %02x\n",
p_yuv->s.y, p_yuv->s.u, p_yuv->s.v, p_yuv->s.t,
rgb[RED_PIXEL], rgb[GREEN_PIXEL], rgb[BLUE_PIXEL],
*p_rgb1, *p_rgb2);
#endif
}
/**
Convert a YUV pixel into a 16-bit RGB 5-6-5 pixel.
A RGB 5-6-5 pixel looks like this:
RGB 5-6-5 bit (MSB) 7 6 5 4 3 2 1 0 (LSB)
p B4 B3 B2 B1 B0 R5 R4 R3
q R2 R1 R0 G4 G3 G2 G1 G0
**/
static inline void
yuv2rgb565(ogt_yuvt_t *p_yuv, uint8_t *p_rgb1, uint8_t *p_rgb2 )
{
uint8_t rgb[RGB_SIZE];
yuv2rgb(p_yuv, rgb);
/* Scale RGB from 8 bits down to 5 or 6 bits. */
rgb[RED_PIXEL] >>= (8-6);
rgb[GREEN_PIXEL] >>= (8-5);
rgb[BLUE_PIXEL] >>= (8-5);
*p_rgb1 = ( (rgb[BLUE_PIXEL] << 3)&0xF8 ) | ( (rgb[RED_PIXEL]>>3) & 0x07 );
*p_rgb2 = ( (rgb[RED_PIXEL] << 5)&0xe0 ) | ( rgb[GREEN_PIXEL]&0x1f );
#if 0
printf("Y,Cb,Cr,T=(%02x,%02x,%02x,%02x), r,g,b=(%d,%d,%d), "
"rgb1: %02x, rgb2 %02x\n",
p_yuv->s.y, p_yuv->s.u, p_yuv->s.v, p_yuv->s.t,
rgb[RED_PIXEL], rgb[GREEN_PIXEL], rgb[BLUE_PIXEL],
*p_rgb1, *p_rgb2);
#endif
}
static inline void
rv16_pack_blend(uint8_t *p_pixel, ogt_yuvt_t *p_source, uint8_t *p_rgb1,
uint8_t *p_rgb2, vlc_bool_t b_15bpp, uint8_t i_trans,
int a_scale_down )
{
uint8_t rgb_sub[3];
uint8_t rgb_vout[RGB_SIZE];
int i;
yuv2rgb(p_source, rgb_sub);
/* Scale RGB from 8 bits down to 6 or 5. */
rgb_sub[GREEN_PIXEL] >>= (8-5);
rgb_sub[BLUE_PIXEL] >>= (8-5);
rgb_vout[GREEN_PIXEL] = *(p_pixel+1) & 0x1f;
if (b_15bpp) {
rgb_sub[RED_PIXEL] >>= (8-5);
rgb_vout[BLUE_PIXEL] = ((*p_pixel)>>2) & 0x1f;
rgb_vout[RED_PIXEL] = ((*p_pixel & 0x03) << 3) | ((*(p_pixel+1)&0xe0)>>5);
} else {
rgb_sub[RED_PIXEL] >>= (8-6);
rgb_vout[BLUE_PIXEL] = ((*p_pixel)>>3) & 0x1f;
rgb_vout[RED_PIXEL] = ((*p_pixel & 0x07) << 3) | ((*(p_pixel+1)&0xe0)>>5);
}
#if 0
printf("r,g,b=(%d,%d,%d), sub r,g,b=(%d,%d,%d), trans %d, inv_trans %d\n",
rgb_vout[RED_PIXEL], rgb_vout[GREEN_PIXEL], rgb_vout[BLUE_PIXEL],
rgb_sub[RED_PIXEL], rgb_sub[GREEN_PIXEL],
rgb_sub[BLUE_PIXEL], i_trans, i_inv_trans);
#endif
#ifdef FIXED_RV16_TRANSPARENCY
avg_8bit_rgb(rgb_vout, rgb_sub, i_trans);
#else
for (i=0; i < RGB_SIZE; i++) {
/* For now the Best we can do is fade the color. Picking up
video underneath doesn't work. */
/* rgb_vout[i] = ( (uint16_t) rgb_vout[i]*i_inv_trans ) >> TRANS_BITS; */
rgb_vout[i] = ( (uint16_t) rgb_sub[i]*i_trans ) >> TRANS_BITS;
#endif
}
#if 0
printf("avg r,g,b=(%d,%d,%d)\n",
rgb_vout[RED_PIXEL], rgb_vout[GREEN_PIXEL], rgb_vout[BLUE_PIXEL] );
#endif
#if 0
if (b_15bpp) {
*p_rgb1 = ( (rgb_vout[BLUE_PIXEL] << 2)&0x7c )|( (rgb_vout[RED_PIXEL]>>3)&0x03 );
} else {
*p_rgb1 = ( (rgb_vout[BLUE_PIXEL] << 3)&0xF8 )|( (rgb_vout[RED_PIXEL]>>3)&0x07 );
}
*p_rgb2 = ( (rgb_vout[RED_PIXEL] << 5)&0xe0 ) | ( rgb_vout[GREEN_PIXEL]&0x1f );
#else
*p_rgb1 = (*p_rgb1)+1;
*p_rgb2 = (*p_rgb2)+1;
#endif
}
#undef BYTES_PER_PIXEL
#define BYTES_PER_PIXEL 2
static void
BlendRV16( vout_thread_t *p_vout, picture_t *p_pic,
const subpicture_t *p_spu, vlc_bool_t b_crop,
vlc_bool_t b_15bpp )
{
/* Common variables */
uint8_t *p_pixel_base;
ogt_yuvt_t *p_src_start = (ogt_yuvt_t *)p_spu->p_sys->p_data;
ogt_yuvt_t *p_src_end = &p_src_start[p_spu->i_height * p_spu->i_width];
ogt_yuvt_t *p_source;
int i_x, i_y;
int i_y_src;
/* Chroma specific */
uint32_t i_xscale; /* Amount we scale subtitle in the x direction,
multiplied by 2**ASCALE. */
uint32_t i_yscale; /* Amount we scale subtitle in the y direction.
multiplied by 2**ASCALE. */
int i_width, i_height, i_ytmp, i_ynext;
/* Crop-specific */
int i_x_start, i_y_start, i_x_end, i_y_end;
struct subpicture_sys_t *p_sys = p_spu->p_sys;
i_xscale = ( p_vout->output.i_width << ASCALE ) / p_vout->render.i_width;
i_yscale = ( p_vout->output.i_height << ASCALE ) / p_vout->render.i_height;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_RENDER),
"spu: %dx%d, scaled: %dx%d, vout render: %dx%d, scale %dx%d",
p_spu->i_width, p_spu->i_height,
p_vout->output.i_width, p_vout->output.i_height,
p_vout->render.i_width, p_vout->render.i_height,
i_xscale, i_yscale
);
i_width = p_spu->i_width * i_xscale;
i_height = p_spu->i_height * i_yscale;
/* Set where we will start blending subtitle from using
the picture coordinates subtitle offsets
*/
p_pixel_base = p_pic->p->p_pixels
+ ( (p_spu->i_x * i_xscale) >> ASCALE ) * BYTES_PER_PIXEL
+ ( (p_spu->i_y * i_yscale) >> ASCALE ) * p_pic->p->i_pitch;
i_x_start = p_sys->i_x_start;
i_y_start = i_yscale * p_sys->i_y_start;
i_x_end = p_sys->i_x_end;
i_y_end = i_yscale * p_sys->i_y_end;
p_source = (ogt_yuvt_t *)p_sys->p_data;
/* Draw until we reach the bottom of the subtitle */
i_y = 0;
for( i_y_src = 0 ; i_y_src < p_spu->i_height * p_spu->i_width;
i_y_src += p_spu->i_width )
{
uint8_t *p_pixel_base_y;
i_ytmp = i_y >> ASCALE;
i_y += i_yscale;
p_pixel_base_y = p_pixel_base + (i_ytmp * p_pic->p->i_pitch);
i_x = 0;
if ( b_crop ) {
if ( i_y > i_y_end ) break;
if (i_x_start) {
i_x = i_x_start;
p_source += i_x_start;
}
}
/* Check whether we need to draw one line or more than one */
if( i_ytmp + 1 >= ( i_y >> ASCALE ) )
{
/* Draw until we reach the end of the line */
for( ; i_x < p_spu->i_width; i_x++, p_source++ )
{
#if 0
uint8_t *p=(uint8_t *) p_source;
printf("+++ %02x %02x %02x %02x\n",
p[0], p[1], p[2], p[3]);
#endif
if( b_crop ) {
/* FIXME: y cropping should be dealt with outside of this
loop.*/
if ( i_y < i_y_start) continue;
if ( i_x > i_x_end )
{
p_source += p_spu->i_width - i_x;
break;
}
}
if (p_source >= p_src_end) {
msg_Err( p_vout, "Trying to access beyond subtitle %dx%d %d",
i_x, i_y / i_yscale, i_height);
return;
}
#ifdef TESTING_TRANSPARENCY
if (p_source->s.t == MAX_TRANS) p_source->s.t >>= 1;
#endif
switch( p_source->s.t )
{
case 0:
/* Completely transparent. Don't change pixel. */
break;
case MAX_TRANS:
{
/* Completely opaque. Completely overwrite underlying
pixel with subtitle pixel. */
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t len = i_xlast - i_xdest;
uint8_t i_rgb1;
uint8_t i_rgb2;
/* This is the location that's going to get changed. */
uint8_t *p_dest = p_pixel_base_y + i_x * BYTES_PER_PIXEL;
if (b_15bpp)
yuv2rgb555(p_source, &i_rgb1, &i_rgb2);
else
yuv2rgb565(p_source, &i_rgb1, &i_rgb2);
for ( len = i_xlast - i_xdest; len ; len--) {
*p_dest++ = i_rgb1;
*p_dest++ = i_rgb2;
}
break;
}
default:
{
/* Blend in underlying pixel subtitle pixel. */
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint8_t i_rgb1;
uint8_t i_rgb2;
uint32_t len = i_xlast - i_xdest;
/* This is the location that's going to get changed. */
uint8_t *p_dest = p_pixel_base_y + i_x * BYTES_PER_PIXEL;
for ( len = i_xlast - i_xdest; len ; len--) {
rv16_pack_blend(p_dest, p_source, &i_rgb1, &i_rgb2,
b_15bpp, p_source->s.t, TRANS_SCALEDOWN);
*p_dest++ = i_rgb1;
*p_dest++ = i_rgb2;
}
break;
}
}
}
}
else
{
i_ynext = p_pic->p->i_pitch * i_y >> ASCALE;
/* Draw until we reach the end of the line */
for( ; i_x < p_spu->i_width; i_x++, p_source++ )
{
if( b_crop ) {
/* FIXME: y cropping should be dealt with outside of this
loop.*/
if ( i_y < i_y_start) continue;
if ( i_x > i_x_end )
{
p_source += p_spu->i_width - i_x;
break;
}
}
if (p_source >= p_src_end) {
msg_Err( p_vout, "trying to access beyond subtitle %dx%d %d",
i_x, i_y / i_yscale, i_height);
return;
}
switch( p_source->s.t )
{
case 0:
/* Completely transparent. Don't change pixel. */
break;
case MAX_TRANS:
{
/* Completely opaque. Completely overwrite underlying
pixel with subtitle pixel. */
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t len = i_xlast - i_xdest;
uint8_t *p_pixel_base_x = p_pixel_base + i_xdest;
for( ; i_ytmp < i_ynext ; i_ytmp += p_pic->p->i_pitch )
{
/* This is the location that's going to get changed. */
uint8_t *p_dest = p_pixel_base_x + i_ytmp;
uint8_t i_rgb1, i_rgb2;
if (b_15bpp)
yuv2rgb555(p_source, &i_rgb1, &i_rgb2);
else
yuv2rgb565(p_source, &i_rgb1, &i_rgb2);
for ( len = i_xlast - i_xdest; len ; len--) {
*p_dest++ = i_rgb1;
*p_dest++ = i_rgb2;
}
}
break;
}
default:
{
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t len = i_xlast - i_xdest;
uint8_t i_rgb1, i_rgb2;
for( ; i_ytmp < i_ynext ; i_ytmp += p_pic->p->i_pitch )
{
/* Blend in underlying pixel subtitle pixel. */
uint8_t *p_dest = p_pixel_base + i_ytmp;
for ( len = i_xlast - i_xdest; len ; len--) {
rv16_pack_blend(p_dest, p_source, &i_rgb1, &i_rgb2,
b_15bpp, p_source->s.t, TRANS_SCALEDOWN);
*p_dest++ = i_rgb1;
*p_dest++ = i_rgb2;
}
}
break;
}
}
}
}
}
}
#undef BYTES_PER_PIXEL
#define BYTES_PER_PIXEL 4
static inline void
rv24_pack_blend(uint8_t *rgb_vout, const uint8_t *rgb_sub, uint8_t i_trans,
int a_scale_down )
{
#if 0
printf("r,g,b=(%d,%d,%d), source r,g,b=(%d,%d,%d), trans %d\n",
rgb_vout[RED_PIXEL], rgb_vout[GREEN_PIXEL], rgb_vout[BLUE_PIXEL],
rgb_sub[RED_PIXEL], rgb_sub[GREEN_PIXEL],
rgb_sub[BLUE_PIXEL], i_trans);
#endif
#ifdef WORDS_BIGENDIAN
*rgb_vout++;
#endif
avg_8bit_rgb(rgb_vout, rgb_sub, i_trans);
#if 0
printf("avg r,g,b=(%d,%d,%d)\n",
rgb_vout[RED_PIXEL], rgb_vout[GREEN_PIXEL], rgb_vout[BLUE_PIXEL] );
#endif
}
/*
RV24 format??? Is this just for X11? Or just not for Win32? Is this
the same as RV32?
a pixel is represented by 3 bytes containing a red,
blue and green sample with blue stored at the lowest address, green
next then red. One padding byte is added between pixels. Although
this may not be part of a spec, images should be stored with each
line padded to a u_int32 boundary.
*/
static void
BlendRV24( vout_thread_t *p_vout, picture_t *p_pic,
const subpicture_t *p_spu, vlc_bool_t b_crop )
{
/* Common variables */
uint8_t *p_pixel_base;
ogt_yuvt_t *p_src_start = (ogt_yuvt_t *)p_spu->p_sys->p_data;
ogt_yuvt_t *p_src_end = &p_src_start[p_spu->i_height * p_spu->i_width];
ogt_yuvt_t *p_source; /* This is the where the subtitle pixels come from */
int i_x, i_y;
int i_y_src;
/* Make sure we start on a word (4-byte) boundary. */
uint32_t i_spu_x;
/* Chroma specific */
uint32_t i_xscale; /* Amount we scale subtitle in the x direction,
multiplied by 2**ASCALE. */
uint32_t i_yscale; /* Amount we scale subtitle in the y direction.
multiplied by 2**ASCALE. */
int i_width, i_height, i_ytmp, i_ynext;
/* Crop-specific */
int32_t i_x_start, i_y_start, i_x_end, i_y_end;
struct subpicture_sys_t *p_sys = p_spu->p_sys;
int i_aspect_x, i_aspect_y;
vlc_reduce( &i_aspect_x, &i_aspect_y, p_vout->render.i_aspect,
VOUT_ASPECT_FACTOR, 0 );
i_xscale = (( p_vout->output.i_width << ASCALE ) * i_aspect_x)
/ (i_aspect_y * p_vout->render.i_width);
i_yscale = ( p_vout->output.i_height << ASCALE ) / p_vout->render.i_height;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_RENDER),
"spu: %dx%d, scaled: %dx%d, vout render: %dx%d, scale %dx%d",
p_spu->i_width, p_spu->i_height,
p_vout->output.i_width, p_vout->output.i_height,
p_vout->render.i_width, p_vout->render.i_height,
i_xscale, i_yscale
);
i_width = p_spu->i_width * i_xscale;
i_height = p_spu->i_height * i_yscale;
/* Set where we will start blending subtitle from using
the picture coordinates subtitle offsets.
*/
i_spu_x = ((p_spu->i_x * i_xscale) >> ASCALE) * BYTES_PER_PIXEL;
p_pixel_base = p_pic->p->p_pixels + i_spu_x
+ ( (p_spu->i_y * i_yscale) >> ASCALE ) * p_pic->p->i_pitch;
i_x_start = p_sys->i_x_start;
i_y_start = i_yscale * p_sys->i_y_start;
i_x_end = p_sys->i_x_end;
i_y_end = i_yscale * p_sys->i_y_end;
p_source = (ogt_yuvt_t *)p_sys->p_data;
/* Draw until we reach the bottom of the subtitle */
i_y = 0;
for( i_y_src = 0 ; i_y_src < p_spu->i_height * p_spu->i_width;
i_y_src += p_spu->i_width )
{
uint8_t *p_pixel_base_y;
i_ytmp = i_y >> ASCALE;
i_y += i_yscale;
p_pixel_base_y = p_pixel_base + (i_ytmp * p_pic->p->i_pitch);
i_x = 0;
if ( b_crop ) {
if ( i_y > i_y_end ) break;
if (i_x_start) {
i_x = i_x_start;
p_source += i_x_start;
}
}
/* Check whether we need to draw one line or more than one */
if( i_ytmp + 1 >= ( i_y >> ASCALE ) )
{
/* Draw until we reach the end of the line */
for( ; i_x < p_spu->i_width; i_x++, p_source++ )
{
if( b_crop ) {
/* FIXME: y cropping should be dealt with outside of this
loop.*/
if ( i_y < i_y_start) continue;
if ( i_x > i_x_end )
{
p_source += p_spu->i_width - i_x;
break;
}
}
if (p_source >= p_src_end) {
msg_Err( p_vout, "trying to access beyond subtitle %dx%d %d",
i_x, i_y / i_yscale, i_height);
return;
}
#ifdef TESTING_TRANSPARENCY
if (p_source->s.t == MAX_TRANS) p_source->s.t >>= 2;
#endif
switch( p_source->s.t )
{
case 0:
/* Completely transparent. Don't change pixel. */
break;
case MAX_TRANS:
{
/* Completely opaque. Completely overwrite underlying
pixel with subtitle pixel. */
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t len = i_xlast - i_xdest;
uint8_t rgb[RGB_SIZE];
/* This is the location that's going to get changed. */
uint8_t *p_dest = p_pixel_base_y + i_xdest;
yuv2rgb(p_source, rgb);
for ( len = i_xlast - i_xdest; len ; len--) {
put_rgb24_pixel(rgb, p_dest);
p_dest += BYTES_PER_PIXEL;
}
default:
{
/* Blend in underlying pixel subtitle pixel. */
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t len = i_xlast - i_xdest * BYTES_PER_PIXEL;
/* To be able to scale correctly for full
opaqueness, we add 1 to the transparency.
This means transparency value 0 won't be
completely transparent which is not correct.
But that's handled in a special case above
anyway. */
/* This is the location that's going to get changed. */
uint8_t *p_dest = p_pixel_base_y + i_xdest;
uint8_t rgb[RGB_SIZE];
yuv2rgb(p_source, rgb);
for ( len = i_xlast - i_xdest; len ; len--) {
rv24_pack_blend(p_dest, rgb, p_source->s.t,
TRANS_SCALEDOWN);
p_dest += BYTES_PER_PIXEL;
}
break;
}
}
}
}
}
else
{
i_ynext = p_pic->p->i_pitch * i_y >> ASCALE;
/* Draw until we reach the end of the line */
for( ; i_x < p_spu->i_width; i_x++, p_source++ )
{
if( b_crop ) {
/* FIXME: y cropping should be dealt with outside of this
loop.*/
if ( i_y < i_y_start) continue;
if ( i_x > i_x_end )
{
p_source += p_spu->i_width - i_x;
break;
}
}
if (p_source >= p_src_end) {
msg_Err( p_vout, "trying to access beyond subtitle %dx%d %d",
i_x, i_y / i_yscale, i_height);
return;
}
switch( p_source->s.t )
{
case 0:
/* Completely transparent. Don't change pixel. */
break;
case MAX_TRANS:
{
/* Completely opaque. Completely overwrite underlying
pixel with subtitle pixel. */
/* This is the location that's going to get changed. */
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t len = i_xlast - i_xdest;
uint8_t rgb[RGB_SIZE];
yuv2rgb(p_source, rgb);
for( ; i_ytmp < i_ynext ; i_ytmp += p_pic->p->i_pitch )
{
/* Completely opaque. Completely overwrite underlying
pixel with subtitle pixel. */
/* This is the location that's going to get changed. */
uint8_t *p_dest = p_pixel_base + i_ytmp + i_xdest;
for ( len = i_xlast - i_xdest; len ; len--) {
put_rgb24_pixel(rgb, p_dest);
p_dest += BYTES_PER_PIXEL;
}
}
break;
}
default:
{
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t len = i_xlast - i_xdest;
uint8_t rgb[RGB_SIZE];
yuv2rgb(p_source, rgb);
for( ; i_ytmp < i_ynext ; i_ytmp += p_pic->p->i_pitch )
{
/* Blend in underlying pixel subtitle pixel. */
/* This is the location that's going to get changed. */
uint8_t *p_dest = p_pixel_base + i_ytmp + i_xdest;
/* To be able to scale correctly for full opaqueness, we
add 1 to the alpha. This means alpha value 0 won't
be completely transparent and is not correct, but
that's handled in a special case above anyway. */
for ( len = i_xlast - i_xdest; len ; len--) {
rv24_pack_blend(p_dest, rgb, p_source->s.t,
TRANS_SCALEDOWN);
p_dest += BYTES_PER_PIXEL;
}
}
break;
}
}
}
}
}
}
#undef BYTES_PER_PIXEL
#define BYTES_PER_PIXEL 4
/*
RV32 format??? Is this just for X11? Or just not for Win32? Is this
the same as RV24?
RV32 format: a pixel is represented by 4 bytes containing a red,
blue and green sample with blue stored at the lowest address, green
next then red. One padding byte is added between pixels. Although
this may not be part of a spec, images should be stored with each
line padded to a u_int32 boundary.
*/
static void
BlendRV32( vout_thread_t *p_vout, picture_t *p_pic,
const subpicture_t *p_spu, vlc_bool_t b_crop )
{
/* Common variables */
uint8_t *p_pixel_base;
ogt_yuvt_t *p_src_start = (ogt_yuvt_t *)p_spu->p_sys->p_data;
ogt_yuvt_t *p_src_end = &p_src_start[p_spu->i_height * p_spu->i_width];
ogt_yuvt_t *p_source; /* This is the where the subtitle pixels come from */
int i_x, i_y;
int i_y_src;
/* Make sure we start on a word (4-byte) boundary. */
uint32_t i_spu_x;
/* Chroma specific */
uint32_t i_xscale; /* Amount we scale subtitle in the x direction,
multiplied by 2**ASCALE. */
uint32_t i_yscale; /* Amount we scale subtitle in the y direction.
multiplied by 2**ASCALE. */
int i_width, i_height, i_ytmp, i_ynext;
/* Crop-specific */
int32_t i_x_start, i_y_start, i_x_end, i_y_end;
struct subpicture_sys_t *p_sys = p_spu->p_sys;
int i_aspect_x, i_aspect_y;
vlc_reduce( &i_aspect_x, &i_aspect_y, p_vout->render.i_aspect,
VOUT_ASPECT_FACTOR, 0 );
i_xscale = (( p_vout->output.i_width << ASCALE ) * i_aspect_x)
/ (i_aspect_y * p_vout->render.i_width);
i_yscale = ( p_vout->output.i_height << ASCALE ) / p_vout->render.i_height;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_RENDER),
"spu: %dx%d, scaled: %dx%d, vout render: %dx%d, scale %dx%d",
p_spu->i_width, p_spu->i_height,
p_vout->output.i_width, p_vout->output.i_height,
p_vout->render.i_width, p_vout->render.i_height,
i_xscale, i_yscale
);
i_width = p_spu->i_width * i_xscale;
i_height = p_spu->i_height * i_yscale;
/* Set where we will start blending subtitle from using
the picture coordinates subtitle offsets.
*/
i_spu_x = ((p_spu->i_x * i_xscale) >> ASCALE) * BYTES_PER_PIXEL;
p_pixel_base = p_pic->p->p_pixels + i_spu_x
+ ( (p_spu->i_y * i_yscale) >> ASCALE ) * p_pic->p->i_pitch;
i_x_start = p_sys->i_x_start;
i_y_start = i_yscale * p_sys->i_y_start;
i_x_end = p_sys->i_x_end;
i_y_end = i_yscale * p_sys->i_y_end;
p_source = (ogt_yuvt_t *)p_sys->p_data;
/* Draw until we reach the bottom of the subtitle */
i_y = 0;
for( i_y_src = 0 ; i_y_src < p_spu->i_height * p_spu->i_width;
i_y_src += p_spu->i_width )
{
uint8_t *p_pixel_base_y;
i_ytmp = i_y >> ASCALE;
i_y += i_yscale;
p_pixel_base_y = p_pixel_base + (i_ytmp * p_pic->p->i_pitch);
i_x = 0;
if ( b_crop ) {
if ( i_y > i_y_end ) break;
if (i_x_start) {
i_x = i_x_start;
p_source += i_x_start;
}
}
/* Check whether we need to draw one line or more than one */
if( i_ytmp + 1 >= ( i_y >> ASCALE ) )
{
/* Draw until we reach the end of the line */
for( ; i_x < p_spu->i_width; i_x++, p_source++ )
{
if( b_crop ) {
/* FIXME: y cropping should be dealt with outside of this
loop.*/
if ( i_y < i_y_start) continue;
if ( i_x > i_x_end )
{
p_source += p_spu->i_width - i_x;
break;
}
}
if (p_source >= p_src_end) {
msg_Err( p_vout, "trying to access beyond subtitle %dx%d %d",
i_x, i_y / i_yscale, i_height);
return;
}
switch( p_source->s.t )
{
case 0:
/* Completely transparent. Don't change pixel. */
break;
default:
case MAX_TRANS:
{
/* Completely opaque. Completely overwrite underlying
pixel with subtitle pixel. */
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t len = i_xlast - i_xdest;
uint8_t rgb[RGB_SIZE];
/* This is the location that's going to get changed. */
uint8_t *p_dest = p_pixel_base_y + i_xdest;
yuv2rgb(p_source, rgb);
for ( len = i_xlast - i_xdest; len ; len--) {
*p_dest++ = rgb[BLUE_PIXEL];
*p_dest++ = rgb[GREEN_PIXEL];
*p_dest++ = rgb[RED_PIXEL];
*p_dest++;
}
#ifdef TRANSPARENCY_FINISHED
default:
{
/* Blend in underlying pixel subtitle pixel. */
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t len = i_xlast - i_xdest;
/* To be able to scale correctly for full opaqueness, we
add 1 to the alpha. This means alpha value 0 won't
be completely transparent and is not correct, but
that's handled in a special case above anyway. */
uint8_t i_destalpha = MAX_TRANS - p_source->s.t;
uint8_t rgb[RGB_SIZE];
/* This is the location that's going to get changed. */
uint8_t *p_dest = p_pixel_base_y + i_xdest;
yuv2rgb(p_source, rgb);
rv32_pack_blend(p_dest, rgb, dest_alpha,
TRANS_SCALEDOWN);
for ( len = i_xlast - i_xdest; len ; len--) {
*p_dest++ = rgb[BLUE_PIXEL];
*p_dest++ = rgb[GREEN_PIXEL];
*p_dest++ = rgb[RED_PIXEL];
*p_dest++;
}
break;
}
#endif /*TRANSPARENCY_FINISHED*/
}
}
}
}
else
{
i_ynext = p_pic->p->i_pitch * i_y >> ASCALE;
/* Draw until we reach the end of the line */
for( ; i_x < p_spu->i_width; i_x++, p_source++ )
{
if( b_crop ) {
/* FIXME: y cropping should be dealt with outside of this
loop.*/
if ( i_y < i_y_start) continue;
if ( i_x > i_x_end )
{
p_source += p_spu->i_width - i_x;
break;
}
}
if (p_source >= p_src_end) {
msg_Err( p_vout, "Trying to access beyond subtitle %dx%d %d",
i_x, i_y / i_yscale, i_height);
return;
}
switch( p_source->s.t )
{
case 0:
/* Completely transparent. Don't change pixel. */
break;
default:
case MAX_TRANS:
{
/* Completely opaque. Completely overwrite underlying
pixel with subtitle pixel. */
/* This is the location that's going to get changed. */
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t len = i_xlast - i_xdest;
uint8_t rgb[RGB_SIZE];
yuv2rgb(p_source, rgb);
for( ; i_ytmp < i_ynext ; i_ytmp += p_pic->p->i_pitch )
{
/* Completely opaque. Completely overwrite underlying
pixel with subtitle pixel. */
/* This is the location that's going to get changed. */
uint8_t *p_dest = p_pixel_base + i_ytmp + i_xdest;
for ( len = i_xlast - i_xdest; len ; len--) {
put_rgb24_pixel(rgb, p_dest);
}
}
break;
}
#ifdef TRANSPARENCY_FINISHED
default:
{
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t len = i_xlast - i_xdest;
uint8_t rgb[RGB_SIZE];
yuv2rgb(p_source, rgb);
for( ; i_ytmp < i_ynext ; y_ytmp += p_pic->p->i_pitch )
{
/* Blend in underlying pixel subtitle pixel. */
/* This is the location that's going to get changed. */
uint8_t *p_dest = p_pixel_base + i_ytmp + i_xdest;
/* To be able to scale correctly for full opaqueness, we
add 1 to the alpha. This means alpha value 0 won't
be completely transparent and is not correct, but
that's handled in a special case above anyway. */
uint8_t i_destalpha = MAX_TRANS - p_source->s.t;
rv32_pack_blend(p_dest, rgb, dest_alpha,
TRANS_SCALEDOWN);
}
break;
#endif /*TRANSPARENCY_FINISHED*/
}
}
}
}
}
/* 4-entry array of colormap indices */
static uint8_t cmap[NUM_SUBTITLE_COLORS];
/* Actual RGB values for above; this is used in blending.*/
static uint8_t cmap_rgb[NUM_SUBTITLE_COLORS][RGB_SIZE];
/*
Return the colormap index for the average of i_pixel and a subtitle
pixel whose subtitle palette entry is i_cmap.
*/
static inline cmap_t
avg_rgb2(const vout_thread_t *p_vout, uint8_t i_pixel, uint8_t i_trans,
int i_cmap, mtime_t i_pts)
{
uint8_t rgb_vout[RGB_SIZE];
/* Cache the average of a vout colormap entry and a subtitle palette
entry. There are not too many of these 256*4 = 1024.
*/
static cmap_t avg_cache[CMAP_RGB2_SIZE][NUM_SUBTITLE_COLORS];
/* subtitle palettes might change between two subtitles. i_last_pts
will be used to figure out if the subtitle has changed, and
we have to invalidate the cache. */
static mtime_t i_last_pts = -1;
if (i_pts != i_last_pts)
{
/* Hack: We rely on the fact that INVALID_CMAP_ENTRY is repeated
0xFF.
*/
memset(avg_cache, 0xFF, sizeof(avg_cache));
i_last_pts = i_pts;
}
if ( avg_cache[i_pixel][i_cmap] != INVALID_CMAP_ENTRY )
return avg_cache[i_pixel][i_cmap];
if ( !query_color(p_vout, i_pixel, rgb_vout) ) return INVALID_CMAP_ENTRY;
avg_8bit_rgb(rgb_vout, cmap_rgb[i_cmap], i_trans);
#if 0
{
uint8_t rgb_approx[RGB_SIZE];
avg_cache[i_pixel][i_cmap] =
find_cmap_rgb8_nearest(p_vout, rgb_vout, rgb_approx);
printf(
"cmap old %0x avg approx 0%x sub: %d sub=(%0x, %0x, %0x) "
"approx=(%0x, %0x, %0x) avg vout=(%0x, %0x, %0x)\n",
i_pixel, avg_cache[i_pixel][i_cmap], i_cmap,
cmap_rgb[i_cmap][RED_PIXEL], cmap_rgb[i_cmap][GREEN_PIXEL],
cmap_rgb[i_cmap][BLUE_PIXEL],
rgb_approx[RED_PIXEL], rgb_approx[GREEN_PIXEL], rgb_approx[BLUE_PIXEL],
rgb_vout[RED_PIXEL], rgb_vout[GREEN_PIXEL], rgb_vout[BLUE_PIXEL]);
}
#else
avg_cache[i_pixel][i_cmap] =
find_cmap_rgb8_nearest(p_vout, rgb_vout, NULL);
#endif
return avg_cache[i_pixel][i_cmap];
}
#undef BYTES_PER_PIXEL
#define BYTES_PER_PIXEL 1
static void
BlendRGB2( vout_thread_t *p_vout, picture_t *p_pic,
const subpicture_t *p_spu, vlc_bool_t b_crop )
{
/* Common variables */
uint8_t *p_pixel_base;
uint8_t *p_src_start = (uint8_t *)p_spu->p_sys->p_data;
uint8_t *p_src_end = &p_src_start[p_spu->i_height * p_spu->i_width];
uint8_t *p_source; /* This is the where the subtitle pixels come from */
int i;
int i_x, i_y;
int i_y_src;
/* Chroma specific */
uint32_t i_xscale; /* Amount we scale subtitle in the x direction,
multiplied by 2**ASCALE. */
uint32_t i_yscale; /* Amount we scale subtitle in the y direction.
multiplied by 2**ASCALE. */
int i_width, i_height, i_ytmp;
/* Crop-specific */
int i_x_start, i_y_start, i_x_end, i_y_end;
struct subpicture_sys_t *p_sys = p_spu->p_sys;
int i_aspect_x, i_aspect_y;
vlc_reduce( &i_aspect_x, &i_aspect_y, p_vout->render.i_aspect,
VOUT_ASPECT_FACTOR, 0 );
i_xscale = (( p_vout->output.i_width << ASCALE ) * i_aspect_x)
/ (i_aspect_y * p_vout->render.i_width);
i_yscale = ( p_vout->output.i_height << ASCALE ) / p_vout->render.i_height;
dbg_print( (DECODE_DBG_CALL|DECODE_DBG_RENDER),
"spu: %dx%d, scaled: %dx%d, vout render: %dx%d, scale %dx%d",
p_spu->i_width, p_spu->i_height,
p_vout->output.i_width, p_vout->output.i_height,
p_vout->render.i_width, p_vout->render.i_height,
i_xscale, i_yscale
);
i_width = p_spu->i_width * i_xscale;
i_height = p_spu->i_height * i_yscale;
/** FIXME: do once per subtitle in subtitle processing, not here
each time we render. */
/* Find a corresponding colormap entries for our palette entries. */
for( i = 0; i < NUM_SUBTITLE_COLORS; i++ )
{
if ( p_sys->p_palette[i].s.t != 0 ) {
uint8_t rgb[RGB_SIZE];
yuv2rgb(&(p_sys->p_palette[i]), rgb);
cmap[i] =
find_cmap_rgb8_nearest(p_vout, rgb, cmap_rgb[i]);
dbg_print( (DECODE_DBG_RENDER),
"palette %d RGB=(%0x, %0x, %0x)\n", i,
rgb[RED_PIXEL], rgb[GREEN_PIXEL], rgb[BLUE_PIXEL]);
}
}
/* Set where we will start blending subtitle from using
the picture coordinates subtitle offsets
*/
p_pixel_base = p_pic->p->p_pixels
+ ( (p_spu->i_x * i_xscale) >> ASCALE ) * BYTES_PER_PIXEL
+ ( (p_spu->i_y * i_yscale) >> ASCALE ) * p_pic->p->i_pitch;
i_x_start = p_sys->i_x_start;
i_y_start = i_yscale * p_sys->i_y_start;
i_x_end = p_sys->i_x_end;
i_y_end = i_yscale * p_sys->i_y_end;
p_source = (uint8_t *)p_sys->p_data;
/* Draw until we reach the bottom of the subtitle */
i_y = 0;
for( i_y_src = 0 ; i_y_src < p_spu->i_height * p_spu->i_width;
i_y_src += p_spu->i_width )
{
uint8_t *p_pixel_base_y;
i_ytmp = i_y >> ASCALE;
i_y += i_yscale;
p_pixel_base_y = p_pixel_base + (i_ytmp * p_pic->p->i_pitch);
i_x = 0;
if ( b_crop ) {
if ( i_y > i_y_end ) break;
if (i_x_start) {
i_x = i_x_start;
p_source += i_x_start;
}
}
/* Check whether we need to draw one line or more than one */
if( i_ytmp + 1 >= ( i_y >> ASCALE ) )
{
/* Draw until we reach the end of the line */
for( ; i_x < p_spu->i_width; i_x ++, p_source++ )
{
ogt_yuvt_t p_yuvt;
if( b_crop ) {
/* FIXME: y cropping should be dealt with outside of this
loop.*/
if ( i_y < i_y_start) continue;
if ( i_x > i_x_end )
{
p_source += p_spu->i_width - i_x;
break;
}
}
if (p_source >= p_src_end) {
msg_Err( p_vout, "trying to access beyond subtitle %dx%d %d",
i_x, i_y / i_yscale, i_height);
return;
}
p_yuvt = p_sys->p_palette[*p_source & 0x3];
#ifdef TESTING_TRANSPARENCY
if (p_yuvt.s.t == MAX_TRANS) p_yuvt.s.t >>= 1;
#endif
switch ( p_yuvt.s.t )
{
case 0:
/* Completely transparent. Don't change pixel. */
#if 0
printf(" "); /*++++*/
#endif
break;
case MAX_TRANS:
{
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
/* This is the pixel that's going to change;*/
uint8_t *p_dest = p_pixel_base_y + i_xdest;
memset( p_dest, cmap[*p_source & 0x3], i_xlast - i_xdest );
#if 0
printf("%1d", *p_source); /*++++*/
#endif
break;
}
default:
{
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
/* This is the pixel that's going to change;*/
uint8_t *p_pixel = p_pixel_base_y + i_xdest;
uint32_t len = i_xlast - i_xdest;
#if FULL_TRANSPARENCY
/* This is what should be done, but it may be too
slow. */
for ( len = i_xlast - i_xdest -1; len >= 0; len-- )
{
cmap_t i_cmap = avg_rgb2(p_vout, *p_pixel, p_yuvt.s.t,
*p_source, p_sys->i_pts);
if (i_cmap != INVALID_CMAP_ENTRY)
*p_pixel= (uint8_t) i_cmap;
p_pixel++;
}
#else
cmap_t i_cmap = avg_rgb2(p_vout, *p_pixel, p_yuvt.s.t,
*p_source, p_sys->i_pts);
if (i_cmap != INVALID_CMAP_ENTRY)
memset(p_pixel, i_cmap, len);
#endif
#if 0
printf("%1d", *p_source); /*++++*/
#endif
}
}
}
#if 0
printf("\n"); /*++++*/
#endif
} else {
/* Have to scale over many lines. */
int i_yreal = p_pic->p->i_pitch * i_ytmp;
int i_ynext = p_pic->p->i_pitch * i_y >> ASCALE;
/* Draw until we reach the end of the line */
for( ; i_x < p_spu->i_width; i_x ++, p_source++ )
{
ogt_yuvt_t p_yuvt = p_sys->p_palette[*p_source & 0x3];
if( b_crop ) {
/* FIXME: y cropping should be dealt with outside of this
loop.*/
if ( i_y < i_y_start) continue;
if ( i_x > i_x_end )
{
p_source += p_spu->i_width - i_x;
break;
}
}
if (p_source >= p_src_end) {
msg_Err( p_vout, "trying to access beyond subtitle %dx%d %d",
i_x, i_y / i_yscale, i_height);
return;
}
#ifdef TESTING_TRANSPARENCY
if (p_yuvt.s.t == MAX_TRANS) p_yuvt.s.t >>= 1;
#endif
switch ( p_yuvt.s.t )
{
case 0:
/* Completely transparent. Don't change pixel. */
#if 0
printf(" "); /*++++*/
#endif
break;
case MAX_TRANS:
{
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t len = i_xlast - i_xdest;
#if 0
printf("%1d", *p_source); /*++++*/
#endif
for( i_ytmp = i_yreal ; i_ytmp < i_ynext ;
i_ytmp += p_pic->p->i_pitch ) {
uint8_t *p_dest = p_pixel_base + i_ytmp + i_xdest;
memset( p_dest, cmap[*p_source & 0x3], len );
}
break;
}
default:
{
uint32_t i_xdest = ( ((i_x*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
uint32_t i_xlast = ( (((i_x+1)*i_xscale) >> ASCALE)
* BYTES_PER_PIXEL );
int32_t len = i_xlast - i_xdest;
#if 0
printf("%1d", *p_source); /*++++*/
#endif
for( i_ytmp = i_yreal ; i_ytmp < i_ynext ;
i_ytmp += p_pic->p->i_pitch ) {
/* This is the pixel that's going to change;*/
uint8_t *p_pixel = p_pixel_base + i_ytmp + i_xdest;
#if FULL_TRANSPARENCY
/* This is what should be done, but it may be too
slow. */
for ( len = i_xlast - i_xdest -1; len >= 0; len-- )
{
cmap_t i_cmap = avg_rgb2(p_vout, *p_pixel,
p_yuvt.s.t, *p_source,
p_sys->i_pts);
if (i_cmap != INVALID_CMAP_ENTRY)
*p_pixel= (uint8_t) i_cmap;
p_pixel++;
}
#else
cmap_t i_cmap = avg_rgb2(p_vout, *p_pixel, p_yuvt.s.t,
*p_source, p_sys->i_pts);
if (i_cmap != INVALID_CMAP_ENTRY)
memset(p_pixel, i_cmap, len);
#endif
}
}
}
}
}
}
}
/*
* Local variables:
* c-file-style: "gnu"
* tab-width: 8
* indent-tabs-mode: nil
* End:
*/
/*****************************************************************************
* render.h : Common SVCD and CVD rendering routine(s).
*****************************************************************************
* Copyright (C) 2003 VideoLAN
* $Id: render.h,v 1.3 2004/01/21 04:45:47 rocky Exp $
*
* Author: Rocky Bernstein
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Prototypes
*****************************************************************************/
void VCDSubBlend ( vout_thread_t *, picture_t *, const subpicture_t * );
/*****************************************************************************
* subtitle.h : Common SVCD and CVD subtitles header
*****************************************************************************
* Copyright (C) 2003,2004 VideoLAN
* $Id$
*
* Author: Rocky Bernstein
* based on code from:
* Julio Sanchez Fernandez (http://subhandler.sourceforge.net)
* Sam Hocevar <sam@zoy.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
#include "pixmap.h"
#define DECODE_DBG_EXT 1 /* Calls from external routines */
#define DECODE_DBG_CALL 2 /* all calls */
#define DECODE_DBG_PACKET 4 /* packet assembly info */
#define DECODE_DBG_IMAGE 8 /* image bitmaps */
#define DECODE_DBG_TRANSFORM 16 /* bitmap transformations */
#define DECODE_DBG_RENDER 32 /* rendering information */
#define DECODE_DBG_PNG 64 /* Extract subtitles to PNG files. */
#define DECODE_DBG_INFO 128
#define DEBUG_TEXT N_( \
"If nonzero, this gives additional debug information." \
)
#define DEBUG_LONGTEXT N_( \
"This integer when viewed in binary is a debugging mask\n" \
"external call 1\n" \
"all calls 2\n" \
"packet assembly info 4\n" \
"image bitmaps 8\n" \
"image transformations 16\n" \
"rendering information 32\n" \
"extract subtitles 64\n" \
"misc info 128\n" )
#define SUB_ASPECT_RATIO_TEXT N_("Subtitle aspect-ratio correction")
#define SUB_ASPECT_RATIO_LONGTEXT N_( \
"Use this to force the subtitle aspect ratio. If you give a null string " \
"the right value will be determined automatically. Usually this is what " \
"you want. For OGT and CVD subtitles this undoes the effect " \
"of the underlying video scaling. And using a value of 1 will cause " \
"no correction; subtitles will be scaled with the same aspect " \
"ratio as as the underlying video (which not correct for OGT or " \
"CVD subtitles). You can also force another ratio by giving a pair of " \
"integers x:y where y should between x and twice x. For example 4:3, or " \
"16:9. Alternatively, you can give a float value expressing pixel " \
"squareness. For example 1.25 or 1.3333 which mean the same thing as " \
"4:3 and 16:9 respectively." \
)
#define DURATION_SCALE_TEXT N_("Factor to increase subtitle display interval")
#define DURATION_SCALE_LONGTEXT N_( \
"If you find you need extra time for reading subtitles, " \
"you can set this higher and it will multiply the display " \
"time by that amount. Use 0 to mean until the next " \
"subtitle.")
#define HORIZONTAL_CORRECT \
N_("Add this to starting horizontal position of subtitle.")
#define HORIZONTAL_CORRECT_LONGTEXT N_( \
"If you need to adjust the subtitle starting position horizontally, " \
"set this. Negative values shift left and positive values right. 0 would " \
"be no deviation from where the position specified in the subtitle." \
)
#define VERTICAL_CORRECT \
N_("Add this to starting vertical position of subtitle.")
#define VERTICAL_CORRECT_LONGTEXT N_( \
"If you need to adjust the subtitle starting position vertically, " \
"set this. Negative values shift up, positive values down. 0 would " \
"be no deviation from where the position specified in the subtitle." \
)
#define DECODE_DEBUG 1
#if DECODE_DEBUG
#define dbg_print(mask, s, args...) \
if (p_sys && p_sys->i_debug & mask) \
msg_Dbg(p_dec, "%s: "s, __func__ , ##args)
#else
#define dbg_print(mask, s, args...)
#endif
#define LOG_ERR(args...) msg_Err( p_input, args )
#define LOG_WARN(args...) msg_Warn( p_input, args )
#define GETINT16(p) ( (p[0] << 8) + p[1] ) ; p +=2;
#define GETINT32(p) ( (p[0] << 24) + (p[1] << 16) + \
(p[2] << 8) + (p[3]) ) ; p += 4;
/* The number of color palette entries allowed in a subtitle. */
#define NUM_SUBTITLE_COLORS 4
typedef enum {
SUBTITLE_BLOCK_EMPTY,
SUBTITLE_BLOCK_PARTIAL,
SUBTITLE_BLOCK_COMPLETE
} packet_state_t;
/* The byte storage used by one pixel */
#define PIXEL_SIZE 4
/* Size in bytes of YUV portion above. */
#define YUV_SIZE 3
/* Transparency plane. NOTE: see vlc_video.h for V_PLANE */
#define T_PLANE V_PLANE+1
struct decoder_sys_t
{
int i_debug; /* debugging mask */
mtime_t i_pts; /* Start PTS of subtitle block */
int i_spu;
packet_state_t state; /* data-gathering state for this subtitle */
uint16_t i_image; /* image number in the subtitle stream; 0 is the
first one. */
uint8_t i_packet;/* packet number for above image number; 0 is the
first one. */
block_t *p_block;/* Bytes of the packet. */
uint8_t buffer[65536 + 20 ]; /* we will never overflow more than 11
bytes if I'm right */
int b_packetizer;
int i_spu_size; /* goal for subtitle_data_pos while gathering,
size of used subtitle_data later */
vout_thread_t *p_vout;
int i_subpic_channel; /* Subpicture channel in which subtitles will
be written */
/* FIXME: Remove this? */
uint8_t *subtitle_data; /* buffer used to accumulate data from
successive packets in the same subtitle */
int subtitle_data_size; /* size of the allocated subtitle_data */
/* Move into subpicture_sys_t? */
uint16_t i_image_offset; /* offset from subtitle_data to compressed
image data */
int i_image_length; /* size of the compressed image data */
int first_field_offset; /* offset of even raster lines. Used
only for CVD. */
int second_field_offset; /* offset of odd raster lines */
int metadata_offset; /* offset to data describing the image */
int metadata_length; /* length of metadata */
int subtitle_data_pos; /* where to write next chunk */
mtime_t i_duration; /* how long to display the image, 0 stands
for "until next subtitle" */
uint16_t i_x_start, i_y_start; /* position of top leftmost pixel of
image when displayed */
uint16_t i_width, i_height; /* dimensions in pixels of image */
ogt_yuvt_t p_palette[NUM_SUBTITLE_COLORS]; /* Palette of colors used
in subtitle */
ogt_yuvt_t p_palette_highlight[NUM_SUBTITLE_COLORS]; /* Only used
for CVD */
uint8_t i_options;
uint8_t i_options2;
uint8_t i_cmd;
uint32_t i_cmd_arg;
};
struct subpicture_sys_t
{
int i_debug; /* debugging mask */
mtime_t i_pts; /* presentation timestamp */
uint8_t *p_data; /* Image data one byte T, Y, U, V */
/* Link to our input */
vlc_object_t * p_input;
/* Cropping properties */
vlc_mutex_t lock;
vlc_bool_t b_crop;
unsigned int i_x_start, i_y_start, i_x_end, i_y_end;
/* This is only used for color palette Chromas like RGB2. */
ogt_yuvt_t p_palette[NUM_SUBTITLE_COLORS]; /* Palette of colors used
in subtitle */
};
/*****************************************************************************
* Dump an Image to a Portable Network Graphics (PNG) file
****************************************************************************
Copyright (C) 2004 VideoLAN
Author: Rocky Bernstein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
#include "config.h"
#ifdef HAVE_LIBPNG
#include <stdio.h>
#include <stdlib.h>
#include "write_png.h"
#include <setjmp.h>
typedef void (*snapshot_messenger_t)(char *message);
#define _(x) x
/*
* Error functions for use as callbacks by the png libraries
*/
void error_msg(char *message)
{
printf("error: %s\n", message);
}
void warning_msg(char *message)
{
printf("warning: %s\n", message);
}
static snapshot_messenger_t error_msg_cb = error_msg;
static snapshot_messenger_t warning_msg_cb = warning_msg;
static void
user_error_fn(png_structp png_ptr, png_const_charp error_msg)
{
if(error_msg_cb) {
char uerror[4096];
memset(&uerror, 0, sizeof(uerror));
sprintf(uerror, _("Error: %s\n"), error_msg);
error_msg_cb(uerror);
}
}
static void
user_warning_fn(png_structp png_ptr, png_const_charp warning_msg)
{
if(error_msg_cb) {
char uerror[4096];
memset(&uerror, 0, sizeof(uerror));
sprintf(uerror, _("Error: %s\n"), warning_msg);
warning_msg_cb(uerror);
}
}
/*
Dump an image to a Portable Network Graphics (PNG) file. File_name
is where the file goes, i_height and i_width are the height and
width in pixels of the image. The data for the image is stored as a
linear array of one byte for each of red, green, and blue
components of an RGB pixel. Thus row[i] will begin at rgb_image +
i*(i_width*3) and the blue pixel at image[i][0] would be rgb_image +
i*(i_width*3) + 1.
*/
void
write_png(const char *file_name, png_uint_32 i_height, png_uint_32 i_width,
void *rgb_image, /*in*/ png_text *text_ptr, int i_text_count )
{
FILE *fp;
png_structp png_ptr;
png_infop info_ptr;
png_color_8 sig_bit;
png_bytep *row_pointers;
unsigned int i,j;
/* open the file */
fp = fopen(file_name, "wb");
if (fp == NULL)
return;
/* Create and initialize the png_struct with the desired error handler
* functions. If you want to use the default stderr and longjump method,
* you can supply NULL for the last three parameters. We also check that
* the library version is compatible with the one used at compile time,
* in case we are using dynamically linked libraries. REQUIRED.
*/
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp) NULL,
user_error_fn, user_warning_fn);
if (png_ptr == NULL)
{
fclose(fp);
return;
}
/* Allocate/initialize the image information data. REQUIRED */
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL)
{
fclose(fp);
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
return;
}
/* Set error handling. REQUIRED if you aren't supplying your own
* error handling functions in the png_create_write_struct() call.
*/
if (setjmp(png_ptr->jmpbuf))
{
/* If we get here, we had a problem writing the file */
fclose(fp);
png_destroy_write_struct(&png_ptr, (png_infopp) &info_ptr);
return;
}
/* Set up the output control using standard C streams. This
is required. */
png_init_io(png_ptr, fp);
/* Set the image information here. i_width and i_height are up to 2^31,
* bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
* the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
* PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
* or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
* PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
* currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
*/
png_set_IHDR(png_ptr, info_ptr, i_width, i_height, 8, PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
PNG_FILTER_TYPE_BASE);
/* For color images: */
sig_bit.red = 8;
sig_bit.green = 8;
sig_bit.blue = 8;
if (text_ptr)
png_set_text(png_ptr, info_ptr, text_ptr, i_text_count);
/* Write the file header information. REQUIRED */
png_write_info(png_ptr, info_ptr);
/* Once we write out the header, the compression type on the text
* chunks gets changed to PNG_TEXT_COMPRESSION_NONE_WR or
* PNG_TEXT_COMPRESSION_zTXt_WR, so it doesn't get written out again
* at the end.
*/
/* Shift the pixels up to a legal bit depth and fill in
* as appropriate to correctly scale the image.
*/
png_set_shift(png_ptr, &sig_bit);
/* pack pixels into bytes */
png_set_packing(png_ptr);
row_pointers = png_malloc(png_ptr, i_height*sizeof(png_bytep *));
for (i=0, j=0; i<i_height; i++, j+=i_width*3) {
row_pointers[i] = rgb_image + j;
}
png_set_rows (png_ptr, info_ptr, row_pointers);
png_write_image(png_ptr, row_pointers);
/* You can write optional chunks like tEXt, zTXt, and tIME at the end
* as well.
*/
/* It is REQUIRED to call this to finish writing the rest of the file */
png_write_end(png_ptr, info_ptr);
/* if you allocated any text comments, free them here */
/* free image data if allocated. */
/* clean up after the write, and free any memory allocated */
png_destroy_info_struct(png_ptr, &info_ptr);
/* clean up after the write, and free any memory allocated */
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
fclose(fp);
return;
}
#ifdef STANDALONE
int
main(int argc, char **argv)
{
char image_data[3*16 * 3*16 * 3];
int i,j,k,l,m;
char r,g,b,t, or,og,ob;
or=0x00; og=0xFF; ob=0x0;
m=0;
for (i=0; i<3; i++) {
t=or; or=og; og=ob; ob=t;
for (j=0; j<16; j++) {
r=or; g=og; b=ob;
for (k=0; k<3; k++) {
for (l=0; l<16; l++) {
image_data[m++]=r;
image_data[m++]=g;
image_data[m++]=b;
}
t=r; r=g; g=b; b=t;
}
}
}
write_png("/tmp/pngtest.png", 3*16, 3*16, (void *) image_data) ;
return 0;
}
#endif /*STANDALONE*/
#endif /*HAVE_LIBPNG*/
/*****************************************************************************
* Dump an Image to a Portable Network Graphics (PNG) file
****************************************************************************
Copyright (C) 2004 VideoLAN
Author: Rocky Bernstein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
#include <png.h>
/*
Dump an image to a Portable Network Graphics (PNG) file. File_name
is where the file goes, i_height and i_width are the height and
width in pixels of the image. The data for the image is stored as a
linear array RGB pixel entries: one byte for each of red, green,
and blue component. Thus row[i] will begin at rgb_image +
i*(i_width*3) and the blue pixel at image[i][0] would be rgb_image
+ i*(i_width*3) + 1.
text_ptr contains comments that can be written to the image. It can
be null. i_text_count is the number of entries in text_ptr.
*/
void write_png(const char *file_name, png_uint_32 i_height,
png_uint_32 i_width, void *rgb_image,
/*in*/ png_text *text_ptr, int i_text_count );
/*****************************************************************************
* svcdsub.c : Overlay Graphics Text (SVCD subtitles) decoder
*****************************************************************************
* Copyright (C) 2003, 2004 VideoLAN
* $Id$
*
* Authors: Rocky Bernstein
* Gildas Bazin <gbazin@videolan.org>
* Julio Sanchez Fernandez (http://subhandler.sourceforge.net)
* Laurent Aimar <fenrir@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
#define DEBUG_SVCD 1
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/decoder.h>
#include "vlc_bits.h"
/*****************************************************************************
* Module descriptor.
*****************************************************************************/
static int DecoderOpen ( vlc_object_t * );
static int PacketizerOpen( vlc_object_t * );
static void DecoderClose ( vlc_object_t * );
vlc_module_begin();
set_description( _("Philips OGT (SVCD subtitle) decoder") );
set_capability( "decoder", 50 );
set_callbacks( DecoderOpen, DecoderClose );
add_submodule();
set_description( _("Philips OGT (SVCD subtitle) packetizer") );
set_capability( "packetizer", 50 );
set_callbacks( PacketizerOpen, DecoderClose );
vlc_module_end();
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static subpicture_t *Decode( decoder_t *, block_t ** );
static block_t *Packetize ( decoder_t *, block_t ** );
static block_t *Reassemble ( decoder_t *, block_t * );
static void ParseHeader( decoder_t *, block_t * );
static subpicture_t *DecodePacket( decoder_t *, block_t * );
static void RenderImage( decoder_t *, block_t *, subpicture_region_t * );
#define GETINT16(p) ( (p[0] << 8) + p[1] ) ; p +=2;
#define GETINT32(p) ( (p[0] << 24) + (p[1] << 16) + \
(p[2] << 8) + (p[3]) ) ; p += 4;
#define SUBTITLE_BLOCK_EMPTY 0
#define SUBTITLE_BLOCK_PARTIAL 1
#define SUBTITLE_BLOCK_COMPLETE 2
struct decoder_sys_t
{
int b_packetizer;
int i_state; /* data-gathering state for this subtitle */
block_t *p_spu; /* Bytes of the packet. */
uint16_t i_image; /* image number in the subtitle stream */
uint8_t i_packet; /* packet number for above image number */
int i_spu_size; /* goal for subtitle_data_pos while gathering,
size of used subtitle_data later */
uint16_t i_image_offset; /* offset from subtitle_data to compressed
image data */
int i_image_length; /* size of the compressed image data */
int second_field_offset; /* offset of odd raster lines */
int metadata_offset; /* offset to data describing the image */
int metadata_length; /* length of metadata */
mtime_t i_duration; /* how long to display the image, 0 stands
for "until next subtitle" */
uint16_t i_x_start, i_y_start; /* position of top leftmost pixel of
image when displayed */
uint16_t i_width, i_height; /* dimensions in pixels of image */
uint8_t p_palette[4][4]; /* Palette of colors used in subtitle */
};
/*****************************************************************************
* DecoderOpen: open/initialize the svcdsub decoder.
*****************************************************************************/
static int DecoderOpen( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
decoder_sys_t *p_sys;
if( p_dec->fmt_in.i_codec != VLC_FOURCC( 'o','g','t',' ' ) )
{
return VLC_EGENERIC;
}
p_dec->p_sys = p_sys = malloc( sizeof( decoder_sys_t ) );
p_sys->b_packetizer = VLC_FALSE;
p_sys->i_image = -1;
p_sys->i_state = SUBTITLE_BLOCK_EMPTY;
p_sys->p_spu = NULL;
es_format_Init( &p_dec->fmt_out, SPU_ES, VLC_FOURCC( 'o','g','t',' ' ) );
p_dec->pf_decode_sub = Decode;
p_dec->pf_packetize = Packetize;
return VLC_SUCCESS;
}
/*****************************************************************************
* PacketizerOpen: open/initialize the svcdsub packetizer.
*****************************************************************************/
static int PacketizerOpen( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
if( DecoderOpen( p_this ) != VLC_SUCCESS ) return VLC_EGENERIC;
p_dec->p_sys->b_packetizer = VLC_TRUE;
return VLC_SUCCESS;
}
/*****************************************************************************
* DecoderClose: closes the svcdsub decoder/packetizer.
*****************************************************************************/
void DecoderClose( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
decoder_sys_t *p_sys = p_dec->p_sys;
if( p_sys->p_spu ) block_ChainRelease( p_sys->p_spu );
free( p_sys );
}
/*****************************************************************************
* Decode:
*****************************************************************************/
static subpicture_t *Decode( decoder_t *p_dec, block_t **pp_block )
{
block_t *p_block, *p_spu;
if( pp_block == NULL || *pp_block == NULL ) return NULL;
p_block = *pp_block;
*pp_block = NULL;
if( !(p_spu = Reassemble( p_dec, p_block )) ) return NULL;
/* Parse and decode */
return DecodePacket( p_dec, p_spu );
}
/*****************************************************************************
* Packetize:
*****************************************************************************/
static block_t *Packetize( decoder_t *p_dec, block_t **pp_block )
{
block_t *p_block, *p_spu;
if( pp_block == NULL || *pp_block == NULL ) return NULL;
p_block = *pp_block;
*pp_block = NULL;
if( !(p_spu = Reassemble( p_dec, p_block )) ) return NULL;
p_spu->i_dts = p_spu->i_pts;
p_spu->i_length = 0;
return p_spu;
}
/*****************************************************************************
Reassemble:
The data for single screen subtitle may come in one of many
non-contiguous packets of a stream. This routine is called when the
next packet in the stream comes in. The job of this routine is to
parse the header, if this is the beginning, and combine the packets
into one complete subtitle unit.
If everything is complete, we will return a block. Otherwise return
NULL.
The format of the beginning of the subtitle packet that is used here.
size description
-------------------------------------------
byte subtitle channel (0..7) in bits 0-3
byte subtitle packet number of this subtitle image 0-N,
if the subtitle packet is complete, the top bit of the byte is 1.
uint16 subtitle image number
*****************************************************************************/
#define SPU_HEADER_LEN 5
static block_t *Reassemble( decoder_t *p_dec, block_t *p_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t *p_buffer;
uint16_t i_expected_image;
uint8_t i_packet, i_expected_packet;
if( p_block->i_buffer < SPU_HEADER_LEN )
{
msg_Dbg( p_dec, "invalid packet header (size %d < %d)" ,
p_block->i_buffer, SPU_HEADER_LEN );
block_Release( p_block );
return NULL;
}
p_buffer = p_block->p_buffer;
if( p_sys->i_state == SUBTITLE_BLOCK_EMPTY )
{
i_expected_image = p_sys->i_image + 1;
i_expected_packet = 0;
}
else
{
i_expected_image = p_sys->i_image;
i_expected_packet = p_sys->i_packet + 1;
}
p_buffer += 2;
if( *p_buffer & 0x80 )
{
p_sys->i_state = SUBTITLE_BLOCK_COMPLETE;
i_packet = *p_buffer++ & 0x7F;
}
else
{
p_sys->i_state = SUBTITLE_BLOCK_PARTIAL;
i_packet = *p_buffer++;
}
p_sys->i_image = GETINT16(p_buffer);
if( p_sys->i_image != i_expected_image )
{
msg_Warn( p_dec, "expected subtitle image %u but found %u",
i_expected_image, p_sys->i_image );
}
if( i_packet != i_expected_packet )
{
msg_Warn( p_dec, "expected subtitle image packet %u but found %u",
i_expected_packet, i_packet );
}
p_block->p_buffer += SPU_HEADER_LEN;
p_block->i_buffer -= SPU_HEADER_LEN;
p_sys->i_packet = i_packet;
/* First packet in the subtitle block */
if( !p_sys->i_packet ) ParseHeader( p_dec, p_block );
block_ChainAppend( &p_sys->p_spu, p_block );
if( p_sys->i_state == SUBTITLE_BLOCK_COMPLETE )
{
block_t *p_spu = block_ChainGather( p_sys->p_spu );
if( p_spu->i_buffer != p_sys->i_spu_size )
{
msg_Warn( p_dec, "SPU packets size=%d should be %d",
p_spu->i_buffer, p_sys->i_spu_size );
}
msg_Dbg( p_dec, "subtitle packet complete, size=%d", p_spu->i_buffer);
p_sys->i_state = SUBTITLE_BLOCK_EMPTY;
p_sys->p_spu = 0;
return p_spu;
}
return NULL;
}
/******************************************************************************
The format is roughly as follows (everything is big-endian):
size description
-------------------------------------------
byte subtitle channel (0..7) in bits 0-3
byte subtitle packet number of this subtitle image 0-N,
if the subtitle packet is complete, the top bit of the byte is 1.
u_int16 subtitle image number
u_int16 length in bytes of the rest
byte option flags, unknown meaning except bit 3 (0x08) indicates
presence of the duration field
byte unknown
u_int32 duration in 1/90000ths of a second (optional), start time
is as indicated by the PTS in the PES header
u_int32 xpos
u_int32 ypos
u_int32 width (must be even)
u_int32 height (must be even)
byte[16] palette, 4 palette entries, each contains values for
Y, U, V and transparency, 0 standing for transparent
byte command,
cmd>>6==1 indicates shift
(cmd>>4)&3 is direction from, (0=top,1=left,2=right,3=bottom)
u_int32 shift duration in 1/90000ths of a second
u_int16 offset of odd-numbered scanlines - subtitle images are
given in interlace order
byte[] limited RLE image data in interlace order (0,2,4... 1,3,5) with
2-bits per palette number
******************************************************************************/
static void ParseHeader( decoder_t *p_dec, block_t *p_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t *p = p_block->p_buffer;
uint8_t i_options, i_options2, i_cmd, i_cmd_arg;
int i;
p_sys->i_spu_size = GETINT16(p);
i_options = *p++;
i_options2 = *p++;
if( i_options & 0x08 ) { p_sys->i_duration = GETINT32(p); }
else p_sys->i_duration = 0; /* Ephemer subtitle */
p_sys->i_duration *= 100 / 9;
p_sys->i_x_start = GETINT16(p);
p_sys->i_y_start = GETINT16(p);
p_sys->i_width = GETINT16(p);
p_sys->i_height = GETINT16(p);
for( i = 0; i < 4; i++ )
{
p_sys->p_palette[i][0] = *p++;
p_sys->p_palette[i][1] = *p++;
p_sys->p_palette[i][2] = *p++;
p_sys->p_palette[i][3] = *p++;
}
i_cmd = *p++;
/* We do not really know this, FIXME */
if( i_cmd ) {i_cmd_arg = GETINT32(p);}
/* Actually, this is measured against a different origin, so we have to
* adjust it */
p_sys->second_field_offset = GETINT16(p);
p_sys->i_image_offset = p - p_block->p_buffer;
p_sys->i_image_length = p_sys->i_spu_size - p_sys->i_image_offset;
p_sys->metadata_length = p_sys->i_image_offset;
#ifdef DEBUG_SVCD
msg_Dbg( p_dec, "x-start: %d, y-start: %d, width: %d, height %d, "
"spu size: %d, duration: %lu (d:%d p:%d)",
p_sys->i_x_start, p_sys->i_y_start,
p_sys->i_width, p_sys->i_height,
p_sys->i_spu_size, (long unsigned int) p_sys->i_duration,
p_sys->i_image_length, p_sys->i_image_offset);
for( i = 0; i < 4; i++ )
{
msg_Dbg( p_dec, "palette[%d]= T: %2x, Y: %2x, u: %2x, v: %2x", i,
p_sys->p_palette[i][3], p_sys->p_palette[i][0],
p_sys->p_palette[i][1], p_sys->p_palette[i][2] );
}
#endif
}
/*****************************************************************************
* DecodePacket: parse and decode an SPU packet
*****************************************************************************
* This function parses and decodes an SPU packet and, if valid, returns a
* subpicture.
*****************************************************************************/
static subpicture_t *DecodePacket( decoder_t *p_dec, block_t *p_data )
{
decoder_sys_t *p_sys = p_dec->p_sys;
subpicture_t *p_spu;
subpicture_region_t *p_region;
video_format_t fmt;
int i;
/* Allocate the subpicture internal data. */
p_spu = p_dec->pf_spu_buffer_new( p_dec );
if( !p_spu ) return NULL;
p_spu->i_x = p_sys->i_x_start;
p_spu->i_x = p_spu->i_x * 3 / 4; /* FIXME: use aspect ratio for x? */
p_spu->i_y = p_sys->i_y_start;
p_spu->i_start = p_data->i_pts;
p_spu->i_stop = p_data->i_pts + p_sys->i_duration;
p_spu->b_ephemer = VLC_TRUE;
/* Create new SPU region */
memset( &fmt, 0, sizeof(video_format_t) );
fmt.i_chroma = VLC_FOURCC('Y','U','V','P');
fmt.i_aspect = VOUT_ASPECT_FACTOR;
fmt.i_width = fmt.i_visible_width = p_sys->i_width;
fmt.i_height = fmt.i_visible_height = p_sys->i_height;
fmt.i_x_offset = fmt.i_y_offset = 0;
p_region = p_spu->pf_create_region( VLC_OBJECT(p_dec), &fmt );
if( !p_region )
{
msg_Err( p_dec, "cannot allocate SPU region" );
//goto error;
}
p_spu->p_region = p_region;
p_region->i_x = p_region->i_y = 0;
/* Build palette */
fmt.p_palette->i_entries = 4;
for( i = 0; i < fmt.p_palette->i_entries; i++ )
{
fmt.p_palette->palette[i][0] = p_sys->p_palette[i][0];
fmt.p_palette->palette[i][1] = p_sys->p_palette[i][1];
fmt.p_palette->palette[i][2] = p_sys->p_palette[i][2];
fmt.p_palette->palette[i][3] = p_sys->p_palette[i][3];
}
RenderImage( p_dec, p_data, p_region );
return p_spu;
}
/*****************************************************************************
* ParseImage: parse the image part of the subtitle
*****************************************************************************
This part parses the subtitle graphical data and renders it.
The image is encoded using two bits per pixel that select a palette
entry except that value 0 starts a limited run-length encoding for
color 0. When 0 is seen, the next two bits encode one less than the
number of pixels, so we can encode run lengths from 1 to 4. These get
filled with the color in palette entry 0.
The encoding of each line is padded to a whole number of bytes. The
first field is padded to an even byte length and the complete subtitle
is padded to a 4-byte multiple that always include one zero byte at
the end.
However we'll transform this so that that the RLE is expanded and
interlacing will also be removed.
*****************************************************************************/
static void RenderImage( decoder_t *p_dec, block_t *p_data,
subpicture_region_t *p_region )
{
decoder_sys_t *p_sys = p_dec->p_sys;
uint8_t *p_dest = p_region->picture.Y_PIXELS;
int i_field; /* The subtitles are interlaced */
int i_row, i_column; /* scanline row/column number */
uint8_t i_color, i_count;
bs_t bs;
bs_init( &bs, p_data->p_buffer + p_sys->i_image_offset,
p_data->i_buffer - p_sys->i_image_offset );
for( i_field = 0; i_field < 2; i_field++ )
{
for( i_row = i_field; i_row < p_sys->i_height; i_row += 2 )
{
for( i_column = 0; i_column < p_sys->i_width; i_column++ )
{
i_color = bs_read( &bs, 2 );
if( i_color == 0 && (i_count = bs_read( &bs, 2 )) )
{
i_count = __MIN( i_count, p_sys->i_width - i_column );
memset( &p_dest[i_row * p_region->picture.Y_PITCH +
i_column], 0, i_count + 1 );
i_column += i_count;
continue;
}
p_dest[i_row * p_region->picture.Y_PITCH + i_column] = i_color;
}
bs_align( &bs );
}
/* odd field */
bs_init( &bs, p_data->p_buffer + p_sys->i_image_offset +
p_sys->second_field_offset,
p_data->i_buffer - p_sys->i_image_offset -
p_sys->second_field_offset );
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment