PCM,YUV数据详解和常用算法

全文一览

一 PCM
基本概念:声道,通道,采样率,采样位数,位速/比特率/码率,存储方式,字节序
算法
(1)分离PCM16LE双声道音频采样数据的左声道和右声道
(2)将PCM16LE双声道音频采样数据的声音速度提高一倍
(3)将PCM16LE双声道音频采样数据转换为PCM8音频采样数据

二 YUV
和RGB转换公式,位深度,存储方式
算法
(1)分离YUV420P像素数据中的Y、U、V分量
(2)YUV420P像素数据的亮度减半
(3) 将YUV420P像素数据的周围加上边框
(4) 计算两个YUV420P像素数据的PSNR

三 为什么要学习音视频?

四 参考资料

一 PCM

  PCM(Pulse Code Modulation)也被称为脉冲编码调制,未经压缩的音频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准的数字音频数据。

声道

  单声道,双声道,多声道

通道

  常用:扬声器,有线耳机,听筒,蓝牙耳机

采样率

  单位Hz,即一秒钟内,我们可以把声音分成多少段,采样越频繁意味着越能够完美的还原采集的声音,采样频率一般共分为11025Hz、22050Hz、24000Hz、44100Hz、48000Hz五个等级,人耳能够感觉到的频率为20Hz到 20000Hz,为了复原波形,一次振动中,必须有2个点的采样

  因此要满足人耳的听觉要求,则需要每秒进行40k次采样,用40kHz

采样位数
  采样就是能播放的最小音频片段声音的数字表达范围,用于实现数据的精准还原,通常采样位分8位字节,和16字节,即在采集音频的某一个数据时,我们需要一个数字来表达这个声音的高低,音调等,8位字节能表达的最大数字就是255,即能够把我们听到的一串声音,能够切成256份,在计算机中存储表达是0-255,最基本的声音是七种,所以8位基本上能够模拟出我们听到所有声音,还有16位采样,可以把声音分成65536份,在计算及中存储的表达是-32768到32767,所以较8位来说能够更加精细的表达声音,当然也意味着音频文件也增大一倍。

位速/比特率/码率
  三者表达的都是同一种意思,kbps,即千比特率,千比特就是1000个byte,单位byte,是储存容量的基本单位,常用的存储单位有:bit、B、KB、MB、GB、TB、PB...。
  比特率这个参数我们往往手机上都能看到,就是我们当前的手机网速,不过一般单位写成了b/s,k/s或者m/s,反映的是数据的传输速度。如果用在了音视频文件上,则是一秒中需要传输多大的数据量。
  比特率 = 采样率 x 采样位数 x 通道

存储方式

  可以看到如果是双声道,数据是左右,左右依次存储

  mac上的Hex Friends可以查看pcm文件数据,如下为双声道立体声音频文件数据(16进制)

字节序

  表示音频PCM数据存储的方式,分为大端存储(big-endian)还是小端存储(little-endian),不同平台存储方式有区别,通过下图演示

  上图是PCM16LE的音频存储数据,从上面看刚开始的4个字节是同一个采样点,其中0x04e8是左声道,0x01c9是右声道,这就是小端排序的特点,整体是内存地址从低往高,但是每个数据中的前后顺序是相反的。

提问:
【例1】请计算对于5分钟双声道、16位采样位数、44.1kHz采样频率声音的不压缩数据量是多少?
  根据公式:数据量=(采样频率×采样位数×声道数×时间)/8
得,数据量(MB)=[44.1×1000×16×2×(5×60)] /(8×1024×1024)=50.47MB
  计算时要注意几个单位的换算细节:
  时间单位换算:1分=60秒
  采样频率单位换算:1kHz=1000Hz
  数据量单位换算:1MB=1024×1024=1048576B

【例2】请计算对于双声道立体声、采样频率为44.1kHz、采样位数为16位的激光唱盘(CD-A),用一个650MB的CD-ROM可存放多长时间的音乐?
  已知音频文件大小的计算公式如下:
  文件的字节数/每秒=采样频率(Hz)X采样位数(位)X声道数/8
  根据上面的公式计算一秒钟时间内的不压缩数据量:(44.1×1000×16×2)/8=0.168MB/s
  那么,一个650MB的CD-ROM可存放的时间为:(650/0.168)/(60×60)=1.07小时。

常见音频处理算法
(1)分离PCM16LE双声道音频采样数据的左声道和右声道

int simplest_pcm16le_split(char *url){
    FILE *fp=fopen(url,"rb+");
    FILE *fp1=fopen("output_l.pcm","wb+");
    FILE *fp2=fopen("output_r.pcm","wb+");
 
    unsigned char *sample=(unsigned char *)malloc(4);
    while(!feof(fp)){
        fread(sample,1,4,fp);
        //L
        fwrite(sample,1,2,fp1);
        //R
        fwrite(sample+2,1,2,fp2);
    }
    free(sample);
    fclose(fp);
    fclose(fp1);
    fclose(fp2);
    return 0;
}

  算法分析:PCM16LE双声道数据中左声道和右声道的采样值是间隔存储的。每个采样值占用2Byte空间。代码运行后,会把NocturneNo2inEflat_44.1k_s16le.pcm的PCM16LE格式的数据分离为两个单声道数据

(2)将PCM16LE双声道音频采样数据的声音速度提高一倍

int simplest_pcm16le_doublespeed(char *url){
    FILE *fp=fopen(url,"rb+");
    FILE *fp1=fopen("output_doublespeed.pcm","wb+");
    int cnt=0;
    unsigned char *sample=(unsigned char *)malloc(4);
    while(!feof(fp)){
        fread(sample,1,4,fp);
        if(cnt%2!=0){
            //L
            fwrite(sample,1,2,fp1);
            //R
            fwrite(sample+2,1,2,fp1);
        }
        cnt++;
    }
    printf("Sample Cnt:%d\n",cnt);
    free(sample);
    fclose(fp);
    fclose(fp1);
    return 0;
}

  算法分析:只采样了每个声道奇数点的样值。处理完成后,原本22秒左右的音频变成了11秒左右。音频的播放速度提高了2倍,音频的音调也变高了很多

(3)将PCM16LE双声道音频采样数据转换为PCM8音频采样数据

int simplest_pcm16le_to_pcm8(char *url){
    FILE *fp=fopen(url,"rb+");
    FILE *fp1=fopen("output_8.pcm","wb+");
    int cnt=0;
    unsigned char *sample=(unsigned char *)malloc(4);
    while(!feof(fp)){
        short *samplenum16=NULL;
        char samplenum8=0;
        unsigned char samplenum8_u=0;
        fread(sample,1,4,fp);
        //(-32768-32767)
        samplenum16=(short *)sample;
        samplenum8=(*samplenum16)>>8;
        //(0-255)
        samplenum8_u=samplenum8+128;
        //L
        fwrite(&samplenum8_u,1,1,fp1);
        samplenum16=(short *)(sample+2);
        samplenum8=(*samplenum16)>>8;
        samplenum8_u=samplenum8+128;
        //R
        fwrite(&samplenum8_u,1,1,fp1);
        cnt++;
    }
    printf("Sample Cnt:%d\n",cnt);
    free(sample);
    fclose(fp);
    fclose(fp1);
    return 0;
}

  算法分析:PCM16LE格式的采样数据的取值范围是-32768到32767,而PCM8格式的采样数据的取值范围是0到255。所以PCM16LE转换到PCM8需要经过两个步骤:第一步是将-32768到32767的16bit有符号数值转换为-128到127的8bit有符号数值,第二步是将-128到127的8bit有符号数值转换为0到255的8bit无符号数值。在本程序中,16bit采样数据是通过short类型变量存储的,而8bit采样数据是通过unsigned char类型存储的。

二 YUV

  YUV 是一种彩色编码系统,主要用在视频、图形处理流水线中(pipeline)。相对于 RGB 颜色空间,设计 YUV 的目的就是为了编码、传输的方便,减少带宽占用和信息出错。它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的频宽。

和RGB转换公式

Y = 0.298R + 0.612G + 0.117B;
U = -0.168R - 0.330G + 0.498B + 128;
V = 0.449R - 0.435G - 0.083B + 128;

R = Y + 1.4075( V - 128);
G = Y - 0.3455( U - 128) - 0.7169( V - 128);
B = Y + 1.779( U - 128);

位深度

  黑白二色的图像是数字图像中最简单的一种,它只有黑、白两种颜色,也就是说它的每个像素只有1位颜色,位深度是1,用2的一次幂来表示;考虑到位深度平均分给R, G, B和Alpha,而只有RGB可以相互组合成颜色。所以4位颜色的图,它的位深度是4,只有2的4次幂种颜色,即16种颜色或16种灰度等级 ) 。8位颜色的图,位深度就是8,用2的8次幂表示,它含有256种颜色 ( 或256种灰度等级 )。24位颜色可称之为真彩色,位深度是24,它能组合成2的24次幂种颜色,即:16777216种颜色 ( 或称千万种颜色 ),超过了人眼能够分辨的颜色数量。当我们用24位来记录颜色时,实际上是以2^(8×3),即红、绿、蓝 ( RGB ) 三基色各以2的8次幂,256种颜色而存在的,三色组合就形成一千六百万种颜色。

存储方式

  在生理学中,有一条规律,那就是人类视网膜上的视网膜杆细胞要多于视网膜锥细胞,说得通俗一些,视网膜杆细胞的作用就是识别亮度,而视网膜锥细胞的作用就是识别色度。所以,你的眼睛对于亮和暗的分辨要比对颜色的分辨精细一些。

  YUV码流的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0,存储方式分俩种:平面格式(planar)和压缩格式(packed)。

  planar格式:先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。

  packed格式:每个像素点的Y,U,V是连续交*存储的。

  用三个图来直观地表示采集的方式吧,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量。


  YUV 4:4:4采样,每一个Y对应一组UV分量。
  YUV 4:2:2采样,每两个Y共用一组UV分量。
  YUV 4:2:0采样,每四个Y共用一组UV分量。
  Cb、Cr的含义等同于U、V。

因为YUV420比较常用, 在这里就重点介绍YUV420。YUV420细分为两类:YUV420p和YUV420sp,具体排列方式又可分以下四种。

I420: YYYYYYYY UU VV    =>YUV420P
YV12: YYYYYYYY VV UU    =>YUV420P
NV12: YYYYYYYY UVUV     =>YUV420SP
NV21: YYYYYYYY VUVU     =>YUV420SP

I420如下



NV12如下


提问:

【例1】根据前面的介绍,如果用 yuv420p 来表示分辨率为 1280 * 720 的图片,位深度为8,需要占用多少存储空间呢?

  每一个像素都需要一个y值。那么总共需要 1280 * 720 = 921600 bytes ; 每四个像素需要一个u 值,那么总共需要 1280 * 720 / 4 = 230400 bytes ; 每四个像素需要一个v 值,那么总共需要 1280 * 720 / 4 = 230400 bytes。
  把 y、u、v 三个 plane 加起来就是:921600 + 230400 + 230400 = 1382400 bytes。

常见算法

(1)分离YUV420P像素数据中的Y、U、V分量

int simplest_yuv420_split(char *url, int w, int h,int num){
FILE *fp=fopen(url,"rb+");
FILE *fp1=fopen("output_420_y.y","wb+");
FILE *fp2=fopen("output_420_u.y","wb+");
FILE *fp3=fopen("output_420_v.y","wb+");
unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
for(int i=0;i

调用方式:

simplest_yuv420_split("lena_256x256_yuv420p.yuv",256,256,1);

原图

YUV单独存储分量的三张图片

  算法分析:如果视频帧的宽和高分别为w和h,那么一帧YUV420P像素数据一共占用wh3/2 Byte的数据。其中前wh Byte存储Y,接着的wh1/4 Byte存储U,最后wh*1/4 Byte存储V。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv420p.yuv的YUV420P格式的像素数据文件分离成为三个文件

output_420_y.y:纯Y数据,分辨率为256x256。
output_420_u.y:纯U数据,分辨率为128x128。
output_420_v.y:纯V数据,分辨率为128x128。

(2)YUV420P像素数据的亮度减半

int simplest_yuv420_halfy(char *url, int w, int h,int num){
FILE *fp=fopen(url,"rb+");
FILE *fp1=fopen("output_half.yuv","wb+");
unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
for(int i=0;i

调用方式:

simplest_yuv420_halfy("lena_256x256_yuv420p.yuv",256,256,1);

原图



处理后



  算法分析:如果打算将图像的亮度减半,只要将图像的每个像素的Y值取出来分别进行除以2的工作就可以了。图像的每个Y值占用1 Byte,取值范围是0至255,对应C语言中的unsigned char数据类型。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv420p.yuv的YUV420P格式的像素数据文件处理成名称为output_half.yuv的YUV420P格式的像素数据文件。

(3) 将YUV420P像素数据的周围加上边框

int simplest_yuv420_border(char *url, int w, int h,int border,int num){
FILE *fp=fopen(url,"rb+");
FILE *fp1=fopen("output_border.yuv","wb+");
unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
for(int i=0;i(w-border)||j(h-border)){
pic[j*w+k]=255;
//pic[j*w+k]=0;
}
}
}
fwrite(pic,1,w*h*3/2,fp1);
}
free(pic);
fclose(fp);
fclose(fp1);
return 0;
}

调用方式:

simplest_yuv420_border("lena_256x256_yuv420p.yuv",256,256,20,1);

原图



处理后



  算法分析:图像的边框的宽度为border,本程序将距离图像边缘border范围内的像素的亮度分量Y的取值设置成了亮度最大值255。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv420p.yuv的YUV420P格式的像素数据文件处理成名称为output_border.yuv的YUV420P格式的像素数据文件。

(4)计算两个YUV420P像素数据的PSNR
  PSNR是最基本的视频质量评价方法。本程序中的函数可以对比两张YUV图片中亮度分量Y的PSNR。

int simplest_yuv420_psnr(char *url1,char *url2,int w,int h,int num){
    FILE *fp1=fopen(url1,"rb+");
    FILE *fp2=fopen(url2,"rb+");
    unsigned char *pic1=(unsigned char *)malloc(w*h);
    unsigned char *pic2=(unsigned char *)malloc(w*h);
    for(int i=0;i

调用方式:

simplest_yuv420_psnr("lena_256x256_yuv420p.yuv","lena_distort_256x256_yuv420p.yuv",256,256,1);

算法分析:对于8bit量化的像素数据来说,PSNR的计算公式如下所示。


上述公式中mse的计算公式如下所示。

  其中M,N分别为图像的宽高,xij和yij分别为两张图像的每一个像素值。PSNR通常用于质量评价,就是计算受损图像与原始图像之间的差别,以此来评价受损图像的质量。本程序输入的两张图像的对比图如下图所示。其中左边的图像为原始图像,右边的图像为受损图像。

  经过程序计算后得到的PSNR取值为26.693。PSNR取值通常情况下都在20-50的范围内,取值越高,代表两张图像越接近,反映出受损图像质量越好。

三 为什么要学习音视频?

音视频作为技术知识点,有以下几大优势

门槛高
  学习音视频的同学想必都遇到找资料难的问题,国内的研究缺乏,国外的资料又看不懂,导致出现要么是音视频新手,要么是音视频大神,很难寻找到循序渐进的资料,导致很多学习的同学望而止步,同样的,别人做不到的你做到才是核心竞争力。

广度,深度
  音视频设计几乎涉及到了我们生活的方方面面,小到拍照录像,大到短视频的应用,可以说学好了音视频,不愁就业,另说深度,无论是安卓,IOS,window等,都提供了简单的API调用,属于浅层,你不会,依然可以完成简单的功能,再深层,如H.264,H.265算法的研究,ffmpeg的熟悉,学无止境。

跨平台
  不同于其他的知识点,音视频的知识适用于任何具有播放功能的设备,无论是安卓,IOS等,音视频知识都是相同的,写好一套兼容性的SDK,就可以给很多平台使用,突破了平台限制。

四 参考资料
https://blog.csdn.net/leixiaohua1020/article/details/50534150
https://blog.csdn.net/leixiaohua1020/article/details/50534316
https://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.html

你可能感兴趣的:(PCM,YUV数据详解和常用算法)