├── README.md └── pdm_mic └── pdm_mic.ino /README.md: -------------------------------------------------------------------------------- 1 | # Audio_Recording_to_WAV 2 | ESP32 - Record data with PDM microphone and save it as .wav file on SD card 3 | 4 | For playing WAV Audio refer to https://github.com/RafigRzayev/WAV_Player_ESP32 5 | -------------------------------------------------------------------------------- /pdm_mic/pdm_mic.ino: -------------------------------------------------------------------------------- 1 | // Save audio from PDM microphone to SD Card in wav format 2 | 3 | /* Microphone has following pins: 4 | VDD 5 | GND 6 | DOUT - connected to DIN I2S pin on ESP32 7 | CLK - connected to WS I2S pin on ESP32 8 | LR - not connected to ESP32. Microphone has internal pull-down to GND for this pin. */ 9 | 10 | // uncomment to demonstrate that valid wav files are being generated 11 | //#define GENERATE_DEMO_WAV 12 | 13 | // Raw PCM data is inside the header 14 | #ifdef GENERATE_DEMO_WAV 15 | #include "pcm_sample.h" 16 | #endif 17 | 18 | #include 19 | #include "FS.h" 20 | #include "SD_MMC.h" 21 | 22 | // I2S perhiperhal number 23 | #define I2S_CHANNEL I2S_NUM_0 // I2S_NUM_1 doesn't support PDM 24 | 25 | // I2S pins 26 | #define I2S_PIN_BIT_CLOCK I2S_PIN_NO_CHANGE // not used 27 | #define I2S_PIN_WORD_SELECT 26 28 | #define I2S_PIN_DATA_OUT I2S_PIN_NO_CHANGE // not used 29 | #define I2S_PIN_DATA_IN 25 30 | 31 | // I2S CONFIG PARAMS 32 | #define I2S_MODE (I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM) 33 | #define I2S_SAMPLE_RATE 16000 34 | #define I2S_BITS_PER_SAMPLE I2S_BITS_PER_SAMPLE_16BIT 35 | #define I2S_CHANNEL_FMT I2S_CHANNEL_FMT_ONLY_RIGHT 36 | #define I2S_COMM_FMT I2S_COMM_FORMAT_PCM 37 | #define I2S_INTERRUPT_PRIO ESP_INTR_FLAG_LEVEL1 38 | #define I2S_DMA_BUF_COUNT 4 39 | #define I2S_DMA_BUF_SIZE 1000 40 | #define I2S_ENABLE_ACCURATE_CLK true 41 | 42 | bool I2S_Init() { 43 | i2s_config_t i2s_config; 44 | memset(&i2s_config, 0, sizeof(i2s_config)); 45 | 46 | i2s_config.mode = (i2s_mode_t)I2S_MODE; 47 | i2s_config.sample_rate = I2S_SAMPLE_RATE; 48 | i2s_config.bits_per_sample = I2S_BITS_PER_SAMPLE; 49 | i2s_config.channel_format = I2S_CHANNEL_FMT; 50 | i2s_config.communication_format = (i2s_comm_format_t)I2S_COMM_FMT; 51 | i2s_config.intr_alloc_flags = I2S_INTERRUPT_PRIO; 52 | i2s_config.dma_buf_count = I2S_DMA_BUF_COUNT; 53 | i2s_config.dma_buf_len = I2S_DMA_BUF_SIZE; 54 | i2s_config.use_apll = I2S_ENABLE_ACCURATE_CLK; 55 | 56 | i2s_pin_config_t pins = { 57 | .bck_io_num = I2S_PIN_BIT_CLOCK, 58 | .ws_io_num = I2S_PIN_WORD_SELECT, 59 | .data_out_num = I2S_PIN_DATA_OUT, 60 | .data_in_num = I2S_PIN_DATA_IN 61 | }; 62 | 63 | if (i2s_driver_install(I2S_CHANNEL, &i2s_config, 0, NULL) != ESP_OK) { 64 | Serial.println("i2s_driver_install() error"); 65 | return false; 66 | } 67 | 68 | if (i2s_set_pin(I2S_NUM_0, &pins) != ESP_OK) { 69 | Serial.println("i2s_set_pin() error"); 70 | return false; 71 | } 72 | } 73 | 74 | void I2S_Quit() { 75 | if (i2s_driver_uninstall(I2S_CHANNEL) != ESP_OK) { 76 | Serial.println("i2s_driver_uninstall() error"); 77 | } 78 | } 79 | 80 | // Create a file and add wav header to it so we can play it from PC later 81 | bool create_wav_file(const char* song_name, uint32_t duration, uint16_t num_channels, const uint32_t sampling_rate, uint16_t bits_per_sample) { 82 | // data size in bytes - > this amount of data should be recorded from microphone later 83 | uint32_t data_size = sampling_rate * num_channels * bits_per_sample * duration / 8; 84 | 85 | if (!SD_MMC.begin()) { 86 | Serial.println("Card Mount Failed"); 87 | return false; 88 | } 89 | 90 | File new_audio_file = SD_MMC.open(song_name, FILE_WRITE); 91 | if (new_audio_file == NULL) { 92 | Serial.println("Failed to create file"); 93 | return false; 94 | } 95 | 96 | /* *************** ADD ".WAV" HEADER *************** */ 97 | uint8_t CHUNK_ID[4] = {'R', 'I', 'F', 'F'}; 98 | new_audio_file.write(CHUNK_ID, 4); 99 | 100 | uint32_t chunk_size = data_size + 36; 101 | uint8_t CHUNK_SIZE[4] = {chunk_size, chunk_size >> 8, chunk_size >> 16, chunk_size >> 24}; 102 | new_audio_file.write(CHUNK_SIZE, 4); 103 | 104 | uint8_t FORMAT[4] = {'W', 'A', 'V', 'E'}; 105 | new_audio_file.write(FORMAT, 4); 106 | 107 | uint8_t SUBCHUNK_1_ID[4] = {'f', 'm', 't', ' '}; 108 | new_audio_file.write(SUBCHUNK_1_ID, 4); 109 | 110 | uint8_t SUBCHUNK_1_SIZE[4] = {0x10, 0x00, 0x00, 0x00}; 111 | new_audio_file.write(SUBCHUNK_1_SIZE, 4); 112 | 113 | uint8_t AUDIO_FORMAT[2] = {0x01, 0x00}; 114 | new_audio_file.write(AUDIO_FORMAT, 2); 115 | 116 | uint8_t NUM_CHANNELS[2] = {num_channels, num_channels >> 8}; 117 | new_audio_file.write(NUM_CHANNELS, 2); 118 | 119 | uint8_t SAMPLING_RATE[4] = {sampling_rate, sampling_rate >> 8, sampling_rate >> 16, sampling_rate >> 24}; 120 | new_audio_file.write(SAMPLING_RATE, 4); 121 | 122 | uint32_t byte_rate = num_channels * sampling_rate * bits_per_sample / 8; 123 | uint8_t BYTE_RATE[4] = {byte_rate, byte_rate >> 8, byte_rate >> 16, byte_rate >> 24}; 124 | new_audio_file.write(BYTE_RATE, 4); 125 | 126 | uint16_t block_align = num_channels * bits_per_sample / 8; 127 | uint8_t BLOCK_ALIGN[2] = {block_align, block_align >> 8}; 128 | new_audio_file.write(BLOCK_ALIGN, 2); 129 | 130 | uint8_t BITS_PER_SAMPLE[2] = {bits_per_sample, bits_per_sample >> 8}; 131 | new_audio_file.write(BITS_PER_SAMPLE, 2); 132 | 133 | uint8_t SUBCHUNK_2_ID[4] = {'d', 'a', 't', 'a'}; 134 | new_audio_file.write(SUBCHUNK_2_ID, 4); 135 | 136 | uint8_t SUBCHUNK_2_SIZE[4] = {data_size, data_size >> 8, data_size >> 16, data_size >> 24}; 137 | new_audio_file.write(SUBCHUNK_2_SIZE, 4); 138 | 139 | // Actual data should be appended after this point later 140 | 141 | new_audio_file.close(); 142 | SD_MMC.end(); 143 | return true; 144 | } 145 | 146 | #ifdef GENERATE_DEMO_WAV 147 | void generate_demo_wav() { 148 | Serial.println("Generating demo wav"); 149 | size_t duration = 3; // in seconds 150 | size_t channels = 1; // mono 151 | size_t sampling_rate = 8000; // 8kHz 152 | size_t bits_per_sample = 16; 153 | const char* song_name = "/demo.wav"; 154 | if (!create_wav_file(song_name, duration, channels, sampling_rate, bits_per_sample)) { 155 | Serial.println("Error during wav header creation"); 156 | return; 157 | } 158 | 159 | if (!SD_MMC.begin()) { 160 | Serial.println("Card Mount Failed"); 161 | return; 162 | } 163 | 164 | File demo_wav = SD_MMC.open(song_name, FILE_APPEND); 165 | if (demo_wav == NULL) { 166 | Serial.println("Failed to create file"); 167 | return; 168 | } 169 | 170 | uint8_t val = 0; 171 | const size_t FILE_SIZE = duration * channels * sampling_rate * bits_per_sample / 8; 172 | for (size_t i = 0; i < FILE_SIZE; ++i) { 173 | demo_wav.write(pcm_data[i]); 174 | } 175 | 176 | demo_wav.close(); 177 | SD_MMC.end(); 178 | Serial.println("Demo wav has been generated"); 179 | } 180 | #endif 181 | 182 | void microphone_record(const char* song_name, uint32_t duration) { 183 | // Add wav header to the file so we can play it from PC later 184 | if (!create_wav_file(song_name, duration, 1, I2S_SAMPLE_RATE, I2S_BITS_PER_SAMPLE)) { 185 | Serial.println("Error during wav header creation"); 186 | return; 187 | } 188 | 189 | // Initiate SD card to save data from microphone 190 | if (!SD_MMC.begin()) { 191 | Serial.println("Card Mount Failed"); 192 | return; 193 | } 194 | 195 | // Buffer to receive data from microphone 196 | const size_t BUFFER_SIZE = 500; 197 | uint8_t* buf = (uint8_t*)malloc(BUFFER_SIZE); 198 | 199 | // Open created .wav file in append+binary mode to add PCM data 200 | File audio_file = SD_MMC.open(song_name, FILE_APPEND); 201 | if (audio_file == NULL) { 202 | Serial.println("Failed to create file"); 203 | return; 204 | } 205 | 206 | // Initialize I2S 207 | I2S_Init(); 208 | 209 | // data size in bytes - > this amount of data should be recorded from microphone 210 | uint32_t data_size = I2S_SAMPLE_RATE * I2S_BITS_PER_SAMPLE * duration / 8; 211 | 212 | // Record until "file_size" bytes have been read from mic. 213 | uint32_t counter = 0; 214 | uint32_t bytes_written; 215 | Serial.println("Recording started"); 216 | while (counter != data_size) { 217 | // Check for file size overflow 218 | if (counter > data_size) { 219 | Serial.println("File is corrupted. data_size must be multiple of BUFFER_SIZE. Please modify BUFFER_SIZE"); 220 | break; 221 | } 222 | 223 | // Read data from microphone 224 | if (i2s_read(I2S_CHANNEL, buf, BUFFER_SIZE, &bytes_written, portMAX_DELAY) != ESP_OK) { 225 | Serial.println("i2s_read() error"); 226 | } 227 | 228 | if(bytes_written != BUFFER_SIZE) { 229 | Serial.println("Bytes written error"); 230 | } 231 | 232 | // Save data to SD card 233 | audio_file.write( buf, BUFFER_SIZE); 234 | 235 | // Increment the counter 236 | counter += BUFFER_SIZE; 237 | } 238 | Serial.println("Recording finished"); 239 | 240 | I2S_Quit(); 241 | audio_file.close(); 242 | free(buf); 243 | SD_MMC.end(); 244 | } 245 | 246 | void setup() { 247 | Serial.begin(115200); 248 | delay(1000); 249 | 250 | #ifdef GENERATE_DEMO_WAV 251 | generate_demo_wav(); 252 | #endif 253 | 254 | microphone_record("/rec1.wav", 8); 255 | } 256 | 257 | void loop() { 258 | 259 | } 260 | --------------------------------------------------------------------------------