Java 的屏幕广播(基于UDP)
Java的屏幕广播,是基于UDP协议的,user datagram protocal 用户数据报协议,无连接,无顺序,不安全,但是作为发送实时数据还是十分常用的。
整个难点在于要字节制定协议,由于UDP的一个包最大不能超过64K,而一帧屏幕截图(1366*768)是肯定超过64K的,所以我们需要对所截出来的image进行分割发送。
假设我们将一张屏幕截图分割成若干个包发送出去,要构成屏幕广播,就需要不断的截图发包,接收端就肯定需要知道哪几个包是同一张图片,而在这几个包中哪个包是图片的哪一部分,便于恢复成一张完整的图片。
如此就需要一个协议来规定各个包之间的关系。
在下面的程序中,每张图片之间我用时间戳来区别,图片中分割的各个部分用编号来确定,还要加上每张图片所分割的数量,就构造了一个数据报包。
首先的代码是一个工具类,将byte转换成long、int,还有反转
package Boardcast;
* 用于存放byte转换成long,int的工具代码
public class Utils {
public static byte[] long2Byte(long number) {
byte[] b = new byte[8];
for(int i=0; i<8; i++) {
b[i] = (byte) (number>>((8-i-1)*8));
}
return b;
}
* 从offset开始往后的8个字节转换为long数据
public static long byte2Long(byte[] b, int offset) {
long end = 0;
for(int i=0; i<8; i++) {
end = end | ((long)(b[i+offset] & 0xff)<
* 其中的long类型转换一定要加上,如果没加上结果就成了int类型
}
return end;
}
public static byte[] int2Byte(int number) {
byte[] b = new byte[4];
b[0] = (byte) (number >> 24);
b[1] = (byte) (number >> 16);
b[2] = (byte) (number >> 8);
b[3] = (byte)number;
return b;
}
public static int byte2Int(byte[] b, int offset) {
int i3 = (b[0+offset] & 0xFF)<< 24;
int i2 = (b[1+offset] & 0xFF)<< 16;
int i1 = (b[2+offset] & 0xFF)<< 8;
int i0 = b[3+offset] & 0xFF;
return i3 | i2 | i1 | i0;
}
}
下面是广播者,发送截屏后过压缩,将压缩后的数据分割后进行发送
package Boardcast;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
* 广播者,屏幕截图后转换成byte[]格式,过压缩,分割成一个个60K左右的小包发送出去
* 对于同一帧的图片分割出来的小包,有着相同的时间戳,同样的包数量,不同的编号,用于区分
public class Boardcaster {
private DatagramSocket socket;
private Robot robot;
private Rectangle rect;
public static void main(String[] args) {
Boardcaster bc = new Boardcaster();
System.out.println("开始发送数据。。。。");
bc.start();
}
public void start() {
try {
socket = new DatagramSocket(8888);
* 实例化一个Robot,用于抓图
robot = new Robot();
* 设置所抓图片的位置,长宽
rect = new Rectangle(0, 0, 1366, 768);
while(true) {
popDatagramPacket(socket);
System.out.println("发送一帧图片");
}
} catch(Exception e) {
e.printStackTrace();
}
}
* 抓图
public BufferedImage getPrintScreen() {
BufferedImage image = robot.createScreenCapture(rect);
return image;
}
* 抓图,压缩,分割发送
private void popDatagramPacket(DatagramSocket socket) {
try {
* 抓图
BufferedImage image = getPrintScreen();
* 将图写入baos
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", baos);
System.out.println("未压缩的byte[]大小 = " + baos.toByteArray().length);
* 过压缩
byte[] buffer = compressImage(baos.toByteArray());
System.out.println("过压缩的byte[]大小 = " + buffer.length);
* 分割图片并发送出去
cutImageByteAndPost(buffer, socket);
} catch (Exception e) {
e.printStackTrace();
}
}
* 将image的byte[]过压缩,返回压缩后的byte[]
private byte[] compressImage(byte[] buffer) throws Exception {
* 新建一个baos,用于存放转压缩后的byte[]数据
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos);
ZipEntry entry = new ZipEntry("image.jpg");
* 放入条目
zos.putNextEntry(entry);
* 写入数据
zos.write(buffer);
zos.close();
baos.close();
return baos.toByteArray();
}
* 将数据的byte[]分割发送出去,同一帧的图片分割成的小包,有着相同的时间戳,同样的包数量,不同的编号
* 每一个小包,开始8个字节存放时间戳,接下来四个字节存放本帧图片所分割出来的包数量,再放入四个字节的编号
public void cutImageByteAndPost(byte[] src, DatagramSocket socket) {
try {
int len = src.length;
* 分割的包数量
int count = len/60/1024;
if(len > count*60*1024) {
count++;
}
System.out.println("len = " + len + ", count = " + count);
byte[] buffer = new byte[60*1024+8+4+4];
long time = System.nanoTime();
* 发count数量的包
for(int i=0; i
* 如果是最后一个包,就将剩下的所有数据都发送出去
if(i == count-1) {
buffer = new byte[len%(60*1024)+16];
}
* 写入时间戳
System.arraycopy(Utils.long2Byte(time), 0, buffer, 0, 8);
* 写入分割的数量
System.arraycopy(Utils.int2Byte(count), 0, buffer, 8, 4);
* 写入当前的编号,从0开始
System.arraycopy(Utils.int2Byte(i), 0, buffer, 12, 4);
* 写入image内容
System.arraycopy(src, i*60*1024, buffer, 16, buffer.length-16);
System.out.println("i = " + (i) + ", buffer.length = " + buffer.length);
* 发送数据,组装成数据报包
DatagramPacket pack = new DatagramPacket(buffer, buffer.length);
* 设置发送地址、端口