Commit 2cdbf636 authored by Laurent Aimar's avatar Laurent Aimar

Closed captions decoder.

The rollup and paint on mode is not really good (a lot of flicker) but
I don't see how to fix it (the subtitle is updated as soon as a new
character is added).
parent 799f3e39
/*****************************************************************************
* cc608.c : CC 608/708 subtitles decoder
*****************************************************************************
* Copyright (C) 2007 Laurent Aimar
* $Id: subsdec.c 22151 2007-09-18 16:20:49Z courmisch $
*
* Authors: 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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
/* The EIA 608 decoder part has been initialy based on ccextractor (GPL)
* and rewritten */
/* TODO:
* On discontinuity reset the decoder state
* Check parity
* 708 decoding
*/
#include <vlc/vlc.h>
#include <vlc_vout.h>
#include <vlc_codec.h>
#include <vlc_input.h>
#include <vlc_osd.h>
#include <vlc_filter.h>
#include <vlc_image.h>
#include <vlc_charset.h>
#include <vlc_stream.h>
#include <vlc_xml.h>
#include <errno.h>
#include <string.h>
/*****************************************************************************
* Module descriptor.
*****************************************************************************/
static int Open ( vlc_object_t * );
static void Close( vlc_object_t * );
vlc_module_begin();
set_shortname( _("CC 608/708"));
set_description( _("Closed Captions decoder") );
set_capability( "decoder", 50 );
set_callbacks( Open, Close );
vlc_module_end();
/*****************************************************************************
* Local prototypes
*****************************************************************************/
typedef enum
{
EIA608_MODE_POPUP = 0,
EIA608_MODE_ROLLUP_2 = 1,
EIA608_MODE_ROLLUP_3 = 2,
EIA608_MODE_ROLLUP_4 = 3,
EIA608_MODE_PAINTON = 4,
EIA608_MODE_TEXT = 5
} eia608_mode_t;
typedef enum
{
EIA608_COLOR_WHITE = 0,
EIA608_COLOR_GREEN = 1,
EIA608_COLOR_BLUE = 2,
EIA608_COLOR_CYAN = 3,
EIA608_COLOR_RED = 4,
EIA608_COLOR_YELLOW = 5,
EIA608_COLOR_MAGENTA = 6,
EIA608_COLOR_USERDEFINED = 7
} eia608_color_t;
typedef enum
{
EIA608_FONT_REGULAR = 0x00,
EIA608_FONT_ITALICS = 0x01,
EIA608_FONT_UNDERLINE = 0x02,
EIA608_FONT_UNDERLINE_ITALICS = EIA608_FONT_UNDERLINE | EIA608_FONT_ITALICS
} eia608_font_t;
#define EIA608_SCREEN_ROWS 15
#define EIA608_SCREEN_COLUMNS 32
struct eia608_screen // A CC buffer
{
uint8_t characters[EIA608_SCREEN_ROWS][EIA608_SCREEN_COLUMNS+1];
eia608_color_t colors[EIA608_SCREEN_ROWS][EIA608_SCREEN_COLUMNS+1];
eia608_font_t fonts[EIA608_SCREEN_ROWS][EIA608_SCREEN_COLUMNS+1]; // Extra char at the end for a 0
int row_used[EIA608_SCREEN_ROWS]; // Any data in row?
};
typedef struct eia608_screen eia608_screen;
typedef struct
{
/* Current channel (used to reject packet without channel information) */
int i_channel;
/* */
int i_screen; /* Displayed screen */
eia608_screen screen[2];
struct
{
int i_row;
int i_column;
} cursor;
/* */
eia608_mode_t mode;
eia608_color_t color;
eia608_font_t font;
int i_row_rollup;
/* Last command pair (used to reject duplicated command) */
struct
{
uint8_t d1;
uint8_t d2;
} last;
} eia608_t;
static void Eia608Init( eia608_t * );
static vlc_bool_t Eia608Parse( eia608_t *h, int i_channel_selected, const uint8_t data[2] );
static char *Eia608Text( eia608_t *h, vlc_bool_t b_html );
static void Eia608Exit( eia608_t * );
/* It will be enough up to 63 B frames, which is far too high for
* broadcast environment */
#define CC_MAX_REORDER_SIZE (64)
struct decoder_sys_t
{
int i;
int i_block;
block_t *pp_block[CC_MAX_REORDER_SIZE];
block_t *p_current;
int i_field;
int i_channel;
eia608_t eia608;
};
static subpicture_t *Decode( decoder_t *, block_t ** );
/*****************************************************************************
* Open: probe the decoder and return score
*****************************************************************************
* Tries to launch a decoder and return score so that the interface is able
* to chose.
*****************************************************************************/
static int Open( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
decoder_sys_t *p_sys;
int i_field;
int i_channel;
switch( p_dec->fmt_in.i_codec )
{
case VLC_FOURCC('c','c','1',' '):
i_field = 0; i_channel = 1;
break;
case VLC_FOURCC('c','c','2',' '):
i_field = 0; i_channel = 2;
break;
case VLC_FOURCC('c','c','3',' '):
i_field = 1; i_channel = 1;
break;
case VLC_FOURCC('c','c','4',' '):
i_field = 1; i_channel = 2;
break;
default:
return VLC_EGENERIC;
}
p_dec->pf_decode_sub = Decode;
/* Allocate the memory needed to store the decoder's structure */
p_dec->p_sys = p_sys = malloc( sizeof( *p_sys ) );
if( p_sys == NULL )
{
msg_Err( p_dec, "out of memory" );
return VLC_ENOMEM;
}
/* init of p_sys */
memset( p_sys, 0, sizeof( *p_sys ) );
p_sys->i_block = 0;
p_sys->p_current = NULL;
p_sys->i_field = i_field;
p_sys->i_channel = i_channel;
Eia608Init( &p_sys->eia608 );
return VLC_SUCCESS;
}
/****************************************************************************
* Decode: the whole thing
****************************************************************************
*
****************************************************************************/
static void Push( decoder_t *, block_t * );
static block_t *Pop( decoder_t * );
static subpicture_t *Convert( decoder_t *, block_t * );
static subpicture_t *Decode( decoder_t *p_dec, block_t **pp_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
if( pp_block && *pp_block )
{
Push( p_dec, *pp_block );
*pp_block = NULL;
}
for( ;; )
{
block_t *p_block;
p_block = p_sys->p_current;
if( !p_block )
p_block = Pop( p_dec );
if( !p_block )
break;
subpicture_t *p_spu = Convert( p_dec, p_block );
if( p_spu )
return p_spu;
}
return NULL;
}
/*****************************************************************************
* CloseDecoder: clean up the decoder
*****************************************************************************/
static void Close( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t *)p_this;
decoder_sys_t *p_sys = p_dec->p_sys;
int i;
for( i = 0; i < p_sys->i_block; i++ )
block_Release( p_sys->pp_block[i] );
Eia608Exit( &p_sys->eia608 );
free( p_sys );
}
/*****************************************************************************
*
*****************************************************************************/
static void Push( decoder_t *p_dec, block_t *p_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
if( p_sys->i_block >= CC_MAX_REORDER_SIZE )
{
msg_Warn( p_dec, "Trashing a CC entry" );
memmove( &p_sys->pp_block[0], &p_sys->pp_block[1], sizeof(*p_sys->pp_block) * (CC_MAX_REORDER_SIZE-1) );
p_sys->i_block--;
}
p_sys->pp_block[p_sys->i_block++] = p_block;
}
static block_t *Pop( decoder_t *p_dec )
{
decoder_sys_t *p_sys = p_dec->p_sys;
block_t *p_block;
int i_index;
int i;
/* XXX Cc captions data are OUT OF ORDER (because we receive them in the bitstream
* order (ie ordered by video picture dts) instead of the display order.
* We will simulate a simple IPB buffer scheme
* and reorder with pts.
* XXX it won't work with H264 which use non out of order B picture or MMCO
*/
/* Wait for a P and output all *previous* picture by pts order (for
* hierarchical B frames) */
if( p_sys->i_block <= 1 ||
( p_sys->pp_block[p_sys->i_block-1]->i_flags & BLOCK_FLAG_TYPE_B ) )
return NULL;
p_block = p_sys->pp_block[i_index = 0];
if( p_block->i_pts > 0 )
{
for( i = 1; i < p_sys->i_block-1; i++ )
{
if( p_sys->pp_block[i]->i_pts > 0 && p_block->i_pts > 0 &&
p_sys->pp_block[i]->i_pts < p_block->i_pts )
p_block = p_sys->pp_block[i_index = i];
}
}
assert( i_index+1 < p_sys->i_block );
memmove( &p_sys->pp_block[i_index], &p_sys->pp_block[i_index+1], sizeof(*p_sys->pp_block) * ( p_sys->i_block - i_index - 1 ) );
p_sys->i_block--;
return p_block;
}
static subpicture_t *Subtitle( decoder_t *p_dec, char *psz_subtitle, char *psz_html, mtime_t i_pts )
{
//decoder_sys_t *p_sys = p_dec->p_sys;
subpicture_t *p_spu = NULL;
video_format_t fmt;
/* We cannot display a subpicture with no date */
if( i_pts == 0 )
{
msg_Warn( p_dec, "subtitle without a date" );
return NULL;
}
EnsureUTF8( psz_subtitle );
if( psz_html )
EnsureUTF8( psz_html );
/* Create the subpicture unit */
p_spu = p_dec->pf_spu_buffer_new( p_dec );
if( !p_spu )
{
msg_Warn( p_dec, "can't get spu buffer" );
free( psz_subtitle );
if( psz_html )
free( psz_html );
return NULL;
}
p_spu->b_pausable = VLC_TRUE;
/* Create a new subpicture region */
memset( &fmt, 0, sizeof(video_format_t) );
fmt.i_chroma = VLC_FOURCC('T','E','X','T');
fmt.i_aspect = 0;
fmt.i_width = fmt.i_height = 0;
fmt.i_x_offset = fmt.i_y_offset = 0;
p_spu->p_region = p_spu->pf_create_region( VLC_OBJECT(p_dec), &fmt );
if( !p_spu->p_region )
{
msg_Err( p_dec, "cannot allocate SPU region" );
free( psz_subtitle );
if( psz_html )
free( psz_html );
p_dec->pf_spu_buffer_del( p_dec, p_spu );
return NULL;
}
/* Decode and format the subpicture unit */
/* Normal text subs, easy markup */
p_spu->p_region->i_align = SUBPICTURE_ALIGN_BOTTOM;// | SUBPICTURE_ALIGN_LEFT;// | p_sys->i_align;
p_spu->i_x = 0; //p_sys->i_align ? 20 : 0;
p_spu->i_y = 10;
p_spu->p_region->psz_text = psz_subtitle;
p_spu->p_region->psz_html = psz_html;
p_spu->i_start = i_pts;
p_spu->i_stop = i_pts + 10000000; /* 10s max */
p_spu->b_ephemer = VLC_TRUE;
p_spu->b_absolute = VLC_FALSE;
return p_spu;
}
static subpicture_t *Convert( decoder_t *p_dec, block_t *p_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
const int64_t i_pts = p_block->i_pts;
vlc_bool_t b_changed = VLC_FALSE;
p_sys->p_current = p_block;
/* TODO do the real decoding here */
while( p_block && p_block->i_buffer >= 3 )
{
if( p_block->p_buffer[0] == p_sys->i_field )
b_changed |= Eia608Parse( &p_sys->eia608, p_sys->i_channel, &p_block->p_buffer[1] );
p_block->i_buffer -= 3;
p_block->p_buffer += 3;
if( p_block->i_buffer <= 0 )
{
block_Release( p_block );
p_sys->p_current = p_block = NULL;
}
}
static int64_t i_last = 0;
if( b_changed )//&& i_pts - i_last > 100*1000 )
{
char *psz_subtitle = Eia608Text( &p_sys->eia608, VLC_FALSE );
char *psz_html = NULL;//Eia608Text( &p_sys->eia608, VLC_TRUE );
i_last = i_pts;
return Subtitle( p_dec, psz_subtitle, psz_html, i_pts );
}
return NULL;
}
/*****************************************************************************
*
*****************************************************************************/
static const struct {
eia608_color_t i_color;
eia608_font_t i_font;
int i_column;
} pac2_attribs[]= {
{ EIA608_COLOR_WHITE, EIA608_FONT_REGULAR, 0 },
{ EIA608_COLOR_WHITE, EIA608_FONT_UNDERLINE, 0 },
{ EIA608_COLOR_GREEN, EIA608_FONT_REGULAR, 0 },
{ EIA608_COLOR_GREEN, EIA608_FONT_UNDERLINE, 0 },
{ EIA608_COLOR_BLUE, EIA608_FONT_REGULAR, 0 },
{ EIA608_COLOR_BLUE, EIA608_FONT_UNDERLINE, 0 },
{ EIA608_COLOR_CYAN, EIA608_FONT_REGULAR, 0 },
{ EIA608_COLOR_CYAN, EIA608_FONT_UNDERLINE, 0 },
{ EIA608_COLOR_RED, EIA608_FONT_REGULAR, 0 },
{ EIA608_COLOR_RED, EIA608_FONT_UNDERLINE, 0 },
{ EIA608_COLOR_YELLOW, EIA608_FONT_REGULAR, 0 },
{ EIA608_COLOR_YELLOW, EIA608_FONT_UNDERLINE, 0 },
{ EIA608_COLOR_MAGENTA, EIA608_FONT_REGULAR, 0 },
{ EIA608_COLOR_MAGENTA, EIA608_FONT_UNDERLINE, 0 },
{ EIA608_COLOR_WHITE, EIA608_FONT_ITALICS, 0 },
{ EIA608_COLOR_WHITE, EIA608_FONT_UNDERLINE_ITALICS, 0 },
{ EIA608_COLOR_WHITE, EIA608_FONT_REGULAR, 0 },
{ EIA608_COLOR_WHITE, EIA608_FONT_UNDERLINE, 0 },
{ EIA608_COLOR_WHITE, EIA608_FONT_REGULAR, 4 },
{ EIA608_COLOR_WHITE, EIA608_FONT_UNDERLINE, 4 },
{ EIA608_COLOR_WHITE, EIA608_FONT_REGULAR, 8 },
{ EIA608_COLOR_WHITE, EIA608_FONT_UNDERLINE, 8 },
{ EIA608_COLOR_WHITE, EIA608_FONT_REGULAR, 12 },
{ EIA608_COLOR_WHITE, EIA608_FONT_UNDERLINE, 12 },
{ EIA608_COLOR_WHITE, EIA608_FONT_REGULAR, 16 },
{ EIA608_COLOR_WHITE, EIA608_FONT_UNDERLINE, 16 },
{ EIA608_COLOR_WHITE, EIA608_FONT_REGULAR, 20 },
{ EIA608_COLOR_WHITE, EIA608_FONT_UNDERLINE, 20 },
{ EIA608_COLOR_WHITE, EIA608_FONT_REGULAR, 24 },
{ EIA608_COLOR_WHITE, EIA608_FONT_UNDERLINE, 24 },
{ EIA608_COLOR_WHITE, EIA608_FONT_REGULAR, 28 },
{ EIA608_COLOR_WHITE, EIA608_FONT_UNDERLINE, 28 } ,
};
#define EIA608_COLOR_DEFAULT EIA608_COLOR_WHITE
static void Eia608Cursor( eia608_t *h, int dx )
{
h->cursor.i_column += dx;
if( h->cursor.i_column < 0 )
h->cursor.i_column = 0;
else if( h->cursor.i_column > EIA608_SCREEN_COLUMNS-1 )
h->cursor.i_column = EIA608_SCREEN_COLUMNS-1;
}
static void Eia608ClearScreenRowX( eia608_t *h, int i_screen, int i_row, int x )
{
eia608_screen *screen = &h->screen[i_screen];
int i;
if( x == 0 )
{
screen->row_used[i_row] = VLC_FALSE;
}
else
{
screen->row_used[i_row] = VLC_FALSE;
for( i = 0; i < x; i++ )
{
if( screen->characters[i_row][i] != ' ' ||
screen->colors[i_row][i] != EIA608_COLOR_DEFAULT ||
screen->fonts[i_row][i] != EIA608_FONT_REGULAR )
{
screen->row_used[i_row] = VLC_TRUE;
break;
}
}
}
for( ; x < EIA608_SCREEN_COLUMNS+1; x++ )
{
screen->characters[i_row][x] = x < EIA608_SCREEN_COLUMNS ? ' ' : '\0';
screen->colors[i_row][x] = EIA608_COLOR_DEFAULT;
screen->fonts[i_row][x] = EIA608_FONT_REGULAR;
}
}
static void Eia608ClearScreenRow( eia608_t *h, int i_screen, int i_row )
{
Eia608ClearScreenRowX( h, i_screen, i_row, 0 );
}
static void Eia608ClearScreen( eia608_t *h, int i_screen )
{
int i;
for( i = 0; i < EIA608_SCREEN_ROWS; i++ )
Eia608ClearScreenRow( h, i_screen, i );
}
static int Eia608GetWritingScreenIndex( eia608_t *h )
{
switch( h->mode )
{
case EIA608_MODE_POPUP: // Non displayed screen
return 1 - h->i_screen;
case EIA608_MODE_ROLLUP_2: // Displayed screen
case EIA608_MODE_ROLLUP_3:
case EIA608_MODE_ROLLUP_4:
case EIA608_MODE_PAINTON:
return h->i_screen;
default:
/* It cannot happen, else it is a bug */
assert( 0 );
return 0;
}
}
static void Eia608EraseScreen( eia608_t *h, vlc_bool_t b_displayed )
{
Eia608ClearScreen( h, b_displayed ? h->i_screen : (1-h->i_screen) );
}
static void Eia608Write( eia608_t *h, const uint8_t c )
{
const int i_row = h->cursor.i_row;
const int i_column = h->cursor.i_column;
eia608_screen *screen;
if( h->mode == EIA608_MODE_TEXT )
return;
screen = &h->screen[Eia608GetWritingScreenIndex( h )];
screen->characters[i_row][i_column] = c;
screen->colors[i_row][i_column] = h->color;
screen->fonts[i_row][i_column] = h->font;
screen->row_used[i_row] = VLC_TRUE;
Eia608Cursor( h, 1 );
}
static void Eia608Erase( eia608_t *h )
{
const int i_row = h->cursor.i_row;
const int i_column = h->cursor.i_column - 1;
eia608_screen *screen;
if( h->mode == EIA608_MODE_TEXT )
return;
if( i_column < 0 )
return;
screen = &h->screen[Eia608GetWritingScreenIndex( h )];
/* FIXME do we need to reset row_used/colors/font ? */
screen->characters[i_row][i_column] = ' ';
Eia608Cursor( h, -1 );
}
static void Eia608EraseToEndOfRow( eia608_t *h )
{
if( h->mode == EIA608_MODE_TEXT )
return;
Eia608ClearScreenRowX( h, Eia608GetWritingScreenIndex( h ), h->cursor.i_row, h->cursor.i_column );
}
static void Eia608RollUp( eia608_t *h )
{
const int i_screen = Eia608GetWritingScreenIndex( h );
eia608_screen *screen = &h->screen[i_screen];
int keep_lines;
int i;
/* Window size */
if( h->mode == EIA608_MODE_ROLLUP_2 )
keep_lines = 2;
else if( h->mode == EIA608_MODE_ROLLUP_3 )
keep_lines = 3;
else if( h->mode == EIA608_MODE_ROLLUP_4 )
keep_lines = 4;
else
return;
/* Reset the cursor */
h->cursor.i_column = 0;
/* Erase lines above our window */
for( i = 0; i < h->cursor.i_row - keep_lines; i++ )
Eia608ClearScreenRow( h, i_screen, i );
/* Move up */
for( i = 0; i < keep_lines-1; i++ )
{
const int i_row = h->cursor.i_row - keep_lines + i + 1;
if( i_row < 0 )
continue;
assert( i_row+1 < EIA608_SCREEN_ROWS );
memcpy( screen->characters[i_row], screen->characters[i_row+1], sizeof(*screen->characters) );
memcpy( screen->colors[i_row], screen->colors[i_row+1], sizeof(*screen->colors) );
memcpy( screen->fonts[i_row], screen->fonts[i_row+1], sizeof(*screen->fonts) );
screen->row_used[i_row] = screen->row_used[i_row+1];
}
/* Reset current row */
Eia608ClearScreenRow( h, i_screen, h->cursor.i_row );
}
static void Eia608ParseChannel( eia608_t *h, uint8_t d1 )
{
if( d1 == 0x14 )
h->i_channel = 1;
else if( d1 == 0x1c )
h->i_channel = 2;
else if( ( d1 >= 0x01 && d1 <= 0x0f ) || d1 == 0x15 )
h->i_channel = 3;