一口君后面會(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)。
本文還用到邏輯分析儀,使用方法如下:《推薦最近在使用的還不錯(cuò)的一款邏輯分析儀》
本文用到的 音頻文件+邏輯分析儀軟件+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 的頻率,此頻率剛好超過人類的聽力極限 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中常見的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)過采樣、量化、編碼轉(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ù),所以通常都比較大,常見的 MP3 格式都是經(jīng)過壓縮的,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 開發(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ù)塊)。
該格式的實(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í)例
下面通過提供給大家的音頻文件《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打開該文件,自動(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)信息然后通過i2s控制器驅(qū)動(dòng),將pcm音頻流通過i2s接口發(fā)送給codec rk809,codec rk809會(huì)將pcm音頻流進(jìn)行DAC轉(zhuǎn)換成對(duì)應(yīng)的模擬信號(hào),并通過耳機(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ì)通過該波形提取對(duì)應(yīng)的pcm數(shù)據(jù),做出相應(yīng)處理之后就可以播放出去了。
四、如何在各種音頻格式之間進(jìn)行轉(zhuǎn)換
處于測(cè)試需要,我們還需要經(jīng)常轉(zhuǎn)換文件格式,可以通過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. 編寫代碼實(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è)試一下。