PCM和WAV是我们常用的两种音频数据格式:

一般包含以下几大要素:

采样率
在我的另一篇博文 音频编码 中已经介绍了采样和量化的概念,这里介绍一下采样率。

采样率表示音频信号每秒的数字快照数。该速率决定了音频文件的频率范围。采样率越高,数字波形的形状越接近原始模拟波形。低采样率会限制可录制的频率范围,这可导致录音表现原始声音的效果不佳。

根据 奈奎斯特采样定理,为了重现给定频率,采样率必须至少是该频率的两倍。例如,CD 的采样率为每秒 44,100 个采样,因此可重现最高为 22,050 Hz 的频率,此频率刚好超过人类的听力极限 20,000 Hz。

A. 使原始声波扭曲的低采样率
B. 完全重现原始声波的高采样率

数字音频常用的采样率:

位深度

位深度决定动态范围。采样声波时,为每个采样指定最接近原始声波振幅的振幅值。较高的位深度可提供更多可能的振幅值,产生更大的动态范围、更低的噪声基准和更高的保真度。

位深度越高,提供的动态范围越大。

PCM 音频数据

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

PCM 音频数据的存储

如果是单声道的音频文件,采样数据按时间的先后顺序依次存入(有的时候也会采用 LRLRLR 方式存储,只是另一个声道的数据为 0),如果是双声道的话通常按照 LRLRLR 的方式存储,存储的时候还和机器的大小端有关。大端模式如下图所示:

PCM 音频数据是未经压缩的数据,所以通常都比较大,常见的 MP3 格式都是经过压缩的,128Kbps 的 MP3 压缩率可以达到 1:11

PCM 音频数据的参数

一般我们描述 PCM 音频数据的参数的时候有如下描述方式:

44100HZ 16bit stereo: 每秒钟有 44100 次采样, 采样数据用 16 位(2 字节)记录, 双声道(立体声)
22050HZ 8bit  mono: 每秒钟有 22050 次采样, 采样数据用 8 位(1 字节)记录, 单声道
48000HZ 32bit 51ch: 每秒钟有 48000 次采样, 采样数据用 32 位(4 字节浮点型)记录, 5.1 声道

44100Hz 指的是采样率,它的意思是每秒取样 44100 次。采样率越大,存储数字音频所占的空间就越大。

16bit 指的是采样精度,意思是原始模拟信号被采样后,每一个采样点在计算机中用 16 位(两个字节)来表示。采样精度越高越能精细地表示模拟信号的差异。

Stereo 指的是声道数,也即采样时用到的麦克风的数量,麦克风越多就越能还原真实的采样环境(当然麦克风的放置位置也是有规定的)。

一般来说 PCM 数据中的波形幅值越大,代表音量越大。

PCM 音频数据的处理
降低某个声道的音量1
因为对于 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 ); // Lfwrite( sample + 2, 1, 2, fp_out ); // R}free( sample );fclose( fp_in );fclose( fp_out );return 0;
}

从源代码可以看出,本程序在读出左声道的 2 Byte 的取样值之后,将其转成了 C 语言中的一个 short 类型的变量。将该数值除以 2 之后写回到了 PCM 文件中。下图为输入 PCM 双声道音频采样数据的波形图。

下图为输出的左声道经过处理后的波形图。可以看出左声道的波形幅度降低了一半。

PCM → WAV
WAV 是 Microsoft 和 IBM 为 PC 开发的一种声音文件格式,它符合 RIFF(Resource Interchange File Format)文件规范,用于保存 Windows 平台的音频信息资源,被 Windows 平台及其应用程序所广泛支持。WAVE 文件通常只是一个具有单个 “WAVE” 块的 RIFF 文件,该块由两个子块(”fmt” 子数据块和 ”data” 子数据块),它的格式如下图所示:

WAV 格式定义

该格式的实质就是在 PCM 文件的前面加了一个文件头,每个字段的的含义如下:

typedef struct {char          ChunkID[4]; //内容为"RIFF"unsigned long ChunkSize;  //存储文件的字节数(不包含ChunkID和ChunkSize这8个字节)char          Format[4];  //内容为"WAVE“
} WAVE_HEADER;typedef struct {char           Subchunk1ID[4]; //内容为"fmt"unsigned long  Subchunk1Size;  //存储该子块的字节数(不含前面的Subchunk1ID和Subchunk1Size这8个字节)unsigned short AudioFormat;    //存储音频文件的编码格式,例如若为PCM则其存储值为1。unsigned short NumChannels;    //声道数,单声道(Mono)值为1,双声道(Stereo)值为2,等等unsigned long  SampleRate;     //采样率,如8k,44.1k等unsigned long  ByteRate;       //每秒存储的bit数,其值 = SampleRate * NumChannels * BitsPerSample / 8unsigned short BlockAlign;     //块对齐大小,其值 = NumChannels * BitsPerSample / 8unsigned short BitsPerSample;  //每个采样点的bit数,一般为8,16,32等。
} WAVE_FMT;typedef struct {char          Subchunk2ID[4]; //内容为“data”unsigned long Subchunk2Size;  //接下来的正式的数据部分的字节数,其值 = NumSamples * NumChannels * BitsPerSample / 8
} WAVE_DATA;

WAV 文件头解析

这里是一个 WAVE 文件的开头 72 字节,字节显示为十六进制数字:

52 49 46 46 | 24 08 00 00 | 57 41 56 45
66 6d 74 20 | 10 00 00 00 | 01 00 02 00
22 56 00 00 | 88 58 01 00 | 04 00 10 00
64 61 74 61 | 00 08 00 00 | 00 00 00 00
24 17 1E F3 | 3C 13 3C 14 | 16 F9 18 F9
34 E7 23 A6 | 3C F2 24 F2 | 11 CE 1A 0D

字段解析如下图:

PCM → WAV 代码1

int simplest_pcm16le_to_wave( const char *pcmpath, int channels, int sample_rate, const char *wavepath )
{ // 省去错误判断short pcmData;FILE* fp = fopen( pcmpath, "rb" );FILE* fpout = fopen( wavepath, "wb+" );// 填充 WAVE_HEADERWAVE_HEADER pcmHEADER;memcpy( pcmHEADER.ChunkID, "RIFF", strlen( "RIFF" ) );memcpy( pcmHEADER.Format, "WAVE", strlen( "WAVE" ) );fseek( fpout, sizeof( WAVE_HEADER ), 1 );//填充 WAVE_FMT WAVE_FMT pcmFMT;pcmFMT.SampleRate = sample_rate;pcmFMT.ByteRate = sample_rate * sizeof( pcmData );pcmFMT.BitsPerSample = 8 * sizeof( pcmData );memcpy( pcmFMT.Subchunk1ID, "fmt ", strlen( "fmt " ) );pcmFMT.Subchunk1Size = 16;pcmFMT.BlockAlign = channels * sizeof( pcmData );pcmFMT.NumChannels = channels;pcmFMT.AudioFormat = 1;fwrite( &pcmFMT, sizeof( WAVE_FMT ), 1, fpout );//填充 WAVE_DATA;WAVE_DATA pcmDATA;memcpy( pcmDATA.Subchunk2ID, "data", strlen( "data" ) );pcmDATA.Subchunk2Size = 0;fseek( fpout, sizeof( WAVE_DATA ), SEEK_CUR );fread( &m_pcmData, sizeof( short ), 1, fp );while ( !feof( fp ) ) {pcmDATA.dwSize += 2;fwrite( &m_pcmData, sizeof( short ), 1, fpout );fread( &m_pcmData, sizeof( short ), 1, fp );}int headerSize = sizeof( pcmHEADER.Format ) + sizeof( WAVE_FMT ) + sizeof( WAVE_DATA ); // 36pcmHEADER.ChunkSize = headerSize + pcmDATA.Subchunk2Size;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;
}

PCM和WAV数据结构详解相关推荐

  1. 万字长文的Redis五种数据结构详解(理论+实战),建议收藏。

    本文脑图 前言 Redis是基于c语言编写的开源非关系型内存数据库,可以用作数据库.缓存.消息中间件,这么优秀的东西一定要一点一点的吃透它. 关于Redis的文章之前也写过三篇,阅读量和读者的反映都还 ...

  2. redis数据结构详解之Hash(四)

    原文:redis数据结构详解之Hash(四) 序言 Hash数据结构累似c#中的dictionary,大家对数组应该比较了解,数组是通过索引快速定位到指定元素的,无论是访问数组的第一个元素还是最后一个 ...

  3. Python中的高级数据结构详解

    这篇文章主要介绍了Python中的高级数据结构详解,本文讲解了Collection.Array.Heapq.Bisect.Weakref.Copy以及Pprint这些数据结构的用法,需要的朋友可以参考 ...

  4. [转]Redis内部数据结构详解-sds

    本文是<Redis内部数据结构详解>系列的第二篇,讲述Redis中使用最多的一个基础数据结构:sds. 不管在哪门编程语言当中,字符串都几乎是使用最多的数据结构.sds正是在Redis中被 ...

  5. Redis数据结构详解之Zset(五)

    原文:Redis数据结构详解之Zset(五) 序言 Zset跟Set之间可以有并集运算,因为他们存储的数据字符串集合,不能有一样的成员出现在一个zset中,但是为什么有了set还要有zset呢?zse ...

  6. 【肝帝一周总结:全网最全最细】☀️Mysql 索引数据结构详解与索引优化☀️《❤️记得收藏❤️》

    [肝帝一周总结:全网最全最细]☀️Mysql 索引数据结构详解与索引优化☀️<❤️记得收藏❤️> 目录

  7. [redis] 10 种数据结构详解

    [redis] 10 种数据结构详解 简介 5种常见数据结构 string: 最常见的 string key value list: 双向链表 set: 集合- zset: 有序集合 hash: 类似 ...

  8. 小猫爪:嵌入式小知识10-I2S,TDM,PCM等音频格式详解

    小猫爪:嵌入式小知识10-I2S,TDM,PCM等音频格式详解 1 前言 2 I2S 3 Codec模式(左/右对齐) 3.1 左对齐(MSB对齐) 3.2 右对齐(LSB对齐) 4 DSP模式 5 ...

  9. PCM和WAV数据结构

    PCM 和 WAV 数据结构 采样率 数字音频常用的采样率 位深度 PCM 音频数据 PCM 音频数据的存储 PCM 音频数据的参数 PCM 音频数据的处理 降低某个声道的音量[^1] PCM → W ...

最新文章

  1. 在网络通讯中,如何自己分配可用的端口号和获取自己的ip地址
  2. 【特征提取+分类模型】4种常见的NLP实践思路
  3. Bengio参与、LeCun点赞:图神经网络权威基准现已开源
  4. Spring框架入门基础,不可多得的干货
  5. nginx curl命令有效 curl_setopt无效_日志分析系列(外传一):Nginx透过代理获取真实客户端IP...
  6. 研究c语言,研究c语言计算机编程实验.docx
  7. 如何优雅的升级内核?
  8. 《HTML5和CSS3快速参考》——1.3HTML5的品牌化
  9. IDEA、Eclipse的详细安装配置及Tomcat服务集成介绍
  10. cannot load such file -- readline
  11. mysql 如何对表排序_学习MySQL:对表中的数据进行排序和过滤
  12. BAT 面试中,遇到知识盲点如何巧妙圆场?
  13. Mysql的master,slave的配置
  14. 用selenium爬取斗鱼信息
  15. 基于SSM+SpringBoot+MySQL的社区管理系统
  16. blowfish算法c语言,blowfish-c源代码(简陋).doc
  17. PDA只需扫描条码能够完成库存盘点
  18. MFC DLL 不能正确调用的问题 + AFX_MANAGE_STATE(AfxGetStaticModuleState());
  19. 人生感悟:影响人成功和幸福的21点
  20. 强烈安利一波,程序猿学习网站

热门文章

  1. jhipster利用JDL文件生成User、Role、userRole的注意事项及操作方法
  2. 文本识别--Focusing Attention: Towards Accurate Text Recognition in Natural Images
  3. Bailian4105 拯救公主【BFS】
  4. 数据库错误 Table 'highmysql.myemp' doesn't exist
  5. 数据解压-----续赫夫曼编码
  6. quartus II【如何进行顶层设计】
  7. [003]-css实现啊飘
  8. e820内存数据处理
  9. 高企认定八个条件财务指标考核评分?
  10. Django 模型中的 choices 字段的使用