以下博客会出现小二这个人,我最近就是一直在给他讲代码,但是他真的太笨了!!
- 首先meeting也是在服务器和客户端的基础上,也是基于C/S通信的,这个就不做过多赘述,视频我们将其理解为由一帧一帧的图片组成,因此视频会议就可以理解为在以非常快的速度传输一张一张图片,这个快的速度是我们肉眼不能识别的,因此看不出来这是一张一张图片,那么接下来就好理解了
首先都是需要客户端和服务器完成最基本的通信连接,接下来就是数据传输操作: - 1.获取视频
- 2.将图片转化为二维数组(图像处理的时候我们知道,一张图片就是由二维数组组成)
- 3.发送二维数组
- 4.服务器接收二维数组
- 5.服务器将二维数组重新绘制出来
- 6.在添加一个在屏幕上画笔的功能,客户端可以在客户端这边画线
- 7.将其坐标也发送到服务器端去
- 8.服务器读取画线的数据,画线在服务器界面上
/**
* @仿腾讯会议
* @思路 首先这是建立在C/S通信上面的 还需要调用开源CV包去获取视频,首先建立服务器创建一个端口用于接收客户端的连接
* @服务器端 需要做什么接收来自客户端发送过来的数据 然后进行绘制
* @客户端 通过开源CV库进行电脑视频的获取 所谓视频就是一帧一帧的图片 图片又是一组二维数组 因此视频的发送我们可以理解为是对于
* @1 一帧一帧图片的二维数组进行发送 那么此时我们需要了解怎么将图片转化为二维数组,*对于图片的理解就是由一个一个的像素点组成
* @2 每一个像素点又是相当于一个由三原色所组成的点 因此需要获取图片上每个像素点的颜色组成去存在二维数组里相对应的坐标* *客户
* @3 客户端界面上也需要将摄像头的视频进行绘制出来,这个时候有两种方法一种是根据二维数组进行一个一个点进行绘制,另外一个就是
* @4 根据方法直接绘制图片,这样两种方法图片即可显示在客户端界面上,但是此时根据坐标绘制的图片就会出现卡顿,因此需要加一个图
* @5 片缓冲器,然后就会解决这个问题* *然后就是将图片转化的二维数组发送到服务器端去,发送的时候因为每个像素点都是一个int 因此
* @6 需要用DataOutputStream进行数据的发送,与此同时服务器端也需要用相同的数据流类型进行接收,这样才不会出错,接收过来之后在
* @7 进行绘制*
* @8
* @author lixiaoer
*
*/
public class Client {
//数据流
public static DataInputStream din;
public static DataOutputStream dout;
public boolean clientConnect() {
try {
//创建客户端去连接服务器
Socket cc = new Socket("127.0.0.1",9999);
//创建数据输入输出流
//先建一个输入流在进行包装
InputStream in = cc.getInputStream();
//输出流
OutputStream out = cc.getOutputStream();
//包装
din = new DataInputStream(in);
//包装输出流
dout = new DataOutputStream(out);
//因为将来需要调用输入输出流去发送数据 因此需要定义为全局变量
//程序执行到这证明客户端连接成功
return true;
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//否则返回错误
return false;
}
//客户端主函数
public static void main(String[] args) {
//先创建客户端类的对象
Client cc = new Client();
if(cc.clientConnect()) {
//如果连接成功
DrawClient dc = new DrawClient(din, dout);
dc.drawClientUi();
}
}
}
接下来为视频的获取和图片转化为二维数组并发送,此时需要注意因为要发送视频还要发送线段坐标因此需要两个不同的报文头来告诉服务器端将要发送什么数据,这样方便服务器的接收,确保数据不会出错
//目的是画出客户端的界面 然后视频会在这个界面上面显示
public class DrawClient extends JFrame{
private DataInputStream din;
private DataOutputStream dout;
//int [][] imagearray;
//Client cc;
//设置构造器将参数传进来数据流
public DrawClient(DataInputStream din,DataOutputStream dout) {
this.din = din;
this.dout = dout;
//cc = new Client();
}
//通过这个方法完成客户端界面的绘制
public void drawClientUi() {
this.setSize(500, 500);
this.setTitle("客户端窗口");
this.setLocationRelativeTo(null);
this.setVisible(true);
this.setDefaultCloseOperation(3);
//从这个界面上面获取画笔
Graphics gg = this.getGraphics();
//加鼠标监听器
MMouseListen ml = new MMouseListen(gg, din, dout);
//MouseListen ml = new MouseListen(gg, cc.din,cc.dout);
//鼠标监听器 可以在客户端界面上进行绘制图形
this.addMouseListener(ml);
//if(cc.clientConnect()) {
Webcam webcam = Webcam.getDefault();
webcam.open();
//ImageIO.write(webcam.getImage(), "PNG", new File("hello-world.png"));
//将从摄像头读取到的图片放进图片缓冲区
while(true) {
BufferedImage imag = webcam.getImage();
//将图片画在窗体上
//获取图片的二维数组
int w = imag.getWidth();
int h = imag.getHeight();
//gg.drawImage(imag, 60, 80, null);
int[][] imgarray = toArray(imag);
//绘制二维数组 也就是在绘制图片
BufferedImage im = new BufferedImage(500, 500, BufferedImage.TYPE_INT_BGR);
Graphics g = im.getGraphics();
for(int i = 0;i < imgarray.length;i++) {
for(int j = 0;j<imgarray[i].length;j++) {
Color c = new Color(imgarray[i][j]);
//设置画笔颜色
g.setColor(c);
g.drawLine(i, j, i, j);
}
}
gg.drawImage(im, 50, 50, null);
synchronized (dout) {
sendArray(imgarray,dout);
}
}
//}
}
//将图片转为二维数组
public int[][] toArray(BufferedImage imag){
//在这里将视频图片画在客户端窗口之后 需要将图片传到服务器端 因此需要
//获得图片的二维数组
int w = imag.getWidth();
int h = imag.getHeight();
int[][] imagearray = new int[w][h];
//知道了图片的尺寸 之后需要进行循环将每个像素点上面的值读取出来
//外部循环图片的行
for(int i = 0;i < w;i++) {
//图片的列
for(int j = 0;j < h;j++) {
//获取到图片上次坐标的颜色
imagearray[i][j] = imag.getRGB(i, j);
}
}
return imagearray;
}
//将图片转化为二维数组之后要将二维数组发送到服务器端去
//因为需要发送二维数组 所以必须有数据流
public void sendArray(int[][] imagearray,DataOutputStream dout) {
//发送二维数组
int w = imagearray.length;
int h = imagearray[0].length;
//synchronized (dout) {
try {
dout.writeByte(2);
dout.writeInt(w);
dout.writeInt(h);
for(int i = 0;i<imagearray.length;i++) {
for(int j = 0;j<imagearray[i].length;j++) {
dout.writeInt(imagearray[i][j]);
}
}
dout.flush();
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//}
}
/*
public static void main(String[] args) {
DrawClient dc = new DrawClient();
dc.drawClientUi();
}
*/
}
然后是画线段的坐标传输
public class MMouseListen implements MouseListener,ActionListener{
Graphics gg;
DataInputStream in;
DataOutputStream out;
//ArrayList<Paint> al;
int x1;
int x2;
int y1;
int y2;
int i;
public MMouseListen(Graphics gg,DataInputStream in,DataOutputStream out) {
this.gg = gg;
this.in = in;
this.out = out;
//this.al = al;
}
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
x1 = e.getX();
y1 =e.getY();
}
@Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
synchronized(out){
x2 = e.getX();
y2 = e.getY();
gg.drawLine(x1, y1, x2, y2);
i++;
//将坐标存入队列
//Paint pp = new Paint(x1,y1,x2,y2);
//pp.draw(gg);
//System.out.println("数据存进去" + i + "个");
//al.add(pp);
//在绘制了一条线之后将坐标全部发送给服务器端
//System.out.println(al.size());
try {
out.writeByte(1);//表示传输线段
out.writeInt(x1);
out.writeInt(y1);
out.writeInt(x2);
out.writeInt(y2);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
}
}
需要注意当你发送两种不同的数据时,除了需要添加报文头还需要进行数据传输的保护,因此需要添加synchronized,来保证在数据传输过程中不会被打断出错,小二你明白了吗?