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 * [type] is S for signed integer, U for unsigned integer, F for IEEE float<br> 060 * [interleaving] is D for default, P is for planar.<br> 061 * [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}