📄 dvbsubpicture.java
字号:
/*
* @(#)DVBSubpicture.java - decodes DVB subtitles
*
* Copyright (c) 2004-2005 by dvb.matt, All rights reserved
*
* This file is part of X, a free Java based demux utility.
* X is intended for educational purposes only, as a non-commercial test project.
* It may not be used otherwise. Most parts are only experimental.
*
*
* This program is free software; you can redistribute it free of charge
* 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-1307 USA
*
*/
/*
* example of a basic implementation of a DVB subtitle decoder
*
* it does not yet implement export of encoded string characters, only bitmapped pictures
*
*/
package net.sourceforge.dvb.projectx.subtitle;
//DM24042004 081.7 int02 introduced
import java.awt.*;
import java.awt.image.*;
import java.util.*;
import net.sourceforge.dvb.projectx.common.Resource;
import net.sourceforge.dvb.projectx.common.X;
public class DVBSubpicture
{
private byte data[];
private int BytePosition, BitPosition;
private Graphics2D big;
private BufferedImage bimg;
private Epoch epoch;
private Page page;
private Region region;
private CLUT clut;
private OBJECT object;
private Hashtable epoches = new Hashtable();
private int table_CLUT_8bit[];
private int IRD;
private int epoch_id = 0;
private boolean biglog;
private int pixel_data[], preview_pixel_data[];
private long pts;
private int width = 720, height = 576;
private int from_index, to_index;
private boolean picture_saved;
private boolean save;
private boolean preview_visible = false;
private int fix_page_id;
//DM13062004 081.7 int04 add
private Hashtable user_table = new Hashtable();
private boolean user_table_enabled;
//DM30072004 081.7 int07 add
private boolean global_error = false;
public DVBSubpicture()
{
table_CLUT_8bit = generateDefaultCLUT_8Bits();
setIRD(8, user_table, false, ""); //DM13062004 081.7 int04 changed
}
public void setIRD(int val, Hashtable table, boolean log, String page_id_str)
{
IRD = val; //2,4,8 = 4,16,256-color support
//DM13062004 081.7 int04 add++
user_table = table;
user_table_enabled = !user_table.isEmpty(); //DM23062004 081.7 int05 changed
if (user_table_enabled)
IRD = Integer.parseInt(user_table.get("model").toString().trim());
//DM13062004 081.7 int04 add--
biglog = log;
resetEpoch();
if (page_id_str.trim().length() != 0)
fix_page_id = Integer.parseInt(page_id_str.trim());
else
fix_page_id = -1;
preview_visible = false;
}
private void addBigMessage(String msg)
{
if (!biglog)
return;
System.out.println(msg);
}
private void resetEpoch()
{
epoches.clear();
epoch = setEpoch(epoch_id);
preview_pixel_data = new int[width * height];
picture_saved = false;
save = false;
}
private int getBits(int N)
{
int Pos, Val;
Pos = BitPosition>>>3;
//DM03082004 081.7 int07 add
if (Pos >= data.length - 4)
{
global_error = true;
BitPosition += N;
BytePosition = BitPosition>>>3;
return 0;
}
Val = (0xFF & data[Pos])<<24 |
(0xFF & data[Pos+1])<<16 |
(0xFF & data[Pos+2])<<8 |
(0xFF & data[Pos+3]);
Val <<= BitPosition & 7;
Val >>>= 32-N;
BitPosition += N;
BytePosition = BitPosition>>>3;
return Val;
}
private int nextBits(int N)
{
int Pos, Val;
Pos = BitPosition>>>3;
//DM03082004 081.7 int07 add
if (Pos >= data.length - 4)
{
global_error = true;
return 0;
}
Val = (0xFF & data[Pos])<<24 |
(0xFF & data[Pos+1])<<16 |
(0xFF & data[Pos+2])<<8 |
(0xFF & data[Pos+3]);
Val <<= BitPosition & 7;
Val >>>= 32-N;
return Val;
}
private void flushBits(int N)
{
BitPosition += N;
BytePosition = BitPosition>>>3;
}
private void alignToByte()
{
alignToByte(1);
}
private void alignToByte(int N)
{
while ( (7 & BitPosition) != 0 )
flushBits(N);
}
private void alignToWord()
{
if ((1 & BytePosition) != 0 && nextBits(8) != 0x0F)
flushBits(8);
}
public int getTimeOut()
{
return page.getTimeOut();
}
public int decodeDVBSubpicture(byte packet[], int BPos[], Graphics2D big, BufferedImage bimg, long pts, boolean save, boolean preview_visible)
{
data = packet; //packet + 4 bytes overhead
BytePosition = BPos[0]; //bytpos
BitPosition = BPos[0]<<3; //bitpos
this.big = big;
this.bimg = bimg;
this.pts = pts;
this.save = save;
this.preview_visible = preview_visible;
picture_saved = false;
//DM30072004 081.7 int07 add
global_error = false;
flushBits(8); //padding
int stream_ident = getBits(8); // 0 = std
int segment_type = 0;
int sync_byte = 0;
//DM30072004 081.7 int07 changed
//while ( nextBits(8) == 0x0F )
for (int ret; BytePosition < data.length - 4; )
{
ret = nextBits(8);
addBigMessage("ret " + Integer.toHexString(ret) + " /bi " + BitPosition + " /by " + BytePosition);
if (ret == 0xFF)
break;
if (ret != 0x0F)
{
flushBits(8);
continue;
}
segment_type = Subtitle_Segment();
alignToByte();
if (global_error && region != null)
region.setError(4);
}
if ( nextBits(8) == 0xFF )
{}
if (picture_saved)
return -1;
return -2;
}
private int Subtitle_Segment()
{
if ( getBits(8) != 0x0F ) //syncbyte
return -1;
int segment_type = getBits(8);
int page_id = getBits(16); //page_id
if ((page_id & fix_page_id) == 0) // exclude unwanted pages
{
stuffing();
return 0xFF;
}
page = epoch.setPage(page_id);
addBigMessage("segm: 0x" + Integer.toHexString(segment_type) + " / " + pts);
switch (segment_type)
{
case 0x10:
return page_composition();
//return segment_type;
case 0x11:
region_composition();
return segment_type;
case 0x12:
CLUT_definition();
return segment_type;
case 0x13:
object_data();
return segment_type;
case 0x80:
end_display();
return segment_type;
case 0xFF:
stuffing();
return segment_type;
default:
stuffing();
return segment_type;
}
}
private void end_display()
{
int segment_length = getBits(16);
flushBits(segment_length * 8); //DM18062004 081.7 int05 changed
}
private void stuffing()
{
int segment_length = getBits(16); //+ BytePosition;
flushBits(segment_length * 8); //DM18062004 081.7 int05 changed
}
private void prepare_output()
{
//DM26052004 081.7 int03 changed
//long new_time_out = (long)Math.round((pts - page.getTimeIn()) / 900.0);
long new_time_out = 1L + ((pts - page.getTimeIn()) / 1000);
if (page.getTimeOut() > 0 && new_time_out > page.getTimeOut())
new_time_out = page.getTimeOut();
int page_pixel_data[] = new int[page.getWidth() * page.getHeight()];
for (int y = 0; y < page.getHeight(); y++)
System.arraycopy(preview_pixel_data, page.getX() + ((page.getY() + y) * width), page_pixel_data, y * page.getWidth(), page.getWidth());
if (page.getWriteStatus())
BMP.savePixels( new Bitmap( page.getX(), page.getY(), page.getWidth(), page.getHeight(), page_pixel_data, IRD, page.getId(), region.getId(), 0, page.getTimeIn(), (int)new_time_out));
if (preview_visible)
bimg.setRGB(page.getX(), page.getY(), page.getWidth(), page.getHeight(), page_pixel_data, 0, page.getWidth());
paintRegionBorder(page.getX(), page.getY(), page.getWidth(), page.getHeight());
picture_saved = true;
addBigMessage("time: in " + page.getTimeIn() + " /len " + new_time_out + " /save " + save + " /prev " + preview_visible);
}
private int page_composition()
{
int segment_end = getBits(16) + BytePosition;
int segment_type = 0x10;
int time_out = getBits(8) * 100; //page_time_out, milliseconds (here ticks) 'til disappearing without another erase event
page.setVersionNumber(getBits(4)); //page_version_number, if number exists, update isn't necessary
page.setState(getBits(2)); //page_state
//0 = only updates of this page contents
//1 = new page instance, all contents with new definitions
//2 = new epoch, see 1, reset all, complete dec. model (IRD) changes possible
flushBits(2);
addBigMessage("pagecomp: state " + page.getState() + " /page " + page.getId() + " /pv " + page.getVersionNumber() + " /to " + time_out);
if (page.getState() > 0)
{
clearBackground();
page.clearArea();
}
for (Enumeration e = epoch.getRegions(); e.hasMoreElements() ; )
{
region = epoch.setRegion(Integer.parseInt(e.nextElement().toString()));
//DM23062004 081.7 int05 add
if (region.getErrors() > 0)
{
X.Msg(Resource.getString("subpicture.msg.error.dvbdecoding", "" + region.getErrors(), "" + region.getId(), "" + page.getTimeIn()));
//region.setActive(false);
}
addBigMessage("enum: region " + region.getId() + " /err " + region.getErrors() + " /acti " + region.isActive() + " /chng " + region.isChanged() + " /ti_o " + time_out);
//
if ( page.getState() == 0 && time_out > 6000 )
continue;
//
region.setError(0); //DM23062004 081.7 int05 add
if ( !region.isActive() || !region.isChanged() )
continue;
region.setChanged(false);
pixel_data = region.getPixel();
page.addArea(region.getXBound(), region.getYBound(), region.getWidth(), region.getHeight());
for (int a = 0; a < region.getHeight(); a++)
System.arraycopy(pixel_data, a * region.getWidth(), preview_pixel_data, region.getXBound() + ((region.getYBound() + a) * width), region.getWidth());
segment_type = 0x80;
addBigMessage("addToBMP: region " + region.getId());
addBigMessage("newSize: x " + page.getX() + " y " + page.getY() + " w " + page.getWidth() + " h " + page.getHeight());
}
if (segment_type == 0x80)
prepare_output();
if (page.getState() > 0) //if not update, page content has to die
{
page = epoch.newPage(page.getId());
page.setTimeIn(pts);
page.setWriteStatus(save);
epoch.clearRegions();
epoch.clearObjects();
}
page.setTimeOut(time_out); //page_time_out, seconds to stand
while (BytePosition < segment_end)
{
region = epoch.setRegion(getBits(8)); //region_ids of this epoch
region.setActive(true);
region.setChanged(true);
flushBits(8);
region.setHorizontalAddress(getBits(16)); // start_x
region.setVerticalAddress(getBits(16)); // start_y
addBigMessage("addreg: reg " + region.getId() + " /x " + region.getXBound() + " /y " + region.getYBound());
}
return segment_type;
}
private void region_composition()
{
int segment_end = getBits(16) + BytePosition;
region = epoch.setRegion(getBits(8)); //region_id
region.setVersionNumber(getBits(4)); //region_version_number, if number exists, update isn't necessary
region.setFillFlag(getBits(1));
flushBits(3);
region.setWidth(getBits(16));
region.setHeight(getBits(16));
region.setLevelOfCompatibility(getBits(3));
region.setDepth(getBits(3));
flushBits(2);
int CLUT_id = getBits(8);
clut = epoch.setCLUT(CLUT_id); //CLUT_id
region.setCLUT_id(CLUT_id);
region.setPixelCode_8bit(getBits(8));
region.setPixelCode_4bit(getBits(4));
region.setPixelCode_2bit(getBits(2));
pixel_data = region.initPixel();
flushBits(2);
paintRegionBackground();
addBigMessage("regcomp: page " + page.getId() + " /reg " + region.getId() + " /rv " + region.getVersionNumber() + " /lv " + region.getCompatibility() + " /clut " + clut.getId() + " /activ " + region.isActive());
while (BytePosition < segment_end)
{
object = epoch.setObject(getBits(16)); //object_id
object.setRegionId(region.getId());
object.setType(getBits(2));
object.setProvider(getBits(2));
object.setHorizontalPosition(getBits(12));
flushBits(4);
object.setVerticalPosition(getBits(12));
if (object.getType() == 1 || object.getType() == 2) //character or ch.strings
{
object.setForegroundCode(getBits(8)); //foreground_pixel_code
object.setBackgroundCode(getBits(8)); //background_pixel_code
}
addBigMessage("addobj: reg " + region.getId() + " /obj " + Integer.toHexString(object.getId()).toUpperCase() + " /x " + object.getHorizontalPosition() + " /y " + object.getVerticalPosition());
}
}
private void CLUT_definition()
{
int segment_end = getBits(16) + BytePosition;
clut = epoch.setCLUT(getBits(8)); //CLUT_id
clut.setVersionNumber(getBits(4)); //CLUT_version_number, if number exists, update isn't necessary
addBigMessage("clutcomp: " + clut.getId() + " /v " + clut.getVersionNumber());
flushBits(4);
//user table
//DM13062004 081.7 int04 add
if (user_table_enabled)
{
setUserClut();
flushBits( (segment_end - BytePosition) * 8);
return;
}
while (BytePosition < segment_end)
{
int CLUT_entry_id = getBits(8);
int CLUT_flag_2bit_entry = getBits(1);
int CLUT_flag_4bit_entry = getBits(1);
int CLUT_flag_8bit_entry = getBits(1);
int flag = CLUT_flag_8bit_entry<<3
| CLUT_flag_4bit_entry<<2
| CLUT_flag_2bit_entry<<1;
flushBits(4);
int full_range_flag = getBits(1);
int ARGB, Y, Cr, Cb, T;
if (full_range_flag == 1)
{
Y = getBits(8);
Cr = getBits(8);
Cb = getBits(8);
T = getBits(8);
}
else //only MSB transmitted
{
Y = getBits(6)<<2;
Cr = getBits(4)<<4;
Cb = getBits(4)<<4;
T = getBits(2)<<6;
}
ARGB = YUVtoRGB(Y, Cr, Cb, T);
addBigMessage("addclut: " + CLUT_entry_id + " /flag " + Integer.toHexString(flag).toUpperCase() + " /ARGB " + Integer.toHexString(ARGB).toUpperCase() + " /range " + full_range_flag);
for (int i=0; i<3; i++)
clut.setClutEntry(mapColorIndex(CLUT_entry_id, region.getDepth(), 2<<i), (flag & 2<<i), ARGB);
}
}
private void object_data()
{
int segment_end = getBits(16) + BytePosition;
object = epoch.setObject(getBits(16)); //object_id
int region_id = object.getRegionId();
if (region_id < 0)
{
flushBits( (segment_end - BytePosition) * 8); //DM18062004 081.7 int05 changed
addBigMessage("object_id " + object.getId() + " with no region_id");
return;
}
region = epoch.setRegion(region_id); //use the right region
region.resetXY();
pixel_data = region.getPixel();
addBigMessage("objdata: reg " + region.getId() + " /obj " + Integer.toHexString(object.getId()).toUpperCase());
object.setVersionNumber(getBits(4)); //object_version_number, if number exists, update isn't necessary
int object_coding_method = getBits(2);
object.setNonModify(getBits(1)); //non_modifying_colour_flag
flushBits(1);
/**
* modify user_table here, if no clut definition were sent
*/
if (user_table_enabled && clut.getModifyFlags() == 0)
setUserClut();
if (object_coding_method == 0) //pixels
{
int top_field_data_block_end = getBits(16);
int bottom_field_data_block_end = getBits(16);
object.setCopyTopFieldFlag(bottom_field_data_block_end);
top_field_data_block_end += BytePosition;
while (BytePosition < top_field_data_block_end)
pixel_block();
region.nextField();
bottom_field_data_block_end += BytePosition;
while (BytePosition < bottom_field_data_block_end) //if 0-length, copy top field line
pixel_block();
//alignToWord(); // flush 8 bits if not aligned
}
if (object_coding_method == 1) //text chars
{
int number_of_codes = getBits(8); //all chars in a line
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -