精华内容
下载资源
问答
  • 哈夫曼树和哈夫曼编码应用之图片压缩编码c++实现

    千次阅读 多人点赞 2018-12-07 22:38:04
    本人正在学习数据结构,在前几天做了压缩图片的项目,感觉到有必要分享...那么为什么要用到压缩软件呢?我们都知道,文件是用编码进行存储的,编码要用到字节,而不可避免的一个文件中会出现很多重复的字节,用压缩...

    本人正在学习数据结构,在前几天做了压缩图片的项目,感觉到有必要分享给大家。因此今天我就分享给大家c语言数据结构有关哈夫曼树压缩图片的项目实现。

     

    一:下面先介绍有关的知识:
    1.背景

    压缩软件是用特定算法压缩数据的工具,压缩后的文件称为压缩包,可以对其进行解压。那么为什么要用到压缩软件呢?我们都知道,文件是用编码进行存储的,编码要用到字节,而不可避免的一个文件中会出现很多重复的字节,用压缩软件可以减少重复的字节,方便在互联网上实现更快的传输,也可以减少文件在磁盘上的占用空间,常见的压缩软件有rar,zip等。

    压缩可以分为无损压缩和有损压缩两种,无损压缩后的文件,经过解压后可以完全恢复到之前的文件,rar,zip等格式都是无损压缩格式,而图片文件jpg,音乐文件mp3都是有损压缩格式。

     

    2.编码介绍

    计算机文件是由一个个字节组成的,一个字节有8位二进制编码构成,共有0-255种可能的情况。由于文件中的字节可能会重复出现,可以对不同的字节设计长度不等的编码,让出现次数较多的字节,采用较短的编码,这样可以使文件编码的总长度减少。

     

    3.哈夫曼树和哈夫曼编码

    (1)哈夫曼树

    有关二叉树的知识这里就不讲解了,大家可以自行学习。这里我们统计文件中256种字节重复的次数作为权值,构造一棵有256个叶节点的二叉树,如果带权路径长度最小,我们称为这样的二叉树为最有二叉树,也叫哈夫曼(Huffman)树。

    (2)哈夫曼编码

    哈夫曼树从根结点到每个叶子结点都有一条路径,对路径上的分支,约定指向左子树的为0,指向右子树的为1,从根到每个叶子结点路径上的0和1构成的序列就是这个叶节点的哈夫曼编码。

    如图所示:

    这时编码就是:

    A:000  B:001  C:010 D:011 E:11

    使用哈夫曼编码给每个字节重新编码,重复次数较多的字节,哈夫曼编码较短,这样就比以前的二进制编码短了许多,因此可以实现压缩。

     

     

    这里我们实现把一个bmp格式的图片进行压缩编码。

    二:过程和代码实现与分析

    1.流程简述:

    (1)读取文件

    先读取文件,生成一棵带权二叉树。树在程序中可以使用顺序结构,链式结构两种方式实现,由于这棵带权二叉树的叶子节点有256个,存储空间是固定的,则这里可以使用顺序结构来表示二叉树。

    (2)定义二叉树结构

    定义一个结构体HaffNode来表示二叉树的叶子节点,记录每个结点的权值,标记,父结点,左孩子和右孩子。创建一个结构体数组来存储这棵带权二叉树的每个叶子结点的信息。

    (3)生成哈夫曼编码

    其次,生成Huffman树后,要生成哈夫曼编码,就要先遍历这棵二叉树,这里我用的是先序遍历方法,定义一个字符串数组Code来存储每个叶子结点的哈夫曼编码。

    (4)字符串转字节实现压缩

    )由于哈夫曼编码是以字符串数组的形式保存的,重新编码后的数据将是一个很长的字符串。定义Str2byte函数,将字符串转化为字节,才能转化为最终的编码,将其保存到*.huf中,则实现了文件压缩。

    (5)解压缩

    最后,为了保证压缩后的数据能够被正常解压,除了保存压缩的数据,还应保存原文件的长度和256种字节重复出现的次数。这时就需要定义一个文件头,用于保存这些信息,再保存压缩文件时,同时向文件中写入文件头和压缩数据,保证文件能够被还原。

     

    2.代码实现与分析

    (1)打开Microsoft Visual Studio 2010,创建一个解决方案,名字为"HuffmanSLN",在HuffmanSLN解决方案下面新建一个空的win32控制台工程,名字为"HfmCompressCPro"。

    (2)打开文件

    在源文件(Source Files)中新建"main.cpp"文件,作为程序运行的入口函数。

    导入<iostream>头文件,声明using bamespace std,使用cout输出信息,用cin接受键盘输入文件信息。

    代码如下:
     

    #include <iostream>
    #include <stdlib.h>
    using namespace std;
    
    int main()
    {
            cout<<"--------------Huffman文件压缩编码---------------"<<endl;
    	cout<<"请输入文件名:";
    	char filename[256];//文件名
    	cin>>filename;
            return 0;
    }

    (3)读取原文件

    以二进制流的方式,只读打开文件,一个个地逐个读取字节数据,统计文件中256种字节重复出现的次数,保存到一个数组中

    int weight[256]中,然后将扫描的结果在控制台打印下来。

    代码如下:

    #include <iostream>
    #include <stdlib.h>
    using namespace std;
    
    int main()
    {
            cout<<"--------------Huffman文件压缩编码---------------"<<endl;
    	cout<<"请输入文件名:";
    	char filename[256];//文件名
    	cin>>filename;
    
            char ch;
    	int weight[256]={0};
    
    	//以二进制流的方式打开文件
    	FILE* in = fopen(filename,"rb");
    	if(in == NULL)
    	{
    		printf("打开文件失败");
    		return 0;
    	}
    	//扫描文件,获得权重
    	while(ch = getc(in) != EOF)
    	{
    		weight[ch]++;
    	}
    	//关闭文件
    	fclose(in);
    
            //显示256个字节出现的次数
    	cout<<"Byte "<<"Weight"<<endl;
    	for(int i=0;i<256;i++)
    	{
    		printf("0x%02X %d\n",i,weight[i]);
    	}
    	system("pause");
            return 0;
    }

    这里我们的图片在E盘的根目录下:

    即下面的一张图:

    运行结果如下:

    (4)生成哈夫曼树

    在Haffman.h文件中,定义一个存储哈夫曼树结点信息的结构体,有权值,标记(若当前结点未加入结构体,flag=0;若当前结点加入结构体,flag=1),双亲结点下标,左孩子结点下标,右孩子结节下标。

    在Haffman.cpp文件中创建构造哈夫曼树的函数,在Haffman.h文件中声明。

    Haffman.h文件

    typedef struct
    {
    	int weight;		//权值
    	int flag;	    //标记
    	int parent;		//双亲结点下标
    	int leftChild;	//左孩子下标
    	int rightChild;	//右孩子下标
    }HaffNode;
    
    //创建叶结点个数为n,权值数组为weight的哈夫曼树haffTree
    void Haffman(int weight[],int n,HaffNode haffTree[]);

    Haffman.cpp文件

    #include "Huffman.h"
    #define MaxValue 10000
    
    //创建叶结点个数为n,权值数组为weight的哈夫曼树haffTree
    void Haffman(int weight[],int n,HaffNode haffTree[])
    {
    	int i,j,m1,m2,x1,x2;
    	//哈夫曼树haffTree初始化,n个叶结点的二叉树共有2n-1结点
    	for(i = 0;i < 2 * n - 1;i++)
    	{
    		if(i < n)	//如果是小于n的结点(叶子结点),则把每个字节的重复次数作为权值赋给这些该结点
    			haffTree[i].weight = weight[i];
    		else
    			haffTree[i].weight = 0;	//其他非叶子结点权值设为0
    		haffTree[i].parent = -1;
    		haffTree[i].flag   = 0;
    		haffTree[i].leftChild  = -1;
    		haffTree[i].rightChild = -1;
    	}
    
    	//构造哈夫曼树haffTree的n-1个非叶结点
    	for(i = 0;i < n - 1;i++)
    	{
    		//这里假设先进行一次循环,i=0,后面的代码注释方便大家理解
    		m1=m2=MaxValue;
    		x1=x2=0;
    		//找到权值最小和次小的子树,就是找到权值最小的两个结点
    		for(j = 0;j < n + i;j++)	//这里先让i=0;则对于n个叶子结点,按权值最小和次小的顺序连接结点生成哈夫曼树
    		{
    			//这里比较每个结点的权值,如果小于上一个结点(已查找的最小权值结点)权值且该结点没有被访问
    			if(haffTree[j].weight < m1 && haffTree[j].flag == 0)	
    			{
    				m2 = m1;	//令m2为上一个结点(非最小结点的前面的权值)的下标
    				x2 = x1;	//x2为上一个结点(非最小结点的前面的权值)下标
    				m1 = haffTree[j].weight;	//m1记录该结点的权值
    				x1 = j;						//x1为该结点的下标
    			}
    			//这里比较每个结点的权值,如果小于非最小结点的前面的结点权值且该结点没有被访问
    			else if(haffTree[j].weight < m2 && haffTree[j].flag == 0)		
    			{
    				m2 = haffTree[j].weight;	//m2记录该结点的权值
    				x2 = j;						//x2记录该结点的下标
    			}
    			//比较完所有的结点后,x1就为最小权值结点的下标,x2就为次小权值结点的下标
    		}
    
    		//将找出的两棵权值最小和次小的子树合并为一棵子树
    		haffTree[x1].parent = n + i;	//x1就为最小权值结点的下标
    		haffTree[x2].parent = n + i;	//x2就为次小权值结点的下标
    		haffTree[x1].flag   = 1;		//x1被访问
    		haffTree[x2].flag   = 1;		//x2被访问
    		haffTree[n+i].weight = haffTree[x1].weight + haffTree[x2].weight;
    		haffTree[n+i].leftChild = x1;	//左孩子存储结点x1
    		haffTree[n+i].rightChild = x2;	//右孩子存储结点x2
    	}
    }

    在main.cpp中编测试一下,改为如下形式:

     

    #include <iostream>
    #include <stdlib.h>
    #include "Haffman.h"
    #define MaxValue 10000
    using namespace std;
    
    int main(){
    	cout<<"--------------Huffman文件压缩编码---------------"<<endl;
    	cout<<"请输入文件名:";
    	char filename[256];//文件名
    	cin>>filename;
    
    	char ch;
    	int i;
    	int n = 256;
    	int weight[256]={0};
    
    	//以二进制流的方式打开文件
    	FILE* in = fopen(filename,"rb");
    	if(in == NULL)
    	{
    		printf("打开文件失败");
    		return 0;
    	}
    	//扫描文件,获得权重
    	while(ch = getc(in) != EOF)
    	{
    		weight[ch]++;
    	}
    	//关闭文件
    	fclose(in);
    
    	//测试哈夫曼树构造函数
    	HaffNode *myHaffNode = (HaffNode *)malloc(sizeof(HaffNode)*(2*n-1));
    	if(n >MaxValue)
    	{
    		printf("给出的n越界,修改MaxValue");
    		exit(1);
    	}
    	Haffman(weight,n,myHaffNode);
    
    	printf("字节种类 权值 标记 双亲结点下标 左孩子结点下标 右孩子结点下标\n");
    	for(i = 0;i < n;i++)
    	{
    		printf("pHT[%d]\t%d\t%d\t%d\t%d\t%d\n",i,myHaffNode[i].weight,myHaffNode[i].flag,myHaffNode[i].parent,myHaffNode[i].leftChild,myHaffNode[i].rightChild);
    	}
    	system("pause");
    	return 0;
    }

    运行结果如下:

    (5)生成哈夫曼编码

    按照先序遍历的算法对上面生成的哈夫曼树进行遍历,生成哈夫曼编码。

    在Haffman.h文件中创建结构体哈夫曼数组,用于存储编码的起始下表和权值。

    在Haffman.cpp文件中创建构造哈夫曼树编码的函数,在Haffman.h文件中声明。

    Haffman.h文件

    typedef struct
    {
    	int weight;		//权值
    	int flag;	    //标记
    	int parent;		//双亲结点下标
    	int leftChild;	//左孩子下标
    	int rightChild;	//右孩子下标
    }HaffNode;
    
    typedef struct
    {
    	int bit[10000];	//数组
    	int start;	//编码的起始下标
    	int weight;	//字符的权值
    }Code;
    
    //创建叶结点个数为n,权值数组为weight的哈夫曼树haffTree
    void Haffman(int weight[],int n,HaffNode haffTree[]);
    
    //有n个结点的哈夫曼树haffTree构造哈夫曼编码haffCode
    void HaffmanCode(HaffNode haffTree[],int n,Code haffNode[]);

    Haffman.cpp文件

    //有n个结点的哈夫曼树haffTree构造哈夫曼编码haffCode
    void HaffmanCode(HaffNode haffTree[],int n,Code haffCode[])
    {
    	Code *cd = (Code *)malloc(sizeof(Code));
    	int i,j,child,parent;
    	//求n个叶结点的哈夫曼编码
    	for(int i = 0;i < n;i++)
    	{
    		cd->start  = n-1;					//不等长编码的最后一位为n-1
    		cd->weight = haffTree[i].weight;	//取得编码对应的权值
    		child  = i;
    		parent = haffTree[child].weight;
    		//由叶结点向上直到根节点
    		while(parent != -1)
    		{
    			if(haffTree[parent].leftChild == child)	//判断左孩子是否存在
    				cd->bit[cd->start] = 0;
    			else									//判断右孩子是否存在
    				cd->bit[cd->start] = 1;
    			cd->start--;
    			child  = parent;
    			parent = haffTree[child].parent;
    		}
    		for(j = cd->start+1;j < n;j++)
    			haffCode[j].bit[j] = cd->bit[j];
    		haffCode[i].start  = cd->start + 1;
    		haffCode[i].weight = cd->weight;
    	}
    }

    现在在主函数中测试一下:

    #include <iostream>
    #include <stdlib.h>
    #include "Haffman.h"
    using namespace std;
    #define MaxValue 10000
    
    
    int main(){
    	cout<<"--------------Huffman文件压缩编码---------------"<<endl;
    	cout<<"请输入文件名:";
    	char filename[256];//文件名
    	cin>>filename;
    
    	char ch;
    	int i,j;
    	int n = 256;
    	int weight[256]={0};
    
    	//以二进制流的方式打开文件
    	FILE* in = fopen(filename,"rb");
    	if(in == NULL)
    	{
    		printf("打开文件失败");
    		fclose(in);
    		return 0;
    	}
    	//扫描文件,获得权重
    	while(ch = fgetc(in) != EOF)
    	{
    		weight[ch]++;
    	}
    	
    	//关闭文件
    	fclose(in);
    
    	
    	//显示256个字节出现的次数
    	cout<<"Byte "<<"Weight"<<endl;
    	for(i=0;i<256;i++)
    	{
    		printf("0x%02X %d\n",i,weight[i]);
    	}
    	
    	HaffNode *myHaffNode = (HaffNode *)malloc(sizeof(HaffNode)*(2*n-1));
    	Code *myHaffCode = (Code *)malloc(sizeof(Code)*n);
    	if(n >MaxValue)
    	{
    		printf("给出的n越界,修改MaxValue");
    		exit(1);
    	}
    	
    
    	//测试哈夫曼树构造函数
    	Haffman(weight,n,myHaffNode);
    	//测试哈夫曼编码函数
    	HaffmanCode(myHaffNode,n,myHaffCode);
    
    	//输出每个字节叶结点的哈夫曼编码
    	printf("编码信息为:");
    	for(i = 0;i < n;i++)
    	{
    		//printf("0x%02X ",i);
    		printf("Weight = %d,Code = ",myHaffCode[i].weight);
    		for(j = myHaffCode[i].start;j < n;j++)
    			printf("%d",myHaffCode[i].bit[j]);
    		printf("\n");
    	} 
    
    	system("pause");
    	return 0;
    }

    运行结果如下:

    这里我们发现权值为141291的字节没有显示编码,原因是这个编码个数太长了,在这里显示不出来。

    (6)压缩源文件

    创建Compress.h和Compress,cpp文件,定义Compress函数用于压缩原文件。

    由于编码是以字符数组的形式保存的,重新编码后的数据将是一个很长的字符串,先计算需要的空间,然后把编码按位进行存放到字符数组中,或者直接存放,这里采用直接存放的方式。

    int k,ji=0;
    	int jiShu[1000];
    	//输出每个字节叶结点的哈夫曼编码
    	printf("编码信息为:");
    	for(i = 0;i < n;i++)
    	{
    		for(j = myHaffCode[i].start;j < n;j++)
    			for(k = 0;k < myHaffCode[k].weight+1;k++)
    			{
    				printf("%d",myHaffCode[i].bit[j]);
    				jiShu[ji] = myHaffCode[i].bit[j];
    				ji++;
    			}
    	} 

    结果如下:

    (6)写入文件

    建立一个新文件,文件名为"原文件名字+.huf",将压缩后的数据写入文件。

    为了保证压缩后的数据能够被正确解压,必须把相关的解压缩规则写进去,就是把权值信息写入进去,还有文件类型,长度,权值。

    在Huffman.h中定义一个文件头结构和InitHead函数声明,在Huffman.cpp中写入函数。

    代码如下:
    Huffman.h文件

    struct HEAD
    {
    	char type[4]; //文件类型
    	int length;	  //原文件长度
    	int weight[256]; //权值数组
    }

    Huffman.cpp文件

    //记录文件信息
    int initHead(char pFileName[256],HEAD &sHead)
    {
    	int i;
    	//初始化文件头
    	strcpy(sHead.type,"bmp");
    	sHead.length = 0;	//原文件长度
    	for(i = 0;i<256;i++)
    	{
    		sHead.weight[i] = 0;
    	}
    
    	//以二进制流的方式打开文件
    	FILE* in = fopen(pFileName,"rb");
    	if(in == NULL)
    	{
    		printf("打开文件失败");
    		fclose(in);
    		return 0;
    	}
    	char ch;
    	//扫描文件,获得权重
    	while(ch = fgetc(in) != EOF)
    	{
    		sHead.weight[ch]++;
    		sHead.length++;
    	}
            //关闭文件
    	fclose(in);
    	in = NULL;
    	return 0;
    }

    现在我们得到文件的信息和编码,就可以得到压缩后的文件,直接在主函数中写代码:

    #include <iostream>
    #include <stdlib.h>
    #include <io.h>
    #include "Haffman.h"
    //#include "Compress.h"
    using namespace std;
    #define MaxValue 10000
    
    
    int main(){
    	cout<<"--------------Huffman文件压缩编码---------------"<<endl;
    	cout<<"请输入文件名:";
    	char filename[256];//文件名
    	cin>>filename;
    
    	char ch;
    	int i,j;
    	int n = 256;
    	int weight[256]={0};
    
    	//以二进制流的方式打开文件
    	FILE* in = fopen(filename,"rb");
    	int fn = _fileno(in); /*取得文件指针的底层流式文件号*/
    	int sz = _filelength(fn);/*根据文件号取得文件大小*/
    	printf("%d字节\n",sz);
    	if(in == NULL)
    	{
    		printf("打开文件失败");
    		fclose(in);
    		return 0;
    	}
    	//扫描文件,获得权重
    	while(ch = fgetc(in) != EOF)
    	{
    		weight[ch]++;
    	}
    	
    	//关闭文件
    	fclose(in);
    
    	/*
    	//显示256个字节出现的次数
    	cout<<"Byte "<<"Weight"<<endl;
    	for(i=0;i<256;i++)
    	{
    		printf("0x%02X %d\n",i,weight[i]);
    	}
    	*/
    	
    	HaffNode *myHaffNode = (HaffNode *)malloc(sizeof(HaffNode)*(2*n-1));
    	Code *myHaffCode = (Code *)malloc(sizeof(Code)*n);
    	if(n >MaxValue)
    	{
    		printf("给出的n越界,修改MaxValue");
    		exit(1);
    	}
    	
    
    	//测试哈夫曼树构造函数
    	Haffman(weight,n,myHaffNode);
    	//测试哈夫曼编码函数
    	HaffmanCode(myHaffNode,n,myHaffCode);
    
    	/*
    	printf("字节种类 权值 标记 双亲结点下标 左孩子结点下标 右孩子结点下标\n");
    	for(i = 0;i < n;i++)
    	{
    		printf("pHT[%d]\t%d\t%d\t%d\t%d\t%d\n",i,myHaffNode[i].weight,myHaffNode[i].flag,myHaffNode[i].parent,myHaffNode[i].leftChild,myHaffNode[i].rightChild);
    	}
    	*/
    	
    	HEAD sHead;
    	initHead(filename,sHead);
    
    	int cc=0;
    	//生成文件名
    	char newFileName[256] = {0};
    	strcpy(newFileName,filename);
    	strcat(newFileName,".huf");
    	//以二进制流的方式打开文件
    	FILE* out = fopen(newFileName,"wb");
    	//写文件头
    	//fwrite(&sHead,sizeof(HEAD),1,out);
    	//int k,ji=0;
    	//int jiShu[1000];
    	//输出每个字节叶结点的哈夫曼编码
    	//printf("编码信息为:");
    	for(i = 0;i < n;i++)
    	{
    		for(j = myHaffCode[i].start;j < n;j++)
    		{
    				//printf("%d",myHaffCode[i].bit[j]);
    				//写压缩后的编码
    				cc+=sizeof(myHaffCode[i].bit[j]);
    		}
    
    	} 
    
    	fclose(out);
    	out = NULL;
    	cout << "生成压缩文件:" << newFileName <<endl;
    	printf("\n");
    	printf("%d字节",sizeof(newFileName)+cc);
    	double bi=(sizeof(newFileName)+cc)/(double)sz;
    	printf("压缩比例为:%.2f",bi);
    	system("pause");
    	return 0;
    }

    结果如下:

    而我们也生成了该buf的压缩文件:

     

    需要详细代码请私信我:

    qq:1657264184

    微信:liang71471494741

     

     

    展开全文
  • iOS直播(四)对视频进行压缩编码

    千次阅读 2019-01-26 09:38:45
    2.视频为什么可以压缩? 视频存在冗余信息,主要为数据冗余和视觉冗余 1.数据冗余:图像的各像素之间存在着很强的相关性。消除这些冗余并不会导致信息损失,属于无损压缩。可以细分为: (1)空间冗余:同一帧...

    1.为什么要进行编码?

    不经过压缩编码的原视频,所占空间大,不便于保存和网络传输,所以视频录制完后,需要先编码,再传输,解码后再播放。

    2.视频为什么可以被压缩?

    视频存在冗余信息,主要为数据冗余和视觉冗余
    1.数据冗余:图像的各像素之间存在着很强的相关性。消除这些冗余并不会导致信息损失,属于无损压缩。可以细分为:

    • 空间冗余:同一帧图像像素之间有较强的相关性,可以进行帧内预测编码去除冗余。
    • 时间冗余:相邻帧的图像具有相似性,可以通过帧间预测编码去除冗余。

    2.视觉冗余:人眼的一些特性比如亮度辨别阈值,视觉阈值,对亮度和色度的敏感度不同,使得在编码的时候引入适量的误差,也不会被察觉出来。可以利用人眼的视觉特性,以一定的客观失真换取数据压缩。这种压缩属于有损压缩。

    3.压缩编码的标准

    目前主要主要使用ITU国际电传视频联盟主导的H.26x系列标准,目前应用最广泛的为H.264,随着4k、8k等超高清时代的来临,H.265也逐渐开始普及。

    4.H.264压缩方式

    (1)H264中图像以序列(GOP)为单位进行组织,把几帧图像分为一个GOP,也就是一个GOP为一段图像编码后的数据流。
    (2)一个GOP内的各帧图像被划分为I帧、B帧、P帧。

    • I帧:帧内编码帧(intra picture):为每个GOP的第一帧,通过去除空间冗余进行压缩,每个GOP有且仅有这一个I帧。

    • P帧:预测编码帧(predictive-frame):通过去除GOP中前面已编码的帧(I帧或P帧)的时间冗余信息来编码图像。每个GOP中有一个或多个P帧。

      一个序列(GOP)的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。
      
    • B帧:双向预测帧(bi-directional interpolated prediction frame):根据相邻的前一帧、本帧以及后一帧数据的不同点来压缩本帧,也即仅记录本帧与前后帧的差值。I帧和P帧间或两个P帧间有一个或多个B帧。
      I帧为基础帧,以I帧预测P帧,再由I帧和P帧一起预测B帧,一般地,I帧压缩效率最低,P帧较高,B帧最高。
      (3)最后将I帧数据与预测的差值信息进行存储和传输。

    5.H.264分层结构

    H.264的功能分为两层

    • 视频编码层(VCL:Video Coding Layer):即被压缩编码后的视频数据序列,我们前面介绍的内容均为VCL层
    • 网络提取层(NAL:Network Abstraction Layer):在VCL数据封装到NAL单元中之后,才可以用来传输或存储。

    6.NAL封装

    (1)封装方式:
    NAL是将每一帧数据写入到一个NAL单元(NALU)中,进行传输或存储的
    NALU分为NAL头和NAL体
    NALU头通常为00 00 00 01,作为一个新的NALU的起始标识
    NALU体封装着VCL编码后的信息或者其他信息

    (2)封装过程:
    I帧、P帧、B帧都是被封装成一个或者多个NALU进行传输或者存储的
    I帧开始之前也有非VCL的NAL单元,用于保存其他信息,比如:PPS、SPS

    • PPS(Picture Parameter Sets):图像参数集
    • SPS(Sequence Parameter Set):序列参数集

    在实际的H264数据帧中,往往帧前面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧,后续是B帧、P帧等数据

    7.编码方式

    (1)硬编码:使用非CPU进行编码,例如使用GPU、专用DSP、FPGA、ASIC芯片等。用此方式对CPU负载小,但对GPU等硬件要求高,iOS8中苹果已经为我们封装了VideoToolBox和AudioToolBox两个框架进行硬编码。
    (2)软编码:使用CPU进行编码,通常使用开源的ffmpeg+x264


    8.代码Demo

    下面以代码演示整个采集和编码的流程:采集–>获取视频帧–>对视频帧进行编码–>获取视频帧信息–>将编码后的数据以NALU方式写入文件

    既然iPhone拥有强大的GPU硬件,也提供了VideoToolBox和AudioToolBox两个优秀的框架,那demo当然选择硬编码啦~

    (1)将前文中利用AVFoundation进行视频采集的代码进行封住,创建VideoCapture类,实现开始采集方法startCapture:和停止采集方法stopCapture。

    在获取到sampleBuffer后,使用下一步创建的VideoEncoder类进行编码。
    VideoCapture.h

    @interface VideoCapture : NSObject
    
    - (void)startCapture:(UIView *)preview;
    
    - (void)stopCapture;
    
    @end
    

    VideoCapture.m

    #import "VideoCapture.h"
    #import "VideoEncoder.h"
    #import <AVFoundation/AVFoundation.h>
    
    @interface VideoCapture () <AVCaptureVideoDataOutputSampleBufferDelegate>
    
    /** 编码对象 */
    @property (nonatomic, strong) VideoEncoder *encoder;
    
    /** 捕捉会话*/
    @property (nonatomic, weak) AVCaptureSession *captureSession;
    
    /** 预览图层 */
    @property (nonatomic, weak) AVCaptureVideoPreviewLayer *previewLayer;
    
    /** 捕捉画面执行的线程队列 */
    @property (nonatomic, strong) dispatch_queue_t captureQueue;
    
    @end
    
    @implementation VideoCapture
    
    - (void)startCapture:(UIView *)preview
    {
        // 0.初始化编码对象
        self.encoder = [[VideoEncoder alloc] init];
        
        // 1.创建捕捉会话
        AVCaptureSession *session = [[AVCaptureSession alloc] init];
        session.sessionPreset = AVCaptureSessionPreset1280x720;
        self.captureSession = session;
        
        // 2.设置输入设备
        AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
        NSError *error = nil;
        AVCaptureDeviceInput *input = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];
        [session addInput:input];
        
        // 3.添加输出设备
        AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
        self.captureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        [output setSampleBufferDelegate:self queue:self.captureQueue];
        [session addOutput:output];
        
        // 设置录制视频的方向
        AVCaptureConnection *connection = [output connectionWithMediaType:AVMediaTypeVideo];
        [connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
        
        // 4.添加预览图层
        AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
        previewLayer.frame = preview.bounds;
        [preview.layer insertSublayer:previewLayer atIndex:0];
        self.previewLayer = previewLayer;
        
        // 5.开始捕捉
        [self.captureSession startRunning];
    }
    
    - (void)stopCapture {
        [self.captureSession stopRunning];
        [self.previewLayer removeFromSuperlayer];
        [self.encoder endEncode];
    }
    
    #pragma mark - 获取到数据
    - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
        [self.encoder encodeSampleBuffer:sampleBuffer];
    }
    
    

    (2)创建VideoEncoder类,实现编码功能。

    • setupFileHandle方法创建了保存编码后视频的文件路径。
    • setupVideoSession创建并初始化了编码会话compressionSession,其中创建方法VTCompressionSessionCreate()中,第8个参数为指定编码回调的c语言方法为didCompressH264。
    • 在上一步采集到SampleBuffer后,调用encodeSampleBuffer:方法进行编码,回调上一步的didCompressH264。
    • 在didCompressH264中判断若为关键帧,则增加sps和pps,并转换为NSData,拼接为NALU单元后写入文件,其他帧也拼接为NALU单元后写入文件。
    • 视频采集结束后调用endEncode方法销毁对话。

    VideoEncoder.h

    #import <UIKit/UIKit.h>
    #import <VideoToolbox/VideoToolbox.h>
    
    @interface VideoEncoder : NSObject
    
    - (void)encodeSampleBuffer:(CMSampleBufferRef)sampleBuffer;
    - (void)endEncode;
    
    @end
    
    

    VideoEncoder.m

    #import "VideoEncoder.h"
    
    @interface VideoEncoder()
    
    /** 记录当前的帧数 */
    @property (nonatomic, assign) NSInteger frameID;
    
    /** 编码会话 */
    @property (nonatomic, assign) VTCompressionSessionRef compressionSession;
    
    /** 文件写入对象 */
    @property (nonatomic, strong) NSFileHandle *fileHandle;
    
    @end
    
    @implementation VideoEncoder
    
    -(instancetype)init{
        if(self = [super init]){
            // 1.初始化写入文件的对象(NSFileHandle用于写入二进制文件)
            [self setUpFileHandle];
            // 2.初始化压缩编码的会话
            [self setUpVideoSession];
        }
        return self;
    }
    
    -(void)setUpFileHandle{
        //1.获取沙盒路径
        NSString *file = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"abc.h264"];
    
        //2.如果已有文件则删除后再创建
        [[NSFileManager defaultManager] removeItemAtPath:file error:nil];
        [[NSFileManager defaultManager] createFileAtPath:file contents:nil attributes:nil];
        
        //3.创建对象
        self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:file];
    }
    
    -(void)setUpVideoSession{
        //1.用于记录当前是第几帧数据
        self.frameID = 0;
        
        //2.录制视频的宽高
        int width = [UIScreen mainScreen].bounds.size.width;
        int height = [UIScreen mainScreen].bounds.size.height;
        
        //3.创建CompressionSession对象,用于对画面进行编码
        VTCompressionSessionCreate(NULL, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, didCompressH264, (__bridge void *)(self), &_compressionSession);
        
        //4.设置实时编码输出(直播需要实时输出)
        VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
        
        //5.设置期望帧率为每秒30帧
        int fps = 30;
        CFNumberRef fpsRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &fps);
        VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_ExpectedFrameRate, fpsRef);
    
        //6.设置码率
        int biteRate = 800*1024;
        CFNumberRef bitRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &biteRate);
        VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitRateRef);
        NSArray *limit = @[@(biteRate*1.5/8),@(1)];
        VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_DataRateLimits, (__bridge CFArrayRef)limit);
        
        //7.设置关键帧间隔为30(GOP长度)
        int frameInterval = 30;
        CFNumberRef frameIntervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &frameInterval);
        VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, frameIntervalRef);
        
        //8.准备编码
        VTCompressionSessionPrepareToEncodeFrames(self.compressionSession);
    }
    
    - (void)encodeSampleBuffer:(CMSampleBufferRef)sampleBuffer{
        //1.将sampleBuffer转成imageBuffer
        CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
        
        //2.根据当前帧数,创建CMTime
        CMTime presentationTimeStamp = CMTimeMake(self.frameID++, 1000);
        VTEncodeInfoFlags flags;
        
        //3.开始编码该帧数据
        OSStatus statusCode = VTCompressionSessionEncodeFrame(self.compressionSession,
                                                              imageBuffer,
                                                              presentationTimeStamp,
                                                              kCMTimeInvalid,
                                                              NULL, (__bridge void * _Nullable)(self), &flags);
        if (statusCode == noErr) {
            NSLog(@"H264: VTCompressionSessionEncodeFrame Success");
        }
    }
    
    
    // 编码完成回调
    void didCompressH264(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) {
        //1.判断状态是否是没有报错
        if (status != noErr) {
            return;
        }
        
        //2.根据传入的参数获取对象
        VideoEncoder* encoder = (__bridge VideoEncoder*)outputCallbackRefCon;
    
        //3.判断是否是关键帧
        bool isKeyframe = !CFDictionaryContainsKey(CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true),0), kCMSampleAttachmentKey_NotSync);
        //判断当前帧是否为关键帧
        //获取sps & pps数据
        if(isKeyframe){
            //获取编码后的信息
            CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
            
            //获取SPS信息
            size_t sparameterSetSize, sparameterSetCount;
            const uint8_t *sparameterSet;
            CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sparameterSet, &sparameterSetSize, &sparameterSetCount, 0 );
    
            // 获取PPS信息
            size_t pparameterSetSize, pparameterSetCount;
            const uint8_t *pparameterSet;
            CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pparameterSet, &pparameterSetSize, &pparameterSetCount, 0 );
            
            //装sps/pps转成NSData,以便写入文件
            NSData *sps = [NSData dataWithBytes:sparameterSet length:sparameterSetSize];
            NSData *pps = [NSData dataWithBytes:pparameterSet length:pparameterSetSize];
            
            //写入文件
            [encoder gotSpsPps:sps pps:pps];
        }
        
        // 获取数据块
        CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
        size_t length, totalLength;
        char *dataPointer;
        OSStatus statusCodeRet = CMBlockBufferGetDataPointer(dataBuffer, 0, &length, &totalLength, &dataPointer);
        if (statusCodeRet == noErr) {
            size_t bufferOffset = 0;
            static const int AVCCHeaderLength = 4; // 返回的nalu数据前四个字节不是0001的startcode,而是大端模式的帧长度length
            
            // 循环获取nalu数据
            while (bufferOffset < totalLength - AVCCHeaderLength) {
                uint32_t NALUnitLength = 0;
                // Read the NAL unit length
                memcpy(&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength);
                
                // 从大端转系统端
                NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);
                
                NSData* data = [[NSData alloc] initWithBytes:(dataPointer + bufferOffset + AVCCHeaderLength) length:NALUnitLength];
                [encoder gotEncodedData:data isKeyFrame:isKeyframe];
                
                // 移动到写一个块,转成NALU单元
                bufferOffset += AVCCHeaderLength + NALUnitLength;
            }
        }
    }
    
    - (void)gotSpsPps:(NSData*)sps pps:(NSData*)pps
    {
        // 1.拼接NALU的header
        const char bytes[] = "\x00\x00\x00\x01";
        size_t length = (sizeof bytes) - 1;
        NSData *ByteHeader = [NSData dataWithBytes:bytes length:length];
        
        // 2.将NALU的头&NALU的体写入文件
        [self.fileHandle writeData:ByteHeader];
        [self.fileHandle writeData:sps];
        [self.fileHandle writeData:ByteHeader];
        [self.fileHandle writeData:pps];
        
    }
    - (void)gotEncodedData:(NSData*)data isKeyFrame:(BOOL)isKeyFrame
    {
        NSLog(@"gotEncodedData %d", (int)[data length]);
        if (self.fileHandle != NULL)
        {
            const char bytes[] = "\x00\x00\x00\x01";
            size_t length = (sizeof bytes) - 1; //string literals have implicit trailing '\0'
            NSData *ByteHeader = [NSData dataWithBytes:bytes length:length];
            [self.fileHandle writeData:ByteHeader];
            [self.fileHandle writeData:data];
        }
    }
    
    
    - (void)endEncode {
        VTCompressionSessionCompleteFrames(self.compressionSession, kCMTimeInvalid);
        VTCompressionSessionInvalidate(self.compressionSession);
        CFRelease(self.compressionSession);
        self.compressionSession = NULL;
    }
    
    
    @end
    
    

    (3)在主页面ViewController.m中,加入点击开始采集和结束采集的按钮并实现对应方法:

    @interface ViewController () 
    
    /** 视频捕捉对象 */
    @property (nonatomic, strong) VideoCapture *videoCapture;
    
    @end
    
    @implementation ViewController
    
    - (IBAction)startCapture {
        [self.videoCapture startCapture:self.view];
    }
    
    - (IBAction)stopCapture {
        [self.videoCapture stopCapture];
    }
    
    - (VideoCapture *)videoCapture {
        if (_videoCapture == nil) {
            _videoCapture = [[VideoCapture alloc] init];
        }
        return _videoCapture;
    }
    
    

    demo源码下载:https://github.com/dolacmeng/encodeWithVideoToolBox

    展开全文
  • 一、为什么要使用压缩压缩技术能够有效减少底层存储系统(HDFS)读写字节数。压缩提高了网络带宽和磁盘空间的效率。在运行MR程序时,I/O操作、网络数据传输、 Shuffle和Merge要花大量的时间,尤其是数据规模很大...

    一、为什么要使用压缩?

    压缩技术能够有效减少底层存储系统(HDFS)读写字节数。压缩提高了网络带宽和磁盘空间的效率。在运行MR程序时,I/O操作、网络数据传输、 Shuffle和Merge要花大量的时间,尤其是数据规模很大和工作负载密集的情况下,因此,使用数据压缩显得非常重要

      鉴于磁盘I/O和网络带宽是Hadoop的宝贵资源,数据压缩对于节省资源、最小化磁盘I/O和网络传输非常有帮助。可以在任意MapReduce阶段启用压缩。不过,尽管压缩与解压操作的CPU开销不高,其性能的提升和资源的节省并非没有代价。【会玩才能提升性能】

    二、压缩的策略和原则

     压缩是提高Hadoop运行效率的一种优化策略。

    通过对Mapper、Reducer运行过程的数据进行压缩,以减少磁盘IO,提高MR程序运行速度。

      注意:采用压缩技术减少了磁盘IO,但同时增加了CPU运算负担。所以,压缩特性运用得当能提高性能,但运用不当也可能降低性能。【压缩就是编码与解码的过程,内置压缩算法,当然会消耗CPU资源】

    压缩基本原则:

    (1)运算密集型的job,少用压缩

    (2)IO密集型的job,多用压缩

    三、MR支持的压缩编码

    压缩格式

    hadoop自带?

    算法

    文件扩展名

    是否可切片

    换成压缩格式后,原来的程序是否需要修改

    DEFLATE

    是,直接使用

    DEFLATE

    .deflate

    和文本处理一样,不需要修改

    Gzip

    是,直接使用

    DEFLATE

    .gz

    和文本处理一样,不需要修改

    bzip2

    是,直接使用

    bzip2

    .bz2

    和文本处理一样,不需要修改

    LZO

    否,需要安装

    LZO

    .lzo

    需要建索引,还需要指定输入格式

    Snappy

    是,直接使用

    Snappy

    .snappy

    和文本处理一样,不需要修改

    为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器,如下表所示。

    压缩格式

    对应的编码/解码器

    DEFLATE

    org.apache.hadoop.io.compress.DefaultCodec

    gzip

    org.apache.hadoop.io.compress.GzipCodec

    bzip2

    org.apache.hadoop.io.compress.BZip2Codec

    LZO

    com.hadoop.compression.lzo.LzopCodec

    Snappy

    org.apache.hadoop.io.compress.SnappyCodec

           压缩性能的比较

    压缩算法

    原始文件大小

    压缩文件大小

    压缩速度

    解压速度

    gzip

    8.3GB

    1.8GB

    17.5MB/s

    58MB/s

    bzip2

    8.3GB

    1.1GB

    2.4MB/s

    9.5MB/s

    LZO

    8.3GB

    2.9GB

    49.3MB/s

    74.6MB/s

    小结:1)Gzip中庸,没有特点;2)Bzip2,慢工出细活,支持切片,压缩率高,但速度慢,合适冷数据;3)LZO,压缩解压缩速度接近于磁盘I/O速度,支持切片,但是使用麻烦;4)Snappy,一个字,就是快,压缩速度250MB/s,解压速度500MB/s,但是不支持切片,故最好压缩后的文件大小100-200MB,就非常适合用Snappy,企业中非常受欢迎,且Hadoop 3.x 配合CensOs7.0默认支持,Hadoop 2.0不支持。 如果有一天Snappy支持切片,估计就没有其他压缩格式的活了。

    三、压缩方式选择

    1.Gzip压缩

    2.Bzip2压缩

    3.LZO压缩

    4.Snappy

    四、压缩位置选择

    五、压缩参数配置

    要在Hadoop中启用压缩,可以配置如下参数:

    参数

    默认值

    阶段

    建议

    io.compression.codecs   

    (在core-site.xml中配置)

    无,这个需要在命令行输入hadoop checknative查看

    输入压缩

    Hadoop使用文件扩展名判断是否支持某种编解码器

    mapreduce.map.output.compress(在mapred-site.xml中配置)

    false

    mapper输出

    这个参数设为true启用压缩

    mapreduce.map.output.compress.codec(在mapred-site.xml中配置)

    org.apache.hadoop.io.compress.DefaultCodec

    mapper输出

    企业使用LZO或Snappy编解码器在此阶段压缩数据

    mapreduce.output.fileoutputformat.compress(在mapred-site.xml中配置)

    false

    reducer输出

    这个参数设为true启用压缩

    mapreduce.output.fileoutputformat.compress.codec(在mapred-site.xml中配置)

    org.apache.hadoop.io.compress.DefaultCodec

    reducer输出

    使用标准工具或者编解码器,如gzip和bzip2

    mapreduce.output.fileoutputformat.compress.type(在mapred-site.xml中配置)

    RECORD

    reducer输出

    SequenceFile输出使用的压缩类型:NONE和BLOCK

    展开全文
  • 为什么这题可以采用石子问题的思路解决?这两者有什么相似之处吗?哪位大神可以详细分析下
  • 高效视频编码(HEVC),也称为H.265,可以通过蓝光最佳视频压缩方法实现两倍的压缩。但它是如何工作的,是否足以让我们看到更好看的4K内容?   我想称之H.265,因为它听起来很酷,但它的全称是高效视频编码...

    什么是HEVC?解释了高效视频编码,H.2654K压缩

    高效视频编码(HEVC),也称为H.265,可以通过蓝光最佳视频压缩方法实现两倍的压缩。但它是如何工作的,是否足以让我们看到更好看的4K内容?

     

    我想称之为H.265,因为它听起来很酷,但它的全称是高效视频编码(HEVC)。它是高级视频编码(AVC)的新继承者,也称为H.264,它是蓝光使用的压缩方案之一。

    HEVC的想法是提供与AVC相同水平的图像质量,但具有更好的压缩,因此需要处理的数据更少。如果我们想要4K /超高清广播(包括卫星),4K蓝光等,这是关键 

    但这是否足够,就此而言,它是如何运作的?

    压缩(好的,坏的和有损的)

    专业高清摄像机背面的原始数据量很大。没有办法方便地将它运到你家。相反,视频被压缩以将数据量减少为更易于管理的形式。

    有很多方法可以做到这一点,其中最简单的方法就是降低质量。在某些情况下,这没关系。想想你平均的YouTube视频。不是很好,对吗?通常这是因为视频是高度压缩的(在上传之前或期间)。重压可能会使分辨率在技术上保持不变,但图像看起来会更柔和,更嘈杂,或者会产生 奇怪的分散注意力(如右图所示)。

    但如果重点是保持导演的意图,或炫耀你的新77英寸OLED,这不是一个好主意 

    所以另一种选择是使用 更好的压缩。在这种情况下,您基本上可以将更好的压缩视为更智能的压缩。所以它采用相同的原始(视频),并找到更好的方法来减少数据量,而不牺牲质量。每隔几年,齿轮的处理能力就会提高到足以使用更多的处理器密集型压缩算法,并进一步压缩数据而不会使图像变得更糟。

    更多压缩和更好压缩之间的这种区别很重要,实际上,这些术语在这种情况下是不可互换的。您可以通过启动压缩并使图像变得丑陋(仅更多压缩)或使用更有效的压缩技术(更好压缩)来减少信号所需的数据量。

    让我这样说吧。说你有一框的苹果。你需要在里面装100个苹果。您可以通过更多压缩(减少苹果到酱汁)或更好的压缩(找到更好的方法使它们都适合,但保留它们的苹果)来做到这一点。

    更多压缩:苹果酱 
    更好的压缩:更多的苹果,相同的空间

    从这个美味的例子可以看出,更多压缩很容易(SMUSH),而更好的压缩需要更多的思考和/或更好的技术。

    输入H.265

    HD一样,数据密集,4K更糟糕。虽然我们大多数人都习惯于在蓝光上使用H.264相对于MPEG-2的优势,但运动图像专家组和国际电信联盟的电信标准化部门(ITU-T)已经开始研究下一代生成视频压缩,着眼未来。

    不希望乱搞小的,渐进的改进,每当引入新的压缩标准时,它必须是一个相当大的变化。对于每次跳转,一般规则是相同质量的比特率的一半(或者在相同比特率下具有更高的质量)。

    它是如何做到的?主要是通过扩展AVC(以及之前的其他压缩技术)的工作方式。

    首先,它会查看多个帧以查看不会发生变化的内容。在电视节目或电影的大多数场景中,绝大多数画面都没有太大变化。想想有人说话的场景。镜头主要是他们的头部。对于许多帧来说,背景不会有太大变化。就此而言,代表他们脸部的大多数像素可能不会有太大变化(当然,除了他们的嘴唇)。因此,不是对每个帧中的每个像素进行编码,而是对初始帧进行编码,然后仅对其进行编码(基本上)。

    然后,HEVC扩大了为这些变化而查看的区域的大小。基本上更大和更小的,提供额外的效率。当图片出现犯规时,您的图像中是否曾见过 ?与先前的压缩方法相比,HEVC可以更大,更小,形状更大。例如,发现较大的块更有效。

    左边是AVC / H.264所做的宏块处理。正如您在右侧所看到的,HEVC / H.265编码器具有更大的灵活性,更不用说更大的尺寸了。

    然后其他事情得到改善,如运动补偿,空间预测等。所有这些事情都可以通过AVC甚至更早的方式完成,但它需要比当时经济上可行的更多的处理能力。

    在开发阶段,压缩算法客观地测试其原始数量效率,但也主观地由视频专业人员在测试中比较不同压缩方法和数量,其中他们不知道哪种方法是哪种。人的因素至关重要。仅仅因为计算机说一级压缩比另一级更好并不意味着它看起来比另一级好。

    由于H.265的处理器密集程度要高得多,因此不要指望通过简单的固件升级来让您的设备进行解码。事实上,这是问题的一部分。你需要一个硬件解码器。如果您的新媒体流媒体,有线电视盒或BD播放器有它,那么您将全部设置(假设您也有 HDMI 2.0因此您可以获得2160p / 60而不仅仅是2160p / 30)。高端PC可以通过软件对其进行解码吗?也许。Xbox One还是PS4?不见得。每个人都喜欢他们最喜欢的控制台,但请记住,这一代的硬件相当于一台普通的PC

    它够了吗?

    嗯,技术上是的,但有一个很大的警告。与之前的AVC(和其他压缩标准)一样,H.265可根据所需带宽进行调整。想要4K平庸的互联网连接?没问题转动拨号(记得苹果酱?)。想要最好的画质吗?没问题从另一个方向转动拨盘。

    虽然这种安排提供了灵活性,但它也意味着“4K”“UHD”不一定能保证比现在的“1080p”“HD”更好的图像质量。在许多方面,高压缩的4K信号看起来可能比压缩程度较低的HD信号更糟糕。

    换句话说,根据使用的压缩程度,流式4K可能看起来比当前的1080p蓝光更差。随着 Netflix现在15.6 Mbps的速度流式传输最初的迹象表明 1080p蓝光看起来更干净,这证实了一些专家的预测。可能的原因?1080p蓝光比在线流媒体有更多的带宽用于视频,而不仅仅是补偿光盘的旧压缩方案。

    虽然所有设备的处理速度遵循摩尔定律,但互联网带宽却没有。当然有一些真正的高速连接,但许多人都很难获得一个体面的高清信号。随着Net Neutrality在美国的不稳定 ,大众的体面和廉价4K流媒体的未来仍然是多云的。

    另一个好处

    虽然HEVC的大部分潜在优势都集中在4K上,但其更好的压缩效果也为HD带来了好处。高清带宽越低意味着更多人 可以获得高清。对于当前HD来说连接太慢的人群可能能够获得HEVC编码的HD。如果您按兆字节(移动或家庭)付费,较低的比特率意味着更便宜的高清观看。

    结束

    开始寻找HEVC(或H.265)作为未来电视,蓝光播放器和其他媒体播放器的产品线。几乎所有主要品牌的20144K电视都包含必要的硬件解码器,尽管2013年的4K电视没有。还有更多的索尼FMP-X10等飘带 包括必备的硬件。

    在蓝光出现的过程中向H.264 / AVC过渡期间有很多抱怨,现在它已经成为现实。最终,HEVC也是如此。降低数据速率,同时保持质量,对每个人都是好事。

     

    展开全文
  • 1为什么要编码?以1080P分辨率,60fps帧率,...以20mbps的带宽传输,仅需要9秒钟,即9秒钟可以传送长度为1分钟视频,满足实时传输的要求,所以原始视频要想通过网络传输,势必要经过压缩编码。2视频编码协议有哪些?...
  • 单词的压缩编码 给定一个单词列表,我们将这个列表编码成一个索引字符串 S 与一个索引列表 A。 例如,如果这个列表是 [“time”, “me”, “bell”],我们就可以将其 表示 S = “time#bell#” 和 indexes = [0, 2...
  • CCF 201612-4 压缩编码 平行四边形优化区间DP问题描述问题思路为什么说这道题可以用石子问题解决?什么是平行四边形区间DP优化?代码 问题描述 问题描述试题编号: 201612-4 试题名称: 压缩编码 时间限制: 3.0...
  • 这样,空间域的图像变换到频域或所谓的变换域,会产生相关性很小的一些变换系数,并可对其进行压缩编码,即所谓的变换编码(Transform Coding)。 变换中有一类叫做正交变换,可用于图像编码。典型的准最佳变换有...
  • 我们为什么编码?要理解这个听上去很高大上的玩意儿其实并不困难—— 简单地说,编码是人类利用信息对象(比如数据)的统计特性等特性,来尽可能地压缩对象在传输过程中的体量!以此,来实现本来不可能实现的信息...
  • 什么压缩文件/压缩包?

    万次阅读 2020-08-12 14:13:24
    为什么压缩文件呢? 首先我们需要知道什么是压缩文件 什么是压缩? 计算机是以二进制的形式来储存所有的文件的,也就是00000001111111类似这种形式,压缩文件就会以一种类似编码的形式来储存这些文档,比如四个0连...
  • 对象头中的Class Pointer默认占8个字节,开启-XX:+UseCompressedOops后,为了节省空间压缩为4个字节,4*8=32位表示可寻址4G个对象,在内存空间小于32G时,可以通过编码、解码方式进行优化,使得jvm可以支持更大的...
  • 预测法是最简单和实用的视频压缩编码方法,这时压缩编码后传输的并不是像素本身的取样幅值,而是该取样的预测值和实际值之差。 为什么取像素预测值与实际值之差...人们可以利用这些性质进行视频压缩编码。这种预测编
  • 为什么mac解压缩软件BetterZip解压文件乱码?该怎么解决? 原因: BetterZip内置了自动编码检测功能,但有时Windows zip存档的文件编码无法自动确定,您需要告诉BetterZip使用什么。 最近这款解压缩工具来了一次小...
  • 前序傅立叶变换,离散傅立叶变换,推荐这个回答的介绍想和大家讨论一下jpeg中的dct变换,为什么有效?它有什么更深层次的本质吗?​www.zhihu.com全篇参考下文,英文较好可以直接看。ucsd ece161​www.svcl.ucsd.edu...
  • 首先我们来介绍一下Varint编码,Varint编码就是一种用一个或多个字节将数据序列化,并对数据进行压缩的方法,因此也可以称之Varint压缩算法。 在进行数据传输过程,我们经常用大位宽来进行数
  • 文件压缩原理: 首先文件压缩是通过HuffmaCode实现的、整体思路通过读取文件获取字符出现频率,通过字符出现频率可以构建HuffmanTree...为什么通过HuffmanCode可以实现文件的压缩呢? 原因:1.每个文件的字符种类...
  • 例如,如果我们将“compression”编码为“comp”那么这篇文章可以用较少的数据位表示。常见的例子是ZIP文件格式,此格式不仅仅提供压缩功能,还可作为归档工具(Archiver),能够将许多文件存储到同一个文件中。数据...
  • 那么视频为什么可以压缩呢,原因是一个视频实际上从不同的方面都含有冗余,故而为压缩提供了可能性. 冗余可以分为以下几个方面: 像素空间冗余: 不知你是否注意过,在同一图像(帧)内,相近像素之间的差别很小,甚至...
  • 目前市场中使用音视频技术的公司太多了,大到全民观看短视频,小到直播带货,大家用手机的时间也被视频占据着国内的音视频行业可以划分三类 第一类 短视频 抖音、快手、微视等短视频平台大量使用音视频技术...
  • 什么压缩BCD码和非压缩BCD码?

    千次阅读 2008-11-05 19:40:00
    最常用的BCD编码,就是...这些编码,大致可以分成有权码和无权码两种: 有权BCD码,如:8421(最常用)、2421、5421… 无权BCD码,如:余3码、格雷码… 压缩BCD码与非压缩BCD码的区别—— 压缩BCD码的每一位用4位二进制表
  • 什么是哈夫曼树? 哈夫曼树(Huffman Tree):给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度(WPL)达到最小,这样的二叉树也称为最优二叉树 一棵带权二叉树: 这里有几个概念: 路径:从一个...
  • Brotli是一种全新的数据格式,可以提供比Zopfli高20-26%的压缩比。 什么是 Brotli 压缩算法 Brotli最初发布于2015年,用于网络字体的离线压缩。Google软件工程师在2015年9月发布了包含通用无损数据压缩的Brotli增强...
  • (添加/删除/重命名)、对ZIP格式的文件名支持Unicode或 MBCS编码、AES256算法加密等实用功能,可以轻松地查看压缩包的文件列表、通过对文件完整性的检查来判断压缩包是否受损、对代码页进行更改的功能以及集成到资源...
  • 编码

    2019-08-27 10:25:14
    1.为什么Ascii码只能表示128个字符? 1byte=8bit,有一位是符号位来表示正负,所以可以表示[0,127]共128个数 2.gbk和gb2312的关系 k是kuo的第一个字母,gbk兼容gb2312 3.UTF-8和Unicode的关系 Unicode为每种语言的每...
  • 借机总结一下~提纲如下:简介前置知识为什么压缩数据固定长度压缩算法哈夫曼压缩算法1.简介霍夫曼编码是MIT的David A. Huffman担任科学博士学位期间开发的一种算法,霍夫曼算法的输出可以视为可变长度(variable-...
  • 为什么要进行DCT变换?什么是DCT变换?为什么要进行DCT变换?匿名网友:DCT变换的基本思路是将图像分解为8*8的子块或16*16的子块,并对每一个子块进行单独的DCT变换,然后对变换结果进行量化、编码。随着子块尺寸的增加...
  • 为什么要进行数据压缩? 从信息论的角度来看数据压缩,本质上就是通过寻找一种编码方案,在不损失或者尽量少损失原始信源信号的前提下,将原始信源信号映射到另一个D元码字空间上。 在机器学习中,我们经常讨论到...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 538
精华内容 215
关键字:

为什么可以压缩编码