001package net.bramp.ffmpeg.nut; 002 003import com.google.common.base.MoreObjects; 004import java.io.IOException; 005import java.util.ArrayList; 006import java.util.List; 007import org.apache.commons.lang3.math.Fraction; 008 009/** Represents the main header packet in the NUT multimedia container format. */ 010public class MainHeaderPacket extends Packet { 011 012 public static final int BROADCAST_MODE = 0; 013 014 long version; 015 long minorVersion; 016 int streamCount; 017 long maxDistance; 018 Fraction[] timeBase; 019 long flags; 020 021 final List<FrameCode> frameCodes = new ArrayList<>(); 022 final List<byte[]> elision = new ArrayList<>(); 023 024 /** Constructs a new empty main header packet. */ 025 public MainHeaderPacket() {} 026 027 @Override 028 protected void readBody(NutDataInputStream in) throws IOException { 029 frameCodes.clear(); 030 031 version = in.readVarLong(); 032 if (version > 3) { 033 minorVersion = in.readVarLong(); 034 } 035 036 streamCount = in.readVarInt(); 037 if (streamCount >= 250) { 038 throw new IOException("Illegal stream count " + streamCount + " must be < 250"); 039 } 040 041 maxDistance = in.readVarLong(); 042 if (maxDistance > 65536) { 043 maxDistance = 65536; 044 } 045 046 int time_base_count = in.readVarInt(); 047 timeBase = new Fraction[time_base_count]; 048 for (int i = 0; i < time_base_count; i++) { 049 int time_base_num = (int) in.readVarLong(); 050 int time_base_denom = (int) in.readVarLong(); 051 timeBase[i] = Fraction.getFraction(time_base_num, time_base_denom); 052 } 053 054 long pts = 0; 055 int mul = 1; 056 int stream_id = 0; 057 int header_idx = 0; 058 059 long match = 1L - (1L << 62); 060 int size; 061 int reserved; 062 long count; 063 064 for (int i = 0; i < 256; ) { 065 final long flags = in.readVarLong(); 066 long fields = in.readVarLong(); 067 if (fields > 0) { 068 pts = in.readSignedVarInt(); 069 } 070 if (fields > 1) { 071 mul = in.readVarInt(); 072 if (mul >= 16384) { 073 throw new IOException("Illegal mul value " + mul + " must be < 16384"); 074 } 075 } 076 if (fields > 2) { 077 stream_id = in.readVarInt(); 078 if (stream_id >= streamCount) { 079 throw new IOException( 080 "Illegal stream id value " + stream_id + " must be < " + streamCount); 081 } 082 } 083 if (fields > 3) { 084 size = in.readVarInt(); 085 } else { 086 size = 0; 087 } 088 if (fields > 4) { 089 reserved = in.readVarInt(); 090 if (reserved >= 256) { 091 throw new IOException("Illegal reserved frame count " + reserved + " must be < 256"); 092 } 093 } else { 094 reserved = 0; 095 } 096 if (fields > 5) { 097 count = in.readVarLong(); 098 } else { 099 count = (long) mul - size; 100 } 101 if (fields > 6) { 102 match = in.readSignedVarInt(); 103 } 104 if (fields > 7) { 105 header_idx = in.readVarInt(); 106 } 107 for (int j = 8; j < fields; j++) { 108 in.readVarLong(); // Throw away 109 } 110 111 if (stream_id >= streamCount) { 112 throw new IOException( 113 String.format("Invalid stream value %d, must be < %d", stream_id, streamCount)); 114 } 115 116 if (count <= 0 || (count > 256 - i - (i <= 'N' ? 1 : 0))) { 117 throw new IOException( 118 String.format( 119 "Invalid count value %d, must be > 0 && < %d", 120 count, 256 - i - (i <= 'N' ? 1 : 0))); 121 } 122 123 for (int j = 0; j < count && i < 256; j++, i++) { 124 FrameCode fc = new FrameCode(); 125 frameCodes.add(fc); 126 127 // Skip 'N' because that is an illegal frame code 128 if (i == 'N') { 129 fc.flags = Frame.FLAG_INVALID; 130 j--; 131 continue; 132 } 133 134 fc.flags = flags; 135 fc.streamId = stream_id; 136 fc.dataSizeMul = mul; 137 fc.dataSizeLsb = size + j; 138 fc.ptsDelta = pts; 139 fc.reservedCount = reserved; 140 fc.matchTimeDelta = match; 141 fc.headerIdx = header_idx; 142 143 if (fc.dataSizeLsb >= 16384) { 144 throw new IOException("Illegal dataSizeLsb value " + fc.dataSizeLsb + " must be < 16384"); 145 } 146 } 147 } 148 149 int remain = 1024; 150 if (in.offset() < (header.end - 4)) { 151 int header_count = in.readVarInt(); 152 if (header_count >= 128) { 153 throw new IOException("Invalid header_count value " + header_count + " must be < 128"); 154 } 155 156 elision.clear(); 157 elision.add(new byte[0]); // First elision is always empty 158 for (int i = 1; i < header_count; i++) { 159 byte[] e = in.readVarArray(); 160 if (e.length == 0 || e.length >= 256) { 161 throw new IOException("Invalid elision length " + e.length + " must be > 0 and < 256"); 162 } 163 if (e.length > remain) { 164 throw new IOException( 165 "Invalid elision length value " + e.length + " must be <= " + remain); 166 } 167 remain -= e.length; 168 elision.add(e); 169 } 170 } 171 172 if (version > 3 && (in.offset() < (header.end - 4))) { 173 flags = in.readVarLong(); 174 } 175 } 176 177 @Override 178 public String toString() { 179 return MoreObjects.toStringHelper(this) 180 .add("header", header) 181 .add("version", version) 182 .add("minorVersion", minorVersion) 183 .add("streamCount", streamCount) 184 .add("maxDistance", maxDistance) 185 .add("timeBase", timeBase) 186 .add("flags", flags) 187 .add("frameCodes", frameCodes.size()) 188 .add("elision", elision) 189 .add("footer", footer) 190 .toString(); 191 } 192}