001package net.bramp.ffmpeg.nut;
002
003import static com.google.common.base.Preconditions.checkArgument;
004import static com.google.common.base.Preconditions.checkNotNull;
005import static net.bramp.ffmpeg.nut.StreamHeaderPacket.fourccToString;
006
007import java.awt.image.BufferedImage;
008import java.io.ByteArrayInputStream;
009import java.io.InputStream;
010import java.nio.ByteBuffer;
011import java.nio.ByteOrder;
012import java.nio.IntBuffer;
013import java.util.Arrays;
014import javax.sound.sampled.AudioFormat;
015import javax.sound.sampled.AudioInputStream;
016
017/** Handles decoding of raw audio and video data in the NUT multimedia container format. */
018public class RawHandler {
019
020  private static int[] bytesToInts(byte[] bytes) {
021    IntBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).asIntBuffer();
022
023    int[] data = new int[buf.capacity()];
024    buf.get(data);
025    return data;
026  }
027
028  /** Converts a video frame to a BufferedImage. */
029  public static BufferedImage toBufferedImage(Frame frame) {
030    checkNotNull(frame);
031
032    final StreamHeaderPacket header = frame.stream.header;
033
034    checkArgument(header.type == StreamHeaderPacket.VIDEO);
035
036    // DataBufferByte buffer = new DataBufferByte(frame.data, frame.data.length);
037    // SampleModel sample = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
038    // streamHeader.width, streamHeader.height, 1);
039    // Raster raster = new Raster(sample, buffer, new Point(0,0));
040
041    int type = BufferedImage.TYPE_INT_ARGB; // TODO Use the type defined in the stream header.
042    BufferedImage img = new BufferedImage(header.width, header.height, type);
043
044    // TODO Avoid this conversion.
045    int[] data = bytesToInts(frame.data);
046    int stride = header.width; // TODO Check this is true
047    img.setRGB(0, 0, header.width, header.height, data, 0, stride);
048
049    return img;
050  }
051
052  /**
053   * Parses a FourCC into a AudioEncoding based on the following rules:<br>
054   * "ALAW" = A-LAW<br>
055   * "ULAW" = MU-LAW<br>
056   * P[type][interleaving][bits] = little-endian PCM<br>
057   * [bits][interleaving][type]P = big-endian PCM<br>
058   * Where:<br>
059   * &nbsp;&nbsp;[type] is S for signed integer, U for unsigned integer, F for IEEE float<br>
060   * &nbsp;&nbsp;[interleaving] is D for default, P is for planar.<br>
061   * &nbsp;&nbsp;[bits] is 8/16/24/32<br>
062   *
063   * @param header The stream's header.
064   * @return The AudioFormat matching this header.
065   */
066  public static AudioFormat streamToAudioFormat(final StreamHeaderPacket header) {
067    checkNotNull(header);
068    checkArgument(header.type == StreamHeaderPacket.AUDIO);
069
070    // Vars that go into the AudioFormat
071    AudioFormat.Encoding encoding;
072    float sampleRate = header.sampleRate.floatValue();
073    int bits = 8;
074    boolean bigEndian = false;
075
076    final byte[] fourcc = header.fourcc;
077
078    if (Arrays.equals(fourcc, new byte[] {'A', 'L', 'A', 'W'})) {
079      encoding = AudioFormat.Encoding.ALAW;
080
081    } else if (Arrays.equals(fourcc, new byte[] {'U', 'L', 'A', 'W'})) {
082      encoding = AudioFormat.Encoding.ULAW;
083
084    } else if (fourcc.length == 4) {
085      byte type;
086      byte interleaving;
087
088      if (fourcc[0] == 'P') {
089        bigEndian = false;
090        type = fourcc[1];
091        interleaving = fourcc[2];
092        bits = fourcc[3];
093      } else if (fourcc[3] == 'P') {
094        bigEndian = true;
095        type = fourcc[2];
096        interleaving = fourcc[1];
097        bits = fourcc[0];
098      } else {
099        throw new IllegalArgumentException(
100            "unknown fourcc value: '" + fourccToString(fourcc) + "'");
101      }
102
103      if (interleaving != 'D') {
104        throw new IllegalArgumentException(
105            "unsupported interleaving '"
106                + interleaving
107                + "' in fourcc value '"
108                + fourccToString(fourcc)
109                + "'");
110      }
111
112      switch (type) {
113        case 'S':
114          encoding = AudioFormat.Encoding.PCM_SIGNED;
115          break;
116        case 'U':
117          encoding = AudioFormat.Encoding.PCM_UNSIGNED;
118          break;
119        case 'F':
120          encoding = AudioFormat.Encoding.PCM_FLOAT;
121          break;
122        default:
123          throw new IllegalArgumentException(
124              "unknown fourcc '" + fourccToString(fourcc) + "' type: " + type);
125      }
126    } else {
127      throw new IllegalArgumentException("unknown fourcc value: '" + fourccToString(fourcc) + "'");
128    }
129
130    int frameSize = (bits * header.channels) / 8;
131    float frameRate = sampleRate; // This may not be true for the compressed formats
132
133    return new AudioFormat(
134        encoding, sampleRate, bits, header.channels, frameSize, frameRate, bigEndian);
135  }
136
137  /** Converts an audio frame to an AudioInputStream. */
138  public static AudioInputStream toAudioInputStream(Frame frame) {
139    checkNotNull(frame);
140    final StreamHeaderPacket header = checkNotNull(frame.stream.header);
141    checkArgument(header.type == StreamHeaderPacket.AUDIO);
142
143    AudioFormat format = streamToAudioFormat(header);
144    InputStream stream = new ByteArrayInputStream(frame.data);
145
146    return new AudioInputStream(stream, format, frame.data.length / format.getFrameSize());
147  }
148}