package org.jcodec.containers.avi;
import static java.lang.System.currentTimeMillis;
import org.jcodec.api.FormatException;
import org.jcodec.common.io.DataReader;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.logging.Logger;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.IllegalArgumentException;
import java.lang.IllegalStateException;
import java.lang.StringBuilder;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* RIFF 'AVI ' Audio/Video Interleaved file
* LIST 'hdrl' Header LIST
* 'avih' Main AVI header
* LIST 'strl' Video stream LIST
* 'strh' Video stream header
* 'strf' Video format
* <Optional Open DML Super Index + List of standard indexes >
* LIST 'strl' Audio stream LIST
* 'strh' Audio stream header
* 'strf' Audio format
* <Optional Open DML Super Index + List of standard indexes >
* LIST 'movi' Main data LIST
* '01wb' Audio data
* '00dc' Video frame
* ...
* 'idx1' Index <Optional old Avi Index>
*
* @author Owen McGovern
*/
public class AVIReader {
public final static int FOURCC_RIFF = 0x46464952; // 'RIFF'
public final static int FOURCC_AVI = 0x20495641; // 'AVI '
public final static int FOURCC_AVIX = 0x58495641; // 'AVIX' // extended AVI
public final static int FOURCC_AVIH = 0x68697661; // 'avih'
public final static int FOURCC_LIST = 0x5453494c; // 'LIST'
public final static int FOURCC_HDRL = 0x6c726468; // 'hdrl'
public final static int FOURCC_JUNK = 0x4b4e554a; // 'JUNK'
public final static int FOURCC_INDX = 0x78646e69; // 'indx' // main index -
// old style Avi Index
public final static int FOURCC_IDXL = 0x31786469; // 'idx1' // index of
// single 'movi' block
public final static int FOURCC_STRL = 0x6c727473; // 'strl'
public final static int FOURCC_STRH = 0x68727473; // 'strh'
public final static int FOURCC_STRF = 0x66727473; // 'strf'
public final static int FOURCC_MOVI = 0x69766f6d; // 'movi'
public final static int FOURCC_REC = 0x20636572; // 'rec '
public final static int FOURCC_SEGM = 0x6D676573; // 'segm' // some padded
// that requires adding 1
// byte to the size given
public final static int FOURCC_ODML = 0x6C6D646F; // 'odml'
public final static int FOURCC_VIDS = 0x73646976;
public final static int FOURCC_AUDS = 0x73647561;
public final static int FOURCC_MIDS = 0x7364696d;
public final static int FOURCC_TXTS = 0x73747874;
public final static int FOURCC_strd = 0x64727473;
public final static int FOURCC_strn = 0x6e727473;
public final static int AVIF_HASINDEX = 0x00000010;
public final static int AVIF_MUSTUSEINDEX = 0x00000020;
public final static int AVIF_ISINTERLEAVED = 0x00000100;
public final static int AVIF_TRUSTCKTYPE = 0x00000800;
public final static int AVIF_WASCAPTUREFILE = 0x00010000;
public final static int AVIF_COPYRIGHTED = 0x00020000;
public final static int AVIIF_LIST = 0x00000001;
public final static int AVIIF_KEYFRAME = 0x00000010;
public final static int AVIIF_FIRSTPART = 0x00000020;
public final static int AVIIF_LASTPART = 0x00000040;
public final static int AVIIF_NOTIME = 0x00000100;
public final static int AUDIO_FORMAT_PCM = 0x0001;
public final static int AUDIO_FORMAT_MP3 = 0x0055;
public final static int AUDIO_FORMAT_AC3 = 0x2000;
public final static int AUDIO_FORMAT_DTS = 0x2001;
public final static int AUDIO_FORMAT_VORBIS = 0x566F;
public final static int AUDIO_FORMAT_EXTENSIBLE = 0xFFFE;
// Open DML Index type codes
public final int AVI_INDEX_OF_INDEXES = 0x00;
public final int AVI_INDEX_OF_CHUNKS = 0x01;
public final int AVI_INDEX_OF_TIMED_CHUNKS = 0x02;
public final int AVI_INDEX_OF_SUB_2FIELD = 0x03;
public final int AVI_INDEX_IS_DATA = 0x80;
public final static int STDINDEXSIZE = 0x4000;
private final static long SIZE_MASK = 0xffffffffL; // For conversion of
// sizes from unsigned
// int to long
private DataReader raf = null;
private long fileLeft = 0;
private AVITag_AVIH aviHeader;
private AVITag_STRH[] streamHeaders;
private AVIChunk[] streamFormats;
private List<AVITag_AviIndex> aviIndexes;
private AVITag_AviDmlSuperIndex[] openDmlSuperIndex;
private PrintStream ps = null;
private boolean skipFrames = true; // DEBUG MODE ONLY, don't read the
// video/audio byte data, just skip over
// it
public AVIReader(SeekableByteChannel src) {
this.raf = DataReader.createDataReader(src, ByteOrder.LITTLE_ENDIAN);
this.aviIndexes = new ArrayList<AVITag_AviIndex>();
}
public static int fromFourCC(final String str) {
byte[] strBytes = str.getBytes();
if (strBytes.length != 4)
throw new IllegalArgumentException("Expected 4 bytes not " + strBytes.length);
int fourCCInt = strBytes[3];
fourCCInt = (fourCCInt <<= 8) | strBytes[2];
fourCCInt = (fourCCInt <<= 8) | strBytes[1];
fourCCInt = (fourCCInt <<= 8) | strBytes[0];
return (fourCCInt);
}
/**
*
* @param fourcc
* @return
*/
public static String toFourCC(int fourcc) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 4; i++) {
int c = fourcc & 0xff;
sb.append(Character.toString((char) c));
fourcc >>= 8;
}
return sb.toString();
}
public long getFileLeft() throws IOException {
return (this.fileLeft);
}
public List<AVITag_AviIndex> getAviIndexes() {
return (this.aviIndexes);
}
public void parse() throws IOException {
try {
long t1 = currentTimeMillis();
long fileSize = raf.size();
fileLeft = fileSize;
int numStreams = 0;
int streamIndex = -1;
int videoFrameNo = 1;
// Read the FOURCC tag code
int dwFourCC = raf.readInt();
if (dwFourCC != FOURCC_RIFF)
throw new FormatException("No RIFF header found");
AVIChunk aviItem = new AVIList();
aviItem.read(dwFourCC, raf);
Logger.debug(aviItem.toString());
int previousStreamType = 0;
do {
dwFourCC = raf.readInt();
String dwFourCCStr = toFourCC(dwFourCC);
switch (dwFourCC) {
case FOURCC_RIFF: {
aviItem = new AVIList();
aviItem.read(dwFourCC, raf);
/*
* if (((AVIList)aviItem).getListType() == FOURCC_AVIX) {
* this.logger.debug("- Extended RIFF AVIX Found"); }
*/
break;
}
case FOURCC_LIST: {
aviItem = new AVIList();
aviItem.read(dwFourCC, raf);
if (((AVIList) aviItem).getListType() == FOURCC_MOVI) {
// this.logger.debug("- Skipping LIST Movi...");
aviItem.skip(raf);
}
break;
}
case FOURCC_STRL: {
aviItem = new AVIList();
aviItem.read(dwFourCC, raf);
break;
}
case FOURCC_AVIH: {
aviItem = aviHeader = new AVITag_AVIH();
aviItem.read(dwFourCC, raf);
numStreams = aviHeader.getStreams();
streamHeaders = new AVITag_STRH[numStreams];
streamFormats = new AVIChunk[numStreams];
openDmlSuperIndex = new AVITag_AviDmlSuperIndex[numStreams];
break;
}
case FOURCC_STRH: {
if (streamIndex >= numStreams) {
throw new IllegalStateException(
"Read more stream headers than expected, expected [" + numStreams + "]");
}
streamIndex++;
aviItem = streamHeaders[streamIndex] = new AVITag_STRH();
aviItem.read(dwFourCC, raf);
previousStreamType = ((AVITag_STRH) aviItem).getType();
break;
}
case FOURCC_STRF: {
// The previous FOURCC_STRH should precede this FOURCC_STRF
switch (previousStreamType) {
case FOURCC_VIDS: {
aviItem = streamFormats[streamIndex] = new AVITag_BitmapInfoHeader();
aviItem.read(dwFourCC, raf);
break;
}
case FOURCC_AUDS: {
aviItem = streamFormats[streamIndex] = new AVITag_WaveFormatEx();
aviItem.read(dwFourCC, raf);
break;
}
default: {
throw new IOException("Expected vids or auds got [" + toFourCC(previousStreamType) + "]");
}
}
break;
}
case FOURCC_SEGM: {
aviItem = new AVI_SEGM();
aviItem.read(dwFourCC, raf);
aviItem.skip(raf);
break;
}
case FOURCC_IDXL: {
// Old style AVI Index
aviItem = new AVITag_AviIndex();
aviItem.read(dwFourCC, raf);
aviIndexes.add((AVITag_AviIndex) aviItem);
break;
}
case FOURCC_INDX: {
// Open DML style Index ( super index )
openDmlSuperIndex[streamIndex] = new AVITag_AviDmlSuperIndex();
openDmlSuperIndex[streamIndex].read(dwFourCC, raf);
aviItem = openDmlSuperIndex[streamIndex];
break;
}
default: {
// Chunks where the FOURCC str is not constant, eg. includes
// a stream no like "ix00", "ix01" etc
if (dwFourCCStr.endsWith("db")) {
// uncompressed video chunk
aviItem = new AVITag_VideoChunk(false, raf);
aviItem.read(dwFourCC, raf);
if (skipFrames) {
aviItem.skip(raf);
} else {
byte[] videoFrameData = ((AVITag_VideoChunk) aviItem).getVideoPacket();
ByteBuffer bb = ByteBuffer.wrap(videoFrameData);
// TODO : Decode uncompressed video data
}
} else if (dwFourCCStr.endsWith("dc")) {
// compressed video chunk
aviItem = new AVITag_VideoChunk(true, raf);
aviItem.read(dwFourCC, raf);
((AVITag_VideoChunk) aviItem).setFrameNo(videoFrameNo);
videoFrameNo++;
String fourccStr = toFourCC(dwFourCC);
int streamNo = Integer.parseInt(fourccStr.substring(0, 2));
if (skipFrames) {
aviItem.skip(raf);
} else {
byte[] videoFrameData = ((AVITag_VideoChunk) aviItem).getVideoPacket();
ByteBuffer bb = ByteBuffer.wrap(videoFrameData);
// TODO: Decode compressed video data
// Look up the stream header from streamNo to find
// the codec
}
} else if (dwFourCCStr.endsWith("wb")) {
// audio chunk
aviItem = new AVITag_AudioChunk();
aviItem.read(dwFourCC, raf);
aviItem.skip(raf);
} else if (dwFourCCStr.endsWith("tx")) {
// subtitle chunk
aviItem = new AVIChunk();
aviItem.read(dwFourCC, raf);
aviItem.skip(raf);
} else if (dwFourCCStr.startsWith("ix")) {
// New style OpenDML AVI Indexes
aviItem = new AVITag_AviDmlStandardIndex();
aviItem.read(dwFourCC, raf);
}
/*
* else if ( (dwFourCC==FOURCC_IDXL) ||
* (dwFourCCStr.startsWith("ix")) ) { if (!openDmlIndexes) {
* // Old style AVI Index aviItem = new AVITag_AviIndex();
* aviItem.read(dwFourCC, raf); } else { // New style
* OpenDML AVI Indexes aviItem = new AVITag_AviDmlIndex();
* aviItem.read(dwFourCC, raf); } }
*/
else {
// Some unknown chunk we will skip
aviItem = new AVIChunk();
aviItem.read(dwFourCC, raf);
aviItem.skip(raf);
}
break;
}
}
Logger.debug(aviItem.toString());
fileLeft = fileSize - raf.position();
} while (fileLeft > 0);
long t2 = currentTimeMillis();
Logger.debug("\tFile Left [" + fileLeft + "]");
Logger.debug("\tParse time : " + (t2 - t1) + "ms");
} finally {
if (ps != null) {
ps.close();
}
}
}
/*
* Generic AVI Chunk class
*/
static class AVIChunk {
protected int dwFourCC;
protected String fwFourCCStr;
protected int dwChunkSize;
protected long startOfChunk;
public void read(final int dwFourCC, final DataReader raf) throws IOException {
startOfChunk = raf.position() - 4; // Add four as we've
// already read the
// dwFourCC DWORD flag
this.dwFourCC = dwFourCC;
this.fwFourCCStr = AVIReader.toFourCC(dwFourCC);
dwChunkSize = raf.readInt();
}
public long getStartOfChunk() {
return (this.startOfChunk);
}
public long getEndOfChunk() {
return (startOfChunk + 8 + getChunkSize()); // 2 x DWORD (dwFourCC &
// dwChunkSize)
}
public int getFourCC() {
return (dwFourCC);
}
public void skip(final DataReader raf) throws IOException {
int chunkSize = getChunkSize();
if (chunkSize < 0)
throw new IOException("Negative chunk size for chunk [" + toFourCC(this.dwFourCC) + "]");
raf.skipBytes(chunkSize);
}
public int getChunkSize() {
// Chunks are padded to 2 byte alignments
// If the size is an odd number, then add one
// Chunk alignment to word
if ((dwChunkSize & 1) == 1)
return (dwChunkSize + 1);
else
return (dwChunkSize);
}
@Override
public String toString() {
String chunkStr = toFourCC(dwFourCC);
if (chunkStr.trim().length() == 0) {
chunkStr = Integer.toHexString(dwFourCC);
}
return ("\tCHUNK [" + chunkStr + "], Size [" + dwChunkSize + "], StartOfChunk [" + getStartOfChunk() + "]");
}
}
/*
* Generic AVI List class
*/
static class AVIList extends AVIChunk {
protected int dwListTypeFourCC;
protected String dwListTypeFourCCStr;
@Override
public void read(final int dwFourCC, final DataReader raf) throws IOException {
super.read(dwFourCC, raf);
dwChunkSize -= 4; // Correct for next field being _in between the
// size and the data
dwListTypeFourCC = raf.readInt();
dwListTypeFourCCStr = AVIReader.toFourCC(dwListTypeFourCC);
}
public int getListType() {
return (dwListTypeFourCC);
}
/*
* @Override public void skip(final DataReader raf) throws IOException {
* // Don't skip, each list contains N number of AviChunks //
* raf.skipBytes(0); }
*/
@Override
public String toString() {
String dwFourCCStr = toFourCC(this.dwFourCC);
return (dwFourCCStr + " [" + dwListTypeFourCCStr + "], Size [" + dwChunkSize + "], StartOfChunk ["
+ getStartOfChunk() + "]");
}
}
static class AVI_SEGM extends AVIChunk {
@Override
public void read(final int dwFourCC, final DataReader raf) throws IOException {
super.read(dwFourCC, raf);
}
@Override
public int getChunkSize() {
// Segment hack
if (dwChunkSize == 0)
return (0);
else
return (dwChunkSize + 1); // Not sure dwSize is always 1, or
// signifies a 2-byte alignment
}
@Override
public String toString() {
return ("SEGMENT Align, Size [" + dwChunkSize + "], StartOfChunk [" + getStartOfChunk() + "]");
}
}
static class AVITag_AVIH extends AVIChunk {
// public byte[] fcc = new byte[]{'a','v','i','h'};
public String _getHeight;
final static int AVIF_HASINDEX = 0x00000010; // Index at end of file?
final static int AVIF_MUSTUSEINDEX = 0x00000020;
final static int AVIF_ISINTERLEAVED = 0x00000100;
final static int AVIF_TRUSTCKTYPE = 0x00000800; // Use CKType to find
// key frames
final static int AVIF_WASCAPTUREFILE = 0x00010000;
final static int AVIF_COPYRIGHTED = 0x00020000;
private int dwMicroSecPerFrame; // (1 / frames per sec) * 1,000,000
private int dwMaxBytesPerSec;
private int dwPaddingGranularity;
private int dwFlags;
private int dwTotalFrames; // replace with correct value
private int dwInitialFrames;
private int dwStreams;
private int dwSuggestedBufferSize;
private int dwWidth; // replace with correct value
private int dwHeight; // replace with correct value
private int[] dwReserved;
public AVITag_AVIH() {
super();
this.dwReserved = new int[4];
}
@Override
public void read(final int dwFourCC, final DataReader raf) throws IOException {
super.read(dwFourCC, raf);
if (dwFourCC != FOURCC_AVIH)
throw new IOException("Unexpected AVI header : " + toFourCC(dwFourCC));
if (getChunkSize() != 56)
throw new IOException("Expected dwSize=56");
dwMicroSecPerFrame = raf.readInt();
dwMaxBytesPerSec = raf.readInt();
dwPaddingGranularity = raf.readInt();
dwFlags = raf.readInt();
dwTotalFrames = raf.readInt();
dwInitialFrames = raf.readInt();
dwStreams = raf.readInt();
dwSuggestedBufferSize = raf.readInt();
dwWidth = raf.readInt();
dwHeight = raf.readInt();
dwReserved[0] = raf.readInt();
dwReserved[1] = raf.readInt();
dwReserved[2] = raf.readInt();
dwReserved[3] = raf.readInt();
}
public int getWidth() {
return dwWidth;
}
public int getHeight() {
return dwHeight;
}
public int getStreams() {
return (dwStreams);
}
public int getTotalFrames() {
return (dwTotalFrames);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if ((dwFlags & AVIF_HASINDEX) != 0)
sb.append("HASINDEX ");
if ((dwFlags & AVIF_MUSTUSEINDEX) != 0)
sb.append("MUSTUSEINDEX ");
if ((dwFlags & AVIF_ISINTERLEAVED) != 0)
sb.append("ISINTERLEAVED ");
if ((dwFlags & AVIF_WASCAPTUREFILE) != 0)
sb.append("AVIF_WASCAPTUREFILE ");
if ((dwFlags & AVIF_COPYRIGHTED) != 0)
sb.append("AVIF_COPYRIGHTED ");
return ("AVIH Resolution [" + this.dwWidth + "x" + this.dwHeight + "], NumFrames [" + this.dwTotalFrames
+ "], Flags [" + Integer.toHexString(dwFlags) + "] - [" + sb.toString().trim() + "]");
}
}
static class AVITag_STRH extends AVIChunk {
final static int AVISF_DISABLED = 0x00000001;
final static int AVISF_VIDEO_PALCHANGES = 0x00010000;
private int fccType; // private byte[] fccType = new
// byte[]{'v','i','d','s'};
private int fccCodecHandler; // private byte[] fccHandler = new
// byte[]{'M','J','P','G'};
private int dwFlags = 0;
private short wPriority = 0;
private short wLanguage = 0;
private int dwInitialFrames = 0;
private int dwScale = 0; // microseconds per frame
private int dwRate = 1000000; // dwRate / dwScale = frame rate
private int dwStart = 0;
private int dwLength = 0; // num frames
private int dwSuggestedBufferSize = 0;
private int dwQuality = -1;
private int dwSampleSize = 0;
private short left = 0;
private short top = 0;
private short right = 0;
private short bottom = 0;
@Override
public void read(final int dwFourCC, final DataReader raf) throws IOException {
super.read(dwFourCC, raf);
if (dwFourCC != FOURCC_STRH)
throw new IOException("Expected 'strh' fourcc got [" + toFourCC(this.dwFourCC) + "]");
fccType = raf.readInt();
fccCodecHandler = raf.readInt();
dwFlags = raf.readInt();
wPriority = raf.readShort();
wLanguage = raf.readShort();
dwInitialFrames = raf.readInt();
dwScale = raf.readInt();
dwRate = raf.readInt();
dwStart = raf.readInt();
dwLength = raf.readInt();
dwSuggestedBufferSize = raf.readInt();
dwQuality = raf.readInt();
dwSampleSize = raf.readInt();
left = raf.readShort();
top = raf.readShort();
right = raf.readShort();
bottom = raf.readShort();
}
public int getType() {
return (fccType);
}
public int getHandler() {
return (fccCodecHandler);
}
public String getHandlerStr() {
if (fccCodecHandler != 0)
return (toFourCC(fccCodecHandler));
else
return ("");
}
public int getInitialFrames() {
return dwInitialFrames;
}
@Override
public String toString() {
return ("\tCHUNK [" + toFourCC(this.dwFourCC) + "], Type[" + (fccType > 0 ? toFourCC(this.fccType) : " ")
+ "], Handler [" + (fccCodecHandler > 0 ? toFourCC(this.fccCodecHandler) : " ") + "]");
}
}
/**
* typedef struct tagBITMAPINFOHEADER { DWORD biSize; LONG biWidth; LONG
* biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD
* biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed;
* DWORD biClrImportant; } BITMAPINFOHEADER;
*/
static class AVITag_BitmapInfoHeader extends AVIChunk {
private int biSize;
private int biWidth; // long
private int biHeight; // long
private short biPlanes;
private short biBitCount;
private int biCompression;
private int biSizeImage;
private int biXPelsPerMeter; // long
private int biYPelsPerMeter; // long
private int biClrUsed;
private int biClrImportant;
// Optional palette info ( number of colours I believe )
private byte r;
private byte g;
private byte b;
private byte x;
@Override
public void read(final int dwFourCC, final DataReader raf) throws IOException {
super.read(dwFourCC, raf);
biSize = raf.readInt();
biWidth = raf.readInt();
biHeight = raf.readInt();
biPlanes = raf.readShort();
biBitCount = raf.readShort();
biCompression = raf.readInt();
biSizeImage = raf.readInt();
biXPelsPerMeter = raf.readInt();
biYPelsPerMeter = raf.readInt();
biClrUsed = raf.readInt();
biClrImportant = raf.readInt();
if (this.getChunkSize() == 56) // Normal size is 40, plus optional
// extra 4 dwords for palette info =
// 16 bytes)
{
r = raf.readByte(); // readSh3ort(raf);
g = raf.readByte();
b = raf.readByte();
x = raf.readByte();
}
/*
* logger.debug("\t\t\t- Compression [" + toFourCC(biCompression)
* + "]"); logger.debug("\t\t\t- Bits Per Pixel [" + biBitCount +
* "]"); logger.debug("\t\t\t- Resolution [" + (biWidth &
* SIZE_MASK) + " x " + ( biHeight & SIZE_MASK) + "]");
* logger.debug("\t\t\t- Planes [" + biPlanes + "]");
*/
}
@Override
public int getChunkSize() {
return (biSize);
}
@Override
public String toString() {
return ("\tCHUNK [" + toFourCC(dwFourCC) + "], BitsPerPixel [" + biBitCount + "], Resolution ["
+ (biWidth & SIZE_MASK) + " x " + (biHeight & SIZE_MASK) + "], Planes [" + biPlanes + "]");
}
}
/*
* typedef struct { WORD wFormatTag; WORD nChannels; DWORD nSamplesPerSec;
* DWORD nAvgBytesPerSec; WORD nBlockAlign; } WAVEFORMAT;
*
* typedef struct { WORD wFormatTag; WORD nChannels; DWORD nSamplesPerSec;
* DWORD nAvgBytesPerSec; WORD nBlockAlign;
*
* WORD wBitsPerSample; WORD cbSize; } WAVEFORMATEX;
*
* typedef struct _GUID { DWORD Data1; WORD Data2; WORD Data3; BYTE
* Data4[8]; } GUID;
*
*
* typedef struct { WAVEFORMATEX Format; union { WORD wValidBitsPerSample;
* // bits of precision WORD wSamplesPerBlock; // valid if wBitsPerSample==0
* WORD wReserved; // If neither applies, set to zero. } Samples; DWORD
* dwChannelMask; // which channels are present in stream GUID SubFormat; }
* WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;
*/
static class AVITag_WaveFormatEx extends AVIChunk {
public final static int SPEAKER_FRONT_LEFT = 0x1;
public final static int SPEAKER_FRONT_RIGHT = 0x2;
public final static int SPEAKER_FRONT_CENTER = 0x4;
public final static int SPEAKER_LOW_FREQUENCY = 0x8;
public final static int SPEAKER_BACK_LEFT = 0x10;
public final static int SPEAKER_BACK_RIGHT = 0x20;
public final static int SPEAKER_FRONT_LEFT_OF_CENTER = 0x40;
public final static int SPEAKER_FRONT_RIGHT_OF_CENTER = 0x80;
public final static int SPEAKER_BACK_CENTER = 0x100;
public final static int SPEAKER_SIDE_LEFT = 0x200;
public final static int SPEAKER_SIDE_RIGHT = 0x400;
public final static int SPEAKER_TOP_CENTER = 0x800;
public final static int SPEAKER_TOP_FRONT_LEFT = 0x1000;
public final static int SPEAKER_TOP_FRONT_CENTER = 0x2000;
public final static int SPEAKER_TOP_FRONT_RIGHT = 0x4000;
public final static int SPEAKER_TOP_BACK_LEFT = 0x8000;
public final static int SPEAKER_TOP_BACK_CENTER = 0x10000;
public final static int SPEAKER_TOP_BACK_RIGHT = 0x20000;
// WaveFormat
protected short wFormatTag;
protected short channels;
protected int nSamplesPerSec;
protected int nAvgBytesPerSec;
protected short nBlockAlign;
// WaveFormatEx // WaveFormat PCM
protected short wBitsPerSample;
// WaveFormatEx
protected short cbSize;
// WaveFormatExtensible
protected short wValidBitsPerSample;
protected short samplesValidBitsPerSample;
protected short wReserved;
protected int channelMask;
protected int guid_data1;
protected short guid_data2;
protected short guid_data3;
protected byte[] guid_data4;
// Optional MP3 parameters if wFormatTag = 0x0055
protected boolean mp3Flag = false;
protected short wID;
protected int fdwFlags;
protected short nBlockSize;
protected short nFramesPerBlock;
protected short nCodecDelay;
private String audioFormat = "?";
public AVITag_WaveFormatEx() {
this.guid_data4 = new byte[8];
}
@Override
public void read(final int dwFourCC, final DataReader raf) throws IOException {
super.read(dwFourCC, raf);
// WAVEFORMAT Fields
wFormatTag = raf.readShort();
channels = raf.readShort();
nSamplesPerSec = raf.readInt();
nAvgBytesPerSec = raf.readInt();
nBlockAlign = raf.readShort();
switch ((int) wFormatTag) {
// See mmreg.h for a list of all audio format tags
case AUDIO_FORMAT_PCM: {
wBitsPerSample = raf.readShort();
if (dwChunkSize == 40) {
// Simulate a C union struct
wValidBitsPerSample = samplesValidBitsPerSample = wReserved = raf.readShort();
cbSize = raf.readShort();
channelMask = raf.readInt();
// GUID SubFormat
guid_data1 = raf.readInt();
guid_data2 = raf.readShort();
guid_data3 = raf.readShort();
raf.readFully(guid_data4);
}
audioFormat = "PCM";
break;
}
case AUDIO_FORMAT_MP3: {
// WaveFormatEX
wBitsPerSample = raf.readShort();
cbSize = raf.readShort();
wID = raf.readShort();
fdwFlags = raf.readInt();
nBlockSize = raf.readShort();
nFramesPerBlock = raf.readShort();
nCodecDelay = raf.readShort();
mp3Flag = true;
audioFormat = "MP3";
break;
}
case AUDIO_FORMAT_AC3: {
audioFormat = "AC3";
break;
}
case AUDIO_FORMAT_DTS: {
audioFormat = "DTS";
break;
}
case AUDIO_FORMAT_VORBIS: {
audioFormat = "VORBIS";
break;
}
case AUDIO_FORMAT_EXTENSIBLE: {
// WaveFormatEX
wBitsPerSample = raf.readShort();
cbSize = raf.readShort();
// WaveFormat Extensible
// Simulate a C union struct
wValidBitsPerSample = samplesValidBitsPerSample = wReserved = raf.readShort();
channelMask = raf.readInt();
// GUID SubFormat
guid_data1 = raf.readInt();
guid_data2 = raf.readShort();
guid_data3 = raf.readShort();
raf.readFully(guid_data4);
audioFormat = "EXTENSIBLE";
break;
}
default: {
audioFormat = "Unknown : " + Integer.toHexString(wFormatTag);
break;
}
}
}
public boolean isMP3() {
return (mp3Flag);
}
public short getCbSize() {
return (this.cbSize);
}
@Override
public String toString() {
return (String.format(
"\tCHUNK [%s], ChunkSize [%d], Format [%s], Channels [%d], Channel Mask [%s], MP3 [%b], SamplesPerSec [%d], nBlockAlign [%d]",
toFourCC(dwFourCC), getChunkSize(), audioFormat, channels, Integer.toHexString(channelMask),
mp3Flag, this.nSamplesPerSec, this.getStartOfChunk(), this.nBlockAlign));
}
}
/*
* Subtitle
*
* char[4]; // 'GAB2' BYTE 0x00; WORD 0x02; // unicode DWORD dwSize_name; //
* length of stream name in bytes char name[dwSize_name]; // zero-terminated
* subtitle stream name encoded in UTF-16 WORD 0x04; DWORD dwSize; // size
* of SRT/SSA text file char data[dwSize]; // entire SRT/SSA file
*/
static class AVITag_VideoChunk extends AVIChunk {
protected int streamNo;
protected boolean compressed = false;
protected int frameNo = -1;
private DataReader raf;
public AVITag_VideoChunk(final boolean compressed, DataReader raf) {
super();
this.compressed = compressed;
this.raf = raf;
}
public int getStreamNo() {
return (streamNo);
}
public void setFrameNo(final int frameNo) {
this.frameNo = frameNo;
}
@Override
public int getChunkSize() {
// Chunks are padded to 2 byte alignments
// If the size is an odd number, then add one
// Chunk alignment
if ((dwChunkSize & 1) == 1)
return (dwChunkSize + 1);
else
return (dwChunkSize);
}
@Override
public void read(final int dwFourCC, final DataReader raf) throws IOException {
super.read(dwFourCC, raf);
String fourccStr = toFourCC(dwFourCC);
streamNo = Integer.parseInt(fourccStr.substring(0, 2));
}
public byte[] getVideoPacket() throws IOException {
byte[] videoFrameData = new byte[dwChunkSize];
int bytesRead = raf.readFully(videoFrameData);
if (bytesRead != dwChunkSize)
throw new IOException(
"Read mismatch expected chunksize [" + dwChunkSize + "], Actual read [" + bytesRead + "]");
int alignment = getChunkSize() - dwChunkSize;
if (alignment > 0)
raf.skipBytes(alignment);
return (videoFrameData);
}
@Override
public String toString() {
return ("\tVIDEO CHUNK - Stream " + streamNo + ", chunkStart=" + this.getStartOfChunk() + ", "
+ (compressed ? "compressed" : "uncompressed") + ", ChunkSize=" + getChunkSize() + ", FrameNo="
+ this.frameNo);
}
}
static class AVITag_AudioChunk extends AVIChunk {
protected int streamNo;
private DataReader raf;
@Override
public void read(final int dwFourCC, final DataReader raf) throws IOException {
this.raf = raf;
super.read(dwFourCC, raf);
String fourccStr = toFourCC(dwFourCC);
streamNo = Integer.parseInt(fourccStr.substring(0, 2));
}
@Override
public int getChunkSize() {
// Chunks are padded to 2 byte alignments
// If the size is an odd number, then add one
// Chunk alignment
if ((dwChunkSize & 1) == 1)
return (dwChunkSize + 1);
else
return (dwChunkSize);
}
public byte[] getAudioPacket() throws IOException {
byte[] audioFrameData = new byte[dwChunkSize];
int bytesRead = raf.readFully(audioFrameData);
if (bytesRead != dwChunkSize)
throw new IOException(
"Read mismatch expected chunksize [" + dwChunkSize + "], Actual read [" + bytesRead + "]");
int alignment = getChunkSize() - dwChunkSize;
if (alignment > 0)
raf.skipBytes(alignment);
return (audioFrameData);
}
@Override
public String toString() {
return ("\tAUDIO CHUNK - Stream " + streamNo + ", StartOfChunk=" + this.getStartOfChunk() + ", ChunkSize="
+ getChunkSize());
}
}
/**
* AVIINDEXENTRY index_entry[n]
*
* typedef struct { DWORD ckid; DWORD dwFlags; DWORD dwChunkOffset; DWORD
* dwChunkLength; } AVIINDEXENTRY;
*
* // Flag bitmasks #define AVIIF_LIST 0x00000001 #define AVIIF_KEYFRAME
* 0x00000010 #define AVIIF_NO_TIME 0x00000100 #define AVIIF_COMPRESSOR
* 0x0FFF0000 // unused?
*/
static class AVITag_AviIndex extends AVIChunk {
protected int numIndexes = 0;
protected int[] ckid;
protected int[] dwFlags;
protected int[] dwChunkOffset;
protected int[] dwChunkLength;
@Override
public void read(final int dwFourCC, final DataReader raf) throws IOException {
super.read(dwFourCC, raf);
numIndexes = this.getChunkSize() >> 4;
ckid = new int[numIndexes];
dwFlags = new int[numIndexes];
dwChunkOffset = new int[numIndexes];
dwChunkLength = new int[numIndexes];
for (int i = 0; i < numIndexes; i++) {
ckid[i] = raf.readInt(); // raf.readInt();
dwFlags[i] = raf.readInt(); // raf.readInt();
dwChunkOffset[i] = raf.readInt(); // raf.readInt();
dwChunkLength[i] = raf.readInt(); // raf.readInt();
}
raf.setPosition(this.getEndOfChunk());
int alignment = getChunkSize() - dwChunkSize;
if (alignment > 0)
raf.skipBytes(alignment);
}
public int getNumIndexes() {
return numIndexes;
}
public int[] getCkid() {
return ckid;
}
public int[] getDwFlags() {
return dwFlags;
}
public int[] getDwChunkOffset() {
return dwChunkOffset;
}
public int[] getDwChunkLength() {
return dwChunkLength;
}
public void debugOut() {
for (int i = 0; i < numIndexes; i++) {
Logger.debug("\t");
}
}
@Override
public String toString() {
return (String.format("\tAvi Index List, StartOfChunk [%d], ChunkSize [%d], NumIndexes [%d]",
this.getStartOfChunk(), this.dwChunkSize, (getChunkSize() >> 4)));
}
}
/**
* typedef struct _avisuperindex_chunk { FOURCC fcc; DWORD cb; WORD
* wLongsPerEntry; BYTE bIndexSubType; BYTE bIndexType; DWORD nEntriesInUse;
* DWORD dwChunkId; DWORD dwReserved[3]; struct _avisuperindex_entry {
* __int64 qwOffset; DWORD dwSize; DWORD dwDuration; } aIndex[ ]; }
* AVISUPERINDEX;
*
* #define STDINDEXSIZE 0x4000 #define NUMINDEX(wLongsPerEntry)
* ((STDINDEXSIZE-32)/4/(wLongsPerEntry)) #define
* NUMINDEXFILL(wLongsPerEntry) ((STDINDEXSIZE/4) -
* NUMINDEX(wLongsPerEntry))
*/
// Open DML style AVI Super Index. Extension index when the first is not
// enough, mainly for files > 1gb
static class AVITag_AviDmlSuperIndex extends AVIChunk {
// Read
protected short wLongsPerEntry;
protected byte bIndexSubType;
protected byte bIndexType;
protected int nEntriesInUse;
protected int dwChunkId;
protected int[] dwReserved;
protected long[] qwOffset;
protected int[] dwSize;
protected int[] dwDuration;
private int numIndex;
private int numIndexFill;
// Generated
StringBuilder sb;
private int streamNo = 0;
public AVITag_AviDmlSuperIndex() {
super();
this.dwReserved = new int[3];
this.sb = new StringBuilder();
}
@Override
public void read(final int dwFourCC, final DataReader raf) throws IOException {
super.read(dwFourCC, raf);
wLongsPerEntry = raf.readShort();
bIndexSubType = raf.readByte();
bIndexType = raf.readByte();
nEntriesInUse = raf.readInt();
dwChunkId = raf.readInt(); // id the index points to eg. 00dx
dwReserved[0] = raf.readInt();
dwReserved[1] = raf.readInt();
dwReserved[2] = raf.readInt();
qwOffset = new long[nEntriesInUse];
dwSize = new int[nEntriesInUse];
dwDuration = new int[nEntriesInUse];
String chunkIdStr = toFourCC(this.dwChunkId);
sb.append(String.format(
"\tAvi DML Super Index List - ChunkSize=%d, NumIndexes = %d, longsPerEntry = %d, Stream = %s, Type = %s",
this.getChunkSize(), nEntriesInUse, wLongsPerEntry, chunkIdStr.substring(0, 2),
chunkIdStr.substring(2)));
for (int i = 0; i < nEntriesInUse; i++) {
qwOffset[i] = raf.readLong();
dwSize[i] = raf.readInt();
dwDuration[i] = raf.readInt();
sb.append(String.format("\n\t\tStandard Index - Offset [%d], Size [%d], Duration [%d]", qwOffset[i],
dwSize[i], dwDuration[i]));
}
raf.setPosition(this.getEndOfChunk());
}
@Override
public String toString() {
return (sb.toString());
}
}
/**
* typedef struct _avistdindex_chunk { FOURCC fcc; DWORD cb; WORD
* wLongsPerEntry; // 2 bytes BYTE bIndexSubType; // 1 byte BYTE bIndexType;
* // 1 byte DWORD nEntriesInUse; // 4 bytes DWORD dwChunkId; // 4 bytes
* __int64 qwBaseOffset; // 8 bytes DWORD dwReserved3; // 4 bytes
*
* struct _avistdindex_entry { DWORD dwOffset; // 4 bytes DWORD dwSize; // 4
* bytes } aIndex[ ]; } AVISTDINDEX;
*
*
* #define AVISTDINDEX_DELTAFRAME ( 0x80000000) // Delta frames have the
* high bit set #define AVISTDINDEX_SIZEMASK (~0x80000000)
*
* #define AVI_INDEX_OF_INDEXES 0x00 #define AVI_INDEX_OF_CHUNKS 0x01
* #define AVI_INDEX_OF_TIMED_CHUNKS 0x02 #define AVI_INDEX_OF_SUB_2FIELD
* 0x03 #define AVI_INDEX_IS_DATA 0x80
*/
// Open DML style AVI Standard Index. Extension index when the first is not
// enough, mainly for files > 1gb
static class AVITag_AviDmlStandardIndex extends AVIChunk {
protected short wLongsPerEntry;
protected byte bIndexSubType;
protected byte bIndexType;
protected int nEntriesInUse;
protected int dwChunkId;
protected long qwBaseOffset;
protected int dwReserved2;
protected int[] dwOffset;
protected int[] dwDuration;
int lastOffset = -1, lastDuration = -1;
@Override
public int getChunkSize() {
return (dwChunkSize);
}
@Override
public void read(final int dwFourCC, final DataReader raf) throws IOException {
super.read(dwFourCC, raf);
wLongsPerEntry = raf.readShort();
bIndexSubType = raf.readByte();
bIndexType = raf.readByte();
nEntriesInUse = raf.readInt();
dwChunkId = raf.readInt(); // id the index points to eg. 00dx
qwBaseOffset = raf.readLong();
dwReserved2 = raf.readInt();
dwOffset = new int[nEntriesInUse];
dwDuration = new int[nEntriesInUse];
try {
for (int i = 0; i < nEntriesInUse; i++) {
dwOffset[i] = raf.readInt();
dwDuration[i] = raf.readInt();
lastOffset = dwOffset[i];
lastDuration = dwDuration[i];
}
} catch (Exception e) {
Logger.debug("Failed to read : " + toString());
}
raf.setPosition(this.getEndOfChunk());
}
@Override
public String toString() {
return (String.format(
"\tAvi DML Standard Index List Type=%d, SubType=%d, ChunkId=%s, StartOfChunk=%d, NumIndexes=%d, LongsPerEntry=%d, ChunkSize=%d, FirstOffset=%d, FirstDuration=%d,LastOffset=%d, LastDuration=%d",
bIndexType, bIndexSubType, toFourCC(this.dwChunkId), this.getStartOfChunk(), nEntriesInUse,
wLongsPerEntry, getChunkSize(), dwOffset[0], dwDuration[0], lastOffset, lastDuration));
}
}
}