/* FlashAACInputStream - provides an InputStream for use with FlashAACPlayer within aacdecoder-android. Allows reading of FLV-wrapped raw AAC data into playable buffers of AAC frames with ADTS headers 20-09-2011 Author: Trevor Lovett (trevlovett (at) gmail.com) Based in part upon FLV_Extract (http://moitah.net/) */ package com.spoledge.aacdecoder; import java.io.*; import java.net.URL; import java.net.URLConnection; import com.rubika.aotalk.util.Logging; public class FlashAACInputStream extends InputStream { private static final String APP_TAG = "--> The Leet :: FlashAACInputStream"; private DataInputStream dis = null; private int countInBackBuffer = 0; private int backBufferLen = 65536; private byte[] backBuffer = new byte[backBufferLen]; private int readBufferLen = 65536; private byte[] readBuffer = new byte[readBufferLen]; private int _aacProfile; private int _sampleRateIndex; private int _channelConfig; public FlashAACInputStream(InputStream istream) throws IOException { dis = new DataInputStream(istream); // Check that stream is a Flash Video stream if ((char)dis.readByte() != 'F' || (char)dis.readByte() != 'L' || (char)dis.readByte() != 'V') throw new IOException("The file is not a FLV file."); // Check if audio stream exists in the video stream //byte version = dis.readByte(); byte exists = dis.readByte(); if ((exists != 5) && (exists != 4)) throw new IOException("No Audio Stream"); dis.readInt(); // data offset of header. ignoring } // don't use-- efficiency is not good @Override public int read() throws IOException { byte[] b = new byte[1]; read(b, 0, 1); return ((int)b[0]) & 0xFF; } // Reads a frame at a time. If the entire frame cannot be accomodated by b, // function saves the remainder in backBuffer for use in next call. // returns: number of bytes read into b @Override public int read(byte[] b, int off, int len) throws IOException { if (off < 0 || len < 0 || b.length - off < len) throw new IndexOutOfBoundsException(); if (len > readBufferLen) throw new IndexOutOfBoundsException("len exceeds readBufferLen"); Logging.log(APP_TAG, "read: countInBackBuffer = " + countInBackBuffer); if (countInBackBuffer > 0) { if (countInBackBuffer >= len) { System.arraycopy(backBuffer, 0, b, off, len); // move the remainder in the backBuffer to the top if (countInBackBuffer > len) System.arraycopy(backBuffer, len, backBuffer, 0, countInBackBuffer - len); countInBackBuffer -= len; return len; } else { System.arraycopy(backBuffer, 0, b, off, countInBackBuffer); } } int remaining = len - countInBackBuffer; int readBytes = 0; int b_off = off + countInBackBuffer; countInBackBuffer = 0; while (true) { readBytes = readFrame(readBuffer); remaining -= readBytes; if (remaining <= 0) { System.arraycopy(readBuffer, 0, b, b_off, readBytes + remaining); if (remaining < 0) { System.arraycopy(readBuffer, readBytes + remaining, backBuffer, 0, Math.abs(remaining)); countInBackBuffer = Math.abs(remaining); } return len; } else if (remaining > 0) { System.arraycopy(readBuffer, 0, b, b_off, readBytes); b_off += readBytes; } } } // reads FLV Tag data private int readFrame(byte[] buf) throws IOException { //int previousTagSize = dis.readInt(); // PreviousTagSize0 skipping byte tagType = dis.readByte(); while (tagType != 8) { long skip = readNext3Bytes() + 11; dis.skipBytes((int)skip); tagType = dis.readByte(); } long dataSize = readNext3Bytes() - 1; int timestamps = dis.readInt(); long streamID = readNext3Bytes(); byte audioHeader = dis.readByte(); Logging.log(APP_TAG, "dataSize = " + dataSize + ", timestamps = " + timestamps + ", streamId = " + streamID + ", audioHeader = " + audioHeader); return fillBuffer(buf, (int)dataSize); } // returns true if header present, false if not private boolean readAACHeader() throws IOException { byte head = dis.readByte(); if (head != 0) return false; int bits = ((dis.readByte() & 0xff)*256 + (dis.readByte() & 0xff)) << 16; _aacProfile = readBits(bits, 5) - 1; bits <<= 5; _sampleRateIndex = readBits(bits, 4); bits <<= 4; _channelConfig = readBits(bits, 4); Logging.log(APP_TAG, "aacProf = " + _aacProfile + "\n_sampleRateIndex = " + _sampleRateIndex + "\n_channelConfig = " + _channelConfig); if ((_aacProfile < 0) || (_aacProfile > 3)) throw new IOException("Unsupported AAC profile."); if (_sampleRateIndex > 12) throw new IOException("Invalid AAC sample rate index."); if (_channelConfig > 6) throw new IOException("Invalid AAC channel configuration."); return true; } // puts a complete AAC frame with ADTS header in buf, ready for playing, returns size of frame in bytes private int fillBuffer(byte[] buf, int dataSize) throws IOException { if (readAACHeader()) return 0; dataSize -= 1; // see http://wiki.multimedia.cx/index.php?title=ADTS for format spec long bits = 0; bits = writeBits(bits, 12, 0xFFF); bits = writeBits(bits, 3, 0); bits = writeBits(bits, 1, 1); buf[0] = (byte)(bits >> 8); buf[1] = (byte)(bits); bits = 0; bits = writeBits(bits, 2, _aacProfile); bits = writeBits(bits, 4, _sampleRateIndex); bits = writeBits(bits, 1, 0); bits = writeBits(bits, 3, _channelConfig); bits = writeBits(bits, 4, 0); bits = writeBits(bits, 2, (dataSize + 7) & 0x1800); buf[2] = (byte)(bits >> 8); buf[3] = (byte)(bits); bits = 0; bits = writeBits(bits, 11, (dataSize + 7) & 0x7FF); bits = writeBits(bits, 11, 0x7FF); bits = writeBits(bits, 2, 0); buf[4] = (byte)(bits >> 16); buf[5] = (byte)(bits >> 8); buf[6] = (byte)(bits); dis.readFully(buf, 7, dataSize); buf[dataSize+7] = 0; return dataSize + 8; } private int readBits(int x, int length) { int r = (int)(x >> (32 - length)); return r; } public long writeBits(long x, int length, int value) { long mask = 0xffffffffL >> (32 - length); x = (x << length) | (value & mask); return x; } private long readNext3Bytes() throws IOException { return dis.readUnsignedByte() * 256 * 256 + dis.readUnsignedByte() * 256 + dis.readUnsignedByte(); } protected static void dumpHeaders( URLConnection cn ) { for (java.util.Map.Entry<String, java.util.List<String>> me : cn.getHeaderFields().entrySet()) { for (String s : me.getValue()) { System.out.println("header: key=" + me.getKey() + ", val=" + s); } } } // simple unit test to see if it can read from a file or network stream public static void main(String args[]) { try { /* File f = new File(args[0]); FileInputStream fis = new FileInputStream(f); FlashAACInputStream aacStream = new FlashAACInputStream(fis); */ // static file on server String url = "http://184.82.135.71/download.flv"; // true stream URL //String url = "http://184.82.135.71:450"; URLConnection cn = new URL( url ).openConnection(); cn.connect(); dumpHeaders( cn ); InputStream istream = cn.getInputStream(); FlashAACInputStream flvStream = new FlashAACInputStream(istream); File outFile = new File("output.aac"); FileOutputStream fos = new FileOutputStream(outFile); //DataOutputStream dos = new DataOutputStream(fos); byte [] myChunk = new byte[4096]; int bytesRead; for (int i = 0; i < 100; i++) { bytesRead = flvStream.readFrame(myChunk); System.out.println("bytesRead = " + bytesRead); fos.write(myChunk, 0, bytesRead); } flvStream.close(); fos.close(); } catch (Exception e) { System.out.println(e); e.printStackTrace(); } } }