test(audiod): r1 P2-1/P2-2 — 补 rapid-toggle 回归用例 + BT 音量语义文档(实现零改动)
评审 r1 的 `p2-2items`,两项都是「缺用例 / 缺一句文档」,不改任何已交付行为。 P2-1(连续拖滑块/ramp 中途反向缺回归): - test_vol_gain 新增 G4b:块长 100(< 512 斜坡)× 60 次 100↔10 反向,实测 59 次落在 斜坡中段;断言增益全程 ∈ [q15(10)=4, 32768] 且从不放大、块内逐样本只朝目标单调走、 相邻样本跳变 ≤64 LSB(硬跳变 ~16380)、停手后 cur=4/left=0/bump_left=0。 - test_audiod_host 新增 T20(daemon 侧等价集成用例):经 IPC 连发 60 次 vol, 每次只喂 100 帧(400B),断言 60 次全 ACK、underrun/dropped=0、 落盘 PCM 无硬跳变(max_delta 1020 = 正弦自身斜率)、尾部 RMS 1.49 比头部 6376.58 低 72dB(10% 真的生效)。 P2-2(文档):vol_gain.h 顶部新增「App/SDK 侧契约」段:BT 下 set_volume 同样生效 (走软件增益 + 持久化),AVRCP 钉 127 只为耳机功放满量程而非「音量交给耳机」, pct 只在 [0,100] 有意义(<=0 真静音、>100 按满量程截断),16bit 写路径的 量化下限说明;vol_gain_dac_att_reg 注释补 pct>100 行为。build_audiod.sh 头部补 M55 说明(必须一起编 vol_gain.c、旧 SDK 说法已过期)。 验证:test_audiod_host 134/134 PASS、test_vol_gain 36/36 PASS、test_bt_dev PASS; mipsel 交叉编译 0 warning,产物 md5 6cef4c54a1cb8e0c55f4ad5b6c4a323e(与 r1 完全一致, 证明二进制未变)。
This commit is contained in:
1 parent
e37c622597
commit
2c0ec2ba15
4 files changed
+151
-3
No files matched your search
@@ -347,8 +347,9 @@ static int wait_wav_open(const char *path, int tmo_ms)
|
||||
}
|
||||
|
||||
/* S16 立体声 WAV 的 data 段统计(M55 音量阶梯的**客观量化**:不靠耳朵)。
|
||||
* 返回 ok=1 时填 rms / peak / maxdelta(相邻样本最大跳变,用来证明改音量没有硬跳变爆音)。 */
|
||||
struct wavstat { int ok; long nsamp; double rms; int peak; int maxdelta; };
|
||||
* 返回 ok=1 时填 rms / peak / maxdelta(相邻样本最大跳变,用来证明改音量没有硬跳变爆音),
|
||||
* tail_rms 只统计最后 4096 个样本(T20「连续改音量后是否稳定在新音量」用)。 */
|
||||
struct wavstat { int ok; long nsamp; double rms; int peak; int maxdelta; double tail_rms; double head_rms; };
|
||||
static struct wavstat wav_s16_stats(const char *path)
|
||||
{
|
||||
struct wavstat s;
|
||||
@@ -382,6 +383,14 @@ static struct wavstat wav_s16_stats(const char *path)
|
||||
s.rms = sqrt((double)sumsq / (double)rd);
|
||||
s.peak = peak;
|
||||
s.maxdelta = maxdelta;
|
||||
{ /* 尾部/头部 4096 样本的 RMS(连续改音量后是否稳定在新音量、停手前后差多少) */
|
||||
size_t take = rd < 4096 ? rd : 4096;
|
||||
long long tq = 0, hq = 0;
|
||||
for (size_t i = rd - take; i < rd; i++) tq += (long long)b[i] * b[i];
|
||||
for (size_t i = 0; i < take; i++) hq += (long long)b[i] * b[i];
|
||||
s.tail_rms = take ? sqrt((double)tq / (double)take) : 0.0;
|
||||
s.head_rms = take ? sqrt((double)hq / (double)take) : 0.0;
|
||||
}
|
||||
free(b);
|
||||
return s;
|
||||
}
|
||||
@@ -1360,6 +1369,82 @@ int main(void)
|
||||
unlink(conf19);
|
||||
}
|
||||
|
||||
/* ---- T20:连续拖滑块(rapid toggle)的**集成**回归(评审 r1 P2-1 的 daemon 侧等价用例)。
|
||||
* 状态机级断言(cur/left/bump_left、mid-ramp 反向计数)在 test_vol_gain 的 G4b——那里能直接
|
||||
* 看内部字段;这里验真实写路径:通过 IPC 在 vol 100↔10 之间连发 60 次,每次只喂 100 帧
|
||||
* (400B < 512 样本斜坡),让多数改动落在斜坡中段,然后看落盘的 PCM 有没有硬跳变/丢帧。 */
|
||||
printf("[T20 rapid volume toggle (slider drag) through the write path]\n");
|
||||
{
|
||||
unlink(TSOCK); unlink(TWAV);
|
||||
const char *conf20 = "/tmp/meow_test_settings20.conf";
|
||||
FILE *f20 = fopen(conf20, "w");
|
||||
if (f20) { fprintf(f20, "# meow-settings 1\naudio.volume = 100\naudio.output_bt = 1\n"); fclose(f20); }
|
||||
g_audiod_pid = start_audiod_settings("file", conf20);
|
||||
usleep(350000);
|
||||
struct cli c20;
|
||||
if (cli_connect(&c20) != 0) { check(0, "T20 connect"); }
|
||||
else {
|
||||
m_audio_open_ack_t a20;
|
||||
if (do_open(&c20, 44100, 2, AUDIO_FMT_S16, &a20) != 0 || a20.err != MEOW_OK) {
|
||||
check(0, "T20 open");
|
||||
} else {
|
||||
enum { NFR20 = 12000 }; /* 6000 帧风暴 + 6000 帧停手后(尾部窗口离过渡区很远) */
|
||||
static uint8_t t20[NFR20 * 4];
|
||||
static int16_t s20[NFR20 * 2];
|
||||
for (int i = 0; i < NFR20 * 2; i++) { /* 相位连续:跳变只可能来自增益 */
|
||||
double ph = 2.0 * M_PI * 440.0 * (double)(i / 2) / 44100.0;
|
||||
s20[i] = (int16_t)(sin(ph) * 0.5 * 32767.0);
|
||||
}
|
||||
memcpy(t20, s20, sizeof t20);
|
||||
size_t off = 0;
|
||||
int ack_ok = 1;
|
||||
for (int k = 0; k < 60; k++) {
|
||||
int32_t pct = (k & 1) ? 100 : 10;
|
||||
if (sf(c20.fd, MSG_AUDIO_VOLUME, (uint32_t)(100 + k), &pct, sizeof pct) == 0) {
|
||||
uint8_t ab[64]; uint16_t at;
|
||||
if (rf(c20.fd, &at, ab, sizeof ab) < 0 || at != MSG_ACK) ack_ok = 0;
|
||||
} else ack_ok = 0;
|
||||
do_write(&c20, t20 + off, 400); /* 100 帧:短于 512 样本斜坡 */
|
||||
off += 400;
|
||||
usleep(3000);
|
||||
}
|
||||
check(ack_ok, "60 rapid vol set all ACKed (async volume path keeps up)");
|
||||
int32_t pct10 = 10; /* 停手:稳定到 10% */
|
||||
if (sf(c20.fd, MSG_AUDIO_VOLUME, 199, &pct10, sizeof pct10) == 0) {
|
||||
uint8_t ab[64]; uint16_t at; rf(c20.fd, &at, ab, sizeof ab);
|
||||
}
|
||||
usleep(80000);
|
||||
for (; off < sizeof t20; off += 4096) { /* 剩余:在 10% 下播完 */
|
||||
size_t left = sizeof t20 - off;
|
||||
do_write(&c20, t20 + off, (uint32_t)(left < 4096 ? left : 4096));
|
||||
}
|
||||
char q20[1024];
|
||||
do_query(&c20, q20, sizeof q20);
|
||||
check(strstr(q20, "vol=10") != NULL, "settled to vol=10 after the toggle storm");
|
||||
check(strstr(q20, "underrun=0") != NULL && strstr(q20, "dropped=0") != NULL,
|
||||
"no underrun/dropped frames during 60 volume toggles");
|
||||
wait_wav_open(TWAV, 3000);
|
||||
usleep(250000);
|
||||
do_ctrl(&c20, MSG_AUDIO_STOP, a20.stream_id);
|
||||
close(c20.fd);
|
||||
stop_audiod();
|
||||
struct wavstat st = wav_s16_stats(TWAV);
|
||||
check(st.ok && st.nsamp > 8000, "T20 wav captured");
|
||||
if (st.ok) {
|
||||
printf(" (nsamp=%ld peak=%d max_delta=%d head_rms=%.2f tail_rms=%.2f; hard jump ~16380)\n",
|
||||
st.nsamp, st.peak, st.maxdelta, st.head_rms, st.tail_rms);
|
||||
check(st.maxdelta < 2000, "rapid toggles never hard-jump the gain (no click)");
|
||||
check(st.peak > 4000, "storm still contains loud passages (toggles took effect)");
|
||||
/* 停手后在 10%:尾部应比头部(满音量段)低 ≥40dB(10% = -77.5dB 理论) */
|
||||
check(st.tail_rms > 0.0 && st.head_rms > st.tail_rms * 100.0,
|
||||
"tail settles >=40dB below head (gain really applied at vol=10)");
|
||||
}
|
||||
}
|
||||
}
|
||||
stop_audiod();
|
||||
unlink(conf20);
|
||||
}
|
||||
|
||||
printf("=== SUMMARY: pass=%d fail=%d ===\n", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user