diff --git a/README.md b/README.md index efbd26c..9aa679d 100644 --- a/README.md +++ b/README.md @@ -542,6 +542,105 @@ int main() 从源代码可以看出,本程序只采样了每个声道偶数点的样值。处理完成后,原本22秒左右的音频变成了11秒左右。音频的播放速度提高了2倍,音频的音调也变高了很多。 +
+
+ +## 将PCM16LE双声道音频采样数据转换为PCM8音频采样数据 +> 注:本文中声音样值的采样频率一律是44100Hz,采样格式一律为16LE。“16”代表采样位数是16bit。由于1Byte=8bit,所以一个声道的一个采样值占用2Byte。“LE”代表Little Endian,代表2 Byte采样值的存储方式为高位存在高地址中。 + +``` C++ +// +//本程序中的函数可以通过计算的方式将PCM16LE双声道数据16bit的采样位数转换为8bit。 +// + +#include +#include +#include + + +int pcm16le_to_pcm8(const char *file) +{ + if (file == NULL) { + printf("原始PCM文件为空!\n"); + return 0; + } + + FILE *fp = fopen(file, "rb+"); + if (fp == NULL) { + printf("原始PCM文件打开失败!\n"); + return 0; + } + + FILE *fp1 = fopen("./output/pcm16le_to_pcm8.pcm", "wb+"); + if (fp1 == NULL) { + printf("文件打开或创建失败!\n"); + return 0; + } + + unsigned char buf[4] = {0}; + + while ( !feof(fp) ) { + //从文件中读取一次采样值,因为是16位的,所以需读取4个字节 + //左右声道采样值间隔存储,前两个字节为左声道采样值,后两个字节为右声道采样值 + fread(buf, 1, 4, fp); + + //将前两个字节(左声道采样值)强制转换为 short类型,因为short类型长度为两个字节 + short *sample = (short *)buf; + + //右移8位,相当于除以256(2的8次方) + //将pcm16(short类型)的值以256为除数取模,作为pcm8的采样值 + unsigned char pcm8 = (*sample) >> 8; + + //因为short类型的范围为-32768~32767,经过上一步获得的结果为-128~127 + //所以转成unsigned char需要加上128,unsigned char类型的范围为0~255 + pcm8 = pcm8 + 128; + + //写入左声道的采样值 + fwrite(&pcm8, 1, 1, fp1); + + //将前两个字节(右声道采样值)强制转换为 short类型 + sample = (short *)(buf + 2); + + pcm8 = (*sample) >> 8; + + //-128~127 => 0~128 + pcm8 = pcm8 + 128; + + //写入右声道的采样值 + fwrite(&pcm8, 1, 1, fp1); + } + + fclose(fp); + fclose(fp1); + + return 1; +} + + +int main() +{ + char file[] = "./mediadata/NocturneNo2inEflat_44.1k_s16le.pcm"; + if (pcm16le_to_pcm8(file)) { + printf("操作成功!\n"); + } else { + printf("操作失败!\n"); + } + + 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类型存储的。 + + + +
+
+ + + + +

diff --git a/pcm16le_to_pcm8.cpp b/pcm16le_to_pcm8.cpp new file mode 100644 index 0000000..d18f226 --- /dev/null +++ b/pcm16le_to_pcm8.cpp @@ -0,0 +1,79 @@ +// +//本程序中的函数可以通过计算的方式将PCM16LE双声道数据16bit的采样位数转换为8bit。 +// + +#include +#include +#include + + +int pcm16le_to_pcm8(const char *file) +{ + if (file == NULL) { + printf("原始PCM文件为空!\n"); + return 0; + } + + FILE *fp = fopen(file, "rb+"); + if (fp == NULL) { + printf("原始PCM文件打开失败!\n"); + return 0; + } + + FILE *fp1 = fopen("./output/pcm16le_to_pcm8.pcm", "wb+"); + if (fp1 == NULL) { + printf("文件打开或创建失败!\n"); + return 0; + } + + unsigned char buf[4] = {0}; + + while ( !feof(fp) ) { + //从文件中读取一次采样值,因为是16位的,所以需读取4个字节 + //左右声道采样值间隔存储,前两个字节为左声道采样值,后两个字节为右声道采样值 + fread(buf, 1, 4, fp); + + //将前两个字节(左声道采样值)强制转换为 short类型,因为short类型长度为两个字节 + short *sample = (short *)buf; + + //右移8位,相当于除以256(2的8次方) + //将pcm16(short类型)的值以256为除数取模,作为pcm8的采样值 + unsigned char pcm8 = (*sample) >> 8; + + //因为short类型的范围为-32768~32767,经过上一步获得的结果为-128~127 + //所以转成unsigned char需要加上128,unsigned char类型的范围为0~255 + pcm8 = pcm8 + 128; + + //写入左声道的采样值 + fwrite(&pcm8, 1, 1, fp1); + + //将前两个字节(右声道采样值)强制转换为 short类型 + sample = (short *)(buf + 2); + + pcm8 = (*sample) >> 8; + + //-128~127 => 0~128 + pcm8 = pcm8 + 128; + + //写入右声道的采样值 + fwrite(&pcm8, 1, 1, fp1); + } + + fclose(fp); + fclose(fp1); + + return 1; +} + + +int main() +{ + char file[] = "./mediadata/NocturneNo2inEflat_44.1k_s16le.pcm"; + if (pcm16le_to_pcm8(file)) { + printf("操作成功!\n"); + } else { + printf("操作失败!\n"); + } + + return 0; +} \ No newline at end of file