#include "app_config.h" #include "app_log.h" #include "system/includes.h" #include "os/os_api.h" #include "asm/includes.h" #include "server/audio_server.h" #include "generic/printf.h" #include "audio_test.h" #define AUDIO_TEST_BYTES_PER_SEC \ (AUDIO_TEST_SAMPLE_RATE * AUDIO_TEST_RECORD_CHANNEL * 2) #define AUDIO_TEST_RECORD_BUF_SIZE \ (AUDIO_TEST_BYTES_PER_SEC * AUDIO_TEST_RECORD_MS / 1000) #define AUDIO_TEST_FRAME_SIZE \ (AUDIO_TEST_SAMPLE_RATE / 100 * 2 * AUDIO_TEST_RECORD_CHANNEL) #define AUDIO_TEST_TASK_NAME "audio_test" #define AUDIO_TEST_TASK_PRIO 6 #define AUDIO_TEST_TASK_STK 1024 #define AUDIO_TEST_MS_TO_TICKS(ms) ((ms) / (1000 / OS_TICKS_PER_SEC)) struct audio_test_hdl { struct server *enc_server; struct server *dec_server; u8 *record_buf; volatile u32 record_len; /* 已录音字节数(单声道) */ volatile u32 play_pos; /* 已回放字节数(单声道计) */ volatile u8 running; volatile u8 recording; volatile u8 playing; u32 peak; u32 sum_abs; u32 samples; int task_pid; }; static struct audio_test_hdl audio_test_hdl; /* MIC录音数据回调: 拷贝进内存缓冲并统计峰值/均值 */ static int audio_test_rec_fwrite(void *file, void *data, u32 len) { struct audio_test_hdl *p = (struct audio_test_hdl *)file; u32 remain, copy_len, i, n; s16 *s; u32 a; if (!p->recording || !p->record_buf) { return len; } remain = AUDIO_TEST_RECORD_BUF_SIZE - p->record_len; if (!remain) { return len; } copy_len = (len > remain) ? remain : len; memcpy(p->record_buf + p->record_len, data, copy_len); p->record_len += copy_len; s = (s16 *)data; n = copy_len / 2; for (i = 0; i < n; i++) { a = (s[i] < 0) ? (u32)(-s[i]) : (u32)s[i]; p->sum_abs += a; if (a > p->peak) { p->peak = a; } } p->samples += n; return len; } /* DAC回放数据回调: 单声道->双声道 */ static int audio_test_play_fread(void *file, void *data, u32 len) { struct audio_test_hdl *p = (struct audio_test_hdl *)file; u32 mono_remain, stereo_remain, frames, i; s16 *src, *dst; if (!p->playing || !p->record_buf || p->play_pos >= p->record_len) { return 0; } mono_remain = p->record_len - p->play_pos; stereo_remain = mono_remain * 2; if (len > stereo_remain) { len = stereo_remain; } frames = len / (2 * 2); if (!frames) { return 0; } src = (s16 *)(p->record_buf + p->play_pos); dst = (s16 *)data; for (i = 0; i < frames; i++) { s16 v = src[i] * AUDIO_TEST_PLAYBACK_GAIN; dst[i * 2] = v; dst[i * 2 + 1] = v; } p->play_pos += frames * 2; return frames * 2 * 2; } static int audio_test_play_flen(void *file) { struct audio_test_hdl *p = (struct audio_test_hdl *)file; return p->record_len * 2; } static int audio_test_vfs_fclose(void *file) { return 0; } static const struct audio_vfs_ops audio_test_rec_vfs = { .fwrite = audio_test_rec_fwrite, .fclose = audio_test_vfs_fclose, }; static const struct audio_vfs_ops audio_test_play_vfs = { .fread = audio_test_play_fread, .fclose = audio_test_vfs_fclose, .flen = audio_test_play_flen, }; static int audio_test_record_start(void) { struct audio_test_hdl *p = &audio_test_hdl; union audio_req req = {0}; if (!p->record_buf) { p->record_buf = malloc(AUDIO_TEST_RECORD_BUF_SIZE); if (!p->record_buf) { APP_LOG("[AUDIO_TEST] malloc record buf fail: %d\n", AUDIO_TEST_RECORD_BUF_SIZE); return -1; } } p->record_len = 0; p->peak = 0; p->sum_abs = 0; p->samples = 0; p->enc_server = server_open("audio_server", "enc"); if (!p->enc_server) { APP_LOG("[AUDIO_TEST] enc server open fail\n"); return -1; } p->recording = 1; req.enc.cmd = AUDIO_ENC_OPEN; req.enc.channel = AUDIO_TEST_RECORD_CHANNEL; req.enc.volume = AUDIO_TEST_ADC_GAIN; req.enc.output_buf = NULL; req.enc.output_buf_len = AUDIO_TEST_DMA_BUF_SIZE; req.enc.sample_rate = AUDIO_TEST_SAMPLE_RATE; req.enc.format = "pcm"; req.enc.frame_size = AUDIO_TEST_FRAME_SIZE; req.enc.sample_source = "mic"; req.enc.channel_bit_map = BIT(AUDIO_TEST_ADC_CHANNEL_L); req.enc.vfs_ops = &audio_test_rec_vfs; req.enc.file = (FILE *)p; req.enc.msec = 0; if (server_request(p->enc_server, AUDIO_REQ_ENC, &req)) { APP_LOG("[AUDIO_TEST] enc open fail\n"); goto __err; } req.enc.cmd = AUDIO_ENC_START; if (server_request(p->enc_server, AUDIO_REQ_ENC, &req)) { APP_LOG("[AUDIO_TEST] enc start fail\n"); goto __err; } return 0; __err: p->recording = 0; server_close(p->enc_server); p->enc_server = NULL; return -1; } static void audio_test_record_stop(void) { struct audio_test_hdl *p = &audio_test_hdl; union audio_req req = {0}; if (p->enc_server) { req.enc.cmd = AUDIO_ENC_STOP; server_request(p->enc_server, AUDIO_REQ_ENC, &req); server_close(p->enc_server); p->enc_server = NULL; } p->recording = 0; } static int audio_test_play_start(void) { struct audio_test_hdl *p = &audio_test_hdl; union audio_req req = {0}; p->play_pos = 0; p->dec_server = server_open("audio_server", "dec"); if (!p->dec_server) { APP_LOG("[AUDIO_TEST] dec server open fail\n"); return -1; } p->playing = 1; req.dec.cmd = AUDIO_DEC_OPEN; req.dec.channel = AUDIO_TEST_PLAYBACK_CHANNEL; req.dec.volume = AUDIO_TEST_DAC_VOLUME; req.dec.output_buf = NULL; req.dec.output_buf_len = AUDIO_TEST_DMA_BUF_SIZE; req.dec.sample_rate = AUDIO_TEST_SAMPLE_RATE; req.dec.dec_type = "pcm"; req.dec.sample_source = "dac"; req.dec.vfs_ops = &audio_test_play_vfs; req.dec.file = (FILE *)p; req.dec.priority = 1; if (server_request(p->dec_server, AUDIO_REQ_DEC, &req)) { APP_LOG("[AUDIO_TEST] dec open fail\n"); goto __err; } req.dec.cmd = AUDIO_DEC_START; if (server_request(p->dec_server, AUDIO_REQ_DEC, &req)) { APP_LOG("[AUDIO_TEST] dec start fail\n"); goto __err; } return 0; __err: p->playing = 0; server_close(p->dec_server); p->dec_server = NULL; return -1; } static void audio_test_play_stop(void) { struct audio_test_hdl *p = &audio_test_hdl; union audio_req req = {0}; if (p->dec_server) { req.dec.cmd = AUDIO_DEC_STOP; server_request(p->dec_server, AUDIO_REQ_DEC, &req); server_close(p->dec_server); p->dec_server = NULL; } p->playing = 0; } /* 显式驱动功放SD脚 */ static void audio_test_amp_set(u8 enable) { u8 level = enable ? AUDIO_TEST_AMP_ENABLE_LEVEL : !AUDIO_TEST_AMP_ENABLE_LEVEL; gpio_direction_output(AUDIO_TEST_AMP_PORT, level); APP_LOG("[AUDIO_TEST] amp SD=PA0 -> %s\n", level ? "HIGH" : "LOW"); } static void audio_test_task(void *priv) { struct audio_test_hdl *p = (struct audio_test_hdl *)priv; u32 timeout; APP_LOG("[AUDIO_TEST] ===== audio test start =====\n"); APP_LOG("[AUDIO_TEST] sample=%dHz record=%dms adc_gain=%d dac_vol=%d ch=%d\n", AUDIO_TEST_SAMPLE_RATE, AUDIO_TEST_RECORD_MS, AUDIO_TEST_ADC_GAIN, AUDIO_TEST_DAC_VOLUME, AUDIO_TEST_ADC_CHANNEL_L); audio_test_amp_set(0); /* 测试开始先静音功放, 防止上电杂音 */ /* 1. MIC 录音 */ if (audio_test_record_start()) { goto __exit; } APP_LOG("[AUDIO_TEST] recording, please speak to MIC...\n"); timeout = AUDIO_TEST_MS_TO_TICKS(AUDIO_TEST_RECORD_MS); while (timeout--) { os_time_dly(1); if (p->record_len >= AUDIO_TEST_RECORD_BUF_SIZE) { break; } } audio_test_record_stop(); APP_LOG("[AUDIO_TEST] record done: %d bytes, peak=%d, avg=%d\n", p->record_len, p->peak, p->samples ? (u32)(p->sum_abs / p->samples) : 0); if (p->peak >= AUDIO_TEST_PEAK_PASS_THRESHOLD) { APP_LOG("[AUDIO_TEST] MIC result: PASS (peak=%d)\n", p->peak); } else { APP_LOG("[AUDIO_TEST] MIC result: WEAK/FAIL (peak=%d < %d), check mic circuit\n", p->peak, AUDIO_TEST_PEAK_PASS_THRESHOLD); } /* 2. 回放录音到功放 */ if (p->record_len < AUDIO_TEST_FRAME_SIZE * 2) { APP_LOG("[AUDIO_TEST] record too short, skip playback\n"); goto __exit; } if (audio_test_play_start()) { goto __exit; } audio_test_amp_set(1); /* 使能功放 */ APP_LOG("[AUDIO_TEST] playing, listen speaker...\n"); timeout = AUDIO_TEST_MS_TO_TICKS(AUDIO_TEST_RECORD_MS) + AUDIO_TEST_MS_TO_TICKS(2000); while (timeout--) { os_time_dly(1); if (p->play_pos >= p->record_len) { break; } } audio_test_play_stop(); audio_test_amp_set(0); /* 回放结束静音功放 */ APP_LOG("[AUDIO_TEST] playback done: %d bytes\n", p->play_pos); APP_LOG("[AUDIO_TEST] ===== audio test finish =====\n"); __exit: p->running = 0; p->task_pid = 0; } int audio_test_start(void) { struct audio_test_hdl *p = &audio_test_hdl; if (p->running || p->task_pid) { APP_LOG("[AUDIO_TEST] already running\n"); return -1; } p->running = 1; if (thread_fork(AUDIO_TEST_TASK_NAME, AUDIO_TEST_TASK_PRIO, AUDIO_TEST_TASK_STK, 0, &p->task_pid, audio_test_task, p) != OS_NO_ERR) { p->running = 0; p->task_pid = 0; APP_LOG("[AUDIO_TEST] task fork fail\n"); return -1; } return 0; } int audio_test_stop(void) { struct audio_test_hdl *p = &audio_test_hdl; audio_test_play_stop(); audio_test_record_stop(); if (p->task_pid) { thread_kill(&p->task_pid, KILL_WAIT); p->task_pid = 0; } p->running = 0; return 0; }