PCM格式音频

Posted shuangmu9768

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PCM格式音频相关的知识,希望对你有一定的参考价值。

PCM(Pulse Code Modulation)编码,即通过脉冲编码调制方法生成数字音频数据的技术或格式,是一种无损编码格式,是音频模拟信号数字化的一种方法,需要经过采样、量化和编码过程,以实现音频模拟信号数字化。

  • 可以从6个方面描述PCM:
    1.采样率;
    2.符号:表示样本数据是否是有符号位,比如用一字节表示的样本数据,有符号的话表示范围为-128 … 127,无符号就是0 … 255;
    3.字节序:字节序分为大端与小端;
    4.样本大小:决定了每个样本由多少位组成,即前面说到的量化深度,一般16位是最常见的;
    5.声道数:常见的有单声道与双声道。
    6.整形或浮点型:大多数格式的PCM样本数据使用整形表示,然而在一些对精度要求高的应用方面,使用浮点类型表示PCM样本数据。

  • 音调、音量、音色
    音调:声音频率的高低。表示人的听觉分辨一个声音的调子高低的程度。音调主要由声音的频率决定,同时也与声音强度有关
    音量:人主观上感觉声音的大小,由“振幅”(amplitude)和人离声源的距离决定,振幅越大响度越大,人和声源的距离越小,响度越大。(单位:分贝dB)
    音色:又称声音的品质,波形决定了声音的音色。声音因不同物体材料的特性而具有不同特性,音色本身是一种抽象的东西,但波形是把这个抽象直观的表现。音色不同,波形则不同。典型的音色波形有方波,锯齿波,正弦波,脉冲波等。不同的音色,通过波形,完全可以分辨的。

  • 有符号数与无符号数互转(内存中存储的值不变,只是改变了解释这些位置的方式)

    有符号byte转无符号int:
    byte b= -120;
    int a= b & 0xff

    无符号byte转有符号byte:
    byte a=(byte)无符号数 #直接强转

  • 采样精度转换

    无符号8位PCM转有符号16位:
    方式一:((byte)(val+128))<<8
    方式二:(val-128)<<8

    有符号16位PCM转无符号8位:
    方式一:parseInt(255 / (65535 / (val + 32768))
    方式二:(val>>8)+128
    方式三:((val + 32768)>>8) & 0xFF

  • 分贝计算
    dB = 20 * log(A1 / A2) #以无符号16位的数值即可计算出大致的分贝,96.32dB=20*lg(65535);
    A1 = A2 * pow(10 , db/20)
    计算得知:每增大2个分贝,则在原振幅的基础上扩大约1.2589倍;

  • 混音算法
    1.线性叠加后求平均
    优点:不会产生溢出,噪音较小;
    缺点:衰减过大,影响通话质量;
    2.归一化混音(自适应加权混音算法)
    思路:使用更多的位数(32 bit)来表示音频数据的一个样本,混完音后在想办法降低其振幅,使其仍旧分布在16 bit所能表示的范围之内,这种方法叫做归一法;
    方法:为避免发生溢出,使用一个可变的衰减因子对语音进行衰减。这个衰减因子也就代表语音的权重,衰减因子随着音频数据的变化而变化,所以称为自适应加权混音。当溢出时,衰减因子较小,使得溢出的数据在衰减后能够处于临界值以内,而在没有溢出时,又让衰减因子慢慢增大,使数据较为平缓的变化。
    3.从newlc上找到的一个关于PCM脉冲编码的音频信号的混音实现:
    if( data1 < 0 && data2 < 0)
    date_mix = data1+data2 - (data1 * data2 / -(pow(2,16-1)-1));
    else
    date_mix = data1+data2 - (data1 * data2 / (pow(2,16-1)-1));
    4.切割时间片,重采样算法
    可以把各个通道的声音叠到一起,让声音的采样率按倍增加,如果提高声音的播放频率,声音可以正常的播放,声音实现了叠加;如果不想修改声音的播放输出频率,可以通过声音的重采样后输出自己想要的输出频率;
    5.自适应混音算法
    参与混音的多路音频信号自身的特点,以它们自身的比例作为权重,从而决定它们在合成后的输出中所占的比重。
    具体的原理可以参考这篇论文:快速实时自适应混音方案研究。
    这种方法对于音轨路数比较多的情况应该会比线性叠加后求平均法要好,但是可能会引入噪音。

  • 大端模式和小端模式的区别
    大端模式中字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中;
    而与大端存储模式相反,在小端存储模式中,低地址中存放的是字数据的低字节,高地址存放的是字数据的高字节。
    定义数组时,其元素的地址从低到高紧密排列。

PCM编码格式

目录

一、PCM音频编码格式

1. 相关英文单词

2. PCM数据结构

2.1 单声道数据

2.2 多声道数据

2.3 小结

二、WAV音频文件格式


一、PCM音频编码格式

1. 相关英文单词

Amplitude:幅度

PAM:Pulse Amplitude Modulation 脉冲幅度调制(数字信号过程采样)

Stereo:立体声(双声道),Mono:单声道

PCM:Pulse Code Modulation

Sampling:采样        Quantization:量化           Code:编码

Nyquist Sampling theorem:奈奎斯特采样定理

Analog Signal:模拟信号   Digital Signal:数字信号

Sample rate:采样率(每秒钟的样本点数)sampleing bit depth:采样位深度

Channel:通道数/声道数

(1)PCM是最原始的音频编码格式,并不是文件格式,WAV/MP3等才是文件格式。

(2)采样率:每秒钟的采样次数/每秒钟的样本点数,即一秒钟能采集到多少个离散的数值点,比如:16KHz=16000Hz,意思就是16000个采样点/s,采样率是保证音质的一个重要参数。

(3)采样位深度是每一个采样点的值的bit数,很容易理解,bit越多,能表示的数范围就越广,更加能表示模拟信号,可以直观理解,如果采样率和位深都很大,采样的离散序列就可以近似等于模拟信号。

8bit:256个值,从0~255

16bit:65536个值,从0~65535

24bit:………………………………

32bit:………………………………….

2. PCM数据结构

(1)关于音频数据流

编码之后的PCM数据流是一串0和1的组合:0011000010101100……

所以,就要根据一些相关信息来解析数据。假如采样位深是8bit,且是有符号的数据。那么数据解析如下:

0011 0000    1010 1100……

第一个字节,除掉最高位符号位后,值就是+48

第二个字节,除掉最高位符号位后,值就是-44

在范围-128~127之内。

一般来说,常用的是16bit的位深,在C语言中用short类型(两字节)的数据来表示即可。

2.1 单声道数据

实例:读取一个32KHz,单声道,16bit的PCM音频文件数据到内存,并再次写入新文件。

int main(int argc, char** argv)

	int i, j, count;
	FILE *fpin = NULL;
	FILE *fpout = NULL;

	fpin = fopen("input.pcm", "rb"); //单声道
	fpout = fopen("output.pcm", "wb");
	fseek(fpin, 0, SEEK_END);
	long inputdata_length = ftell(fpin);
	inputdata_length /= 2;
	printf("input_file_len:% ld\\n", inputdata_length);
	short *stream_in = (short *)malloc(inputdata_length * sizeof(short));
	//读出数据流(0/1数据流--->字节数据)
	rewind(fpin);
	count = fread(stream_in, sizeof(short), inputdata_length, fpin);
	printf("count=%d\\n", count);

	//将数据写入文件
	fwrite(stream_in, sizeof(short), inputdata_length, fpout);
	fclose(fpin);
	fclose(fpout);
	free(stream_in);
	printf("process finished.\\r\\n");
	return 0;

注:

(1)音频数据一般都有符号,可以直接打印出每一个采样点的音频数据:printf(“stream_in[%d]=%hd\\n”, 100, stream_in[100]);

(2) 16bit的深度,所以缓存空间的数据类型定义成short,刚好合适

(3) 代码运行结束,生成的文件大小没变,可使用cooledit pro打开对比查看波形图

2.2 多声道数据

(1)数据排布

对于多声道的数据流,声道数据会交叉排列。当然,这跟底层驱动程序相关,比如一些ADC采集模块,stereo数据排布就是这样的。

第1个byte(左声道)/第2个byte(右)/第3个byte(左)/第4个byte(左)……

(2)关于字节序

数据是大端存储结构还是小端存储结构,一般使用的是小端存储结构(Little Endian)。如下图:

 实例:分离双声道PCM文件数据(LE/44100Hz/16bi/立体声)

int main(int argc, char** argv)

	int i, j, count;
	FILE *fpin = NULL;
	FILE *fp_l = NULL;
	FILE *fp_r = NULL;

	fpin = fopen("44100_2ch_stereo.pcm", "rb");
	fp_l = fopen("left.pcm", "wb");
	fp_r = fopen("right.pcm", "wb");

	fseek(fpin, 0, SEEK_END);
	int inputdata_length = ftell(fpin);
	inputdata_length = inputdata_length/2;  //16bit
	printf("input_file_len:% d\\n", inputdata_length);

	short *stream_in = (short *)malloc(inputdata_length * sizeof(short));
	short *stream_l  = (short *)malloc((inputdata_length/2) * sizeof(short));
	short *stream_r  = (short *)malloc((inputdata_length/2) * sizeof(short));

	//读出数据流(数据流--->字节数据)
	rewind(fpin);
	count = fread(stream_in, sizeof(short), inputdata_length, fpin);
	printf("count=%d\\n", count);


	//分离左右声道数据
	int loop=inputdata_length/2;
	short *dat_ptr=stream_in;
	for(int i=0; i<loop; i=i+2) 
		stream_l[i]=*dat_ptr;
		stream_l[i+1]=*(dat_ptr+1);
		dat_ptr=dat_ptr+2;
		stream_r[i]=*dat_ptr;
		stream_r[i+1]=*(dat_ptr+1);
		dat_ptr=dat_ptr+2;
	

	//将左右声道数据写入文件
	fwrite(stream_l, sizeof(short), inputdata_length/2, fp_l);
	fwrite(stream_r, sizeof(short), inputdata_length/2, fp_r);
	fclose(fpin);
	fclose(fp_l);
	fclose(fp_r);
	free(stream_in);
	free(stream_l);
	free(stream_r);

	printf("process finished.\\r\\n");
	return 0;

文件大小和数据量减小一半,左右声道均选择44100Hz的采样率进行播放,声音正常。

 

2.3 小结

(1)s16le

s-----signed,就是有符号数据,根据采样位深度,可以知道数值范围,所以在音频处理的时候,需要注意数据是否溢出

16----采样位深16bit

 le----小端存储

(2)PC的声卡一般是16bit或者24bit

(3)PCM不是音频文件格式,因此一般的音乐播放软件无法打开,需要保存为wav或者其他格式(加个文件头)才行。

二、WAV音频文件格式

关于wav音频文件格式不放在这里解析,算法处理过程中(实际上算法一般都是针对PCM原始编码格式进行),需要注意下面几点即可:

  1. 构成:文件头+数据部分。
  2. 文件头占44个字节,后面就是数据部分,读取音频数据时,要注意偏移。
  3. 音频部分的编码可以是PCM,也可以是其他格式。

实例:读取wav音频文件数据,伪代码如下

/* 01. 读取音频数据 */

fp_in = fopen("clean.wav", "rb");

fseek(fp_in, 0, SEEK_END);

long clean_len = ftell(fp_in);

clean_len = clean_len -44; //减掉文件头

clean_len /= 2;

printf("clean voice len:% ld\\n", clean_len);

short *clean_buf = (short *)malloc(clean_len * sizeof(short)); //申请音频数据存储空间

rewind(fp_in);

//先偏移,跳过文件头再读数据

fseek(fp_in, 44, SEEK_SET);

count = fread(clean_buf, sizeof(short), clean_len, fp_in);

/* 02. 分帧,加窗,傅里叶变换到频域,计算 */

/* 03. 还原到时域 */

/* 04. 写入新文件(注意文件头部信息的写入) */

以上是关于PCM格式音频的主要内容,如果未能解决你的问题,请参考以下文章

PCM格式音频

如何将 PCM 音频流转换为在线播放

在 Python 中检测并录制音频为 PCM 格式

音频数据文件格式(PCM,WAV,MIDI)简记

PCM编码格式

大多数(如果不是全部)音频端点是 PCM 吗?