package server;
/**
* Created : Mar 6, 2017
*
* @author pquiring
*/
import java.io.*;
import java.net.*;
import javaforce.*;
import javaforce.media.*;
public class Session {
public void start(Socket socket) {
this.socket = socket;
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
window = new Window();
readThread = new ReadThread();
readThread.start();
}
});
}
private Socket socket;
private MediaDecoder decoder; //ffmpeg decoder
private long frameCount;
private long audioCount;
private final Object countLock = new Object();
private boolean playing, eof;
private Window window;
private Thread playThread;
private ReadThread readThread;
private int frame;
private static boolean inuse = false;
private static Object lock = new Object();
private AudioBuffer audio_buffer;
private VideoBuffer video_buffer;
final int audio_bufsiz = 1024;
final int chs = 2; //currently all formats are converted to stereo
float fps;
int width, height;
int new_width, new_height;
boolean resizeVideo;
Object sizeLock = new Object();
//buffer size in seconds
//too small can cause problems
//too large causes resizes to take a long time to take effect
//the problem is that some video files are not interlaced very well
final int buffer_seconds = 4;
final int pre_buffer_seconds = 2;
public class ReadThread extends Thread implements MediaIO {
private InputStream is;
int m_in; //input data
int m_out[] = new int[4]; //output data
public void run() {
if (inuse) {
try {socket.close();} catch (Exception e) {}
JFLog.log("Connection denied : Projector in use!");
return;
} else {
inuse = true;
JFLog.log("Connection from:" + socket.getRemoteSocketAddress());
}
frameCount = 0;
audioCount = 0;
audio_buffer = new AudioBuffer(44100, chs, buffer_seconds);
video_buffer = null;
width = -1;
height = -1;
resizeVideo = false;
eof = false;
try {
is = socket.getInputStream();
decoder = new MediaDecoder();
if (decoder == null) throw new Exception("Unable to allocate decoder");
if (!decoder.start(this, -1, -1, chs, 44100, false)) throw new Exception("Unable to start decoder");
long mediaLength = decoder.getDuration();
JFLog.log("Duration=" + mediaLength);
fps = decoder.getFrameRate();
JFLog.log("FPS=" + fps);
width = window.getWidth();
height = window.getHeight();
JFLog.log("size=" + width + "x" + height);
decoder.resize(width, height);
video_buffer = new VideoBuffer(width, height, buffer_seconds * (int)fps);
playThread = new PlayAudioVideoThread();
playThread.start();
JFLog.log("Video Bit Rate=" + decoder.getVideoBitRate());
JFLog.log("Audio Bit Rate=" + decoder.getAudioBitRate());
// int avBitRate = decoder.getVideoBitRate() + decoder.getAudioBitRate();
// if (avBitRate == 0) avBitRate = 64000;
playing = true;
while (playing) {
if (resizeVideo) {
synchronized(sizeLock) {
width = new_width;
height = new_height;
decoder.resize(width, height);
resizeVideo = false;
}
}
switch (decoder.read()) {
case MediaCoder.AUDIO_FRAME: //audio packet read
short audio[] = decoder.getAudio();
audio_buffer.add(audio, 0, audio.length);
synchronized(lock) {
lock.notify();
}
break;
case MediaCoder.VIDEO_FRAME: //video packet read
int video[] = decoder.getVideo();
JFImage img = video_buffer.getNewFrame();
if (img != null) {
if ((img.getWidth() != width) || (img.getHeight() != height)) {
img.setSize(width, height);
}
img.putPixels(video, 0, 0, width, height, 0);
video_buffer.freeNewFrame();
} else {
JFLog.log("Warning : VideoBuffer overflow");
}
synchronized(lock) {
lock.notify();
}
break;
case MediaCoder.END_FRAME:
eof = true;
playing = false;
break;
}
}
} catch (Exception e) {
JFLog.log(e);
}
try {
playThread.join();
} catch (Exception e) {
JFLog.log(e);
}
playThread = null;
audio_buffer = null;
video_buffer = null;
if (window != null) {
window.dispose();
window = null;
}
decoder = null;
inuse = false;
}
public int read(MediaCoder coder, byte data[]) {
int read = 0;
try {
read = is.read(data, 0, data.length);
} catch (Exception e) {
JFLog.log(e);
playing = false;
return read;
}
if (read == -1) {
playing = false;
read = 0;
}
return read;
}
public int write(MediaCoder coder, byte data[]) {
return 0;
}
public long seek(MediaCoder coder, long pos, int how) {
switch (how) {
case MediaCoder.SEEK_SET: return pos;
case MediaCoder.SEEK_CUR: return pos;
case MediaCoder.SEEK_END: return pos;
}
return 0;
}
}
public class PlayAudioVideoThread extends Thread {
public void run() {
AudioOutput output = new AudioOutput();
output.start(chs, 44100, 16, audio_bufsiz * 2 /*bytes*/, "<default>");
short samples[] = new short[audio_bufsiz];
for(int a=0;a<2;a++) output.write(samples); //prime audio output
while (playing) {
synchronized(lock) {
try {lock.wait();} catch (Exception e) {}
}
while (audio_buffer.size() >= audio_bufsiz) {
audio_buffer.get(samples, 0, audio_bufsiz);
output.write(samples);
synchronized(countLock) { audioCount += audio_bufsiz; };
}
while (video_buffer.size() > 0) {
JFImage img = video_buffer.getNextFrame();
synchronized(countLock) { frameCount++; }
if (img != null) {
String txt = "Frame:" + frame++ + ":" + video_buffer.size();
img.getGraphics().drawBytes(txt.getBytes(), 0, txt.length(), 0, 25);
window.setImage(img);
video_buffer.freeNextFrame();
}
}
}
output.stop();
}
}
public class PlayAudioOnlyThread extends Thread {
public void run() {
AudioOutput output = new AudioOutput();
output.start(chs, 44100, 16, audio_bufsiz * 2 /*bytes*/, "<default>");
short samples[] = new short[audio_bufsiz];
for(int a=0;a<2;a++) output.write(samples); //prime output
while (playing) {
synchronized(lock) {
try {lock.wait();} catch (Exception e) {}
}
while (audio_buffer.size() >= audio_bufsiz) {
audio_buffer.get(samples, 0, audio_bufsiz);
output.write(samples);
synchronized(countLock) { audioCount += audio_bufsiz; };
}
}
output.stop();
}
}
}