From ca3543fef246a19627845aaf5839dc59cd687750 Mon Sep 17 00:00:00 2001 From: wtp <15908282659@163.com> Date: Mon, 9 Nov 2026 09:17:04 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20stop()=20=E6=B8=85=E7=A9=BA=E6=9C=AA?= =?UTF-8?q?=E6=B6=88=E8=B4=B9=20seek=20=E6=A7=BD=E5=B9=B6=E9=94=81?= =?UTF-8?q?=E5=AE=9A=E7=8A=B6=E6=80=81=E5=A5=91=E7=BA=A6=EF=BC=88#113?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AudioStreamDecoder::stop() 在 join 之后清 m_seekRequestMs:cancelRequested() 把「槽内有值」当取消,残留会让停止后该谓词仍为真。start() 本就在 stop() 之后清槽,正常路径行为等价。 - 加两个测试专用读写钩子(#112 批准的第二次限定例外):SDK 内部类型、不进公开 API、setter release / getter acquire、只在控制线程调用、生产路径零调用。 - sdk_unit_tests 新增确定性判据:不启线程、不依赖素材、不命中时序窗口;变异验证可红可绿。 - 不改 :546 与 E 链 #59/#60/#65 时序,不改 MusicPlayer.cpp / 公开 API / 既有判据 / TIMEOUT / 坏输入 55 例基线 / CI 配置。 - 边界:本改动不修复 42488656322 的 SIGSEGV(无复现、无栈、平台不对齐),根因未定位;取证与决议见 #112。 --- sdk/audio/decode/AudioStreamDecoder.cpp | 14 ++++++++++++++ sdk/audio/decode/AudioStreamDecoder.h | 10 ++++++++++ sdk/test/unit/test_stream_decoder.cpp | 23 +++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/sdk/audio/decode/AudioStreamDecoder.cpp b/sdk/audio/decode/AudioStreamDecoder.cpp index dd5dba769..febd7a9d7 100644 --- a/sdk/audio/decode/AudioStreamDecoder.cpp +++ b/sdk/audio/decode/AudioStreamDecoder.cpp @@ -213,9 +213,23 @@ void AudioStreamDecoder::stop() } m_stopRequest.store(false); m_abortDecode.store(false); + // #113:join 已完成,未消费的 seek 目标不得留到下一次任务。cancelRequested() + // 把「槽内有值」当取消,残留会让 stop() 返回后该谓词仍为真。start() 本就会在 + // stop() 之后清槽,这里补齐 stop() 自身的退出状态,正常路径行为等价。 + m_seekRequestMs.store(-1); m_state.store(StreamDecoderState::IDLE); } +void AudioStreamDecoder::setPendingSeekForTest(int64_t targetMs) +{ + m_seekRequestMs.store(targetMs, std::memory_order_release); +} + +int64_t AudioStreamDecoder::pendingSeekMsForTest() const +{ + return m_seekRequestMs.load(std::memory_order_acquire); +} + void AudioStreamDecoder::seekToMs(uint64_t targetMs) { if (m_state.load() == StreamDecoderState::IDLE || m_state.load() == StreamDecoderState::ERROR) { diff --git a/sdk/audio/decode/AudioStreamDecoder.h b/sdk/audio/decode/AudioStreamDecoder.h index 225e65513..216174089 100644 --- a/sdk/audio/decode/AudioStreamDecoder.h +++ b/sdk/audio/decode/AudioStreamDecoder.h @@ -70,6 +70,16 @@ class AudioStreamDecoder : public NonCopyable, // 只有解码器自己看得见的静默窗口(SEEKING / 已请求停止 / 已置取消位)里的调用才计数。 void noteSilenceFill(); + // 测试专用(决策与验收见 issue #112 / #113):直接读写「尚未被解码线程取走的 seek 目标」槽, + // 用于锁定 stop() 必须清空该槽的状态契约。AudioStreamDecoder 是 SDK 内部类型, + // 不进 soundbridge::Player 公开 API;生产路径零调用,测试里只在控制线程调用。 + // 但槽本身跨线程共享:解码线程用 exchange(-1) 取走目标,因此 setter 用 release 写、 + // getter 用 acquire 读,不能因为「只在测试里开」就降为 relaxed;stop() 的清槽沿用 + // 该槽既有的默认 seq_cst store,与 acquire 读兼容。 + // 放行仍须走「master 与分支同轮数 ctest -j4 对照」稳定性闸门,本注释不构成任何豁免。 + void setPendingSeekForTest(int64_t targetMs); + int64_t pendingSeekMsForTest() const; + // 刻意不提供同步的 decodeNext()(#18)。它曾对三类 codec 有三套语义: // FLAC/Vorbis 每次调用新建解码器 → 只解首帧、返回「有进度」但内容每轮完全相同, // 通用路径又一次性解完整段。结果是 `while (ret != EOS)` 在 FLAC/OGG 上永不终止。 diff --git a/sdk/test/unit/test_stream_decoder.cpp b/sdk/test/unit/test_stream_decoder.cpp index 33683c0de..6dcf61552 100644 --- a/sdk/test/unit/test_stream_decoder.cpp +++ b/sdk/test/unit/test_stream_decoder.cpp @@ -823,3 +823,26 @@ TEST_SUITE("AudioStreamDecoder Edge Cases") decoder.stop(); } } + + +// #113:stop() 必须清空「尚未被解码线程取走」的 seek 目标槽。 +// 不启解码线程、不依赖真实素材、不命中任何时序窗口 —— 判据确定性、毫秒级、可红可绿。 +// 背景:临时探针取证显示「seek 提交后立刻 stop」窗口每轮 2/3 次命中(160/240,三组一致), +// 但该窗口在 Windows 侧 0 崩溃;本用例锁的是状态契约本身,不宣称修复 SIGSEGV。 +TEST_SUITE("AudioStreamDecoder Stop Contract") +{ + TEST_CASE("stop clears an unconsumed seek request") + { + AudioRingBuffer ring(AudioRingBuffer::kDefaultCapacity); + AudioStreamDecoder decoder(&ring, makeDevSpec()); + + decoder.setPendingSeekForTest(5000); + // 前置 sanity:验证 setter 确实写了槽,避免 setter 失效让下面的判据假绿。 + CHECK(decoder.pendingSeekMsForTest() == 5000); + + decoder.stop(); + + CHECK_MESSAGE(decoder.pendingSeekMsForTest() == -1, + "stop() 必须清空未消费的 seek 目标,否则 cancelRequested() 在停止后仍为真"); + } +}