下周终于可去公司了,有点快放假的感觉。阳台吹着晚风,用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:
下面是TCP无损传输在15%丢包率时传输时间:
重传引入这么大的延时换无损清晰度,值得吗?还是相信香农定律吧,有噪信道编码定理。
附上代码,我不怎么会编程,但也不是一点也不会,稍微会一点,编的不好,代码是搜来的一些片段拼凑的,自己也写了点,不多:
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,看下效果。
浙江温州皮鞋湿,下雨进水不会胖。