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. 写入新文件(注意文件头部信息的写入) */

物联沃分享整理
物联沃-IOTWORD物联网 » PCM编码:探索数字音频的完美之旅

发表评论