Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions sdk/audio/decode/AudioStreamDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
10 changes: 10 additions & 0 deletions sdk/audio/decode/AudioStreamDecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 上永不终止。
Expand Down
23 changes: 23 additions & 0 deletions sdk/test/unit/test_stream_decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() 在停止后仍为真");
}
}
Loading