/* * @(#)StreamConverter * * Copyright (c) 2005-2006 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.util.ArrayList; import java.util.List; import java.util.Arrays; import java.io.File; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.io.PushbackInputStream; import net.sourceforge.dvb.projectx.common.Resource; import net.sourceforge.dvb.projectx.common.Common; import net.sourceforge.dvb.projectx.common.Keys; import net.sourceforge.dvb.projectx.common.JobProcessing; import net.sourceforge.dvb.projectx.common.JobCollection; import net.sourceforge.dvb.projectx.io.IDDBufferedOutputStream; import net.sourceforge.dvb.projectx.thirdparty.TS; import net.sourceforge.dvb.projectx.video.Video; import net.sourceforge.dvb.projectx.parser.CommonParsing; import net.sourceforge.dvb.projectx.parser.StreamDemultiplexer; import net.sourceforge.dvb.projectx.xinput.XInputFile; /** * create streams */ public class StreamConverter extends Object { private String FileName = ""; private boolean FirstPacket = true; private boolean ptsover = false; private boolean ContainsVideo = false; private boolean BrokenLinkFlag = false; private boolean Debug = false; private boolean AddPcrToStream = false; private boolean ExportVideo = false; private boolean ExportNonVideo = false; private boolean MustStartWithVideo = false; private boolean SetMainAudioAc3 = false; private boolean GenerateTTX = false; private boolean GeneratePMT = false; private boolean PcrCounter = false; private boolean CreateVdrIndex = false; private boolean MuxPES = false; private byte[] PackHeader = { 0, 0, 1, (byte)0xBA, 0x44, 0, 4, 0, 4, 1, 0, (byte)0xEA, 0x63, (byte)0xF8 }; // 8000kbps private byte[] LeadingPackHeader = { 0, 0, 1, (byte)0xBA, 0x44, 0, 4, 0, 4, 1, 0, (byte)0xEA, 0x63, (byte)0xF8 }; // 8000kbps private byte[] SequenceEndCode = { 0, 0, 1, (byte)0xB9 }; private byte[] TsStartPacketHeader = { 0x47, 0x40, 0, 0 }; // 1x = no adap, 3x adap follw private byte[] TsSubPacketHeader = { 0x47, 0, 0, 0 }; // 1x = no adap, 3x adap follw private byte[] PvaPacketHeader = { 0x41, 0x56, 0, 0, 0x55, 0, 0, 0 }; private byte[] PvaPacketHeaderAndPTS = { 0x41, 0x56, 0, 0, 0x55, 0x10, 0, 0, 0, 0, 0, 0 }; private byte[] SystemHeader = { 0, 0, 1, (byte)0xBB, 0, 0xC, (byte)0x80, (byte)0x9C, 0x41, 4, 0x21, 0x7F }; // 12+ byte std 1A, 1V 8Mbps (20000 * 400) private byte[][] sys = { { (byte)0xE0, (byte)0xE0, (byte)0xE0 }, // 224kb 0 MPV { (byte)0xBD, (byte)0xC0, 0x20 }, // 4kb 1 pd_AC3 { (byte)0xC0, (byte)0xC0, 0x20 } // 4kb 2 MPA }; private byte[] subID = { (byte)0x80, 1, 0, 1 }; // std 0x80,1,0,1 private byte[] sysID = { (byte)0xE0, (byte)0xC0 }; // ID adder mpv+mpa private byte[] adapt = { 0, 0 }; private byte[] StuffingData = new byte[2324]; private long[] time = new long[2]; private long SCR_Value = 0; private long PmtCounter = 0; private long PCR_Delta = 65000; private final int PVA_MAINVIDEO = 1; private final int PVA_MAINAUDIO = 2; private int Action = 0; private int CutMode = 0; private int SystemHeaderInsertPoint = 26; private int SystemHeaderLength = 138; private int Packet = 0; private int TsHeaderMode = 0; private int[] PacketCounter = new int[70]; // pva counter , 1=stream1,2=stream2 4... rest private int[] SystemHeaderStreams = new int[2]; // number of audio[0], video[1] streams private List PaddingStreamPositions; private List IDs; private IDDBufferedOutputStream OutputStream; private ByteArrayOutputStream Buffer; // private ArrayList remuxList = new ArrayList(); // public StreamConverter() { PaddingStreamPositions = new ArrayList(); IDs = new ArrayList(); Buffer = new ByteArrayOutputStream(); } /** * */ private void getSettings(JobCollection collection) { ExportVideo = collection.getSettings().getBooleanProperty(Keys.KEY_WriteOptions_writeVideo); ExportNonVideo = collection.getSettings().getBooleanProperty(Keys.KEY_WriteOptions_writeAudio); Debug = collection.getSettings().getBooleanProperty(Keys.KEY_DebugLog); PCR_Delta = collection.getSettings().getIntProperty(Keys.KEY_PcrDelta_Value); CutMode = collection.getSettings().getIntProperty(Keys.KEY_CutMode); AddPcrToStream = collection.getSettings().getBooleanProperty(Keys.KEY_Conversion_addPcrToStream); MustStartWithVideo = collection.getSettings().getBooleanProperty(Keys.KEY_Conversion_startWithVideo); SetMainAudioAc3 = collection.getSettings().getBooleanProperty(Keys.KEY_TS_setMainAudioAc3); GenerateTTX = collection.getSettings().getBooleanProperty(Keys.KEY_TS_generateTtx); GeneratePMT = collection.getSettings().getBooleanProperty(Keys.KEY_TS_generatePmt); TsHeaderMode = collection.getSettings().getIntProperty(Keys.KEY_TsHeaderMode); PcrCounter = collection.getSettings().getBooleanProperty(Keys.KEY_Conversion_PcrCounter); CreateVdrIndex = collection.getSettings().getBooleanProperty(Keys.KEY_ExternPanel_createVdrIndex); } /** * */ public void init(JobCollection collection, String _name, int buffersize, int action, int filenumber) { FileName = _name; FirstPacket = true; ContainsVideo = false; Action = action; BrokenLinkFlag = false; ptsover = false; PmtCounter = 0; SystemHeaderInsertPoint = 26; LeadingPackHeader[2] = 0; time[0] = -1; SCR_Value = 0; Packet = 0; getSettings(collection); Arrays.fill(StuffingData, (byte)0xFF); Buffer.reset(); if (filenumber == 0) { IDs.clear(); //prepare teletext insertion, at first file if (action == CommonParsing.ACTION_TO_TS) TS.buildTeletextStream(((XInputFile) collection.getInputFile(0)).toString()); //prepare muxing of secondary pes at toVDR remuxList.clear(); MuxPES = Common.getSettings().getBooleanProperty("HiddenKey.VDRExport.MuxPES", false); if (action == CommonParsing.ACTION_TO_VDR) scanSecondaryStreams(collection); } try { OutputStream = new IDDBufferedOutputStream(new FileOutputStream(FileName), buffersize); if (Action == CommonParsing.ACTION_TO_VDR && CreateVdrIndex) OutputStream.InitVdr(new File(FileName).getParent() + System.getProperty("file.separator") + "index.vdr", filenumber); } catch (IOException e) { Common.setExceptionMessage(e); } } /** * set broken link flag after a cut */ private void setBrokenLink(JobProcessing job_processing, byte[] pes_packet, int pes_offset) { if (CommonParsing.validateStartcode(pes_packet, pes_offset) < 0) { Common.setMessage("!> invalid start_code of packet (sbl), pos: " + job_processing.getLastHeaderBytePosition()); return; } int pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, pes_offset); int pes_headerlength = 9; int pes_packetlength = pes_headerlength - 3 + pes_payloadlength; int pes_extensionlength = CommonParsing.getPES_ExtensionLengthField(pes_packet, pes_offset); for (int i = pes_headerlength + pes_extensionlength + pes_offset, j = pes_packetlength - 7 + pes_offset, returncode; i < j; ) { if ((returncode = CommonParsing.validateStartcode(pes_packet, i)) < 0 || CommonParsing.getPES_IdField(pes_packet, i) != CommonParsing.GROUP_START_CODE) { i += returncode < 0 ? -returncode : 4; continue; } pes_packet[7 + i] |= 0x20; break; } BrokenLinkFlag = true; // even if it wasn't found } /** * repack mpg1 to mpg2 */ private void repackMpg1(byte[] pes_packet, int pes_offset, StreamDemultiplexer streamdemultiplexer) { if (streamdemultiplexer.getStreamType() != CommonParsing.MPEG1PS_TYPE) return; int pes_packetoffset = 6; int pes_headerlength = 9; int pes_extensionlength = 0; int pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, pes_offset); int pes_packetlength = pes_packetoffset + pes_payloadlength; int offset = pes_packetoffset; int value = 0x800000; skiploop: for (;;) { switch (0xC0 & pes_packet[offset + pes_offset]) { case 0x40: offset += 2; continue skiploop; case 0x80: offset += 3; continue skiploop; case 0xC0: offset++; continue skiploop; case 0: break; } switch (0x30 & pes_packet[offset + pes_offset]) { case 0x20: //PTS pes_extensionlength = 5; break skiploop; case 0x30: //PTS+DTS pes_extensionlength = 10; break skiploop; case 0x10: //DTS offset += 5; break skiploop; case 0: offset++; break skiploop; } } if (pes_extensionlength == 5) value = 0x808005; else if (pes_extensionlength == 10) value = 0x80C00A; System.arraycopy(pes_packet, offset + pes_offset, pes_packet, pes_headerlength + pes_offset, pes_packetlength - offset); CommonParsing.setValue(pes_packet, pes_packetoffset + pes_offset, 3, !CommonParsing.BYTEREORDERING, value); CommonParsing.setPES_LengthField(pes_packet, pes_offset, pes_packetlength - offset + 3); } /** * */ private void setSCRField(byte[] packet_header) { if (Debug) System.out.println("SCR_Value " + SCR_Value); if (!AddPcrToStream) return; packet_header[4] = (byte)(0x44 | (0x3 & (SCR_Value>>>28)) | (0x38 & (SCR_Value>>>27))); packet_header[5] = (byte)(0xFF & (SCR_Value>>>20)); packet_header[6] = (byte)(0x4 | (0x3 & (SCR_Value>>>13)) | (0xF8 & (SCR_Value>>>12))); packet_header[7] = (byte)(0xFF & (SCR_Value>>>5)); packet_header[8] = (byte)(0x4 | (0xF8 & (SCR_Value<<3))); } /** * */ private void updateSCR(int length) { SCR_Value += (length * 8) / 150; } /** * */ private void updateSCR(long value) { SCR_Value += value; } /** * entry point and pre functions */ public void write(JobProcessing job_processing, byte[] pes_packet, StreamDemultiplexer streamdemultiplexer, long cutposition, boolean qinfo, List cutpoints) throws IOException { write(job_processing, pes_packet, 0, pes_packet.length, streamdemultiplexer, cutposition, qinfo, cutpoints); } /** * entry point and pre functions */ public void write(JobProcessing job_processing, byte[] pes_packet, int pes_offset, StreamDemultiplexer streamdemultiplexer, long cutposition, boolean qinfo, List cutpoints) throws IOException { write(job_processing, pes_packet, pes_offset, pes_packet.length, streamdemultiplexer, cutposition, qinfo, cutpoints); } /** * entry point and pre functions */ public void write(JobProcessing job_processing, byte[] pes_packet, int pes_offset, int pes_packetlength, StreamDemultiplexer streamdemultiplexer, long cutposition, boolean qinfo, List cutpoints) throws IOException { /** * cut-off determination */ if (CutMode == CommonParsing.CUTMODE_BYTE && !qinfo && !CommonParsing.makecut(job_processing, cutposition + 5, cutpoints) ) { BrokenLinkFlag = false; return; } /** * 1:1 copy filter */ if (Action == CommonParsing.ACTION_FILTER) { writePacket(job_processing, pes_packet, pes_offset, pes_packetlength); return; } /** * repack mpg1 pes source to mpg2 */ repackMpg1(pes_packet, pes_offset, streamdemultiplexer); if (streamdemultiplexer.getType() == CommonParsing.MPEG_VIDEO && !BrokenLinkFlag) setBrokenLink(job_processing, pes_packet, pes_offset); if (Buffer.size() != 0) Buffer.reset(); switch(Action) { case CommonParsing.ACTION_TO_VDR: packetizeToVDR(job_processing, pes_packet, pes_offset, streamdemultiplexer); return; case CommonParsing.ACTION_TO_M2P: packetizeToM2P(job_processing, pes_packet, pes_offset, streamdemultiplexer); return; case CommonParsing.ACTION_TO_PVA: packetizeToPVA(job_processing, pes_packet, pes_offset, streamdemultiplexer); return; case CommonParsing.ACTION_TO_TS: packetizeToTS(job_processing, pes_packet, pes_offset, streamdemultiplexer, qinfo); return; } } /** * simply write the packet */ public void writePacket(JobProcessing job_processing, byte[] packet) throws IOException { writePacket(job_processing, packet, 0, packet.length); } /** * simply write the packet */ public void writePacket(JobProcessing job_processing, byte[] packet, int offset, int length) throws IOException { if (offset < 0 || offset >= packet.length) { Common.setMessage("!> packet writing: index out of bounds, ignore it.. (" + Packet + ")"); return; } if (offset + length > packet.length) { Common.setMessage("!> packet writing: length index out of bounds, shortened.. (" + Packet + ")"); length = packet.length - offset; } OutputStream.write(packet, offset, length); job_processing.countMediaFilesExportLength(length); job_processing.countAllMediaFilesExportLength(length); } /** * export VDR */ private void packetizeToVDR(JobProcessing job_processing, byte[] pes_packet, int pes_offset, StreamDemultiplexer streamdemultiplexer) { try { int es_streamtype = streamdemultiplexer.getType(); int pes_streamtype = streamdemultiplexer.getStreamType(); int newID = streamdemultiplexer.getnewID(); if (CommonParsing.validateStartcode(pes_packet, pes_offset) < 0) { Common.setMessage("!> invalid startcode " + Packet + ", pos: " + job_processing.getLastHeaderBytePosition() + " (" + Integer.toHexString(newID) + "/" + es_streamtype + ")"); return; } int pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, pes_offset); int pes_headerlength = 9; int pes_packetlength = pes_headerlength - 3 + pes_payloadlength; int offset = 0; if (pes_packetlength + pes_offset > pes_packet.length) return; int pes_extensionlength = CommonParsing.getPES_ExtensionLengthField(pes_packet, pes_offset); boolean containsPTS = CommonParsing.clearBit33ofPTS(pes_packet, pes_offset); CommonParsing.clearBit33ofDTS(pes_packet, pes_offset); switch (es_streamtype) { case CommonParsing.AC3_AUDIO: case CommonParsing.DTS_AUDIO: if (!ExportNonVideo || (0xFF & newID) != 0x80) return; // not more than one stream if (pes_streamtype == CommonParsing.MPEG2PS_TYPE) { // skip substream-header pes_payloadlength -= 4; CommonParsing.setPES_LengthField(pes_packet, pes_offset, pes_payloadlength); offset = pes_headerlength + pes_extensionlength; writePacket(job_processing, pes_packet, pes_offset, offset); // write leading hader offset += 4; } break; case CommonParsing.MPEG_AUDIO: if (!ExportNonVideo) return; CommonParsing.setPES_IdField(pes_packet, pes_offset, newID); break; case CommonParsing.TELETEXT: if (!ExportNonVideo || (0x7F & newID) > 0x1F) return; // not more than 16 streams CommonParsing.setPES_SubIdField(pes_packet, pes_offset, pes_headerlength, pes_extensionlength, newID - 0x80); break; case CommonParsing.MPEG_VIDEO: if (!ExportVideo) return; CommonParsing.setPES_IdField(pes_packet, pes_offset, newID); //pes remux if (containsPTS) remuxPES(job_processing, CommonParsing.getPTSfromBytes(pes_packet, pes_headerlength + pes_offset)); break; case CommonParsing.LPCM_AUDIO: case CommonParsing.SUBPICTURE: default: return; } writePacket(job_processing, pes_packet, offset + pes_offset, pes_packetlength - offset); } catch (IOException e) { Common.setExceptionMessage(e); } } /** * write MPG */ private void packetizeToM2P(JobProcessing job_processing, byte[] pes_packet, int pes_offset, StreamDemultiplexer streamdemultiplexer) { try { int es_streamtype = streamdemultiplexer.getType(); int pes_streamtype = streamdemultiplexer.getStreamType(); int newID = streamdemultiplexer.getnewID(); if (CommonParsing.validateStartcode(pes_packet, pes_offset) < 0) { Common.setMessage("!> invalid startcode " + Packet + ", pos: " + job_processing.getLastHeaderBytePosition() + " (" + Integer.toHexString(newID) + "/" + es_streamtype + ")"); return; } int pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, pes_offset); int pes_headerlength = 9; int pes_packetlength = pes_headerlength - 3 + pes_payloadlength; int offset = 0; if (pes_packetlength + pes_offset > pes_packet.length) return; int pes_extensionlength = CommonParsing.getPES_ExtensionLengthField(pes_packet, pes_offset); boolean newSCR = false; boolean containsPTS; containsPTS = CommonParsing.clearBit33ofPTS(pes_packet, pes_offset); CommonParsing.clearBit33ofDTS(pes_packet, pes_offset); switch (es_streamtype) { case CommonParsing.AC3_AUDIO: case CommonParsing.DTS_AUDIO: if (!ExportNonVideo) return; break; case CommonParsing.MPEG_AUDIO: if (!ExportNonVideo) return; CommonParsing.setPES_IdField(pes_packet, pes_offset, newID); break; case CommonParsing.MPEG_VIDEO: if (!ExportVideo || (0xFF & newID) != 0xE0) // not more than one stream return; CommonParsing.setPES_IdField(pes_packet, pes_offset, newID); break; case CommonParsing.LPCM_AUDIO: case CommonParsing.SUBPICTURE: case CommonParsing.TELETEXT: default: return; } /** * return, newID is not set, for mpg (curr. 0x20..,0xA0..) */ if (newID == 0) return; // indexof, int a = 0; /** * new ID arrived, align data */ for ( ; a < IDs.size(); a++) { if (newID == (0xFF & Integer.parseInt(IDs.get(a).toString()))) break; } if (a == IDs.size()) { if (es_streamtype != CommonParsing.MPEG_VIDEO && !ContainsVideo && MustStartWithVideo) return; if (!CommonParsing.alignSyncword(pes_packet, pes_offset, es_streamtype)) return; pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, pes_offset); pes_packetlength = pes_headerlength - 3 + pes_payloadlength; IDs.add(String.valueOf((newID > 0xDF) ? newID : (0x100 + newID))); } // if (FirstPacket) { writePacket(job_processing, PackHeader); writePacket(job_processing, new byte[SystemHeaderLength - 8]); // placeholder for systemheader FirstPacket = false; } if (es_streamtype == CommonParsing.AC3_AUDIO || es_streamtype == CommonParsing.DTS_AUDIO) { if (pes_streamtype == CommonParsing.MPEG2PS_TYPE) { CommonParsing.setPES_SubIdField(pes_packet, pes_offset, pes_headerlength, pes_extensionlength, newID); updateSCR(pes_packetlength); setSCRField(PackHeader); writePacket(job_processing, PackHeader); writePacket(job_processing, pes_packet, pes_offset, pes_packetlength); return; } else { pes_payloadlength += 4; pes_packetlength += 4; offset = pes_headerlength + pes_extensionlength; CommonParsing.setPES_LengthField(pes_packet, pes_offset, pes_payloadlength); CommonParsing.setValue(subID, pes_offset, 1, !CommonParsing.BYTEREORDERING, newID); updateSCR(pes_packetlength); setSCRField(PackHeader); writePacket(job_processing, PackHeader); writePacket(job_processing, pes_packet, pes_offset, offset); writePacket(job_processing, subID, pes_offset, subID.length); // write 4 byte of substreamheader writePacket(job_processing, pes_packet, offset + pes_offset, pes_packetlength - offset - subID.length); return; } } if (!ContainsVideo) { if (es_streamtype == CommonParsing.MPEG_VIDEO) ContainsVideo = true; else PaddingStreamPositions.add(String.valueOf(job_processing.getAllMediaFilesExportLength() + 17)); } if (es_streamtype == CommonParsing.MPEG_VIDEO && containsPTS && AddPcrToStream) { for (int i = pes_headerlength + pes_extensionlength + pes_offset, picture_type, returncode; i < pes_packetlength - 6 + pes_offset; i++) { /** * search picture header */ if ((returncode = CommonParsing.validateStartcode(pes_packet, i)) < 0 || CommonParsing.getPES_IdField(pes_packet, i) != CommonParsing.PICTURE_START_CODE) { i += (returncode < 0 ? -returncode : 4) - 1; continue; } picture_type = 7 & pes_packet[i + 5]>>>3; /** * is I or P-Frame */ // if (picture_type == CommonParsing.FRAME_I_TYPE || picture_type == CommonParsing.FRAME_P_TYPE) if (picture_type == CommonParsing.FRAME_I_TYPE) { SCR_Value = CommonParsing.getPTSfromBytes(pes_packet, pes_headerlength + pes_offset); updateSCR(-PCR_Delta); setSCRField(PackHeader); if (LeadingPackHeader[2] == 0) System.arraycopy(PackHeader, 0, LeadingPackHeader, 0, PackHeader.length); writePacket(job_processing, PackHeader); newSCR = true; } break; } } if (!newSCR) { updateSCR(pes_packetlength); setSCRField(PackHeader); writePacket(job_processing, PackHeader); } writePacket(job_processing, pes_packet, pes_offset, pes_packetlength); } catch (IOException e) { Common.setExceptionMessage(e); } } /** * write pva */ private void packetizeToPVA(JobProcessing job_processing, byte[] pes_packet, int pes_offset, StreamDemultiplexer streamdemultiplexer) { try { long pts; int es_streamtype = streamdemultiplexer.getType(); int pes_streamtype = streamdemultiplexer.getStreamType(); int newID = streamdemultiplexer.getnewID(); // read new mapped ID if (CommonParsing.validateStartcode(pes_packet, pes_offset) < 0) { Common.setMessage("!> invalid startcode " + Packet + ", pos: " + job_processing.getLastHeaderBytePosition() + " (" + Integer.toHexString(newID) + "/" + es_streamtype + ")"); return; } int pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, pes_offset); int pes_headerlength = 9; int pes_packetlength = pes_headerlength - 3 + pes_payloadlength; int offset = 0; int countID = 3; if (pes_packetlength + pes_offset > pes_packet.length) return; int pes_extensionlength = CommonParsing.getPES_ExtensionLengthField(pes_packet, pes_offset); boolean containsPTS; containsPTS = CommonParsing.clearBit33ofPTS(pes_packet, pes_offset); CommonParsing.clearBit33ofDTS(pes_packet, pes_offset); switch (es_streamtype) { case CommonParsing.AC3_AUDIO: case CommonParsing.DTS_AUDIO: case CommonParsing.TELETEXT: if (!ExportNonVideo) return; break; case CommonParsing.MPEG_AUDIO: if (!ExportNonVideo) return; CommonParsing.setPES_IdField(pes_packet, pes_offset, newID); break; case CommonParsing.MPEG_VIDEO: if (!ExportVideo || (0xFF & newID) != 0xE0) // not more than one video return; /** * looking for sequence_start_code */ for (int i = pes_headerlength + pes_extensionlength + pes_offset; !ContainsVideo && i < pes_packetlength - 4 + pes_offset; i++) { if (pes_packet[i] != 0 || pes_packet[1 + i] != 0 || pes_packet[2 + i] != 1 || pes_packet[3 + i] != (byte)CommonParsing.SEQUENCE_HEADER_CODE) continue; ContainsVideo = true; } if (MustStartWithVideo && !ContainsVideo) return; offset = pes_headerlength; CommonParsing.setPES_IdField(pes_packet, pes_offset, newID); if (containsPTS) { pts = CommonParsing.getPTSfromBytes(pes_packet, offset + pes_offset); offset += pes_extensionlength; pes_payloadlength = pes_packetlength - offset; CommonParsing.setValue(PvaPacketHeaderAndPTS, 2, 1, !CommonParsing.BYTEREORDERING, PVA_MAINVIDEO); CommonParsing.setValue(PvaPacketHeaderAndPTS, 3, 1, !CommonParsing.BYTEREORDERING, 0xFF & PacketCounter[PVA_MAINVIDEO]); CommonParsing.setValue(PvaPacketHeaderAndPTS, 6, 2, !CommonParsing.BYTEREORDERING, pes_payloadlength + 4); CommonParsing.setValue(PvaPacketHeaderAndPTS, 8, 4, !CommonParsing.BYTEREORDERING, pts); writePacket(job_processing, PvaPacketHeaderAndPTS); } else { offset += pes_extensionlength; pes_payloadlength = pes_packetlength - offset; CommonParsing.setValue(PvaPacketHeader, 2, 1, !CommonParsing.BYTEREORDERING, PVA_MAINVIDEO); CommonParsing.setValue(PvaPacketHeader, 3, 1, !CommonParsing.BYTEREORDERING, 0xFF & PacketCounter[PVA_MAINVIDEO]); CommonParsing.setValue(PvaPacketHeader, 5, 1, !CommonParsing.BYTEREORDERING, 0); CommonParsing.setValue(PvaPacketHeader, 6, 2, !CommonParsing.BYTEREORDERING, pes_payloadlength); writePacket(job_processing, PvaPacketHeader); } writePacket(job_processing, pes_packet, offset + pes_offset, pes_payloadlength); PacketCounter[PVA_MAINVIDEO]++; return; case CommonParsing.LPCM_AUDIO: case CommonParsing.SUBPICTURE: default: return; } if (MustStartWithVideo && !ContainsVideo) return; switch (newID) { case 0xC0: //�ndern CommonParsing.setValue(PvaPacketHeader, 2, 1, !CommonParsing.BYTEREORDERING, PVA_MAINAUDIO); CommonParsing.setValue(PvaPacketHeader, 3, 1, !CommonParsing.BYTEREORDERING, 0xFF & PacketCounter[PVA_MAINAUDIO]); CommonParsing.setValue(PvaPacketHeader, 5, 1, !CommonParsing.BYTEREORDERING, 0x10); CommonParsing.setValue(PvaPacketHeader, 6, 2, !CommonParsing.BYTEREORDERING, pes_packetlength); PacketCounter[PVA_MAINAUDIO]++; writePacket(job_processing, PvaPacketHeader); writePacket(job_processing, pes_packet, pes_offset, pes_packetlength); return; default: //�ndern switch (0xF0 & newID) { case 0x90: //ttx �ndern countID = newID - 0x8C; break; case 0x80: countID = newID - 0x6C; break; case 0xC0: case 0xD0: countID = newID - 0x9C; } CommonParsing.setValue(PvaPacketHeader, 2, 1, !CommonParsing.BYTEREORDERING, newID); CommonParsing.setValue(PvaPacketHeader, 3, 1, !CommonParsing.BYTEREORDERING, 0xFF & PacketCounter[countID]); CommonParsing.setValue(PvaPacketHeader, 5, 1, !CommonParsing.BYTEREORDERING, 0x10); PacketCounter[countID]++; if (es_streamtype == CommonParsing.AC3_AUDIO || es_streamtype == CommonParsing.DTS_AUDIO) { if (pes_streamtype == CommonParsing.MPEG2PS_TYPE) { // skip substream-header pes_payloadlength -= 4; CommonParsing.setPES_LengthField(pes_packet, pes_offset, pes_payloadlength); CommonParsing.setValue(PvaPacketHeader, 6, 2, !CommonParsing.BYTEREORDERING, pes_packetlength - 4); offset = pes_headerlength + pes_extensionlength; writePacket(job_processing, PvaPacketHeader); writePacket(job_processing, pes_packet, pes_offset, offset); offset += 4; writePacket(job_processing, pes_packet, offset + pes_offset, pes_packetlength - offset); return; } } CommonParsing.setValue(PvaPacketHeader, 6, 2, !CommonParsing.BYTEREORDERING, pes_packetlength); writePacket(job_processing, PvaPacketHeader); writePacket(job_processing, pes_packet, pes_offset, pes_packetlength); } } catch (IOException e) { Common.setExceptionMessage(e); } } /** * mpeg-ts */ private void packetizeToTS(JobProcessing job_processing, byte[] pes_packet, int pes_offset, StreamDemultiplexer streamdemultiplexer, boolean qinfo) { try { boolean pcr = false; boolean ttx_pts_check = false; //search for frame pts for ttx insertion long pcrbase = 0; long pts = 0; int es_streamtype = streamdemultiplexer.getType(); int pes_streamtype = streamdemultiplexer.getStreamType(); int newID = streamdemultiplexer.getnewID(); if (CommonParsing.validateStartcode(pes_packet, pes_offset) < 0) { Common.setMessage("!> invalid startcode " + Packet + ", pos: " + job_processing.getLastHeaderBytePosition() + " (" + Integer.toHexString(newID) + "/" + es_streamtype + ")"); return; } int pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, pes_offset); int pes_headerlength = 9; int pes_packetlength = pes_headerlength - 3 + pes_payloadlength; int offset = 0; int countID = 3; if (pes_packetlength + pes_offset > pes_packet.length) return; int pes_extensionlength = CommonParsing.getPES_ExtensionLengthField(pes_packet, pes_offset); boolean containsPTS; containsPTS = CommonParsing.clearBit33ofPTS(pes_packet, pes_offset); CommonParsing.clearBit33ofDTS(pes_packet, pes_offset); switch (es_streamtype) { case CommonParsing.AC3_AUDIO: case CommonParsing.DTS_AUDIO: if (!ExportNonVideo) return; break; case CommonParsing.MPEG_AUDIO: if (!ExportNonVideo) return; break; case CommonParsing.TELETEXT: if (!ExportNonVideo) return; break; case CommonParsing.MPEG_VIDEO: if (!ExportVideo || (0xFF & newID) != 0xE0) // not more than one stream return; break; // case CommonParsing.SUBPICTURE: if (!ExportNonVideo || !Common.getSettings().getBooleanProperty("HiddenKey.TSExport.Subpicture", false)) return; break; // case CommonParsing.LPCM_AUDIO: default: return; } if (!qinfo) if (es_streamtype != CommonParsing.MPEG_VIDEO && !ContainsVideo && MustStartWithVideo) return; // must start with video if (es_streamtype == CommonParsing.AC3_AUDIO || es_streamtype == CommonParsing.DTS_AUDIO) { if (pes_streamtype == CommonParsing.MPEG2PS_TYPE) { // skip substream-header offset = pes_headerlength + pes_extensionlength + 4; // System.arraycopy(pes_packet, pes_headerlength + pes_extensionlength + pes_offset, pes_packet, offset + pes_offset, pes_packetlength - offset); System.arraycopy(pes_packet, offset + pes_offset, pes_packet, pes_headerlength + pes_extensionlength + pes_offset, pes_packetlength - offset); pes_payloadlength -= 4; pes_packetlength -= 4; CommonParsing.setPES_LengthField(pes_packet, pes_offset, pes_payloadlength); } } // int a = 0; /** * new ID arrived, save for PMT */ for (; a < IDs.size(); a++) { if (newID == (0xFF & Integer.parseInt(IDs.get(a).toString()))) break; } if (a == IDs.size()) //make adaptions of 1st paket { int newID2 = newID; //sort the id's for PMT table, MPV has highest prior. if (newID2 < 0x90) //AC3 DTS SUP newID2 |= 0x200; if (newID2 < 0xC0) //TTX PCM newID2 |= 0x300; if (newID2 < 0xE0) //MPA newID2 |= 0x100; if (!qinfo) // align syncwords { // if (es_streamtype != CommonParsing.TELETEXT) if (es_streamtype != CommonParsing.TELETEXT && es_streamtype != CommonParsing.SUBPICTURE) { if (!CommonParsing.alignSyncword(pes_packet, pes_offset, es_streamtype)) return; } pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, pes_offset); pes_packetlength = pes_headerlength - 3 + pes_payloadlength; } IDs.add(String.valueOf(newID2)); } // CommonParsing.setValue(TsStartPacketHeader, 2, 1, !CommonParsing.BYTEREORDERING, newID); CommonParsing.setValue(TsSubPacketHeader, 2, 1, !CommonParsing.BYTEREORDERING, newID); if (es_streamtype == CommonParsing.MPEG_VIDEO) CommonParsing.setPES_LengthField(pes_packet, pes_offset, 0); /** * add TS header */ if (FirstPacket) { Buffer.write(TS.init( job_processing, FileName, SetMainAudioAc3, GenerateTTX, TsHeaderMode)); FirstPacket = false; } if (containsPTS && TS.getfirstID() == (0xFF & newID)) { pts = CommonParsing.getPTSfromBytes(pes_packet, pes_headerlength + pes_offset); pcrbase = pts - PCR_Delta; if ( (pts & 0xFF000000L) == 0xFF000000L ) ptsover = true; if (ptsover && pts < 0xF0000000L) pts |= 0x100000000L; time[1] = pts; pcr = true; if (time[0] == -1) time[0] = time[1]; } switch (0xF0 & newID) //�ndern { case 0x80: countID = newID - 0x6C; break; case 0x90: countID = newID - 0x8C; break; case 0xC0: case 0xD0: countID = newID - 0x9C; break; case 0xE0: countID = 1; if (containsPTS) { pcr = false; for (int i = pes_headerlength + pes_extensionlength + pes_offset, picture_type, returncode; i < pes_packetlength - 6 + pes_offset; i++) { /** * search picture header */ if ((returncode = CommonParsing.validateStartcode(pes_packet, i)) < 0 || CommonParsing.getPES_IdField(pes_packet, i) != CommonParsing.PICTURE_START_CODE) { i += (returncode < 0 ? -returncode : 4) - 1; continue; } picture_type = 7 & pes_packet[i + 5]>>>3 ; /** * is I or P-Frame */ //if (picture_type == CommonParsing.FRAME_I_TYPE || picture_type == CommonParsing.FRAME_P_TYPE) if (picture_type == CommonParsing.FRAME_I_TYPE) { pcr = true; ContainsVideo = true; } if (picture_type == CommonParsing.FRAME_I_TYPE || picture_type == CommonParsing.FRAME_P_TYPE) ttx_pts_check = true; //work with I+P+B-frames for ttx pts check break; } } break; } if (es_streamtype == CommonParsing.MPEG_VIDEO && !ContainsVideo && MustStartWithVideo) return; // must start with video, reached on qinfo if (30000L * PmtCounter <= job_processing.getAllMediaFilesExportLength()) { Buffer.write(TS.getPAT()); if (GeneratePMT) Buffer.write(TS.getAutoPMT()); else Buffer.write(TS.getPMT()); PmtCounter++; } /** * add own TTX */ //if (pcr && GeneratePMT && GenerateTTX) // Buffer.write( TS.getTTX(pes_packet, pes_offset, Common.formatTime_1((pcrbase + PCR_Delta) / 90)) ); //TTX sub insertion- from 0.90.4.00b28 if (GenerateTTX && ttx_pts_check) { Buffer.write(TS.getTeletextStream(time[1])); } /** * add Pcr */ if (pcr && AddPcrToStream) { if (!PcrCounter) Buffer.write(TS.getPCR(pcrbase, PacketCounter[countID], (0xFF & newID))); // no payload==no counter++ else Buffer.write(TS.getPCR(pcrbase, ++PacketCounter[countID], (0xFF & newID))); } int i = 0; int stuffinglength; while (i < pes_packetlength) { if (i == 0) { if (pes_packetlength - i < 183) { TsStartPacketHeader[3] = (byte)(0x30 | (0xF & ++PacketCounter[countID])); stuffinglength = 182 - (pes_packetlength - i); adapt[0] = (byte)(stuffinglength + 1); Buffer.write(TsStartPacketHeader); Buffer.write(adapt); Buffer.write(StuffingData, 6, stuffinglength); Buffer.write(pes_packet, i + pes_offset, pes_packetlength - i); i += (pes_packetlength - i); } else if (pes_packetlength - i == 184) { TsStartPacketHeader[3] = (byte)(0x10 | (0xF & ++PacketCounter[countID])); Buffer.write(TsStartPacketHeader); Buffer.write(pes_packet, i + pes_offset, 184); i += 184; } else if (pes_packetlength - i < 185) { TsStartPacketHeader[3] = (byte)(0x30 | (0xF & ++PacketCounter[countID])); adapt[0] = 1; Buffer.write(TsStartPacketHeader); Buffer.write(adapt); Buffer.write(pes_packet, i + pes_offset, 182); i += 182; } else { TsStartPacketHeader[3] = (byte)(0x10 | (0xF & ++PacketCounter[countID])); Buffer.write(TsStartPacketHeader); Buffer.write(pes_packet, i + pes_offset, 184); i += 184; } } else if (pes_packetlength - i < 183) { TsSubPacketHeader[3] = (byte)(0x30 | (0xF & ++PacketCounter[countID])); stuffinglength = 182 - (pes_packetlength - i); adapt[0] = (byte)(stuffinglength + 1); Buffer.write(TsSubPacketHeader); Buffer.write(adapt); Buffer.write(StuffingData, 6, stuffinglength); Buffer.write(pes_packet, i + pes_offset, pes_packetlength - i); i += (pes_packetlength - i); } else if (pes_packetlength - i == 184) { TsSubPacketHeader[3] = (byte)(0x10 | (0xF & ++PacketCounter[countID])); Buffer.write(TsSubPacketHeader); Buffer.write(pes_packet, i + pes_offset, 184); i += 184; } else if (pes_packetlength - i < 185) { TsSubPacketHeader[3] = (byte)(0x30 | (0xF & ++PacketCounter[countID])); adapt[0] = 1; Buffer.write(TsSubPacketHeader); Buffer.write(adapt); Buffer.write(pes_packet, i + pes_offset, 182); i += 182; } else { TsSubPacketHeader[3] = (byte)(0x10 | (0xF & ++PacketCounter[countID])); Buffer.write(TsSubPacketHeader); Buffer.write(pes_packet, i + pes_offset, 184); i += 184; } job_processing.countMediaFilesExportLength(+188); job_processing.countAllMediaFilesExportLength(+188); } Buffer.writeTo(OutputStream); Buffer.reset(); } catch (IOException e) { Common.setExceptionMessage(e); } } public void close(JobProcessing job_processing, boolean qinfo) { try { if (Action == CommonParsing.ACTION_TO_M2P) writePacket(job_processing, SequenceEndCode); OutputStream.flush(); OutputStream.close(); switch (Action) { case CommonParsing.ACTION_TO_M2P: RandomAccessFile mpg = new RandomAccessFile(FileName, "rw"); if (LeadingPackHeader[2] == 1) { mpg.seek(0); mpg.write(LeadingPackHeader); } mpg.seek(SystemHeaderInsertPoint); Object[] ID = IDs.toArray(); Arrays.sort(ID); // write systemheader pointer stuff for (int i = 0; i < ID.length; i++) { int IDt = (0xFF & Integer.parseInt(ID[i].toString())); if ((0xF0 & IDt) == 0xE0) { sys[0][0] = (byte)(sysID[0]++); mpg.write(sys[0]); SystemHeaderInsertPoint += 3; SystemHeaderStreams[1]++; } else if ((0xF0 & IDt) == 0x80) { mpg.write(sys[1]); SystemHeaderInsertPoint += 3; SystemHeaderStreams[0]++; } else if ((0xE0 & IDt) == 0xC0) { sys[2][0] = (byte)(sysID[1]++); mpg.write(sys[2]); SystemHeaderInsertPoint += 3; SystemHeaderStreams[0]++; } } /**** add padding ***/ mpg.writeInt(0x100 | CommonParsing.PADDING_STREAM_CODE); mpg.writeShort((short)(0x8A - SystemHeaderInsertPoint)); mpg.write(StuffingData, 0, (0x8A - SystemHeaderInsertPoint)); int syslen = ((SystemHeaderStreams[0] + SystemHeaderStreams[1]) * 3) + 6; // IDs (3byte) + std CommonParsing.setPES_LengthField(SystemHeader, 0, syslen); SystemHeader[9] = (byte)(SystemHeaderStreams[0]<<2); // set number of audios SystemHeader[10] = (byte)(0x20 | (0x1F & SystemHeaderStreams[1])); // set number of videos mpg.seek(14); mpg.write(SystemHeader); // write system header for (int i = 0; ContainsVideo && MustStartWithVideo && i < PaddingStreamPositions.size(); i++) { mpg.seek(Long.parseLong(PaddingStreamPositions.get(i).toString())); mpg.write((byte)CommonParsing.PADDING_STREAM_CODE); } mpg.close(); break; case CommonParsing.ACTION_TO_TS: if (qinfo && GeneratePMT) TS.setPmtPids(IDs); FileName = TS.updateAdditionalHeader(FileName, time, TsHeaderMode, job_processing); break; case CommonParsing.ACTION_TO_VDR: if (CreateVdrIndex) { if (new File(FileName).length() < 150) OutputStream.deleteIdd(); else FileName = OutputStream.renameVdrTo(new File(FileName).getParent() + System.getProperty("file.separator"), FileName); } } if (new File(FileName).length() < 150) new File(FileName).delete(); else { Common.setMessage(Resource.getString("msg.newfile") + " " + FileName); job_processing.addSummaryInfo(Resource.getString("StreamConverter.Summary") + "\t'" + FileName + "'"); } } catch (IOException e) { Common.setExceptionMessage(e); } } //// /** * scan secondary streams for timestamps */ private void scanSecondaryStreams(JobCollection collection) { if (!MuxPES) return; try { XInputFile xif; for (int i = collection.getPrimaryInputFileSegments(), j = collection.getInputFilesCount(); i < j; i++) { xif = (XInputFile) collection.getInputFile(i); //use PS1 type only ATM (ttx+sub) if (xif.getStreamInfo().getStreamType() != CommonParsing.PES_PS1_TYPE) continue; int packet_count = 0; long[] time = { 0, 0 }; long pts_value = 0; ArrayList indexList = new ArrayList(); byte[] pes_packet = new byte[0x10010]; int pes_payloadlength; int pes_packetlength; int pes_extensionlength; int pes_headerlength = 9; int pes_packetoffset = 6; int returncode = 0; int pesID = 0; long base = 0; long count = 0; long size = xif.length(); PushbackInputStream in = new PushbackInputStream(xif.getInputStream(base), pes_packet.length); Common.setMessage("-> scanning for remux: " + xif.getName()); Common.updateProgressBar(" scanning for remux: " + xif.getName(), count, size); loop: while (count < size) { Common.updateProgressBar(count, size); while (Common.waitingMainProcess()) {} in.read(pes_packet, 0, pes_packetoffset); pesID = CommonParsing.getPES_IdField(pes_packet, 0); if ((returncode = CommonParsing.validateStartcode(pes_packet, 0)) < 0) { returncode = returncode < 0 ? -returncode : 4; in.read(pes_packet, pes_packetoffset, pes_packet.length - pes_packetoffset); int k = returncode; for (; k < pes_packet.length - 3; ) { returncode = CommonParsing.validateStartcode(pes_packet, k); if (returncode < 0) { k += -returncode; continue; } else { in.unread(pes_packet, k, pes_packet.length - k); count += k; continue loop; } } in.unread(pes_packet, k, pes_packet.length - k); count += k; continue loop; } if (pesID != CommonParsing.PRIVATE_STREAM_1_CODE) continue loop; pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, 0); in.read(pes_packet, pes_packetoffset, pes_payloadlength); pes_packetlength = pes_packetoffset + pes_payloadlength; pes_extensionlength = CommonParsing.getPES_ExtensionLengthField(pes_packet, 0); if (CommonParsing.clearBit33ofPTS(pes_packet, 0)) pts_value = CommonParsing.getPTSfromBytes(pes_packet, pes_headerlength); //Common.setMessage("A cnt " + count + " / ID " + pesID + " / payl " + pes_payloadlength + " / pckl " + pes_packetlength + " / extl " + pes_extensionlength + " / pts " + pts_value); packet_count++; indexList.add(new long[] { pts_value, count, pes_packetlength }); count += pes_packetlength; } in.close(); // Common.setMessage("B " + packet_count + " / " + indexList.size()); remuxList.add(new Object[] { indexList , xif.toString(), new int[1] } ); } } catch (Exception e) { Common.setExceptionMessage(e); } } /** * fill in secondary streams in pes */ private void remuxPES(JobProcessing job_processing, long video_pts) { if (!MuxPES) return; Object[] obj; String fn; ArrayList indexList; byte[] pes_packet; long[] pes_values; int[] pes_index; for (int i = 0, j = remuxList.size(); i < j; i++) { obj = (Object[]) remuxList.get(i); indexList = (ArrayList) obj[0]; fn = obj[1].toString(); pes_index = (int[]) obj[2]; try { RandomAccessFile pes = new RandomAccessFile(fn, "r"); for (int k = indexList.size(); pes_index[0] < k; pes_index[0]++) { pes_values = (long[]) indexList.get(pes_index[0]); if (video_pts < pes_values[0]) break; if (video_pts > pes_values[0] + 43200) continue; pes_packet = new byte[(int) pes_values[2]]; pes.seek(pes_values[1]); pes.read(pes_packet); writePacket(job_processing, pes_packet); } pes.close(); } catch (Exception e) { Common.setExceptionMessage(e); } } } }