三、PCM & WAV 开发实践
1. PCM格式转为WAV格式(基于C语言)int simplest_pcm16le_to_wave(const char *pcmpath,int channels,int sample_rate,const char *wavepath)
{
typedef struct WAVE_HEADER{
char fccID[4];
unsigned long dwSize;
char fccType[4];
}WAVE_HEADER;
typedef struct WAVE_FMT{
char fccID[4];
unsigned long dwSize;
unsigned short wFormatTag;
unsigned short wChannels;
unsigned long dwSamplesPerSec;
unsigned long dwAvgBytesPerSec;
unsigned short wBlockAlign;
unsigned short uiBitsPerSample;
}WAVE_FMT;
typedef struct WAVE_DATA{
char fccID[4];
unsigned long dwSize;
}WAVE_DATA;
if(channels==0||sample_rate==0){
channels = 2;
sample_rate = 44100;
}
int bits = 16;
WAVE_HEADER pcmHEADER;
WAVE_FMT pcmFMT;
WAVE_DATA pcmDATA;
unsigned short m_pcmData;
FILE *fp,*fpout;
fp=fopen(pcmpath, "rb");
if(fp == NULL) {
printf("open pcm file error\n");
return -1;
}
fpout=fopen(wavepath, "wb+");
if(fpout == NULL) {
printf("create wav file error\n");
return -1;
}
//WAVE_HEADER
memcpy(pcmHEADER.fccID,"RIFF",strlen("RIFF"));
memcpy(pcmHEADER.fccType,"WAVE",strlen("WAVE"));
fseek(fpout,sizeof(WAVE_HEADER),1);
//WAVE_FMT
pcmFMT.dwSamplesPerSec=sample_rate;
pcmFMT.dwAvgBytesPerSec=pcmFMT.dwSamplesPerSec*sizeof(m_pcmData);
pcmFMT.uiBitsPerSample=bits;
memcpy(pcmFMT.fccID,"fmt ",strlen("fmt "));
pcmFMT.dwSize=16;
pcmFMT.wBlockAlign=2;
pcmFMT.wChannels=channels;
pcmFMT.wFormatTag=1;
fwrite(&pcmFMT,sizeof(WAVE_FMT),1,fpout);
//WAVE_DATA;
memcpy(pcmDATA.fccID,"data",strlen("data"));
pcmDATA.dwSize=0;
fseek(fpout,sizeof(WAVE_DATA),SEEK_CUR);
fread(&m_pcmData,sizeof(unsigned short),1,fp);
while(!feof(fp)){
pcmDATA.dwSize+=2;
fwrite(&m_pcmData,sizeof(unsigned short),1,fpout);
fread(&m_pcmData,sizeof(unsigned short),1,fp);
}
pcmHEADER.dwSize=44+pcmDATA.dwSize;
rewind(fpout);
fwrite(&pcmHEADER,sizeof(WAVE_HEADER),1,fpout);
fseek(fpout,sizeof(WAVE_FMT),SEEK_CUR);
fwrite(&pcmDATA,sizeof(WAVE_DATA),1,fpout);
fclose(fp);
fclose(fpout);
return 0;
}
注意:函数里声明的数据类型 unsigned long 在有些C编译器上是64位的,这时候要改成unsigned int才可以,否则wav头有88bytes,标准的是44bytes,改完就正常了,对C还不熟悉的人小小的心得。 另外,声道数和采样率也要注意,一般采样率有44100/16000/8000,要确认是哪个,声道是1还是2,这两个参数要设置好才会有正确的转换结果。
2. PCM降低某个声道的音量(基于C语言)一般来说 PCM 数据中的波形幅值越大,代表音量越大,对于 PCM 音频数据而言,它的幅值(即该采样点采样值的大小)代表音量的大小。
如果我们需要降低某个声道的音量,可以通过减小某个声道的数据的值来实现降低某个声道的音量。
int pcm16le_half_volume_left( char *url ) {
FILE *fp_in = fopen( url, "rb+" );
FILE *fp_out = fopen( "output_half_left.pcm", "wb+" );
unsigned char *sample = ( unsigned char * )malloc(4); // 一次读取一个sample,因为是2声道,所以是4字节
while ( !feof( fp_in ) ){
fread( sample, 1, 4, fp_in );
short* sample_num = ( short* )sample; // 转成左右声道两个short数据
*sample_num = *sample_num / 2; // 左声道数据减半
fwrite( sample, 1, 2, fp_out ); // L
fwrite( sample + 2, 1, 2, fp_out ); // R
}
free( sample );
fclose( fp_in );
fclose( fp_out );
return 0;
}
上述代码做的事情是:在读出左声道的 2 Byte 的取样值之后,将其转成了 C 语言中的一个 short 类型的变量。将该数值除以 2 之后写回到了 PCM 文件中。
3. 分离PCM音频数据左右声道的数据因为 PCM 音频数据是按照 LRLRLR 的方式来存储左右声道的音频数据的,所以我们可以通过将它们交叉的读出来的方式来分离左右声道的数据:
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;
}
4. 从PCM16LE单声道音频采样数据中截取一部分数据本程序中的函数可以从PCM16LE单声道数据中截取一段数据,并输出截取数据的样值。函数的代码如下所示:
/**
* Cut a 16LE PCM single channel file.
* @param url Location of PCM file.
* @param start_num start point
* @param dur_num how much point to cut
*/
int simplest_pcm16le_cut_singlechannel(char *url,int start_num,int dur_num){
FILE *fp=fopen(url,"rb+");
FILE *fp1=fopen("output_cut.pcm","wb+");
FILE *fp_stat=fopen("output_cut.txt","wb+");
unsigned char *sample=(unsigned char *)malloc(2);
int cnt=0;
while(!feof(fp)){
fread(sample,1,2,fp);
if(cnt>start_num&&cnt<=(start_num+dur_num)){
fwrite(sample,1,2,fp1);
short samplenum=sample[1];
samplenum=samplenum*256;
samplenum=samplenum+sample[0];
fprintf(fp_stat,"%6d,",samplenum);
if(cnt%10==0)
fprintf(fp_stat,"\n",samplenum);
}
cnt++;
}
free(sample);
fclose(fp);
fclose(fp1);
fclose(fp_stat);
return 0;
}
5. 将PCM16LE双声道音频采样数据转换为PCM8音频采样数据本程序中的函数可以通过计算的方式将PCM16LE双声道数据16bit的采样位数转换为8bit。函数的代码如下所示:
/**
* Convert PCM-16 data to PCM-8 data.
* @param url Location of PCM file.
*/
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类型存储的。
6. 将PCM16LE双声道音频采样数据的声音速度提高一倍本程序中的函数可以通过抽象的方式将PCM16LE双声道数据的速度提高一倍,采用采样每个声道奇(偶)数点的样值的方式,函数的代码如下所示:
/**
* Re-sample to double the speed of 16LE PCM file
* @param url Location of PCM file.
*/
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;
}
参考资料:视音频数据处理入门:PCM音频采样数据处理 --> 致敬雷神!
作者:灰色飘零 来源:https://www.cnblogs.com/renhui/p/12148330.html
|