/**
*
* @author pquiring
*
* Created : Sept 25, 2013
*/
import java.io.*;
import javax.swing.*;
import javaforce.*;
import javaforce.media.*;
public class MainPanel extends javax.swing.JPanel implements MediaIO {
public static String version = "0.4";
/**
* Creates new form MainPanel
*/
public MainPanel() {
initComponents();
listCameras();
listAudioDevices();
}
/**
* This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The
* content of this method is always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
buttonGroup1 = new javax.swing.ButtonGroup();
buttonGroup2 = new javax.swing.ButtonGroup();
jLabel1 = new javax.swing.JLabel();
cameraDevices = new javax.swing.JComboBox();
audio = new javax.swing.JRadioButton();
noaudio = new javax.swing.JRadioButton();
timeLapse = new javax.swing.JRadioButton();
start = new javax.swing.JButton();
freq = new javax.swing.JComboBox();
mono = new javax.swing.JRadioButton();
stereo = new javax.swing.JRadioButton();
seconds = new javax.swing.JSpinner();
jLabel2 = new javax.swing.JLabel();
jLabel3 = new javax.swing.JLabel();
fps = new javax.swing.JSpinner();
jLabel4 = new javax.swing.JLabel();
audioDevices = new javax.swing.JComboBox();
preview = new javax.swing.JCheckBox();
previewVideo = new javax.swing.JLabel();
previewAudio = new javax.swing.JProgressBar();
jLabel5 = new javax.swing.JLabel();
vBitRate = new javax.swing.JComboBox();
jLabel6 = new javax.swing.JLabel();
aBitRate = new javax.swing.JComboBox();
jLabel7 = new javax.swing.JLabel();
stopMotion = new javax.swing.JRadioButton();
next = new javax.swing.JButton();
jLabel8 = new javax.swing.JLabel();
jLabel9 = new javax.swing.JLabel();
jLabel1.setText("Camera Device");
buttonGroup1.add(audio);
audio.setSelected(true);
audio.setText("Audio");
buttonGroup1.add(noaudio);
noaudio.setText("No Audio");
buttonGroup1.add(timeLapse);
timeLapse.setText("Time Lapse");
start.setText("Start");
start.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
startActionPerformed(evt);
}
});
freq.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "44100", "22050", "11025", "8000" }));
buttonGroup2.add(mono);
mono.setSelected(true);
mono.setText("mono");
buttonGroup2.add(stereo);
stereo.setText("stereo");
seconds.setModel(new javax.swing.SpinnerNumberModel(60, 1, 3600, 1));
jLabel2.setText("Seconds delay per frame");
jLabel3.setText("FPS");
fps.setModel(new javax.swing.SpinnerNumberModel(24, 1, 60, 1));
jLabel4.setText("Audio Device");
preview.setSelected(true);
preview.setText("Preview");
preview.setToolTipText("Preview (can reduce frame rate)");
previewVideo.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
previewVideo.setBorder(javax.swing.BorderFactory.createEtchedBorder());
previewAudio.setOrientation(1);
previewAudio.setToolTipText("");
jLabel5.setText("Video Quality");
vBitRate.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "8M", "6M", "4M", "2M", "1M", "800k", "400k", "200k" }));
vBitRate.setSelectedIndex(4);
vBitRate.setToolTipText("");
jLabel6.setText("Audio Quality");
aBitRate.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "256k", "192k", "128k", "64k", "32k" }));
aBitRate.setSelectedIndex(2);
aBitRate.setToolTipText("");
jLabel7.setText("Freq:");
buttonGroup1.add(stopMotion);
stopMotion.setText("Stop Motion (user advance frame)");
next.setText("Next Frame");
next.setEnabled(false);
next.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
nextActionPerformed(evt);
}
});
jLabel8.setText("bits/sec");
jLabel9.setText("bits/sec");
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(jLabel6)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(aBitRate, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jLabel9)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(start))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jLabel1)
.addComponent(jLabel4))
.addGap(13, 13, 13)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(cameraDevices, 0, 206, Short.MAX_VALUE)
.addComponent(audioDevices, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(audio)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jLabel7))
.addComponent(noaudio))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(freq, javax.swing.GroupLayout.PREFERRED_SIZE, 93, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addComponent(mono)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(stereo))
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel3)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(fps, javax.swing.GroupLayout.PREFERRED_SIZE, 56, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addComponent(stopMotion)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(next))
.addGroup(layout.createSequentialGroup()
.addComponent(timeLapse)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(seconds, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jLabel2))
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel5)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(vBitRate, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jLabel8)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(preview)
.addGroup(layout.createSequentialGroup()
.addComponent(previewVideo, javax.swing.GroupLayout.PREFERRED_SIZE, 216, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(previewAudio, javax.swing.GroupLayout.PREFERRED_SIZE, 9, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel1)
.addComponent(cameraDevices, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(preview))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel4)
.addComponent(audioDevices, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(fps, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel3))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(audio)
.addComponent(freq, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(mono)
.addComponent(stereo)
.addComponent(jLabel7))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(noaudio)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(timeLapse)
.addComponent(seconds, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel2))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(next)
.addComponent(stopMotion))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel5)
.addComponent(vBitRate, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel8))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel6)
.addComponent(aBitRate, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(start)
.addComponent(jLabel9))
.addGap(5, 5, 5))
.addGroup(layout.createSequentialGroup()
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(previewAudio, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(previewVideo, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
.addContainerGap(12, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
private void startActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_startActionPerformed
start();
}//GEN-LAST:event_startActionPerformed
private void nextActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextActionPerformed
synchronized(doNext) {
doNext.notify();
}
}//GEN-LAST:event_nextActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JComboBox aBitRate;
private javax.swing.JRadioButton audio;
private javax.swing.JComboBox audioDevices;
private javax.swing.ButtonGroup buttonGroup1;
private javax.swing.ButtonGroup buttonGroup2;
private javax.swing.JComboBox cameraDevices;
private javax.swing.JSpinner fps;
private javax.swing.JComboBox freq;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JLabel jLabel4;
private javax.swing.JLabel jLabel5;
private javax.swing.JLabel jLabel6;
private javax.swing.JLabel jLabel7;
private javax.swing.JLabel jLabel8;
private javax.swing.JLabel jLabel9;
private javax.swing.JRadioButton mono;
private javax.swing.JButton next;
private javax.swing.JRadioButton noaudio;
private javax.swing.JCheckBox preview;
private javax.swing.JProgressBar previewAudio;
private javax.swing.JLabel previewVideo;
private javax.swing.JSpinner seconds;
private javax.swing.JButton start;
private javax.swing.JRadioButton stereo;
private javax.swing.JRadioButton stopMotion;
private javax.swing.JRadioButton timeLapse;
private javax.swing.JComboBox vBitRate;
// End of variables declaration//GEN-END:variables
private Camera camera;
private AudioInput mic;
private RandomAccessFile raf;
private boolean active = false;
private boolean working = false;
private Object doNext = new Object();
public void listCameras() {
camera = new Camera();
camera.init();
String list[] = camera.listDevices();
camera.uninit();
camera = null;
cameraDevices.removeAllItems();
for(int a=0;a<list.length;a++) {
cameraDevices.addItem(list[a]);
}
}
public void listAudioDevices() {
mic = new AudioInput();
String list[] = mic.listDevices();
audioDevices.removeAllItems();
for(int a=0;a<list.length;a++) {
audioDevices.addItem(list[a]);
}
}
public void setState(boolean state) {
start.setEnabled(state);
if (state) start.setText("Start");
cameraDevices.setEnabled(state);
audioDevices.setEnabled(state);
audio.setEnabled(state);
noaudio.setEnabled(state);
timeLapse.setEnabled(state);
seconds.setEnabled(state);
mono.setEnabled(state);
stereo.setEnabled(state);
freq.setEnabled(state);
fps.setEnabled(state);
preview.setEnabled(state);
}
public void start() {
if (working) {
active = false;
start.setText("Stopping");
if (stopMotion.isSelected()) {
synchronized(doNext) {
doNext.notify();
}
}
return;
}
working = true;
setState(false);
new Worker().start();
}
public void failed(String msg) {
JF.showError("Error", msg);
setState(true);
working = false;
}
public int amplitude(short sams[]) {
short peak = 0;
int len = sams.length;
for(int a=0;a<len;a++) {
if (sams[a] > peak) peak = sams[a];
}
return (peak * 100) / 32768;
}
public void swapEndian(byte in[], short out[]) {
int p = 0;
for(int a=0;a<out.length;a++) {
out[a] = in[p++];
out[a] <<= 8;
out[a] += in[p++] & 0xff;
}
}
public class Worker extends Thread {
public void run() {
int idx = cameraDevices.getSelectedIndex();
if (idx == -1) {
failed("Please select a camera device");
return;
}
setState(false);
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.setMultiSelectionEnabled(false);
chooser.setCurrentDirectory(new File(JF.getUserPath() + "/Videos"));
javax.swing.filechooser.FileFilter ffAVI = new javax.swing.filechooser.FileFilter() {
public boolean accept(File file) {
if (file.isDirectory()) return true;
if (file.getName().endsWith(".avi")) return true;
return false;
}
public String getDescription() {
return "AVI (*.avi)";
}
public String toString() {
return ".avi";
}
};
chooser.addChoosableFileFilter(ffAVI);
chooser.setFileFilter(ffAVI);
if (chooser.showSaveDialog(MainPanel.this) != JFileChooser.APPROVE_OPTION) {
setState(true);
working = false;
return;
}
String fn = chooser.getSelectedFile().getAbsolutePath();
String fnlc = fn.toLowerCase();
if ((!fnlc.endsWith(".avi"))) {
// javax.swing.filechooser.FileFilter ff = chooser.getFileFilter();
// fn += ff.toString();
fn += ".avi";
}
boolean doAudio = audio.isSelected();
int audioRate = JF.atoi((String)freq.getSelectedItem());
if (audioRate < 8000 || audioRate > 44100) audioRate = 44100;
int chs = mono.isSelected() ? 1 : 2;
int frameRate = (Integer)fps.getValue();
int secondsDelay = (Integer)seconds.getValue();
int samples = audioRate * chs / frameRate;
try {
raf = new RandomAccessFile(fn, "rw");
raf.setLength(0);
} catch (Exception e) {
JFLog.log(e);
failed("Unable to create output file");
return;
}
camera = new Camera();
camera.init();
camera.listDevices();
if (!camera.start(cameraDevices.getSelectedIndex(), 640, 480)) {
failed("Unable to start recording from camera");
return;
}
MediaEncoder encoder = new MediaEncoder();
encoder.setAudioBitRate(getAudioBitRate());
encoder.setVideoBitRate(getVideoBitRate());
int width = camera.getWidth();
int height = camera.getHeight();
JFLog.log("size=" + width + "," + height);
JFLog.log("frameRate=" + frameRate);
JFLog.log("audioRate=" + audioRate + ",chs=" + chs);
if (!encoder.start(MainPanel.this, width, height, frameRate, chs
, audioRate, "avi", true, doAudio))
{
failed("Unable to create output file");
return;
}
if (doAudio) {
mic = new AudioInput();
if (!mic.start(chs, audioRate, 16, samples * 2, (String)audioDevices.getSelectedItem())) {
failed("Unable to start recording audio");
return;
}
}
active = true;
start.setText("Stop");
start.setEnabled(true);
byte sams8[] = new byte[samples*2];
short sams16[] = new short[samples];
boolean skip_frame = false;
//sync video/audio
boolean ready;
do{
ready = camera.getFrame() != null;
if (doAudio) {
while (mic.read(sams8)) {}
}
} while (active && !ready);
double current = System.currentTimeMillis();
double delay = 1000.0 / frameRate;
int px[] = null;
boolean doPreview = preview.isSelected();
int previewWidth = previewVideo.getWidth();
int previewHeight = previewVideo.getHeight();
JFImage previewImg = new JFImage(previewWidth, previewHeight);
JFImage videoImg = new JFImage(width, height);
Icon orgPreview = previewVideo.getIcon();
if (stopMotion.isSelected()) {
next.setEnabled(true);
}
while (active) {
if (!skip_frame) {
px = camera.getFrame();
if (px != null && doPreview) {
videoImg.putPixels(px, 0,0,width,height,0);
previewImg.putJFImageScale(videoImg, 0, 0, previewWidth, previewHeight);
previewVideo.setIcon(previewImg);
previewVideo.repaint();
}
} else {
skip_frame = false;
}
if (px == null) {
JFLog.log("no video frame:sleeping 5ms");
JF.sleep(5);
continue;
}
encoder.addVideo(px);
if (doAudio) {
while (mic.read(sams8)) {
swapEndian(sams8, sams16);
encoder.addAudio(sams16);
if (doPreview) {
previewAudio.setValue(amplitude(sams16));
}
}
}
if (stopMotion.isSelected()) {
synchronized(doNext) {
try {doNext.wait();} catch (Exception e) {}
}
continue;
}
if (timeLapse.isSelected()) {
for(int a=0;a<secondsDelay;a++) {
JF.sleep(1000);
if (!active) break;
}
} else {
double now = System.currentTimeMillis();
int sleep = (int)(delay - (now - current));
if (sleep > 0) {
JF.sleep(sleep);
} else {
JFLog.log("sleep <= 0");
skip_frame = true; //system too slow
}
current += delay;
}
}
encoder.stop();
camera.stop();
camera.uninit();
camera = null;
if (doAudio) mic.stop();
try {
raf.close();
} catch (Exception e) {
JFLog.log(e);
}
working = false;
if (stopMotion.isSelected()) next.setEnabled(false);
setState(true);
previewVideo.setIcon(orgPreview);
previewAudio.setValue(0);
}
}
public int read(MediaCoder coder, byte[] bytes) {
return 0;
}
public int write(MediaCoder coder, byte[] bytes) {
try {
raf.write(bytes);
return bytes.length;
} catch (Exception e) {
JFLog.log(e);
return 0;
}
}
public long seek(MediaCoder coder, long pos, int how) {
try {
switch (how) {
case MediaCoder.SEEK_SET: break;
case MediaCoder.SEEK_CUR: pos += raf.getFilePointer(); break;
case MediaCoder.SEEK_END: pos += raf.length(); break;
}
raf.seek(pos);
return pos;
} catch (Exception e) {
JFLog.log(e);
}
return 0;
}
private int getRate(String rate) {
int scale = 1;
if (rate.endsWith("M")) {
rate = rate.substring(0, rate.length()-1);
scale = 1024 * 1024;
}
else if (rate.endsWith("k")) {
rate = rate.substring(0, rate.length()-1);
scale = 1024;
}
int value = JF.atoi(rate) * scale;
System.out.println("rate=" + value);
return value;
}
private int getAudioBitRate() {
return getRate((String)aBitRate.getSelectedItem());
}
private int getVideoBitRate() {
return getRate((String)vBitRate.getSelectedItem());
}
}