/***************************************
* ViPER-MPEG *
* The Video Processing *
* Evaluation Resource *
* MPEG-1 Decoder *
* Distributed under the LGPL license *
* Terms available at gnu.org. *
* *
* Copyright University of Maryland, *
* College Park. *
***************************************/
package edu.umd.cfar.lamp.mpeg1.video;
import java.io.*;
import java.util.*;
import edu.columbia.ee.flavor.*;
import edu.umd.cfar.lamp.mpeg1.*;
/** Indexes the Groups of Pictures within a Video Sequence. */
public class VideoIndex
{
public static int MAGIC_NUMBER = 0x11172201; // for ISO/IEC 11172-2 version 1
private Vector index = null;
private Vector sequenceHeaderIndex = new Vector();
private SequenceHeader firstSequenceHeader = null;
private VideoDecoder videoDecoder = null;
private IndexerState indexerState = new IndexerState();
public VideoIndex(VideoDecoder videoDecoder)
{
this.videoDecoder = videoDecoder;
}
public int getLastSequenceHeader()
{
return sequenceHeaderIndex.size()-1;
}
public void writeIndex(DataOutput out) throws IOException, MpegException
{
index();
out.writeInt(MAGIC_NUMBER);
int numSequenceHeaders = sequenceHeaderIndex.size();
out.writeInt(numSequenceHeaders);
for (int i = 0; i < numSequenceHeaders; i++)
{
((SequenceHeader)sequenceHeaderIndex.get(i)).writeIndex(out);
}
int numGOPs = index.size();
out.writeInt(numGOPs);
for (int i = 0; i < numGOPs; i++)
{
((VideoIndexElement)index.get(i)).writeIndex(out);
}
}
public void readIndex(DataInput in) throws IOException, MpegException
{
int magicNumber = in.readInt();
byte version = (byte)(magicNumber & 0xFF); // version is last byte of magic number
if ((magicNumber >>> 8) != (MAGIC_NUMBER >>> 8)) // compare first 3 bytes of magic number
throw new IndexException("Invalid magic number: " + Integer.toHexString(magicNumber));
switch (version)
{
case 0x01:
index = new Vector();
int numSequenceHeaders = in.readInt();
for (int i = 0; i < numSequenceHeaders; i++)
{
SequenceHeader sh = new SequenceHeader();
sh.readIndex(in, version);
addSequenceHeader(sh);
}
int numGOPs = in.readInt();
for (int i = 0; i < numGOPs; i++)
{
long startPosition = in.readLong();
int numPictures = in.readInt();
int sequenceHeader = in.readInt();
addGroupOfPictures(startPosition, numPictures, sequenceHeader);
}
break;
default:
throw new UnsupportedIndexVersionException("version " + Integer.toHexString(version));
}
}
public void complete()
{
Collections.sort(index);
}
public VideoDecoder getVideoDecoder()
{
return videoDecoder;
}
public VideoSource getVideoSource()
{
return videoDecoder.getVideoSource();
}
public IndexerState getIndexerState()
{
return indexerState;
}
public int getMbWidth(int frame) throws IOException, MpegException
{
return getSequenceHeader(frame).getMbWidth();
}
public int[][] getIntraQuantizerMatrix(int frame) throws IOException, MpegException
{
return getSequenceHeader(frame).getIntraQuantizerMatrix();
}
public int[][] getNonIntraQuantizerMatrix(int frame) throws IOException, MpegException
{
return getSequenceHeader(frame).getNonIntraQuantizerMatrix();
}
public void index() throws IOException, MpegException
{
if (!indexed())
{
index = new Vector();
VideoSequence.index(new Bitstream(getVideoSource().copySource()), indexerState, this);
}
}
public boolean indexed()
{
return (index != null);
}
public void getFirstSequenceHeader() throws IOException, MpegException
{
if (firstSequenceHeader == null)
{
VideoSource videoSource = getVideoSource().copySource();
firstSequenceHeader = VideoSequence.getFirstSequenceHeader(new Bitstream(videoSource));
videoSource.close();
}
}
public SequenceHeader getSequenceHeader(int frame) throws IOException, MpegException
{
index();
int gop = getGroupOfPicturesNumberForFrame(frame);
VideoIndexElement vie = (VideoIndexElement)index.get(gop);
return (SequenceHeader)sequenceHeaderIndex.get(vie.getSequenceHeader());
}
public void addSequenceHeader(SequenceHeader sequenceHeader) throws IOException, MpegException
{
sequenceHeaderIndex.add(sequenceHeader);
}
public void addGroupOfPictures(long startPosition, int numPictures, int sequenceHeader) throws IOException, MpegException
{
int startPicture;
try
{
VideoIndexElement lastElement = (VideoIndexElement)index.lastElement();
startPicture = lastElement.getStartPicture() + lastElement.getNumPictures();
}
catch (NoSuchElementException nsee)
{
startPicture = 0;
}
index.add(new VideoIndexElement(startPosition, startPicture, numPictures, sequenceHeader, this));
}
public int getFrameWidth() throws IOException, MpegException
{
int currentFrame = videoDecoder.getCurrentFrame();
if (!indexed() || (currentFrame < 0))
{
getFirstSequenceHeader();
return firstSequenceHeader.getFrameWidth();
}
else
return getSequenceHeader(currentFrame).getFrameWidth();
}
public int getFrameHeight() throws IOException, MpegException
{
int currentFrame = videoDecoder.getCurrentFrame();
if (!indexed() || (currentFrame < 0))
{
getFirstSequenceHeader();
return firstSequenceHeader.getFrameHeight();
}
else
return getSequenceHeader(currentFrame).getFrameHeight();
}
public int getBitRate() throws IOException, MpegException
{
int currentFrame = videoDecoder.getCurrentFrame();
if (!indexed() || (currentFrame < 0))
{
getFirstSequenceHeader();
return firstSequenceHeader.getBitRate();
}
else
return getSequenceHeader(currentFrame).getBitRate();
}
public PelAspectRatio getPixelAspectRatio() throws IOException, MpegException
{
int currentFrame = videoDecoder.getCurrentFrame();
if (!indexed() || (currentFrame < 0))
{
getFirstSequenceHeader();
return firstSequenceHeader.getPixelAspectRatio();
}
else
return getSequenceHeader(currentFrame).getPixelAspectRatio();
}
public float getFrameRate() throws IOException, MpegException
{
int currentFrame = videoDecoder.getCurrentFrame();
if (!indexed() || (currentFrame < 0))
{
getFirstSequenceHeader();
return firstSequenceHeader.getFrameRate();
}
else
return getSequenceHeader(currentFrame).getFrameRate();
}
/**
* Gets the group of pictures for the given frame
* @param frame the frame
* @return 0-based number of the Group Of Pictures containing
* the given (0-based) frame.
* @throws FrameNotFoundException
*/
public int getGroupOfPicturesNumberForFrame(int frame) throws FrameNotFoundException
{
// binary search
int low = 0;
int high = index.size() - 1;
while (low <= high)
{
int mid = (low + high) / 2;
int c = ((VideoIndexElement)index.elementAt(mid)).findPicture(frame);
if (c < 0)
high = mid - 1;
else if (c > 0)
low = mid + 1;
else
return mid;
}
throw new FrameNotFoundException("frame " + frame);
}
public long getStartPosition(int groupOfPicturesNumber)
{
return ((VideoIndexElement)index.elementAt(groupOfPicturesNumber)).getStartPosition();
}
public int getStartFrame(int groupOfPicturesNumber)
{
return ((VideoIndexElement)index.elementAt(groupOfPicturesNumber)).getStartPicture();
}
public int getNumFrames()
{
if (index == null)
return 0;
if (index.size() == 0)
return 0;
VideoIndexElement lastElement = (VideoIndexElement)index.lastElement();
return lastElement.getLastPicture() + 1; // 0-based, so have to add 1
}
public long getPositionOfFrame(int n) throws IOException, MpegException
{
int gopNumber = getGroupOfPicturesNumberForFrame(n);
VideoIndexElement vie = (VideoIndexElement)index.elementAt(gopNumber);
return vie.getPositionOfPicture(n);
}
public int getLastIOrPFrame(int n) throws IOException, MpegException
{
int gopNumber = getGroupOfPicturesNumberForFrame(n);
VideoIndexElement vie = (VideoIndexElement)index.elementAt(gopNumber);
try
{
return vie.getLastIOrPPicture(n);
}
catch (FrameNotFoundException fnfe)
{
if (gopNumber == 0)
throw new FrameNotFoundException();
vie = (VideoIndexElement)index.elementAt(gopNumber-1);
return vie.getLastIOrPPicture(n);
}
}
public byte getPictureCodingTypeOfFrame(int n) throws IOException, MpegException
{
int gopNumber = getGroupOfPicturesNumberForFrame(n);
VideoIndexElement vie = (VideoIndexElement)index.elementAt(gopNumber);
return vie.getPictureCodingTypeOfPicture(n);
}
public String toString()
{
String result = new String();
Iterator iter = index.iterator();
while (iter.hasNext())
{
result += iter.next().toString() + "\n";
}
return result;
}
}