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}