文档章节

【wav音频解析】之wavread函数的C++实现

刘小米_思聪
 刘小米_思聪
发布于 2014/09/29 10:39
字数 2783
阅读 12780
收藏 34

      本文由三部分组成,第一部分背景介绍 —— 音频类型及本文动机,第二部分类比matlab下wavread()函数的作用,第三部分则给出该函数的C++实现。

一 背景介绍

1.1 本文动机

1)所有wav音频处理的基础就是将wav格式的文件解析出来,解析成数组才能供我们去做后续的处理(fft等等)。

2)在matlab中直接有一个很好用的函数wavread(' test.wav'),输入是wav音频,输出是数组,如第二章所述。

3)一般的C++函数读取出来的数据,格式如1.2节所述,然而不管是什么格式,数据之间是可互相转换的。

4) 我在解决问题的过程中,没有发现一篇详细的参考文献。

      鉴于此,本文将介绍如何用C++完全实现matlab的wavread函数,输出数据格式一模一样,在这个过程中,大家也可以领略文件中数据的本质,及相互间的转换关系。

1.2 音频类型

RIFF全称为资源互换文件格式(ResourcesInterchange FileFormat),RIFF文件是windows环境下大部分多媒体文件遵循的一种文件结构,RIFF文件所包含的数据类型由该文件的扩展名来标识,能以RIFF文件存储的数据包括:音频视频交错格式数据(.AVI) 波形格式数据(.WAV) 位图格式数据(.RDI) MIDI格式数据(.RMI)调色板格式(.PAL)多媒体电影(.RMN)动画光标(.ANI)其它RIFF文件(.BND)。

Chunk是组成RIFF文件的基本单元它的基本结构如下:

struct chunk{
  u32 id; //由4个ASCII字符组成,用以识别块中所包含的数据。如:'RIFF','LIST','fmt','data','WAV','AVI'等
  u32 size;    //块大小,是存储在data域中数据的长度,id与size域的大小则不包括在该值内
  u8 dat[size];   //块内容,数据以字(WORD)为单位排列,如果该数据结构长度是奇数,则最后添一个NULL字节
};

1.3 wav音频文件

WAVE 文件作为多媒体中使用的声音波形文件格式之一,它是以RIFF(Resource Interchange File Format)格式为标准的。每个WAVE文件的头四个字节便是“RIFF”。同样的,WAVE 文件由文件头和数据体两大部分组成。其中文件头又分为 RIFF/WAV 文件标识段和声音数据格式说明段两部分。WAVE文件各部分内容及格式见后文。

常见的声音文件主要有两种,分别对应于单声道(11.025KHz 采样率、8Bit 的采样值)和双声道(44.1KHz 采样率、16Bit 的采样值)。采样率是指:声音信号在“模→数”转换过程中单位时间内采样的次数。采样值是指每一次采样周期      
内声音模拟信号的积分值。

对于单声道声音文件,采样数据为八位的短整数(short int 00H-FFH);而对于双声道立体声声音文件,每次采样数据为一个16位的整数(int),高八位和低八位分别代表左右两个声道。

WAVE 文件数据块包含以脉冲编码调制(PCM)格式表示的样本。WAVE 文件是由样本组织而成的。在单声道 WAVE 文件中,声道0代表左声道,声道1代表右声道。在多声道WAVE文件中,样本是交替出现的。

WAVE 文件除了前面一小段文件头对数据组织进行说明之外,Data 块就是声音的原始采样数据,WAVE 文件虽然可以压缩,但一般都使用不压缩的格式。44.1KHz 采样率、16Bit的分辨率、双声道,所以WAVE可以保存音质要求非常高的声音文件,CD 采用的也是这种格式,声音方面的专家或是音乐发烧友们应该非常熟悉。但这种文件的体积也非常大,以 44.1KHz 16bit 双声道的数据为例,一分钟的声音数据量为:4100*2byte*2channel*60s/1024/1024=10.09M 。所以不合适在网上传送。


下面我们具体地分析 WAVE 文件的格式

endian

field name

Size

 
big ChunkID 4 文件头标识,一般就是" RIFF" 四个字母
little ChunkSize 4 整个数据文件的大小,不包括上面ID和Size本身
big Format 4 一般就是" WAVE" 四个字母
big SubChunk1ID 4 格式说明块,本字段一般就是"fmt "
little SubChunk1Size 4 本数据块的大小,不包括ID和Size字段本身
little AudioFormat 2 音频的格式说明
little NumChannels 2 声道数
little SampleRate 4 采样率
little ByteRate 4 比特率,每秒所需要的字节数
little BlockAlign 2 数据块对齐单元
little BitsPerSample 2 采样时模数转换的分辨率
big SubChunk2ID 4 真正的声音数据块,本字段一般是"data"
little SubChunk2Size 4 本数据块的大小,不包括ID和Size字段本身
little Data N 音频的采样数据

以下是对各个字段的详细解说:

ChunkID 4bytes ASCII 码表示的“RIFF”。(0x52494646)
ChunkSize 4bytes 36+SubChunk2Size,或是            
4 + ( 8 + SubChunk1Size ) + ( 8 + SubChunk2Size ),            
这是整个数据块的大小(不包括ChunkID和ChunkSize的大小)
Format 4bytes ASCII 码表示的“WAVE”。(0x57415645)
SubChunk1ID 新的数据块(格式信息说明块)            
ASCII 码表示的“fmt ”——最后是一个空格。(0x666d7420)
SubChunk1Size 4bytes 本块数据的大小(对于PCM,值为16)。
AudioFormat 2bytes PCM = 1 (比如,线性采样),如果是其它值的话,则可能是一些压缩形式
NumChannels 2bytes 1 => 单声道  |  2 => 双声道
SampleRate 4bytes 采样率,如 8000,44100 等值
ByteRate 4bytes 等于: SampleRate * numChannels * BitsPerSample / 8
BlockAlign 2bytes 等于:NumChannels * BitsPerSample / 8
BitsPerSample 2bytes 采样分辨率,也就是每个样本用几位来表示,一般是 8bits 或是 16bits
SubChunk2ID 4bytes 新数据块,真正的声音数据            
ASCII 码表示的“data ”——最后是一个空格。(0x64617461)
SubChunk2Size 4bytes 数据大小,即,其后跟着的采样数据的大小。
Data N bytes 真正的声音数据

对于Data块,根据声道数和采样率的不同情况,布局如下(每列代表8bits):

1). 8 Bit 单声道:

采样1 采样2
数据1 数据2

2). 8 Bit 双声道

采样1 采样2
声道1数据1 声道2数据1 声道1数据2 声道2数据2

3). 16 Bit 单声道:

采样1 采样2
数据1低字节 数据1高字节 数据1低字节 数据1高字节

4). 16 Bit 双声道

采样1  
声道1数据1低字节 声道1数据1高字节 声道2数据1低字节 声道2数据1高字节
采样2      
声道1数据2低字节 声道1数据2高字节 声道2数据2低字节 声道2数据2高字节

 下面我们看一个具体的例子,wav音频文件如下:(十六进制的形式)

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 3c 13 3c 14 16 18 34 23 3c 24 11 1a 0d

对应的分析如下图所示:

untitled

     举例分析数据:形如 'FFFF' 为一个我们需要的完整的数据。如上图中 sample3:3c 和 13是两个数组合在一起是一个我们需要的数, 3c 13,但右端为大端,则应为 3c 13,十六进制数3c按位转换为2进制为0011 1100,同理13按位转换为2进制为0001 0011,则连起来的16bits的二进制数为0011 1100 0001 0011,那么我们可以看到符号位为0,即为正数。

二 matlab中的wavread( )函数

  1. wavread('testwav.wav' )

    读者试试看输出。例如,取我的一个声音文件'testwav.wav',输出的最后10个数据为:

        -0.0001 -0.0001 -0.0002 -0.0003 -0.0002 -0.0002  -0.0002  -0.0003  -0.0002  -0.0002

   2.  wavread('testwav.wav','native')

        读者可以试试看输出。我的'testwav.wav' 输出的最后10个数据为:

         -4  -2  -8  -9  -7  -8  -8  -11  -5  -7

1 和 2 的输出数据之间的转换公式为:-0.0002 = -7 / 32768 (其中32768 = 2 ^15,即2的15次幂。这是归一化。因为编码为16bits)

三 readwav的 C++实现

上面介绍了这么多,我们来进入主题,怎么用C++实现matlab中的wavread('testwav.wav')函数,且输出一致。

3.1 编码转换规则

在介绍之前,我们需要了解这几串数据之间的关系。本章节以test.wav文件的数据为例来分析:

(1)该wave文件的Data块即原始采样数据的最后20个数据是:

 fc ff fe ff f8 ff f7 ff f9 ff f8 ff f8 ff f5 ff fb ff f9 ff

(2)在matlab中解析得到的最后10个数据是:

-0.0001 -0.0001 -0.0002 -0.0003 -0.0002 -0.0002  -0.0002  -0.0003  -0.0002  -0.0002

     这两组数据之间是原码与补码的关系,即(1)是原码而(2)是补码。

由数据(1)转换为数据(2)的步骤是:先将(1)转换为其补码,再用补码除以32768,则得到(2)。

原码与补码之间的转换原则:

(2进制形式的转换):若原码为正数,则补码是其本身。若原码为负数,则补码为符号位不变,数值位按位取反,再加1。

(数值形式的转换):若原码为正数,则补码是其本身。若原码为负数,补码 = 原码 - 2^16。温馨提示: 为了方便计算数值上有等价替换 2^16 = FFFF - 1。

为了更好的理解,举例说明:

步骤一(每次读16字节):由于数据是从X0000到XFFFF的数据。以f9 ff为例,右端为大端,换言之,右端是高位,则应该是fff9。步骤二(转换为补码):按位转换为二进制形式为1111 1111 1111 1001(1位16进制数值对应4位二进制数值),该数据为原码,转换成带符号的十进制形式,先看符号位判断其为负数,则补码为FFF9 - FFFF -1 = -7。步骤三(归一化):用补码数值-7除以32768,取小数点后4位(四舍五入),则等于-0.0002,正确。

读者可以试着用我的方法算一下(1)中的右起第3第4个数,是否对应等于(2)的右起第2个数。

3.2 C++实现

那么C++实现,就是先读取原始采样数据,每次读16字节,然后将16字节的16进制数字转化成十进制数,再转换成其补码,并归一化。转换时注意大小端和符号问题。

具体的C++代码,我已分享,读者可移步查看:http://www.oschina.net/code/snippet_1768500_39013

参考文献

1. http://www.cnblogs.com/liyiwen/archive/2010/04/19/1715715.html

© 著作权归作者所有

刘小米_思聪
粉丝 58
博文 60
码字总数 43955
作品 0
西安
其他
私信 提问
加载中

评论(8)

1
12abc超
楼主有没有wav时域转为频域的文章
刘小米_思聪
刘小米_思聪 博主

引用来自“lebronze”的评论

楼主你好,你的博文和代码都写得非常好,赞一个。
有些问题想要请教,
1.wav文件中的数据表示什么?是拾音器采集声音的电压值还是什么?如果是电压值,单位又是什么?
2.我用matlab里waveread读取的声音值是[N,1]矩阵(单通道),采用你的代码读出来数据尺寸刚好是2N,这个是为什么呢?
3.我很好奇wav文件数据是存的什么?按照博文中的意思是存储的原始数据,可以进行归一化。那一般后面进行声波处理是常用的是原始数据还是归一化后的数据?
希望楼主不吝赐教0
第一个问题,这个单位我没研究过,不太清楚。第2个问题,我执行时后是一样的,您可以查看下matlab waveread 和 C++的 这个函数的一些参数是否一样,比如通道数等。第三个问题,一般我们用matlab操作音频,都是第一步做wareread,然后再做进一步的滤波,提取特征等音频处理,因此应该是针对归一化之后的
lebronze
lebronze
楼主你好,你的博文和代码都写得非常好,赞一个。
有些问题想要请教,
1.wav文件中的数据表示什么?是拾音器采集声音的电压值还是什么?如果是电压值,单位又是什么?
2.我用matlab里waveread读取的声音值是[N,1]矩阵(单通道),采用你的代码读出来数据尺寸刚好是2N,这个是为什么呢?
3.我很好奇wav文件数据是存的什么?按照博文中的意思是存储的原始数据,可以进行归一化。那一般后面进行声波处理是常用的是原始数据还是归一化后的数据?
希望楼主不吝赐教0
刘小米_思聪
刘小米_思聪 博主

引用来自“又见深秋沐风”的评论

楼主,这输出的最后10个数据跟matlab中用wavread输出的一样吗
一样的
又见深秋沐风
又见深秋沐风
楼主,这输出的最后10个数据跟matlab中用wavread输出的一样吗
又见深秋沐风
又见深秋沐风
请问楼主,我为什么输出的数据跟matlab中用wavread输出的数据不一样呢
RenKaidi
RenKaidi
好顶赞
RenKaidi
RenKaidi
好顶赞
C++软件开发第三方库大全

1.boost 这个使用的人多不多说了 2.pthread windows下的posix线程实现 3.libcurl 一个有名的开源网络爬虫库 阿里旺旺中使用到了 4.libeay32 OpenSSL Library 5.libtidy 一个专门解析htm的库 ...

晨曦之光
2012/04/13
1K
0
MediaInfo 19.07 发布,多媒体文件解析软件

MediaInfo 19.07 发布,MediaInfo 用来分析视频和音频文件的相关技术和内容信息的开源软件。它提供多媒体文件的一般信息,如:标题、作者、导演、专辑、音轨号码、日期、持续时间等。它还为音...

afterer
07/21
611
0
深入剖析Android音频之AudioPolicyService

From AudioPolicyService是策略的制定者,比如什么时候打开音频接口设备、某种Stream类型的音频对应什么设备等等。而AudioFlinger则是策略的执行者,例如具体如何与音频设备通信,如何维护现...

慢慢的燃烧
04/17
0
0
C语言编程学习程序解析:控制语句之goto语句

C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到...

小辰带你看世界
2018/05/29
0
0
【jni 编程】—— NDK环境搭建

在《站在巨人的肩膀上,谈app的创新性》一文中 http://my.oschina.net/liusicong/blog/311971,我提到过构建app技术壁垒的必要性。在构建技术壁垒时,我们往往需要调用许多库函数,例如:图像...

刘小米
2014/09/10
777
0

没有更多内容

加载失败,请刷新页面

加载更多

JavaScript设计模式——适配器模式

  适配器模式是设计模式行为型模式中的一种模式;   定义:   适配器用来解决两个已有接口之间不匹配的问题,它并不需要考虑接口是如何实现,也不用考虑将来该如何修改;适配器不需要修...

有梦想的咸鱼前端
8分钟前
0
0
Andorid SQLite数据库开发基础教程(1)

Andorid SQLite数据库开发基础教程(1) Android数据库访问方式 SQLite是Android系统默认支持的文件数据库。该数据库支持SQL语言,适合开发人员上手。本教程将讲解如何开发使用SQLite的Andro...

大学霸
11分钟前
1
0
Handler简解

Handler 这里简化一下代码 以便理解 Handler不一定要在主线程建 但如Handler handler = new Handler(); 会使用当前的Looper的, 由于要更新UI 所以最好在主线程 new Handler() { mLooper = Lo...

shzwork
33分钟前
4
0
h5获取摄像头拍照功能

完整代码展示: <!DOCTYPE html> <head> <title>HTML5 GetUserMedia Demo</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum......

诗书易经
35分钟前
3
0
正向代理和反向代理

文章来源 运维公会:正向代理和反向代理 1、正向代理 (1)服务对象不同 正向代理服务器的服务对象是客户端,可以将客户端和代理服务器看作一个整体。 (2)配置方法不同 需要在客户端配置代...

运维团
52分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部