刚刚接触esp32,想要实现边录边播,想法是通过两个I2S,分别控制录音和播放。
我的代码实现如下:
公共部分的初始化:
esp_log_level_set("*", ESP_LOG_WARN);
esp_log_level_set(TAG, ESP_LOG_INFO);
esp_periph_config_t periph_cfg = { 0 };
esp_periph_init(&periph_cfg);
periph_sdcard_cfg_t sdcard_cfg = {
.root = "/sdcard",
.card_detect_pin = SD_CARD_INTR_GPIO, //GPIO_NUM_34
};
esp_periph_handle_t sdcard_handle = periph_sdcard_init(&sdcard_cfg);
esp_periph_start(sdcard_handle);
while (!periph_sdcard_is_mounted(sdcard_handle)) {
vTaskDelay(100 / portTICK_PERIOD_MS);
}
periph_touch_cfg_t touch_cfg = {
.touch_mask = TOUCH_SEL_SET | TOUCH_SEL_PLAY | TOUCH_SEL_VOLUP | TOUCH_SEL_VOLDWN,
.tap_threshold_percent = 70,
};
touch_periph = periph_touch_init(&touch_cfg);
esp_periph_start(touch_periph);
audio_hal_codec_config_t audio_hal_codec_cfg = AUDIO_HAL_ES8388_DEFAULT();
audio_hal_codec_cfg.i2s_iface.samples = AUDIO_HAL_44K_SAMPLES;
hal = audio_hal_init(&audio_hal_codec_cfg, 0);
audio_hal_ctrl_codec(hal, AUDIO_HAL_CODEC_MODE_BOTH, AUDIO_HAL_CTRL_START);
播放部分的初始化:
audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
pipeline_mp3 = audio_pipeline_init(&pipeline_cfg);
mem_assert(pipeline_mp3);
i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
i2s_cfg.i2s_port = I2S_NUM_0;
i2s_cfg.type = AUDIO_STREAM_WRITER;
i2s_stream_writer = i2s_stream_init(&i2s_cfg);
mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
mp3_decoder = mp3_decoder_init(&mp3_cfg);
audio_element_set_read_cb(mp3_decoder, my_sdcard_read_cb, NULL);
audio_pipeline_register(pipeline_mp3, mp3_decoder, "mp3");
audio_pipeline_register(pipeline_mp3, i2s_stream_writer, "i2s_mp3");
ESP_LOGI(TAG, "Link it together [my_sdcard_read_cb]-->mp3_decoder-->i2s_stream-->[codec_chip]");
audio_pipeline_link(pipeline_mp3, (const char *[]) {"mp3", "i2s_mp3"}, 2);
audio_event_iface_cfg_t evt_mp3_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
evt_mp3 = audio_event_iface_init(&evt_mp3_cfg);
audio_pipeline_set_listener(pipeline_mp3, evt_mp3);
audio_event_iface_set_listener(esp_periph_get_event_iface(), evt_mp3);
录音部分的初始化:
audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
pipeline_rec = audio_pipeline_init(&pipeline_cfg);
mem_assert(pipeline_rec);
fatfs_stream_cfg_t fatfs_cfg = FATFS_STREAM_CFG_DEFAULT();
fatfs_cfg.type = AUDIO_STREAM_WRITER;
fatfs_cfg.task_core = 1;
fatfs_stream_writer = fatfs_stream_init(&fatfs_cfg);
i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
i2s_cfg.i2s_port = I2S_NUM_1;
i2s_cfg.task_core = 1;
i2s_cfg.i2s_config.sample_rate = 8000;
i2s_cfg.i2s_config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT;
i2s_cfg.type = AUDIO_STREAM_READER;
i2s_stream_reader = i2s_stream_init(&i2s_cfg);
amrnb_encoder_cfg_t amr_enc_cfg = DEFAULT_AMRNB_ENCODER_CONFIG();
amr_enc_cfg.task_core = 1;
amr_encoder = amrnb_encoder_init(&amr_enc_cfg);
audio_pipeline_register(pipeline_rec, i2s_stream_reader, "i2s_rec");
audio_pipeline_register(pipeline_rec, amr_encoder, "amr");
audio_pipeline_register(pipeline_rec, fatfs_stream_writer, "file");
audio_pipeline_link(pipeline_rec, (const char *[]) {"i2s_rec", "amr", "file"}, 3);
audio_element_set_uri(fatfs_stream_writer, "/sdcard/rec.amr");
audio_event_iface_cfg_t evt_rec_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
evt_rec = audio_event_iface_init(&evt_rec_cfg);
audio_pipeline_set_listener(pipeline_rec, evt_rec);
播放事件:
int player_volume = 50;
audio_hal_set_volume(hal, player_volume);
audio_pipeline_run(pipeline_mp3);
while (1) {
/* Handle event interface messages from pipeline
to set music info and to advance to the next song
*/
audio_event_iface_msg_t msg;
esp_err_t ret = audio_event_iface_listen(evt_mp3, &msg, portMAX_DELAY);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret);
continue;
}
if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT){
// Set music info for a new song to be played
if (msg.source == (void *) mp3_decoder
&& msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
audio_element_info_t music_info = {0};
audio_element_getinfo(mp3_decoder, &music_info);
ESP_LOGI(TAG, "[ * ] Received music info from mp3 decoder, sample_rates=%d, bits=%d, ch=%d",
music_info.sample_rates, music_info.bits, music_info.channels);
audio_element_setinfo(i2s_stream_writer, &music_info);
i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
continue;
}
// Advance to the next song when previous finishes
if (msg.source == (void *) i2s_stream_writer
&& msg.cmd == AEL_MSG_CMD_REPORT_STATUS) {
audio_element_state_t el_state = audio_element_get_state(i2s_stream_writer);
if (el_state == AEL_STATE_FINISHED) {
ESP_LOGI(TAG, "[ * ] Finished, advancing to the next song");
audio_pipeline_stop(pipeline_mp3);
audio_pipeline_wait_for_stop(pipeline_mp3);
get_file(NEXT);
audio_pipeline_run(pipeline_mp3);
}
continue;
}
}
录音事件:
audio_pipeline_run(pipeline_rec);
int second_recorded = 0;
while (1) {
audio_event_iface_msg_t msg;
if (audio_event_iface_listen(evt_rec, &msg, 1000 / portTICK_RATE_MS) != ESP_OK) {
second_recorded++;
ESP_LOGI(TAG, "[ * ] Recording ... %d", second_recorded);
if (second_recorded >= 5) {
ESP_LOGI(TAG, "Finishing amr recording");
break;
}
continue;
}
/* Stop when the last pipeline element (i2s_stream_reader in this case) receives stop event */
if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *)i2s_stream_reader && msg.cmd ==
AEL_MSG_CMD_REPORT_STATUS && (int)msg.data == AEL_STATUS_STATE_STOPPED) {
ESP_LOGW(TAG, "[ * ] Stop event received");
break;
}
}
两个线程单独运行ok,如果同步运行的话,会出现播放卡顿,录音出来的声音是很尖锐的噪音。
请问是不是我的处理思路有问题,或是配置有问题?烦请解答,不胜感激!
最近编辑记录 dy_xie (2018-11-23 10:11:52)
离线
采集线程与播放线程如何通讯呢?
这里没有考虑通讯,只是在测试可行性,如果要通讯的话可能会通过锁来同步。
离线
这个ESP32框架代码没看懂,这是录到 TF 卡,然后播放?
或者录在RAM里面,然后播放线程再从内存播放?
是这样的:TF卡里有MP3资源,先启动播放线程读取卡中的资源并播放。开始播放之后启动录音线程采集并保存到TF卡中。
实际上就是做到自带BGM的录音笔小demo。
离线
dy_xie 说:lilo 说:这个ESP32框架代码没看懂,这是录到 TF 卡,然后播放?
或者录在RAM里面,然后播放线程再从内存播放?是这样的:TF卡里有MP3资源,先启动播放线程读取卡中的资源并播放。开始播放之后启动录音线程采集并保存到TF卡中。
实际上就是做到自带BGM的录音笔小demo。播放和录音线程是操作同一个文件吗?
不是的,播放的是MP3文件,保存的是AMR文件。
离线
播放的I2S初始化:
i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
i2s_cfg.i2s_port = I2S_NUM_0;
i2s_cfg.type = AUDIO_STREAM_WRITER;
i2s_stream_writer = i2s_stream_init(&i2s_cfg);录音的I2S初始化:
i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
i2s_cfg.i2s_port = I2S_NUM_1;
i2s_cfg.task_core = 1;
i2s_cfg.i2s_config.sample_rate = 8000;
i2s_cfg.i2s_config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT;
i2s_cfg.type = AUDIO_STREAM_READER;
i2s_stream_reader = i2s_stream_init(&i2s_cfg);这是播放用I2S0 口, 录音用I2S1 口?
是的,这样有问题吗?
离线
这样做貌似都挺好,没有问题, 看来得用逻辑分析仪上了。
条件有限,不知道如何从软件上来处理了。
离线
dy_xie 说:lilo 说:这样做貌似都挺好,没有问题, 看来得用逻辑分析仪上了。
条件有限,不知道如何从软件上来处理了。
淘宝上逻辑分析仪不贵, 大概就几十块钱,可以非常直观分析各种协议 I2C/SPI/UART/I2S 等
好的,非常感谢你的建议,我学习一下如何使用分析。
离线