音频-三-wav格式和处理
wav格式与处理
pcm格式简介
PCM(脉冲编码调制)就是把一个时间连续,取值连续的模拟信号变换成时间离散,取值离散的数字信号后在信道中传输。脉冲编码调制就是对模拟信号先抽样,再对样值幅度量化,编码的过程。也就是前边的说过的采样-量化过程,这个过程可以较好的存储原始的音频模拟信号,并真实还原。但是PCM并不是一种友好的存储格式,所以一些机构定制了一些准则来记录PCM信号,包括声道、采样率、位宽等信息,比较通用的就是windows平台下的wav格式和osx平台下的aiff格式,这两种都可以以一种友好可读的方式保存PCM信息。
wav格式简介
以下内容来源百度:WAV
为微软公司(Microsoft)开发的一种声音文件格式,它符合RIFF(Resource Interchange File Format)
文件规范,用于保存Windows平台的音频信息资源,被Windows平台及其应用程序所广泛支持,该格式也支持MSADPCM,CCITT A LAW等多种压缩运算法,支持多种音频数字,取样频率和声道,标准格式化的WAV文件和CD格式一样,也是44.1K的取样频率,16位量化数字,因此在声音文件质量和CD相差无几! WAV打开工具是WINDOWS的媒体播放器。
通常使用三个参数来表示声音,量化位数,取样频率和采样点振幅。量化位数分为8位,16位,24位三种,声道有单声道和立体声之分,单声道振幅数据为n1矩阵点,立体声为n2矩阵点,取样频率一般有11025Hz(11kHz) ,22050Hz(22kHz)和44100Hz(44kHz) 三种,不过尽管音质出色,但在压缩后的文件体积过大!相对其他音频格式而言是一个缺点,其文件大小的计算方式为:WAV格式文件所占容量(B) = (取样频率 X量化位数X 声道) X 时间 / 8 (字节= 8bit) 每一分钟WAV格式的音频文件的大小为10MB,其大小不随音量大小及清晰度的变化而变化。
WAV是最接近无损的音乐格式,所以文件大小相对也比较大。
这张图表明了wav个基本结构和存储信息的格式:
- 所有的字符采用big-endian存储,所有的数字采用little-endian存储。
- headerchunk下包含fmtchunk和datachunk
- 每个chunk包含chunkId,chunkSize和chunkData
- headerchunk指明了RIFF格式的具体格式,wav格式必须是“WAVE”
- fmtchunk指明了pcm文件的一些基本格式,采样率、声道、位宽等
- datachunk是用来存储具体的PCM数据
1. headerchunk介绍
headerchunk是一个总章,包含了最基本的wav格式的信息:
字段名称 | 字段长度 | 大小端 | 表示信息 |
---|---|---|---|
chunkId | 4byte | big-endian | 这个地方必须是“RIFF” |
chunkSize | 4byte | little-endian | 表示该文件除了chunkId和chunkSize以外的文件剩余大小 |
Format | 4byte | big-endian | wav文件必须是“WAVE” |
上表可以看出来headerchunk总共占24byte
2.formatchunk介绍
formatchunk是wav格式中最重要的信息,包含了该文件在读取时的所有信息。
字段名称 | 字段长度 | 大小端 | 表示信息 |
---|---|---|---|
chunkId | 4byte | big-endian | 这个地方必须是“fmt ”(注意最后是空格补齐) |
chunkSize | 4byte | little-endian | 表示该文件除了chunkId和chunkSize以外的文件剩余大小 |
AudioFormat | 2byte | little-endian | 表示音频的编码格式,一般pcm编码用1表示见表 |
Num channels | 2byte | little-endian | 表示音频的声道个数,1表示单声道(MONO),2表示双声道(STEREO) |
SampleRate | 4byte | little-endian | 表示音频的采样率,比如44100Hz |
ByteRate | 4byte | little-endian | 表示音频的比特率 |
BlockAlign | 2byte | little-endian | 表示音频的块长度,也就是单元长度 |
bitPerSample | 2byte | little-endian | 表示位宽,一般是8或者16,或者更高 |
extensionChunk | 长度不定 | 由chunkSize决定 |
chunksize一般是是16,表示出chunkid和chunksize以外的所有formatchunk的字节。假如是16的时候则不会有extensionchunk,大于16的时候则会产生这个字段,这个字段的长度就是chunksize-16,后边有介绍这个字段。
ByteRate表示音频数据的比特率,也就是每秒的数据大小,这个值=Numchannels*SampleRate*BitPerSample/8.
BlockAlign表示一个音频数据块的长度,取决于位宽(bitPerSample)和声道(NumChannels),这个值=NumbleChannels*bitPerSample/8.
下表表示AudioFormat的详细内容,注意写入的时候小端字节序
formatcode | data | fact |
---|---|---|
0x0001 | PCM | |
0x0003 | IEEE float | yes |
0x0006 | 8-bitITU G.711 A-law | yes |
0x0007 | 8-bitITU G.711 µ-law | yes |
0xFFFE | 由extensionchunk中的subformat决定 |
3.datachunk介绍
datachunk记录了真实的pcm数据并将它简单封装
字段名称 | 字段长度 | 大小端 | 表示信息 |
---|---|---|---|
chunkId | 4byte | big-endian | 这个地方必须是“data” |
chunkSize | 4byte | little-endian | 表示data的数据长度 |
data |
4.extensionchunk介绍
该部分是可选部分,由formatchunk部分的chunksize决定,当chunksize大于16的时候,必然包含该块。它本身是属于formatchunk,但是我为了方便专门提取出来。
字段名称 | 字段长度 | 大小端 | 表示信息 |
---|---|---|---|
chunksize | 2byte | little-endian | 可以是0或者22 |
ValidBitsPerSample | 2byte | little-endian | |
ChannelMask | 4byte | big-endian | 表示扬声器的位置 |
SubFormat | 16byte | GUID,包含数据的编码格式,最后14byte是固定的\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71. |
5.factchunk介绍
在Rev.3之后,所有的非PCM压缩格式都必须包含factchunk。这个部分最少包含一个值,就是采样数量
字段名称 | 字段长度 | 大小端 | 表示信息 |
---|---|---|---|
chunkId | 4byte | big-endian | 这个地方必须是“fact” |
chunkSize | 4byte | little-endian | 最小值是4 |
samplelength | 4byte | big-endian | 每个声道的采样数量 |
其实在Rev.3之后,所有的新wav文件建议有fac这个字段,IEEE float格式必须有这个字段,但PCM格式的编码并不一定必须有这个字段。
6.peakchunk介绍
这个字段基本很少见了,
|字段名称|字段长度|大小端|表示信息
|—|—|—|—
|chunkId|4byte|big-endian|这个地方必须是“PEAK”
|chunkSize|4byte|little-endian|表示该文件除了chunkId和chunkSize以外的文件剩余大小
|data|不定|我也不知道啥意思
实例分析
我现在从cd上考过来一首音乐-17.wav,这个文件总共有48627746字节,是wav格式编码的。具体内容如下:
00000000: 5249 4646 1a00 e602 5741 5645 666d 7420 RIFF....WAVEfmt
00000010: 1000 0000 0100 0200 44ac 0000 10b1 0200 ........D.......
00000020: 0400 1000 6461 7461 90ff e502 0000 0000 ....data........
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
这是通过vim打开并转换为16进制查看格式来表示的一段代码。我们来详细分析这一段:
5249 4646 1a00 e602 5741 5645
这段是headerchunk字段,前四个字节和后四个字节可以转换为asc码
52 - R 49 - I 46 -F 46 - F //这段表示RIFF
1a 00 e6 02 //转换int后为48627738,正好是文件大小减去8,8就是RIFF和这个长度的字节
57 - W 41 - A 56 - V 45 - E //这段表示WAVE
666d 7420 1000 0000 0100 0200 44ac 0000 10b1 0200 0400 1000
这段表示fmt字段:
666d 7420 //转换为asc为fmt
1000 0000 //表示chunksize,转为大端后为16
0100 //表示编码格式,此处为1,表示是PCM编码
0200 //表示声道数量,此处为2,表示双声道
44ac 0000 //表示采样率,转换后等于44100Hz
10b1 0200 //表示比特率,每秒传送的字节数,此处为176400
0040 //表示单元块长度,此处为4,双声道,位宽为16
1000 //表示位宽,或者说是每次采样的bit,转换后为16
PCM数据的单声道和双声道
音频在存储的时候可以根据mic的数量来决定录入的轨道是双声道还是单声道,但是现代技术也可以用1个mic录制双声道音频。单声道PCM数据是按采样的时间顺序排列,双声道的PCM数据是按采样的时间交替存储两个声道的数据,在存储的时候是按下边的图排列的:
单声道只包含声道0,双声道包含声道0(左)和声道1(右)
MONO 8bitPCM---->|声道0|声道0|...
STEREO 8bitPCM---->|声道0|声道1|声道0|声道1|...
MONO 16bitPCM--->|声道0(低字节)|声道0(高字节)|声道0(低字节)|声道0(高字节)|...
STERO 16bitPCM--->|声道0(低字节)|声道0(高字节)|声道1(低字节)|声道1(高字节)|声道0(低字节)|声道0(高字节)|声道1(低字节)|声道1(高字节)|...
在播放的时候回根据设置好的blockalign读取一块数据,这块数据会根据对应的声道数分发给扬声器。利用这个特性我们可以提取双声道的数据为单声道,也可以将单声道数据合并为双声道数据,同时也可以将双声道设置为只播放左耳和只播放右耳。这里主要讲的是声音的采集,暂时不讲声音的播放。
安卓实现PCM录制并封装为wav格式
AudioRecord类是在sdk提供的java层的音频录制工具。在api21的时候这个类的方法还比较少。当我们使用这个类的时候,必须使用read方法来从缓冲区不断的拉出数据来,假如未能及时从缓冲区取出数据,则数据在缓冲区会积累并抛出异常。我曾经尝试在read的时候讲左声道的数值全部置为0,并且采用的是同步的处理方式,左声道置零的操作时间过长,导致数据溢出抛出异常,根本不能工作。简单贴一下代码:
private void startRecord() throws IOException {
Log.e("---", "start");
int bufferSize = AudioRecord.getMinBufferSize(22050,
AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 22050,
AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
byte[] audio = new byte[bufferSize];
audioRecord.startRecording();
Log.e("---", "samplerate=" audioRecord.getSampleRate() ",channelcount" audioRecord.getChannelCount());
isRecording = true;
fileName = "" System.currentTimeMillis() ".wav";
File file = new File(Environment.getExternalStorageDirectory(), fileName);
if (!file.exists()) {
file.createNewFile();
}
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file, true));
pcmToWav(bos);
while (isRecording) {
audioRecord.read(audio, 0, bufferSize);
bos.write(audio);
}
audioRecord.stop();
bos.flush();
bos.close();
long length = file.length() - 44;
Log.e("-----", "" length);
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.seek(4);
raf.write(ByteUtil.intToLittleByte((int) length 36));
raf.seek(40);
raf.write(ByteUtil.intToLittleByte((int) length));
raf.close();
}
我是在最开始的时候将chunk写入文件,录制完成后计算差值来更改chunk部分的数值大小。
private void pcmToWav(BufferedOutputStream fileOutputStream) {
try {
fileOutputStream.write(HEADER_CHUNK.getBytes());
fileOutputStream.write(ByteUtil.intToLittleByte(36));
fileOutputStream.write(FORMAT.getBytes());
fileOutputStream.write(FMT_CHUNK.getBytes());
fileOutputStream.write(ByteUtil.intToLittleByte(16));
fileOutputStream.write(ByteUtil.shortToLittleByte((short) 1));
fileOutputStream.write(ByteUtil.shortToLittleByte((short) (audioRecord.getChannelCount())));
fileOutputStream.write(ByteUtil.intToLittleByte(audioRecord.getSampleRate()));
fileOutputStream.write(ByteUtil.intToLittleByte(2 * 22050 * 16 / 8));
fileOutputStream.write(ByteUtil.shortToLittleByte((short) (2 * 16 / 8)));
fileOutputStream.write(ByteUtil.shortToLittleByte((short) 16));
fileOutputStream.write(DATA_CHUNK.getBytes());
fileOutputStream.write(0);
fileOutputStream.write(0);
fileOutputStream.write(0);
fileOutputStream.write(0);
fileOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
在上述的代码中主要是注意大小端的问题,关于这个问题可以参考我的另一篇文章:
java中大小端问题研究
参考资料
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhbghgih
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01