/* * @(#)TS.java - constants to create TS packets * * 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 * */ /* * Arion export introduced by Cameron D (AU) * from 0.90.4.00b27 */ package net.sourceforge.dvb.projectx.thirdparty; import java.util.List; import java.util.Calendar; import java.util.Date; import java.util.Arrays; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.StringTokenizer; import java.util.ArrayList; import net.sourceforge.dvb.projectx.parser.CommonParsing; import net.sourceforge.dvb.projectx.common.Common; import net.sourceforge.dvb.projectx.common.Resource; import net.sourceforge.dvb.projectx.common.JobProcessing; public class TS { private static byte[] service_name = null; private static byte[] event_name = null; private static byte[] event_text = null; public TS() {} private static byte[] TF4000header = { (byte)0xCD, 0x39, 0xc, 0, //MJD (byte)0xCD, 0x39, 0xc, 0, //MJD 0, 0x3c, //duration 0, 0x1f, //service 0, 0, //0=tv, 1=radio 5, 0, 6, (byte)0xb0, //tuner 1, 2, //sid 1, 0, //pmt 0, (byte)0xe0, //pcr 0, (byte)0xe0, //vid 0, (byte)0xc0, //aud 0x4D,0x79,0x20,0x70,0x65,0x72,0x73,0x6F,0x6E,0x61,0x6C,0x20,0x54,0x56,0x20,0x43,0x68,0x61,0x6E,0x6E,0x65,0x6C,0,0, 5, 0, 0x30, (byte)0xc0, 0x6b, 0x6c, 0, 1, 0x40, 0x1f, 1, 1, (byte)0xCD, 0x39, 0xb, 0, (byte)0xCD, 0x39, 0xc, 0, 0, 0x3c, 4, 4, 84, 69, 83, 84, 0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0, 1,2 }; //introduced by 'catapult' 09082004 private static byte[] TF5000header = { // HEADER 14 bytes, start at 0 0x54, 0x46, 0x72, 0x63, // Id "TFrc" * 0x50, 0, // Version * 0, 0, // Reserved * 0, 0, // Duration in Minutes 0, 0xa, // Service number in channel list (Does not matter in playback) 0, 0, // Service type 0:TV 1:Radio // SERVICE_INFO 38 bytes starts at 14 0, 0, 1, 0x30, // Reserved and Tuner (Does not matter in playback) (Tuner 1+2 flagged) 1, 2, // Service ID of TS stream 1, 0, // PID number of PMT TS packet 0, (byte)0xe0, // PID number of PCR TS packet 0, (byte)0xe0, // PID number of Video TS packet 0, (byte)0xc0, // PID number of Audio TS packet, MPA as std 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x52, 0x65, 0x63, 0x6F, 0x72, 0x64, 0x69, 0x6E, 0x67, 0, 0, 0, 0, 0, 0, 0, // Service Name // TP_INFO 16 bytes starts at 52 0, // Satelite Index 8, 7, 0, // Polarity and Reserved (Does not matter in playback) 0, 0, 0x2F, (byte)0x9B, // Frequency (Does not matter in playback) 0x6B, 0x6C, // Symbol Rate (Does not matter in playback) 1, 1, // Transport Stream Id (Does not matter in playback) 0, 0, 0, 0, // Reserved * // EVT_INFO 160 bytes starts at 68 (byte)0x80, 0x02, // Reserved * 0, 0, // Duration in Minutes 0, 0x3c, 4, 4, // Event Id 84, 69, // Modified Julian date start time 83, // Hour of start time 84, // Minute os start time 0, 0, // Modified Julian date end time 0, // Hour of end time 0, // Minute os end time 4, // Reserved 0, // Length of name in Event text 0, // Parental rate // the rest is 0 so it's not defined explicitly // Event text // EXT_EVT_INFO 1088 bytes starts at 228 // Extended Event text }; //introduced by 'jkit' 23012009 private static byte[] TF5200header = { // HEADER 14 bytes, start at 0 0x54, 0x46, 0x72, 0x63, // Id "TFrc" * 0x50, 0, // Version * 0, 0, // Reserved * 0, 0, // Duration in Minutes 0, 0xa, // Service number in channel list (Does not matter in playback) 0, 0, // Service type 0:TV 1:Radio // SERVICE_INFO 38 bytes starts at 14 0, 0, 1, 0x30, // Reserved and Tuner (Does not matter in playback) (Tuner 1+2 flagged) 1, 2, // Service ID of TS stream 1, 0, // PID number of PMT TS packet 0, (byte)0xe0, // PID number of PCR TS packet 0, (byte)0xe0, // PID number of Video TS packet 0, (byte)0xc0, // PID number of Audio TS packet, MPA as std 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x52, 0x65, 0x63, 0x6F, 0x72, 0x64, 0x69, 0x6E, 0x67, 0, 0, 0, 0, 0, 0, 0, // Service Name // TP_INFO 12 bytes starts at 52 0, 6, 0x41, (byte)0x90, // Frequency (e.g. 410000 KHz) 0x1A, (byte)0xF4, // Symbol_Rate (e.g. 6900 kS/s) 1, 1, // Transport_Stream_Id (e.g. 0x044D) 0, 1, // Network_Id 2, // Modulation 0=16QAM, 1=32QAM, 2=64QAM, 3=128QAM, 4=256QAM 0, // Reserved1 // EVT_INFO 160 bytes starts at 64 (byte)0x80, 0x02, // Reserved * 0, 0, // Duration in Minutes 0, 0x3c, 4, 4, // Event Id 84, 69, // Modified Julian date start time 83, // Hour of start time 84, // Minute os start time 0, 0, // Modified Julian date end time 0, // Hour of end time 0, // Minute os end time 4, // Reserved 0, // Length of name in Event text 0, // Parental rate // the rest is 0 so it's not defined explicitly // Event text // EXT_EVT_INFO // Extended Event text }; /* * header code for Arion TS file (.AVR file) * There are two file formats, .AVR (the AV stream), and .AVF, the real header file. */ // The AVF filename must be retained for inclusion into each AVR header file. // the filename format is: // <user-supplied-rootname>_#00n_<date-time>.AVx // Where n is the part number (starting from 1) // date-time is in the format yyyymmddhhmm // full pathname on PVR disc, private static StringBuffer ArionAVFPathname_onPVR = new StringBuffer( 128 ); // local filename - as finally named on this system; may or may not include directories. private static StringBuffer ArionAVRLocalFilename = new StringBuffer( 1024 ); private static File ArionAVFLocalPathname ; // the initial date+time used must be remembered so we can rebuild the // matching name for second and later file parts. private static StringBuffer ArionAVFTime = new StringBuffer( 128 ); // same the initial filename prefix for building other names later private static StringBuffer ArionFilenameRoot = new StringBuffer( 128 ); // we need to keep track of total file sizes and playback length. private static long ArionCumulativeTime = 0L; // seconds // bytes (excluding header) - note Java long is 64 bits, so // it has no problems with large files. private static long ArionCumulativeStreamSize = 0L; private static final int ArionAVR_HEADERSIZE = 0x8000; private static byte[] ArionAVR_Header = { // HEADER 32k bytes, start at 0 0x41, 0x52, 0x41, 0x56, // Id "ARAV" * 0x10, 0, // unknown * 0, (byte)0x86, // unknown * (byte)0x80, 0, // possibly length of header 0x43, 0x3a, 0x5c, 0x50, 0x56, 0x52, 0x5c, 0x41, 0x56, 0x5c, // pathname of AVF file. // the rest is 0 so it's not defined explicitly }; private static final int ArionAVF_HEADERSIZE = 15160; // File is 15k bytes private static byte[] ArionAVF_Header = { 0x41, 0x52, 0x4e, 0x46, // Id "ARNF" * 0x11, 0, 0, 0x04, // unknown 0x3f, 0x04, 0, 0x0a, // unknown // Channel-related info 0, // unknown 0, 0, 0, // varies with broadcast channel 0, 0x01, // 0x10: channel number 0x07, 0, // constant 0, 0x48, // constant 0x01, 0, // 1 = TV, 2 = radio // SID/PID details 1, 2, // SID 0, 1, // number of audio streams 0, (byte)0xc0, // audio PID 1 0x7f, (byte)0xff, // audio PID 2 0x11, 0x10, // 0x20: unknown 0, (byte)0xe0, // PID for video 0, 0, // unknown 0, (byte)0x90, // PID for text/subtitles 1, 0, // varies with channel 0, // 0x2a: unknown... 0x50, 0x72, 0x6f, 0x6a, 0x65, // Program stream name 0x63, 0x74, 0x2d, 0x58, 0x20, 0x63, 0x6f, 0x6e, // 0x30: 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x0a, 0x00, 0, 0, 0, 0, // 0x40: 1, 0x11, // unknown, sometimes zero. 0, 0, // unknown 0, 1, // unknown - zero or 1 0, 0, // 0x4a: unknown - quite variable // block of zeroes - unknown 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // EPG start - this is EPG "now" when recording started. 2, (byte)0xca, // size of EPG block 7, (byte)0xd6, // year of scheduled start of tv program 9, // 0x60: month of scheduled start of tv program 8, // day of scheduled start of tv program 7, // hour of scheduled start of tv program 6, // minute of scheduled start of tv program 3, // scheduled duration (hours) 30, // scheduled duration (minutes) // the program title - filled in with file name 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // total 64 bytes, null padded // the program subtitle or description 0x53, 0x75, 0x62, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2d, 0x58, 0x0a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // the rest is 0 so it's not defined explicitly - actually - filled at run-time. }; /* * data structure defining the layout of EPG data stored in file in two places */ private static final int ArionEPG_BLOCKSIZE = 714; private static byte[] Arion_EPG_Block = { // EPG - this is EPG "now" during recording or when started. 2, (byte)0xca, // size of EPG block 7, (byte)0xd6, // year of scheduled start of tv program 9, // 0x60: month of scheduled start of tv program 8, // day of scheduled start of tv program 7, // hour of scheduled start of tv program 6, // minute of scheduled start of tv program 5, // scheduled duration (hours) 30, // scheduled duration (minutes) // the program title 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x20, 0x74, 0x69, 0x74, 0x6c, 0x65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // total 64 bytes, null padded // the program subtitle or description 0x53, 0x75, 0x62, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2d, 0x58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // total 128 bytes // program description (apparently not used) // the rest is 0 so it's not defined explicitly - actually - filled at run-time. }; private static byte[] pmt1 = { 0x47,0x41,0,0x10, 0, 2, (byte)0xB0, (byte)0x95, 1, 2,(byte)0xC1, 0, 0, (byte)0xE0, (byte)0xE0, (byte)0xF0, 0, 2, (byte)0xE0, (byte)0xE0, (byte)0xF0, 3, 0x52, 1, 1, 3, (byte)0xE0, (byte)0xc0, (byte)0xF0, 0x9, 0x52, 1, 3, 0xA, 4, 0x64, 0x65, 0x75, 1, 3, (byte)0xE0, (byte)0xc1, (byte)0xF0, 0x9, 0x52, 1, 4, 0xA, 4, 0x64, 0x65, 0x75, 1, 3, (byte)0xE0, (byte)0xc2, (byte)0xF0, 0x9, 0x52, 1, 5, 0xA, 4, 0x64, 0x65, 0x75, 1, 3, (byte)0xE0, (byte)0xc3, (byte)0xF0, 0x9, 0x52, 1, 6, 0xA, 4, 0x64, 0x65, 0x75, 1, 6, (byte)0xE0, (byte)0x80, (byte)0xF0, 0xC, 0x52, 1, 0x11, 0x6A, 1, 0, 0xA, 4, 0x64, 0x65, 0x75, 0, 6, (byte)0xE0, (byte)0x81, (byte)0xF0, 0xC, 0x52, 1, 0x12, 0x6A, 1, 0, 0xA, 4, 0x64, 0x65, 0x75, 0, 6, (byte)0xE0, (byte)0x82, (byte)0xF0, 0xC, 0x52, 1, 0x13, 0x6A, 1, 0, 0xA, 4, 0x64, 0x65, 0x75, 0, 6, (byte)0xE0, (byte)0x90, (byte)0xF0, 0x10,0x52, 1, (byte)0xC2, 0x56, 5, 0x65, (byte)0x6E, 0x67, 0x9, 0, 0xA, 4, 0x64, 0x65, 0x75, 0, (byte)0x85, (byte)0x33, (byte)0x49, (byte)0x7e // CRC32 }; private static int count1=0, count2=0, count3=0; private static byte[] pcr = new byte[188]; private static byte[] pat = new byte[188]; private static byte[] pmt = new byte[188]; private static byte[] pmtHead = { 0x47,1,0,0x10 }; private static byte[] pmtStart = { 0, 2, (byte)0xB0, 0, 1, 2,(byte)0xC1, 0 }; private static byte[] pmtPCR = { 0, (byte)0xE0, (byte)0xE0, (byte)0xF0, 0 }; private static byte[] pmtMPV = { 2, (byte)0xE0, (byte)0xE0, (byte)0xF0, 3, 0x52, 1, 1 }; private static byte[] pmtMPA = { 3, (byte)0xE0, (byte)0xC0, (byte)0xF0, 0x9, 0x52, 1, 3, 0xA, 4, 0x64, 0x65, 0x75, 1 }; private static byte[] pmtAC3 = { 6, (byte)0xE0, (byte)0x80, (byte)0xF0, 0xC, 0x52, 1, 4, 0x6A, 1, 0, 0xA, 4, 0x64, 0x65, 0x75, 0 }; private static byte[] pmtAC3_atsc = { (byte)0x81, (byte)0xE0, (byte)0x80, (byte)0xF0, 0xF, 0x52, 1, 4, 0xA, 4, 0x65, 0x6E, 0x67, 0, 5, 4, 0x41, 0x43, 0x2D, 0x33 }; // private static byte[] pmtTTX = { 6, (byte)0xE0, (byte)0x90, (byte)0xF0, 0x1F, 0x52, 1, 5, 0x56, 20, 0x65, 0x6E, 0x67, 0x9, 0, 0x64, 0x65, 0x75, 0x11, 0x50, 0x67, 0x65, 0x72, 0x17, 0x77, 0x65, 0x6E, 0x67, 0x10, (byte)0x88, 0xA, 4, 0x64, 0x65, 0x75, 0 }; private static byte[] pmtTTX = { 6, (byte)0xE0, (byte)0x90, (byte)0xF0, 0x2C, 0x52, 1, 5, 0x56, 20, 0x65, 0x6E, 0x67, 0x9, 0, 0x64, 0x65, 0x75, 0x11, 0x50, 0x67, 0x65, 0x72, 0x17, 0x77, 0x65, 0x6E, 0x67, 0x10, (byte)0x88, 0xA, 4, 0x64, 0x65, 0x75, 0, 0x45, 0x0B, 0x01, 0x06, (byte)0xE7, (byte)0xE8, (byte)0xE9, (byte)0xEA, (byte)0xEB, (byte)0xEC, 0x04, 0x01, (byte)0xF0 }; private static byte[] pmtSUP = { 6, (byte)0xE0, (byte)0x20, (byte)0xF0, 0x0D, 0x52, 1, 6, 0x59, 8, 0x64, 0x65, 0x75, 0x10, 0, 1, 0, 1 }; private static byte[] autopmt = new byte[0]; private static int firstID = 0xE0; private static boolean myTTX = false; public static void setPmtPids(List PIDs) throws IOException { if (myTTX) PIDs.add("" + 0x39F); Object[] Pids = PIDs.toArray(); if (Pids.length == 0) { Common.setMessage(Resource.getString("ts.msg1")); autopmt = pmt; return; } ByteArrayOutputStream pmtout = new ByteArrayOutputStream(); Arrays.sort(Pids); int lfn = 1; // byte 7 = substreamID for program component pmtout.write(pmtStart); firstID = (0xFF & Integer.parseInt(Pids[0].toString())); pmtPCR[2] = (byte)firstID; updateHeader(23, firstID); pmtout.write(pmtPCR); for (int a = 0; a < Pids.length; a++) // get Pid Hex: 0..=V, 1..=MPA, 2..=AC3, 3..=TTX { int Pid = Integer.parseInt(Pids[a].toString()); switch (0xF & (Pid>>>8)) { case 0: // vid pmtMPV[2] = (byte)(0xFF & Pid); pmtMPV[7] = (byte)lfn++; pmtout.write(pmtMPV); break; case 1: // mpeg-1 (-2) audio pmtMPA[2] = (byte)(0xFF & Pid); pmtMPA[7] = (byte)lfn++; pmtout.write(pmtMPA); break; case 2: if ((0xFF & Pid) < 0x40) // sup { pmtSUP[2] = (byte)(0xFF & Pid); pmtSUP[7] = (byte)lfn++; pmtout.write(pmtSUP); } else // ac3, dts audio { pmtAC3[2] = (byte)(0xFF & Pid); pmtAC3[7] = (byte)lfn++; pmtout.write(pmtAC3); // ac3_atsc addition, same values pmtAC3_atsc[2] = (byte)(0xFF & Pid); pmtAC3_atsc[7] = (byte)(lfn - 1); pmtout.write(pmtAC3_atsc); } break; case 3: // ttx pmtTTX[2] = (byte)(0xFF & Pid); pmtTTX[7] = (byte)lfn++; pmtout.write(pmtTTX); break; } } byte newpmt[] = pmtout.toByteArray(); int sectionlen = newpmt.length; newpmt[2] = (byte)(0xB0 | (0xF & sectionlen>>8)); newpmt[3] = (byte)(0xFF & sectionlen); pmtout.reset(); pmtout.write(newpmt); pmtout.write(generateCRC32(newpmt, 1)); newpmt = pmtout.toByteArray(); int pmtpacks = ((newpmt.length - 1) / 184) + 1; // = number of needed pmt packs autopmt = new byte[pmtpacks * 188]; Arrays.fill(autopmt, (byte)0xff); int i = 0, c = 0; while (i < newpmt.length) { System.arraycopy(pmtHead, 0, autopmt, c, 4); if (newpmt.length >= i+184) { System.arraycopy(newpmt, i, autopmt, 4 + c, 184); i += 184; c += 188; } else { System.arraycopy(newpmt, i, autopmt, 4 + c, newpmt.length - i); break; } } autopmt[1] = 0x41; // = set startpacket bit pmtout.close(); } // auto PMT public static byte[] getAutoPMT() { for (int i=0; i < autopmt.length; i+=188) autopmt[i+3] = (byte)(0x10 | (0xf&(count1++))); return autopmt; } public static int getfirstID() { return firstID; } //DM09082004 081.7 int08 changed public static void setfirstID() { firstID = 0xE0; pmtPCR[2] = (byte)firstID; updateHeader(23, firstID); } // PAT with section 0 and SID = 0x102, PMT = 0x100 , CRC32 private static byte[] pat1 = { 0x47,0x40,0,0x10, 0, 0, (byte)0xB0, 0xd, 0, 1, 1, 0, 0, 1, 2, (byte)0xE1, 0, (byte)0x8f, (byte)0xa5, 0x26, (byte)0xcf, }; // counter shall not be updated in PCR only paket (but do it), 42bits for PCR private static byte pcr1[] = { 0x47,0,(byte)0xe0,0x20, (byte)0xB7,0x10,0,0,0,0,0,0 }; /** private static byte ttx[] = { 0x47,0x40,(byte)0x9F,0x10, 0,0,1,(byte)0xBD,0,(byte)0xB2,(byte)0x84,(byte)0x80,0x24, (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, (byte)0xFF, 0x10, // 022C E7E4 40A8 A8CE A80B A80B 7A40 2,44,-25,-28,64,-88,-88,-50,-88,11,-88,11,122,64, 38,-50,117,87,-122,79,4,42,-53,-75,-110,118,103,-9,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, // 022C E8E4 40D9 2,44,-24,-28,64,-39, // l i n e 2 2 : 4,55,-105,118,-89,4,76,76, 4,107,67,-110,4,-116,-12,28,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, // 022C E9E4 E3D9 2,44,-23,-28,-29,-39, // line 23 PTS 4,55,-105,118,-89,4,76,-51, 4,107,67,-110,4,-116,-12,-99,4,11,42,-53,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 }; //DM26052004 081.7 int03 changed public static byte[] getTTX(byte[] data, int offset, String pts) { byte[] tPTS = pts.getBytes(); for (int a = 0; a < tPTS.length; a++) tPTS[a] = Common.getTeletextClass().bytereverse(Common.getTeletextClass().parity(tPTS[a])); System.arraycopy(tPTS, 0, ttx, 169, tPTS.length); System.arraycopy(data, 9 + offset, ttx, 13, 5); ttx[13] &= ~0x10; ttx[3] = (byte)(0x10 | (0xF & (count3++))); return ttx; } **/ ////////// private static byte[] ttx_stream = null; private static long[] ttx_pts_index = null; private static int ttx_index = 0; /** * read .sub text file and create complete TS TTX stream * from 0.90.4.00b28 */ public static byte[] getTeletextStream(long video_pts) { if (ttx_pts_index == null) return (new byte[0]); ByteArrayOutputStream bo = new ByteArrayOutputStream(); for (int j = ttx_pts_index.length; ttx_index < j; ttx_index++) { if (video_pts < ttx_pts_index[ttx_index]) break; //pts_delta, ttx_pts shall match video_pts at 40ms (3600ticks) boundary video_pts -= ((video_pts - ttx_pts_index[ttx_index]) / 3600L) * 3600L; CommonParsing.setPES_PTSField(ttx_stream, 4 + ttx_index * 376, video_pts); CommonParsing.setPES_PTSField(ttx_stream, 4 + 188 + ttx_index * 376, video_pts); bo.write(ttx_stream, ttx_index * 376, 376); } return bo.toByteArray(); } /** * read .sub text file and create complete TS TTX stream * from 0.90.4.00b28 */ public static void buildTeletextStream(String filename) { filename = filename.substring(0, filename.lastIndexOf(".")) + ".sub"; File f = new File(filename); ttx_index = 0; if (!f.exists()) return; try { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filename))); ByteArrayOutputStream bo = new ByteArrayOutputStream(); String line = "", tmp = ""; ArrayList rowList = new ArrayList(); int pos1 = 0, pos2 = 0; long[] time = { 0, 0, 0 }; byte[] pts_value = new byte[5]; byte[] pts_value1 = new byte[8]; StringTokenizer st; ArrayList indexList = new ArrayList(); long delay = 90L * Common.getSettings().getIntProperty("TTXInsertion.Delay", 0); long[] framenumber = { 0, 0, 0 }; Common.setMessage("-> build teletext stream from file: '" + filename + "' / delay = " + (delay / 90) + " ms" ); Common.getTeletextClass().setMappingTable(); while ((line = br.readLine()) != null) { //if (!line.startsWith("{")) //problems with file signature reading utf files - omits a line // continue; framenumber[0] = Long.parseLong(line.substring(pos1 = (line.indexOf("{") + 1), pos2 = line.indexOf("}"))); framenumber[1] = Long.parseLong(line.substring(line.indexOf("{", ++pos1) + 1, pos2 = line.indexOf("}", ++pos2))); time[0] = delay + 90L * (1000/25) * framenumber[0]; time[1] = delay + 90L * (1000/25) * framenumber[1]; st = new StringTokenizer(line.substring(pos2 + 1), "|"); rowList.clear(); while (st.hasMoreTokens()) rowList.add(st.nextToken()); // every page consists of 376 (2x188) byte for (int i = 0; i < 2; i++, framenumber[2]++) { /** // insert padding packets for every frame for (; framenumber[2] < framenumber[i]; framenumber[2]++) { Arrays.fill(pts_value, (byte) 0); // clear old value time[2] = delay + 90L * (1000/25) * framenumber[2]; CommonParsing.setPES_PTSField(pts_value, -9, time[2]); bo.write(Common.getTeletextClass().getTTXPadding_TSPacket(count3, pts_value)); indexList.add(new Long(time[2])); count3 += 2; } **/ Arrays.fill(pts_value, (byte) 0); // clear old value CommonParsing.setPES_PTSField(pts_value, -9, time[i]); bo.write(Common.getTeletextClass().getTTX_TSPacket(rowList, count3, pts_value)); indexList.add(new Long(time[i])); count3 += 2; rowList.clear(); //2nd call for time out (placing an empty page) } } br.close(); /** FileOutputStream fos = new FileOutputStream(filename + ".ttx"); fos.write(bo.toByteArray()); fos.flush(); fos.close(); **/ ttx_stream = bo.toByteArray(); ttx_pts_index = new long[indexList.size()]; for (int i = 0, j = ttx_pts_index.length; i < j; i++) ttx_pts_index[i] = ((Long) indexList.get(i)).longValue(); } catch (Exception e) { Common.setExceptionMessage(e); } } ///////////// /* * */ public static byte[] getPMT() { pmt[3] = (byte)(0x10 | (0xf & (count1++))); return pmt; } public static byte[] getPAT() { pat[3] = (byte)(0x10 | (0xf & (count2++))); return pat; } public static byte[] getPCR(long pts, int count, int PCRPid) { /* Construct the PCR, PTS-55000 (2ms) 1Bit ~ 3Ticks (counter) */ pcr[2] = (byte)(PCRPid); pcr[3] = (byte)(0x20 | (0xF & count)); pcr[6] = (byte)(0xFF & pts>>>25); pcr[7] = (byte)(0xFF & pts>>>17); pcr[8] = (byte)(0xFF & pts>>>9); pcr[9] = (byte)(0xFF & pts>>>1); pcr[10] = (byte)((1 & pts) << 7 ); /* PCR ext is 0, byte10+byte11 */ return pcr; } /** * init additional header * copy pmt entries */ public static byte[] init( JobProcessing job_processing, String name, boolean ac3, boolean _myTTX, int mode) { count1 = count2 = count3 = 0; myTTX = _myTTX; Arrays.fill(pat, (byte) 0xFF); Arrays.fill(pmt, (byte) 0xFF); Arrays.fill(pcr, (byte) 0xFf); System.arraycopy(pmt1, 0, pmt, 0, pmt1.length); System.arraycopy(pat1, 0, pat, 0, pat1.length); System.arraycopy(pcr1, 0, pcr, 0, pcr1.length); switch (mode) { case 1: return initTFHeader(TF4000header, 564, name, ac3, mode); case 2: return initTFHeader(TF5000header, 1692, name, ac3, mode); //fmly 1316 case 3: return initTFHeader(TF5000header, 3760, name, ac3, mode); case 4: return initTFHeader(TF5200header, 3760, name, ac3, mode); case 5: return initArionHeader(name, ac3, ArionAVR_HEADERSIZE , job_processing); } return (new byte[0]); } /** * init topfield header * set main audio */ private static byte[] initTFHeader(byte[] header, int headerlength, String name, boolean ac3, int mode) { byte newheader[] = new byte[headerlength]; System.arraycopy(header, 0, newheader, 0, header.length); /** byte file_name[] = new File(name).getName().getBytes(); header[75] = (byte)(file_name.length - 3); System.arraycopy(file_name, 0, newheader, 76, file_name.length - 3); **/ // primary_audio_pid = !ac3 ? 0xC0 : 0x80; newheader[26] = 0; newheader[27] = !ac3 ? (byte)0xC0 : (byte)0x80; //MPA // set 1. AC3 PID as main TFaudio return newheader; } /** * sets PID values */ private static void updateHeader(int pos, int val) { //only last 8 bits used TF4000header[pos] = (byte) val; TF5000header[pos] = (byte) val; TF5200header[pos] = (byte) val; } public static String updateAdditionalHeader(String old_name, long time[], int mode, JobProcessing job_processing) throws IOException { String new_name = ""; String[] new_ext = { "", ".raw", ".rec", ".rec", ".rec", ""}; switch (mode) { case 0: return old_name; case 1: case 2: case 3: case 4: new_name = old_name.substring(0, old_name.length() - 3) + new_ext[mode]; if (new File(new_name).exists()) new File(new_name).delete(); Common.renameTo(old_name, new_name); finishTFHeader(new_name, time, mode); break; case 5: new_name = finishArionheaders(old_name, time, job_processing); break; } return new_name; } /** * save event info */ public static void setEventInfo(byte[] data1, byte[] data2, byte[] data3) { if (data1 == null) service_name = null; else { service_name = new byte[data1.length]; System.arraycopy(data1, 0, service_name, 0, data1.length); } if (data2 == null) event_name = null; else { event_name = new byte[data2.length]; System.arraycopy(data2, 0, event_name, 0, data2.length); } if (data3 == null) event_text = null; else { event_text = new byte[data3.length]; System.arraycopy(data3, 0, event_text, 0, data3.length); } } /** * completes Topfield header */ private static void finishTFHeader(String name, long time[], int mode) { long event[] = new long[4]; long millis = (time[1] - time[0]) / 90L; short minutes = (short)(0xFFFF & (Math.round(millis / 60000f))); event[0] = System.currentTimeMillis(); event[1] = event[0] - millis; //JD 2440588 1.1.1970 = 0 //24*60*60*1000 event[2] = (event[0] / 86400000L) + 2440588 - 2400001; event[3] = (event[1] / 86400000L) + 2440588 - 2400001; Calendar datum = Calendar.getInstance(); datum.setTime(new Date(event[0])); switch (mode) { case 1: finishTF4000header(name, time, minutes, event, datum); break; case 2: case 3: finishTF5X00header(name, time, minutes, event, datum, 4); break; case 4: finishTF5X00header(name, time, minutes, event, datum, 0); } } /** * completes Topfield 4000 header */ private static void finishTF4000header(String name, long time[], short minutes, long[] event, Calendar datum) { try { RandomAccessFile ts = new RandomAccessFile(name, "rw"); ts.seek(0); ts.writeShort((short)event[2]); ts.writeByte((byte)datum.get(Calendar.HOUR_OF_DAY)); ts.writeByte((byte)datum.get(Calendar.MINUTE)); ts.writeShort((short)event[2]); ts.writeByte((byte)datum.get(Calendar.HOUR_OF_DAY)); ts.writeByte((byte)datum.get(Calendar.MINUTE)); ts.writeShort(minutes); ts.seek(0x44); ts.writeShort((short)event[2]); ts.writeByte((byte)datum.get(Calendar.HOUR_OF_DAY)); ts.writeByte((byte)datum.get(Calendar.MINUTE)); ts.writeShort(minutes); datum.setTime(new Date(event[1])); ts.seek(0x40); ts.writeShort((short)event[3]); ts.writeByte((byte)datum.get(Calendar.HOUR_OF_DAY)); ts.writeByte((byte)datum.get(Calendar.MINUTE)); ts.close(); } catch (Exception e) { Common.setExceptionMessage(e); } } /** * completes Topfield 5X00 header */ //introduced by 'catapult' 09082004 //dvb-c mod's jkit 23012009 private static void finishTF5X00header(String name, long time[], short minutes, long[] event, Calendar datum, int event_info_offset) { try { RandomAccessFile ts = new RandomAccessFile(name, "rw"); ts.seek(0x08); ts.writeShort(minutes); if (service_name != null) { ts.seek(0x1C); ts.write(service_name); } ts.seek(0x42 + event_info_offset); ts.writeShort(minutes); ts.seek(0x4C + event_info_offset); ts.writeShort((short)event[2]); ts.writeByte((byte)datum.get(Calendar.HOUR_OF_DAY)); ts.writeByte((byte)datum.get(Calendar.MINUTE)); datum.setTime(new Date(event[1])); ts.seek(0x48 + event_info_offset); ts.writeShort((short)event[3]); // datum ts.writeByte((byte)datum.get(Calendar.HOUR_OF_DAY)); ts.writeByte((byte)datum.get(Calendar.MINUTE)); if (event_name != null) { ts.seek(0x51 + event_info_offset); ts.write(event_name); } else { String eventname = new File(name).getName(); if (eventname.length() > 128) eventname = eventname.substring(0, 128); ts.seek(0x51 + event_info_offset); ts.writeUTF(eventname); //filename ts.seek(0x52 + event_info_offset); int val = ts.read(); ts.seek(0x51 + event_info_offset); ts.writeShort(val<<8); } if (event_text != null) { ts.seek(0xE0 + event_info_offset); ts.write(event_text); } ts.close(); } catch (Exception e) { Common.setExceptionMessage(e); } setEventInfo(null, null, null); //reset } private static byte[] initArionHeader(String name, boolean ac3, int headerlength, JobProcessing job_processing) { int splitPart = job_processing.getSplitPart(); if ( splitPart == 0 ) { /* * this is the first part, so build the AVF info file as well as the AVR header */ long now = System.currentTimeMillis(); // time now Calendar datum = Calendar.getInstance(); datum.setTime( new Date(now) ); ArionAVFPathname_onPVR.setLength( 0 ); ArionAVFTime.setLength( 0 ); ArionFilenameRoot.setLength( 0 ); ArionCumulativeTime = 0L; ArionCumulativeStreamSize = 0L; // ArionAVFTime.append( String.format( "%1$tY%1$tm%1$td%1$tH%1$tM", now ) ); ArionAVFTime.append(Common.formatTime_5(now)); // ProjectX automatically appends (<partnumber>).ts to the filename. // we want to remove those bits and any leading path component. // The file will be renamed at the end (in the finishArionHeader routine), // we just save the details for now. ArionFilenameRoot.append( new File(name).getName() ); // start with leading path removed int parenIndex = ArionFilenameRoot.toString().lastIndexOf( "(" ); if ( parenIndex > 0 ) ArionFilenameRoot.delete( parenIndex, ArionFilenameRoot.length() ); /* * now check for total length of file name: * "C:\PVR\AV\" = 10 * "_#001_" = 6 * "yyyymmddhhmm" = 12 * ".AVR" = 4 * total 32. Max allowed is 125 plus null terminator. */ if ( ArionFilenameRoot.length() > 93 ) ArionFilenameRoot.setLength( 93 ); String avf_name = new String( ArionFilenameRoot.toString( ) + "_#001_" + ArionAVFTime.toString( ) + ".avf" ); ArionAVFPathname_onPVR.append( "C:\\PVR\\AV\\" + avf_name.toUpperCase() ); StringBuffer arionAVRPathname_onPVR = new StringBuffer( ArionAVFPathname_onPVR.toString() ); // full DOS-style pathname is only used in AVF header. arionAVRPathname_onPVR.setCharAt( arionAVRPathname_onPVR.length()-1, 'R' ); // now create AVF file in same place as new avr file. ArionAVFLocalPathname = new File( new File(name).getParentFile(), avf_name ); try { Common.setMessage("Arion: Creating initial AVF file"); RandomAccessFile avf_fd = new RandomAccessFile(ArionAVFLocalPathname, "rw"); byte avf_header[] = new byte[ ArionAVF_HEADERSIZE ]; System.arraycopy(ArionAVF_Header , 0, avf_header, 0, ArionAVF_Header.length); System.arraycopy(arionAVRPathname_onPVR.toString().getBytes() , 0, avf_header, 0x326, arionAVRPathname_onPVR.length()); avf_header[0x1B] = 0x02; avf_header[0x1C] = (byte) 0x80; avf_header[0x1D] = !ac3 ? (byte)0xC0 : (byte)0x80; //MPA // set 1. AC3 PID as main audio avf_header[0x1E] = 0x00; avf_header[0x1F] = ac3 ? (byte)0xC0 : (byte)0x80; //MPA // set 1. AC3 PID as main audio // now do the time/date for EPG and recording info. // since we have no meaningful information we will just use current time // for both sets of data. short year = (short) datum.get( datum.YEAR ); byte month = (byte) datum.get( datum.MONTH ); byte day = (byte) datum.get( datum.DAY_OF_MONTH ); byte hour = (byte) datum.get( datum.HOUR_OF_DAY ); byte minute = (byte) datum.get( datum.MINUTE ); avf_header[0x1f48] = 0x1b; avf_header[0x1f49] = (byte)0xee; avf_header[0x1f4a] = (byte)(year >>> 8 ); avf_header[0x1f4b] = (byte)(year & 0xff); avf_header[0x1f4c] = month; avf_header[0x1f4d] = day; avf_header[0x1f4e] = hour; avf_header[0x1f4f] = minute; avf_header[0x1f50] = 1; // one EPG entry. byte epg_entry[] = new byte[ ArionEPG_BLOCKSIZE ]; System.arraycopy(Arion_EPG_Block , 0, epg_entry, 0, Arion_EPG_Block.length); epg_entry[2] = (byte)(year >>> 8 ); epg_entry[3] = (byte)(year & 0xff); epg_entry[4] = month; epg_entry[5] = day; epg_entry[6] = hour; epg_entry[7] = minute; // assume the filename is useful as a program name. System.arraycopy(ArionFilenameRoot.toString().getBytes() , 0, epg_entry, 10, ArionFilenameRoot.length() ); // first copy into the "epg at start" area System.arraycopy(epg_entry , 0, avf_header, 0x5c, epg_entry.length); // then create the EPG table - with a single entry. System.arraycopy(epg_entry , 0, avf_header, 0x1f52, epg_entry.length); avf_fd.write( avf_header); avf_fd.close( ); } catch ( IOException e ) { Common.setExceptionMessage(e); } } /* create the local AVR filename for use in the finish. */ ArionAVRLocalFilename.setLength( 0 ); // ArionAVRLocalFilename.append(ArionFilenameRoot.toString() + String.format( "_#%03d_", splitPart+1) + ArionAVFTime.toString( ) + ".avr"); ArionAVRLocalFilename.append(ArionFilenameRoot.toString() + "_#" + Common.adaptString(splitPart + 1, 3) + "_" + ArionAVFTime.toString() + ".avr"); // Now create the AVR header, populate, and return it to caller. byte header[] = new byte[headerlength]; System.arraycopy(ArionAVR_Header , 0, header, 0, ArionAVR_Header.length); // copy the AVF filename into the AVR file header. System.arraycopy( ArionAVFPathname_onPVR.toString().getBytes(), 0, header, 10, ArionAVFPathname_onPVR.length() ); return header; } /* * called each time a file part has been written and been closed. * Need to: * rename AVR file to Arion style. * add entries to AVF header for: * size of this file, number of file parts, bitrate, * total playback time (minutes), total size of stream (redundant, but...) */ // todo--- PIDs, file sizes, and total size.; bitrate. private static String finishArionheaders(String old_name, long time[], JobProcessing job_processing ) throws IOException { // duration of recording - convert from 90kHz clock to ms. long millis = (time[1] - time[0]) / 90L; long duration_seconds = Math.round( millis / 1000.0f ); //total duration in seconds ArionCumulativeTime += duration_seconds; long duration_minutes = Math.round( ArionCumulativeTime / 60.0f ); //total duration in minutes int splitPart = job_processing.getSplitPart(); File avrFile = new File( new File(old_name).getParentFile(), ArionAVRLocalFilename.toString() ); if ( avrFile.exists() ) avrFile.delete(); Common.renameTo( new File(old_name), avrFile); // now get the AVR file length //RandomAccessFile ts = new RandomAccessFile(avrFile, "rw"); long currentStreamSize = avrFile.length() - ArionAVR_HEADERSIZE; ArionCumulativeStreamSize += currentStreamSize; //ts.close(); if ( currentStreamSize > 0x7f9a0000 ) { // not sure how to abort this cleanly. Common.setMessage("!> Arion: File is too large " + String.valueOf(avrFile.length()) + " bytes)"); return (new String( "noFileCreated" ) ); } // now reopen the AVF file and update the timing info... RandomAccessFile ts = new RandomAccessFile( ArionAVFLocalPathname, "rw"); Common.setMessage("Arion: renamed '" + old_name + "'"); Common.setMessage(" to '" + avrFile.getPath() + "'"); Common.setMessage(" size 0x" + Long.toHexString(currentStreamSize).toUpperCase() + ", duration " + String.valueOf(duration_seconds) + " sec (cumulative: " + String.valueOf(duration_minutes) + " min)"); int byterate = (int) (ArionCumulativeStreamSize / ArionCumulativeTime); Common.setMessage("Arion: bitrate = " + String.valueOf(byterate) + " bytes/s"); // write the nth stream size ... ts.seek(0x3a4 + 4 * splitPart ); ts.writeInt( (int)currentStreamSize ); ts.seek(0x6c4); ts.writeInt( byterate ); ts.writeInt( (int)duration_minutes ); ts.writeShort( splitPart+1 ); ts.seek(0x6d0); ts.writeLong( ArionCumulativeStreamSize ); Common.setMessage("Arion: Written " + String.valueOf(ArionCumulativeStreamSize) + " bytes"); ts.close(); return old_name; } private static byte[] generateCRC32(byte[] data, int offset) { // x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1 int[] g = { 1,1,1,0,1,1,0,1,1,0,1,1,1,0,0,0,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,1 }; int[] shift_reg = new int[32]; long crc = 0; byte[] crc32 = new byte[4]; // Initialize shift register's to '1' Arrays.fill(shift_reg, 1); // Calculate nr of data bits, summa of bits int nr_bits = (data.length - offset) * 8; for (int bit_count = 0, bit_in_byte = 0, data_bit; bit_count < nr_bits; bit_count++) { // Fetch bit from bitstream data_bit = (data[offset] & 0x80>>>(bit_in_byte++)) != 0 ? 1 : 0; if ((bit_in_byte &= 7) == 0) offset++; // Perform the shift and modula 2 addition data_bit ^= shift_reg[31]; for (int i = 31; i > 0; i--) shift_reg[i] = g[i]==1 ? (shift_reg[i - 1] ^ data_bit) : shift_reg[i - 1]; shift_reg[0] = data_bit; } for (int i = 0; i < 32; i++) crc = ((crc << 1) | (shift_reg[31 - i])); for (int i = 0; i < 4; i++) crc32[i] = (byte)(0xFF & (crc >>>((3 - i) * 8))); return crc32; } }