修改雷鸟音频测试改为音量检测
authorpenglei <1624952103@qq.com>
Tue, 2 Dec 2025 12:17:44 +0000 (20:17 +0800)
committerpenglei <1624952103@qq.com>
Tue, 2 Dec 2025 12:17:44 +0000 (20:17 +0800)
code/sdk/middleware/services/media/foundation/audiomanager/frameworks/audioservice/src/devicemanager/src/device_hal.c
code/sdk/zhihui/drivers/charge/hp4570/hp4570.c
code/sdk/zhihui/services/ble_service/bleservice/bleService.c
code/sdk/zhihui/utils/custom_utils_at_cmd.c
code/sdk/zhihui/utilscpp/CMakeLists.txt
code/sdk/zhihui/utilscpp/audio_test.cpp [new file with mode: 0644]
code/sdk/zhihui/utilscpp/include/pcm_1khz_period.h [new file with mode: 0644]
code/sdk/zhihui/utilscpp/include/pcm_volume_calculator.h [new file with mode: 0644]
code/sdk/zhihui/utilscpp/pcm_volume_calculator.cpp [new file with mode: 0644]

index 577ca80..99b249c 100644 (file)
@@ -451,7 +451,13 @@ int32_t OpenInputStream(uint32_t device, AudioStreamConfig attr, Handle *inputSt
         return AUDIO_ERROR;\r
     }\r
     struct AudioSampleAttributes param;\r
-    param.type = (enum AudioCategory)StreamTypeFromFrameworkToHal(attr.type);\r
+#ifdef CUSTOM_UTILS\r
+    extern uint8_t custom_get_factory_test_flag(void);\r
+    if(custom_get_factory_test_flag())\r
+        param.type = AUDIO_IN_MEDIA;\r
+    else\r
+#endif\r
+        param.type = (enum AudioCategory)StreamTypeFromFrameworkToHal(attr.type);\r
     param.sampleRate = attr.sampleRate;\r
     param.format = FormatFromFrameworkToHal(attr.format, attr.sampleFmt);\r
     param.channelCount = attr.channelCount;\r
index 0be191a..62f87e5 100644 (file)
@@ -122,6 +122,101 @@ bool hp4570_init(uint8_t chip_ver){
   ret = hp4570_set_iindpm_mA(1000);\r
   printf("hp4570_set_iindpm_mA(1000) ret:%d\n",ret);\r
 \r
+#if 0\r
+  uint8_t value = 0;\r
+  hp4570_read_reg(HP4570_REG_DPM_SET, &value);\r
+  printf("HP4570_REG_DPM_SET : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_SYS_CTRL1, &value);\r
+  printf("HP4570_REG_SYS_CTRL1 : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_SYS_CTRL0, &value);\r
+  printf("HP4570_REG_SYS_CTRL0 : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_IDSG_ITERM_SET, &value);\r
+  printf("HP4570_REG_IDSG_ITERM_SET : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_BAT_VOL_SET, &value);\r
+  printf("HP4570_REG_BAT_VOL_SET : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_SYS_CTRL3, &value);\r
+  printf("HP4570_REG_SYS_CTRL3 : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_SYS_CTRL4, &value);\r
+  printf("HP4570_REG_SYS_CTRL4 : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_CHIP_STAT0, &value);\r
+  printf("HP4570_REG_CHIP_STAT0 : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_CHIP_STAT1, &value);\r
+  printf("HP4570_REG_CHIP_STAT1 : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_CHIP_STAT2, &value);\r
+  printf("HP4570_REG_CHIP_STAT2 : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_VIPRE, &value);\r
+  printf("HP4570_REG_VIPRE : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_EXT_CTRL, &value);\r
+  printf("HP4570_REG_EXT_CTRL : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_SYS_CTRL5, &value);\r
+  printf("HP4570_REG_SYS_CTRL5 : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_SYS_CTRL6, &value);\r
+  printf("HP4570_REG_SYS_CTRL6 : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_SYS_CTRL7, &value);\r
+  printf("HP4570_REG_SYS_CTRL7 : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_SYS_WAKE_UP_SRC, &value);\r
+  printf("HP4570_REG_SYS_WAKE_UP_SRC : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_INT0_ENABLE, &value);\r
+  printf("HP4570_REG_INT0_ENABLE : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_INT0_STAT, &value);\r
+  printf("HP4570_REG_INT0_STAT : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_VID_LSB, &value);\r
+  printf("HP4570_REG_VID_LSB : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_VID_MSB, &value);\r
+  printf("HP4570_REG_VID_MSB : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_PID_LSB, &value);\r
+  printf("HP4570_REG_PID_LSB : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_PID_MSB, &value);\r
+  printf("HP4570_REG_PID_MSB : 0x%x\n",value);\r
+\r
+  value = 0;\r
+  hp4570_read_reg(HP4570_REG_CHIP_VER, &value);\r
+  printf("HP4570_REG_CHIP_VER : 0x%x\n",value);\r
+\r
+#endif\r
+\r
   // if( hp4570_set_chrg_vbat_mV(4012.5) &&\r
       // hp4570_set_chrg_ipre_mA(34) &&\r
       // hp4570_set_chrg_ichg_mA(200) &&\r
index 20a02bd..d65da75 100644 (file)
@@ -31,7 +31,9 @@ extern "C" {
 #endif\r
 \r
 #include "bleService.h"\r
+#ifdef SUPPORT_ALIPAY_SEC\r
 #include "alipay_feature.h"\r
+#endif\r
 #ifdef AILPAY_SUPPORT\r
 // #include "alipay_bluetooth.h"\r
 #endif\r
index 60b8bf5..02dce28 100644 (file)
@@ -1718,6 +1718,7 @@ static uint32_t custom_factory_bt_test_stop(uint8_t *para, uint32_t paraLen, cha
     return ERRCODE_SUCC;
 }
 
+
 // 录音相关AT指令实现
 static uint32_t custom_audio_test(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
 {
@@ -1731,15 +1732,28 @@ static uint32_t custom_audio_test(uint8_t *para, uint32_t paraLen, char *strComm
         return ERRCODE_FAIL;
     }
 
-    if (argc != 1) {
-        wstp_print("AUDIO TEST ATCMD Set Parameter Failed argc %d\n", argc);
-        wstp_print("Usage: AT^AUDIOTEST=cmd\n");
-        wstp_print("  cmd: 1=start record, 2=stop record, 3=start play, 4=stop play\n");
-        return ERRCODE_FAIL;
-    }
-
     uint8_t cmd = (uint8_t)atoi((char*)argv[0]);
     
+    // 音量设置命令需要2个参数
+    if (cmd == 7) {
+        if (argc != 2) {
+            wstp_print("AUDIO TEST ATCMD Set Parameter Failed argc %d\n", argc);
+            wstp_print("Usage: AT^AUDIOTEST=7,volume\n");
+            wstp_print("  volume: 0.0-2.0 (1.0 = 100%%, 0.5 = 50%%, 2.0 = 200%%)\n");
+            return ERRCODE_FAIL;
+        }
+    } else {
+        if (argc != 1) {
+            wstp_print("AUDIO TEST ATCMD Set Parameter Failed argc %d\n", argc);
+            wstp_print("Usage: AT^AUDIOTEST=cmd\n");
+            wstp_print("  cmd: 1=start record, 2=stop record, 3=start play, 4=stop play\n");
+            wstp_print("       5=start audio test (play 1kHz + monitor volume), 6=stop audio test\n");
+            wstp_print("       7=set play volume (requires volume parameter: 0.0-2.0)\n");
+            wstp_print("       8=get play volume\n");
+            return ERRCODE_FAIL;
+        }
+    }
+    
     switch (cmd) {
         case 1: // 开始录音
             {
@@ -1784,10 +1798,55 @@ static uint32_t custom_audio_test(uint8_t *para, uint32_t paraLen, char *strComm
                 }
             }
             break;
+        case 5: // 启动音频测试(播放1kHz + 监控音量)
+            {
+                extern int custom_audio_test_start(void);
+                int ret = custom_audio_test_start();
+                if (ret == 0) {
+                    wstp_print("AUDIO TEST START: Success\n");
+                    wstp_print("  Playing 1kHz tone, monitoring volume every 0.5s...\n");
+                } else {
+                    wstp_print("AUDIO TEST START: Failed, ret=%d\n", ret);
+                }
+            }
+            break;
+        case 6: // 停止音频测试
+            {
+                extern int custom_audio_test_stop(void);
+                int ret = custom_audio_test_stop();
+                if (ret == 0) {
+                    wstp_print("AUDIO TEST STOP: Success\n");
+                } else {
+                    wstp_print("AUDIO TEST STOP: Failed, ret=%d\n", ret);
+                }
+            }
+            break;
+        case 7: // 设置播放音量
+            {
+                float volume = (float)atof((char*)argv[1]);
+                extern int custom_audio_test_set_volume(float);
+                int ret = custom_audio_test_set_volume(volume);
+                if (ret == 0) {
+                    wstp_print("AUDIO TEST SET VOLUME: Success (%.2f, %.0f%%)\n", volume, volume * 100.0f);
+                } else {
+                    wstp_print("AUDIO TEST SET VOLUME: Failed, ret=%d\n", ret);
+                }
+            }
+            break;
+        case 8: // 获取播放音量
+            {
+                extern float custom_audio_test_get_volume(void);
+                float volume = custom_audio_test_get_volume();
+                wstp_print("AUDIO TEST GET VOLUME: %.2f (%.0f%%)\n", volume, volume * 100.0f);
+            }
+            break;
         default:
             wstp_print("AUDIO TEST ATCMD Unknown command %d\n", cmd);
             wstp_print("Usage: AT^AUDIOTEST=cmd\n");
             wstp_print("  cmd: 1=start record, 2=stop record, 3=start play, 4=stop play\n");
+            wstp_print("       5=start audio test (play 1kHz + monitor volume), 6=stop audio test\n");
+            wstp_print("       7=set play volume (requires volume parameter: 0.0-2.0)\n");
+            wstp_print("       8=get play volume\n");
             return ERRCODE_FAIL;
     }
 
index 78ef612..9099957 100644 (file)
@@ -11,10 +11,13 @@ set(SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/spi_protocol.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/spi_protocol_api.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/spi_protocol_test.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/audio_test.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/pcm_volume_calculator.cpp
 )
 
 set(PUBLIC_HEADER
     ${CMAKE_CURRENT_SOURCE_DIR}
+    ${CMAKE_CURRENT_SOURCE_DIR}/include
 )
 
 set(PRIVATE_HEADER
diff --git a/code/sdk/zhihui/utilscpp/audio_test.cpp b/code/sdk/zhihui/utilscpp/audio_test.cpp
new file mode 100644 (file)
index 0000000..322e22b
--- /dev/null
@@ -0,0 +1,525 @@
+/**
+ * @file audio_test.cpp
+ * @brief 音频测试程序 - 播放1kHz PCM信号并实时计算录音音量
+ * 
+ * 功能:
+ * 1. 启动AI语音服务(使用old_Start_AIVoice)
+ * 2. 在独立线程中循环播放1kHz PCM信号(使用AI_WriteAudioData)
+ * 3. 通过录音回调收集数据,每0.5秒计算并打印一次音量
+ * 4. 支持开始/停止测试指令(使用StopAIVoice)
+ */
+
+#include "custom_utilscpp.h"
+#include "debug_print.h"
+#include "securec.h"
+#include "media_thread_adapt.h"
+#include "cmsis_os2.h"
+#include "los_base.h"
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <unistd.h>
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// 包含AI媒体服务
+#include "../../ai/include/ai_media.h"
+
+// 包含PCM数据和音量计算函数
+#include "pcm_1khz_period.h"
+#include "pcm_volume_calculator.h"
+
+// 测试状态
+typedef struct {
+    bool is_running;                    // 测试是否正在运行
+    MediaThreadIdHandle play_thread;    // 播放线程句柄
+    MediaMutexHandle mutex;             // 互斥锁
+    
+    // 播放音量调节(0.0-2.0,1.0为原始音量)
+    float play_volume;                  // 播放音量增益
+    MediaMutexHandle volume_mutex;      // 音量互斥锁
+    
+    // 录音数据缓冲区(用于音量计算)
+    int16_t* record_buffer;             // 录音数据缓冲区
+    uint32_t buffer_size;               // 缓冲区大小
+    uint32_t buffer_write_pos;          // 写入位置
+    MediaMutexHandle buffer_mutex;      // 缓冲区互斥锁
+    
+    // 音量计算相关
+    uint32_t last_volume_check_time;    // 上次音量检查时间(毫秒)
+    uint32_t volume_check_interval_ms;  // 音量检查间隔(500ms)
+} AudioTestContext;
+
+static AudioTestContext g_audio_test_ctx = {0};
+
+// 前向声明
+static void* AudioPlayThread(void* arg);
+static void AudioTestDataCallback(const uint8_t* data, uint32_t size, void* userData);
+static uint32_t GetCurrentTimeMs(void);
+
+/**
+ * @brief 获取当前时间(毫秒)
+ */
+static uint32_t GetCurrentTimeMs(void)
+{
+    // 使用CMSIS OS2的tick计数
+    uint32_t tick_count = osKernelGetTickCount();
+    uint32_t tick_freq = osKernelGetTickFreq();
+    return (tick_count * 1000) / tick_freq;
+}
+
+/**
+ * @brief AI语音数据回调函数 - 收集录音数据用于音量计算
+ * @param data 音频数据
+ * @param size 数据大小
+ * @param userData 用户数据
+ */
+static void AudioTestDataCallback(const uint8_t* data, uint32_t size, void* userData)
+{
+    (void)userData;
+    
+    if (data == nullptr || size == 0 || !g_audio_test_ctx.is_running) {
+        return;
+    }
+    
+    // 将uint8_t数据转换为int16_t PCM数据
+    uint32_t sample_count = size / 2;  // 16位PCM,每样本2字节
+    
+    MediaMutexLock(g_audio_test_ctx.buffer_mutex);
+    
+    // 确保缓冲区足够大
+    if (g_audio_test_ctx.buffer_size < sample_count * 2) {
+        // 重新分配缓冲区(保留2倍空间)
+        if (g_audio_test_ctx.record_buffer != nullptr) {
+            free(g_audio_test_ctx.record_buffer);
+        }
+        g_audio_test_ctx.buffer_size = sample_count * 2;
+        g_audio_test_ctx.record_buffer = (int16_t*)malloc(g_audio_test_ctx.buffer_size * sizeof(int16_t));
+        if (g_audio_test_ctx.record_buffer == nullptr) {
+            PRINT("[AudioTest] Failed to allocate record buffer\r\n");
+            MediaMutexUnLock(g_audio_test_ctx.buffer_mutex);
+            return;
+        }
+        g_audio_test_ctx.buffer_write_pos = 0;
+    }
+    
+    // 复制数据到缓冲区(循环缓冲区)
+    const int16_t* pcm_data = (const int16_t*)data;
+    for (uint32_t i = 0; i < sample_count; i++) {
+        g_audio_test_ctx.record_buffer[g_audio_test_ctx.buffer_write_pos] = pcm_data[i];
+        g_audio_test_ctx.buffer_write_pos = (g_audio_test_ctx.buffer_write_pos + 1) % g_audio_test_ctx.buffer_size;
+    }
+    
+    // 检查是否需要计算音量(每0.5秒)
+    uint32_t current_time = GetCurrentTimeMs();
+    bool should_calculate = (current_time - g_audio_test_ctx.last_volume_check_time >= g_audio_test_ctx.volume_check_interval_ms);
+    
+    if (should_calculate) {
+        g_audio_test_ctx.last_volume_check_time = current_time;
+        
+        // 计算音量(使用最近0.5秒的数据,约8000样本@16kHz)
+        const uint32_t samples_for_volume = 8000;  // 0.5秒数据@16kHz
+        uint32_t samples_to_use = (g_audio_test_ctx.buffer_write_pos > samples_for_volume) ? 
+                                   samples_for_volume : g_audio_test_ctx.buffer_write_pos;
+        
+        if (samples_to_use > 0) {
+            // 分配临时缓冲区用于计算(简化循环缓冲区处理)
+            int16_t* temp_buffer = (int16_t*)malloc(samples_to_use * sizeof(int16_t));
+            if (temp_buffer != nullptr) {
+                // 从循环缓冲区复制最近的数据
+                if (g_audio_test_ctx.buffer_write_pos >= samples_to_use) {
+                    // 直接从buffer_write_pos往前取
+                    memcpy(temp_buffer, 
+                           g_audio_test_ctx.record_buffer + (g_audio_test_ctx.buffer_write_pos - samples_to_use),
+                           samples_to_use * sizeof(int16_t));
+                } else {
+                    // 需要从缓冲区末尾开始取(循环缓冲区)
+                    uint32_t part1 = g_audio_test_ctx.buffer_write_pos;
+                    uint32_t part2 = samples_to_use - part1;
+                    memcpy(temp_buffer, 
+                           g_audio_test_ctx.record_buffer + (g_audio_test_ctx.buffer_size - part2),
+                           part2 * sizeof(int16_t));
+                    memcpy(temp_buffer + part2,
+                           g_audio_test_ctx.record_buffer,
+                           part1 * sizeof(int16_t));
+                }
+                
+                pcm_volume_result_t result;
+                if (pcm_calculate_volume(temp_buffer, samples_to_use, &result) == 0) {
+                    PRINT("[AudioTest] Volume - RMS: %.2f%% (%.2f dB), Peak: %.2f%% (%.2f dB), Level: %s\r\n",
+                          result.rms * 100.0, result.rms_db,
+                          result.peak * 100.0, result.peak_db,
+                          pcm_get_volume_level(result.rms_db));
+                    printf("fac:mic level:%s\n",pcm_get_volume_level(result.rms_db));
+                }
+                
+                free(temp_buffer);
+            }
+        }
+    }
+    MediaMutexUnLock(g_audio_test_ctx.buffer_mutex);
+}
+
+/**
+ * @brief 播放线程 - 循环播放1kHz PCM信号
+ */
+static void* AudioPlayThread(void* arg)
+{
+    (void)arg;
+    
+    PRINT("[AudioTest] Play thread started\r\n");
+    
+    // 16kHz采样率,16样本 = 1ms,所以每1ms发送一个周期
+    // 为了最大程度减少卡顿,采用最小的数据包和最高的发送频率
+    // 每次发送5个周期(5ms数据,160字节),usleep(40*160) = 6.4ms,非常接近实时
+    const uint32_t periods_per_send = 5;   // 每次发送5个周期(5ms数据,160字节)
+    
+    // 预先准备数据缓冲区(避免在循环中重复分配)
+    uint32_t buffer_size = PCM_1KHZ_DATA_BYTES * periods_per_send;
+    uint8_t* pcm_buffer = (uint8_t*)malloc(buffer_size);
+    if (pcm_buffer == nullptr) {
+        PRINT("[AudioTest] Failed to allocate play buffer\r\n");
+        return nullptr;
+    }
+    
+    int16_t* pcm_samples = (int16_t*)pcm_buffer;
+    
+    // 预先填充原始数据缓冲区(不应用音量)
+    int16_t* raw_pcm_samples = (int16_t*)malloc(buffer_size);
+    if (raw_pcm_samples == nullptr) {
+        PRINT("[AudioTest] Failed to allocate raw buffer\r\n");
+        free(pcm_buffer);
+        return nullptr;
+    }
+    
+    for (uint32_t period = 0; period < periods_per_send; period++) {
+        for (uint32_t i = 0; i < PCM_1KHZ_DATA_SIZE; i++) {
+            raw_pcm_samples[period * PCM_1KHZ_DATA_SIZE + i] = pcm_1khz_data[i];
+        }
+    }
+    
+    PRINT("[AudioTest] Pre-filled %u periods (%u bytes, %.1f ms) of PCM data\r\n", 
+          periods_per_send, buffer_size, (float)periods_per_send * PCM_1KHZ_DATA_PERIOD_DURATION_MS);
+    
+    // 计算发送间隔:5ms数据,但AI_WriteAudioData内部会sleep(40*size) = 6.4ms
+    // 所以实际间隔约为6.4ms,非常接近实时播放速度
+    // 采用连续发送策略,不等待,让AI_WriteAudioData内部的sleep自然控制节奏
+    
+    // 获取初始音量并应用(确保pcm_buffer被正确初始化)
+    MediaMutexLock(g_audio_test_ctx.volume_mutex);
+    float initial_volume = g_audio_test_ctx.play_volume;
+    printf("initial_volume : %.3f \n",initial_volume);
+    printf("g_audio_test_ctx.play_volume : %.3f \n",g_audio_test_ctx.play_volume);
+    MediaMutexUnLock(g_audio_test_ctx.volume_mutex);
+    
+    // 初始化pcm_buffer:应用音量增益
+    uint32_t sample_count = buffer_size / sizeof(int16_t);
+    for (uint32_t i = 0; i < sample_count; i++) {
+        int32_t sample = (int32_t)raw_pcm_samples[i];
+        printf("initial_volume : %.3f \n",initial_volume);
+        sample = (int32_t)(sample * initial_volume);
+        // 限制在int16_t范围内,避免削波
+        if (sample > 32767) sample = 32767;
+        if (sample < -32768) sample = -32768;
+        pcm_samples[i] = (int16_t)sample;
+    }
+    
+    float last_volume = initial_volume;  // 记录上次音量,避免重复计算
+    
+    while (true) {
+        // 检查是否需要停止(在循环开始就检查,避免不必要的处理)
+        MediaMutexLock(g_audio_test_ctx.mutex);
+        bool should_stop = !g_audio_test_ctx.is_running;
+        MediaMutexUnLock(g_audio_test_ctx.mutex);
+        
+        if (should_stop) {
+            break;
+        }
+        
+        // 获取当前音量设置(只在音量改变时重新计算)
+        MediaMutexLock(g_audio_test_ctx.volume_mutex);
+        float volume = g_audio_test_ctx.play_volume;
+        MediaMutexUnLock(g_audio_test_ctx.volume_mutex);
+        
+        // 如果音量改变,重新应用音量增益到数据
+        if (volume != last_volume) {
+            for (uint32_t i = 0; i < sample_count; i++) {
+                int32_t sample = (int32_t)raw_pcm_samples[i];
+                sample = (int32_t)(sample * volume);
+                // 限制在int16_t范围内,避免削波
+                if (sample > 32767) sample = 32767;
+                if (sample < -32768) sample = -32768;
+                pcm_samples[i] = (int16_t)sample;
+            }
+            last_volume = volume;
+        }
+        
+        // 连续发送数据,保持缓冲区充足
+        // 每次循环发送40批,总共200ms的数据
+        // 由于AI_WriteAudioData内部有usleep(40*size),每批会等待约6.4ms
+        // 40批总共约256ms,这样可以保持缓冲区有足够数据
+        // 不额外等待,让AI_WriteAudioData内部的sleep自然控制节奏
+        for (int batch = 0; batch < 40; batch++) {
+            // 检查停止标志(每批检查一次,保持响应性)
+            MediaMutexLock(g_audio_test_ctx.mutex);
+            bool should_stop_now = !g_audio_test_ctx.is_running;
+            MediaMutexUnLock(g_audio_test_ctx.mutex);
+            
+            if (should_stop_now) {
+                goto exit_loop;
+            }
+            
+            // 使用AI_WriteAudioData发送到播放服务
+            // 注意:AI_WriteAudioData内部有usleep(40*size),5个周期(160字节)会sleep约6.4ms
+            // 这个sleep会自然控制播放节奏,我们不需要额外等待
+            int32_t ret = AI_WriteAudioData(pcm_buffer, buffer_size);
+            if (ret != 0) {
+                // 如果发送失败,短暂等待后继续
+                usleep(500);
+            }
+        }
+    }
+    
+exit_loop:
+    // 释放缓冲区
+    free(pcm_buffer);
+    free(raw_pcm_samples);
+    
+    PRINT("[AudioTest] Play thread stopped\r\n");
+    return nullptr;
+}
+
+/**
+ * @brief 启动音频测试
+ * @return 0 成功, -1 失败
+ */
+int custom_audio_test_start(void)
+{
+    PRINT("[AudioTest] Starting audio test...\r\n");
+    
+    // 检查是否已经在运行
+    MediaMutexLock(g_audio_test_ctx.mutex);
+    if (g_audio_test_ctx.is_running) {
+        PRINT("[AudioTest] Test already running\r\n");
+        MediaMutexUnLock(g_audio_test_ctx.mutex);
+        return -1;
+    }
+    
+    // 初始化互斥锁
+    if (g_audio_test_ctx.mutex == nullptr) {
+        g_audio_test_ctx.mutex = MediaMutexCreate(nullptr);
+        if (g_audio_test_ctx.mutex == nullptr) {
+            PRINT("[AudioTest] Failed to create mutex\r\n");
+            return -1;
+        }
+    }
+    
+    if (g_audio_test_ctx.buffer_mutex == nullptr) {
+        g_audio_test_ctx.buffer_mutex = MediaMutexCreate(nullptr);
+        if (g_audio_test_ctx.buffer_mutex == nullptr) {
+            PRINT("[AudioTest] Failed to create buffer mutex\r\n");
+            MediaMutexUnLock(g_audio_test_ctx.mutex);
+            return -1;
+        }
+    }
+    
+    if (g_audio_test_ctx.volume_mutex == nullptr) {
+        g_audio_test_ctx.volume_mutex = MediaMutexCreate(nullptr);
+        if (g_audio_test_ctx.volume_mutex == nullptr) {
+            PRINT("[AudioTest] Failed to create volume mutex\r\n");
+            MediaMutexUnLock(g_audio_test_ctx.mutex);
+            return -1;
+        }
+    }
+    
+    // 初始化播放音量(默认1.0,即原始音量)
+    g_audio_test_ctx.play_volume = 1.0f;
+    
+    // 初始化AI媒体服务
+    int32_t ret = ai_media_service_init();
+    if (ret != 0) {
+        PRINT("[AudioTest] Failed to init AI media service: %d\r\n", ret);
+        MediaMutexUnLock(g_audio_test_ctx.mutex);
+        return -1;
+    }
+    
+    // 设置输入和输出音频参数(PCM格式,16kHz,单声道)
+    // PCM = 1 (根据audio_base_type.h定义)
+    AIVoiceAudioParams inputParams = {
+        .audioFormat = 1,  // PCM格式
+        .channelCount = 1,
+        .sampleRate = 16000
+    };
+    
+    AIVoiceAudioParams outputParams = {
+        .audioFormat = 1,  // PCM格式
+        .channelCount = 1,
+        .sampleRate = 16000
+    };
+    
+    // 启动AI语音服务(使用old_Start_AIVoice)
+    // 不使用OGG封装
+    ret = old_Start_AIVoice(AudioTestDataCallback, nullptr, &inputParams, &outputParams, nullptr,false);
+    if (ret != 0) {
+        PRINT("[AudioTest] Failed to start AI voice service: %d\r\n", ret);
+        MediaMutexUnLock(g_audio_test_ctx.mutex);
+        return -1;
+    }
+    
+    // 初始化缓冲区
+    g_audio_test_ctx.buffer_size = 16000;  // 16kHz采样率,1秒数据
+    g_audio_test_ctx.record_buffer = (int16_t*)malloc(g_audio_test_ctx.buffer_size * sizeof(int16_t));
+    if (g_audio_test_ctx.record_buffer == nullptr) {
+        PRINT("[AudioTest] Failed to allocate record buffer\r\n");
+        StopAIVoice();
+        MediaMutexUnLock(g_audio_test_ctx.mutex);
+        return -1;
+    }
+    g_audio_test_ctx.buffer_write_pos = 0;
+    g_audio_test_ctx.last_volume_check_time = GetCurrentTimeMs();
+    g_audio_test_ctx.volume_check_interval_ms = 500;  // 0.5秒
+    
+    // 启动播放线程
+    g_audio_test_ctx.is_running = true;
+    MediaMutexUnLock(g_audio_test_ctx.mutex);
+    
+    MediaThreadattr attr = {
+        "AudioTestPlay",
+        0x2000,  // 8KB栈
+        THREAD_SCHED_INVALID,
+        0,
+        false
+    };
+    
+    g_audio_test_ctx.play_thread = MediaThreadCreate(AudioPlayThread, nullptr, &attr);
+    if (g_audio_test_ctx.play_thread == nullptr) {
+        PRINT("[AudioTest] Failed to create play thread\r\n");
+        g_audio_test_ctx.is_running = false;
+        StopAIVoice();
+        free(g_audio_test_ctx.record_buffer);
+        g_audio_test_ctx.record_buffer = nullptr;
+        return -1;
+    }
+    
+    PRINT("[AudioTest] Audio test started successfully\r\n");
+    PRINT("[AudioTest] Playing 1kHz tone, monitoring volume every 0.5s...\r\n");
+    
+    return 0;
+}
+
+/**
+ * @brief 停止音频测试
+ * @return 0 成功, -1 失败
+ */
+int custom_audio_test_stop(void)
+{
+    PRINT("[AudioTest] Stopping audio test...\r\n");
+    
+    MediaMutexLock(g_audio_test_ctx.mutex);
+    
+    if (!g_audio_test_ctx.is_running) {
+        PRINT("[AudioTest] Test not running\r\n");
+        MediaMutexUnLock(g_audio_test_ctx.mutex);
+        return 0;  // 已经停止,返回成功
+    }
+    
+    // 设置停止标志,让回调和播放线程知道要停止
+    g_audio_test_ctx.is_running = false;
+    MediaMutexUnLock(g_audio_test_ctx.mutex);
+    
+    // 给播放线程一点时间退出(最多等待500ms)
+    MediaThreadIdHandle thread_to_join = nullptr;
+    for (int i = 0; i < 5; i++) {  // 最多等待500ms
+        MediaMutexLock(g_audio_test_ctx.mutex);
+        thread_to_join = g_audio_test_ctx.play_thread;
+        bool has_thread = (thread_to_join != nullptr);
+        MediaMutexUnLock(g_audio_test_ctx.mutex);
+        
+        if (!has_thread) {
+            break;  // 线程已经退出
+        }
+        
+        usleep(100000);  // 等待100ms
+    }
+    
+    // 清理播放线程句柄(如果还在)
+    if (thread_to_join != nullptr) {
+        MediaMutexLock(g_audio_test_ctx.mutex);
+        if (g_audio_test_ctx.play_thread == thread_to_join) {
+            g_audio_test_ctx.play_thread = nullptr;  // 清空句柄,避免重复join
+        }
+        MediaMutexUnLock(g_audio_test_ctx.mutex);
+        // 不等待线程结束,避免阻塞
+    }
+    
+    // 清理缓冲区
+    MediaMutexLock(g_audio_test_ctx.buffer_mutex);
+    if (g_audio_test_ctx.record_buffer != nullptr) {
+        free(g_audio_test_ctx.record_buffer);
+        g_audio_test_ctx.record_buffer = nullptr;
+    }
+    g_audio_test_ctx.buffer_size = 0;
+    g_audio_test_ctx.buffer_write_pos = 0;
+    MediaMutexUnLock(g_audio_test_ctx.buffer_mutex);
+    
+    // 停止AI语音服务(使用StopAIVoice)
+    PRINT("[AudioTest] Calling StopAIVoice()...\r\n");
+    StopAIVoice();
+    
+    PRINT("[AudioTest] Audio test stopped\r\n");
+    
+    return 0;
+}
+
+/**
+ * @brief 获取测试状态
+ * @return true 正在运行, false 未运行
+ */
+bool custom_audio_test_is_running(void)
+{
+    MediaMutexLock(g_audio_test_ctx.mutex);
+    bool is_running = g_audio_test_ctx.is_running;
+    MediaMutexUnLock(g_audio_test_ctx.mutex);
+    return is_running;
+}
+
+/**
+ * @brief 设置播放音量
+ * @param volume 音量值,范围0.0-2.0,1.0为原始音量
+ * @return 0 成功, -1 失败
+ */
+int custom_audio_test_set_volume(float volume)
+{
+    if (volume < 0.0f || volume > 2.0f) {
+        PRINT("[AudioTest] Invalid volume: %.2f (range: 0.0-2.0)\r\n", volume);
+        return -1;
+    }
+    
+    MediaMutexLock(g_audio_test_ctx.volume_mutex);
+    g_audio_test_ctx.play_volume = volume;
+    MediaMutexUnLock(g_audio_test_ctx.volume_mutex);
+    
+    PRINT("[AudioTest] Play volume set to %.2f (%.0f%%)\r\n", volume, volume * 100.0f);
+    return 0;
+}
+
+/**
+ * @brief 获取播放音量
+ * @return 当前音量值(0.0-2.0)
+ */
+float custom_audio_test_get_volume(void)
+{
+    MediaMutexLock(g_audio_test_ctx.volume_mutex);
+    float volume = g_audio_test_ctx.play_volume;
+    MediaMutexUnLock(g_audio_test_ctx.volume_mutex);
+    return volume;
+}
+
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/code/sdk/zhihui/utilscpp/include/pcm_1khz_period.h b/code/sdk/zhihui/utilscpp/include/pcm_1khz_period.h
new file mode 100644 (file)
index 0000000..c5cd349
--- /dev/null
@@ -0,0 +1,14 @@
+// 1000Hz正弦波PCM数据
+// 采样率: 16000Hz, 位深: 16位, 声道: 单声道
+// 数据长度: 16 样本 (32 字节) - 一个完整周期
+// 周期时长: 1.00 ms
+// 注意: 此数据可以循环播放来生成连续的正弦波
+
+#define PCM_1KHZ_DATA_SIZE 16
+#define PCM_1KHZ_DATA_BYTES (16 * 2)
+#define PCM_1KHZ_DATA_PERIOD_SAMPLES 16
+#define PCM_1KHZ_DATA_PERIOD_DURATION_MS (1)
+
+static const int16_t pcm_1khz_data[] = {
+         0,  10031,  18535,  24218,  26213,  24218,  18535,  10031,      0, -10031, -18535, -24218, -26213, -24218, -18535, -10031
+};
diff --git a/code/sdk/zhihui/utilscpp/include/pcm_volume_calculator.h b/code/sdk/zhihui/utilscpp/include/pcm_volume_calculator.h
new file mode 100644 (file)
index 0000000..f2567cb
--- /dev/null
@@ -0,0 +1,104 @@
+/**
+ * @file pcm_volume_calculator.h
+ * @brief PCM音频数据音量计算函数
+ * @version 1.0
+ * 
+ * 提供多种PCM音频数据音量计算方法:
+ * - RMS (均方根) 值
+ * - 峰值 (Peak)
+ * - 分贝 (dB) 转换
+ * - 归一化音量
+ */
+
+#ifndef PCM_VOLUME_CALCULATOR_H
+#define PCM_VOLUME_CALCULATOR_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <math.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief PCM音量计算结果结构体
+ */
+typedef struct {
+    double rms;              ///< RMS (均方根) 值,范围 0.0 ~ 1.0
+    double peak;             ///< 峰值,范围 0.0 ~ 1.0
+    double rms_db;           ///< RMS 分贝值 (dB)
+    double peak_db;          ///< 峰值分贝值 (dB)
+    int16_t max_sample;      ///< 最大样本值
+    int16_t min_sample;      ///< 最小样本值
+    uint32_t sample_count;   ///< 样本数量
+} pcm_volume_result_t;
+
+/**
+ * @brief 计算PCM数据的音量信息
+ * 
+ * @param pcm_data PCM数据指针 (16位有符号整数)
+ * @param sample_count 样本数量
+ * @param result 输出结果结构体指针
+ * 
+ * @return 0 成功, -1 失败
+ * 
+ * @note 支持的格式: 16位有符号PCM (int16_t)
+ */
+int pcm_calculate_volume(const int16_t *pcm_data, size_t sample_count, pcm_volume_result_t *result);
+
+/**
+ * @brief 计算PCM数据的RMS值
+ * 
+ * @param pcm_data PCM数据指针
+ * @param sample_count 样本数量
+ * 
+ * @return RMS值 (0.0 ~ 1.0), 失败返回 -1.0
+ */
+double pcm_calculate_rms(const int16_t *pcm_data, size_t sample_count);
+
+/**
+ * @brief 计算PCM数据的峰值
+ * 
+ * @param pcm_data PCM数据指针
+ * @param sample_count 样本数量
+ * 
+ * @return 峰值 (0.0 ~ 1.0), 失败返回 -1.0
+ */
+double pcm_calculate_peak(const int16_t *pcm_data, size_t sample_count);
+
+/**
+ * @brief 将线性值转换为分贝值
+ * 
+ * @param linear_value 线性值 (0.0 ~ 1.0)
+ * @param reference 参考值,默认为1.0
+ * 
+ * @return 分贝值 (dB)
+ */
+double linear_to_db(double linear_value, double reference);
+
+/**
+ * @brief 将分贝值转换为线性值
+ * 
+ * @param db_value 分贝值 (dB)
+ * @param reference 参考值,默认为1.0
+ * 
+ * @return 线性值 (0.0 ~ 1.0)
+ */
+double db_to_linear(double db_value, double reference);
+
+/**
+ * @brief 获取音量等级描述(用于显示)
+ * 
+ * @param rms_db RMS分贝值
+ * 
+ * @return 音量等级字符串指针
+ */
+const char* pcm_get_volume_level(double rms_db);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PCM_VOLUME_CALCULATOR_H */
+
diff --git a/code/sdk/zhihui/utilscpp/pcm_volume_calculator.cpp b/code/sdk/zhihui/utilscpp/pcm_volume_calculator.cpp
new file mode 100644 (file)
index 0000000..20e364c
--- /dev/null
@@ -0,0 +1,147 @@
+/**
+ * @file pcm_volume_calculator.c
+ * @brief PCM音频数据音量计算函数实现
+ */
+
+#include "pcm_volume_calculator.h"
+#include <string.h>
+#include <stdlib.h>
+
+#define PCM_MAX_VALUE 32767.0
+#define PCM_MIN_VALUE -32768.0
+#define DB_MIN_VALUE -120.0  // 最小分贝值(接近静音)
+
+int pcm_calculate_volume(const int16_t *pcm_data, size_t sample_count, pcm_volume_result_t *result)
+{
+    if (pcm_data == NULL || sample_count == 0 || result == NULL) {
+        return -1;
+    }
+
+    // 初始化结果结构
+    memset(result, 0, sizeof(pcm_volume_result_t));
+    result->sample_count = sample_count;
+
+    // 计算RMS和峰值
+    double sum_squares = 0.0;
+    int16_t max_val = PCM_MIN_VALUE;
+    int16_t min_val = PCM_MAX_VALUE;
+
+    for (size_t i = 0; i < sample_count; i++) {
+        int16_t sample = pcm_data[i];
+        
+        // 计算平方和(用于RMS)
+        double normalized = (double)sample / PCM_MAX_VALUE;
+        sum_squares += normalized * normalized;
+        
+        // 查找最大值和最小值
+        if (sample > max_val) {
+            max_val = sample;
+        }
+        if (sample < min_val) {
+            min_val = sample;
+        }
+    }
+
+    // 计算RMS
+    result->rms = sqrt(sum_squares / sample_count);
+    
+    // 计算峰值(取绝对值较大的)
+    double max_abs = (abs(max_val) > abs(min_val)) ? abs(max_val) : abs(min_val);
+    result->peak = max_abs / PCM_MAX_VALUE;
+    
+    // 保存最大最小样本值
+    result->max_sample = max_val;
+    result->min_sample = min_val;
+    
+    // 计算分贝值
+    result->rms_db = linear_to_db(result->rms, 1.0);
+    result->peak_db = linear_to_db(result->peak, 1.0);
+
+    return 0;
+}
+
+double pcm_calculate_rms(const int16_t *pcm_data, size_t sample_count)
+{
+    if (pcm_data == NULL || sample_count == 0) {
+        return -1.0;
+    }
+
+    double sum_squares = 0.0;
+    
+    for (size_t i = 0; i < sample_count; i++) {
+        double normalized = (double)pcm_data[i] / PCM_MAX_VALUE;
+        sum_squares += normalized * normalized;
+    }
+
+    return sqrt(sum_squares / sample_count);
+}
+
+double pcm_calculate_peak(const int16_t *pcm_data, size_t sample_count)
+{
+    if (pcm_data == NULL || sample_count == 0) {
+        return -1.0;
+    }
+
+    int16_t max_abs = 0;
+    
+    for (size_t i = 0; i < sample_count; i++) {
+        int16_t abs_val = abs(pcm_data[i]);
+        if (abs_val > max_abs) {
+            max_abs = abs_val;
+        }
+    }
+
+    return (double)max_abs / PCM_MAX_VALUE;
+}
+
+double linear_to_db(double linear_value, double reference)
+{
+    if (linear_value <= 0.0) {
+        return DB_MIN_VALUE;
+    }
+    
+    if (reference <= 0.0) {
+        reference = 1.0;
+    }
+    
+    double ratio = linear_value / reference;
+    double db = 20.0 * log10(ratio);
+    
+    // 限制最小分贝值
+    if (db < DB_MIN_VALUE) {
+        db = DB_MIN_VALUE;
+    }
+    
+    return db;
+}
+
+double db_to_linear(double db_value, double reference)
+{
+    if (reference <= 0.0) {
+        reference = 1.0;
+    }
+    
+    if (db_value <= DB_MIN_VALUE) {
+        return 0.0;
+    }
+    
+    return reference * pow(10.0, db_value / 20.0);
+}
+
+const char* pcm_get_volume_level(double rms_db)
+{
+    if (rms_db < -60.0) {
+        return "静音";
+    } else if (rms_db < -40.0) {
+        return "极低";
+    } else if (rms_db < -20.0) {
+        return "低";
+    } else if (rms_db < -6.0) {
+        return "中";
+    } else if (rms_db < 0.0) {
+        return "高";
+    } else {
+        return "最大";
+    }
+}
+