Added I2S example for microphone recording to WAV file and I2S examples folder moved

Added import module check on generate_audio_file.py and removed from mypy ignore list
Added sugestions and minor changes on the README
This commit is contained in:
pedro.minatel 2021-03-22 18:08:57 +00:00
parent 2f70a76572
commit 86fa666343
26 changed files with 413 additions and 6 deletions

View File

@ -5,8 +5,13 @@ import struct
import wave
from builtins import range
try:
from typing import List
except ImportError:
pass
def get_wave_array_str(filename, target_bits):
def get_wave_array_str(filename, target_bits): # type: (str, int) -> str
wave_read = wave.open(filename, 'r')
array_str = ''
nchannels, sampwidth, framerate, nframes, comptype, compname = wave_read.getparams()
@ -24,7 +29,7 @@ def get_wave_array_str(filename, target_bits):
return array_str
def gen_wave_table(wav_file_list, target_file_name, scale_bits=8):
def gen_wave_table(wav_file_list, target_file_name, scale_bits=8): # type: (List[str], str, int) -> None
with open(target_file_name, 'w') as audio_table:
print('#include <stdio.h>', file=audio_table)
print('const unsigned char audio_table[] = {', file=audio_table)
@ -38,7 +43,7 @@ def gen_wave_table(wav_file_list, target_file_name, scale_bits=8):
if __name__ == '__main__':
print('Generating audio array...')
wav_list = []
for filename in os.listdir('./'):
if filename.endswith('.wav'):
wav_list.append(filename)
for wavefile in os.listdir('./'):
if wavefile.endswith('.wav'):
wav_list.append(wavefile)
gen_wave_table(wav_file_list=wav_list, target_file_name='audio_example_file.h')

View File

@ -0,0 +1,7 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
VERBOSE = 1
PROJECT_NAME := esp32-i2s-recorder-example
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,102 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
# I2S Digital Microphone Recording Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
In this example, we record a sample audio file captured from the digital MEMS microphone on the I2S peripheral using PDM data format.
The audio is recorded into the SDCard using WAVE file format.
| Audio Setting | Value |
|:---:|:---:|
| Sample Rate |44100 Hz|
| Bits per Sample |16 bits|
## How to Use Example
### Hardware Required
* A development board with ESP32 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A USB cable for power supply and programming
* A digital microphone (SPK0838HT4H PDM output was used in this example)
The digital PDM microphone is connected on the I2S interface `I2S_NUM_0`.
The default GPIO configuration is the following:
|Mic | GPIO |
|:---------:|:------:|
| PDM Clock | GPIO22 |
| PDM Data | GPIO23 |
The SDCard is connected using SPI peripheral.
| SPI | SDCard | GPIO |
|:----:|:------:|:------:|
| MISO | DAT0 | GPIO2 |
| MOSI | CMD | GPIO15 |
| SCLK | CLK | GPIO14 |
| CS | CD | GPIO13 |
To change the GPIO configuration, see the `Example Configuration` from the menuconfig.
### Configure the Project
```
idf.py menuconfig
```
In the `Example Configuration` menu:
* Use `SDCard Configuration` to assign the SPI peripheral GPIOs.
* Use `I2S MEMS MIC Configuration` to assign the I2S peripheral GPIOs and audio settings.
Optional: If you need, change the other options according to your requirements.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
* [ESP-IDF Getting Started Guide on ESP32](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html)
## Example Output
Running this example, you will see the Bits per Sample changes every 5 seconds after you have run this example. You can use `i2s_set_clk` to change the Bits per Sample and the Sample Rate. The output log can be seen below:
```
I (361) pdm_rec_example: PDM microphone recording Example start
I (371) I2S: DMA Malloc info, datalen=blocksize=2048, dma_buf_count=64
I (401) I2S: APLL: Req RATE: 44100, real rate: 88199.977, BITS: 16, CLKM: 1, BCK_M: 8, MCLK: 22579194.000, SCLK: 2822399.250000, diva: 1, divb: 0
I (431) I2S: APLL: Req RATE: 44100, real rate: 88199.977, BITS: 16, CLKM: 1, BCK_M: 8, MCLK: 22579194.000, SCLK: 2822399.250000, diva: 1, divb: 0
I (431) pdm_rec_example: Initializing SD card
I (431) pdm_rec_example: Using SDMMC peripheral
I (441) gpio: GPIO[13]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
Name: USD
Type: SDHC/SDXC
Speed: 20 MHz
Size: 3813MB
I (481) pdm_rec_example: Starting recording for 60 seconds!
I (481) pdm_rec_example: Opening file
I (60451) pdm_rec_example: Recording done!
I (60471) pdm_rec_example: File written on SDCard
I (60471) pdm_rec_example: Card unmounted
```
## Troubleshooting
* Program upload failure
* Hardware connection is not correct: run `idf.py -p PORT monitor`, and reboot your board to see if there are any output logs.
* The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again.
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "i2s_recorder_main.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,71 @@
menu "Example Configuration"
menu "SDCard Configuration"
config EXAMPLE_SPI_MISO_GPIO
int "SPI MISO GPIO"
default 2
help
Set the GPIO number used for MISO from SPI.
config EXAMPLE_SPI_MOSI_GPIO
int "SPI MOSI GPIO"
default 15
help
Set the GPIO number used for MOSI from SPI.
config EXAMPLE_SPI_SCLK_GPIO
int "SPI SCLK GPIO"
default 14
help
Set the GPIO number used for SCLK from SPI.
config EXAMPLE_SPI_CS_GPIO
int "SPI CS GPIO"
default 13
help
Set the GPIO number used for CS from SPI.
endmenu
menu "I2S MEMS MIC Configuration"
config EXAMPLE_I2S_CH
int "I2S Channel Number"
default 0
help
Set the I2S channel number.
config EXAMPLE_AUDIO_SAMPLE_RATE
int "Audio Sample Rate"
default 44100
help
Set the audio sample rate frequency. Usually 16000 or 44100 Hz.
config EXAMPLE_AUDIO_BIT_SAMPLE
int "Audio Bit Sample"
default 16
help
Define the number of bits for each sample. Default 16 bits per sample.
config EXAMPLE_I2S_DATA_GPIO
int "I2S Data GPIO"
default 23
help
Set the GPIO number used for transmitting/receiving data from I2S.
config EXAMPLE_I2S_CLK_GPIO
int "I2S Clock GPIO"
default 22
help
Set the GPIO number used for the clock line from I2S.
endmenu
config EXAMPLE_REC_TIME
int "Example Recording Time in Seconds"
default 15
help
Set the time for recording audio in seconds.
endmenu

View File

@ -0,0 +1,209 @@
/* I2S Digital Microphone Recording Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include <math.h>
#include "esp_log.h"
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_err.h"
#include "esp_vfs_fat.h"
#include "driver/sdspi_host.h"
#include "driver/spi_common.h"
#include "sdmmc_cmd.h"
#include "sdkconfig.h"
static const char* TAG = "pdm_rec_example";
#define AUDIO_SAMPLE_SIZE (CONFIG_EXAMPLE_AUDIO_BIT_SAMPLE * 1024)
#define SAMPLES_NUM ((CONFIG_EXAMPLE_AUDIO_SAMPLE_RATE/100) * 2)
#define BYTE_RATE (1 * CONFIG_EXAMPLE_AUDIO_SAMPLE_RATE * ((CONFIG_EXAMPLE_AUDIO_BIT_SAMPLE / 8))
#define FLASH_RECORD_SIZE (BYTE_RATE * CONFIG_EXAMPLE_REC_TIME))
#define SD_MOUNT_POINT "/sdcard"
#define SPI_DMA_CHAN (1)
// When testing SD and SPI modes, keep in mind that once the card has been
// initialized in SPI mode, it can not be reinitialized in SD mode without
// toggling power to the card.
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
sdmmc_card_t* card;
static int16_t i2s_readraw_buff[AUDIO_SAMPLE_SIZE];
size_t bytes_read;
const int WAVE_HEADER_SIZE = 44;
void mount_sdcard(void)
{
esp_err_t ret;
// Options for mounting the filesystem.
// If format_if_mount_failed is set to true, SD card will be partitioned and
// formatted in case when mounting fails.
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
ESP_LOGI(TAG, "Initializing SD card");
// Use settings defined above to initialize SD card and mount FAT filesystem.
// Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions.
// Please check its source code and implement error recovery when developing
// production applications.
ESP_LOGI(TAG, "Using SPI peripheral");
spi_bus_config_t bus_cfg = {
.mosi_io_num = CONFIG_EXAMPLE_SPI_MOSI_GPIO,
.miso_io_num = CONFIG_EXAMPLE_SPI_MISO_GPIO,
.sclk_io_num = CONFIG_EXAMPLE_SPI_SCLK_GPIO,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4000,
};
ret = spi_bus_initialize(host.slot, &bus_cfg, SPI_DMA_CHAN);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize bus.");
return;
}
// This initializes the slot without card detect (CD) and write protect (WP) signals.
// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = CONFIG_EXAMPLE_SPI_CS_GPIO;
slot_config.host_id = host.slot;
ret = esp_vfs_fat_sdspi_mount(SD_MOUNT_POINT, &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem. "
"If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
} else {
ESP_LOGE(TAG, "Failed to initialize the card (%s). "
"Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
}
return;
}
// Card has been initialized, print its properties
sdmmc_card_print_info(stdout, card);
}
void wavHeader(char* wav_header, uint32_t wav_size, uint32_t sample_rate){
// See this for reference: http://soundfile.sapp.org/doc/WaveFormat/
uint32_t file_size = wav_size + WAVE_HEADER_SIZE - 8;
uint32_t byte_rate = 1 * CONFIG_EXAMPLE_AUDIO_SAMPLE_RATE * (CONFIG_EXAMPLE_AUDIO_BIT_SAMPLE / 8);
const char set_wav_header[] = {
'R','I','F','F', // ChunkID
file_size, file_size >> 8, file_size >> 16, file_size >> 24, // ChunkSize
'W','A','V','E', // Format
'f','m','t',' ', // Subchunk1ID
0x10, 0x00, 0x00, 0x00, // Subchunk1Size (16 for PCM)
0x01, 0x00, // AudioFormat (1 for PCM)
0x01, 0x00, // NumChannels (1 channel)
sample_rate, sample_rate >> 8, sample_rate >> 16, sample_rate >> 24, // SampleRate
byte_rate, byte_rate >> 8, byte_rate >> 16, byte_rate >> 24, // ByteRate
0x02, 0x00, // BlockAlign
0x10, 0x00, // BitsPerSample (16 bits)
'd','a','t','a', // Subchunk2ID
wav_size, wav_size >> 8, wav_size >> 16, wav_size >> 24, // Subchunk2Size
};
memcpy(wav_header, set_wav_header, sizeof(set_wav_header));
}
void record_wav(void)
{
// Use POSIX and C standard library functions to work with files.
int flash_wr_size = 0;
ESP_LOGI(TAG, "Opening file");
char wav_header_fmt[WAVE_HEADER_SIZE];
wavHeader(wav_header_fmt, FLASH_RECORD_SIZE, CONFIG_EXAMPLE_AUDIO_SAMPLE_RATE);
// First check if file exists before creating a new file.
struct stat st;
if (stat(SD_MOUNT_POINT"/record.wav", &st) == 0) {
// Delete it if it exists
unlink(SD_MOUNT_POINT"/record.wav");
}
// Create new WAV file
FILE* f = fopen(SD_MOUNT_POINT"/record.wav", "a");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for writing");
return;
}
// Write the header to the WAV file
fwrite(wav_header_fmt, 1, WAVE_HEADER_SIZE, f);
// Start recording
while (flash_wr_size < FLASH_RECORD_SIZE) {
// Read the RAW samples from the microphone
i2s_read(CONFIG_EXAMPLE_I2S_CH, (char *)i2s_readraw_buff, AUDIO_SAMPLE_SIZE, &bytes_read, portMAX_DELAY);
// Write the samples to the WAV file
fwrite(i2s_readraw_buff, 1, bytes_read, f);
flash_wr_size += bytes_read;
}
ESP_LOGI(TAG, "Recording done!");
fclose(f);
ESP_LOGI(TAG, "File written on SDCard");
// All done, unmount partition and disable SPI peripheral
esp_vfs_fat_sdcard_unmount(SD_MOUNT_POINT, card);
ESP_LOGI(TAG, "Card unmounted");
// Deinitialize the bus after all devices are removed
spi_bus_free(host.slot);
}
void init_microphone(void)
{
i2s_config_t i2s_config = {
.mode = I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM,
.sample_rate = CONFIG_EXAMPLE_AUDIO_SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 64,
.dma_buf_len = 1024,
.use_apll = 1
};
i2s_pin_config_t pin_config;
pin_config.bck_io_num = I2S_PIN_NO_CHANGE;
pin_config.ws_io_num = CONFIG_EXAMPLE_I2S_CLK_GPIO;
pin_config.data_out_num = I2S_PIN_NO_CHANGE;
pin_config.data_in_num = CONFIG_EXAMPLE_I2S_DATA_GPIO;
i2s_driver_install(CONFIG_EXAMPLE_I2S_CH, &i2s_config, 0, NULL);
i2s_set_pin(CONFIG_EXAMPLE_I2S_CH, &pin_config);
i2s_set_clk(CONFIG_EXAMPLE_I2S_CH, CONFIG_EXAMPLE_AUDIO_SAMPLE_RATE, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);
}
void app_main(void)
{
ESP_LOGI(TAG, "PDM microphone recording Example start");
// Mount the SDCard for recording the audio file
mount_sdcard();
// Init the PDM digital microphone
init_microphone();
ESP_LOGI(TAG, "Starting recording for %d seconds!", CONFIG_EXAMPLE_REC_TIME);
// Start Recording
record_wav();
}

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp32-i2s-driver-example)

View File

@ -0,0 +1,6 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
COMPONENT_ADD_INCLUDEDIRS := .

View File

@ -65,7 +65,6 @@ examples/get-started/blink/example_test.py
examples/get-started/hello_world/example_test.py
examples/peripherals/gpio/generic_gpio/example_test.py
examples/peripherals/i2c/i2c_tools/example_test.py
examples/peripherals/i2s_adc_dac/tools/generate_audio_file.py
examples/peripherals/rmt/ir_protocols/example_test.py
examples/peripherals/sdio/sdio_test.py
examples/peripherals/twai/twai_alert_and_recovery/example_test.py