/** Manages the local camera and sends the encoded image to all remote parties. * * @author pquiring */ import java.io.*; import java.util.*; import javaforce.*; import javaforce.jni.*; import javaforce.media.*; import javaforce.voip.*; public class LocalCamera extends Thread implements MediaIO { private volatile boolean active = false; private volatile boolean main_done = false; private Camera camera; private static Codec codec; private static volatile boolean inuse[] = new boolean[6]; private static PhoneLine lines[]; private static LocalCamera thread; private static Object useLock = new Object(); // private RandomAccessFile raf; public static void setCodec(Codec codec) { LocalCamera.codec = codec; } public static Codec getCodec() { return codec; } public static void init(PhoneLine lines[]) { LocalCamera.lines = lines; } public static void enable(int line) { synchronized(useLock) { inuse[line] = true; if (thread == null) { thread = new LocalCamera(); thread.start(); } } } public static void disable(int line) { synchronized(useLock) { if (inuse[line] == false) return; inuse[line] = false; for(int a=0;a<6;a++) { if (inuse[a] == true) return; } } if (thread != null) { thread.cancel(); thread = null; } } public static boolean isRunning() { return thread != null; } public void run() { active = true; main_done = false; try { int[] res = Settings.current.getVideoResolution(); camera = new Camera(); // try { raf = new RandomAccessFile("media_local." + codec.name, "rw"); } catch (Exception e) {} if (!camera.init()) { JF.showError("Error", "Failed to init camera"); return; } String[] devices = camera.listDevices(); if (devices == null || devices.length == 0) { JF.showError("Error", "Failed to find a camera"); return; } int idx = 0; for(int a=0;a<devices.length;a++) { if (devices[a].equals(Settings.current.videoDevice)) { idx = a; break; } } if (!camera.start(idx, PhonePanel.vx, PhonePanel.vy)) { JF.showError("Error", "Failed to start camera"); main_done = true; return; } int cx = camera.getWidth(); int cy = camera.getHeight(); JFLog.log("camera size=" + cx + "x" + cy); localImage = new JFImage(); localImage.setImageSize(PhonePanel.vx, PhonePanel.vy); localCameraSender = new LocalCameraSender(); localCameraSender.start(); JFLog.log("LocalCamera starting"); while (active) { while (active && list.size() > 0) { JF.sleep(10); } JF.sleep(1000 / Settings.current.videoFPS); int[] px = camera.getFrame(); if (px == null) { continue; } JFImage tmp = new JFImage(cx, cy); tmp.putPixels(px, 0, 0, cx, cy, 0); localImage.getGraphics().drawImage(tmp.getImage(), 0, 0, PhonePanel.vx, PhonePanel.vy, null); synchronized(useLock) { for(int a=0;a<6;a++) { if (inuse[a]) { VideoWindow vw = lines[a].videoWindow; if (vw != null) vw.setLocalImage(localImage); } } } if (codec.name.equals("JPEG")) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); localImage.saveJPG(baos); byte[][] packets = rtpJpeg.encode(baos.toByteArray(), PhonePanel.vx, PhonePanel.vy); for (int a = 0; a < packets.length; a++) { list.add(packets[a]); } synchronized (lock) { lock.notify(); } continue; } if (codec.name.startsWith("H263")) { //H263,H263-1998,H263-2000 px = localImage.getPixels(); if (encoder == null) { encoder = new MediaEncoder(); encoder.setFramesPerKeyFrame(5); if (!encoder.start(this, PhonePanel.vx, PhonePanel.vy, 24, -1, -1, "h263", true, false)) { JFLog.log("H263 encoder failed to start"); encoder.stop(); encoder = null; continue; } } encoder.addVideo(px); continue; } if (codec.name.equals("H264")) { px = localImage.getPixels(); if (encoder == null) { encoder = new MediaEncoder(); encoder.setFramesPerKeyFrame(5); if (!encoder.start(this, PhonePanel.vx, PhonePanel.vy, 24, -1, -1, "h264", true, false)) { JFLog.log("H264 encoder failed to start"); encoder.stop(); encoder = null; continue; } } encoder.addVideo(px); continue; } if (codec.name.equals("VP8")) { px = localImage.getPixels(); if (encoder == null) { encoder = new MediaEncoder(); encoder.setFramesPerKeyFrame(5); if (!encoder.start(this, PhonePanel.vx, PhonePanel.vy, 24, -1, -1, "vpx", true, false)) { JFLog.log("VP8 encoder failed to start"); encoder.stop(); encoder = null; continue; } } encoder.addVideo(px); continue; } JFLog.log("err:local camera running without a valid codec"); } camera.stop(); camera.uninit(); if (encoder != null) { encoder.stop(); encoder = null; } while (!localCameraSender.sender_done) { JFLog.log("Waiting for LocalCameraSender to stop"); JF.sleep(500); synchronized (lock) { lock.notify(); } } // try { raf.close(); } catch (Exception e) {} } catch (Exception e) { JFLog.log(e); } JFLog.log("LocalCamera done"); main_done = true; } public void cancel() { active = false; while (!main_done) { JFLog.log("Waiting for LocalCamera to stop"); JF.sleep(500); } } private class LocalCameraSender extends Thread { private volatile boolean sender_done = false; public void run() { try { while (active) { if (list.isEmpty()) { synchronized (lock) { try { lock.wait(); } catch (Exception e) { } } } if (list.isEmpty()) { continue; } byte[] data = list.remove(0); if (data == null) { continue; } synchronized(useLock) { for(int a=0;a<6;a++) { if (!inuse[a]) continue; RTP rtp = lines[a].videoRTP; if (rtp == null) continue; if (!rtp.active) continue; RTPChannel channels[] = (RTPChannel[])rtp.channels.toArray(new RTPChannel[0]); for(int r=0;r<channels.length;r++) { RTPChannel channel = channels[r]; if (channel.stream.type == SDP.Type.video && channel.stream.canSend()) { channel.writeRTP(data, 0, data.length); } } } } } } catch (Exception e) { JFLog.log(e); } JFLog.log("LocalCameraSender done"); sender_done = true; } } private LocalCamera.LocalCameraSender localCameraSender; private JFImage localImage; private RTPJPEG rtpJpeg = new RTPJPEG(); private RTPH263 rtpH263 = new RTPH263(); private RTPH263_1998 rtpH263_1998 = new RTPH263_1998(); private RTPH263_2000 rtpH263_2000 = new RTPH263_2000(); private RTPH264 rtpH264 = new RTPH264(); private RTPVP8 rtpVP8 = new RTPVP8(); private Object lock = new Object(); private Vector<byte[]> list = new Vector<byte[]>(); private MediaEncoder encoder; public int read(MediaCoder coder, byte[] bytes) { return 0; } public int write(MediaCoder coder, byte[] bytes) { int len = bytes.length; byte[][] tmp; if (codec.name.equals("H263")) { // printArray("encoded_h263", bytes, 0, bytes.length); tmp = rtpH263.encode(bytes, PhonePanel.vx, PhonePanel.vy, codec.id); if (tmp != null) { for (int a = 0; a < tmp.length; a++) { list.add(tmp[a]); } } } else if (codec.name.equals("VP8")) { // printArray("encoded_vp8", bytes, 0, bytes.length); // try { raf.write(bytes); } catch (Exception e) {} tmp = rtpVP8.encode(bytes, PhonePanel.vx, PhonePanel.vy, codec.id); if (tmp != null) { for (int a = 0; a < tmp.length; a++) { // printArray("o:rtpVP8:"+a, tmp[a], 0, tmp[a].length); list.add(tmp[a]); } } } else if (codec.name.equals("H264")) { // printArray("encoded_h264", bytes, 0, bytes.length); // try { raf.write(bytes); } catch (Exception e) {} tmp = rtpH264.encode(bytes, PhonePanel.vx, PhonePanel.vy, codec.id); if (tmp != null) { for (int a = 0; a < tmp.length; a++) { // printArray("o:rtpH264:"+a, tmp[a], 0, tmp[a].length); list.add(tmp[a]); } } } else if (codec.name.equals("H263-1998")) { // printArray("encoded_1998", bytes, 0, bytes.length); tmp = rtpH263_1998.encode(bytes, PhonePanel.vx, PhonePanel.vy, codec.id); if (tmp != null) { for (int a = 0; a < tmp.length; a++) { list.add(tmp[a]); } } } else if (codec.name.equals("H263-2000")) { // printArray("encoded_2000", bytes, 0, bytes.length); tmp = rtpH263_2000.encode(bytes, PhonePanel.vx, PhonePanel.vy, codec.id); if (tmp != null) { for (int a = 0; a < tmp.length; a++) { list.add(tmp[a]); } } } synchronized (lock) { lock.notify(); } return len; } public long seek(MediaCoder coder, long l, int i) { return 0; } }