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