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