欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java實(shí)現(xiàn)視頻初步壓縮和解壓的代碼示例

 更新時(shí)間:2023年10月12日 08:53:16   作者:Ha_Ha_Wu  
從攝像頭讀取每一幀的圖片,用一些簡單的方法將多張圖片信息壓縮到一份文件中(自定義的視頻文件),自定義解碼器讀取視頻文件,并將每幀圖片展示成視頻,本文主要介紹了Java實(shí)現(xiàn)視頻初步壓縮和解壓,需要的朋友可以參考下

第一步:按照某些算法幀內(nèi)壓縮

常見的視頻壓縮算法(H264,H265,MP4)過程很復(fù)雜,實(shí)現(xiàn)的壓縮比率也很恐怖(H265可以做到0.5%的壓縮率,也就是就算每幀圖片加起來有2個(gè)GB,合并起來的視頻也就10MB),其中壓縮算法流程大致如下,我的程序沒有細(xì)究算法,簡單實(shí)現(xiàn)了25%的壓縮率。

幀內(nèi)壓縮:

  • 幀分割: 將原本RGB格式的圖像用YUV表示,用YUV是將原本的像素信息轉(zhuǎn)化成亮度和色度信息,由于人眼對色度的變化并不敏感,所以YUV可以在多個(gè)像素點(diǎn)之上采用同一數(shù)據(jù)以實(shí)現(xiàn)數(shù)據(jù)壓縮。具體的做法是:將原本圖片分成22 / 44 / 88 / 1616的宏塊,每個(gè)宏塊(4*4為例)內(nèi)按照YUV格式數(shù)據(jù)采集——記錄每個(gè)像素格的亮度Y,記錄每橫向兩個(gè)像素格的色度U,記錄每個(gè)宏塊左上角像素各的色度V。算法將Y,U,V分別存儲,再在接收端分別取出某個(gè)宏塊對應(yīng)的數(shù)據(jù),恢復(fù)成YUV,再恢復(fù)成RGB。
  • 幀內(nèi)預(yù)測: 鄰近的宏塊之間可以進(jìn)行預(yù)測,算法思想是由一個(gè)宏塊,通過某種預(yù)測模式,得到一個(gè)預(yù)測的模塊,將實(shí)際值和預(yù)測值之間的殘差進(jìn)行保存。
  • 離散余弦變換(DCT) 對每個(gè)塊的殘差執(zhí)行DCT變換,算法思想是:圖像數(shù)據(jù)分為細(xì)節(jié)、紋理和快速變化這類的高頻信息,和像整體趨勢、平均值和慢速變化這類低頻信息;DCT主要保留包含了數(shù)據(jù)整體特征的低頻信息。
  • 量化: 由于DCT的結(jié)果中浮點(diǎn)數(shù)較多,量化將其截?cái)酁檎麛?shù)以減少數(shù)據(jù)量
  • 熵編碼: 熵編碼用于編碼多種類型的信息,像文本、圖像、音頻等信息根據(jù)數(shù)據(jù)的概率分布(如字符、像素、采樣值)映射為可變長度的編碼。經(jīng)典哈夫曼樹就是一種實(shí)現(xiàn)。在此就是將像素值/YUV值根據(jù)其概率分布設(shè)置不同編碼。

幀間壓縮:

  • 幀間預(yù)測: 由于很多幀之間存在冗余,算法首先選擇一個(gè)參考幀,然后計(jì)算參考幀和當(dāng)前幀之間的運(yùn)動矢量,由此去除冗余信息
  • 運(yùn)動補(bǔ)償...
  • 殘差計(jì)算...
  • ...

我的代碼:

  • 主要Controller:
@GetMapping("/compressedVideos")
public void getCompressedBytes() throws IOException {
	//錄制5秒的視頻,存在List中
    webcam.open();
    long startTime = System.currentTimeMillis();
    List<BufferedImage> bufferedImages = new ArrayList<>();
    while (System.currentTimeMillis() - startTime < 5000) {
        BufferedImage image = webcam.getImage();
        bufferedImages.add(image);
    }
    System.out.println("錄制結(jié)束");
    webcam.close();
    //調(diào)用壓縮方法,將結(jié)果寫入文件中
    byte[] bytes = outerCompressionUtils.photosToCompressedBytes(bufferedImages);
    File file = new File("壓縮中的壓縮.dat");
    FileOutputStream fos = new FileOutputStream(file);
    fos.write(bytes);
    fos.close();
    System.out.println("持久化結(jié)束");
}

壓縮:

  • 工具方法:將rgb轉(zhuǎn)化成YUV
public static int[] rgb2YUV(int rgb) {
    int[] rgb1 = photoOps.RGBToInts(rgb);
    int red = rgb1[0];
    int green = rgb1[1];
    int blue = rgb1[2];
    int Y = (int) (0.299 * red + 0.587 * green + 0.114 * blue -128); //-128 到 127
    int U = (int) (-0.1684 * red - 0.3316 * green + 0.5 * blue);//-128 到 127
    int V = (int) (0.5 * red - 0.4187 * green - 0.083 * blue); //-128 到 127
    return new int[]{Y, U, V};
}
  • 工具方法:一張圖片化成YUV
public static byte[] compressToOneChannel(BufferedImage bufferedImage) {
    byte[] Ys = new byte[bufferedImage.getWidth() * bufferedImage.getHeight()];
    byte[] Us = new byte[bufferedImage.getHeight() * (bufferedImage.getWidth() / 2)];
    byte[] Vs = new byte[(bufferedImage.getWidth() / 2) * (bufferedImage.getHeight() / 2)];
    int targetYs = 0;
    int targetUs = 0;
    int targetVs = 0;
	/*
	這里就是遍歷2*2的宏塊,將其中對應(yīng)YUV分別寫到Y(jié)UV的數(shù)組中
	需要注意的是我犯的一個(gè)錯誤:沒有注意到Y(jié)和U的遍歷過程,導(dǎo)致在解碼的時(shí)候圖片異常
	*/
    for (int i = 0; i < bufferedImage.getHeight(); i += 2) {
        for (int j = 0; j < bufferedImage.getWidth(); j += 2) {
            for (int k = 0; k < 2; k++) {
                for (int l = 0; l < 2; l++) {
                    int[] ints = rgb2YUV(bufferedImage.getRGB(j + l, i + k));
                    int Y = ints[0];
                    Ys[targetYs] = (byte) (Y);
                    targetYs++;
                }
                int[] ints = rgb2YUV(bufferedImage.getRGB(j, i + k));
                int U = ints[1];
                Us[targetUs] = (byte) (U);
                targetUs++;
            }
            int[] ints = rgb2YUV(bufferedImage.getRGB(j, i));
            int V = ints[2];
            Vs[targetVs] = (byte) (V);
            targetVs++;
        }
    }
    int length1 = Ys.length; //大小估計(jì) : 圖片3000*2000 = 6000000 不會超int范圍
    int length2 = Us.length;
    int length3 = Vs.length;
    byte[] targetBytes = new byte[4 * 5 + length1 + length2 + length3];
    int targetIndex = 0;
	//這里是將byte[]開頭填充一些用于解碼的信息,因?yàn)閅s,Us,Vs都是一起傳的,需要在包開頭標(biāo)明每個(gè)數(shù)組長度
	//Y區(qū)的長度
    byte[] bytes1 = intToByte(length1);
    for (byte b : bytes1) {
        targetBytes[targetIndex] = b;
        targetIndex++;
    }
    //U區(qū)長度
    byte[] bytes2 = intToByte(length2);
    for (byte b : bytes2) {
        targetBytes[targetIndex] = b;
        targetIndex++;
    }
    //V區(qū)長度
    byte[] bytes3 = intToByte(length3);
    for (byte b : bytes3) {
        targetBytes[targetIndex] = b;
        targetIndex++;
    }
    //圖片的高
    byte[] bytes4 = intToByte(bufferedImage.getHeight());
    for (byte b : bytes4) {
        targetBytes[targetIndex] = b;
        targetIndex++;
    }
    //圖片的寬
    byte[] bytes5 = intToByte(bufferedImage.getWidth());
    for (byte b : bytes5) {
        targetBytes[targetIndex] = b;
        targetIndex++;
    }
	//傳遞真實(shí)數(shù)據(jù)
    for (byte y : Ys) {
        targetBytes[targetIndex] = y;
        targetIndex++;
    }
    for (byte u : Us) {
        targetBytes[targetIndex] = u;
        targetIndex++;
    }
    for (byte v : Vs) {
        targetBytes[targetIndex] = v;
        targetIndex++;
    }
    return targetBytes;
}
  • 工具方法:多張圖片化成YUV并壓縮
public static byte[] photosToCompressedBytes(List<BufferedImage> bufferedImages) throws IOException {
    //數(shù)據(jù)流中未必要有各種輔助信息,比如各類字段長度,在外規(guī)定好算了
    //這里每一幀的長度就是: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;
}
  • 嘗試用哈夫曼編碼優(yōu)化
class HuffmanNode implements Comparable<HuffmanNode>{
    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<Byte,String> encodingTable;
    public static String huffmanEncoding(byte[] originalBytes){
        Map<Byte,Integer> frequencyMap = new HashMap<>();
        for (byte b: originalBytes
             ) {
            frequencyMap.put(b, frequencyMap.getOrDefault(b,0)+1);
        }
        PriorityQueue<HuffmanNode> minHeap = new PriorityQueue<>();
        for (Map.Entry<Byte, Integer> 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("原始數(shù)組長度"+originalBytes.length);
        System.out.println("哈夫曼后數(shù)組長度"+encodingData.length());
        return encodingData.toString();
    }
public static void buildEncodingTable(HuffmanNode node,String currentCode,Map<Byte,String> 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);
        }
    }

但其實(shí)這里用哈夫曼并不會優(yōu)化數(shù)據(jù)量,原因如下: 我傳輸?shù)臄?shù)據(jù)是-128到127的byte類型,這些byte來自圖片的亮度和色度,調(diào)試中發(fā)現(xiàn)這255個(gè)數(shù)字出現(xiàn)的頻率差不多,全部都在14萬到20萬之間,兩個(gè)最小值加起來任然比最大值大,這就意味著這顆哈夫曼樹會比較滿,類似完全二叉樹,于是就無法區(qū)分出現(xiàn)頻率最高的某個(gè)字符。

另外,原本255個(gè)數(shù)將8位byte全都占滿,假如有一個(gè)頻率很高的元素,我們把較短的0101賦給它,那勢必會導(dǎo)致原本以0101開頭的元素用8位以上的長度進(jìn)行表示,而程序中各元素出現(xiàn)頻率相近,這就會導(dǎo)致如果有元素用短于8位的編碼,其他長于8位編碼的元素會導(dǎo)致數(shù)據(jù)更加龐大。

我在用huffman編碼后,數(shù)據(jù)量一點(diǎn)都沒有變,只是由長度為40647865的byte數(shù)組變成長度為325182920的字符串,其實(shí)就是×8 。懷疑是代碼哪里錯了...

常見的壓縮算法是將DCT變換后的結(jié)果進(jìn)行哈夫曼編碼,DCT變換后低頻信息和高頻信息自然區(qū)分開,確實(shí)更適合這個(gè)熵編碼方法

  • 解壓:

    先將java zip包的壓縮過程解壓

public static InflaterInputStream inflaterCompressedBytes(byte[] bytes) throws IOException {
        //解壓數(shù)據(jù)
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        InflaterInputStream lis = new InflaterInputStream(bais, new Inflater());
        return lis;
    }
  • 依據(jù)壓縮時(shí)自定義的格式進(jìn)行對byte數(shù)組解析
public static BufferedImage getBfi(byte[] originalBytes) {
		//分別先把開頭表示各個(gè)區(qū)長度以及圖片寬高的參數(shù)取出來
        byte one = originalBytes[0];
        byte two = originalBytes[1];
        byte three = originalBytes[2];
        byte four = originalBytes[3];
        int Y = ((one & 0xff) << 24) | ((two & 0xff) << 16) | ((three & 0xff) << 8) | (four & 0xff);
        byte one2 = originalBytes[4];
        byte two2 = originalBytes[5];
        byte three2 = originalBytes[6];
        byte four2 = originalBytes[7];
        int U = ((one2 & 0xff) << 24) | ((two2 & 0xff) << 16) | ((three2 & 0xff) << 8) | (four2 & 0xff);
        byte one3 = originalBytes[8];
        byte two3 = originalBytes[9];
        byte three3 = originalBytes[10];
        byte four3 = originalBytes[11];
        int V = ((one3 & 0xff) << 24) | ((two3 & 0xff) << 16) | ((three3 & 0xff) << 8) | (four3 & 0xff);
        byte one4 = originalBytes[12];
        byte two4 = originalBytes[13];
        byte three4 = originalBytes[14];
        byte four4 = originalBytes[15];
        int height = ((one4 & 0xff) << 24) | ((two4 & 0xff) << 16) | ((three4 & 0xff) << 8) | (four4 & 0xff);
        byte one5 = originalBytes[16];
        byte two5 = originalBytes[17];
        byte three5 = originalBytes[18];
        byte four5 = originalBytes[19];
        int width = ((one5 & 0xff) << 24) | ((two5 & 0xff) << 16) | ((three5 & 0xff) << 8) | (four5 & 0xff);
        System.out.println("Y: " + Y);
		//將數(shù)據(jù)讀取出來
        byte[] Ys = Arrays.copyOfRange(originalBytes, 20, Y + 20);
        byte[] Us = Arrays.copyOfRange(originalBytes, Y + 20, Y + U + 20);
        byte[] Vs = Arrays.copyOfRange(originalBytes, Y + U + 20, Y + U + V + 20);
        BufferedImage bfi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        int hongW = width / 2;
        int hongH = height / 2;
		//用YUV數(shù)據(jù)恢復(fù)成RGB,填充到圖片的每一個(gè)像素
        for (int i = 0; i < height - 1; i++) {
            for (int j = 0; j < width - 1; j++) {
                int H = i / 2;
                int W = j / 2;
                byte y = Ys[(i / 2 * 2) * width + j / 2 * 4 + (i % 2) * 2 + j % 2];
                byte u = Us[H * hongW * 2 + j / 2 * 2 + i % 2];
                byte v = Vs[H * hongW + W];
                int r = (int) (y + 128 + 1.14075 * (v));
                int g = (int) (y + 128 - 0.3455 * (u) - 0.7169 * (v));
                int b = (int) (y + 128 + 1.779 * (u));
                r = Math.min(255, Math.max(0, r));
                g = Math.min(255, Math.max(0, g));
                b = Math.min(255, Math.max(0, b));
                int color = (r) << 16 | (g) << 8 | b;
                if (i < 1 && j < 20) {
                bfi.setRGB(j, i, color);
            }
        }
        return bfi;
    }
  • 簡單的播放器(基于Swing)
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\吳松林\\IdeaProjects\\meitu2\\壓縮中的壓縮.dat");
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); //此輸出流中寫入所有信息,最后轉(zhuǎn)出為byte[],類似桶子
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = fileInputStream.read(buffer))!=-1){
            outputStream.write(buffer,0,bytesRead);
        }
        byte[] data = outputStream.toByteArray();
        InflaterInputStream iutputStream1 = utils.inflaterCompressedBytes(data); //解壓
        BufferedInputStream bis = new BufferedInputStream(iutputStream1);
        List<BufferedImage> bufferedImages = new ArrayList<>();
        byte[] eachImage = new byte[(int) (20+640*480*1.75)];
        int testIndex = 0;
        int index;
        System.out.println("length: "+eachImage.length);
        try {
            while ((index = bis.read(eachImage)) != -1) {
                System.out.println("本次讀取長度:" + index);
                testIndex++;
                System.out.println("test: " + testIndex);
                BufferedImage bfi = utils.getBfi(eachImage);
                bufferedImages.add(bfi);
            }
        }catch (Exception e){
            System.out.println("跳過異常,省略最后一張圖片");
            e.printStackTrace();
        }
        bis.close();
        iutputStream1.close();
        outputStream.close();
        fileInputStream.close();
        JFrame jFrame = new JFrame();
        myPanel panel = new myPanel();
        jFrame.add(panel);
        jFrame.setSize(new Dimension(640,480));
        jFrame.setVisible(true);
        panel.list = bufferedImages;
        while (true){
            panel.repaint();
        }
    }
}
class myPanel extends JPanel{
    int index = 0;
    List<BufferedImage> list;
    @Override
    public void paint(Graphics g) {
        g.drawImage(list.get(index), 0, 0, null);
        if (index < list.size() - 2) {
            index++;
        }
        try {
            Thread.sleep(34);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

注意:

  • zip包在使用時(shí)我遇到報(bào):Unexpected end of ZLIB input stream,沒找到很合適的解決辦法,但發(fā)現(xiàn)這個(gè)異常是在讀取到最后一張圖片時(shí)才觸發(fā),于是我選擇舍棄最后一張圖
  • 這個(gè)播放器只用Swing簡單寫了一個(gè)用于測試能否讀取文件,很明顯我的播放器只能播放我的視頻,因?yàn)槠浣獯a方式和編碼方式息息相關(guān),而各種常見的編碼方式里的算法又太過復(fù)雜。所以這個(gè)程序就相當(dāng)于寫著玩而已,和其他視頻/播放器難有半點(diǎn)干系。

以上就是Java實(shí)現(xiàn)視頻初步壓縮和解壓的代碼示例的詳細(xì)內(nèi)容,更多關(guān)于Java視頻壓縮和解壓的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 從最基本的Java工程搭建SpringMVC+SpringDataJPA+Hibernate

    從最基本的Java工程搭建SpringMVC+SpringDataJPA+Hibernate

    本文會介紹從一個(gè)最基本的java工程,到Web工程,到集成Spring、SpringMVC、SpringDataJPA+Hibernate,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧
    2016-05-05
  • 教你利用springboot集成swagger并生成接口文檔

    教你利用springboot集成swagger并生成接口文檔

    有很多小伙伴不會利用springboot集成swagger并生成接口文檔,今天特地整理了這篇文章,文中有非常詳細(xì)的代碼圖文介紹及代碼示例,對不會這個(gè)方法的小伙伴們很有幫助,需要的朋友可以參考下
    2021-05-05
  • 通過FeignClient如何獲取文件流steam?is?close問題

    通過FeignClient如何獲取文件流steam?is?close問題

    這篇文章主要介紹了通過FeignClient如何獲取文件流steam?is?close問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • 如何使用Java調(diào)用Linux系統(tǒng)命令

    如何使用Java調(diào)用Linux系統(tǒng)命令

    這篇文章主要介紹了如何使用Java調(diào)用Linux系統(tǒng)命令,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java?中向?Arraylist?添加對象的示例代碼

    Java?中向?Arraylist?添加對象的示例代碼

    本文介紹了如何在 Java 中向 ArrayList 添加對象,并提供了示例和注意事項(xiàng),通過掌握這些知識,讀者可以在自己的 Java 項(xiàng)目中有效地使用 ArrayList 來存儲和操作對象,需要的朋友可以參考下
    2023-11-11
  • SpringBoot3.1.2 引入Swagger報(bào)錯Type javax.servlet.http.HttpServletRequest not present解決辦法

    SpringBoot3.1.2 引入Swagger報(bào)錯Type javax.servlet.http

    這篇文章主要介紹了SpringBoot3.1.2 引入Swagger報(bào)錯Type javax.servlet.http.HttpServletRequest not present解決辦法,文中通過代碼示例給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2024-03-03
  • HashMap之keyset()方法底層原理解讀

    HashMap之keyset()方法底層原理解讀

    這篇文章主要介紹了HashMap之keyset()方法底層原理解讀,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • spring的jdbctemplate的crud的基類dao

    spring的jdbctemplate的crud的基類dao

    本文主要介紹了使用spring的jdbctemplate進(jìn)行增刪改查的基類Dao的簡單寫法,需要的朋友可以參考下
    2014-02-02
  • Java之注解@Data和@ToString(callSuper=true)解讀

    Java之注解@Data和@ToString(callSuper=true)解讀

    在使用Lombok庫的@Data注解時(shí),若子類未通過@ToString(callSuper=true)注明包含父類屬性,toString()方法只打印子類屬性,解決方法:1. 子類重寫toString方法;2. 子類使用@Data和@ToString(callSuper=true),父類也應(yīng)使用@Data
    2024-11-11
  • JAVA中堆、棧,靜態(tài)方法和非靜態(tài)方法的速度問題

    JAVA中堆、棧,靜態(tài)方法和非靜態(tài)方法的速度問題

    這篇文章主要介紹了JAVA中堆、棧,靜態(tài)方法和非靜態(tài)方法的速度問題,堆和棧得速度性能分析多角度給大家分析,非常不錯,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-08-08

最新評論