瑞芯微-I2S | 語(yǔ)音文件格式wav與pcm快速入門(mén)-4
一口君后面會(huì)陸續(xù)更新基于瑞芯微rk3568的I2S系列文章。
預(yù)計(jì)10篇左右。有對(duì)語(yǔ)音感興趣的朋友,可以收藏該專題。
《瑞芯微-I2S | 音頻驅(qū)動(dòng)調(diào)試基本命令和工具-基于rk3568-2》
調(diào)試I2S,最常用到的測(cè)試文件就是wav格式和pcm格式,本文主要講解語(yǔ)音格式相關(guān)知識(shí)點(diǎn)。
本文還用到邏輯分析儀,使用方法如下:
本文用到的 音頻文件+邏輯分析儀軟件+i2s數(shù)據(jù)波形 后臺(tái)回復(fù):i2s
一、pcm
與pcm相關(guān)的幾個(gè)參數(shù):
1. PCM數(shù)據(jù)常用量化指標(biāo)
- 采樣率(Sample rate):
每秒鐘采樣多少次,以Hz為單位。
采樣率表示音頻信號(hào)每秒的數(shù)字快照數(shù)。該速率決定了音頻文件的頻率范圍。
采樣率越高,數(shù)字波形的形狀越接近原始模擬波形。
低采樣率會(huì)限制可錄制的頻率范圍,這可導(dǎo)致錄音表現(xiàn)原始聲音的效果不佳。
根據(jù) 奈奎斯特采樣定理,為了重現(xiàn)給定頻率,采樣率必須至少是該頻率的兩倍。
例如,CD 的采樣率為每秒 44,100 個(gè)采樣,因此可重現(xiàn)最高為 22,050 Hz 的頻率,此頻率剛好超過(guò)人類的聽(tīng)力極限 20,000 Hz。
-
位深度(Bit-depth):
表示用多少個(gè)二進(jìn)制位來(lái)描述采樣數(shù)據(jù),一般為16bit。
位深度決定動(dòng)態(tài)范圍。
采樣聲波時(shí),為每個(gè)采樣指定最接近原始聲波振幅的振幅值。
較高的位深度可提供更多可能的振幅值,產(chǎn)生更大的動(dòng)態(tài)范圍、更低的噪聲基準(zhǔn)和更高的保真度。 -
字節(jié)序:
表示音頻PCM數(shù)據(jù)存儲(chǔ)的字節(jié)序是大端存儲(chǔ)(big-endian)還是小端存儲(chǔ)(little-endian),為了數(shù)據(jù)處理效率的高效,通常為小端存儲(chǔ)。 -
聲道數(shù)(channel number):
當(dāng)前PCM文件中包含的聲道數(shù),是單聲道(mono)、雙聲道(stereo)?此外還有5.1聲道等。 -
采樣數(shù)據(jù)是否有符號(hào)(Sign):
要表達(dá)的就是字面上的意思,需要注意的是,使用有符號(hào)的采樣數(shù)據(jù)不能用無(wú)符號(hào)的方式播放。
以FFmpeg中常見(jiàn)的PCM數(shù)據(jù)格式s16le為例:
- 它描述的是有符號(hào)16位小端PCM數(shù)據(jù)
s表示有符號(hào),
16表示位深,
le表示小端存儲(chǔ)。
2. PCM數(shù)據(jù)流
PCM (Pulse Code Modulation) 也被稱為脈沖編碼調(diào)制。PCM 音頻數(shù)據(jù)是未經(jīng)壓縮的音頻采樣數(shù)據(jù)裸流,它是由模擬信號(hào)經(jīng)過(guò)采樣、量化、編碼轉(zhuǎn)換成的標(biāo)準(zhǔn)的數(shù)字音頻數(shù)據(jù)。

PCM 音頻數(shù)據(jù)的存儲(chǔ)
如果是單聲道的音頻文件,采樣數(shù)據(jù)按時(shí)間的先后順序依次存入(有的時(shí)候也會(huì)采用 LRLRLR 方式存儲(chǔ),只是另一個(gè)聲道的數(shù)據(jù)為 0),如果是雙聲道的話通常按照 LRLRLR 的方式存儲(chǔ),存儲(chǔ)的時(shí)候還和機(jī)器的大小端有關(guān)。
小端模式如下圖所示:
PCM 音頻數(shù)據(jù)是未經(jīng)壓縮的數(shù)據(jù),所以通常都比較大,常見(jiàn)的 MP3 格式都是經(jīng)過(guò)壓縮的,128Kbps 的 MP3 壓縮率可以達(dá)到 1:11
PCM 音頻數(shù)據(jù)的參數(shù)
一般我們描述 PCM 音頻數(shù)據(jù)的參數(shù)的時(shí)候有如下描述方式:
- 44100HZ 16bit stereo:
每秒鐘有 44100 次采樣,
采樣數(shù)據(jù)用 16 位(2 字節(jié))記錄,
雙聲道(立體聲)
44100Hz 指的是采樣率,它的意思是每秒取樣 44100 次。采樣率越大,存儲(chǔ)數(shù)字音頻所占的空間就越大。
16bit 指的是采樣精度,意思是原始模擬信號(hào)被采樣后,每一個(gè)采樣點(diǎn)在計(jì)算機(jī)中用 16 位(兩個(gè)字節(jié))來(lái)表示。采樣精度越高越能精細(xì)地表示模擬信號(hào)的差異。
Stereo 指的是聲道數(shù),也即采樣時(shí)用到的麥克風(fēng)的數(shù)量,麥克風(fēng)越多就越能還原真實(shí)的采樣環(huán)境(當(dāng)然麥克風(fēng)的放置位置也是有規(guī)定的)。
其他格式例子:
- 22050HZ 8bit mono:
每秒鐘有 22050 次采樣, 采樣數(shù)據(jù)用 8 位(1 字節(jié))記錄, 單聲道
- 48000HZ 32bit 51ch:
每秒鐘有 48000 次采樣, 采樣數(shù)據(jù)用 32 位(4 字節(jié)浮點(diǎn)型)記錄, 5.1 聲道
二、WAV文件
WAV 是 Microsoft 和 IBM 為 PC 開(kāi)發(fā)的一種聲音文件格式,它符合 RIFF(Resource Interchange File Format)文件規(guī)范,用于保存 Windows 平臺(tái)的音頻信息資源,被 Windows 平臺(tái)及其應(yīng)用程序所廣泛支持。
1. wav文件頭
WAVE 文件通常只是一個(gè)具有單個(gè) “WAVE” 塊的 RIFF 文件,該塊由兩個(gè)子塊(”fmt” 子數(shù)據(jù)塊和 ”data” 子數(shù)據(jù)塊),它的標(biāo)準(zhǔn)格式如下圖所示:

圖片來(lái)源:
http://soundfile.sapp.org/doc/WaveFormat/
該格式的實(shí)質(zhì)就是在 PCM 文件的前面加了一個(gè)文件頭,各字段含義如下:
| 偏移與大小 | 名稱 | 說(shuō)明 |
|---|---|---|
| 0 4 | ChunkID | 包含 ASCII 形式的字母“RIFF”(0x52494646 大端形式)。 |
| 4 4 | ChunkSize | 36 + SubChunk2Size,或更準(zhǔn)確地說(shuō):4 + (8 + SubChunk1Size) + (8 + SubChunk2Size)這是此數(shù)字之后的塊的其余部分的大小。這是整個(gè)文件的大小(以字節(jié)為單位)減去未包含在此計(jì)數(shù)中的兩個(gè)字段的 8 字節(jié):ChunkID 和 ChunkSize。 |
| 8 4 | 格式 | 包含字母“WAVE”(0x57415645 大端形式)。 |
| 12 4 | Subchunk1ID | 包含字母“fmt”(0x666d7420 大端格式)。 |
| 16 4 | Subchunk1Size | 16 用于 PCM。這是該數(shù)字之后的其余子塊的大小。 |
| 20 2 | AudioFormat | PCM = 1(即線性量化)1 以外的值表示某種形式的壓縮。 |
| 22 2 | NumChannels | Mono = 1、Stereo = 2 等 |
| 24 4 | SampleRate | 8000、44100 等 |
| 28 4 | ByteRate | == SampleRate * NumChannels * BitsPerSample/8 |
| 32 2 | BlockAlign | == NumChannels * BitsPerSample/8 1 的字節(jié)數(shù)樣本包括所有通道。 |
| 34 2 | BitsPerSample | 8 位 = 8,16 位 = 16,等等 |
| 2 | ExtraParamSize | 如果是 PCM,則不存在 |
| X | ExtraParams | 用于額外參數(shù)的空間 |
| 36 4 | Subchunk2ID | 包含字母“數(shù)據(jù)”(0x64617461 大端形式)。 |
| 40 4 | Subchunk2Size | == NumSamples * NumChannels * BitsPerSample/8 這是數(shù)據(jù)中的字節(jié)數(shù)。您還可以將其視為該數(shù)字后面的子塊的讀取大小。 |
| 44 * | Data | 實(shí)際的聲音數(shù)據(jù)。 |
2. wav文件頭結(jié)構(gòu)體
wav文件頭信息對(duì)應(yīng)結(jié)構(gòu)體:
typedef struct {
char ChunkID[4]; //內(nèi)容為"RIFF"
unsigned long ChunkSize; //存儲(chǔ)文件的字節(jié)數(shù)(不包含ChunkID和ChunkSize這8個(gè)字節(jié))
char Format[4]; //內(nèi)容為"WAVE“
} WAVE_HEADER;
typedef struct {
char Subchunk1ID[4]; //內(nèi)容為"fmt"
unsigned long Subchunk1Size; //存儲(chǔ)該子塊的字節(jié)數(shù)(不含前面的Subchunk1ID和Subchunk1Size這8個(gè)字節(jié))
unsigned short AudioFormat; //存儲(chǔ)音頻文件的編碼格式,例如若為PCM則其存儲(chǔ)值為1。
unsigned short NumChannels; //聲道數(shù),單聲道(Mono)值為1,雙聲道(Stereo)值為2,等等
unsigned long SampleRate; //采樣率,如8k,44.1k等
unsigned long ByteRate; //每秒存儲(chǔ)的bit數(shù),其值 = SampleRate * NumChannels * BitsPerSample / 8
unsigned short BlockAlign; //塊對(duì)齊大小,其值 = NumChannels * BitsPerSample / 8
unsigned short BitsPerSample; //每個(gè)采樣點(diǎn)的bit數(shù),一般為8,16,32等。
} WAVE_FMT;
typedef struct {
char Subchunk2ID[4]; //內(nèi)容為“data”
unsigned long Subchunk2Size; //接下來(lái)的正式的數(shù)據(jù)部分的字節(jié)數(shù),其值 = NumSamples * NumChannels * BitsPerSample / 8
} WAVE_DATA;
3. WAV 文件頭解析實(shí)例
下面通過(guò)提供給大家的音頻文件《xiaoniao.wav》來(lái)詳細(xì)講解wav文件格式,該音頻文件格式為:S16_LE
peng@ubuntu:~/test$ ls -l xiaoniao.wav
-rwxrw-rw- 1 peng peng 1764448 May 10 20:41 xiaoniao.wav
用ue打開(kāi)該文件,自動(dòng)顯示為十六進(jìn)制數(shù)字,

文件頭信息解析如下圖:
數(shù)據(jù)是小端,比如采樣率4個(gè)字段是 44 AC 00 00
實(shí)際數(shù)據(jù)是0x0000ac44,轉(zhuǎn)換成10進(jìn)制是44100
讀者對(duì)照結(jié)構(gòu)體,可以解析出改文件的所有信息。
三、i2s音頻波形分析
wav文件格式我們搞清楚了,那么它和i2s是什么關(guān)系呢?
1. 嵌入式設(shè)備音頻架構(gòu)
一個(gè)典型的嵌入式設(shè)備的音頻架構(gòu)大致如下【以rk3568為例】,

當(dāng)我們使用aplay工具播放wav文件時(shí):
- 解析wav文件頭,讀取相應(yīng)信息
- 然后通過(guò)i2s控制器驅(qū)動(dòng),將pcm音頻流通過(guò)i2s接口發(fā)送給codec rk809,
- codec rk809會(huì)將pcm音頻流進(jìn)行DAC轉(zhuǎn)換成對(duì)應(yīng)的模擬信號(hào),并通過(guò)耳機(jī)/喇叭播放出去。
2. 播放命令
播放命令:
root@ATK-DLRK356X:/sdcard# aplay -v xiaoniao.wav
Playing WAVE 'xiaoniao.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
ALSA <-> PulseAudio PCM I/O Plugin
Its setup is:
stream : PLAYBACK
access : RW_INTERLEAVED
format : S16_LE
subformat : STD
channels : 2
rate : 44100
exact rate : 44100 (44100/1)
msbits : 16
buffer_size : 22050
period_size : 5512
period_time : 125000
tstamp_mode : NONE
tstamp_type : GETTIMEOFDAY
period_step : 1
avail_min : 5512
period_event : 0
start_threshold : 22050
stop_threshold : 22050
silence_threshold: 0
silence_size : 0
boundary : 6206523236469964800
3.波形分析
現(xiàn)在我在圖中i2s控制器與codec之間位置用邏輯分析儀抓取了i2s數(shù)據(jù)波形,
【該操作需要飛線,建議找硬件工程師幫忙】
波形文件aplay_xiaoniao.kvdat,
一口君實(shí)際測(cè)試的i2s控制器為24位小端格式。
由上圖可知:
- xiaomiao.wav文件為s16_le格式,所以i2s控制器依次每次讀取data后面2個(gè)字節(jié)的數(shù)據(jù)
- 根據(jù)幀時(shí)鐘,依次在左右聲道時(shí)隙,將pcm數(shù)據(jù)放到數(shù)據(jù)線中。
- 因?yàn)榭刂破魇?4位,所以各channel會(huì)有24個(gè)bit的時(shí)鐘周期;
- 根據(jù)i2s協(xié)議,默認(rèn)有效數(shù)據(jù)靠左,并且空1個(gè)bit的位置;多出來(lái)的8個(gè)bit位置默認(rèn)補(bǔ)充填0。

5. codec就會(huì)通過(guò)該波形提取對(duì)應(yīng)的pcm數(shù)據(jù),做出相應(yīng)處理之后就可以播放出去了。
四、如何在各種音頻格式之間進(jìn)行轉(zhuǎn)換
處于測(cè)試需要,我們還需要經(jīng)常轉(zhuǎn)換文件格式,可以通過(guò)FFmpeg工具
1. FFmpeg
對(duì)于其他格式的音頻文件,一般用FFmpeg軟件進(jìn)行轉(zhuǎn)換,先在當(dāng)前的設(shè)備安裝好FFmpeg軟件,然后用命令行就可以進(jìn)行轉(zhuǎn)換了,常用的示范如下:
- 將mp4視頻提取wav格式:
ffmpeg -i D:\input.mp4 -vn -acodec pcm_s16le -ar 44100 -ac 2 D:\output.wav
- 將wav格式轉(zhuǎn)變?yōu)閜cm格式:
ffmpeg -i D:\output.wav -f s16le -acodec pcm_s16le D:\output.pcm
- 將pcm格式轉(zhuǎn)變?yōu)閣av格式:
ffmpeg -f s16le -ar 44100 -ac 2 -i D:\output.pcm c:\output.wav
注意上面的命令中指定的采樣率為44.1k ,雙聲道,存儲(chǔ)格式是s16le
2. 編寫(xiě)代碼實(shí)現(xiàn)PCM → WAV 代碼
下面是一個(gè)實(shí)現(xiàn)將pcm文件轉(zhuǎn)換成wav文件的代碼實(shí)例:
int simplest_pcm16le_to_wave( const char *pcmpath, int channels, int sample_rate, const char *wavepath )
{ // 省去錯(cuò)誤判斷
short pcmData;
FILE* fp = fopen( pcmpath, "rb" );
FILE* fpout = fopen( wavepath, "wb+" );
// 填充 WAVE_HEADER
WAVE_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 ); // 36
pcmHEADER.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;
}
大家可以用我提供的sound.pcm、xiaoniao.wav語(yǔ)音文件,測(cè)試一下。

浙公網(wǎng)安備 33010602011771號(hào)