package com.silicondust.libhdhomerun;
public final class HDHomerun_Video {
public static class hdhomerun_video_stats_t {
public long packet_count;
public long network_error_count;
public long transport_error_count;
public long sequence_error_count;
public long overflow_error_count;
public hdhomerun_video_stats_t() {
reset();
}
public void reset() {
packet_count = 0;
network_error_count = 0;
transport_error_count = 0;
sequence_error_count = 0;
overflow_error_count = 0;
}
};
private static final int TS_PACKET_SIZE = 188;
private static final int VIDEO_DATA_PACKET_SIZE = (188 * 7);
public static final int VIDEO_DATA_BUFFER_SIZE_1S = (20000000 / 8);
private static final int VIDEO_RTP_DATA_PACKET_SIZE = ((188 * 7) + 12);
private Mutex mLock = null;
private HDHomerun_Debug dbg = null;
HDHomerun_Sock mSock = null;
private int mMulticast_ip = 0;
private volatile int mHead = 0;
private volatile int mTail = 0;
private byte[] mBuffer = null;
private int mBuffer_size = 0;
private int mAdvance = 0;
private VideoThread mThread = null;
private volatile boolean mTerminate = false;
private volatile long mPacket_count = 0;
private volatile long mTransport_error_count = 0;
private volatile long mNetwork_error_count = 0;
private volatile long mSequence_error_count = 0;
private volatile long mOverflow_error_count = 0;
private volatile long mRTP_Sequence = 0;
private volatile byte[] mSequence = new byte[0x2000];
public HDHomerun_Video(int listen_port, int buffer_size, boolean allowReuse, HDHomerun_Debug dbg) throws Exception
{
this.dbg = dbg;
mSock = null;
mLock = new Mutex();
/* Reset sequence tracking. */
flush();
/* Buffer size. */
mBuffer_size = (buffer_size / VIDEO_DATA_PACKET_SIZE) * VIDEO_DATA_PACKET_SIZE;
if (mBuffer_size == 0) {
dbg.printf(String.format("hdhomerun_video_create: invalid buffer size (%d bytes)\n", mBuffer_size));
ConstructorError();
return;
}
mBuffer_size += VIDEO_DATA_PACKET_SIZE;
/* Create buffer. */
mBuffer = new byte[mBuffer_size];
if (null == mBuffer) {
dbg.printf(String.format("hdhomerun_video_create: failed to allocate buffer (%d bytes)\n", mBuffer_size));
ConstructorError();
return;
}
/* Create socket. */
/* Expand socket buffer size. */
int rx_size = 1024 * 1024;
try {
mSock = new HDHomerun_Sock(0, listen_port, 0, rx_size, allowReuse);
if (mSock == null) {
dbg.printf("hdhomerun_video_create: failed to allocate socket\n");
ConstructorError();
return;
}
}
catch (Exception e) {
dbg.printf(String.format("hdhomerun_video_create: failed to bind socket (port %d)\n", listen_port));
ConstructorError();
return;
}
/* Start thread. */
mThread = new VideoThread();
if (mThread == null) {
dbg.printf("hdhomerun_video_create: failed to start thread\n");
ConstructorError();
return;
}
mThread.start();
/* Success. */
}
private void ConstructorError() throws Exception {
if (mSock != null) {
mSock.destroy();
}
if (null != mBuffer) {
mBuffer = null;
}
throw new Exception();
}
public void destroy()
{
mTerminate = true;
try {
mThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mSock.destroy();
mBuffer = null;
}
public HDHomerun_Sock get_sock()
{
return mSock;
}
int get_local_port()
{
int port = mSock.getsockname_port();
if (port == 0) {
dbg.printf(String.format("hdhomerun_video_get_local_port: getsockname failed (%s)\n", mSock.getlasterror()));
return 0;
}
return port;
}
public int join_multicast_group(int multicast_ip)
{
if (multicast_ip != 0) {
leave_multicast_group();
}
if (!mSock.addGroup(mMulticast_ip)) {
dbg.printf(String.format("hdhomerun_video_join_multicast_group: setsockopt failed (%s)\n", mSock.getlasterror()));
return -1;
}
mMulticast_ip = multicast_ip;
return 1;
}
public int leave_multicast_group()
{
if (mMulticast_ip == 0) {
return 1;
}
if (!mSock.dropGroup(mMulticast_ip)) {
dbg.printf(String.format("hdhomerun_video_leave_multicast_group: setsockopt failed (%s)\n", mSock.getlasterror()));
}
mMulticast_ip = 0;
return 1;
}
private void stats_ts_pkt(byte[] ptr, int start)
{
short packet_identifier = (short) (((short)(ptr[start + 1] & 0x1F) << 8) | (short)ptr[start + 2]);
if (packet_identifier == 0x1FFF) {
return;
}
boolean transport_error = (ptr[start + 1] >> 7) != 0;
if (transport_error) {
mTransport_error_count++;
mSequence[packet_identifier] = (byte) 0xFF;
return;
}
byte btyeSequence = (byte) (ptr[start + 3] & 0x0F);
byte previous_sequence = mSequence[packet_identifier];
mSequence[packet_identifier] = btyeSequence;
if (previous_sequence == 0xFF) {
return;
}
if (btyeSequence == ((previous_sequence + 1) & 0x0F)) {
return;
}
if (btyeSequence == previous_sequence) {
return;
}
mSequence_error_count++;
}
private void parse_rtp(HDHomerun_Pkt pkt)
{
pkt.posIndex += 2;
long iRTP_sequence = pkt.read_u16();
pkt.posIndex += 8;
long previous_rtp_sequence = mRTP_Sequence;
mRTP_Sequence = iRTP_sequence;
/* Initial case - first packet received. */
if (previous_rtp_sequence == 0xFFFFFFFF) {
return;
}
/* Normal case - next sequence number. */
if (iRTP_sequence == ((previous_rtp_sequence + 1) & 0xFFFF)) {
return;
}
/* Error case - sequence missed. */
mNetwork_error_count++;
/* Restart pid sequence check after packet loss. */
for (int i = 0; i < 0x2000; i++) {
mSequence[i] = (byte) 0xFF;
}
}
private class VideoThread extends Thread
{
public void run()
{
HDHomerun_Pkt pkt = new HDHomerun_Pkt();
while (!mTerminate) {
pkt.reset();
/* Receive. */
int[] length = new int[1];
length[0] = VIDEO_RTP_DATA_PACKET_SIZE;
if (!mSock.recv(pkt.buffer, pkt.endIndex, length, 25)) {
continue;
}
pkt.endIndex += length[0];
if (length[0] == VIDEO_RTP_DATA_PACKET_SIZE) {
parse_rtp(pkt);
length[0] = pkt.endIndex - pkt.posIndex;
}
if (length[0] != VIDEO_DATA_PACKET_SIZE) {
/* Data received but not valid - ignore. */
continue;
}
mLock.lock();
/* Store in ring buffer. */
int head = mHead;
for(int i = 0; i < length[0]; ++i)
mBuffer[head + i] = pkt.buffer[pkt.posIndex + i];
/* Stats. */
mPacket_count++;
stats_ts_pkt(mBuffer, head + (TS_PACKET_SIZE * 0));
stats_ts_pkt(mBuffer, head + (TS_PACKET_SIZE * 1));
stats_ts_pkt(mBuffer, head + (TS_PACKET_SIZE * 2));
stats_ts_pkt(mBuffer, head + (TS_PACKET_SIZE * 3));
stats_ts_pkt(mBuffer, head + (TS_PACKET_SIZE * 4));
stats_ts_pkt(mBuffer, head + (TS_PACKET_SIZE * 5));
stats_ts_pkt(mBuffer, head + (TS_PACKET_SIZE * 6));
/* Calculate new head. */
head += length[0];
if (head >= mBuffer_size) {
head -= mBuffer_size;
}
/* Check for buffer overflow. */
if (head == mTail) {
mOverflow_error_count++;
mLock.unlock();
continue;
}
mHead = head;
mLock.unlock();
}
}
}
byte[] recv(int max_size, int[] pactual_size)
{
mLock.lock();
int head = mHead;
int tail = mTail;
if (mAdvance > 0) {
tail += mAdvance;
if (tail >= mBuffer_size) {
tail -= mBuffer_size;
}
mTail = tail;
}
if (head == tail) {
mAdvance = 0;
pactual_size[0]= 0;
mLock.unlock();
return null;
}
int size = (max_size / VIDEO_DATA_PACKET_SIZE) * VIDEO_DATA_PACKET_SIZE;
if (size == 0) {
mAdvance = 0;
pactual_size[0] = 0;
mLock.unlock();
return null;
}
int avail;
if (head > tail) {
avail = head - tail;
} else {
avail = mBuffer_size - tail;
}
if (size > avail) {
size = avail;
}
mAdvance = size;
pactual_size[0] = size;
int result_len = mBuffer_size - mTail;
byte[] result = new byte[result_len];
for(int i = 0; i < result_len; ++i)
result[i] = mBuffer[mTail + i];
mLock.unlock();
return result;
}
public void flush()
{
mLock.lock();
mTail = mHead;
mAdvance = 0;
mRTP_Sequence = 0xFFFFFFFF;
for (int i = 0; i < 0x2000; i++) {
mSequence[i] = (byte) 0xFF;
}
mPacket_count = 0;
mTransport_error_count = 0;
mNetwork_error_count = 0;
mSequence_error_count = 0;
mOverflow_error_count = 0;
mLock.unlock();
}
public void debug_print_stats()
{
hdhomerun_video_stats_t stats = new hdhomerun_video_stats_t();
get_stats(stats);
dbg.printf(String.format("video sock: pkt=%d net=%d te=%d miss=%d drop=%d\n",
stats.packet_count, stats.network_error_count,
stats.transport_error_count, stats.sequence_error_count,
stats.overflow_error_count)
);
}
public void get_stats(hdhomerun_video_stats_t stats)
{
stats.reset();
mLock.lock();
stats.packet_count = mPacket_count;
stats.network_error_count = mNetwork_error_count;
stats.transport_error_count = mTransport_error_count;
stats.sequence_error_count = mSequence_error_count;
stats.overflow_error_count = mOverflow_error_count;
mLock.unlock();
}
}