仅仅播放摄像头拍摄内容(修改版)
package org.bruce.myown_product; import java.awt.Component; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.Panel; import javax.media.CaptureDeviceInfo; import javax.media.CaptureDeviceManager; import javax.media.Manager; import javax.media.MediaLocator; import javax.media.Player; import javax.media.protocol.DataSource; import javax.swing.JFrame; import javax.swing.JPanel; public class PlayCapturedVideo extends JPanel { private static final long serialVersionUID = -8040938476462674297L; // 本地摄像头的注册信息(通过JMF.exe获得) String url = "vfw:Microsoft WDM Image Capture (Win32):0"; // 获取的摄像头信息 private CaptureDeviceInfo captureDeviceInfo = null; // 从获取的摄像头信息中提取的摄像头地址 private MediaLocator mediaLocator = null; private DataSource datasource = null; public static Player player = null; // 构造函数! public PlayCapturedVideo() { Panel editor = new Panel(); editor.setPreferredSize(new Dimension()); editor.setLayout(null); editor.setLayout(new GridLayout(1, 3)); // CaptureDeviceInfo: 捕获设备信息 captureDeviceInfo = CaptureDeviceManager.getDevice(url); // MediaLocator: 媒体定位器。根据捕获设备信息(是一份设备的列表) 获取 媒体定位器 mediaLocator = captureDeviceInfo.getLocator(); try { datasource = Manager.createDataSource(mediaLocator); // 根据媒体定位器创建一个数据源 player = Manager.createRealizedPlayer(datasource); player.start(); // 调用 player.start() 以后就可以用那个一个可视的组件来呈现摄像头拍摄的景象了! Component comp = null; if ((comp = player.getVisualComponent()) != null) { this.add(comp); // 这个 this 继承了 Applet(相当于一个顶级容器) } } catch (Exception b) { b.printStackTrace(); } } public static void main(String[] args) { JFrame jf = new JFrame("用于呈现摄像头拍摄景象"); PlayCapturedVideo pcv = new PlayCapturedVideo(); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.add(pcv); jf.pack(); jf.setResizable(false); jf.setLocation(200, 200); jf.setVisible(true); } }
播放+录制代码(修改版)
package org.bruce.myown_product; import java.awt.BorderLayout; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.util.Vector; import javax.media.CaptureDeviceInfo; import javax.media.CaptureDeviceManager; import javax.media.DataSink; import javax.media.Format; import javax.media.Manager; import javax.media.MediaLocator; import javax.media.Player; import javax.media.Processor; import javax.media.ProcessorModel; import javax.media.control.FormatControl; import javax.media.format.VideoFormat; import javax.media.protocol.DataSource; import javax.media.protocol.FileTypeDescriptor; import javax.media.protocol.SourceCloneable; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; // 这是我自己改写的一个示例,实现了简单的录制和再次录制功能! public class RecordVideo extends JFrame { private static final long serialVersionUID = -2570548473293856129L; private CaptureDeviceInfo captureDevice; private MediaLocator mediaLocator; private Component visualComponent; private Processor processor; private Player player; private DataSource source; private DataSource cloneableSource; private DataSource clonedDataSource; private DataSink dataSink; private JPanel videoPanel = new JPanel(new BorderLayout()); private JPanel controlPanel = new JPanel(); private JPanel contentPane; private JButton btnStart; private JButton btnStop; private JFileChooser fileChooser = new JFileChooser(); public RecordVideo() { this.setTitle("视频采集软件"); contentPane = (JPanel) this.getContentPane(); btnStart = new JButton("开始采集"); btnStart.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { start(); } }); btnStop = new JButton("停止采集"); btnStop.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { stop(); } }); btnStop.setEnabled(false); controlPanel.add(btnStart); controlPanel.add(btnStop); contentPane.add("South", controlPanel); this.captureDevice = this.getCaptureDeviceInfo(); try { mediaLocator = captureDevice.getLocator(); source = Manager.createDataSource(mediaLocator); // 在调用 createCloneableDataSource(source) 方法以后,上句代码中的 source 引用不再能被使用。 cloneableSource = Manager.createCloneableDataSource(source); player = Manager.createRealizedPlayer(cloneableSource); player.start(); } catch (Exception e) { this.processException(e); } visualComponent = player.getVisualComponent(); if (visualComponent != null) { videoPanel.add(visualComponent); } contentPane.add("North", videoPanel); // 打包使整个界面显得紧凑! this.pack(); // 注释掉这句代码的话,窗口就缩成一个小布丁儿了! this.setResizable(false); this.setDefaultCloseOperation(EXIT_ON_CLOSE); this.setLocationRelativeTo(null); this.setVisible(true); } private void start() { // ①第一个参数 clonedDataSource = ((SourceCloneable)cloneableSource).createClone(); // ②第二个参数 FormatControl formatControl = (FormatControl) player.getControl("javax.media.control.FormatControl"); Format defaultFormat = formatControl.getFormat(); // defaultFormat的值为: MJPG, 640x480, FrameRate=15.0, Length=921600 0 extra bytes // ③第三个参数 FileTypeDescriptor outputFileType = new FileTypeDescriptor(FileTypeDescriptor.QUICKTIME); //.QUICKTIME ProcessorModel processorModel = new ProcessorModel(clonedDataSource, new Format[] { defaultFormat }, outputFileType); // DataSource 可能为 mixed, 故用数组 try { processor = Manager.createRealizedProcessor(processorModel); } catch (Exception e) { this.processException(e); } String locatorString = this.getLocatorString(); //locatorString 表示存储录像数据的文件的路径 if (locatorString == null) { return; } MediaLocator dest = new MediaLocator(locatorString); DataSource outputDataSource = processor.getDataOutput(); try { // 本地文件传输 dataSink = Manager.createDataSink(outputDataSource, dest); dataSink.open(); dataSink.start(); } catch (Exception e) { this.processException(e); } processor.start(); btnStart.setEnabled(false); btnStop.setEnabled(true); } private void stop() { processor.close(); processor.deallocate(); // 必须在processor 关闭并调用这个方法以后,录像文件才能保存下来 dataSink.close(); processor = null; // 清空 processor,回收无用的内存! btnStop.setEnabled(false); btnStart.setEnabled(true); } private void processException(Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(this, e.toString(), "错误", JOptionPane.ERROR_MESSAGE); System.exit(0); } private String getLocatorString() { if (JFileChooser.APPROVE_OPTION != fileChooser.showSaveDialog(this)) { return null; } File file = fileChooser.getSelectedFile(); if (file == null) { return null; } System.out.println(file); // C:/Documents and Settings/Administrator/桌面/123 String locatorString = file.getAbsolutePath(); System.out.println(locatorString); // C:/Documents and Settings/Administrator/桌面/123 if (!locatorString.endsWith(".QUICKTIME")) { // 没有后缀的话就程式化地加上 .QUICKTIME 的后缀! locatorString += ".QUICKTIME"; } locatorString = "file://" + locatorString; System.out.println(locatorString); // file://C:/Documents and Settings/Administrator/桌面/123.QUICKTIME return locatorString; } @SuppressWarnings("unchecked") private CaptureDeviceInfo getCaptureDeviceInfo() { // 以下是原来的代码,害惨我了,老是找不到我的摄像头,原来是因为我的摄像头输出不属于 RGB 格式! // Format videoFormat = new VideoFormat(VideoFormat.RGB); Format videoFormat = new VideoFormat(VideoFormat.MJPG); // 在改成 MJPG 后顺利找到我的视频设备 Vector<CaptureDeviceInfo> deviceList = CaptureDeviceManager.getDeviceList(videoFormat); if (deviceList.size() < 1) { JOptionPane.showMessageDialog(this, "未检测到视频输入设备!", "错误", JOptionPane.ERROR_MESSAGE); System.exit(0); } String[] deviceNames = new String[deviceList.size()]; for (int i = 0; i < deviceList.size(); i++) { deviceNames[i] = deviceList.get(i).getName(); } String deviceName = (String) JOptionPane.showInputDialog(this, "请选择视频输入设备", "请选择", JOptionPane.QUESTION_MESSAGE, null, deviceNames, deviceNames[0]); if (deviceName == null) { System.exit(0); } CaptureDeviceInfo captureDevice = null; for (int i = 0; i < deviceList.size(); i++) { captureDevice = deviceList.get(i); if (deviceName.equals(captureDevice.getName())) { return captureDevice; } } return null; } public static void main(String[] args) { new RecordVideo(); } }
播放+录制+照相代码(简化版)
package org.bruce.most_valuable; import java.applet.Applet; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.FileDialog; import java.awt.Frame; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Image; import java.awt.Panel; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.media.Buffer; import javax.media.CaptureDeviceInfo; import javax.media.CaptureDeviceManager; import javax.media.ConfigureCompleteEvent; import javax.media.ControllerEvent; import javax.media.ControllerListener; import javax.media.DataSink; import javax.media.Manager; import javax.media.MediaLocator; import javax.media.Player; import javax.media.Processor; import javax.media.RealizeCompleteEvent; import javax.media.control.FrameGrabbingControl; import javax.media.format.VideoFormat; import javax.media.protocol.DataSource; import javax.media.protocol.FileTypeDescriptor; import javax.media.protocol.SourceCloneable; import javax.media.util.BufferToImage; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; public class WebCamSwingSimplified extends Applet implements ActionListener { private static final long serialVersionUID = -8040938476462674297L; // 本地摄像头的注册信息(通过JMF.exe获得) String url = "vfw:Microsoft WDM Image Capture (Win32):0"; // 获取的摄像头信息 private CaptureDeviceInfo captureDeviceInfo = null; // 从获取的摄像头信息中提取的摄像头地址 private MediaLocator mediaLocator = null; // 原始的数据源 private DataSource dataSource = null; // 由原始数据源转变成的,可以被克隆的数据源 private DataSource cloneableDataSource = null; // 由可以克隆的数据源 cloneableDataSource 克隆出来的 clonedDataSource private DataSource clonedDataSource = null; // 用来播放的 player public static Player player = null; // 处理录制的视频的 Processor private Processor processor = null; // 保存录制数据的数据池(datasink: 数据接收装置; 数据接收器) private DataSink dataSink = null; // StateHelper 是处理 Player 或 Processor 的同步状态转换的一个很有用的类。 private StateHelper stateHelper = null; private ImagePanel imagePanel = null; // buffer 用于截取实时图像的缓冲区 private Buffer buffer = null; // 将 buffer 转换为图像的“中间变量” private BufferToImage buffer2image = null; // 拍下来的照片赋给img private Image img = null; // 保存图片的宽度和高度 private int imgWidth = 320; private int imgHeight = 240; // 拍照按钮 private JButton capture = null; // 保存照片按钮 private JButton save = null; // 录像按钮 private JButton recordVideo = null; // 停止录像按钮 private JButton quitRecord = null; /* * 此 WebCamSwing 函数完成的功能: 创建一个对话框。 将摄像头的物理地址转化为电脑能够识别的 medialocator(媒体定位器) , * 并且通过 medialocator 产生一个数据源 dataSource , 通过这个 dataSource 数据源生成 player 用来播放视频。 * 然后将数据源进行处理,产生一个能够被克隆的 cloneableDataSource, * 再这个 cloneableDataSource 进行克隆,产生一个 clonedDataSource 数据源, 用于后面的 processor 录制视频 */ public WebCamSwingSimplified() { Panel editor = new Panel(); editor.setPreferredSize(new Dimension()); editor.setLayout(null); editor.setLayout(new GridLayout(1, 3)); // ImagePanel 初始化 imagePanel = new ImagePanel(); // 各 Button 的初始化 capture = new JButton("拍照"); capture.addActionListener(this); save = new JButton("保存照片"); save.addActionListener(this); recordVideo = new JButton("开始录像"); recordVideo.addActionListener(this); quitRecord = new JButton("停止录像"); quitRecord.addActionListener(this); captureDeviceInfo = CaptureDeviceManager.getDevice(url); // CaptureDeviceInfo: 捕获设备信息 // 根据捕获设备信息(是一份设备的列表) 获取 媒体定位器 mediaLocator = captureDeviceInfo.getLocator(); // MediaLocator: 媒体定位器 try { dataSource = Manager.createDataSource(mediaLocator); // 根据媒体定位器创建一个数据源 cloneableDataSource = Manager.createCloneableDataSource(dataSource); clonedDataSource = ((SourceCloneable)cloneableDataSource).createClone(); player = Manager.createRealizedPlayer(cloneableDataSource); player.start(); // 调用 player.start() 以后就可以用那个一个可视的组件来呈现摄像头拍摄的景象了! Component comp = null; if ((comp = player.getVisualComponent()) != null) { this.add(comp); // 这个 this 继承了 Applet(相当于一个顶级容器) } } catch (Exception e) { e.printStackTrace(); } // 将4个按钮添加到主界面上 JPanel rightJPanel = new JPanel(); rightJPanel.setLayout(new BorderLayout()); JPanel buttonJPanel = new JPanel(new GridLayout(1, 4)); // 1行4列 buttonJPanel.add(capture); buttonJPanel.add(save); buttonJPanel.add(recordVideo); buttonJPanel.add(quitRecord); rightJPanel.add(buttonJPanel, BorderLayout.NORTH); rightJPanel.add(imagePanel, BorderLayout.SOUTH); // 这个是干什么用的?是拍照时看效果用的 this.add(rightJPanel); } /* * 点击拍照 * ActionEvent: 指示发生了组件定义的动作的语义事件。 当特定于组件的动作(比如 * 被按下)发生时, 由组件(比如 Button)生成此高级别事件。 事件被传递给每一个 ActionListener 对象, 这些对 * 象是使用组件的 addActionListener 方法注册的,用以接收这类事件。 注:要使用键盘在 Button 上触发 * ActionEvent,请使用空格键。 */ public void actionPerformed(ActionEvent e) { // 捕获 ActionEvent,将其转化为 JComponent 类,应该转化为是被单击的 “按钮” 的意思 // 这种写法比用 setActionCommand("capture") 要节省代码(当然是在按钮比较少的 时候)! JComponent c = (JComponent) e.getSource(); // ① 拍照 if (c == capture) { // 取得对 Player 的控制(反射机制的典型应用) FrameGrabbingControl fgc = (FrameGrabbingControl)player .getControl("javax.media.control.FrameGrabbingControl"); buffer = fgc.grabFrame(); // System.out.println(buffer.getFormat()); buffer2image = new BufferToImage((VideoFormat) buffer.getFormat()); img = buffer2image.createImage(buffer); // 在 imagePanel 上显示拍下来的样照,由用户决定是否保存下来 imagePanel.setImage(img); } // 如果还没有拍照就保存的话,弹出对话框进行提醒! else if (c == save && img == null) { JOptionPane.showMessageDialog(null, "请先拍照再保存"); } // ②保存照片(弹出提示窗口让用户输入保存图片的路径) else if (c == save && img != null) { Frame savePhotoFrame = new Frame("保存"); FileDialog savePhotoFileDialog = new FileDialog(savePhotoFrame, "保存图像文件", FileDialog.SAVE); savePhotoFileDialog.setFile("*.GIF"); // 用于设置保存图片的格式 savePhotoFileDialog.setVisible(true); String savepath = savePhotoFileDialog.getDirectory(); String savename = savePhotoFileDialog.getFile(); // ImageIO.write是将图片保存到上面已经写好的路径里,并且用上面的savename命名 try { ImageIO.write((RenderedImage)img, "GIF", new File(savepath + savename)); } catch (IOException e1) { e1.printStackTrace(); } img = null; // 将 img 清空(回收所占的内存资源) } // ③ 如果单击了“开始录像”按钮,但是processor不为空,那么就弹出提示表明正在录像 else if (c == recordVideo && processor != null) { JOptionPane.showMessageDialog(null, "正在录像!"); } // ④ 如果单击了“开始录像”按钮,且processor为空,那么就开始录像 else if (c == recordVideo && processor == null) { Frame saveVideoFrame = new Frame("保存视频文件"); FileDialog saveVideoFileDialog = new FileDialog(saveVideoFrame, "保存视频文件", FileDialog.SAVE); saveVideoFileDialog.setFile("*.QUICKTIME"); saveVideoFileDialog.setVisible(true); String videoFileSavePath = saveVideoFileDialog.getDirectory(); String videoFileSaveName = saveVideoFileDialog.getFile(); // 如果输入了保存名字(没有点取消)的话,才可运行 if (videoFileSaveName != null) { videoFileSavePath = videoFileSavePath.replace("//", "/"); try { // player 用 cloneableDataSource 数据源,processor 用 clonedDataSource 的数据源 processor = Manager.createProcessor(clonedDataSource); stateHelper = new StateHelper(processor); } catch (Exception e1) { e1.printStackTrace(); System.exit(-1); } // configure the processor, 让 processor 进入 configured 状态 if (!stateHelper.configure(10000)) { System.out.println("configure wrong!"); System.exit(-1); } // 设置存储录像文件的格式为 .QUICKTIME processor.setContentDescriptor(new FileTypeDescriptor(FileTypeDescriptor.QUICKTIME)); // realize the processor,让 processor 进入 realized 状态 if (!stateHelper.realize(10000)) { System.out.println("realize wrong!"); System.exit(-1); } // 取得 processor 的输出,并且启动 processor // DataSource 被送进 processor 处理,输出地时候还是以 DataSource 的类型输出! DataSource outSource = processor.getDataOutput(); // 替换前:file:///C:/Documents and Settings/Administrator/桌面/12312.QUICKTIME // 替换后:file:///C:/Documents and Settings/Administrator/桌面/12312.QUICKTIME String destinationString = "file:///" + videoFileSavePath + videoFileSaveName; // MediaLocator 类的两个构造方法: // ①MediaLocator(java.lang.String locatorString) // ②MediaLocator(java.net.URL url) MediaLocator destination = new MediaLocator(destinationString); processor.start(); try { dataSink = Manager.createDataSink(outSource, destination); dataSink.open(); dataSink.start(); } catch (Exception e1) { e1.printStackTrace(); System.exit(-1); } } } // 停止录像(如果还没开始录像就单击这个按钮的话,弹出提示框提示)! else if (c == quitRecord && processor == null) { JOptionPane.showMessageDialog(null, "请先单击录像才能停止!"); } // ⑤停止录像 else if (c == quitRecord && processor != null) { // 如果要是能够连续录像,关键在于两点: // 1、重新设置 clonedDataSource ,我认为在 clonedDataSource 被调用后 ,clonedDataSource被改变了 // 2、清空 processor processor.close(); processor.deallocate(); dataSink.close(); clonedDataSource = ((SourceCloneable) cloneableDataSource).createClone(); processor = null; } } public class StateHelper implements ControllerListener { Player xplayer = null; boolean configured = false; boolean realized = false; // boolean prefetched = false; // boolean eom = false;// End of media. boolean failed = false; boolean closed = false; public StateHelper(Player p) { xplayer = p; p.addControllerListener(this); } public boolean configure(int timeOutMillis) { // RealizeCompleteEvent 发生了的话使 ce 事件与之比较,若相等,那么 realized 为 true。 /* * 监听 ConfigureCompleteEvent 和 ConfigureCompleteEvent 事件的发生。 * 如 ConfigureCompleteEvent 事件发生,那么就会赋给 configured 为 ture, 使得 public * boolean configure 方法中的 while (!configured && !failed){} 这个循环退出。 */ long startTime = System.currentTimeMillis(); synchronized (this) { if (xplayer instanceof Processor) { ((Processor) xplayer).configure(); } else { return false; } while (!configured && !failed) { try { wait(timeOutMillis); } catch (InterruptedException ex) { ex.printStackTrace(); } if (System.currentTimeMillis() - startTime > timeOutMillis) { break; } } } return configured; } public boolean realize(int timeOutMillis) { long startTime = System.currentTimeMillis(); synchronized (this) { xplayer.realize(); while (!realized && !failed) { try { wait(timeOutMillis); } catch (InterruptedException ie) { ie.printStackTrace(); } if (System.currentTimeMillis() - startTime > timeOutMillis) { break; } } } return realized; } public synchronized void controllerUpdate(ControllerEvent ce) { if (ce instanceof RealizeCompleteEvent) { realized = true; } else if (ce instanceof ConfigureCompleteEvent) { configured = true; } else { return; } notifyAll(); } } // 显而易见,ImagePanel 是用于呈现 Image 的 Panel!实践检验我错了! // 把 Panel 改成 JPanel 妈的竟然显示不出来了! class ImagePanel extends Panel { private static final long serialVersionUID = -5371206459688057671L; private Image ImagePanelImg = null; public ImagePanel() { setLayout(null); setSize(imgWidth, imgHeight); } public void setImage(Image img) { this.ImagePanelImg = img; this.setVisible(true); repaint(); } public void update(Graphics graphics) { if (ImagePanelImg != null) { graphics.drawImage(ImagePanelImg, 0, 0, this); } } } public static void main(String[] args) { JFrame jf = new JFrame("拍照程序"); WebCamSwingSimplified cf = new WebCamSwingSimplified(); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.add("Center", cf); jf.pack(); jf.setResizable(false); jf.setVisible(true); } }