第四十二章錄音機實驗
1)實驗平台:正點原子DNESP32S3開發板
2)章節摘自【正點原子】ESP32-S3使用指南—IDF版 V1.6
3)購買鏈接:https://detail.tmall.com/item.htm?&id=768499342659
4)全套實驗源碼+手冊+視頻下載地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32S3.html
5)正點原子官方B站:https://space.bilibili.com/394620890
6)正點原子DNESP32S3開發板技術交流羣:132780729
上一章,我們實現了一個簡單的音樂播放器,本章我們將在上一章的基礎上,繼續用ES8388實現一個簡單的錄音機,錄製WAV格式的錄音。
本章分為如下幾個小節:
42.1 ES8388錄音簡介
42.2 硬件設計
42.3 程序設計
42.4 下載驗證
42.1 ES8388錄音簡介
本章涉及的知識點基本上在上一章都有介紹。本章要實現WAV錄音,還是和上一章一樣,要了解:WAV文件格式、ES8388和I²S。WAV文件格式,我們在上一章已經做了詳細介紹了,這裏就不作介紹了。
正點原子DNESP32S3開發板將板載的一個MIC分別接入到了ES8388的2個差分輸入通道(LIP/LIN和RIP/RIN,原理圖見:圖42.2.3)。代碼上,我們採用立體聲WAV錄音,不過,左右聲道的音源都是一樣的,錄音出來的WAV文件,聽起來就是個單聲道效果。
關於ES8388的驅動與上一章是一樣的,區別在於ES8388的工作狀態不一樣,在本章錄音實驗中ES8388設置為開啓ADC,上一章節則是設置為開啓DAC,讀者想了解可以參考第42章的介紹,或者參考本例程源代碼和ES8388的pdf數據手冊理解。
42.2 硬件設計
42.2.1例程功能
本章實驗功能簡介:開機後,先初始化各外設,然後檢測字庫是否存在,如果檢測無問題,則開始循環播放SD卡MUSIC文件夾裏面的歌曲(必須在SD卡根目錄建立一個MUSIC文件夾,並存放歌曲在裏面),在SPILCD上顯示歌曲名字、播放時間、歌曲總時間、歌曲總數目、當前歌曲的編號等信息。KEY0用於選擇下一曲,KEY2用於選擇上一曲,KEY3用來控制暫停/繼續播放。LED閃爍,提示程序運行狀態。
42.2.2硬件資源
本實驗,大家需要準備1個microSD/SD卡(在裏面新建一個MUSIC文件夾,並存放一些歌曲在MUSIC文件夾下)和一個耳機(非必備),分別插入SD卡接口和耳機接口,然後下載本實驗就可以實現錄音機的效果。實驗用到的硬件資源如下:
1.LED燈
LED -IO0
2.獨立按鍵
KEY0(XL9555) - IO1_7
KEY1(XL9555) - IO1_6
KEY2(XL9555) - IO1_5
KEY3(XL9555) - IO1_4
3.XL9555
IIC_SDA-IO41
IIC_SCL-IO42
4.SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在P5端口,使用跳線帽將IO_SET和LCD_DC相連)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555)
5.SD
CS-IO2
SCK-IO12
MOSI-IO11
MISO-IO13
6.ES8388音頻CODEC芯片(IIC端口0)
IIC_SDA-IO41
IIC_SCL-IO42
I2S_BCK_IO-IO46
I2S_WS_IO-IO9
I2S_DO_IO-IO10
I2S_DI_IO-IO14
IS2_MCLK_IO-IO3
7.開發闆闆載的咪頭或自備麥克風輸入
8.喇叭或耳機
錄音機實驗與上一章(音樂播放器實驗)用到的硬件資源基本一樣,我們這裏就不重複介紹原理圖了,有差異的是這次我們用到板載的咪頭用於信號輸入,也可以通過3.5mm的音頻接口通過LINE_IN接入麥克風輸入錄音音源。
42.2.3原理圖
本實驗相關的原理圖同上一章節。
42.3 程序設計
42.3.1 程序流程圖
程序流程圖能幫助我們更好的理解一個工程的功能和實現的過程,對學習和設計工程有很好的主導作用。下面看看本實驗的程序流程圖:
圖42.3.1.1錄音實驗程序流程圖
42.3.2 錄音實驗函數解析
本章實驗所使用ESP32-S3的API函數在上一章節已經講述過了,在此不再贅述。
42.3.3 錄音實驗驅動解析
在IDF版的31_recoding例程中,作者在31_recoding\components\BSP路徑下新增了一個I2S文件夾和一個ES8388文件夾,分別用於存放i2s.c、i2s.h和es8388.c以及es8388.h這四個文件。其中,i2s.h和es8388.h文件負責聲明I2S以及ES8388相關的函數和變量,而i2s.c和es8388.c文件則實現了I2S以及ES8388的驅動代碼。下面,我們將詳細解析這四個文件的實現內容。
1,recorder驅動
錄這裏我們只講解核心代碼,詳細的源碼請大家參考光盤本實驗對應源碼,RECORDER的驅動主要包括兩個文件:recorder.c和recorder.h。
音樂播放器實驗中我們已經學過配置ES8388的方法,我們在recoder.c編寫函數配置ES8388工作在PCM錄音模式,我們編寫代碼如下:
/**
* @brief 進入PCM 錄音模式
* @param 無
* @retval 無
*/
voidrecoder_enter_rec_mode(void)
{
es8388_adda_cfg(0, 1); /* 開啓ADC */
es8388_input_cfg(0); /* 開啓輸入通道(通道1,MIC所在通道) */
es8388_mic_gain(8); /*MIC增益設置為最大 */
es8388_alc_ctrl(3, 4, 4); /* 開啓立體聲ALC控制,以提高錄音音量 */
es8388_output_cfg(0, 0); /* 關閉通道1和2的輸出 */
es8388_spkvol_set(0); /* 關閉喇叭. */
es8388_sai_cfg(0, 3); /* 飛利浦標準,16位數據長度 */
/* 初始化I2S*/
i2s_set_samplerate_bits_sample(SAMPLE_RATE,
I2S_BITS_PER_SAMPLE_16BIT);
i2s_trx_start(); /* 開啓I2S */
recoder_remindmsg_show(0);
}
該函數就是用我們前面介紹的方法,激活ES8388的PCM模式,本章,我們使用的是44.1Khz採樣率,16位單聲道線性PCM模式。
由於最後要把錄音寫入到文件,這裏需要準備wav的文件頭,為方便,我們定義了一個__WaveHeader結構體來定義文件頭的數據字節,這個結構體包含了前面提到的wav文件的數據結構塊:
Typedef struct
{
ChunkRIFF riff; /*riff塊 */
ChunkFMT fmt; /*fmt塊 */
//ChunkFACT fact; /*fact塊 線性PCM,沒有這個結構體 */
ChunkDATA data; /*data塊 */
}__WaveHeader;
我們定義一個recoder_wav_init()函數方便初始化文件信息,代碼如下:
voidrecoder_wav_init(__WaveHeader *wavhead)
{
wavhead->riff.ChunkID= 0x46464952; /* RIFF" */
wavhead->riff.ChunkSize= 0; /* 還未確定,最後需要計算 */
wavhead->riff.Format= 0x45564157; /*"WAVE" */
wavhead->fmt.ChunkID= 0x20746D66; /* "fmt" */
wavhead->fmt.ChunkSize= 16; /* 大小為16個字節 */
/*0x01,表示PCM; 0x00,表示IMA ADPCM */
wavhead->fmt.AudioFormat= 0x01;
wavhead->fmt.NumOfChannels= 2; /* 雙聲道 */
wavhead->fmt.SampleRate= SAMPLE_RATE; /* 採樣速率 */
wavhead->fmt.ByteRate= wavhead->fmt.SampleRate* 4;
/* 字節速率=採樣率*通道數*(ADC位數/8) */
wavhead->fmt.BlockAlign= 4; /* 塊大小=通道數*(ADC位數/8) */
wavhead->fmt.BitsPerSample= 16; /* 16位PCM*/
wavhead->data.ChunkID= 0x61746164; /*"data" */
wavhead->data.ChunkSize= 0; /* 數據大小,還需要計算 */
}
錄音完成我們還要重新計算錄音文件的大小寫入文件頭,以保證音頻文件能正常被解析。我們把這些數據直接按順序寫入文件即可完成錄音操作,結合文件操作和按鍵功能定義,我們用wav_recoder()函數實現錄音過程,代碼如下:
/**
* @brief WAV錄音
* @param 無
* @retval 無
*/
voidwav_recorder(void)
{
uint8_t res;
uint8_t key;
uint8_t rval = 0;
uint32_t bw;
__WaveHeader *wavhead= 0;
/* 目錄 */
FF_DIR recdir;
/* 錄音文件 */
FIL *f_rec;
/* 數據緩存指針 */
uint8_t *pdatabuf;
/* 文件名稱 */
uint8_t *pname = 0;
/* 錄音時間 */
uint32_t recsec = 0;
/* 計時器 */
uint8_t timecnt = 0;
uint16_t bytes_read = 0;
/* 打開錄音文件夾 */
while (f_opendir(&recdir, "0:/RECORDER"))
{
lcd_show_string(30, 230, 240, 16, 16, "RECORDERfolder error!", RED);
vTaskDelay(200);
/* 清除顯示 */
lcd_fill(30, 230, 240, 246, WHITE);
vTaskDelay(200);
/* 創建該目錄 */
f_mkdir("0:/RECORDER");
}
/* 錄音存儲區 */
pdatabuf =malloc(1024 * 10);
/* 開闢FIL字節的內存區域 */
f_rec = (FIL*)malloc(sizeof(FIL));
/* 開闢__WaveHeader字節的內存區域 */
wavhead = (__WaveHeader*)malloc(sizeof(__WaveHeader));
/* 申請30個字節內存,文件名類似"0:RECORDER/REC00001.wav"*/
pname =malloc(30);
if (!f_rec || !wavhead|| !pname || !pdatabuf)
{
/* 任意一項失敗, 則失敗 */
rval = 1;
}
if (rval == 0)
{
/* 進入錄音模式,此時耳機可以聽到咪頭採集到的音頻 */
recoder_enter_rec_mode();
/* pname沒有任何文件名 */
pname[0] = 0;
while (rval == 0)
{
key =xl9555_key_scan(0);
switch (key)
{
/* STOP&SAVE*/
caseKEY2_PRES:
/* 有錄音 */
if (g_rec_sta& 0x80)
{
/* 關閉錄音 */
g_rec_sta = 0;
/* 整個文件的大小-8; */
wavhead->riff.ChunkSize= g_wav_size + 36;
/* 數據大小 */
wavhead->data.ChunkSize= g_wav_size;
/* 偏移到文件頭. */
f_lseek(f_rec, 0);
/* 寫入頭數據 */
f_write(f_rec,
(const void *)wavhead,
sizeof(__WaveHeader),
&bw);
f_close(f_rec);
g_wav_size = 0;
}
g_rec_sta = 0;
recsec = 0;
/* 關閉DS0 */
LED(1);
/* 清除顯示,清除之前顯示的錄音文件名 */
lcd_fill(30,
190,
lcd_self.width,
lcd_self.height,
WHITE);
break;
/* REC/PAUSE */
caseKEY0_PRES:
/* 如果是暫停,繼續錄音 */
if (g_rec_sta& 0x01)
{
/* 取消暫停 */
g_rec_sta &= 0xFE;
}
/* 已經在錄音了,暫停 */
else if (g_rec_sta& 0x80)
{
/* 暫停 */
g_rec_sta |= 0x01;
}
/* 還沒開始錄音 */
else
{
recsec = 0;
/* 得到新的名字 */
recoder_new_pathname(pname);
text_show_string(30,
190,
lcd_self.width,
16,
"錄製:",
16,
0,
RED);
/* 顯示當前錄音文件名字 */
text_show_string(30 + 40,
190,
lcd_self.width,
16,
(char *)pname + 11,
16,
0,
RED);
/* 初始化wav數據 */
recoder_wav_init(wavhead);
/* 打開文件 */
res =f_open(f_rec,
(const TCHAR*)pname,
FA_CREATE_ALWAYS |
FA_WRITE);
/* 文件創建失敗 */
if (res)
{
/* 創建文件失敗,不能錄音 */
g_rec_sta = 0;
/* 提示是否存在SD卡 */
rval = 0xFE;
}
else
{
/* 寫入頭數據 */
res =f_write(f_rec,
(const void *)wavhead,
sizeof(__WaveHeader),
(UINT*)&bw);
recoder_msg_show(0, 0);
/* 開始錄音 */
g_rec_sta |= 0x80;
}
}
if (g_rec_sta& 0x01)
{
/* 提示正在暫停 */
LED(0);
}
else
{
LED(1);
}
break;
/* 播放最近一段錄音 */
caseKEY3_PRES:
/* 沒有在錄音 */
if (g_rec_sta!= 0x80)
{
/* 如果按鍵被按下,且pname不為空 */
if (pname[0])
{
text_show_string(30,
190,
lcd_self.width, 16, "播放:", 16, 0, RED);
/* 顯示當播放的文件名字 */
text_show_string(30 + 40,
190,
lcd_self.width,
16,
(char *)pname + 11,
16,
0,
RED);
/* 進入播放模式 */
recoder_enter_play_mode();
/* 播放pname */
audio_play_song(pname);
/* 清除顯示,清除之前顯示的錄音文件名 */
lcd_fill(30,
190,
lcd_self.width,
lcd_self.height,
WHITE);
/* 重新進入錄音模式 */
recoder_enter_rec_mode();
}
}
break;
}
if ((g_rec_sta& 0x80) == 0x80)
{
if ((g_rec_sta& 0x01) == 0x00)
{
bytes_read =i2s_rx_read((uint8_t *)pdatabuf, 1024 * 10);
/* 寫入文件 */
res =f_write(f_rec, pdatabuf,bytes_read, (UINT*)&bw);
if (res)
{
printf("writeerror:%d\r\n", res);
}
/* WAV數據大小增加 */
g_wav_size +=bytes_read;
}
}
else
{
vTaskDelay(1);
}
timecnt++;
if ((timecnt% 20) == 0)
{
/* LED閃爍 */
LED_TOGGLE();
}
/* 錄音時間顯示 */
if (recsec!= (g_wav_size / wavhead->fmt.ByteRate))
{
/* 錄音時間 */
recsec =g_wav_size / wavhead->fmt.ByteRate;
/* 顯示碼率 */
recoder_msg_show(recsec,
wavhead->fmt.SampleRate*
wavhead->fmt.NumOfChannels*
wavhead->fmt.BitsPerSample);
}
}
}
/* 釋放內存 */
free(pdatabuf);
/* 釋放內存 */
free(f_rec);
/* 釋放內存 */
free(wavhead);
/* 釋放內存 */
free(pname);
}
42.3.4 CMakeLists.txt文件
打開本實驗BSP下的CMakeLists.txt文件,其內容如下所示:
set(src_dirs
IIC
LCD
LED
SDIO
SPI
XL9555
ES8388
I2S)
set(include_dirs
IIC
LCD
LED
SDIO
SPI
XL9555
ES8388
I2S)
set(requires
driver
fatfs)
idf_component_register(SRC_DIRS${src_dirs}
INCLUDE_DIRS ${include_dirs}REQUIRES ${requires})
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
上述的紅色I2C、ES8388驅動需要由開發者自行添加,以確保錄音驅動能夠順利集成到構建系統中。這一步驟是必不可少的,它確保了錄音驅動的正確性和可用性,為後續的開發工作提供了堅實的基礎。
打開本實驗main文件下的CMakeLists.txt文件,其內容如下所示:
idf_component_register(
SRC_DIRS
"."
"app"
INCLUDE_DIRS
"."
"app")
上述的紅色app驅動需要由開發者自行添加,在此便不做贅述了。
42.3.5 實驗應用代碼
打開main/main.c文件,該文件定義了工程入口函數,名為app_main。該函數代碼如下。
i2c_obj_ti2c0_master;
/**
* @brief 程序入口
* @param 無
* @retval 無
*/
voidapp_main(void)
{
esp_err_t ret;
uint8_t key = 0;
/* 初始化NVS*/
ret =nvs_flash_init();
if (ret ==ESP_ERR_NVS_NO_FREE_PAGES ||
ret ==ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret =nvs_flash_init();
}
/* 初始化LED*/
led_init();
/* 初始化IIC0*/
i2c0_master =iic_init(I2C_NUM_0);
/* 初始化SPI*/
spi2_init();
/* 初始化IO擴展芯片 */
xl9555_init(i2c0_master);
/* 初始化LCD*/
lcd_init();
/* ES8388初始化 */
es8388_init(i2c0_master);
/* 設置耳機音量 */
es8388_hpvol_set(25);
/* 設置喇叭音量 */
es8388_spkvol_set(25);
/* 打開喇叭 */
xl9555_pin_write(SPK_EN_IO,0);
/* I2S初始化 */
i2s_init();
/* 檢測不到SD卡 */
while (sd_spi_init())
{
lcd_show_string(30, 110, 200, 16, 16, "SDCard Error!", RED);
vTaskDelay(500);
lcd_show_string(30, 130, 200, 16, 16, "PleaseCheck! ", RED);
vTaskDelay(500);
}
/* 檢查字庫 */
while (fonts_init())
{
/* 清屏 */
lcd_clear(WHITE);
lcd_show_string(30, 30, 200, 16, 16, "ESP32-S3", RED);
/* 更新字庫 */
key =fonts_update_font(30, 50, 16, (uint8_t *)"0:", RED);
/* 更新失敗 */
while (key)
{
lcd_show_string(30, 50, 200, 16, 16, "FontUpdate Failed!", RED);
vTaskDelay(200);
lcd_fill(20, 50, 200 + 20, 90 + 16, WHITE);
vTaskDelay(200);
}
lcd_show_string(30, 50, 200, 16, 16, "FontUpdate Success! ", RED);
vTaskDelay(1500);
/* 清屏 */
lcd_clear(WHITE);
}
/* 為fatfs相關變量申請內存 */
ret =exfuns_init();
/* 實驗信息顯示延時 */
vTaskDelay(500);
text_show_string(30, 50, 200, 16, "正點原子ESP32開發板", 16, 0, RED);
text_show_string(30, 70, 200, 16, "WAV錄音機 實驗", 16, 0, RED);
text_show_string(30, 90, 200, 16, "正點原子@ALIENTEK", 16, 0, RED);
while (1)
{
/* 錄音 */
wav_recorder();
}
}
可以看到main函數與音樂播放器實驗十分類似,封裝好了APP,main函數會精簡很多。
42.4 下載驗證
在代碼編譯成功之後,我們下載代碼到正點原子DNESP32S3開發板上,先初始化各外設,然後檢測字庫是否存在,如果檢測無問題,再檢測SD卡根目錄是否存在RECORDER文件夾,如果不存在則創建,如果創建失敗,則報錯。在找到SD卡的RECORDER文件夾後,即進入錄音模式(包括配置ES8388和I²S等),此時可以在耳機(或喇叭)聽到採集到的音頻。KEY0用於開始/暫停錄音,KEY2用於保存並停止錄音,KEY3用於播放最近一次的錄音。
圖42.4.1 錄音機實驗界面
此時,我們按下KEY0就開始錄音了,此時看到屏幕顯示錄音文件的名字以及錄音時長,如圖42.4.2所示:
圖42.4.2 錄音進行中
在錄音的時候按下KEY0則執行暫停/繼續錄音的切換,通過LED指示錄音暫停。通過按下KEY2,可以停止當前錄音,並保存錄音文件。在完成一次錄音文件保存之後,我們可以通過按KEY3按鍵,來實現播放這個錄音文件(即播放最近一次的錄音文件),實現試聽。
我們可以把錄音完成的wav文件放到電腦上,可以通過一些播放軟件播放並查看詳細的音頻編碼信息,本例程使用的是KMPlayer播放,查看到的信息如圖42.4.3所示:
圖42.4.3 錄音文件屬性
這和我們程序設計時的效果一樣,通過電腦端的播放器可以直接播放我們所錄的音頻。經實測,效果還是非常不錯的。