/*
Milenia Grafter Server
Copyright (c) 2007-2008 by Milan Toth. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.milgra.server;
/**
FrameBuffer class
@mail milgra@milgra.com
@author Milan Toth
@version 20080315
Tasks of Streambuffer
- store frame properties of a specific flv
- buffer frames based on speed
- return frames by request
**/
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.ArrayList;
import com.milgra.server.Server;
import com.milgra.server.OProcess;
public class FrameBuffer extends OProcess
{
// last - last timestamp, for duration count
// type - type of buffer content, 0x08 audio, 0x09 video
// frames - frame count in buffer
public int last;
public int type;
public int frames;
// lastStamp - timestamp of last sent frame
// lastPosition - position of last sent frame
public int lastStamp;
public int lastPosition;
// bufferSize - size of buffer
// bufferLimit - buffer limits timestamp
public double bufferSize;
public double bufferLimit;
// active - buffer is active
// forward - direction of buffering
// finished - buffer reached end
// buffering - buffering
public boolean active;
public boolean forward;
public boolean finished;
public boolean buffering;
// positions - container for frame file positions
// durations - for frame durations
// bodysizes - for frame sizes
// flvstamps - for frame stamps
public ArrayList < Long > positions;
public ArrayList < Integer > durations;
public ArrayList < Integer > bodysizes;
public ArrayList < Integer > flvstamps;
// file - the streams file
// pool - buffer pool
public RandomAccessFile file;
public HashMap < Integer , RtmpPacket > pool;
/**
* FrameBuffer constructor
* @param typeX type of frames stored in this buffer
* @param fileX file containing the stream
**/
public FrameBuffer ( int typeX , RandomAccessFile fileX )
{
// System.out.println( System.currentTimeMillis( ) + " FrameBuffer.construct " + typeX + " " + fileX );
// create
pool = new HashMap < Integer , RtmpPacket > ( );
positions = new ArrayList < Long > ( );
bodysizes = new ArrayList < Integer > ( );
flvstamps = new ArrayList < Integer > ( );
durations = new ArrayList < Integer > ( );
// set
type = typeX;
file = fileX;
frames = 0;
forward = true;
bufferSize = Library.NSBUFFER;
bufferLimit = 0;
lastStamp = 0;
lastPosition = 0;
}
/**
* FrameBuffer destructor
**/
public void destruct ( )
{
// System.out.println( System.currentTimeMillis( ) + " FrameBuffer.destruct " + type + " " + file );
pool = null;
positions = null;
bodysizes = null;
flvstamps = null;
durations = null;
}
/**
* Closes buffer, unregisters process
**/
public void close ( )
{
// System.out.println( System.currentTimeMillis( ) + " " + type + " FrameBuffer.close " );
Server.unregisterProcess( this , "io" );
destruct( );
}
/**
* Registers a frame related to the stream
* @param positionX frame position in file
* @param stampX frames timestamp
* @param sizeX frames body size
**/
public void registerFrame ( long positionX , int stampX , int sizeX )
{
// System.out.println( System.currentTimeMillis( ) + " " + type + " FrameBuffer.registerFrame " + positionX + " " + stampX + " " + sizeX );
bodysizes.add( sizeX );
flvstamps.add( stampX );
positions.add( positionX );
durations.add( stampX - last );
frames++;
active = true;
if ( stampX != last ) last = stampX;
}
/**
* Loads a specific frame
* @param indexX frame index
**/
public void loadFrame ( int indexX )
{
// System.out.println( System.currentTimeMillis( ) + " " + type + " FrameBuffer.loadFrame " + indexX );
try
{
// create packet and body container
RtmpPacket framePacket = new RtmpPacket( );
framePacket.body = new byte[ bodysizes.get( indexX ) ];
// read up body
file.seek( positions.get( indexX ) );
file.read( framePacket.body );
framePacket.bodyType = type;
framePacket.flvStamp = durations.get( indexX );
pool.put( indexX , framePacket );
}
catch ( IOException exception ) { exception.printStackTrace( ); }
}
/**
* Starts buffer checker process
*/
public void start ( )
{
// System.out.println( System.currentTimeMillis( ) + " " + type + " FrameBuffer.start " );
Server.registerProcess( this , "io" );
}
/**
* Stops buffer checker process
*/
public void stop ( )
{
// System.out.println( System.currentTimeMillis( ) + " " + type + " FrameBuffer.stop " );
Server.unregisterProcess( this , "io" );
}
/**
* Sets speed multiplier, needed for forward and buffer length
* @param multiplierX
**/
public void speed ( double multiplierX )
{
// System.out.println( System.currentTimeMillis( ) + " " + type + " FrameBuffer.setSpeed " + multiplierX );
forward = multiplierX < 0 ? false : true;
bufferSize = multiplierX * Library.NSBUFFER;
}
/**
* Seeks position to wanted timestamp from actual position
* @param stampX timestamp
**/
public void seek ( double stampX )
{
// System.out.println( System.currentTimeMillis( ) + " " + type + " FrameBuffer.seek " + stampX + " " + lastPosition );
if ( active )
{
// checking endpoints
int timeStamp;
if ( lastPosition == -1 ) lastPosition += 1;
if ( lastPosition == frames ) lastPosition -= 1;
if ( stampX < lastStamp )
{
do
{
timeStamp = flvstamps.get( lastPosition );
if ( stampX < timeStamp ) -- lastPosition;
}
while ( stampX < timeStamp && lastPosition > 0 );
}
else
{
do
{
timeStamp = flvstamps.get( lastPosition );
if ( stampX > timeStamp ) ++ lastPosition;
}
while ( stampX > timeStamp && lastPosition < frames );
}
if ( lastPosition == frames ) lastPosition -= 1;
if ( lastPosition == -1 ) lastPosition += 1;
// set limit, clear buffer
bufferLimit = flvstamps.get( lastPosition );
finished = false;
pool.clear( );
}
}
/**
* Buffers a specific number of frames from actual position. *
* @param limitX last stamp to buffer
**/
public void bufferFrames ( double limitX )
{
// System.out.println( System.currentTimeMillis( ) + " " + type + " FrameBuffer.bufferFrames " + limitX );
if ( !active ) return;
if ( lastPosition == frames ) lastPosition -= 1;
if ( lastPosition == -1 ) lastPosition += 1;
int flvstamp = 0;
int position = lastPosition;
if ( forward )
{
do
{
flvstamp = flvstamps.get( position );
if ( flvstamp <= limitX )
if ( !pool.containsKey( position ) ) loadFrame( position );
position ++;
}
while ( flvstamp <= limitX && position < frames );
}
else
{
do
{
flvstamp = flvstamps.get( position );
if ( flvstamp >= limitX )
if ( !pool.containsKey( position ) ) loadFrame( position );
position --;
}
while ( flvstamp >= limitX && position > -1 );
}
}
/**
* Calculates frame index related to a timestamp
* @param stampX timestamp
* @return index of frame related to timestamp
*/
public void giveFrames ( double stampX , ArrayList < RtmpPacket > framesX )
{
// System.out.println( System.currentTimeMillis( ) + " " + type + " FrameBuffer.giveFrames " + stampX + " finished: " + finished );
if ( active && !finished )
{
if ( forward )
{
// getting frames from pool
do
{
lastStamp = flvstamps.get( lastPosition );
if ( lastStamp <= stampX )
{
RtmpPacket packet = pool.remove( lastPosition++ );
if ( packet != null ) framesX.add( packet );
//else System.out.println( "No packet in buffer pool: " + ( lastPosition - 1 ) );
}
if ( lastPosition == frames ) finished = true;
}
while ( lastStamp <= stampX && !finished );
}
else
{
// getting frames from pool
do
{
lastStamp = flvstamps.get( lastPosition );
if ( lastStamp >= stampX )
{
RtmpPacket packet = pool.remove( lastPosition-- );
if ( packet != null ) framesX.add( packet );
//else System.out.println( "No packet in buffer pool: " + ( lastPosition + 1 ) );
}
if ( lastPosition == -1 ) finished = true;
}
while ( lastStamp >= stampX && !finished );
}
}
}
/**
* Checkign buffer underrun, if needed, buffer
**/
public void step ( )
{
// System.out.println( System.currentTimeMillis( ) + " " + type + " FrameBuffer.step " );
if ( forward )
{
if ( lastStamp >= bufferLimit - 1000 )
{
bufferLimit = lastStamp + bufferSize;
bufferFrames( bufferLimit );
}
}
else
{
if ( lastStamp <= bufferLimit + 1000 )
{
bufferLimit = lastStamp + bufferSize;
bufferFrames( bufferLimit );
}
}
}
}