Skip to content

Commit 044545f

Browse files
committed
cmodule/m5unified: Add Mic.recordWavFile method.
Signed-off-by: lbuque <[email protected]>
1 parent cb6906b commit 044545f

File tree

6 files changed

+478
-3
lines changed

6 files changed

+478
-3
lines changed

m5stack/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ patch:
298298
$(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0005-micropython-fix-SDCard-16223.patch))
299299
$(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0006-modtime-add-timezone-method.patch))
300300
$(call Package/patche,$(abspath $(IDF_PATH)),$(abspath ./patches/1003-WIP-Compatible-with-esp-adf-v2.7.diff))
301-
$(call Package/patche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2003-Support-LTR553.patch))
301+
$(call Package/patche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2005-Support-LTR553.patch))
302302
$(call Package/patche,$(abspath $(ADF_PATH)),$(abspath ./patches/3002-Modify-i2s_stream_idf5.patch))
303303
$(call Package/patche,$(abspath ./components/epdiy),$(abspath ./patches/4001-Avoid-epdiy-compilation-failure-on-esp32-c6.patch))
304304

@@ -309,6 +309,6 @@ unpatch:
309309
$(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0005-micropython-fix-SDCard-16223.patch))
310310
$(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0004-micropython-1.24-machine-adc-v5.x.diff))
311311
$(call Package/unpatche,$(abspath $(IDF_PATH)),$(abspath ./patches/1003-WIP-Compatible-with-esp-adf-v2.7.diff))
312-
$(call Package/unpatche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2003-Support-LTR553.patch))
312+
$(call Package/unpatche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2005-Support-LTR553.patch))
313313
$(call Package/unpatche,$(abspath $(ADF_PATH)),$(abspath ./patches/3002-Modify-i2s_stream_idf5.patch))
314314
$(call Package/unpatche,$(abspath ./components/epdiy),$(abspath ./patches/4001-Avoid-epdiy-compilation-failure-on-esp32-c6.patch))

m5stack/cmodules/m5unified/m5unified_mic.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ MAKE_METHOD_0(mic, isEnabled);
1414
MAKE_METHOD_0(mic, isRecording);
1515
MAKE_METHOD_KW(mic, setSampleRate, 1);
1616
MAKE_METHOD_KW(mic, record, 1);
17+
MAKE_METHOD_KW(mic, recordWavFile, 1);
1718

1819
static const mp_rom_map_elem_t mic_member_table[] = {
1920
MAKE_TABLE(mic, config),
@@ -24,6 +25,7 @@ static const mp_rom_map_elem_t mic_member_table[] = {
2425
MAKE_TABLE(mic, isRecording),
2526
MAKE_TABLE(mic, setSampleRate),
2627
MAKE_TABLE(mic, record),
28+
MAKE_TABLE(mic, recordWavFile),
2729
};
2830

2931
static MP_DEFINE_CONST_DICT(mic_member, mic_member_table);
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
3+
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
4+
*
5+
* SPDX-License-Identifier: Unlicense OR CC0-1.0
6+
*/
7+
#pragma once
8+
9+
#include <stdint.h>
10+
11+
#ifdef __cplusplus
12+
extern "C" {
13+
#endif
14+
15+
/**
16+
* @brief Header structure for WAV file with only one data chunk
17+
*
18+
* @note See this for reference: http://soundfile.sapp.org/doc/WaveFormat/
19+
*
20+
* @note Assignment to variables in this struct directly is only possible for little endian architectures
21+
* (including Xtensa & RISC-V)
22+
*/
23+
typedef struct {
24+
struct {
25+
char chunk_id[4]; /*!< Contains the letters "RIFF" in ASCII form */
26+
uint32_t chunk_size; /*!< This is the size of the rest of the chunk following this number */
27+
char chunk_format[4]; /*!< Contains the letters "WAVE" */
28+
} descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */
29+
struct {
30+
char subchunk_id[4]; /*!< Contains the letters "fmt " */
31+
uint32_t subchunk_size; /*!< This is the size of the rest of the Subchunk which follows this number */
32+
uint16_t audio_format; /*!< PCM = 1, values other than 1 indicate some form of compression */
33+
uint16_t num_of_channels; /*!< Mono = 1, Stereo = 2, etc. */
34+
uint32_t sample_rate; /*!< 8000, 44100, etc. */
35+
uint32_t byte_rate; /*!< ==SampleRate * NumChannels * BitsPerSample s/ 8 */
36+
uint16_t block_align; /*!< ==NumChannels * BitsPerSample / 8 */
37+
uint16_t bits_per_sample; /*!< 8 bits = 8, 16 bits = 16, etc. */
38+
} fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */
39+
struct {
40+
char subchunk_id[4]; /*!< Contains the letters "data" */
41+
uint32_t subchunk_size; /*!< ==NumSamples * NumChannels * BitsPerSample / 8 */
42+
int16_t data[0]; /*!< Holds raw audio data */
43+
} data_chunk; /*!< The "data" subchunk contains the size of the data and the actual sound */
44+
} wav_header_t;
45+
46+
/**
47+
* @brief Default header for PCM format WAV files
48+
*
49+
*/
50+
#define WAV_HEADER_PCM_DEFAULT(wav_sample_size, wav_sample_bits, wav_sample_rate, wav_channel_num) { \
51+
.descriptor_chunk = { \
52+
.chunk_id = {'R', 'I', 'F', 'F'}, \
53+
.chunk_size = (wav_sample_size) + sizeof(wav_header_t) - 8, \
54+
.chunk_format = {'W', 'A', 'V', 'E'} \
55+
}, \
56+
.fmt_chunk = { \
57+
.subchunk_id = {'f', 'm', 't', ' '}, \
58+
.subchunk_size = 16, /* 16 for PCM */ \
59+
.audio_format = 1, /* 1 for PCM */ \
60+
.num_of_channels = (wav_channel_num), \
61+
.sample_rate = (wav_sample_rate), \
62+
.byte_rate = (wav_sample_bits) * (wav_sample_rate) * (wav_channel_num) / 8, \
63+
.block_align = (uint16_t)((wav_sample_bits) * (wav_channel_num) / 8), \
64+
.bits_per_sample = (wav_sample_bits) \
65+
}, \
66+
.data_chunk = { \
67+
.subchunk_id = {'d', 'a', 't', 'a'}, \
68+
.subchunk_size = (wav_sample_size) \
69+
} \
70+
}
71+
72+
#ifdef __cplusplus
73+
}
74+
#endif

m5stack/components/M5Unified/mpy_m5mic.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ extern "C"
1010
{
1111
#include "mpy_m5mic.h"
1212
#include "mic_config_t.h"
13+
#include "format_wav.h"
14+
#include "extmod/vfs_fat.h"
15+
#include "py/builtin.h"
16+
#include "py/runtime.h"
17+
#include "py/stream.h"
18+
#include "py/mphal.h"
1319

1420
namespace m5
1521
{
@@ -284,5 +290,77 @@ namespace m5
284290
return mp_obj_new_bool(ret);
285291
}
286292

293+
mp_obj_t mic_recordWavFile(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
294+
enum {ARG_path, ARG_rate, ARG_time, ARG_stereo};
295+
const mp_arg_t allowed_args[] = {
296+
/* *FORMAT-OFF* */
297+
{ MP_QSTR_path, MP_ARG_OBJ | MP_ARG_REQUIRED, { .u_obj = MP_OBJ_NULL } },
298+
{ MP_QSTR_rate, MP_ARG_INT, { .u_int = 16000 } },
299+
{ MP_QSTR_time, MP_ARG_INT, { .u_int = 5 } },
300+
{ MP_QSTR_stereo, MP_ARG_BOOL, { .u_bool = false } },
301+
/* *FORMAT-ON* */
302+
};
303+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
304+
// The first parameter is the Power object, parse from second parameter.
305+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
306+
307+
const char *file_path = mp_obj_str_get_str(args[ARG_path].u_obj);
308+
uint32_t rate = args[ARG_rate].u_int;
309+
uint32_t rec_time = args[ARG_time].u_int;
310+
bool stereo = args[ARG_stereo].u_bool;
311+
uint16_t channels = stereo ? 2 : 1;
312+
313+
char ftype[5] = {0};
314+
for (size_t i = 0; i < 4; i++) {
315+
ftype[i] = tolower((char)file_path[i + strlen(file_path) - 4]);
316+
}
317+
ftype[4] = '\0';
318+
319+
mp_obj_t file_args[2];
320+
file_args[0] = args[ARG_path].u_obj;
321+
file_args[1] = mp_obj_new_str("wb+", strlen("wb+"));
322+
mp_obj_t wav_file = mp_vfs_open(2, file_args, (mp_map_t *)&mp_const_empty_map);
323+
if (wav_file == mp_const_none) {
324+
mp_raise_OSError(MP_ENOENT);
325+
// mp_raise_ValueError(MP_ERROR_TEXT("open file failed"));
326+
}
327+
328+
int flash_wr_size = 0;
329+
uint32_t byte_rate = rate * 2 * channels; // bit sample size = 16bit, byte rate = sample rate * (bit sample size / 8) * channel
330+
uint32_t flash_rec_time = byte_rate * rec_time;
331+
332+
wav_header_t wav_header = WAV_HEADER_PCM_DEFAULT(flash_rec_time, 16, rate, channels);
333+
334+
int rlen = mp_stream_posix_write(wav_file, &wav_header, sizeof(wav_header_t));
335+
if (rlen != sizeof(wav_header_t)) {
336+
mp_stream_close(wav_file);
337+
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short"));
338+
}
339+
340+
Mic_Class *Mic = getMic(&pos_args[0]);
341+
342+
constexpr const size_t buf_size = 1024;
343+
int16_t rec_data[512];
344+
345+
// mp_printf(&mp_plat_print, "flash_rec_time: %d\n", flash_rec_time);
346+
347+
while (flash_wr_size < flash_rec_time) {
348+
bool ret = Mic->record(rec_data, buf_size >> 1, rate, stereo);
349+
350+
while (Mic->isRecording() != 0) {
351+
mp_hal_delay_ms(1);
352+
}
353+
size_t remain = flash_rec_time - flash_wr_size;
354+
size_t len = remain > buf_size ? buf_size : remain;
355+
int wlen = mp_stream_posix_write(wav_file, rec_data, len);
356+
if (wlen != len) {
357+
mp_stream_close(wav_file);
358+
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short"));
359+
}
360+
flash_wr_size += wlen;
361+
}
362+
mp_stream_close(wav_file);
363+
return mp_obj_new_bool(true);
364+
}
287365
}
288366
}

0 commit comments

Comments
 (0)