一、概述
在实现视频通信前我们先了解一下UDP通信协议,UDP即用户数据报协议,UDP 只在 IP 的数据报服务之上增加了很少一点的功能,这就是复用和分用的功能以及差错检测的功能。有区别与TCP/IP。UDP通信特点如下:
(1)、无连接性,即收发数据时不需要建立连接,由此可以大幅减少数据收发的延迟
(2)、UDP 使用尽最大努力交付。即不保证可靠交付,因此主机不需要维护复杂的连接状态表
(3)、UDP 没有拥塞控制。因此网络出现的拥塞不会使源主机的发送速率降低。这对某些实时应用是很重要的。很多的实时应用要求源主机以恒定的速率发送数据,并且允许在网络出现拥塞时丢失一部分数据,但却不允许数据有太大的时延。UDP 协议正好适合这种要求。
二、为何使用UDP
上述提到UDP通信协议虽然不可靠,但数据收发的延迟低,同时无拥塞控制。对于视频这种流媒体来说,用户体验时比较靠前的因素,即尽可能保持视频不会产生卡顿。因此UDP通信协议是视频传输首选的协议。
三、实现步骤
1、启动摄像头
对于Java视频通信来说,首先是需要捕捉图像,因此需要使用Java调用电脑自带的摄像头。对于Java调用摄像头有很多驱动,如openCV等,这里主要使用webcam包进行讲解。启动摄像头时我们需要一个窗口面板来展示画面。
2、图像编码
若要将图像发送出去,需要将图像转化为数据流,并通过UDP协议发送。因此需要将图像进行编码。
简单来说一个30帧的视频在每秒钟会刷新30张图片,而视频通信只需要将每秒中的30张图片编码并发送即可,因此视频编码就可以简化为图片编码,再通过线程休眠进行速率的控制,即可完成对视频编码。
图片编码即需要获取整张图片所有的像素值,通过BufferImage缓冲后将图片的编码信息一次性发送出去。实现代码如下。
import com.github.sarxos.webcam.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
//创建视频通话界面,并实现webapp中提供的接口
public class CamStart extends JFrame implements Runnable, WebcamListener, WindowListener, Thread.UncaughtExceptionHandler, ItemListener, WebcamDiscoveryListener {
JFrame videoFrame;
WebcamPanel northPanel;//创建WebcamPanel即需要通过一个面板显示画面
JPanel southPanel;
static Webcam webcam = null;//创建Webcam对象
WebcamPanel panel = null;
WebcamPicker picker = null;//创建摄像头选择器
String windowTitle;
public CamStart(String windowTitle){
this.windowTitle = windowTitle;
}
public void setWindow(){
videoFrame = new JFrame(windowTitle);
videoFrame.setSize(640 , 640);
videoFrame.setResizable(false);
videoFrame.addWindowListener(this);
videoFrame.add(northPanel , BorderLayout.NORTH);
southPanel = new JPanel();
southPanel.setPreferredSize(new Dimension(640 , 120));
JButton jb1 = new JButton("接通");
JButton jb2 = new JButton("挂断");
jb1.setBounds(30 , 100 , 70 , 30);
jb2.setBounds(120 , 100 , 70 , 30);
southPanel.add(jb1);
southPanel.add(jb2);
videoFrame.add(southPanel , BorderLayout.SOUTH);
videoFrame.setLocationRelativeTo(null);
videoFrame.setVisible(true);
videoFrame.setDefaultCloseOperation(1);
jb1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
}
});
jb2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
shutDownTip();
}
});
}
public void getCam(){
//查找本地机的摄像头并启动
Webcam.addDiscoveryListener(this);
picker = new WebcamPicker();
webcam = picker.getSelectedWebcam();
//未找到摄像头的情况
if (webcam == null) {
JFrame tip = new JFrame("提示");
tip.setSize(300 , 200);
tip.setLocationRelativeTo(null);
JPanel panel1 = new JPanel();
panel1.setPreferredSize(new Dimension(220 , 100));
JLabel label = new JLabel("未在您的设备上找到摄像头,请检查连接情况");
label.setSize(200 , 90);
panel1.add(label);
JPanel panel2 = new JPanel();
panel2.setPreferredSize(new Dimension(220 , 35));
JButton jb1 = new JButton("确定");
jb1.setBounds(70 , 40 , 70 , 30);
panel2.add(jb1);
tip.add(panel1 , BorderLayout.NORTH);
tip.add(panel2 , BorderLayout.SOUTH);
tip.setDefaultCloseOperation(1);
tip.setVisible(true);
tip.setResizable(false);
jb1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
tip.dispose();
tip.setDefaultCloseOperation(1);
}
});
}
//设置画面尺寸
webcam.setViewSize(WebcamResolution.VGA.getSize());
webcam.addWebcamListener(CamStart.this);
northPanel = new WebcamPanel(webcam , false);
northPanel.setFPSDisplayed(true);
Thread t = new Thread(){
@Override
public void run() {
northPanel.start();
}
};
t.setDaemon(true);
t.setUncaughtExceptionHandler(this);
t.start();
new Thread(() -> {
imageFlush();
}).start();
}
//设置视频通信
public void imageFlush(){
while (true) {
try {
Thread.sleep(33);
//缓冲图像,避免画面加载错误
BufferedImage image = webcam.getImage();
OutputStream out = socket.getOutputStream();
DataOutputStream flue = new DataOutputStream(out);
//将图像进行编码
//获取图像宽高
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
//将图像尺寸发送,以便于接收端解码
flue.writeInt(imageWidth);
flue.writeInt(imageHeight);
//根据图像宽高逐个获取图像像素
for (int i = 0; i < imageWidth; i++) {
for (int j = 0; j < imageHeight; j++) {
flue.writeInt(image.getRGB(i , j));
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
public void init(){
socketInit();
getCam();
setWindow();
}
public void shutDownTip(){
JFrame tipWindow = new JFrame();
tipWindow.setTitle("系统提示");
tipWindow.setSize(300 , 200);
tipWindow.setLocationRelativeTo(null);
JPanel panel1 = new JPanel();
panel1.setPreferredSize(new Dimension(220 , 100));
JLabel label = new JLabel("是否结束当前视频通信?");
label.setSize(200 , 90);
panel1.add(label);
JPanel panel2 = new JPanel();
panel2.setPreferredSize(new Dimension(220 , 35));
JButton jb1 = new JButton("是");
jb1.setBounds(70 , 40 , 70 , 30);
JButton jb2 = new JButton("否");
jb1.setBounds(160 , 40 , 20 , 30);
panel2.add(jb1);
panel2.add(jb2);
tipWindow.add(panel1 , BorderLayout.NORTH);
tipWindow.add(panel2 , BorderLayout.SOUTH);
tipWindow.setDefaultCloseOperation(1);
tipWindow.setVisible(true);
tipWindow.setResizable(false);
jb1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
webcam.close();
videoFrame.dispose();
tipWindow.dispose();
videoFrame.setDefaultCloseOperation(1);
tipWindow.setDefaultCloseOperation(1);
}
});
jb2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
tipWindow.dispose();
tipWindow.setDefaultCloseOperation(1);
}
});
}
@Override
public void webcamFound(WebcamDiscoveryEvent webcamDiscoveryEvent) {
}
@Override
public void webcamGone(WebcamDiscoveryEvent webcamDiscoveryEvent) {
}
@Override
public void webcamOpen(WebcamEvent webcamEvent) {
}
@Override
public void webcamClosed(WebcamEvent webcamEvent) {
}
@Override
public void webcamDisposed(WebcamEvent webcamEvent) {
}
@Override
public void webcamImageObtained(WebcamEvent webcamEvent) {
}
@Override
public void itemStateChanged(ItemEvent e) {
}
@Override
public void windowOpened(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
webcam.close();
this.setDefaultCloseOperation(1);
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowDeactivated(WindowEvent e) {
}
@Override
public void uncaughtException(Thread t, Throwable e) {
}
}
四、总结
本篇主要讲解UDP下的视频通信,目前项目还在改进中,博客将持续更新