Property changes on: . ___________________________________________________________________ Name: svn:ignore + *.swp Index: SOURCES =================================================================== --- SOURCES (revision 15105) +++ SOURCES (working copy) @@ -17,3 +17,4 @@ video_out_rockbox.c mpeg_settings.c mpegplayer.c +subtitles.c Index: mpegplayer.c =================================================================== --- mpegplayer.c (revision 15105) +++ mpegplayer.c (working copy) @@ -380,6 +380,8 @@ only decoding one frame for use in the menu. If 0, normal operation */ +static uint32_t start_pts; /* The movie start time */ +static uint32_t end_pts; /* The movie end time */ static int end_pts_time IBSS_ATTR; /* The movie end time as represented by the maximum audio PTS tag in the stream converted to half minutes */ @@ -401,6 +403,39 @@ #define MPEG_GUARDBUF_SIZE (65*1024) /* Keep a bit extra - excessive for now */ #define MPEG_LOW_WATERMARK (1024*1024) +#include "subtitles.h" +#define OVERLAY_Y_BUFFER_SIZE (320*240) /* FIXME */ +#ifdef OVERLAY_CHROMA +# define OVERLAY_UV_BUFFER_SIZE (160*120) /* FIXME */ +#else +# define OVERLAY_UV_BUFFER_SIZE (0) +#endif +#define OVERLAY_BUFFER_SIZE (OVERLAY_Y_BUFFER_SIZE+2*OVERLAY_UV_BUFFER_SIZE) + +static subtitles_t subs; +static timebar_t timebar; +static overlay_t overlay; + +static bool init_subbuf( size_t buffer_size ) +{ + subs.buffer = mpeg2_malloc( buffer_size, -2 ); + if( !subs.buffer ) + return false; + return true; +} + +static bool init_overlaybuf( void ) +{ + overlay.update = -1; + overlay.count = 0; + return ( overlay.Y_PIXELS = mpeg2_malloc( OVERLAY_Y_BUFFER_SIZE, -2) ) +#ifdef OVERLAY_CHROMA + && ( overlay.U_PIXELS = mpeg2_malloc( OVERLAY_UV_BUFFER_SIZE, -2) ) + && ( overlay.V_PIXELS = mpeg2_malloc( OVERLAY_UV_BUFFER_SIZE, -2) ) +#endif + ; +} + static void pcm_playback_play_pause(bool play); /* libmad related functions/definitions */ @@ -1082,6 +1117,7 @@ rb->sound_set(SOUND_VOLUME, vol); rb->global_settings->volume = vol; } + timebar_show( overlay.timebar, get_playback_time() ); break; case MPEG_VOLDOWN: @@ -1098,6 +1134,7 @@ rb->sound_set(SOUND_VOLUME, vol); rb->global_settings->volume = vol; } + timebar_show( overlay.timebar, get_playback_time() ); break; case MPEG_MENU: @@ -1603,6 +1640,13 @@ case STATE_SEQUENCE: /* New GOP, inform output of any changes */ vo_setup(info->sequence); + /* Init the overlay stuff if needed */ + if( overlay.update == -1 ) + overlay_init( &overlay, + info->sequence->width, + info->sequence->height, + info->sequence->chroma_width, + info->sequence->chroma_height ); break; case STATE_PICTURE: @@ -1846,6 +1890,17 @@ /* Record last frame time */ last_render = *rb->current_tick; + /* Render overlay stuff in a cache */ + overlay_render( &overlay, get_playback_time(), + end_pts-start_pts ); + /* Blend the overlay cache on the video yuv buffer */ + overlay_blend( &overlay, + (uint8_t **)info->display_fbuf->buf, + info->sequence->width, + info->sequence->height, + info->sequence->chroma_width, + info->sequence->chroma_height ); + if (video_thumb_print) vo_draw_frame_thumb(info->display_fbuf->buf); else @@ -1986,7 +2041,10 @@ } while (tmp.tagged != 1 && count < 30); if (tmp.tagged == 1) + { + start_pts = tmp.curr_pts; start_pts_time = (int)((tmp.curr_pts/45000)/30); + } } return 0; } @@ -2029,8 +2087,11 @@ { get_next_data(&tmp, 2); if (tmp.tagged == 1) + { + end_pts = tmp.curr_pts; /* 10 sec less to insure the video frame exist */ end_pts_time = (int)((tmp.curr_pts/45000-10)/30); + } } while (tmp.curr_packet_end != NULL); } @@ -2208,12 +2269,15 @@ void* audiobuf; ssize_t audiosize; int in_file; + int subs_file; size_t disk_buf_len; + size_t sub_buf_len; ssize_t seek_pos; #ifndef HAVE_LCD_COLOR long graysize; int grayscales; #endif + char str[200]; audio_sync_start = 0; audio_sync_time = 0; @@ -2260,13 +2324,23 @@ if (audiosize == 0) return PLUGIN_ERROR; + /* Try loading subtitles in file with same name except extension */ + rb->snprintf( str, sizeof(str), "%s", (char*)parameter ); + rb->snprintf( rb->strrchr( str, '.' ), 10/*FIXME*/, ".srt" ); + subs_file = rb->open( str, O_RDONLY ); + if( subs_file >= 0 ) + sub_buf_len = rb->filesize(subs_file); + else + sub_buf_len = 0; + /* Set disk pointers to NULL */ disk_buf_end = disk_buf_start = NULL; /* Grab most of the buffer for the compressed video - leave some for PCM audio data and some for libmpeg2 malloc use. */ disk_buf_size = audiosize - (PCMBUFFER_SIZE+PCMBUFFER_GUARD_SIZE+ - MPABUF_SIZE); + MPABUF_SIZE+sub_buf_len + +OVERLAY_BUFFER_SIZE); DEBUGF("audiosize=%ld, disk_buf_size=%ld\n",audiosize,disk_buf_size); disk_buf_start = mpeg_malloc(disk_buf_size,-1); @@ -2280,6 +2354,24 @@ if (!init_pcmbuf()) return PLUGIN_ERROR; + if( !init_subbuf(sub_buf_len) ) + return PLUGIN_ERROR; + + if( !init_overlaybuf()) + return PLUGIN_ERROR; + + subs_init( &subs ); + timebar_init( &timebar ); + overlay.subs = &subs; + overlay.timebar = &timebar; + + if( subs_file >= 0 ){ + /* load the subs */ + if( rb->read( subs_file, subs.buffer, sub_buf_len ) ) + subs.cur = subs.buffer; + rb->close( subs_file ); + } + /* The remaining buffer is for use by libmpeg2 */ /* Open the video file */ Index: subtitles.c =================================================================== --- subtitles.c (revision 0) +++ subtitles.c (revision 0) @@ -0,0 +1,476 @@ +#include "plugin.h" +#include "subtitles.h" + +#define SUBS_TOP 10 +#define TIMEBAR_TOP( height ) (height-20) + +extern struct plugin_api *rb; + +static inline void uint8_buffer_putsxyofs( + uint8_t *, int, int, int, int, int, + const unsigned char *, struct font* ); + +void subs_init( subtitles_t *subs ) +{ + subs->cur = NULL; + subs->i_start = 0; + subs->i_stop = 0; + subs->i_prev_date = 0; + subs->pf = rb->font_get( FONT_UI ); + if( !subs->pf ) subs->pf = rb->font_get( FONT_SYSFIXED ); +} + +extern struct plugin_api *rb; + +static inline char *strchr2( char *str, const char *strend, const char c ) +{ + while( *str && *str != c && str < strend ) str ++; + return str; +} + +static inline uint32_t subs_convert_date( int h, int m, int s, int d ) +{ + return h*3600*44100 + m*60*44100 + s*44100 + d*44; +} + +static inline int strtol10( char *str, char **end ) +{ + int r = 0; + while( *str && *str >= '0' && *str <= '9' ) + { + r = r*10+*str-'0'; + str++; + } + if( end ) *end = str; + return r; +} + +/** + * Parse SRT subs. Format is: + * + * n + * h1:m1:s1,d1 --> h2:m2:s2,d2 + * Line1 + * Line2 + * ... + * [empty line] + */ +static inline char *subs_get_next( subtitles_t *subs ) +{ + int h,m,s,d; + char *cur = subs->cur; + + /* TODO: add some error checking */ + +#define skipline(a) (rb->strchr(a,'\n')+1) + if( cur[0] != '1' && cur[1] != '\n' && cur[1] != '\r' ) + { + /* Skip all text lines */ + while( *cur != '\n' && *cur != '\r' ) + cur = skipline( cur ); + /* This is the empty line. Skip it */ + cur = skipline( cur ); + } + cur = skipline( cur ); + h = strtol10( cur, &cur ); cur ++; /* ":" */ + m = strtol10( cur, &cur ); cur ++; /* ":" */ + s = strtol10( cur, &cur ); cur ++; /* "," */ + d = strtol10( cur, &cur ); cur += 1 + 3 + 1; /* " --> " */ + subs->i_start = subs_convert_date( h, m, s, d ); + h = strtol10( cur, &cur ); cur ++; /* ":" */ + m = strtol10( cur, &cur ); cur ++; /* ":" */ + s = strtol10( cur, &cur ); cur ++; /* "," */ + d = strtol10( cur, &cur ); cur = skipline( cur ); + subs->i_stop = subs_convert_date( h, m, s, d ); +#undef skipline + + subs->cur = cur; + return cur; +} + +static int subs_need_update( subtitles_t *subs, uint32_t date ) +{ + return subs->cur + && ( !( subs->i_start || subs->i_stop ) + || ( subs->i_stop < date ) + || ( subs->i_start <= date + && subs->i_prev_date < subs->i_start ) ); +} + +static int subs_render( subtitles_t *subs, uint32_t date, + uint8_t *p_y, int i_y_width, int i_y_height ) +{ + char *cur; + int x = 0, y = SUBS_TOP; + if( !subs->cur ) return 0; + + /* FIXME: current code doesn't have any error checking */ + + if( subs->i_stop < date ) + { + cur = subs_get_next( subs ); + if( !cur ) return 0; /* Crap, something failed */ + subs->i_prev_date = 0; + } + else + { + cur = subs->cur; + } + + if( subs->i_start <= date + /* FIXME: add back if rendering out of video && subs->i_prev_date < subs->i_start */ ) + { + char *end = cur; + do /* Loop on lines in srt buffer */ + { + char *lineend = cur; + end = rb->strchr( end, '\n' ); + while( lineend != end ) /* We need to skip lines at ever pipe */ + { + char c = 0; + int ugly_hack; + lineend = strchr2( cur, end, '|' ); + + /* \r\n handling ... gra */ + ugly_hack = *lineend == '\n' && *(lineend-1) == '\r'; + + if( ugly_hack ) + *(lineend-1) = '\0'; + else + { + c = *lineend; + *lineend = '\0'; + } + + /* Render in the buffer */ + uint8_buffer_putsxyofs( p_y, i_y_width, i_y_height, + x, y, 0, cur, subs->pf ); + + if( ugly_hack ) + *(lineend-1) = '\r'; + else + *lineend = c; + cur = lineend+1; + y += subs->pf->height; + } + end++; + } while( *end != '\n' && *end != '\r' ); + } + else return 0; + subs->i_prev_date = date; + return y; +} + +static int line_horizontal_render( uint8_t *p_y, int width, + int y, int x1, int x2 ) +{ + int x; + for( x = x1; x <= x2; x++ ) + { + p_y[width*y+x] = 0xff; + p_y[width*(y+1)+x] = 0x01; /* The shadow */ + } + return 2; +} + +static int line_vertical_render( uint8_t *p_y, int width, + int x, int y1, int y2 ) +{ + int y; + for( y = y1; y <= y2; y++ ) + { + p_y[width*y+x] = 0xff; + p_y[width*y+x+1] = 0x01; /* The shadow */ + } + return 2; +} + +void timebar_init( timebar_t *t ) +{ + t->date = 0; +} + +void timebar_show( timebar_t *t, uint32_t date ) +{ + /* Display the timebar for 3 seconds */ + t->date = date + 3*44100; +} + +static int timebar_need_update( timebar_t *t, uint32_t date ) +{ + if( t->date ) + { + if( date > t->date ) t->date = 0; + return 1; + } + return 0; +} + +static int timebar_render( timebar_t *t, uint32_t date, uint32_t duration, + uint8_t *p_y, int width, int height ) +{ + if( !t->date ) return 0; + line_horizontal_render( p_y, width, + TIMEBAR_TOP(height), 10, width-10 ); + line_vertical_render( p_y, width, + 10, TIMEBAR_TOP(height)+1, TIMEBAR_TOP(height)+10 ); + line_vertical_render( p_y, width, 10+((width-20)*date/duration), + TIMEBAR_TOP(height)+1, TIMEBAR_TOP(height)+10 ); + line_vertical_render( p_y, width, width-10, + TIMEBAR_TOP(height), TIMEBAR_TOP(height)+10 ); + line_horizontal_render( p_y, width, TIMEBAR_TOP(height)+10, + 10+1, width-10 ); + return 12; +} + +/*********************************************************************** + * Offscreen buffer/Text/Fonts handling + * + * Parts of code taken from firmware/drivers/lcd-16bit.c + ***********************************************************************/ +static void uint8_buffer_mono_bitmap_part( + uint8_t *buf, int buf_width, int buf_height, + const unsigned char *src, int src_x, int src_y, + int stride, int x, int y, int width, int height ) +/* this function only draws the foreground part of the bitmap */ +/* TODO: add a black border */ +{ + const unsigned char *src_end; + uint8_t *dst, *dst_end; + const uint8_t fgcolor = 0xff; + const uint8_t shcolor = 0x01; + + /* nothing to draw? */ + if( ( width <= 0 ) || ( height <= 0 ) || ( x >= buf_width ) + || ( y >= buf_height ) || ( x + width <= 0 ) || ( y + height <= 0 ) ) + return; + + /* clipping */ + if( x < 0 ) + { + width += x; + src_x -= x; + x = 0; + } + if( y < 0 ) + { + height += y; + src_y -= y; + y = 0; + } + if( x + width > buf_width ) + width = buf_width - x; + if( y + height > buf_height ) + height = buf_height - y; + + src += stride * (src_y >> 3) + src_x; /* move starting point */ + src_y &= 7; + src_end = src + width; + + dst = buf + y*buf_width + x; + + do + { + const unsigned char *src_col = src++; + unsigned data = *src_col >> src_y; + uint8_t *dst_col = dst++; + int numbits = 8 - src_y; + + dst_end = dst_col + height * buf_width; + do + { + if( data & 0x01 ) + { + dst_col[0] = fgcolor; + dst_col[1] = shcolor; + } + + dst_col += buf_width; + + data >>= 1; + if( --numbits == 0 ) + { + src_col += stride; + data = *src_col; + numbits = 8; + } + } while( dst_col < dst_end ); + } while( src < src_end ); +} + +static inline void uint8_buffer_putsxyofs( + uint8_t *buf, int buf_width, int buf_height, + int x, int y, int ofs, const unsigned char *str, + struct font *pf ) +{ + unsigned short ch; + unsigned short *ucs; + + ucs = rb->bidi_l2v( str, 1 ); + + while( (ch = *ucs++) != 0 && x < buf_width ) + { + int width; + const unsigned char *bits; + + /* get proportional width and glyph bits */ + width = rb->font_get_width( pf, ch ); + + if( ofs > width ) + { + ofs -= width; + continue; + } + + bits = rb->font_get_bits( pf, ch ); + + uint8_buffer_mono_bitmap_part( buf, buf_width, buf_height, bits, + ofs, 0, width, x, y, + width - ofs, pf->height); + + x += width - ofs; + ofs = 0; + } +} + +static void overlay_init_plane( overlay_plane_t *p, int width, int height ) +{ + p->width = width; + p->height = height; + p->status = PLANE_DIRTY; + p->bb_y = 0; + p->bb_height = 0; +} + +/** + * Overlay framework + */ +void overlay_init( overlay_t *o, int width, int height, int chroma_width, int chroma_height ) +{ + overlay_init_plane( o->plane + Y_PLANE, width, height ); +#ifdef OVERLAY_CHROMA + overlay_init_plane( o->plane + U_PLANE, chroma_width, chroma_height ); + overlay_init_plane( o->plane + V_PLANE, chroma_width, chroma_height ); +#else + (void)chroma_width; + (void)chroma_height; +#endif + + o->update = 1; + o->count = 0; +} + +static inline void overlay_clear_plane( overlay_plane_t *op ) +{ + if( op->status != PLANE_CLEAN ) + { + if( op->status == PLANE_DIRTY ) + rb->memset( op->buf, OVERLAY_TRANSPARENT, op->width * op->height ); + else /* PLANE_USED */ + rb->memset( op->buf + op->bb_y*op->width, OVERLAY_TRANSPARENT, + op->bb_height*op->width ); + op->status = PLANE_CLEAN; + op->bb_y = 0; + op->bb_height = 0; + } +} + +static inline void overlay_add_bb( overlay_plane_t *op, int y, int height ) +{ + if( op->status == PLANE_USED ) + { + /* Merge bounding boxes */ + int ly = MAX(y+height,op->bb_y+op->bb_height); + op->bb_y = MIN(y,op->bb_y); + op->bb_height = ly-op->bb_y+1; + } + else + { + op->bb_y = y; + op->bb_height = height; + op->status = PLANE_USED; + } +} + +void overlay_render( overlay_t *o, uint32_t date, uint32_t duration ) +{ + int r; + + if( o->update == -1 ) return; + o->update |= subs_need_update( o->subs, date ) + || timebar_need_update( o->timebar, date ); + + if( !o->update ) return; + o->update = 0; + o->count = 0; + + overlay_clear_plane( o->plane+Y_PLANE ); +#ifdef OVERLAY_CHROMA + overlay_clear_plane( o->plane+U_PLANE ); + overlay_clear_plane( o->plane+V_PLANE ); +#endif + + if( ( r = subs_render( o->subs, date, o->Y_PIXELS, + o->plane[Y_PLANE].width, + o->plane[Y_PLANE].height ) ) ) + { + overlay_add_bb( o->plane+Y_PLANE, SUBS_TOP, r ); + o->count ++; + } + + if( ( r = timebar_render( o->timebar, date, duration, o->Y_PIXELS, + o->plane[Y_PLANE].width, + o->plane[Y_PLANE].height ) ) ) + { + overlay_add_bb( o->plane+Y_PLANE, + TIMEBAR_TOP(o->plane[Y_PLANE].height), r ); + o->count ++; + } +} + +static inline void overlay_blend_plane( uint8_t *dst, overlay_plane_t *p ) +{ + uint8_t *src = p->buf+p->bb_y*p->width; + const uint8_t *srcend = src + p->bb_height*p->width; + dst += p->bb_y*p->width; + while( src < srcend ) + { + if( *src != OVERLAY_TRANSPARENT ) *dst = *src; dst++; src++; + if( *src != OVERLAY_TRANSPARENT ) *dst = *src; dst++; src++; + if( *src != OVERLAY_TRANSPARENT ) *dst = *src; dst++; src++; + if( *src != OVERLAY_TRANSPARENT ) *dst = *src; dst++; src++; + if( *src != OVERLAY_TRANSPARENT ) *dst = *src; dst++; src++; + if( *src != OVERLAY_TRANSPARENT ) *dst = *src; dst++; src++; + if( *src != OVERLAY_TRANSPARENT ) *dst = *src; dst++; src++; + } +} + +void overlay_blend( overlay_t *o, uint8_t **buf, + int width, int height, + int chroma_width, int chroma_height ) +{ +#ifndef OVERLAY_CHROMA + (void)chroma_width; + (void)chroma_height; +#endif + + if( !o->count ) return; + if( o->plane[Y_PLANE].width != width || o->plane[Y_PLANE].height != height +#ifdef OVERLAY_CHROMA + || o->plane[U_PLANE].width != chroma_width + || o->plane[U_PLANE].height != chroma_height + || o->plane[V_PLANE].width != chroma_width + || o->plane[V_PLANE].height != chroma_height +#endif + ) + return; /* TODO: make it work even if dimensions don't match */ + + if( o->plane[Y_PLANE].status == PLANE_USED ) + overlay_blend_plane( buf[0], o->plane+Y_PLANE ); +#ifdef OVERLAY_CHROMA + if( o->plane[U_PLANE].status == PLANE_USED ) + overlay_blend_plane( buf[1], o->plane+U_PLANE ); + if( o->plane[V_PLANE].status == PLANE_USED ) + overlay_blend_plane( buf[2], o->plane+V_PLANE ); +#endif +} Index: subtitles.h =================================================================== --- subtitles.h (revision 0) +++ subtitles.h (revision 0) @@ -0,0 +1,82 @@ +#ifndef _SUBTITLES_H +#define _SUBTITLES_H + +/** + * Subtitles + */ +typedef struct { + char *buffer; + char *cur; + uint32_t i_start; /* don't display before date >= i_start */ + uint32_t i_stop; /* if date >= i_stop, read next subs block (and stop displaying the previous one) */ + uint32_t i_prev_date; /* Use if rendering out of video only */ + struct font *pf; +} subtitles_t; + +void subs_init( subtitles_t * ); + +/** + * Time bar display + */ +typedef struct { + uint32_t date; +} timebar_t; + +void timebar_init( timebar_t * ); +void timebar_show( timebar_t *, uint32_t ); + +/** + * Video overlay + * + * Do we need a seperate A_PLANE? (alpha) + */ +#define OVERLAY_TRANSPARENT 0 + +#define Y_PLANE 0 +#define Y_PIXELS plane[Y_PLANE].buf +#ifdef OVERLAY_CHROMA +# define U_PLANE 1 +# define U_PIXELS plane[U_PLANE].buf +# define V_PLANE 2 +# define V_PIXELS plane[V_PLANE].buf +#endif + +enum { + PLANE_DIRTY = -1, + PLANE_CLEAN = 0, + PLANE_USED = 1 +}; + +typedef struct { + uint8_t *buf; + int width; + int height; + int status; + + int bb_y; + int bb_height; +} overlay_plane_t; + +typedef struct { +#ifdef OVERLAY_CHROMA + overlay_plane_t plane[3]; +#else + overlay_plane_t plane[1]; +#endif + /* TODO: add "overlay bounding box" coordinates so we only try blending + * pixels that are likely to be non-transparent. */ + + int count; /* Blend only when count > 1 */ + int update; + + /* List stuff that can be rendered in the overlay buffer here */ + subtitles_t *subs; + timebar_t *timebar; + /* TODO: add playback info widgets (like volume, seek bar, ...) */ +} overlay_t; + +void overlay_init( overlay_t *, int, int, int, int ); +void overlay_render( overlay_t *, uint32_t, uint32_t ); +void overlay_blend( overlay_t *, uint8_t **buf, int, int, int, int ); + +#endif