Java实现视频初步压缩和解压
本文主要做了什么
从摄像头读取每一帧的图片,用一些简单的方法将多张图片信息压缩到一份文件中(自定义的视频文件),自定义解码器读取视频文件,并将每帧图片展示成视频
第一步:按照某些算法帧内压缩
常见的视频压缩算法(H264,H265,MP4)过程很复杂,实现的压缩比率也很恐怖(H265可以做到0.5%的压缩率,也就是就算每帧图片加起来有2个GB,合并起来的视频也就10MB),其中压缩算法流程大致如下,我的程序没有细究算法,简单实现了25%的压缩率。
帧内压缩:
帧间压缩:
我的代码:
@GetMapping("/compressedVideos")
public void getCompressedBytes() throws IOException {
//录制5秒的视频,存在List中
webcam.open();
long startTime = System.currentTimeMillis();
List bufferedImages = new ArrayList();
while (System.currentTimeMillis() - startTime < 5000) {
BufferedImage image = webcam.getImage();
bufferedImages.add(image);
}
System.out.println("录制结束");
webcam.close();
//调用压缩方法,将结果写入文件中
byte[] bytes = outerCompressionUtils.photosToCompressedBytes(bufferedImages);
File file = new File("压缩中的压缩.dat");
FileOutputStream fos = new FileOutputStream(file);
fos.write(bytes);
fos.close();
System.out.println("持久化结束");
}
public static byte[] photosToCompressedBytes(List bufferedImages) throws IOException {
//数据流中未必要有各种辅助信息,比如各类字段长度,在外规定好算了
//这里每一帧的长度就是:20 + 640 * 480 * 1.75
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//java提供的压缩工具,此输出流将输出的东西压缩输出
//传入的Deflater对象用于控制压缩算法
DeflaterOutputStream dos = new DeflaterOutputStream(baos,new Deflater());
//帧信息添加到压缩流
for (BufferedImage bufferedImage: bufferedImages
) {
byte[] bytes = innerCompressionUtils.compressToOneChannel(bufferedImage);
System.out.println("一帧的长度为:"+bytes.length);
dos.write(bytes);
}
byte[] compressedData = baos.toByteArray();
return compressedData;
}
class HuffmanNode implements Comparable{
byte value;
int frequency;
HuffmanNode left;
HuffmanNode right;
public HuffmanNode(byte value,int frequency){
this.value = value;
this.frequency = frequency;
}
@Override
public int compareTo(@NotNull HuffmanNode o) {
return this.frequency - o.frequency;
}
}
public class Huffman {
public static Map encodingTable;
public static String huffmanEncoding(byte[] originalBytes){
Map frequencyMap = new HashMap();
for (byte b: originalBytes
) {
frequencyMap.put(b, frequencyMap.getOrDefault(b,0)+1);
}
PriorityQueue minHeap = new PriorityQueue();
for (Map.Entry entry : frequencyMap.entrySet()
) {
minHeap.add(new HuffmanNode(entry.getKey(),entry.getValue()));
}
while (minHeap.size()>1){
HuffmanNode left = minHeap.poll();
HuffmanNode right = minHeap.poll();
HuffmanNode mergeNode = new HuffmanNode((byte)0, left.frequency + right.frequency);
mergeNode.left = left;
mergeNode.right = right;
minHeap.add(mergeNode);
}
encodingTable = new HashMap();
HuffmanNode root = minHeap.poll();
buildEncodingTable(root,"",encodingTable);
StringBuilder encodingData = new StringBuilder();
for (Byte b: originalBytes
) {
encodingData.append(encodingTable.get(b));
}
System.out.println("原始数组长度"+originalBytes.length);
System.out.println("哈夫曼后数组长度"+encodingData.length());
return encodingData.toString();
}
public static void buildEncodingTable(HuffmanNode node,String currentCode,Map encodingMap) {
if (node == null) {
return;
}
if (node.left == null && node.right == null) {
encodingMap.put(node.value, currentCode);
} else {
buildEncodingTable(node.left, currentCode + "0", encodingMap);
buildEncodingTable(node.right, currentCode + "1", encodingMap);
}
}
但其实这里用哈夫曼并不会优化数据量,原因如下: 我传输的数据是-128到127的byte类型,这些byte来自图片的亮度和色度,调试中发现这255个数字出现的频率差不多,全部都在14万到20万之间,两个最小值加起来任然比最大值大,这就意味着这颗哈夫曼树会比较满,类似完全二叉树,于是就无法区分出现频率最高的某个字符。
另外,原本255个数将8位byte全都占满,假如有一个频率很高的元素,我们把较短的0101赋给它,那势必会导致原本以0101开头的元素用8位以上的长度进行表示,而程序中各元素出现频率相近,这就会导致如果有元素用短于8位的编码,其他长于8位编码的元素会导致数据更加庞大。
我在用huffman编码后,数据量一点都没有变,只是由长度为40647865的byte数组变成长度为325182920的字符串,其实就是×8 。怀疑是代码哪里错了...
常见的压缩算法是将DCT变换后的结果进行哈夫曼编码,DCT变换后低频信息和高频信息自然区分开,确实更适合这个熵编码方法
public static InflaterInputStream inflaterCompressedBytes(byte[] bytes) throws IOException {
//解压数据
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
InflaterInputStream lis = new InflaterInputStream(bais, new Inflater());
return lis;
}
public static BufferedImage getBfi(byte[] originalBytes) {
//分别先把开头表示各个区长度以及图片宽高的参数取出来
byte one = originalBytes[0];
byte two = originalBytes[1];
byte three = originalBytes[2];
byte four = originalBytes[3];
int Y = ((one & 0xff)