net.sf.fmj.media.rtp.JitterBuffer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fmj Show documentation
Show all versions of fmj Show documentation
Freedom for Media in Java
package net.sf.fmj.media.rtp;
import java.util.concurrent.locks.*;
import javax.media.*;
/**
* Implements an RTP packet queue and the storage-related functionality of a
* jitter buffer for the purposes of {@link RTPSourceStream}. The effect of a
* complete jitter buffer is achieved through the combined use of
* JitterBuffer and JitterBufferBehaviour.
*
* @author Lyubomir Marinov
*/
class JitterBuffer
{
/**
* The capacity of this instance in terms of the maximum number of
* Buffers that it may contain.
*/
private int capacity;
/**
* The Condition which is used for synchronization purposes instead
* of synchronizing a block on this instance because the latter is not
* flexible enough for the thread complexity of JitterBuffer.
*/
final Condition condition;
/**
* The Buffers of this JitterBuffer which may contain
* valid media data to be read out of this instance (referred to as
* "fill") or may represent preallocated Buffer instances
* for the purposes of reducing the effects of allocation and garbage
* collection (referred to as "free"). The storage is of a
* circular nature with the first "fill" at index {@link #offset}
* and the number of "fill" equal to {@link #length}.
*/
private Buffer[] elements;
/**
* The number of "fill" Buffers in {@link #elements}.
*/
private int length;
/**
* The Lock which is used for synchronization purposes instead of
* synchronizing a block on this instance because the latter is not flexible
* enough for the thread complexity of JitterBuffer.
*/
final Lock lock;
/**
* The index in {@link #elements} of the Buffer, if any, which has
* been retrieved from this queue and has not been returned yet.
*/
private int locked;
/**
* The index in {@link #elements} of the first "fill"
* Buffer.
*/
private int offset;
/**
* Initializes a new JitterBuffer instance with a specific capacity
* of Buffers.
*
* @param capacity the capacity of the new instance in terms of number of
* Buffers
*/
public JitterBuffer(int capacity)
{
if (capacity < 1)
throw new IllegalArgumentException("capacity");
elements = new Buffer[capacity];
for (int i = 0; i < elements.length; i++)
elements[i] = new Buffer();
this.capacity = capacity;
length = 0;
locked = -1;
offset = 0;
lock = new ReentrantLock();
condition = lock.newCondition();
}
/**
* Inserts buffer in its proper place in this queue according
* to its sequence number. The elements are always kept in ascending
* order by sequence number.
*
* TODO: Check for duplicate packets
*
* @param buffer the Buffer to insert in this queue
* @see #insert(Buffer)
*/
public void addPkt(Buffer buffer)
{
assertLocked(buffer);
if (noMoreFree())
throw new IllegalStateException("noMoreFree");
long firstSN = getFirstSeq();
long lastSN = getLastSeq();
long bufferSN = buffer.getSequenceNumber();
if (firstSN == Buffer.SEQUENCE_UNKNOWN
&& lastSN == Buffer.SEQUENCE_UNKNOWN)
append(buffer);
else if (bufferSN < firstSN)
prepend(buffer);
else if (firstSN < bufferSN && bufferSN < lastSN)
insert(buffer);
else if (bufferSN > lastSN)
append(buffer);
else //only if (bufferSN == firstSN) || (bufferSN == lastSN)?
returnFree(buffer);
locked = -1;
}
/**
* Adds buffer to the end of this queue.
*
* @param buffer the Buffer to be added to the end of this
* queue
*/
private void append(Buffer buffer)
{
int index = (offset + length) % capacity;
if (index != locked)
{
elements[locked] = elements[index];
elements[index] = buffer;
}
length++;
}
/**
* Asserts that a Buffer has been retrieved from this
* JitterBuffer and has not been returned yet.
*
* @throws IllegalStateException if no Buffer has been retrieved
* from this JitterBuffer and has not been returned yet
*/
private void assertLocked(Buffer buffer)
throws IllegalStateException
{
if (locked == -1)
{
throw new IllegalStateException(
"No Buffer has been retrieved from this JitterBuffer"
+ " and has not been returned yet.");
}
if (buffer != elements[locked])
throw new IllegalArgumentException("buffer");
}
/**
* Asserts that no Buffer has been retrieved from this
* JitterBuffer and has not been returned yet.
*
* @throws IllegalStateException if a Buffer has been retrieved
* from this JitterBuffer and has not been returned yet
*/
private void assertNotLocked()
throws IllegalStateException
{
if (locked != -1)
{
throw new IllegalStateException(
"A Buffer has been retrieved from this JitterBuffer"
+ " and has not been returned yet.");
}
}
void dropFill(int index)
{
assertNotLocked();
if ((index < 0) || (index >= length))
throw new IndexOutOfBoundsException(Integer.toString(index));
index = (offset + index) % capacity;
Buffer buffer = elements[index];
if (index == offset)
offset = (offset + 1) % capacity;
else
{
int end = (offset + length - 1) % capacity;
if (index != end)
{
while (index != offset)
{
int i = index - 1;
if (i < 0)
i = capacity - 1;
elements[index] = elements[i];
index = i;
}
elements[index] = buffer;
offset = (offset + 1) % capacity;
}
}
length--;
locked = index;
returnFree(buffer);
}
/**
* Removes the first element (the one with the least sequence number)
* from fill and releases it to be reused (adds it to
* free)
*/
public void dropFirstFill()
{
returnFree(getFill());
}
/**
* Determines whether there are "fill" Buffers in this
* queue.
*
* @return true if there are "fill" Buffers in
* this queue; otherwise, false
*/
boolean fillNotEmpty()
{
return (getFillCount() != 0);
}
/**
* Determines whether there are "free" Buffers in this
* queue.
*
* @return true if there are "free" Buffers in
* this queue; otherwise, false
*/
boolean freeNotEmpty()
{
return (getFreeCount() != 0);
}
/**
* Gets the capacity in (RTP) packets of this queue/jitter buffer.
*
* @return the capacity in (RTP) packets of this queue/jitter buffer
*/
public int getCapacity()
{
int capacity;
lock.lock();
try
{
capacity = this.capacity;
}
finally
{
lock.unlock();
}
return capacity;
}
/**
* Pops the element/Buffer at the head of this queue which contains
* valid media data.
*
* @return the element/Buffer at the head of this queue which
* contains valid media data
*/
public Buffer getFill()
{
assertNotLocked();
if (noMoreFill())
throw new IllegalStateException("noMoreFill");
int index = offset;
Buffer buffer = elements[index];
offset = (offset + 1) % capacity;
length--;
locked = index;
return buffer;
}
public Buffer getFill(int index)
{
if ((index < 0) || (index >= length))
throw new IndexOutOfBoundsException(Integer.toString(index));
return elements[(offset + index) % capacity];
}
/**
* Gets the number of "fill" Buffers in this queue.
*
* @return the number of "fill" Buffers in this queue
*/
public int getFillCount()
{
int length;
lock.lock();
try
{
length = this.length;
}
finally
{
lock.unlock();
}
return length;
}
/**
* Gets the sequence number of the element/Buffer at the head
* of this queue or Buffer.SEQUENCE_UNKNOWN if this queue is empty.
*
* @return the sequence number of the element/Buffer at the
* head of this queue or Buffer.SEQUENCE_UNKNOWN if this queue is
* empty.
*/
public long getFirstSeq()
{
return
(length == 0)
? Buffer.SEQUENCE_UNKNOWN
: elements[offset].getSequenceNumber();
}
/**
* Retrieves a "free" Buffers from this queue.
*
* @return a "free" Buffer from this queue
*/
public Buffer getFree()
{
assertNotLocked();
if (noMoreFree())
throw new IllegalStateException("noMoreFree");
int index = (offset + length) % capacity;
Buffer buffer = elements[index];
locked = index;
return buffer;
}
/**
* Gets the number of "free" Buffers in this queue.
*
* @return the number of "free" Buffers in this queue
*/
public int getFreeCount()
{
return (capacity - length);
}
/**
* Gets the sequence number of the element/Buffer at the tail
* of this queue or Buffer.SEQUENCE_UNKNOWN if this queue is empty.
*
* @return the sequence number of the element/Buffer at the tail
* of this queue or Buffer.SEQUENCE_UNKNOWN if this queue is empty.
*/
public long getLastSeq()
{
return
(length == 0)
? Buffer.SEQUENCE_UNKNOWN
: elements[(offset + length - 1) % capacity]
.getSequenceNumber();
}
/**
* Inserts buffer in the correct place in the queue, so that
* the order is preserved. The order is by ascending sequence numbers.
*
* Note: This could potentially be slow, since all the elements 'bigger'
* than buffer are moved.
*
* @param buffer the Buffer to insert
*/
private void insert(Buffer buffer)
{
int i = offset;
int end = (offset + length) % capacity;
long bufferSN = buffer.getSequenceNumber();
while (i != end)
{
if (elements[i].getSequenceNumber() > bufferSN)
break;
if (++i >= capacity)
i = 0;
}
if (i == offset)
prepend(buffer);
else if (i == end)
append(buffer);
else
{
elements[locked] = elements[end];
for (int j = end; j != i;)
{
int k = j - 1;
if (k < 0)
k = capacity - 1;
elements[j] = elements[k];
j = k;
}
elements[i] = buffer;
length++;
}
}
/**
* Determines whether there are no more "fill"
* elements/Buffers in this queue.
*
* @return true if there are no more "fill"
* elements/Buffers in this queue; otherwise, false
*/
boolean noMoreFill()
{
return (getFillCount() == 0);
}
/**
* Determines whether there are no more "free"
* elements/Buffers in this queue.
*
* @return true if there are no more "free"
* elements/Buffers in this queue; otherwise, false
*/
boolean noMoreFree()
{
return (getFreeCount() == 0);
}
/**
* Adds buffer to the beginning of this queue.
*
* @param buffer the Buffer to add to the beginning of this
* queue
*/
private void prepend(Buffer buffer)
{
int index = offset - 1;
if (index < 0)
index = capacity - 1;
if (index != locked)
{
elements[locked] = elements[index];
elements[index] = buffer;
}
offset = index;
length++;
}
/**
* Returns (releases) buffer to the free queue.
*
* @param buffer the Buffer to return
*/
public void returnFree(Buffer buffer)
{
assertLocked(buffer);
locked = -1;
}
/**
* Sets the capacity of this instance in terms of the maximum number of
* Buffers that it may contain.
*
* @param capacity the capacity of this instance in terms of the maximum
* number of Buffers that it may contain
*/
public void setCapacity(int capacity)
{
assertNotLocked();
if (capacity < 1)
throw new IllegalArgumentException("capacity");
if (this.capacity == capacity)
return;
Buffer[] elements = new Buffer[capacity];
while (getFillCount() > capacity)
dropFirstFill();
int length = Math.min(getFillCount(), capacity);
for (int i = 0; i < length; i++)
elements[i] = getFill(i);
for (int i = length; i < capacity; i++)
elements[i] = new Buffer();
this.capacity = capacity;
this.elements = elements;
this.length = length;
this.offset = 0;
}
}