Code sample: Parsing a RIMM streaming video file
import java.io.*;
import java.util.*;
import javax.microedition.io.*;
import javax.microedition.io.file.*;
/**
*
*/
public class KeyFrameOutputStream extends OutputStream {
// output locations on the sd card
private static final String OUT_DIR = "file:///SDCard/securitycam/";
private static final String OUT_FILE = "output.frames";
// some size constants
private static final int HEADER_SIZE = 8;
private static final int CHUNK_INFO_SIZE = 7;
// parsing states
private static final int STATE_HEADER = 0;
private static final int STATE_CHUNK_INFO = 1;
private static final int STATE_DATA = 2;
private static final int STATE_WAIT_FOR_NEXT = 3;
// member variables
private int _state;
private int _pos;
private boolean _isVideoFrame;
private boolean _isKeyFrame;
private boolean _isConfigFrame;
private boolean _startSaving;
private boolean _saveFrame;
private int _dataSize;
private int _duration;
// temp buffer ref
private byte[] _buf;
private FileConnection _file;
private OutputStream _out;
private WriteThread _writer;
private boolean _reading;
public KeyFrameOutputStream() {
_state = STATE_HEADER;
_pos = 0;
}
public void open() {
_reading = true;
try {
// create the file connection for our frame destination
FileConnection dir = (FileConnection)Connector.open( OUT_DIR );
if( !dir.exists() ) {
dir.mkdir();
}
dir.close();
_file = (FileConnection)Connector.open( OUT_DIR + OUT_FILE );
if( !_file.exists() ) {
_file.create();
} else {
_file.truncate( 0L );
}
_out = _file.openOutputStream();
} catch ( Exception e ) {
}
// start the write thread
_writer = new WriteThread( _out );
_writer.start();
}
public void startClosing() {
// shuts down the write thread
_reading = false;
if( _writer != null ) _writer.stop();
}
public void write( int b ) throws IOException {
if( _reading ) {
switch( _state ) {
case STATE_HEADER:
// read the video stream header
_pos++;
if( _pos == HEADER_SIZE ) {
_state = STATE_CHUNK_INFO;
_buf = new byte[CHUNK_INFO_SIZE];
_pos = 0;
}
break;
case STATE_CHUNK_INFO:
// parse the information about the next chunk
_buf[_pos] = (byte)b;
_pos++;
if( _pos == CHUNK_INFO_SIZE ) {
// 1 indicates video frame, 0 indicates audio
_isVideoFrame = (_buf[0] != 0);
// key frame and config frame flags are in the top two bits of the data size value
_isKeyFrame = ((_buf[4] & 0x80) != 0);
_isConfigFrame = ((_buf[4] & 0x40) != 0);
_dataSize = ((int)(_buf[4] & 0x3f) << 24) |
((int)(_buf[3] & 0xff) << 16) |
((int)(_buf[2] & 0xff) << 8) |
((int)(_buf[1] & 0xff));
// duration is stored in the next two bytes
_duration = ((int)(_buf[6] & 0xff) << 8) |
((int)(_buf[5] & 0xff));
// we want the config frame to be the first frame in our output file
if( !_startSaving ) {
if( _isVideoFrame && _isConfigFrame ) {
_startSaving = true;
}
}
// after that only save the key frames
_saveFrame = _startSaving && _isVideoFrame && ( _isConfigFrame || _isKeyFrame );
_state = STATE_DATA;
if( _saveFrame ) {
_buf = new byte[_dataSize];
}
_pos = 0;
}
break;
case STATE_DATA:
// buffer the frame for writing to file
if( _saveFrame ) _buf[_pos] = (byte)b;
_pos++;
if( _pos == _dataSize ) {
if( _saveFrame ) {
_writer.addFrame( _buf );
}
_state = STATE_WAIT_FOR_NEXT;
_buf = new byte[CHUNK_INFO_SIZE];
_pos = 0;
}
break;
case STATE_WAIT_FOR_NEXT:
// skip over the chunk footer
_pos++;
if( _pos == CHUNK_INFO_SIZE ) {
_state = STATE_CHUNK_INFO;
_buf = new byte[CHUNK_INFO_SIZE];
_pos = 0;
}
break;
}
}
}
public void close() throws IOException {
// shut down the write thread and close our file
try {
_writer.join();
} catch ( InterruptedException ie ) {
}
_out.close();
_file.close();
}
private static final class WriteThread extends Thread {
// writes key frames to a file as they are found by our parser
private Vector _frames;
private boolean _running;
private OutputStream _out;
public WriteThread( OutputStream out ) {
_frames = new Vector();
_running = true;
_out = out;
}
public void run() {
for( ;; ) {
ByteArray frame = null;
synchronized( this ) {
if( _frames.size() > 0 ) {
frame = (ByteArray)_frames.elementAt( 0 );
if( frame == null ) break;
_frames.removeElementAt( 0 );
} else {
if( !_running ) break;
try {
wait();
if( _running ) continue;
} catch ( InterruptedException ie ) {
}
}
}
if( frame == null ) break;
try {
byte[] bytes = frame.array;
_out.write( bytes, 0, bytes.length );
_out.flush();
} catch ( Exception e ) {
}
}
}
public synchronized void addFrame( byte[] frame ) {
_frames.addElement( new ByteArray( frame ) );
notifyAll();
}
public synchronized void stop() {
_running = false;
notifyAll();
}
}
private static final class ByteArray {
public byte[] array;
public ByteArray( byte[] array ) {
this.array = array;
}
}
}
Next topic: Working with pictures on a
BlackBerry device
Previous topic: RIM proprietary video format (RIMM streaming file)