i2s_record_play

這章主要講述i2s
1:環境及配件
esp32c3
esp32s3
idf5.4.1
INMP441
MAX98357A
都使用dma

2:eps32c3 測試
只有1個i2s 只能一邊錄 完 再播放 ,內存太小,這里用 flash 存audio里
只說能 錄音 能播放 ,效果不好,后面再優化下
nvs, data, nvs, , 0x6000,
phy_init, data, phy, , 0x1000,
factory, app, factory, , 2M,
assets, data, spiffs, , 500K,
audio, data, spiffs, , 1M,

main/cmakefile
idf_component_register(SRCS “i2s_std_example_main.c”
#PRIV_REQUIRES nvs_flash esp_driver_i2s esp_driver_gpio i2s_examples_common
PRIV_REQUIRES nvs_flash driver spiffs
INCLUDE_DIRS “.”)
上代碼

#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <sys/stat.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "driver/i2s_std.h"
#include "esp_spiffs.h"// ========================== 基礎配置 ==========================
#define SAMPLE_RATE     44100          // 44.1kHz采樣率(人聲常用)
#define MAX_RECORD_TIME 5.0f           // 最大錄音時間:5秒(適配1M SPIFFS)
#define DATA_BIT_WIDTH  I2S_DATA_BIT_WIDTH_32BIT  // 32bit接收(適配INMP441的24bit輸出)
#define BYTES_PER_SAMPLE 4             // 每采樣字節數(32bit=4字節)
#define MAX_RECORD_SAMPLES (uint32_t)(MAX_RECORD_TIME * SAMPLE_RATE)  // 最大采樣數:220500
#define MAX_RECORD_BYTES (MAX_RECORD_SAMPLES * BYTES_PER_SAMPLE)      // 最大錄音字節數:882000字節// 硬件引腳配置
#define BUTTON_GPIO_NUM 2       // 按鈕GPIO
#define I2S_NUM         I2S_NUM_0       // 使用I2S0
#define I2S_MCLK_GPIO   11      // INMP441必需的主時鐘引腳
#define I2S_BCK_GPIO    3       // I2S位時鐘(BCK)
#define I2S_WS_GPIO     10      // I2S字選擇(LRCK)
#define I2S_DO_GPIO     6       // 揚聲器數據輸出
#define I2S_DI_GPIO     7       // INMP441數據輸入// DMA配置(適配ESP32-C3內存)
#define DMA_DESC_NUM    6       // DMA描述符數量
#define DMA_FRAME_NUM   256     // 每個描述符幀數(256×4=1024字節/緩沖區)
#define DMA_BUFFER_BYTES (DMA_FRAME_NUM * BYTES_PER_SAMPLE)  // 單DMA緩沖區大小// SPIFFS配置(匹配分區表)
#define SPIFFS_PARTITION_LABEL "audio"  // 分區表中"audio"分區
#define RECORD_FILE_PATH "/spiffs/recording.raw"  // 錄音文件路徑
#define SPIFFS_MAX_OPEN_FILES 1        // 最大同時打開文件數static const char *TAG = "inmp441_spiffs_fixed";// ========================== 全局變量(補充任務句柄定義)==========================
static i2s_chan_handle_t tx_handle = NULL;  // 播放通道句柄
static i2s_chan_handle_t rx_handle = NULL;  // 錄音通道句柄
static SemaphoreHandle_t i2s_mutex = NULL;   // I2S資源互斥鎖
static SemaphoreHandle_t file_mutex = NULL;  // 文件操作互斥鎖
static volatile bool button_pressed = false; // 按鈕狀態
static volatile bool is_recording = false;   // 錄音狀態
static volatile uint32_t recorded_samples = 0;  // 已錄音采樣數
// 補充任務句柄的全局定義(解決未定義錯誤)
static TaskHandle_t audio_record_task_handle = NULL;  // 錄音任務句柄
static TaskHandle_t audio_play_task_handle = NULL;     // 播放任務句柄// ========================== 按鈕中斷處理 ==========================
static void IRAM_ATTR button_isr_handler(void *arg)
{int level = gpio_get_level(BUTTON_GPIO_NUM);button_pressed = (level == 0);if (level == 0) { // 按鈕按下:啟動錄音is_recording = true;recorded_samples = 0;if (audio_record_task_handle != NULL) {BaseType_t xHigherPriorityTaskWoken = pdFALSE;vTaskNotifyGiveFromISR(audio_record_task_handle, &xHigherPriorityTaskWoken);portYIELD_FROM_ISR(xHigherPriorityTaskWoken);}} else { // 按鈕釋放:啟動播放is_recording = false;if (audio_play_task_handle != NULL) {BaseType_t xHigherPriorityTaskWoken = pdFALSE;vTaskNotifyGiveFromISR(audio_play_task_handle, &xHigherPriorityTaskWoken);portYIELD_FROM_ISR(xHigherPriorityTaskWoken);}}
}// ========================== SPIFFS初始化 ==========================
static esp_err_t spiffs_audio_partition_init(void)
{ESP_LOGI(TAG, "Initializing SPIFFS (partition: %s)", SPIFFS_PARTITION_LABEL);esp_vfs_spiffs_conf_t spiffs_conf = {.base_path = "/spiffs",.partition_label = SPIFFS_PARTITION_LABEL,.max_files = SPIFFS_MAX_OPEN_FILES,.format_if_mount_failed = true};esp_err_t ret = esp_vfs_spiffs_register(&spiffs_conf);if (ret != ESP_OK) {if (ret == ESP_FAIL) ESP_LOGE(TAG, "SPIFFS mount failed");else if (ret == ESP_ERR_NOT_FOUND) ESP_LOGE(TAG, "SPIFFS partition not found");else ESP_LOGE(TAG, "SPIFFS init error: 0x%x", ret);return ret;}size_t total_size = 0, used_size = 0;ret = esp_spiffs_info(SPIFFS_PARTITION_LABEL, &total_size, &used_size);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to get SPIFFS info: 0x%x", ret);esp_vfs_spiffs_unregister(SPIFFS_PARTITION_LABEL);return ret;}ESP_LOGI(TAG, "SPIFFS mounted: Total=%d KB, Used=%d KB", total_size/1024, used_size/1024);return ESP_OK;
}// ========================== INMP441錄音初始化 ==========================
static esp_err_t init_i2s_inmp441_dma(void)
{esp_err_t ret = ESP_OK;if (xSemaphoreTake(i2s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {ESP_LOGE(TAG, "Failed to take I2S mutex (recording)");return ESP_FAIL;}if (rx_handle != NULL) {i2s_channel_disable(rx_handle);i2s_del_channel(rx_handle);rx_handle = NULL;}i2s_chan_config_t chan_cfg = {.id = I2S_NUM,.role = I2S_ROLE_MASTER,.dma_desc_num = DMA_DESC_NUM,.dma_frame_num = DMA_FRAME_NUM,.auto_clear = true,.intr_priority = 0,};ret = i2s_new_channel(&chan_cfg, NULL, &rx_handle);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to create RX channel: 0x%x", ret);goto release_i2s_mutex;}i2s_std_config_t std_cfg = {.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(DATA_BIT_WIDTH, I2S_SLOT_MODE_MONO),.gpio_cfg = {.mclk = I2S_MCLK_GPIO,.bclk = I2S_BCK_GPIO,.ws = I2S_WS_GPIO,.dout = I2S_GPIO_UNUSED,.din = I2S_DI_GPIO,.invert_flags = {false, false, false}},};ret = i2s_channel_init_std_mode(rx_handle, &std_cfg);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to init RX mode: 0x%x", ret);goto delete_rx_channel;}ret = i2s_channel_enable(rx_handle);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to enable RX channel: 0x%x", ret);goto delete_rx_channel;}ESP_LOGI(TAG, "INMP441 DMA init success");return ESP_OK;delete_rx_channel:i2s_del_channel(rx_handle);rx_handle = NULL;
release_i2s_mutex:xSemaphoreGive(i2s_mutex);return ret;
}// ========================== 揚聲器播放初始化 ==========================
static esp_err_t init_i2s_speaker_dma(void)
{esp_err_t ret = ESP_OK;if (xSemaphoreTake(i2s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {ESP_LOGE(TAG, "Failed to take I2S mutex (playback)");return ESP_FAIL;}if (tx_handle != NULL) {i2s_channel_disable(tx_handle);i2s_del_channel(tx_handle);tx_handle = NULL;}i2s_chan_config_t chan_cfg = {.id = I2S_NUM,.role = I2S_ROLE_MASTER,.dma_desc_num = DMA_DESC_NUM,.dma_frame_num = DMA_FRAME_NUM,.auto_clear = true,.intr_priority = 0,};ret = i2s_new_channel(&chan_cfg, &tx_handle, NULL);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to create TX channel: 0x%x", ret);goto release_i2s_mutex;}i2s_std_config_t std_cfg = {.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(DATA_BIT_WIDTH, I2S_SLOT_MODE_MONO),.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,.bclk = I2S_BCK_GPIO,.ws = I2S_WS_GPIO,.dout = I2S_DO_GPIO,.din = I2S_GPIO_UNUSED,.invert_flags = {false, false, false}},};ret = i2s_channel_init_std_mode(tx_handle, &std_cfg);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to init TX mode: 0x%x", ret);goto delete_tx_channel;}ret = i2s_channel_enable(tx_handle);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to enable TX channel: 0x%x", ret);goto delete_tx_channel;}ESP_LOGI(TAG, "Speaker DMA init success");return ESP_OK;delete_tx_channel:i2s_del_channel(tx_handle);tx_handle = NULL;
release_i2s_mutex:xSemaphoreGive(i2s_mutex);return ret;
}// ========================== I2S資源釋放 ==========================
static void deinit_i2s_dma(void)
{if (rx_handle != NULL) {i2s_channel_disable(rx_handle);i2s_del_channel(rx_handle);rx_handle = NULL;}if (tx_handle != NULL) {i2s_channel_disable(tx_handle);i2s_del_channel(tx_handle);tx_handle = NULL;}xSemaphoreGive(i2s_mutex);
}// ========================== 錄音任務 ==========================
static void audio_record_task_dma(void *arg)
{ESP_LOGI(TAG, "Record task started (save to: %s)", RECORD_FILE_PATH);uint8_t dma_rx_buf[DMA_BUFFER_BYTES] = {0};while (1) {ulTaskNotifyTake(pdTRUE, portMAX_DELAY);ESP_LOGI(TAG, "Recording started (max %.1f sec)", MAX_RECORD_TIME);if (init_i2s_inmp441_dma() != ESP_OK) {ESP_LOGE(TAG, "INMP441 init failed, skip recording");continue;}FILE *rec_file = NULL;if (xSemaphoreTake(file_mutex, pdMS_TO_TICKS(500)) == pdTRUE) {rec_file = fopen(RECORD_FILE_PATH, "wb");xSemaphoreGive(file_mutex);}if (rec_file == NULL) {ESP_LOGE(TAG, "Failed to open record file");deinit_i2s_dma();continue;}recorded_samples = 0;bool record_error = false;while (is_recording && recorded_samples < MAX_RECORD_SAMPLES && !record_error) {size_t bytes_read = 0;esp_err_t ret = i2s_channel_read(rx_handle,dma_rx_buf,DMA_BUFFER_BYTES,&bytes_read,pdMS_TO_TICKS(100));if (ret != ESP_OK || bytes_read == 0) {ESP_LOGE(TAG, "DMA read failed (ret=0x%x, bytes_read=%d)", ret, bytes_read);record_error = true;break;}uint32_t new_samples = bytes_read / BYTES_PER_SAMPLE;if (recorded_samples + new_samples > MAX_RECORD_SAMPLES) {new_samples = MAX_RECORD_SAMPLES - recorded_samples;bytes_read = new_samples * BYTES_PER_SAMPLE;is_recording = false;ESP_LOGW(TAG, "Reached max record time");}bool write_ok = false;if (xSemaphoreTake(file_mutex, pdMS_TO_TICKS(500)) == pdTRUE) {size_t bytes_written = fwrite(dma_rx_buf, 1, bytes_read, rec_file);write_ok = (bytes_written == bytes_read);xSemaphoreGive(file_mutex);}if (write_ok) {recorded_samples += new_samples;if (recorded_samples % (uint32_t)(SAMPLE_RATE * 0.5) == 0) {ESP_LOGI(TAG, "Recorded: %.1f sec (%" PRIu32 " samples)", recorded_samples/(float)SAMPLE_RATE, recorded_samples);}} else {ESP_LOGE(TAG, "File write failed");record_error = true;}}if (xSemaphoreTake(file_mutex, pdMS_TO_TICKS(500)) == pdTRUE) {fclose(rec_file);xSemaphoreGive(file_mutex);}deinit_i2s_dma();if (record_error) {ESP_LOGE(TAG, "Recording aborted due to error");} else {ESP_LOGI(TAG, "Recording finished: %.1f sec", recorded_samples/(float)SAMPLE_RATE);}}vTaskDelete(NULL);
}// ========================== 播放任務 ==========================
static void audio_play_task_dma(void *arg)
{ESP_LOGI(TAG, "Play task started (read from: %s)", RECORD_FILE_PATH);uint8_t dma_tx_buf[DMA_BUFFER_BYTES] = {0};while (1) {ulTaskNotifyTake(pdTRUE, portMAX_DELAY);ESP_LOGI(TAG, "Playback started");while (is_recording) vTaskDelay(pdMS_TO_TICKS(10));struct stat file_stat;bool file_exists = false;if (xSemaphoreTake(file_mutex, pdMS_TO_TICKS(500)) == pdTRUE) {file_exists = (stat(RECORD_FILE_PATH, &file_stat) == 0);xSemaphoreGive(file_mutex);}if (!file_exists) {ESP_LOGW(TAG, "Record file not found");continue;} else if (file_stat.st_size == 0) {ESP_LOGW(TAG, "Record file is empty");recorded_samples = 0;continue;} else if (recorded_samples == 0) {recorded_samples = file_stat.st_size / BYTES_PER_SAMPLE;ESP_LOGW(TAG, "Sample count reset from file size: %" PRIu32, recorded_samples);}if (init_i2s_speaker_dma() != ESP_OK) {ESP_LOGE(TAG, "Speaker init failed, skip playback");continue;}FILE *play_file = NULL;if (xSemaphoreTake(file_mutex, pdMS_TO_TICKS(500)) == pdTRUE) {play_file = fopen(RECORD_FILE_PATH, "rb");xSemaphoreGive(file_mutex);}if (play_file == NULL) {ESP_LOGE(TAG, "Failed to open play file");deinit_i2s_dma();continue;}size_t total_bytes_played = 0;const size_t total_play_bytes = recorded_samples * BYTES_PER_SAMPLE;bool play_error = false;while (total_bytes_played < total_play_bytes && !play_error) {size_t bytes_to_read = DMA_BUFFER_BYTES;if (total_bytes_played + bytes_to_read > total_play_bytes) {bytes_to_read = total_play_bytes - total_bytes_played;}size_t bytes_read = 0;if (xSemaphoreTake(file_mutex, pdMS_TO_TICKS(500)) == pdTRUE) {bytes_read = fread(dma_tx_buf, 1, bytes_to_read, play_file);xSemaphoreGive(file_mutex);}if (bytes_read == 0) {ESP_LOGE(TAG, "File read failed");play_error = true;break;}size_t bytes_written = 0;esp_err_t ret = i2s_channel_write(tx_handle,dma_tx_buf,bytes_read,&bytes_written,pdMS_TO_TICKS(100));if (ret != ESP_OK || bytes_written != bytes_read) {ESP_LOGE(TAG, "DMA write failed (ret=0x%x, written=%d)", ret, bytes_written);play_error = true;break;}total_bytes_played += bytes_written;uint32_t played_samples = total_bytes_played / BYTES_PER_SAMPLE;if (played_samples % (uint32_t)(SAMPLE_RATE * 0.5) == 0) {ESP_LOGI(TAG, "Played: %.1f sec / %.1f sec", played_samples/(float)SAMPLE_RATE,recorded_samples/(float)SAMPLE_RATE);}}if (xSemaphoreTake(file_mutex, pdMS_TO_TICKS(500)) == pdTRUE) {fclose(play_file);xSemaphoreGive(file_mutex);}deinit_i2s_dma();if (play_error) {ESP_LOGE(TAG, "Playback aborted due to error");} else {ESP_LOGI(TAG, "Playback finished: %.1f sec", recorded_samples/(float)SAMPLE_RATE);}}vTaskDelete(NULL);
}// ========================== 主函數 ==========================
void app_main(void)
{ESP_LOGI(TAG, "INMP441 SPIFFS DMA Loopback starting...");// 創建互斥鎖i2s_mutex = xSemaphoreCreateMutex();file_mutex = xSemaphoreCreateMutex();if (i2s_mutex == NULL || file_mutex == NULL) {ESP_LOGE(TAG, "Failed to create semaphores");return;}// 初始化SPIFFSesp_err_t ret = spiffs_audio_partition_init();if (ret != ESP_OK) {ESP_LOGE(TAG, "SPIFFS init failed, app exit");vSemaphoreDelete(i2s_mutex);vSemaphoreDelete(file_mutex);return;}// 初始化按鈕GPIOgpio_config_t io_conf = {.pin_bit_mask = (1ULL << BUTTON_GPIO_NUM),.mode = GPIO_MODE_INPUT,.pull_up_en = GPIO_PULLUP_ENABLE,.pull_down_en = GPIO_PULLDOWN_DISABLE,.intr_type = GPIO_INTR_ANYEDGE};ESP_ERROR_CHECK(gpio_config(&io_conf));ESP_ERROR_CHECK(gpio_install_isr_service(0));ESP_ERROR_CHECK(gpio_isr_handler_add(BUTTON_GPIO_NUM, button_isr_handler, NULL));// 創建錄音/播放任務(使用全局句柄)xTaskCreate(audio_record_task_dma, "audio_record_task", 10240, NULL, 5, &audio_record_task_handle);xTaskCreate(audio_play_task_dma, "audio_play_task", 10240, NULL, 5, &audio_play_task_handle);ESP_LOGI(TAG, "App ready: Press button to record, release to play");
}

直接用內存的

#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "driver/i2s_std.h"// 定義常量
#define SAMPLE_RATE 16000
#define MAX_RECORD_TIME (3.0f)  // 最大錄音時間(秒)
#define MAX_RECORD_SAMPLES (uint32_t)(MAX_RECORD_TIME * SAMPLE_RATE)  //16000*3 =48000
#define AUDIO_BUFFER_SIZE (MAX_RECORD_SAMPLES * sizeof(int16_t))  //48000*2 =96000// 根據您的硬件配置修改這些定義
#define BUTTON_GPIO_NUM 2       // 按鈕連接到GPIO9
#define I2S_NUM I2S_NUM_0       // 使用I2S0
#define I2S_BCK_GPIO 3          // I2S位時鐘GPIO
#define I2S_WS_GPIO 10          // I2S字選擇GPIO
#define I2S_DO_GPIO 6           // I2S數據輸出GPIO(播放到MAX98357)
#define I2S_DI_GPIO 7           // I2S數據輸入GPIO(從INMP441錄音)
//GAIN:增益控制(懸空=3dB,接地=9dB,接VDD=15dB)
static const char *TAG = "audio_loop";// 全局變量
static TaskHandle_t audio_play_task_handle = NULL;
static TaskHandle_t audio_record_task_handle = NULL;
static volatile bool is_recording = false;
static volatile uint32_t recorded_samples = 0;
static uint8_t *audio_buffer = NULL;
static i2s_chan_handle_t tx_handle = NULL;  // I2S TX通道句柄
static i2s_chan_handle_t rx_handle = NULL;  // I2S RX通道句柄
static SemaphoreHandle_t i2s_mutex = NULL;  // I2S資源互斥鎖// 簡單的按鈕狀態變量
static volatile bool button_pressed = false;// 按鈕中斷處理函數 - 簡化版本,避免使用任何可能引起棧問題的函數
static void IRAM_ATTR button_isr_handler(void *arg)
{int level = gpio_get_level(BUTTON_GPIO_NUM);button_pressed = (level == 0);if (level == 0) { // 按下is_recording = true;recorded_samples = 0;// 使用輕量級通知方式if (audio_record_task_handle != NULL) {BaseType_t xHigherPriorityTaskWoken = pdFALSE;vTaskNotifyGiveFromISR(audio_record_task_handle, &xHigherPriorityTaskWoken);portYIELD_FROM_ISR(xHigherPriorityTaskWoken);}} else { // 釋放is_recording = false;// 使用輕量級通知方式if (audio_play_task_handle != NULL) {BaseType_t xHigherPriorityTaskWoken = pdFALSE;vTaskNotifyGiveFromISR(audio_play_task_handle, &xHigherPriorityTaskWoken);portYIELD_FROM_ISR(xHigherPriorityTaskWoken);}}
}// 初始化I2S用于錄音(INMP441)
static esp_err_t init_i2s_microphone(void)
{esp_err_t ret = ESP_OK;// 獲取I2S資源鎖if (xSemaphoreTake(i2s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {ESP_LOGE(TAG, "Failed to acquire I2S mutex for recording");return ESP_FAIL;}// 如果已有通道,先刪除if (rx_handle != NULL) {i2s_channel_disable(rx_handle);i2s_del_channel(rx_handle);rx_handle = NULL;}// 配置I2S通道i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM, I2S_ROLE_MASTER);ret = i2s_new_channel(&chan_cfg, NULL, &rx_handle);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to create I2S RX channel: %d", ret);xSemaphoreGive(i2s_mutex);return ret;}// 配置I2S標準模式i2s_std_config_t std_cfg = {.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,  // INMP441不需要MCLK.bclk = I2S_BCK_GPIO,.ws = I2S_WS_GPIO,.dout = I2S_GPIO_UNUSED,.din = I2S_DI_GPIO,.invert_flags = {.mclk_inv = false,.bclk_inv = false,.ws_inv = false,},},};// 初始化RX通道ret = i2s_channel_init_std_mode(rx_handle, &std_cfg);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to init I2S RX channel: %d", ret);i2s_del_channel(rx_handle);rx_handle = NULL;xSemaphoreGive(i2s_mutex);return ret;}ret = i2s_channel_enable(rx_handle);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to enable I2S RX channel: %d", ret);i2s_del_channel(rx_handle);rx_handle = NULL;xSemaphoreGive(i2s_mutex);return ret;}return ESP_OK;
}// 初始化I2S用于播放(MAX98357)
static esp_err_t init_i2s_speaker(void)
{esp_err_t ret = ESP_OK;// 獲取I2S資源鎖if (xSemaphoreTake(i2s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {ESP_LOGE(TAG, "Failed to acquire I2S mutex for playback");return ESP_FAIL;}// 如果已有通道,先刪除if (tx_handle != NULL) {i2s_channel_disable(tx_handle);i2s_del_channel(tx_handle);tx_handle = NULL;}// 配置I2S通道i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM, I2S_ROLE_MASTER);ret = i2s_new_channel(&chan_cfg, &tx_handle, NULL);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to create I2S TX channel: %d", ret);xSemaphoreGive(i2s_mutex);return ret;}// 配置I2S標準模式i2s_std_config_t std_cfg = {.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,  // MAX98357不需要MCLK.bclk = I2S_BCK_GPIO,.ws = I2S_WS_GPIO,.dout = I2S_DO_GPIO,.din = I2S_GPIO_UNUSED,.invert_flags = {.mclk_inv = false,.bclk_inv = false,.ws_inv = false,},},};// 初始化TX通道ret = i2s_channel_init_std_mode(tx_handle, &std_cfg);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to init I2S TX channel: %d", ret);i2s_del_channel(tx_handle);tx_handle = NULL;xSemaphoreGive(i2s_mutex);return ret;}ret = i2s_channel_enable(tx_handle);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to enable I2S TX channel: %d", ret);i2s_del_channel(tx_handle);tx_handle = NULL;xSemaphoreGive(i2s_mutex);return ret;}return ESP_OK;
}// 完全釋放I2S資源
static void deinit_i2s(void)
{// 禁用并刪除RX通道if (rx_handle != NULL) {i2s_channel_disable(rx_handle);i2s_del_channel(rx_handle);rx_handle = NULL;}// 禁用并刪除TX通道if (tx_handle != NULL) {i2s_channel_disable(tx_handle);i2s_del_channel(tx_handle);tx_handle = NULL;}// 釋放I2S資源鎖xSemaphoreGive(i2s_mutex);
}// 音頻播放任務
static void audio_play_task(void *arg)
{ESP_LOGI(TAG, "Audio play task started");while (1) {// 等待通知開始播放ulTaskNotifyTake(pdTRUE, portMAX_DELAY);// 確保錄音已經停止while (is_recording) {vTaskDelay(pdMS_TO_TICKS(10));}if (recorded_samples > 0) {ESP_LOGI(TAG, "Start playback, recorded samples: %" PRIu32, recorded_samples);// 初始化I2S用于播放if (init_i2s_speaker() != ESP_OK) {ESP_LOGE(TAG, "Failed to initialize I2S for playback");continue;}// 播放錄音size_t bytes_written = 0;size_t total_bytes = recorded_samples * sizeof(int16_t);while (bytes_written < total_bytes) {size_t bytes_to_write = total_bytes - bytes_written;if (bytes_to_write > 4096) bytes_to_write = 4096; // 每次最多寫入4KBsize_t bytes_written_this_time = 0;i2s_channel_write(tx_handle, audio_buffer + bytes_written, bytes_to_write, &bytes_written_this_time, portMAX_DELAY);bytes_written += bytes_written_this_time;// 添加小延遲,避免任務占用過多CPUvTaskDelay(pdMS_TO_TICKS(1));}// 禁用并刪除通道deinit_i2s();ESP_LOGI(TAG, "Playback finished");} else {ESP_LOGW(TAG, "No audio data to play");}}vTaskDelete(NULL);
}// 音頻錄音任務
static void audio_record_task(void *arg)
{ESP_LOGI(TAG, "Audio record task started");while(1) {// 等待通知開始錄音ulTaskNotifyTake(pdTRUE, portMAX_DELAY);ESP_LOGI(TAG, "Start recording");// 初始化I2S用于錄音if (init_i2s_microphone() != ESP_OK) {ESP_LOGE(TAG, "Failed to initialize I2S for recording");continue;}recorded_samples = 0;// 錄音直到按鈕釋放或達到最大時間while (is_recording && recorded_samples < MAX_RECORD_SAMPLES) {// 每次讀取100ms的音頻數據uint32_t samples_to_read = SAMPLE_RATE / 10;if (recorded_samples + samples_to_read > MAX_RECORD_SAMPLES) {samples_to_read = MAX_RECORD_SAMPLES - recorded_samples;}size_t bytes_read = 0;i2s_channel_read(rx_handle, audio_buffer + (recorded_samples * sizeof(int16_t)), samples_to_read * sizeof(int16_t),&bytes_read,portMAX_DELAY);if (bytes_read > 0) {recorded_samples += bytes_read / sizeof(int16_t);// 減少日志輸出頻率,避免占用過多資源if (recorded_samples % (SAMPLE_RATE / 2) == 0) {ESP_LOGI(TAG, "Recorded %" PRIu32 " samples", recorded_samples);}} else {ESP_LOGE(TAG, "Failed to read audio data");break;}vTaskDelay(pdMS_TO_TICKS(10)); // 稍微延遲以避免任務占用過多CPU}// 禁用并刪除通道deinit_i2s();ESP_LOGI(TAG, "Recording finished, total samples: %" PRIu32, recorded_samples);}vTaskDelete(NULL);
}void app_main(void)
{ESP_LOGI(TAG, "Audio loopback application starting");// 創建I2S資源互斥鎖i2s_mutex = xSemaphoreCreateMutex();if (i2s_mutex == NULL) {ESP_LOGE(TAG, "Failed to create I2S mutex");return;}// 初始化按鈕GPIOgpio_config_t io_conf = {.pin_bit_mask = (1ULL << BUTTON_GPIO_NUM),.mode = GPIO_MODE_INPUT,.pull_up_en = GPIO_PULLUP_ENABLE,.pull_down_en = GPIO_PULLDOWN_DISABLE,.intr_type = GPIO_INTR_ANYEDGE,};esp_err_t ret = gpio_config(&io_conf);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to configure button GPIO: %d", ret);vSemaphoreDelete(i2s_mutex);return;}// 安裝GPIO中斷服務ret = gpio_install_isr_service(0);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to install GPIO ISR service: %d", ret);vSemaphoreDelete(i2s_mutex);return;}ret = gpio_isr_handler_add(BUTTON_GPIO_NUM, button_isr_handler, NULL);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to add GPIO ISR handler: %d", ret);vSemaphoreDelete(i2s_mutex);return;}// 分配音頻緩沖區audio_buffer = heap_caps_malloc(AUDIO_BUFFER_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);if (audio_buffer == NULL) {// 如果SPIRAM不可用,嘗試內部內存audio_buffer = malloc(AUDIO_BUFFER_SIZE);if (audio_buffer == NULL) {ESP_LOGE(TAG, "Failed to allocate audio buffer of size %" PRIu32, (uint32_t)AUDIO_BUFFER_SIZE);vSemaphoreDelete(i2s_mutex);return;}ESP_LOGI(TAG, "Audio buffer allocated in internal memory");} else {ESP_LOGI(TAG, "Audio buffer allocated in SPIRAM");}memset(audio_buffer, 0, AUDIO_BUFFER_SIZE);// 創建任務 - 增加棧大小xTaskCreate(audio_play_task, "audio_play_task", 8192, NULL, 5, &audio_play_task_handle);xTaskCreate(audio_record_task, "audio_record_task", 8192, NULL, 5, &audio_record_task_handle);ESP_LOGI(TAG, "Audio loopback application started");ESP_LOGI(TAG, "Press and hold the button to record, release to play");
}

3:eps32s3 n16r8
配置
Partition Table
Single factory app (large), no OTA
Offset of partition table
0x8000

參考 https://blog.csdn.net/qq_44392698/article/details/147032820
實時錄音播放 別人的代碼 修改下

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"#define BUTTON_GPIO_NUM 47      // 按鈕連接到GPIO9
//INMP引腳,根據自己連線修改
#define INMP_SD     GPIO_NUM_6
#define INMP_SCK    GPIO_NUM_5
#define INMP_WS     GPIO_NUM_4//MAX98357A引腳,根據自己連線修改
#define MAX_DIN     GPIO_NUM_7
#define MAX_BCLK    GPIO_NUM_15
#define MAX_LRC     GPIO_NUM_16//配置rx對INMP441的采樣率為44.1kHz,這是常用的人聲采樣率
#define SAMPLE_RATE 44100//buf size計算方法:根據esp32官方文檔,buf size = dma frame num * 聲道數 * 數據位寬 / 8
#define BUF_SIZE    (1023 * 1 * 32 / 8)//音頻buffer
uint8_t buf[BUF_SIZE];i2s_chan_handle_t rx_handle;
i2s_chan_handle_t tx_handle;//初始化i2s rx,用于從INMP441接收數據
static void i2s_rx_init(void)
{i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);//dma frame num使用最大值,增大dma一次搬運的數據量,能夠提高效率,減小雜音,使用1023可以做到沒有一絲雜音chan_cfg.dma_frame_num = 511;i2s_new_channel(&chan_cfg, NULL, &rx_handle);i2s_std_config_t std_cfg = {.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),//雖然inmp441采集數據為24bit,但是仍可使用32bit來接收,中間存儲過程不需考慮,只要讓聲音怎么進來就怎么出去即可.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,.dout = I2S_GPIO_UNUSED,.bclk = INMP_SCK,.ws = INMP_WS,.din = INMP_SD,.invert_flags = {.mclk_inv = false,.bclk_inv = false,.ws_inv = false,},},};i2s_channel_init_std_mode(rx_handle, &std_cfg);i2s_channel_enable(rx_handle);
}//初始化tx,用于向MAX98357A寫數據
static void i2s_tx_init(void)
{i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER);chan_cfg.dma_frame_num = 511;i2s_new_channel(&chan_cfg, &tx_handle, NULL);i2s_std_config_t std_cfg = {.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT,I2S_SLOT_MODE_STEREO),.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,.din = I2S_GPIO_UNUSED,.bclk = MAX_BCLK,.ws = MAX_LRC,.dout = MAX_DIN,.invert_flags = {.mclk_inv = false,.bclk_inv = false,.ws_inv = false,},},};i2s_channel_init_std_mode(tx_handle, &std_cfg);i2s_channel_enable(tx_handle);
}static void i2s_read_task(void *args)
{size_t bytes = 0;//一次性讀取buf_size數量的音頻,即dma最大搬運一次的數據量,讀成功后,寫入tx,即可通過MAX98357A播放while (1) {if (i2s_channel_read(rx_handle, buf, BUF_SIZE, &bytes, 1000) == ESP_OK){i2s_channel_write(tx_handle, buf, BUF_SIZE, &bytes, 1000);}vTaskDelay(pdMS_TO_TICKS(5));}vTaskDelete(NULL);
}void app_main(void){i2s_tx_init();i2s_rx_init();xTaskCreate(i2s_read_task, "i2s_read_task", 4096 * 2, NULL, tskIDLE_PRIORITY, NULL);
}

按鍵47 錄音 播放 接線直接按小智AI

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"static const char *TAG = "inmp441_MAX98357A";#define RECORD_BUTTON_GPIO_NUM 47 // Record/play control button connected to GPIO47// INMP pin configuration
#define INMP_SD     GPIO_NUM_6
#define INMP_SCK    GPIO_NUM_5
#define INMP_WS     GPIO_NUM_4// MAX98357A pin configuration
#define MAX_DIN     GPIO_NUM_7
#define MAX_BCLK    GPIO_NUM_15
#define MAX_LRC     GPIO_NUM_16// Sample rate configuration
#define SAMPLE_RATE 22050 // 降低采樣率以減少內存使用// Calculate buffer size for 2 seconds of audio
#define RECORD_DURATION 2 // Record for 2 seconds
#define BUFFER_SIZE (SAMPLE_RATE * RECORD_DURATION * 2 * (16 / 8)) // 使用16位而不是32位// Task handles
TaskHandle_t record_task_handle = NULL;
TaskHandle_t play_task_handle = NULL;// Audio buffer (allocated on heap)
uint8_t *audio_buffer = NULL;
size_t recorded_bytes = 0; // Track actual recorded bytes// Button event queue
QueueHandle_t button_event_queue = NULL;// Button events
typedef enum {BUTTON_PRESSED,BUTTON_RELEASED
} button_event_t;i2s_chan_handle_t rx_handle;
i2s_chan_handle_t tx_handle;// GPIO interrupt handler
static void IRAM_ATTR gpio_isr_handler(void* arg)
{uint32_t gpio_num = (uint32_t) arg;button_event_t event = gpio_get_level(gpio_num) == 0 ? BUTTON_PRESSED : BUTTON_RELEASED;// Send event to queueBaseType_t xHigherPriorityTaskWoken = pdFALSE;xQueueSendFromISR(button_event_queue, &event, &xHigherPriorityTaskWoken);if (xHigherPriorityTaskWoken) {portYIELD_FROM_ISR();}
}// Initialize I2S receiver (INMP441)
static void i2s_rx_init(void)
{i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);chan_cfg.dma_frame_num = 256; // 減少DMA幀數ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));i2s_std_config_t std_cfg = {.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,.dout = I2S_GPIO_UNUSED,.bclk = INMP_SCK,.ws = INMP_WS,.din = INMP_SD,.invert_flags = {.mclk_inv = false,.bclk_inv = false,.ws_inv = false,},},};ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg));ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
}// Initialize I2S transmitter (MAX98357A)
static void i2s_tx_init(void)
{i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER);chan_cfg.dma_frame_num = 256; // 減少DMA幀數ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));i2s_std_config_t std_cfg = {.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,.din = I2S_GPIO_UNUSED,.bclk = MAX_BCLK,.ws = MAX_LRC,.dout = MAX_DIN,.invert_flags = {.mclk_inv = false,.bclk_inv = false,.ws_inv = false,},},};ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
}// Record task
static void record_task(void *arg)
{ESP_LOGI(TAG, "Record task started");while (1) {// Wait for notification to start recordingulTaskNotifyTake(pdTRUE, portMAX_DELAY);ESP_LOGI(TAG, "Starting recording");size_t bytes_read = 0;recorded_bytes = 0;// Record audio for 2 secondswhile (recorded_bytes < BUFFER_SIZE) {if (i2s_channel_read(rx_handle, audio_buffer + recorded_bytes, BUFFER_SIZE - recorded_bytes, &bytes_read, 1000) == ESP_OK) {recorded_bytes += bytes_read;}vTaskDelay(pdMS_TO_TICKS(1));}ESP_LOGI(TAG, "Recording completed, recorded %u bytes", recorded_bytes);// Notify play task that recording is completexTaskNotifyGive(play_task_handle);}
}// Play task
static void play_task(void *arg)
{ESP_LOGI(TAG, "Play task started");while (1) {// Wait for notification to start playingulTaskNotifyTake(pdTRUE, portMAX_DELAY);if (recorded_bytes == 0) {ESP_LOGI(TAG, "No audio recorded, cannot play");continue;}ESP_LOGI(TAG, "Starting playback");size_t bytes_written = 0;size_t offset = 0;while (offset < recorded_bytes) {if (i2s_channel_write(tx_handle, audio_buffer + offset, recorded_bytes - offset, &bytes_written, 1000) == ESP_OK) {offset += bytes_written;}vTaskDelay(pdMS_TO_TICKS(1));}ESP_LOGI(TAG, "Playback completed");}
}// Button task to handle button events
static void button_task(void *arg)
{button_event_t event;bool recording_active = false;ESP_LOGI(TAG, "Button task started");while (1) {if (xQueueReceive(button_event_queue, &event, portMAX_DELAY)) {// Simple debouncing - ignore events that come too quicklystatic TickType_t last_event_time = 0;TickType_t current_time = xTaskGetTickCount();if (current_time - last_event_time > pdMS_TO_TICKS(50)) {last_event_time = current_time;if (event == BUTTON_PRESSED) {ESP_LOGI(TAG, "Button pressed, starting recording");recording_active = true;// Notify record task to startxTaskNotifyGive(record_task_handle);} else if (event == BUTTON_RELEASED && recording_active) {ESP_LOGI(TAG, "Button released");recording_active = false;// Notify play task to start (it will wait for record task to finish)}}}}
}void app_main(void)
{ESP_LOGI(TAG, "Free heap: %lu bytes", esp_get_free_heap_size());ESP_LOGI(TAG, "Largest free block: %u bytes", heap_caps_get_largest_free_block(MALLOC_CAP_8BIT));// Allocate audio buffer on heap using special function for large allocationsaudio_buffer = (uint8_t*)heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);if (audio_buffer == NULL) {// If SPIRAM is not available, try normal allocationaudio_buffer = (uint8_t*)malloc(BUFFER_SIZE);if (audio_buffer == NULL) {ESP_LOGE(TAG, "Failed to allocate audio buffer of size %d bytes", BUFFER_SIZE);//        ESP_LOGI(TAG, "Available heap: %ld bytes", esp_get_free_heap_size());return;}ESP_LOGI(TAG, "Audio buffer allocated in internal RAM: %d bytes", BUFFER_SIZE);} else {ESP_LOGI(TAG, "Audio buffer allocated in SPIRAM: %d bytes", BUFFER_SIZE);}// Create button event queuebutton_event_queue = xQueueCreate(5, sizeof(button_event_t));// Initialize record/play control button GPIO with interruptgpio_config_t io_conf = {.pin_bit_mask = (1ULL << RECORD_BUTTON_GPIO_NUM),.mode = GPIO_MODE_INPUT,.pull_up_en = GPIO_PULLUP_ENABLE,.pull_down_en = GPIO_PULLDOWN_DISABLE,.intr_type = GPIO_INTR_ANYEDGE, // Interrupt on both rising and falling edges};gpio_config(&io_conf);// Install GPIO ISR servicegpio_install_isr_service(0);// Add ISR handler for GPIOgpio_isr_handler_add(RECORD_BUTTON_GPIO_NUM, gpio_isr_handler, (void*)RECORD_BUTTON_GPIO_NUM);// Initialize I2Si2s_tx_init();i2s_rx_init();ESP_LOGI(TAG, "System initialization completed");ESP_LOGI(TAG, "Press and hold GPIO47 button to record, release to play");// Create tasks with smaller stack sizesxTaskCreate(record_task, "record_task", 3072, NULL, 5, &record_task_handle);xTaskCreate(play_task, "play_task", 3072, NULL, 5, &play_task_handle);xTaskCreate(button_task, "button_task", 2048, NULL, 6, NULL);// Keep main task alivewhile (1) {vTaskDelay(pdMS_TO_TICKS(1000));}
}

4: 如果對你又幫助,麻煩點個贊,加個關注
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/97972.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/97972.shtml
英文地址,請注明出處:http://en.pswp.cn/web/97972.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring Boot 3 + EasyExcel 文件導入導出實現

SpringBoot集成EasyExcel 3.x&#xff1a;高效實現Excel數據的優雅導入與導出 在現代企業應用中&#xff0c;Excel作為數據交換的重要工具&#xff0c;幾乎無處不在。如何高效且優雅地實現Excel數據的導入與導出&#xff0c;是每個開發者都需要面對的問題。EasyExcel是阿里巴巴…

Ruby編程實踐:20個實用練習

1、編寫一個程序,計算一年有多少小時。 以下是兩種實現方式的代碼: 方式一: puts 24*365方式二: puts 24*365 puts "(or #{24*366} on a leap year)"2、編寫一個程序,計算十年中有多少分鐘。 以下兩種實現方式: 簡單計算(未考慮閏年數量差異): ru…

邏輯回歸(二):從原理到實戰 - 訓練、評估與應用指南

引言&#xff1a; 上期我們講了什么是邏輯回歸&#xff0c;了解了它如何利用Sigmoid函數將線性回歸的輸出轉化為概率&#xff0c;并通過最大似然估計來尋找最佳參數。今天&#xff0c;我們將繼續這段旅程&#xff0c;學習如何訓練這個 模型、如何評估它的表現&#xff0c;以及如…

9.8C++作業

思維導圖#include <iostream> #include <vector> #include <fstream> using namespace std;class Stu {friend ofstream &operator<<(ofstream &ofs,const Stu &stu); private:string name;string id;int age;double score; public:Stu(){…

Linux內存管理章節十六:非均勻的內存訪問:深入Linux NUMA架構內存管理

引言 在傳統的SMP&#xff08;對稱多處理&#xff09;系統中&#xff0c;所有CPU核心通過一條共享總線訪問同一塊內存&#xff0c;所有內存訪問延遲是均勻的&#xff08;UMA&#xff09;。然而&#xff0c;隨著CPU核心數量的增加&#xff0c;共享總線成為了巨大的性能和 scalab…

【論文翻譯】Seg-Zero: Reasoning-Chain Guided Segmentation via Cognitive Reinforcement

0. 摘要Traditional methods for reasoning segmentation rely on supervised fine-tuning with categorical labels and simple descriptions, limiting its out-of-domain generalization and lacking explicit reasoning processes. To address these limitations, we propo…

Playwright MCP瀏覽器自動化教程

你是否曾厭倦在編程軟件和瀏覽器之間反復切換&#xff0c;只為了檢查AI生成的代碼能否正常運行&#xff1f;現在&#xff0c;有了Playwright MCP&#xff08;Model Context Protocol&#xff09;&#xff0c;你可以直接讓AI自己操作瀏覽器&#xff0c;查看自己寫的代碼運行效果…

矩陣中遍歷某個點周圍的九個點

又是學習新知識的一天,以下為Java版本部分關鍵代碼int[] neighbors {0, 1, -1};int rows board.length;int cols board[0].length;int[][] copyBoard new int[rows][cols];for (int row 0; row < rows; row) {for (int col 0; col < cols; col) {int liveNeighbors…

單例模式:只有一個對象

目錄 什么是單例模式 能解決什么問題 使用場景 如何實現 __new__ 方法&#xff1a;經典又直接 裝飾器&#xff1a;不改類本身&#xff0c;也能單例 模塊本身就是單例 注意事項 總結 你有沒有過這樣的困擾&#xff1a; “為什么我明明只創建了一次數據庫連接&#xff0…

AI大模型學習(6)Yolo V8神經網絡的基礎應用

Yolo V8神經網絡的基礎應用2024-2025年最火的目標檢測神器&#xff0c;一篇文章讓你徹底搞懂&#xff01;&#x1f929;大家好呀&#xff01;今天我們要聊一聊計算機視覺領域的「明星模型」——YOLO神經網絡&#xff01;&#x1f3af; 如果你對「目標檢測」這個詞還比較陌生&am…

C++:imagehlp庫

imagehlp庫1. 簡介2. 主要函數與用途2.1PE 文件解析相關2.2 符號處理相關2.3 崩潰轉儲相關2.4 版本資源相關3. 使用示例3.1 解析內存地址對應的函數名和行號3.2 創建目錄使用示例1. 簡介 imagehlp 是 Windows 系統提供的一個圖像處理與調試輔助 API 庫&#xff08;Image Helpe…

如何在Anaconda中配置你的CUDA Pytorch cuNN環境(2025最新教程)

目錄 一、簡介 二、下載CUDA 三、下載Pytorch-GPU版本 四、下載CUDNN 五、總結 六、測試代碼 一、簡介 啥是Anaconda?啥是CUDA?啥是CUDNN&#xff1f;它們和Pytorch、GPU之間有啥關系? 怎么通俗解釋它們三者的用途和關系&#xff1f; 1.GPU(圖形處理單元&#xff09…

算法面試(1)-----目標檢測和圖像分類、語義分割的區別

操作系統&#xff1a;ubuntu22.04 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 目標檢測&#xff08;Object Detection&#xff09;、圖像分類&#xff08;Image Classification&#xff09;、語義分割&#xff08;Semantic Segmentation&#xff09; 是計算機視…

電腦散熱風扇有噪音怎么解決

一、初步檢查與清理斷電并拆機關閉電腦并拔掉電源&#xff0c;打開機箱側板&#xff08;筆記本需先拆除后蓋螺絲&#xff09;。操作前建議佩戴防靜電手環&#xff0c;避免靜電損壞硬件。清理風扇及散熱片灰塵使用壓縮空氣罐從風扇進風口吹走灰塵&#xff0c;或用軟毛刷輕輕刷去…

SeaweedFS深度解析(九):k8s環境使用helm部署Seaweedfs集群

上一篇&#xff1a;《SeaweedFS深度解析&#xff08;八&#xff09;&#xff1a;k8s環境使用Operator部署Seaweedfs集群》 鏈接: link #作者&#xff1a;閆乾苓 文章目錄k8s環境使用helm部署Seaweedfs集群準備鏡像seaweed-master-localpv-storageclass.yamlseaweed-volume-lo…

MATLAB繪制一個新穎的混沌圖像(新四翼混沌系統)

新四翼混沌系統:dx/dt a(y - x) yz dy/dt cx - y - xz dz/dt -bz xyMATLAB代碼:function plot_novel_chaotic_system() % 參數設置 a 10; b 8/3; c 28;% 初始條件 x0 [1, 1, 1];% 時間范圍 tspan [0 100];% 求解微分方程 [t, x] ode45((t, x) chaotic_system(t, x, …

金融數據---獲取股票日線數據

獲取股票日線的數據方式有很多&#xff0c;包括東方財富&#xff0c;同花順&#xff0c;tushare&#xff0c;這里我們就利用東方財富的數據&#xff0c;是免費的開源獲取&#xff0c;第一步先安裝akshare&#xff0c;pip安裝就可以py -m pip install akshareAkshare 股票數據獲…

Mac 真正多顯示器支持:TESmart USB-C KVM(搭載 DisplayLink 技術)如何實現

多顯示器已經不再是奢侈品&#xff0c;而是專業人士提升生產力的必需工具。無論是創意設計師、股票交易員還是軟件開發人員&#xff0c;多屏幕都能讓工作流程更高效、更有條理。 然而&#xff0c;Mac 用戶長期以來面臨一個主要障礙&#xff1a;macOS 原生不支持多流傳輸&#x…

【實時Linux實戰系列】靜態鏈接與libc選擇:musl vs glibc的時延權衡

背景與重要性 在實時系統開發中&#xff0c;選擇合適的C標準庫&#xff08;libc&#xff09;和鏈接方式對系統的啟動時間、線程性能和內存分配效率有著顯著影響。glibc和musl是兩種流行的C標準庫實現&#xff0c;它們在設計目標和性能表現上存在差異。通過對比這兩種libc在啟動…

Altium Designer(AD24)的三種文件組織形式,工程文件,自由文件與存盤文件

??《專欄目錄》 目錄 1,概述 2,工程文件 3,自由文件 4,存盤文件 5,文件轉換 5.1,工程文件于自由文件互轉換 5.2,工程文件于存盤文件互轉換 6,注意事項 1,概述 本文介紹Altium Designer 24軟件(后文簡稱AD24或軟件)的三種文件組織形式,工程文件,自由文件和存盤文…