/* * @(#)SCAN.java - pre-scanning to check supported files * * Copyright (c) 2002-2009 by dvb.matt, All Rights Reserved. * * This file is part of ProjectX, a free Java based demux utility. * By the authors, ProjectX is intended for educational purposes only, * as a non-commercial test project. * * * 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-1307 USA * */ package net.sourceforge.dvb.projectx.parser; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.File; import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import net.sourceforge.dvb.projectx.audio.AudioFormat; import net.sourceforge.dvb.projectx.common.Resource; import net.sourceforge.dvb.projectx.common.Common; import net.sourceforge.dvb.projectx.video.Video; import net.sourceforge.dvb.projectx.xinput.XInputFile; import net.sourceforge.dvb.projectx.common.Keys; import net.sourceforge.dvb.projectx.parser.CommonParsing; import net.sourceforge.dvb.projectx.xinput.StreamInfo; public class Scan extends Object { private final String msg_1 = Resource.getString("scan.msg1"); private final String msg_2 = Resource.getString("scan.msg2"); private final String msg_3 = Resource.getString("scan.msg3"); private final String msg_4 = Resource.getString("scan.msg4"); private final String msg_5 = Resource.getString("scan.msg5"); private final String msg_6 = Resource.getString("scan.msg6"); private final String msg_7 = Resource.getString("scan.msg7"); private final String msg_8 = Resource.getString("scan.msg8"); private final String msg_9 = Resource.getString("scan.msg9"); private String addInfo = ""; private String playtime = ""; private boolean hasVideo = false; private byte[] vbasic = new byte[12]; private int filetype = CommonParsing.Unsupported; private ArrayList pidlist; private ArrayList video_streams; private ArrayList audio_streams; private ArrayList ttx_streams; private ArrayList pic_streams; private ArrayList unknown_streams; private AudioFormat Audio = new AudioFormat(); /** * */ public Scan() { video_streams = new ArrayList(); audio_streams = new ArrayList(); ttx_streams = new ArrayList(); pic_streams = new ArrayList(); unknown_streams = new ArrayList(); pidlist = new ArrayList(); } /** * performs a pre-scan of XInputFile and saves results in StreamInfo of XInputFile */ public void getStreamInfo(XInputFile aXInputFile) { getStreamInfo(aXInputFile, 0, -1); } /** * performs a pre-scan of XInputFile and saves results in StreamInfo of XInputFile */ public void getStreamInfo(XInputFile aXInputFile, long position) { getStreamInfo(aXInputFile, position, -1); } /** * performs a pre-scan of XInputFile and saves results in StreamInfo of XInputFile * forces a streamtype assignment */ public void getStreamInfo(XInputFile aXInputFile, long position, int assigned_streamtype) { String _name = getName(aXInputFile); String _location = getLocation(aXInputFile); String _date = getDate(aXInputFile); String _size = getSize(aXInputFile); StreamInfo streamInfo = aXInputFile.getStreamInfo(); if (aXInputFile.exists()) { if (streamInfo == null) streamInfo = new StreamInfo(); // type must be first when scanning streamInfo.setStreamInfo(aXInputFile.getFileType().getName(), getType(aXInputFile, position, assigned_streamtype), _name, _location, _date, _size, getPlaytime(), getVideo(), getAudio(), getText(), getPics()); streamInfo.setStreamType(filetype, addInfo); streamInfo.setPIDs(getPIDs()); streamInfo.setVideoHeader(getVBasic()); } else streamInfo = new StreamInfo("none", Resource.getString("ScanInfo.NotFound"), _name, _location, "", "", ""); aXInputFile.setStreamInfo(streamInfo); } /** * saves video basic info, for patch */ private byte[] getVBasic() { if (hasVideo) return vbasic; return null; } /** * */ private String getType(XInputFile aXInputFile, long position, int assigned_streamtype) { if (position > 0) filetype = testFile(aXInputFile, true, position, assigned_streamtype); else filetype = testFile(aXInputFile, true, assigned_streamtype); return (Keys.ITEMS_FileTypes[getStreamType()].toString() + " [" + getStreamSubType() + "] " + addInfo); } /** * */ private int getStreamType() { return (0xFF & filetype); } /** * */ private int getStreamSubType() { return (0xFF & filetype>>>8); } /** * */ private String getName(XInputFile aXInputFile) { return aXInputFile.getName(); } /** * */ private String getLocation(XInputFile aXInputFile) { return aXInputFile.getParent(); } /** * */ private String getDate(XInputFile aXInputFile) { return DateFormat.getDateInstance(DateFormat.LONG).format(new Date(aXInputFile.lastModified())) + " " + DateFormat.getTimeInstance(DateFormat.LONG).format(new Date(aXInputFile.lastModified())); } /** * */ private String getSize(XInputFile aXInputFile) { long length = aXInputFile.length(); return (String.valueOf(length / 1048576) + " MB (" + Common.formatNumber(length) + " " + Resource.getString("ScanInfo.Bytes") + ")"); } /** * */ private String getPlaytime() { return playtime; } /** * */ private Object[] getVideo() { return video_streams.toArray(); } /** * */ private Object[] getAudio() { return audio_streams.toArray(); } /** * */ private Object[] getText() { return ttx_streams.toArray(); } /** * */ private Object[] getPics() { return pic_streams.toArray(); } /** * */ private Object[] getPIDs() { return pidlist.toArray(); } /** * */ private String getAudioTime(long len) { return Common.formatTime_1((len * 8000) / Audio.getBitrate()); } /** * */ private int testFile(XInputFile aXInputFile, boolean more, int assigned_streamtype) { long len = aXInputFile.length(); int ret = testFile(aXInputFile, more, 0, assigned_streamtype); if (ret != 0) return ret; // if type is not yet detected, try it again on a later position (10% of length) return testFile(aXInputFile, more, len / 10, assigned_streamtype); } /** * */ private int testFile(XInputFile aXInputFile, boolean more, long position, int assigned_streamtype) { video_streams.clear(); audio_streams.clear(); ttx_streams.clear(); pic_streams.clear(); unknown_streams.clear(); pidlist.clear(); addInfo = ""; playtime = ""; hasVideo = false; long size = 0; int buffersize = Integer.parseInt(Common.getSettings().getProperty(Keys.KEY_ScanBuffer)); if (buffersize <= 0) buffersize = 1024000; int bs0 = buffersize / 100; int bs1 = buffersize / 50; int bs2 = buffersize / 10; int bs3 = buffersize / 4; int bs4 = buffersize - 65536; byte[] check = new byte[buffersize]; ByteArrayOutputStream bytecheck = new ByteArrayOutputStream(); try { size = aXInputFile.length(); XInputFile aXif = aXInputFile.getNewInstance(); if (aXif == null) { check = null; return CommonParsing.Unsupported; } aXif.randomAccessSingleRead(check, position); int returncode = -1; // int[] mapping = { -1, -1, 5, 4, 4, 3, 2, 6, 6, 10, 9, 8, 8, 7, 7, 0, 0, 1 }; //1+16 int[] mapping = { -1, -1, 5, 4, 4, 3, 2, 6, 6, 10, 9, 8, 8, 7, 7, 0, 0, 11 }; //1+16 int streamtype = mapping[assigned_streamtype + 1]; scanloop: for (int index = streamtype; returncode < CommonParsing.Unsupported; index++) { returncode = scanPjxOwn(aXif, check, bs0); if (returncode != -1) break; switch (index) { case -1: break; case 0: returncode = scanRiffAudio(check, bs0, more, size); break; case 1: //empty // returncode = scanSubpicture(check, bs0, more, aXif.toString()); break; case 2: returncode = scanTS(check, bs4, more); break; case 3: returncode = scanPVA(check, bs1, more); break; case 4: returncode = scanMpg12(check, bs2, bs1, more); break; case 5: returncode = scanPrimaryPES(check, bs4, bs3, more); break; case 6: returncode = scanSecondaryPES(check, bs2, bs3, more); break; case 7: returncode = scanDtsAudio(check, bs1, more, size); break; case 8: returncode = scanAc3Audio(check, bs1, more, size); break; case 9: returncode = scanMpgAudio(check, bs1, more, size); break; case 10: returncode = scanMpgVideo(check, bs3, more); break; case 11: returncode = scanSubpicture(check, bs0, more, aXif.toString()); break; default: break scanloop; } if (assigned_streamtype != -1) returncode = assigned_streamtype; } check = null; if (returncode > 0) return returncode; } catch (Exception e) { playtime = msg_8; Common.setExceptionMessage(e); } check = null; return CommonParsing.Unsupported; } /** * */ private int scanPjxOwn(XInputFile xInputFile, byte[] check, int buffersize) throws Exception { for (int i = 0; i < 16; i++) { if (check[i] != CommonParsing.PTSVideoHeader[i]) return -1; } int length = (int) xInputFile.length(); byte[] data = new byte[length]; xInputFile.randomAccessSingleRead(data, 0); long pts1 = CommonParsing.readPTS(data, 16, 8, false, false); long pts2 = CommonParsing.readPTS(data, length - 24, 8, false, false); long pts3 = CommonParsing.readPTS(data, 32, 8, false, false); long pts4 = CommonParsing.readPTS(data, length - 8, 8, false, false); playtime = "Src " + Common.formatTime_1(pts1 / 90L) + " -- " + Common.formatTime_1(pts2 / 90L); playtime += " / Out " + Common.formatTime_1(pts3 / 90L) + " -- " + Common.formatTime_1(pts4 / 90L); return CommonParsing.PJX_PTS_TYPE; } /** * */ private int scanRiffAudio(byte[] check, int buffersize, boolean more, long size) throws Exception { Audio.setNewType(CommonParsing.WAV_AUDIO); riffcheck: for (int i = 0, ERRORCODE; i < buffersize; i++) //compressed as AC3,MPEG is currently better detected as ES-not RIFF { ERRORCODE = Audio.parseHeader(check, i); if (ERRORCODE > -1) { Audio.saveHeader(); if (more) { audio_streams.add(Audio.displayHeader()); playtime = getAudioTime(size); } if (ERRORCODE > 0) return CommonParsing.ES_RIFF_TYPE; else if (Audio.getLastModeExtension() > 1) break riffcheck; else return CommonParsing.ES_cRIFF_TYPE; } } return -1; } /** * */ private int scanSubpicture(byte[] check, int buffersize, boolean more, String supname) throws Exception { supcheck: for (int i = 0, supframe_size, supframe_link, supframe_check, supframe_check2; i < buffersize; i++) { if (check[i] != 0x53 || check[i + 1] != 0x50) continue supcheck; supframe_size = (0xFF & check[i + 10])<<8 | (0xFF & check[i + 11]); supframe_link = (0xFF & check[i + 12])<<8 | (0xFF & check[i + 13]); supframe_check = (0xFF & check[i + 12 + supframe_link])<<8 | (0xFF & check[i + 13 + supframe_link]); // supframe_check2 = (0xFF & check[i + 36 + supframe_link])<<8 | (0xFF & check[i + 37 + supframe_link]); // if (supframe_link == supframe_check - 24 && supframe_check == supframe_check2) // 24 is wrong if ((0xFF & check[i + supframe_size + 9]) == 0xFF) // end stuffing, at least one { if (more) { // int b = i + 14 + supframe_link, c = b + 24, d, xa, xe, ya, ye; // 24 is wrong int b = i + 14 + supframe_link, c = b + 24, d, xa, xe, ya, ye; for (d = b; d < c; d++) { switch(0xFF & check[d]) { case 1: d++; continue; case 2: d += 24; // is wrong continue; case 3: case 4: d += 2; continue; case 6: d += 4; continue; case 5: xa= (0xFF & check[++d])<<4 | (0xF0 & check[++d])>>>4; xe= (0xF & check[d])<<8 | (0xFF & check[++d]); ya= (0xFF & check[++d])<<4 | (0xF0 & check[++d])>>>4; ye= (0xF & check[d])<<8 | (0xFF & check[++d]); pic_streams.add("up.left x" + xa + ",y" + ya + " @ size " + (xe - xa + 1) + "*" + (ye - ya + 1)); continue; } break; } byte packet[] = new byte[10 + supframe_size]; System.arraycopy(check, i, packet, 0, 10 + supframe_size); // shows first found subpicture scaled on OSD Common.getSubpictureClass().setColorTable(setIFOColorTable(supname)); Common.getSubpictureClass().decode_picture(packet, 10, true, new String[2]); } return CommonParsing.ES_SUP_TYPE; } } return -1; } /** * read colors from ifo (pjx auto generated) */ private int[] setIFOColorTable(String supname) { try { String nsupname = supname + ".IFO"; File f = new File(nsupname); if (!f.exists()) { f = new File(supname.substring(0, supname.lastIndexOf(".")) + ".IFO"); if (!f.exists()) return null; //Common.setMessage("!> no existing .ifo file : '" + supname + "'"); } XInputFile xif = new XInputFile(f); byte[] data = new byte[64]; int[] values = new int[16]; xif.randomAccessSingleRead(data, 0x10B4); //read 16x 4bytes from pos 0x10B4 for (int i = 0, j = values.length; i < j; i++) values[i] = YUVtoRGB(CommonParsing.getIntValue(data, i * 4, 4, !CommonParsing.BYTEREORDERING)); return values; } catch (Exception e) { Common.setExceptionMessage(e); } return null; } /** * convert colors from ifo (pjx auto generated) */ private int YUVtoRGB(int values) { int Y = 0xFF & values>>16; int Cr = 0xFF & values>>8; int Cb = 0xFF & values; if (Y == 0) return 0; int R = (int)((float)Y +1.402f * (Cr-128)); int G = (int)((float)Y -0.34414 * (Cb-128) -0.71414 * (Cr-128)); int B = (int)((float)Y +1.722 * (Cb-128)); R = R < 0 ? 0 : (R > 0xFF ? 0xFF : R); G = G < 0 ? 0 : (G > 0xFF ? 0xFF : G); B = B < 0 ? 0 : (B > 0xFF ? 0xFF : B); int T = 0xFF; return (T<<24 | R<<16 | G<<8 | B); } /** * scan TS */ private int scanTS(byte[] check, int buffersize, boolean more) throws Exception { // 188bytes TS for (int i = 0, j = 188; i < buffersize; i++) { if ( check[i] != 0x47 || check[i + j] != 0x47 || check[i + j * 2] != 0x47 || check[i + j * 3] != 0x47 || check[i + j * 4] != 0x47 || check[i + j * 5] != 0x47 || check[i + j * 6] != 0x47) continue; readPMT(check, i, 0); if (pidlist.isEmpty()) scanTSPids(check, i, buffersize, 0, more); return scanTSSubtype(check, buffersize); } // 192bytes TS for (int i = 0, j = 192; i < buffersize; i++) { if ( check[i] != 0x47 || check[i + j] != 0x47 || check[i + j * 2] != 0x47 || check[i + j * 3] != 0x47 || check[i + j * 4] != 0x47 || check[i + j * 5] != 0x47 || check[i + j * 6] != 0x47) continue; readPMT(check, i, 4); if (pidlist.isEmpty()) scanTSPids(check, i, buffersize, 4, more); return CommonParsing.TS_TYPE_192BYTE; } return -1; } /** * scan TS sub type */ private int scanTSSubtype(byte[] check, int buffersize) { //TFrc TF5x00 if (check[0] == 0x54 && check[1] == 0x46 && check[2] == 0x72 && check[3] == 0x63 && check[4] == 0x50 && check[5] == 0) { /** jkit 23012009 * find header type (DVB-s,-t,-c), to know the * start of the event info structure * * validate three typical values in the transponder info * for each type and take the best matching */ int isDVBsVal = 0; int isDVBtVal = 0; int isDVBcVal = 0; int offset = 0x34; // test as dvb-s transponder info byte Polarity = check[offset + 1]; long Frequency = (check[offset + 4] << 24) | (check[offset + 5] << 16) | (check[offset + 6] << 8) | check[offset + 7]; int Symbol_Rate = (check[offset + 8] << 8) | check[offset + 9]; if ((Polarity & 0x6F) == 0) isDVBsVal++; else isDVBsVal--; if ((Symbol_Rate > 2000) && (Symbol_Rate < 30000)) isDVBsVal++; else isDVBsVal--; if ((Symbol_Rate > 10000) && (Symbol_Rate < 13000)) isDVBsVal++; else isDVBsVal--; // test as dvb-t transponder info byte Bandwidth = check[offset + 2]; Frequency = (check[offset + 4] << 24) | (check[offset + 5] << 16) | (check[offset + 6] << 8) | check[offset + 7]; byte LP_HP_Stream = check[offset + 10]; if ((Bandwidth >= 6) && (Bandwidth <= 8)) isDVBtVal++; if (((Frequency >= 174000) && (Frequency <= 230000)) || ((Frequency >= 470000) && (Frequency <= 862000))) isDVBtVal++; if ((LP_HP_Stream & 0xFE) == 0) isDVBtVal++; // test as dvb-c transponder info Frequency = (check[offset + 0] << 24) | (check[offset + 1] << 16) | (check[offset + 2] << 8) | check[offset + 3]; Symbol_Rate = (check[offset + 4] << 8) | check[offset + 5]; byte Modulation = check[offset + 10]; if ((Frequency >= 47000) && (Frequency <= 862000)) isDVBcVal++; if ((Symbol_Rate > 2000) && (Symbol_Rate < 30000)) isDVBcVal++; if (Modulation <= 4) isDVBcVal++; if ((isDVBsVal >= isDVBcVal) && (isDVBsVal >= isDVBtVal)) { // event_info_offset = 0x44; // default //Common.setMessage("-> topfield header has DVB-s format"); return CommonParsing.TS_TYPE_TF5X00; } else if (isDVBtVal > isDVBcVal) { //event_info_offset = 0x44; // default //Common.setMessage("-> topfield header has DVB-t format"); return CommonParsing.TS_TYPE_TF5X00; } else { /** * the transponder info structure of the TF5200 is shorter, * so the following data starts at 0x40 */ //event_info_offset = 0x40; //Common.setMessage("-> topfield header has DVB-c format"); return CommonParsing.TS_TYPE_TF5X00C; } } //TF4000 else if (check[0] != 0x47 && check[188] != 0x47 && check[376] != 0x47 && check[564] == 0x47 && check[752] == 0x47 && check[940] == 0x47) return CommonParsing.TS_TYPE_TF4000; //Handan/Hojin for (int i = 0, j = buffersize - 10; i < j; i++) { if (check[i] != 0x5B || check[i + 1] != 0x48 || check[i + 2] != 0x4F || check[i + 3] != 0x4A || check[i + 4] != 0x49 || check[i + 5] != 0x4E || check[i + 6] != 0x20 || check[i + 7] != 0x41) continue; return CommonParsing.TS_TYPE_HANDAN; } //comag if (check[0] == 0 && check[1] == 0 && check[2] == 1 && check[3] == (byte)0xBA && check[14] == 0 && check[15] == 0 && check[16] == 1 && check[17] == (byte)0xBF) return CommonParsing.TS_TYPE_COMAG; return CommonParsing.TS_TYPE; } /** * */ private void scanTSPids(byte[] check, int i, int buffersize, int lead, boolean more) throws Exception { String str; mpegtscheck: for (int pid, scrambling, j = 188 + lead; i < buffersize; i++) { if ( check[i] != 0x47 || check[i + j] != 0x47 || check[i + j * 2] != 0x47) continue mpegtscheck; pid = (0x1F & check[i + 1])<<8 | (0xFF & check[i + 2]); scrambling = (0xC0 & check[i + 3])>>>6; // scrambling i += j - 1; str = "PID 0x" + Integer.toHexString(pid).toUpperCase(); if (scrambling > 0) str += "-($)"; if (!video_streams.contains(str)) video_streams.add(str); } addInfo += " (no PMT found)"; } /** * */ private int scanPVA(byte[] check, int buffersize, boolean more) throws Exception { pvacheck: for (int i = 0; i < buffersize; i++) { if ( check[i] != 0x41 || check[i + 1] != 0x56 || check[i + 4] != 0x55 ) continue pvacheck; int next = i + 8 + ((0xFF & check[i + 6])<<8 | (0xFF & check[i + 7])); if ( check[next] != 0x41 || check[next + 1] != 0x56 || check[next + 4] != 0x55 ) continue pvacheck; else { if (more) loadPVA(check, i); return CommonParsing.PVA_TYPE; } } return -1; } /** * */ private int scanMpg12(byte[] check, int buffersize_1, int buffersize_2, boolean more) throws Exception { boolean nullpacket = false; mpgcheck: for (int i = 0, j, flag, returncode; i < buffersize_1; i++) { if ((returncode = CommonParsing.validateStartcode(check, i)) < 0 || CommonParsing.getPES_IdField(check, i) != CommonParsing.PACK_START_CODE) { i += (returncode < 0 ? -returncode : 4) - 1; continue mpgcheck; } flag = 0xC0 & check[i + 4]; if (flag == 0) { j = i + 12; if (CommonParsing.validateStartcode(check, j) < 0 || CommonParsing.getPES_IdField(check, j) < CommonParsing.SEQUENCE_HEADER_CODE) continue mpgcheck; if (more) loadMPG2(check, i, false, true, nullpacket, buffersize_2); return CommonParsing.MPEG1PS_TYPE; } else if (flag == 0x40 ) { j = i + 14 + (7 & check[i + 13]); if (CommonParsing.validateStartcode(check, j) < 0 || CommonParsing.getPES_IdField(check, j) < CommonParsing.SEQUENCE_HEADER_CODE) continue mpgcheck; if (more) loadMPG2(check, i, Common.getSettings().getBooleanProperty(Keys.KEY_simpleMPG), false, nullpacket, buffersize_2); return CommonParsing.MPEG2PS_TYPE; } } return -1; } /** * */ private int scanPrimaryPES(byte[] check, int buffersize_1, int buffersize_2, boolean more) throws Exception { boolean nullpacket = false; vdrcheck: for (int i = 0, returncode; i < buffersize_1; i++) { if ((returncode = CommonParsing.validateStartcode(check, i)) < 0 || (0xF0 & CommonParsing.getPES_IdField(check, i)) != 0xE0) { i += (returncode < 0 ? -returncode : 4) - 1; continue vdrcheck; } int next = i + 6 + CommonParsing.getPES_LengthField(check, i); if ( (next == i + 6) && (0xC0 & check[i + 6]) == 0x80 && (0xC0 & check[i + 8]) == 0) { addInfo = " !!(VPacketLengthField is 0)"; next = i; nullpacket = true; } if (CommonParsing.validateStartcode(check, next) < 0) continue vdrcheck; else { if (more) loadMPG2(check, i, !Common.getSettings().getBooleanProperty(Keys.KEY_enhancedPES), false, nullpacket, buffersize_2); return CommonParsing.PES_AV_TYPE; } } return -1; } /** * */ private int scanSecondaryPES(byte[] check, int buffersize_1, int buffersize_2, boolean more) throws Exception { boolean nullpacket = false; rawcheck: for (int i = 0, returncode; i < buffersize_1; i++) { if ((returncode = CommonParsing.validateStartcode(check, i)) < 0) { i += (-returncode) - 1; continue rawcheck; } if ( (0xE0 & check[i + 3]) == 0xC0 || (0xFF & check[i + 3]) == 0xBD ) { int next = i + 6 + CommonParsing.getPES_LengthField(check, i); if (CommonParsing.validateStartcode(check, next) < 0) continue rawcheck; if ( (0xE0 & check[i + 3]) == 0xC0 && (0xE0 & check[i + 3]) == (0xE0 & check[next + 3]) ) { if (more) loadMPG2(check, i, true, false, nullpacket, buffersize_2); return CommonParsing.PES_MPA_TYPE; } else if ( (0xFF & check[i + 3]) == 0xBD && ((0xFF & check[next + 3]) == 0xBD || (0xFF & check[next + 3]) == 0xBE)) { if (more) { if (check[i + 8] == 0x24 && (0xF0 & check[i + 9 + 0x24])>>>4 == 1) { addInfo = " (TTX)"; ttx_streams.add("SubID 0x" + Integer.toHexString((0xFF & check[i + 9 + 0x24])).toUpperCase()); } else loadMPG2(check, i, true, false, nullpacket, buffersize_2); } return CommonParsing.PES_PS1_TYPE; } } } return -1; } /** * */ private int scanDtsAudio(byte[] check, int buffersize, boolean more, long size) throws Exception { Audio.setNewType(CommonParsing.DTS_AUDIO); audiocheck: for (int i = 0; i < buffersize; i++) { /* DTS stuff taken from the VideoLAN project. */ /* Added by R One, 2003/12/18. */ if (Audio.parseHeader(check, i) < 1) continue; for (int b = 0; b < 15; b++) { if (Audio.parseNextHeader(check, i + Audio.getSize() + b) != 1) continue; if ( (0xFF & check[i + Audio.getSize()]) > 0x7F || (0xFF & check[i + Audio.getSize()]) == 0 ) //smpte continue audiocheck; if (more) { audio_streams.add(Audio.saveAndDisplayHeader()); playtime = getAudioTime(size); } if (b == 0) return CommonParsing.ES_DTS_TYPE; else return CommonParsing.ES_DTS_A_TYPE; } if (Common.getSettings().getBooleanProperty(Keys.KEY_AudioPanel_allowSpaces)) { if (more) audio_streams.add(Audio.saveAndDisplayHeader()); playtime = getAudioTime(size); return CommonParsing.ES_DTS_TYPE; } } return -1; } /** * */ private int scanAc3Audio(byte[] check, int buffersize, boolean more, long size) throws Exception { Audio.setNewType(CommonParsing.AC3_AUDIO); audiocheck: for (int i = 0, gg=0; i < buffersize; i++) { if (Audio.parseHeader(check, i) < 1) continue; for (int b = 0; b < 17; b++) { if (Audio.parseNextHeader(check, i + Audio.getSize() + b) != 1) continue; if ( (0xFF & check[i + Audio.getSize()]) > 0x3F || (0xFF & check[i + Audio.getSize()]) == 0 ) //smpte continue audiocheck; if (more) { audio_streams.add(Audio.saveAndDisplayHeader()); playtime = getAudioTime(size); } if (b == 0) return CommonParsing.ES_AC3_TYPE; else return CommonParsing.ES_AC3_A_TYPE; } if (Common.getSettings().getBooleanProperty(Keys.KEY_AudioPanel_allowSpaces)) { if (more) audio_streams.add(Audio.saveAndDisplayHeader()); playtime = getAudioTime(size); return CommonParsing.ES_AC3_TYPE; } } return -1; } /** * */ private int scanMpgAudio(byte[] check, int buffersize, boolean more, long size) throws Exception { Audio.setNewType(CommonParsing.MPEG_AUDIO); audiocheck: for (int i = 0; i < buffersize; i++) { if (Audio.parseHeader(check, i) < 1) continue; if (!Common.getSettings().getBooleanProperty(Keys.KEY_AudioPanel_allowSpaces) && Audio.parseNextHeader(check, i + Audio.getSize()) < 0) continue audiocheck; // if (!Common.getSettings().getBooleanProperty(Keys.KEY_AudioPanel_allowSpaces) && Audio.parseNextHeader(check, i + Audio.getSize() + Audio.getNextSize()) < 0) continue audiocheck; // if (more) { audio_streams.add(Audio.saveAndDisplayHeader()); playtime = getAudioTime(size); } return CommonParsing.ES_MPA_TYPE; } return -1; } /** * */ private int scanMpgVideo(byte[] check, int buffersize, boolean more) throws Exception { mpvcheck: if (checkVid(check, buffersize)) return CommonParsing.ES_MPV_TYPE; return -1; } /** * */ private int AC3Audio(byte[] check) { Audio.setNewType(CommonParsing.AC3_AUDIO); audiocheck: for (int a=0; a<10000; a++) { if (Audio.parseHeader(check, a) < 0) continue audiocheck; for (int b = 0; b < 17; b++) { if (Audio.parseNextHeader(check, a + Audio.getSize() + b) == 1) { if ( (0xFF & check[a + Audio.getSize()]) > 0x3f || (0xFF & check[a + Audio.getSize()]) == 0 ) //smpte continue audiocheck; audio_streams.add(Audio.saveAndDisplayHeader()); return 1; } } } return 0; } /* DTS stuff taken from the VideoLAN project. */ /* Added by R One, 2003/12/18. */ private int DTSAudio(byte[] check) { Audio.setNewType(CommonParsing.DTS_AUDIO); audiocheck: for (int i = 0; i < 10000; i++) { if (Audio.parseHeader(check, i) < 0) continue audiocheck; for (int j = 0; j < 15; j++) { if (Audio.parseNextHeader(check, i + Audio.getSize() + j) == 1) { if ( (0xFF & check[i + Audio.getSize()]) > 0x7F || (0xFF & check[i + Audio.getSize()]) == 0 ) //smpte continue audiocheck; audio_streams.add(Audio.saveAndDisplayHeader()); return 1; } } } return 0; } /** * */ private int MPEGAudio(byte[] check) { Audio.setNewType(CommonParsing.MPEG_AUDIO); audiocheck: for (int a = 0; a < 10000; a++) { if (Audio.parseHeader(check, a) < 0) continue audiocheck; if (Audio.parseNextHeader(check, a + Audio.getSize()) < 0) continue audiocheck; audio_streams.add(Audio.saveAndDisplayHeader()); return 1; } return 0; } /** * */ private int LPCMAudio(byte[] check) { Audio.setNewType(CommonParsing.LPCM_AUDIO); audiocheck: for (int a = 0; a < 1000; a++) { if (Audio.parseHeader(check, a) < 0) continue audiocheck; audio_streams.add(Audio.saveAndDisplayHeader()); return 1; } return 0; } /** * */ private byte[] loadPES(byte[] check, int a) { ByteArrayOutputStream bytecheck = new ByteArrayOutputStream(); int jump, offs, len; boolean mpg1; for (; a < check.length; ) { jump = (0xFF & check[a + 4])<<8 | (0xFF & check[a + 5]); mpg1 = (0x80 & check[a + 6]) == 0; offs = a + 6 + ( !mpg1 ? 3 + (0xFF & check[a + 8]) : 0); len = jump - ( !mpg1 ? 3 + (0xFF & check[a + 8]) : 0); if (offs + len > check.length) break; bytecheck.write(check, offs, len); a += 6 + jump; } return bytecheck.toByteArray(); } /** * */ private void loadMPG2(byte[] check, int b, boolean vdr, boolean mpg1, boolean nullpacket, int size) throws IOException { Hashtable table = new Hashtable(); ScanObject scanobject; String str; int jump = 1, id; int end = check.length > 580000 ? 512000 : check.length - 65000; mpg2check: for (int a = b, returncode, subid; a < end; a += jump) { if ((returncode = CommonParsing.validateStartcode(check, a)) < 0) { jump = -returncode; continue mpg2check; } id = CommonParsing.getPES_IdField(check, a); str = String.valueOf(id); if (id == CommonParsing.PACK_START_CODE) { if ((0xC0 & check[4 + a]) == 0) //mpg1 jump = 12; else if ((0xC0 & check[4 + a]) == 0x40) //mpg2 jump = 14 + (7 & check[13 + a]); else jump = 4; } //video mpg e0..ef else if ( (0xF0 & id) == 0xE0 ) { jump = nullpacket ? 2048 : (6 + CommonParsing.getPES_LengthField(check, a)); if (!table.containsKey(str)) table.put(str, new ScanObject(id)); scanobject = (ScanObject)table.get(str); scanobject.write(check, a + 6 + (!mpg1 ? 3 + CommonParsing.getPES_ExtensionLengthField(check, a) : 0), jump - (!mpg1 ? 6 - 3 - CommonParsing.getPES_ExtensionLengthField(check, a) : 0) ); } //audio mpg c0..df else if ( (0xE0 & id) == 0xC0 ) { jump = 6 + CommonParsing.getPES_LengthField(check, a); if (!table.containsKey(str)) table.put(str, new ScanObject(id)); scanobject = (ScanObject)table.get(str); scanobject.write(check, a, jump ); } //private bd else if (id == CommonParsing.PRIVATE_STREAM_1_CODE) { jump = 6 + CommonParsing.getPES_LengthField(check, a); int pes_extensionlength = CommonParsing.getPES_ExtensionLengthField(check, a); boolean pes_alignment = (4 & check[a + 6]) != 0; int pes_subid = 0xFF & check[a + 9 + pes_extensionlength]; if (pes_extensionlength == 0x24 && pes_subid>>>4 == 1) { str = "SubID 0x" + Integer.toHexString(pes_subid).toUpperCase(); if (ttx_streams.indexOf(str) < 0) ttx_streams.add(str); } else if ( ((!mpg1 && !vdr) || (vdr && pes_alignment)) && (pes_subid>>>4 == 2 || pes_subid>>>4 == 3)) { str = "SubID 0x" + Integer.toHexString(pes_subid).toUpperCase(); if (pic_streams.indexOf(str) < 0) pic_streams.add(str); } else { if (!vdr) { id = 0xFF & check[a + 9 + pes_extensionlength]; str = String.valueOf(id); check[a + 8] = (byte)((pes_subid>>>4 == 0xA ? 1 : 4) + pes_extensionlength); // check[a + 8] = (byte)(4 + pes_extensionlength); } if (!table.containsKey(str)) table.put(str, new ScanObject(id)); scanobject = (ScanObject)table.get(str); scanobject.write(check, a, jump ); } } else { switch (id) { case 0xBB: case 0xBC: case 0xBE: case 0xBF: case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7: case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: jump = 6 + CommonParsing.getPES_LengthField(check, a); break; default: jump = 1; } } } for (Enumeration n = table.keys(); n.hasMoreElements() ; ) { str = n.nextElement().toString(); id = Integer.parseInt(str); scanobject = (ScanObject)table.get(str); if ( (0xF0 & id) == 0xE0) { try { checkVid(scanobject.getData()); } catch (Exception e) { video_streams.add(msg_8); } } else { try { checkPES(scanobject.getData()); } catch (Exception e) { audio_streams.add(msg_8); } } } table.clear(); return; } /** * */ private void loadPVA(byte[] check, int a) throws IOException { Hashtable table = new Hashtable(); ScanObject scanobject; String str; int jump, id; int end = check.length > 580000 ? 512000 : check.length - 65000; while ( a < end) { jump = (0xFF & check[a+6])<<8 | (0xFF & check[a+7]); if (a + 8 + ((1 & check[a+5]>>>4) * 4) + jump > check.length) break; id = 0xFF & check[a+2]; str = String.valueOf(id); if (!table.containsKey(str)) table.put(str, new ScanObject(id)); scanobject = (ScanObject)table.get(str); switch (id) { case 1: scanobject.write(check, a + 8 + ((1 & check[a+5]>>>4) * 4), jump ); break; default: scanobject.write(check, a + 8, jump ); } a += 8 + jump; } for (Enumeration n = table.keys(); n.hasMoreElements() ; ) { str = n.nextElement().toString(); scanobject = (ScanObject)table.get(str); if (str.equals("1")) { try { checkVid(scanobject.getData()); } catch ( Exception e) { video_streams.add(msg_8); } } else { try { checkPES(scanobject.getData()); } catch ( Exception e) { audio_streams.add(msg_8); } } } table.clear(); return; } /** * */ private void checkPES(byte[] check) { checkPES(check, 0); } /** * */ private void checkPES(byte[] check, int a) { int end = a + 8000; rawcheck: for (int returncode; a < end; a++) { if ((returncode = CommonParsing.validateStartcode(check, a)) < 0) { a += (-returncode) - 1; continue rawcheck; } if ( (0xE0 & check[a+3]) == 0xC0 || CommonParsing.getPES_IdField(check, a) == CommonParsing.PRIVATE_STREAM_1_CODE) { int next = a + 6 + CommonParsing.getPES_LengthField(check, a); if (CommonParsing.validateStartcode(check, next) < 0) continue rawcheck; if ( (0xE0 & check[a+3]) == 0xC0 && (0xE0 & check[a+3]) == (0xE0 & check[next+3]) ) { MPEGAudio(loadPES(check,a)); return; } else if (CommonParsing.getPES_IdField(check, a) == CommonParsing.PRIVATE_STREAM_1_CODE && CommonParsing.getPES_IdField(check, a) == CommonParsing.getPES_IdField(check, next)) { byte buffer[] = loadPES(check, a); if (AC3Audio(buffer) != 0) return; else if (DTSAudio(buffer) != 0) return; LPCMAudio(buffer); return; } } } } /** * */ private void checkVid(byte[] check) { checkVid(check, check.length - 630); } /** * */ private boolean checkVid(byte[] check, int length) { ByteArrayOutputStream bytecheck = new ByteArrayOutputStream(); for (int i = 0, returncode, pes_ID; i < length; i++) { if ((returncode = CommonParsing.validateStartcode(check, i)) < 0 || CommonParsing.getPES_IdField(check, i) != CommonParsing.SEQUENCE_HEADER_CODE) { i += (returncode < 0 ? -returncode : 4) - 1; continue; } for (int j = 7, mpgtype = 1; j < 600; j++) { if ((returncode = CommonParsing.validateStartcode(check, i + j)) < 0) { j += (-returncode) - 1; continue; } pes_ID = CommonParsing.getPES_IdField(check, i + j); if (pes_ID == CommonParsing.EXTENSION_START_CODE && (0xF0 & check[4 + i + j]) == 0x10) mpgtype = 2; else if (pes_ID == CommonParsing.GROUP_START_CODE || pes_ID == CommonParsing.PICTURE_START_CODE) { hasVideo = true; System.arraycopy(check, i, vbasic, 0, 12); bytecheck.write(check, i, 20); video_streams.add("MPEG-" + mpgtype + ", " + Video.getVideoformatfromBytes(bytecheck.toByteArray())); return true; } } } String[] h264_info = new String[18]; boolean isH264 = Common.getMpvDecoderClass().parseH264(check, length, h264_info); if (isH264) { video_streams.add(h264_info[0] + " " + h264_info[1] + " " + h264_info[2] + " " + h264_info[3]); return true; } return false; } /** * */ private void readPMT(byte[] check, int a, int lead) { ByteArrayOutputStream bytecheck = new ByteArrayOutputStream(); // pidlist.clear(); boolean ts_start = false; int packetlength = 188; tscheck: for (int adaptfield, pmtpid, offset; a < check.length - 1000; a++) { if ( check[a] != 0x47 || check[a + packetlength + lead] != 0x47 || check[a + (packetlength + lead) * 2] != 0x47 ) continue tscheck; if ((0x40 & check[a + 1]) == 0) // start continue tscheck; if ((0xC0 & check[a + 3]) != 0) // scrambling continue tscheck; adaptfield = (0x30 & check[a + 3])>>>4; if ((adaptfield & 1) == 0) // adapt - no payload continue tscheck; offset = adaptfield == 3 ? 1 + (0xFF & check[a + 4]) : 0; //adaptlength if (check[a + offset + 4] != 0 || check[a + offset + 5] != 2 || (0xF0 & check[a + offset + 6]) != 0xB0) { a += (packetlength + lead - 1); continue tscheck; } bytecheck.write(check, a + 4 + offset, packetlength - 4 - offset); pmtpid = (0x1F & check[a + 1])<<8 | (0xFF & check[a + 2]); if ( bytecheck.size() < packetlength ) { a += packetlength + lead; addpack: for ( ; a < check.length - 500; a++) { if ( check[a] != 0x47 || check[a + packetlength + lead] != 0x47 || check[a + (packetlength + lead) * 2] != 0x47 ) continue addpack; if ((0x40 & check[a + 1]) != 0) // start continue addpack; if ((0xC0 & check[a + 3]) != 0) // scrambling continue addpack; adaptfield = (0x30 & check[a + 3])>>>4; if ((adaptfield & 1) == 0) // adapt - no payload continue addpack; offset = adaptfield == 3 ? 1 + (0xFF & check[a + 4]) : 0; //adaptlength if ( ((0x1F & check[a + 1])<<8 | (0xFF & check[a + 2])) != pmtpid ) { a += (packetlength + lead - 1); continue addpack; } bytecheck.write(check, a + 4 + offset, packetlength - 4 - offset); if ( bytecheck.size() > packetlength ) break addpack; } } byte[] pmt = bytecheck.toByteArray(); if (pmt.length > 5) { int sid = (0xFF & pmt[4])<<8 | (0xFF & pmt[5]); pidlist.add("" + sid); pidlist.add("" + pmtpid); addInfo = " (SID 0x" + Common.adaptString(Integer.toHexString(sid).toUpperCase(), 4) + ", PMT 0x" + Common.adaptString(Integer.toHexString(pmtpid).toUpperCase(), 4) + ")"; } int pmt_len = (0xF&pmt[2])<<8 | (0xFF&pmt[3]); pidsearch: for (int b=8, r=8; b < pmt_len-4 && b < pmt.length-6; b++) { r = b; if ( (0xe0 & pmt[b+1]) != 0xe0 ) continue pidsearch; int pid = (0x1F & pmt[b+1])<<8 | (0xFF & pmt[b+2]); switch(0xFF & pmt[b]) { case 1: getDescriptor(pmt, b+5, (b += 4+ (0xFF & pmt[b+4])), pid, 1); pidlist.add("" + pid); break; case 2: getDescriptor(pmt, b+5, (b += 4+ (0xFF & pmt[b+4])), pid, 2); pidlist.add("" + pid); break; case 3: //mp1a getDescriptor(pmt, b+5, (b += 4+ (0xFF & pmt[b+4])), pid, 3); pidlist.add("" + pid); break; case 4: //mp2a getDescriptor(pmt, b+5, (b += 4+ (0xFF & pmt[b+4])), pid, 4); pidlist.add("" + pid); break; case 0x11: //mp4a getDescriptor(pmt, b+5, (b += 4+ (0xFF & pmt[b+4])), pid, 0x11); pidlist.add("" + pid); break; case 0x1B: getDescriptor(pmt, b+5, (b += 4+ (0xFF & pmt[b+4])), pid, 0x1B); pidlist.add("" + pid); break; case 0x80: case 0x81: //private data of AC3 in ATSC case 0x82: case 0x83: case 0xA0: //user private case 6: getDescriptor(pmt, b+5, (b += 4+ (0xFF & pmt[b+4])), pid, 6); pidlist.add("" + pid); break; default: b += 4+ (0xFF & pmt[b+4]); } if (b < 0) b = r; } return; } return; } /** * */ private void getDescriptor(byte check[], int off, int end, int pid, int type) { String str = ""; int chunk_end = 0; try { loop: for (; off < end && off < check.length; off++) { switch(0xFF & check[off]) { case 0x59: //dvb subtitle descriptor type = 0x59; chunk_end = off + 2 + (0xFF & check[off+1]); str += "("; for (int a=off+2; a<chunk_end; a+=8) { for (int b=a; b<a+3; b++) //language str += (char)(0xFF & check[b]); int page_type = 0xFF & check[a+3]; int comp_page_id = (0xFF & check[a+4])<<16 | (0xFF & check[a+5]); int anci_page_id = (0xFF & check[a+6])<<16 | (0xFF & check[a+7]); str += "_0x" + Integer.toHexString(page_type).toUpperCase(); str += "_p" + comp_page_id; str += "_a" + anci_page_id + " "; } str += ")"; break loop; case 0x56: //teletext descriptor incl. index page + subtitle pages type = 0x56; chunk_end = off + 2 + (0xFF & check[off+1]); str += "("; for (int a=off+2; a<chunk_end; a+=5) { for (int b=a; b<a+3; b++) //language str += (char)(0xFF & check[b]); int page_type = (0xF8 & check[a+3])>>>3; int page_number = 0xFF & check[a+4]; str += "_"; switch (page_type) { case 1: str += "i"; break; case 2: str += "s"; break; case 3: str += "ai"; break; case 4: str += "ps"; break; case 5: str += "s.hip"; break; default: str += "res"; } str += Integer.toHexString((7 & check[a+3]) == 0 ? 8 : (7 & check[a+3])).toUpperCase(); str += (page_number < 0x10 ? "0" : "") + Integer.toHexString(page_number).toUpperCase() + " "; } str += ")"; //break loop; off++; off += (0xFF & check[off]); break; case 0xA: //ISO 639 language descriptor str += "{"; for (int a=off+2; a<off+5; a++) if ((0xFF & check[a]) > 0) str += (char)(0xFF & check[a]); str += "}"; off++; off += (0xFF & check[off]); break; case 0x6A: //ac3 descriptor str += "(AC-3)"; off++; off += (0xFF & check[off]); break; case 0x7C: //aac descriptor str += "(AAC)"; off++; off += (0xFF & check[off]); break; case 0xA0: //user priv descriptor str += "(FOURCC="; for (int a=off+2; a<off+6; a++) if ((0xFF & check[a]) > 0) str += (char)(0xFF & check[a]); str += ")"; off++; off += (0xFF & check[off]); break; case 0xC3: //VBI descriptor off++; switch (0xFF & check[off + 1]) { case 4: str += "(VPS)"; type = 0xC3; break; case 5: str += "(WSS)"; break; case 6: str += "(CC)"; break; case 1: str += "(EBU-TTX)"; break; case 7: str += "(VBI)"; break; } off += (0xFF & check[off]); break; case 0x52: //ID of service chunk_end = off + 2 + (0xFF & check[off+1]); str += "(#" + (0xFF & check[off + 2]) + ")"; off++; off += (0xFF & check[off]); break; case 0x6B: //ancillary desc chunk_end = off + 2 + (0xFF & check[off + 1]); str += "(RDS)"; off++; off += (0xFF & check[off]); break; case 0x5: //registration descriptor chunk_end = off + 2 + (0xFF & check[off+1]); str += "("; for (int a=off+2; a<chunk_end; a++) str += (char)(0xFF & check[a]); str += ")"; // break;//?? default: off++; off += (0xFF & check[off]); } } String out = "PID: 0x" + Common.adaptString(Integer.toHexString(pid).toUpperCase(), 4); switch (type) { case 0x59: pic_streams.add(out + str); break; case 0x56: ttx_streams.add(out + str); break; case 0x1B: video_streams.add(out + str + "(H.264)"); break; case 1: video_streams.add(out + str + "(MPEG-1)"); break; case 2: video_streams.add(out + str + "(MPEG-2)"); break; case 0xC3: video_streams.add(out + str); break; case 3: audio_streams.add(out + str + "(Mpg1)"); break; case 4: audio_streams.add(out + str + "(Mpg2)"); break; case 0x11: audio_streams.add(out + str + "(Mpg4)"); break; default: audio_streams.add(out + str + "[PD]"); } } catch (ArrayIndexOutOfBoundsException ae) { playtime += msg_6; } } /** * */ private class ScanObject { private ByteArrayOutputStream buf = null; private int id; private int type; private ScanObject() { buf = new ByteArrayOutputStream(); id = 0; } private ScanObject(int val1) { buf = new ByteArrayOutputStream(); id = val1; } private int getType() { return type; } private void write(byte data[]) throws IOException { buf.write(data); } private void write(byte data[], int offset, int length) throws IOException { buf.write(data, offset, length); } private byte[] getData() throws IOException { buf.flush(); return buf.toByteArray(); } private void reset() { buf.reset(); } } }