001package net.bramp.ffmpeg.nut;
002
003import com.google.common.base.MoreObjects;
004import java.io.IOException;
005import java.nio.charset.StandardCharsets;
006import org.apache.commons.lang3.math.Fraction;
007
008/** Represents a stream header packet in the NUT multimedia container format. */
009public class StreamHeaderPacket extends Packet {
010
011  public static final int VIDEO = 0;
012  public static final int AUDIO = 1;
013  public static final int SUBTITLE = 2;
014  public static final int USER_DATA = 3;
015
016  int id;
017  long type; // One of VIDEO/AUDIO/SUBTITLE/USER_DATA // TODO Convert to enum.
018  byte[] fourcc;
019  int timeBaseId;
020  int msbPtsShift;
021  int maxPtsDistance;
022  long decodeDelay;
023  long flags;
024  byte[] codecSpecificData;
025
026  // If video
027  int width;
028  int height;
029  int sampleWidth;
030  int sampleHeight;
031  long colorspaceType;
032
033  // If audio
034  Fraction sampleRate = Fraction.ZERO;
035  int channels;
036
037  /** Converts a fourcc byte array to its string representation. */
038  protected static String fourccToString(byte[] fourcc) {
039    return new String(fourcc, StandardCharsets.ISO_8859_1);
040  }
041
042  @Override
043  protected void readBody(NutDataInputStream in) throws IOException {
044
045    id = in.readVarInt();
046    type = in.readVarLong();
047    fourcc = in.readVarArray();
048
049    if (fourcc.length != 2 && fourcc.length != 4) {
050      // TODO In future fourcc could be a different size, but for sanity checking lets leave this
051      // check in.
052      throw new IOException("Unexpected fourcc length: " + fourcc.length);
053    }
054
055    timeBaseId = in.readVarInt();
056    msbPtsShift = in.readVarInt();
057    if (msbPtsShift >= 16) {
058      throw new IOException("invalid msbPtsShift " + msbPtsShift + " want < 16");
059    }
060    maxPtsDistance = in.readVarInt();
061    decodeDelay = in.readVarLong();
062    flags = in.readVarLong();
063    codecSpecificData = in.readVarArray();
064
065    if (type == VIDEO) {
066      width = in.readVarInt();
067      height = in.readVarInt();
068
069      if (width == 0 || height == 0) {
070        throw new IOException("invalid video dimensions " + width + "x" + height);
071      }
072
073      sampleWidth = in.readVarInt();
074      sampleHeight = in.readVarInt();
075
076      // Both MUST be 0 if unknown otherwise both MUST be nonzero.
077      if ((sampleWidth == 0 || sampleHeight == 0) && sampleWidth != sampleHeight) {
078        throw new IOException(
079            "invalid video sample dimensions " + sampleWidth + "x" + sampleHeight);
080      }
081
082      colorspaceType = in.readVarLong();
083
084    } else if (type == AUDIO) {
085      int samplerateNum = in.readVarInt();
086      int samplerateDenom = in.readVarInt();
087      sampleRate = Fraction.getFraction(samplerateNum, samplerateDenom);
088      channels = in.readVarInt();
089    }
090  }
091
092  @Override
093  public String toString() {
094    return MoreObjects.toStringHelper(this)
095        .add("header", header)
096        .add("id", id)
097        .add("type", type)
098        .add("fourcc", fourccToString(fourcc))
099        .add("timeBaseId", timeBaseId)
100        .add("msbPtsShift", msbPtsShift)
101        .add("maxPtsDistance", maxPtsDistance)
102        .add("decodeDelay", decodeDelay)
103        .add("flags", flags)
104        .add("codecSpecificData", codecSpecificData)
105        .add("width", width)
106        .add("height", height)
107        .add("sampleWidth", sampleWidth)
108        .add("sampleHeight", sampleHeight)
109        .add("colorspaceType", colorspaceType)
110        .add("sampleRate", sampleRate)
111        .add("channels", channels)
112        .add("footer", footer)
113        .toString();
114  }
115}