/* * Copyright (C) 2004 the xine project * * This file is part of xine, a free video player. * * xine 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. * * xine 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-1307, USA * * $Id: xine_decoder.c,v 1.4 2004/12/01 22:16:12 mlampard Exp $ * * DVB Subtitle decoder (ETS 300 743) * (c) 2004 Mike Lampard * based on the application dvbsub by Dave Chapman * * TODO: * - Implement support for teletext based subtitles * - Use transmitted CLUT rather than default */ #include "xine_internal.h" #define MAX_REGIONS 5 typedef struct { int x,y; unsigned char is_visible; } visible_region_t; typedef struct { int page_time_out; int page_version_number; int page_state; visible_region_t regions[MAX_REGIONS]; } page_t; typedef struct { int width,height; int depth; int win; int CLUT_id; int objects_start,objects_end; unsigned int object_pos[65536]; unsigned char img[720*576]; } region_t; typedef struct { /* dvbsub stuff */ int x; int y; unsigned int curr_obj; unsigned int curr_reg[64]; uint8_t *buf; int i; int nibble_flag; int in_scanline; page_t page; region_t regions[MAX_REGIONS]; unsigned int sub_segment_size; } sub_image_t; typedef struct dvb_spu_class_s { spu_decoder_class_t class; xine_t *xine; } dvb_spu_class_t; typedef struct dvb_spu_decoder_s { spu_decoder_t spu_decoder; dvb_spu_class_t *class; xine_stream_t *stream; xine_event_queue_t *event_queue; spu_dvb_descriptor_t *spu_descriptor; osd_object_t *osd; char *pes_pkt; char *pes_pkt_wrptr; unsigned int pes_pkt_size; unsigned int pes_pkt_complete; unsigned int pes_pkt_rec; uint64_t pts; uint64_t vpts; sub_image_t *dvbsub; int timeout; int show; } dvb_spu_decoder_t; void create_region(dvb_spu_decoder_t *this, int region_id,int region_width,int region_height,int region_depth); void do_plot(dvb_spu_decoder_t *this, int r,int x, int y, unsigned char pixel); void plot(dvb_spu_decoder_t *this, int r,int run_length, unsigned char pixel); unsigned char next_nibble (dvb_spu_decoder_t *this); void set_clut(dvb_spu_decoder_t *this,int CLUT_id,int CLUT_entry_id,int Y_value, int Cr_value, int Cb_value, int T_value); void decode_4bit_pixel_code_string(dvb_spu_decoder_t *this, int r, int object_id, int ofs, int n); void process_pixel_data_sub_block(dvb_spu_decoder_t *this, int r, int o, int ofs, int n); void process_page_composition_segment(dvb_spu_decoder_t *this); void process_region_composition_segment(dvb_spu_decoder_t *this); void process_CLUT_definition_segment(dvb_spu_decoder_t *this); void process_object_data_segment(dvb_spu_decoder_t *this); void draw_subtitles(dvb_spu_decoder_t *this); void create_region(dvb_spu_decoder_t *this, int region_id,int region_width,int region_height,int region_depth) { sub_image_t *dvbsub = this->dvbsub; dvbsub->regions[region_id].win=1; dvbsub->regions[region_id].width=region_width; dvbsub->regions[region_id].height=region_height; memset(dvbsub->regions[region_id].img,15,sizeof(dvbsub->regions[region_id].img)); } void do_plot(dvb_spu_decoder_t *this, int r,int x, int y, unsigned char pixel) { int i; sub_image_t *dvbsub = this->dvbsub; if ( ( y >= 0) && (y < dvbsub->regions[r].height)) { i = (y * dvbsub->regions[r].width) + x; dvbsub->regions[r].img[i]=pixel; } } void plot(dvb_spu_decoder_t *this, int r,int run_length, unsigned char pixel) { sub_image_t *dvbsub = this->dvbsub; int x2 = dvbsub->x + run_length; while (dvbsub->x < x2) { do_plot(this, r,dvbsub->x,dvbsub->y,pixel); dvbsub->x++; } } unsigned char next_nibble (dvb_spu_decoder_t *this) { unsigned char x; sub_image_t *dvbsub = this->dvbsub; if (dvbsub->nibble_flag==0) { x=(dvbsub->buf[dvbsub->i]&0xf0)>>4; dvbsub->nibble_flag=1; } else { x=(dvbsub->buf[dvbsub->i++]&0x0f); dvbsub->nibble_flag=0; } return(x); } void decode_4bit_pixel_code_string(dvb_spu_decoder_t *this, int r, int object_id, int ofs, int n) { int next_bits, switch_1, switch_2, switch_3, run_length, pixel_code; sub_image_t *dvbsub = this->dvbsub; int bits; unsigned int data; int j; if (dvbsub->in_scanline==0) { dvbsub->in_scanline=1; } dvbsub->nibble_flag=0; j=dvbsub->i + n; while(dvbsub->i < j) { bits=0; pixel_code=0; next_bits=next_nibble(this); if (next_bits!=0) { pixel_code=next_bits; plot(this, r,1,pixel_code); bits+=4; } else { bits+=4; data=next_nibble(this); switch_1=(data&0x08)>>3; bits++; if (switch_1==0) { run_length=(data&0x07); bits+=3; if (run_length!=0) { plot(this, r,run_length+2,pixel_code); } else { break; } } else { switch_2=(data&0x04)>>2; bits++; if (switch_2==0) { run_length=(data&0x03); bits+=2; pixel_code=next_nibble(this); bits+=4; plot(this, r,run_length+4,pixel_code); } else { switch_3=(data&0x03); bits+=2; switch (switch_3) { case 0: plot(this, r,1,pixel_code); break; case 1: plot(this, r,2,pixel_code); break; case 2: run_length=next_nibble(this); bits+=4; pixel_code=next_nibble(this); bits+=4; plot(this, r,run_length+9,pixel_code); break; case 3: run_length=next_nibble(this); run_length=(run_length<<4)|next_nibble(this); bits+=8; pixel_code=next_nibble(this); bits+=4; plot(this, r,run_length+25,pixel_code); } } } } } if (dvbsub->nibble_flag==1) { dvbsub->i++; dvbsub->nibble_flag=0; } } void process_pixel_data_sub_block(dvb_spu_decoder_t *this, int r, int o, int ofs, int n) { int data_type; int j; sub_image_t *dvbsub = this->dvbsub; j = dvbsub->i + n; dvbsub->x=(dvbsub->regions[r].object_pos[o])>>16; dvbsub->y=((dvbsub->regions[r].object_pos[o])&0xffff)+ofs; while (dvbsub->i < j) { data_type=dvbsub->buf[dvbsub->i++]; switch(data_type) { case 0: dvbsub->i++; case 0x11: decode_4bit_pixel_code_string(this, r,o,ofs,n-1); break; case 0xf0: dvbsub->in_scanline=0; dvbsub->x=(dvbsub->regions[r].object_pos[o])>>16; dvbsub->y+=2; break; default: fprintf(stderr,"unimplemented data_type %02x in pixel_data_sub_block\n",data_type); } } dvbsub->i=j; } void process_page_composition_segment(dvb_spu_decoder_t *this) { int page_id, segment_length, page_time_out, page_version_number, page_state; int region_id,region_x,region_y; int j; int r; sub_image_t *dvbsub = this->dvbsub; page_id=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; segment_length=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; j=dvbsub->i+segment_length; page_time_out = dvbsub->buf[dvbsub->i++]; this->timeout=page_time_out; page_version_number = (dvbsub->buf [dvbsub->i] & 0xf0)>>4; page_state=(dvbsub->buf[dvbsub->i]&0x0c)>>2; dvbsub->i++; if ((page_state!=2) && (page_state!=1)) { return; } else { if(page_state==1 && page_id==this->spu_descriptor->comp_page_id) draw_subtitles(this); } for (r=0;rpage.regions[r].is_visible=0; } while (dvbsub->ibuf[dvbsub->i++]; dvbsub->i++; /* reserved */ region_x=(dvbsub->buf[dvbsub->i]<<8) | dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; region_y=(dvbsub->buf[dvbsub->i]<<8) | dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; dvbsub->page.regions[region_id].x=region_x; dvbsub->page.regions[region_id].y=region_y; dvbsub->page.regions[region_id].is_visible=1; } } void process_region_composition_segment(dvb_spu_decoder_t *this) { int page_id, segment_length, region_id, region_version_number, region_fill_flag, region_width, region_height, region_level_of_compatibility, region_depth, CLUT_id, region_8_bit_pixel_code, region_4_bit_pixel_code, region_2_bit_pixel_code; int object_id, object_type, object_provider_flag, object_x, object_y, foreground_pixel_code, background_pixel_code; int j; int o; sub_image_t *dvbsub = this->dvbsub; page_id=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; segment_length=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; j=dvbsub->i+segment_length; region_id=dvbsub->buf[dvbsub->i++]; region_version_number=(dvbsub->buf[dvbsub->i]&0xf0)>>4; region_fill_flag=(dvbsub->buf[dvbsub->i]&0x08)>>3; dvbsub->i++; region_width=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; region_height=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; region_level_of_compatibility=(dvbsub->buf[dvbsub->i]&0xe0)>>5; region_depth=(dvbsub->buf[dvbsub->i]&0x1c)>>2; dvbsub->i++; CLUT_id=dvbsub->buf[dvbsub->i++]; region_8_bit_pixel_code=dvbsub->buf[dvbsub->i++]; region_4_bit_pixel_code=(dvbsub->buf[dvbsub->i]&0xf0)>>4; region_2_bit_pixel_code=(dvbsub->buf[dvbsub->i]&0x0c)>>2; dvbsub->i++; if (dvbsub->regions[region_id].win < 0) { /* If the region doesn't exist, then open it. */ create_region(this, region_id,region_width,region_height,region_depth); dvbsub->regions[region_id].CLUT_id=CLUT_id; } dvbsub->regions[region_id].width=region_width; dvbsub->regions[region_id].height=region_height; if (region_fill_flag==1) { memset(dvbsub->regions[region_id].img,region_4_bit_pixel_code,sizeof(dvbsub->regions[region_id].img)); } dvbsub->regions[region_id].objects_start=dvbsub->i; dvbsub->regions[region_id].objects_end=j; for (o=0;o<65536;o++) { dvbsub->regions[region_id].object_pos[o]=0xffffffff; } while (dvbsub->i < j) { object_id=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; object_type=(dvbsub->buf[dvbsub->i]&0xc0)>>6; object_provider_flag=(dvbsub->buf[dvbsub->i]&0x30)>>4; object_x=((dvbsub->buf[dvbsub->i]&0x0f)<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; object_y=((dvbsub->buf[dvbsub->i]&0x0f)<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; dvbsub->regions[region_id].object_pos[object_id]=(object_x<<16)|object_y; if ((object_type==0x01) || (object_type==0x02)) { foreground_pixel_code=dvbsub->buf[dvbsub->i++]; background_pixel_code=dvbsub->buf[dvbsub->i++]; } } } void process_object_data_segment(dvb_spu_decoder_t *this) { int page_id, segment_length, object_id, object_version_number, object_coding_method, non_modifying_colour_flag; int top_field_data_block_length, bottom_field_data_block_length; sub_image_t *dvbsub = this->dvbsub; int j; int old_i; int r; page_id=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; segment_length=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; j=dvbsub->i+segment_length; object_id=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; dvbsub->curr_obj=object_id; object_version_number=(dvbsub->buf[dvbsub->i]&0xf0)>>4; object_coding_method=(dvbsub->buf[dvbsub->i]&0x0c)>>2; non_modifying_colour_flag=(dvbsub->buf[dvbsub->i]&0x02)>>1; dvbsub->i++; old_i=dvbsub->i; for (r=0;rregions[r].win >= 0) { if (dvbsub->regions[r].object_pos[object_id]!=0xffffffff) { dvbsub->i=old_i; if (object_coding_method==0) { top_field_data_block_length=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; bottom_field_data_block_length=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2; process_pixel_data_sub_block(this, r,object_id,0,top_field_data_block_length); process_pixel_data_sub_block(this, r,object_id,1,bottom_field_data_block_length); } } } } } void draw_subtitles(dvb_spu_decoder_t *this) { int r; int x,y,out_y; int v; int count; /* update clipping area */ this->osd->x1 = 1; this->osd->x2 = 720; this->osd->y1 = 1; this->osd->y2 = 576; /* clear it */ memset(this->osd->area,0,720*576); out_y=0; count=0; for (r=0;rdvbsub->regions[r].win >= 0) { if (this->dvbsub->page.regions[r].is_visible) { count++; out_y=this->dvbsub->page.regions[r].y*720; for (y=0;ydvbsub->regions[r].height;y++) { for (x=0;xdvbsub->regions[r].width;x++) { v=this->dvbsub->regions[r].img[(y*this->dvbsub->regions[r].width)+x]; this->osd->area[out_y+x+this->dvbsub->page.regions[r].x]=v*this->dvbsub->regions[r].CLUT_id; } out_y+=720; } } } } /* display immediately */ this->stream->osd_renderer->show (this->osd, 0); } static void spudec_decode_data (spu_decoder_t *this_gen, buf_element_t *buf) { dvb_spu_decoder_t *this = (dvb_spu_decoder_t *) this_gen; if(buf->decoder_flags & BUF_FLAG_SPECIAL){ if(buf->decoder_info[1]==BUF_SPECIAL_SPU_DVB_DESCRIPTOR) { if(buf->decoder_info[2]==0) { this->stream->osd_renderer->clear(this->osd); this->stream->osd_renderer->hide(this->osd,0); this->show=0; } else { if(this->spu_descriptor) free(this->spu_descriptor); this->show=1; this->spu_descriptor = malloc(buf->decoder_info[2]); memcpy(this->spu_descriptor,buf->decoder_info_ptr[2],buf->decoder_info[2]); } } } else { if(buf->type & 0xffff){ if(this->pes_pkt) free(this->pes_pkt); this->pes_pkt = malloc (buf->type & 0xffff); memset(this->pes_pkt,0xff,buf->type & 0xffff); this->pes_pkt_wrptr = this->pes_pkt; this->pes_pkt_size = (buf->type & 0xffff); this->pts = buf->pts; memcpy(this->pes_pkt, buf->content, buf->size); this->pes_pkt_wrptr+=buf->size; this->pes_pkt_rec=1; }else{ if(this->pes_pkt){ memcpy(this->pes_pkt_wrptr, buf->content, buf->size); this->pes_pkt_wrptr+=buf->size; } } } /* inform metronom we've received the package */ if (buf->pts) { metronom_t *metronom = this->stream->metronom; this->vpts = metronom->got_spu_packet(metronom, buf->pts); } /* process the pes section */ if(this->show) if(((this->pes_pkt_wrptr - this->pes_pkt) == this->pes_pkt_size) && this->pes_pkt_rec) { char *buf=this->pes_pkt; int page_id; int new_i; int data_identifier,subtitle_stream_id; int segment_length, segment_type; int PES_header_data_length; int PES_packet_length=this->pes_pkt_size; this->dvbsub->buf = this->pes_pkt; PES_header_data_length=0; this->dvbsub->i=0; data_identifier = buf[this->dvbsub->i++]; subtitle_stream_id = buf[this->dvbsub->i++]; while(this->dvbsub->i <= (PES_packet_length)) { /* SUBTITLING SEGMENT */ this->dvbsub->i++; segment_type=buf[this->dvbsub->i++]; page_id=(buf[this->dvbsub->i]<<8)|buf[this->dvbsub->i+1]; segment_length=(buf[this->dvbsub->i+2]<<8)|buf[this->dvbsub->i+3]; new_i=this->dvbsub->i+segment_length+4; /* SEGMENT_DATA_FIELD */ switch(segment_type & 0xff) { case 0x10: process_page_composition_segment(this); break; case 0x11: process_region_composition_segment(this); break; case 0x12: /* process_CLUT_definition_segment(this); */ break; case 0x13: process_object_data_segment(this); break; case 0x80: /* we have enough data to decode */ break; default: return; } this->dvbsub->i=new_i; } } return; } static void spudec_reset (spu_decoder_t *this_gen) { dvb_spu_decoder_t *this = (dvb_spu_decoder_t *) this_gen; if(this->osd) this->stream->osd_renderer->hide(this->osd,0); } static void spudec_discontinuity (spu_decoder_t *this_gen) { /* do nothing */ } static void spudec_dispose (spu_decoder_t *this_gen) { dvb_spu_decoder_t *this = (dvb_spu_decoder_t *) this_gen; if (this->osd) { this->stream->osd_renderer->free_object (this->osd); this->osd = NULL; } if(this->pes_pkt) free(this->pes_pkt); if(this->dvbsub) free(this->dvbsub); free(this); } static spu_decoder_t *dvb_spu_class_open_plugin (spu_decoder_class_t *class_gen, xine_stream_t *stream) { int i; dvb_spu_decoder_t *this ; dvb_spu_class_t *class = (dvb_spu_class_t *)class_gen; this = (dvb_spu_decoder_t *) xine_xmalloc (sizeof (dvb_spu_decoder_t)); this->spu_decoder.decode_data = spudec_decode_data; this->spu_decoder.reset = spudec_reset; this->spu_decoder.discontinuity = spudec_discontinuity; this->spu_decoder.dispose = spudec_dispose; this->spu_decoder.get_interact_info = NULL; this->spu_decoder.set_button = NULL; this->class = class; this->stream = stream; this->event_queue = NULL; this->dvbsub=malloc(sizeof(sub_image_t)); for (i=0;idvbsub->page.regions[i].is_visible=0; this->dvbsub->regions[i].win=-1; } this->osd = this->stream->osd_renderer->new_object(this->stream->osd_renderer, 720, 600); this->stream->osd_renderer->set_position(this->osd, 1, 1); this->stream->osd_renderer->set_font(this->osd, "cetus", 26); this->stream->osd_renderer->set_encoding(this->osd, NULL); this->stream->osd_renderer->set_text_palette(this->osd, TEXTPALETTE_WHITE_BLACK_TRANSPARENT, OSD_TEXT1); return (spu_decoder_t *) this; } static void dvb_spu_class_dispose (spu_decoder_class_t *this) { free (this); } static char *dvb_spu_class_get_identifier (spu_decoder_class_t *this) { return "spudvb"; } static char *dvb_spu_class_get_description (spu_decoder_class_t *this) { return "DVB subtitle decoder plugin"; } static void *init_spu_decoder_plugin (xine_t *xine, void *data) { dvb_spu_class_t *this; this = (dvb_spu_class_t *) xine_xmalloc (sizeof (dvb_spu_class_t)); this->class.open_plugin = dvb_spu_class_open_plugin; this->class.get_identifier = dvb_spu_class_get_identifier; this->class.get_description = dvb_spu_class_get_description; this->class.dispose = dvb_spu_class_dispose; this->xine = xine; return &this->class; } /* plugin catalog information */ static uint32_t supported_types[] = { BUF_SPU_DVB, 0 }; static decoder_info_t spudec_info = { supported_types, /* supported types */ 1 /* priority */ }; plugin_info_t xine_plugin_info[] = { /* type, API, "name", version, special_info, init_function */ { PLUGIN_SPU_DECODER, 16, "spudvb", XINE_VERSION_CODE, &spudec_info, &init_spu_decoder_plugin }, { PLUGIN_NONE, 0, "", 0, NULL, NULL } };