下周终于可以去公司了,感觉假期有点快。阳台吹晚风,用Java以柔性有损传输为例:
- 发送端发送的任何数据包都可以丢弃,不影响接收端的解码。
- 不做发送端编码FEC,不做冗余。
- 发送端不检测丢包不重传,接收端能在很短的时间内收到多少。
这绝对具有超低延迟效果,但结合良好的编解码算法。它背后的想法是:
- 信息(尤其是像素或帧差)具有连续性特征,不会突变的概率很大。
- 预测网络拥塞和帧差比RTT更容易,更准确。
一个简单的实现UDP柔性有损传输,以下是运行代码后的实际效果,本地lo口传输,tc netem模拟丢包。文件名 dst-x.jpg,x表示丢包率,第一个是原图,分别是1%、5%、10%、40%和80%。
时间关系,例子很简单,只传输一个jpeg但可想而知,图片可以做得更好。
看效果,丢包造成的损失是有规律的损失,因为我懒惰,我使用固定步长的像素间隔集成packet,例如,将1、101、201、301、401整合成一个packet,2、202、302、402整合成下一个packet,正确的做法应该是随机集成,或者根据图像像素的意义集成,如编码不均匀,尽量集中背景等类似像素,反之亦然,尽量分散以换取更大的冗余。
即使是80%的丢包率,也能显示出良好的轮廓。
结合好的编码解码,效果肯定更好。如果深挖,结合像素的意义,编码纠错效果可以弥补丢包带来的很多损失,比如黑色皮鞋面。像素丢失后,很容易猜出是黑色的。预测比预测网络拥塞容易得多,不是吗?
总之,主要是编解码算法,至于传输效果,交给香农定律。
在上述10%的丢包率场景中,损坏传输的分辨率效果良好。皮鞋清晰可见,无重传。最短延迟只包括传输延迟和编解码延迟,但如果使用TCP呢?为了换取原图效果,延迟增加了多少? 当丢包率分别为1%和80%时,以下是损坏传输的时间tcpdump -ttttt: 下面是TCP15%丢包率时无损传输时间: 重传引入这么大的延迟无损清晰度值得吗?相信香农定律,有噪音信道编码定理。 附上代码,我不知道怎么编程,但也不是一点也不知道。我编得不好。代码是用搜索的一些片段拼凑起来的,我自己写了一些,不多:
import java.util.Random; import java.awt.image.*; import java.io.*; import java.net.*; import javax.imageio.ImageIO; public class ImgTrans {
public static int [] dst_array = null; public static int DELTA = 500; public static int[] getPixels(String path) throws Exception {
BufferedImage img = null; int w, h; int data[] = null; img = ImageIO.read(new File(path));
w = img.getWidth(); h = img.getHeight();
data = new int[2 + w * h];
data[0] = w; data[1] = h;
for (int i = 0; i < h; i ++) {
for (int j = 0; j < w; j ++) {
data[2 + w * i + j] = img.getRGB(j, i);
}
}
return data;
}
public static void setPixels(int [] data, String path) throws Exception {
BufferedImage img = null;
File outfile = new File(path);
int w, h, k = 0;
w = data[0]; h = data[1];
img = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
for (int i = 0; i < h; i ++) {
for (int j = 0; j < w; j ++) {
img.setRGB(j, i, data[2 + w * i + j]);
}
}
ImageIO.write(img, "jpg", outfile);
}
public static void receiveImg(int [] sub) {
int delta = ImgTrans.DELTA;
int len = sub[3];
int offset = sub[4];
int count = len/delta;
if (dst_array == null) {
dst_array = new int[len + 2];
for (int i = 0; i < len + 2; i ++) {
dst_array[i] = 0;
}
dst_array[0] = sub[0]; dst_array[1] = sub[1];
}
for (int j = 0; j < count; j ++) {
dst_array[2 + offset + j*ImgTrans.DELTA] = sub[j + 5];
}
if (offset < len%ImgTrans.DELTA) {
dst_array[2 + offset + count*ImgTrans.DELTA] = sub[count + 5];
}
}
public static void sendImg(int [] data) throws Exception {
int delta = ImgTrans.DELTA;
int count;
int len = data.length - 2;
int k = 0;
int[] sub;
DatagramSocket socket = new DatagramSocket();
count = len/delta;
for (int i = 0; i < ImgTrans.DELTA; i ++) {
if (i < len%ImgTrans.DELTA) {
sub = new int[count + 6];
sub[2] = count + 6;
} else {
sub = new int[count + 5];
sub[2] = count + 5;
}
sub[0] = data[0];
sub[1] = data[1];
sub[3] = len;
sub[4] = i;
for (int j = 0; j < count; j ++) {
sub[5 + j] = data[i + j*ImgTrans.DELTA + 2];
}
if (i < len % ImgTrans.DELTA) {
sub[count] = data[count*ImgTrans.DELTA + i + 2];
}
byte[] bs = ImgTrans.iatoba(sub);
DatagramPacket subpkt = new DatagramPacket(bs, bs.length, InetAddress.getLocalHost(), 1234);
socket.send(subpkt);
/* for test Random rand = new Random(); int r = rand.nextInt(200); if (r < 190) { System.out.println(sub.length + " len count " + count); receiveImg(sub); } */
}
}
public static byte[] iatoba(int[] intarr) {
int blen = intarr.length * 4;
byte[] bt = new byte[blen];
int curint = 0;
for (int j = 0, k = 0; j < intarr.length; j ++, k += 4) {
curint = intarr[j];
bt[k] = (byte)((curint>>24) & 0xFF);
bt[k+1] = (byte)((curint>>16) & 0xFF);
bt[k+2] = (byte)((curint>>8) & 0xFF);
bt[k+3] = (byte)((curint>>0 )& 0xFF);
}
return bt;
}
public static int[] batoia(byte[] btarr) {
int i1,i2,i3,i4;
int[] intarr = new int[btarr.length/4];
for (int j = 0, k = 0; j < intarr.length; j ++, k += 4) {
i1 = btarr[k];
i2 = btarr[k+1];
i3 = btarr[k+2];
i4 = btarr[k+3];
if (i1 < 0)
i1 += 256;
if (i2 < 0)
i2 += 256;
if(i3 < 0)
i3+=256;
if(i4 < 0)
i4+=256;
intarr[j] = (i1<<24) + (i2<<16) + (i3<<8) + (i4<<0);
}
return intarr;
}
static class RecvThread extends Thread {
private DatagramSocket mSocket;
public void run() {
try {
mSocket = new DatagramSocket(1234);
while(true) {
byte[] bs = new byte[5000];
DatagramPacket subpkt = new DatagramPacket(bs, bs.length);
mSocket.receive(subpkt);
ImgTrans.receiveImg(ImgTrans.batoia(bs));
}
} catch (Exception e) {
}
}
}
// tc qdisc add dev lo root netem loss 1%
// tc qdisc add dev lo root netem loss 5%
// tc qdisc add dev lo root netem loss 10%
// tc qdisc add dev lo root netem loss 40%
// tc qdisc add dev lo root netem loss 80%
public static void main(String []args) throws Exception {
int data[] = null;
RecvThread thread = new RecvThread();
thread.start();
Thread.sleep(100);
data = getPixels(args[0]);
sendImg(data);
Thread.sleep(1000);
setPixels(dst_array, args[1]);
System.exit(0);
}
}
傍晚凉风体感不错,我不是一直说什么有损柔性传输吗,写个有损传输的demo,看下效果。
浙江温州皮鞋湿,下雨进水不会胖。