已合并
fix echo #112
海思-陈哲宇创建于 9月8日
fix echo #112
已合并
海思-陈哲宇创建于 9月8日
共 2 个文件变更+60-5
@@ -72,6 +72,7 @@ extern "C" int32_t VoiceCloudLlmAudioPlayerStop(void);
72extern "C" int32_t VoiceCloudLlmAudioPlayerFeed(const uint8_t *data, uint32_t len);72extern "C" int32_t VoiceCloudLlmAudioPlayerFeed(const uint8_t *data, uint32_t len);
73extern "C" int32_t VoiceCloudLlmAudioPlayerFlush(void);73extern "C" int32_t VoiceCloudLlmAudioPlayerFlush(void);
74extern "C" bool VoiceCloudLlmAudioPlayerIsRunning(void);74extern "C" bool VoiceCloudLlmAudioPlayerIsRunning(void);
75+extern "C" bool VoiceCloudLlmAudioPlayerIsPlaying(void);
75 76 
76namespace {77namespace {
77 78 
@@ -1268,6 +1269,18 @@ static void VoiceCloudLlmHandleChatCompletedEvent(VoiceCloudLlmContext *ctx)
1268 VoiceCloudLlmGetState(ctx) != VOICE_CLOUD_LLM_STATE_AWAITING_CHAT_COMPLETION) {1269 VoiceCloudLlmGetState(ctx) != VOICE_CLOUD_LLM_STATE_AWAITING_CHAT_COMPLETION) {
1269 return;1270 return;
1270 }1271 }
1272+ /*
1273+ * Drain the reply tail first: feeding the player returns before the
1274+ * audio is heard, so when CHAT_COMPLETED arrives the tail may still
1275+ * span the player queue, an in-flight write and the DAC ring. Poll
1276+ * until it has actually finished sounding, then restart capture into
1277+ * a quiet downlink. Stops/flushes (call interrupt, player stop) make
1278+ * IsPlaying() false, so this cannot hang on a dead player.
1279+ */
1280+ const uint32_t kDrainPollMs = 10;
1281+ while (VoiceCloudLlmAudioPlayerIsPlaying()) {
1282+ osDelay(kDrainPollMs);
1283+ }
1271 VoiceCloudLlmStopWakeupRecognition();1284 VoiceCloudLlmStopWakeupRecognition();
1272 if (VoiceCloudLlmAudioCaptureStart(VoiceCloudLlmGetConfig()) != 0) {1285 if (VoiceCloudLlmAudioCaptureStart(VoiceCloudLlmGetConfig()) != 0) {
1273 WEARABLE_LOGE(WEARABLE_LOG_MODULE_SMART_VOICE,1286 WEARABLE_LOGE(WEARABLE_LOG_MODULE_SMART_VOICE,
@@ -102,9 +102,15 @@ struct VoiceCloudLlmPlayerContext {
102 * thread answers with flushAck. */102 * thread answers with flushAck. */
103 std::atomic<bool> flushRequested;103 std::atomic<bool> flushRequested;
104 std::atomic<bool> flushAck;104 std::atomic<bool> flushAck;
105- /* Tick by which audio written before the current flush has finished105+ /* Tick by which everything written so far has finished sounding
106- * sounding (last write tick + ring estimate). */106+ * (last write tick + ring estimate); published after every real chunk
107+ * write — see VoiceCloudLlmAudioPlayerIsPlaying(). */
107 std::atomic<uint32_t> ringEmptyTick;108 std::atomic<uint32_t> ringEmptyTick;
109+ /* Real frames queued but not yet fully written: incremented on feed,
110+ * decremented when the player thread (or a flush) frees the frame.
111+ * Counted here instead of querying the queue so the dequeue window is
112+ * covered too. */
113+ std::atomic<uint32_t> pendingFrames;
108 osMessageQueueId_t queue;114 osMessageQueueId_t queue;
109 MediaThreadIdHandle thread;115 MediaThreadIdHandle thread;
110 std::unique_ptr<Audio::AudioStreamOut> streamOut;116 std::unique_ptr<Audio::AudioStreamOut> streamOut;
@@ -117,7 +123,7 @@ struct VoiceCloudLlmPlayerContext {
117 123 
118 VoiceCloudLlmPlayerContext()124 VoiceCloudLlmPlayerContext()
119 : started(false), stopRequested(false), flushRequested(false), flushAck(false),125 : started(false), stopRequested(false), flushRequested(false), flushAck(false),
120- ringEmptyTick(0), queue(nullptr), thread(nullptr),126+ ringEmptyTick(0), pendingFrames(0), queue(nullptr), thread(nullptr),
121 streamOut(nullptr), sessionId(AUDIO_SESSION_ID_NONE), interruptActive(false), chunkBytes(0), volume(0)127 streamOut(nullptr), sessionId(AUDIO_SESSION_ID_NONE), interruptActive(false), chunkBytes(0), volume(0)
122 {128 {
123 (void)memset_s(&renderConfig, sizeof(renderConfig), 0, sizeof(renderConfig));129 (void)memset_s(&renderConfig, sizeof(renderConfig), 0, sizeof(renderConfig));
@@ -157,12 +163,17 @@ bool VoiceCloudLlmQueueFrame(const uint8_t *data, uint32_t len)
157 }163 }
158 frame.len = len;164 frame.len = len;
159 165 
166+ /* Count before enqueue (rolled back on failure): the player thread
167+ * decrements when it frees the frame, so the add must be visible
168+ * before the frame is. */
169+ g_playerCtx.pendingFrames.fetch_add(1);
160 uint32_t try_times = 0;170 uint32_t try_times = 0;
161 while (osMessageQueuePut(g_playerCtx.queue, &frame, 0, 0) != osOK) {171 while (osMessageQueuePut(g_playerCtx.queue, &frame, 0, 0) != osOK) {
162 try_times++;172 try_times++;
163 if (try_times >= VOICE_CLOUD_LLM_PLAYER_PUT_MESSAGE_MAX_TIMES) {173 if (try_times >= VOICE_CLOUD_LLM_PLAYER_PUT_MESSAGE_MAX_TIMES) {
164 WEARABLE_LOGW(WEARABLE_LOG_MODULE_SMART_VOICE, "voice cloud llm player queue put fail\r\n");174 WEARABLE_LOGW(WEARABLE_LOG_MODULE_SMART_VOICE, "voice cloud llm player queue put fail\r\n");
165 VoiceCloudLlmFreeFrame(&frame);175 VoiceCloudLlmFreeFrame(&frame);
176+ g_playerCtx.pendingFrames.fetch_sub(1);
166 return false;177 return false;
167 }178 }
168 osDelay(VOICE_CLOUD_LLM_PLAYER_PUT_MESSAGE_DELAY_MS);179 osDelay(VOICE_CLOUD_LLM_PLAYER_PUT_MESSAGE_DELAY_MS);
@@ -179,6 +190,7 @@ void VoiceCloudLlmFlushPlayerQueue()
179 VoiceCloudLlmPcmFrame frame = {};190 VoiceCloudLlmPcmFrame frame = {};
180 while (osMessageQueueGet(g_playerCtx.queue, &frame, nullptr, 0) == osOK) {191 while (osMessageQueueGet(g_playerCtx.queue, &frame, nullptr, 0) == osOK) {
181 VoiceCloudLlmFreeFrame(&frame);192 VoiceCloudLlmFreeFrame(&frame);
193+ g_playerCtx.pendingFrames.fetch_sub(1);
182 }194 }
183}195}
184 196 
@@ -361,6 +373,11 @@ static int16_t VoiceCloudLlmPlayerWriteFrameChunks(
361 wCtx.pacing.lastWriteCompleteTick = osKernelGetTickCount();373 wCtx.pacing.lastWriteCompleteTick = osKernelGetTickCount();
362 wCtx.pacing.armed = true;374 wCtx.pacing.armed = true;
363 wCtx.ringEst.update(wCtx.pacing.lastWriteCompleteTick, cfg.ms);375 wCtx.ringEst.update(wCtx.pacing.lastWriteCompleteTick, cfg.ms);
376+ /* Publishing the drain deadline here is what makes IsPlaying() see
377+ * the write→sound-out latency: bytes handed to StreamWrite keep
378+ * sounding for ringEst.ms after the call returns. */
379+ g_playerCtx.ringEmptyTick.store(wCtx.pacing.lastWriteCompleteTick +
380+ static_cast<uint32_t>(wCtx.ringEst.estMs));
364 381 
365 if (writeLen >= 2U) {382 if (writeLen >= 2U) {
366 const int16_t *samples = reinterpret_cast<const int16_t *>(data + offset);383 const int16_t *samples = reinterpret_cast<const int16_t *>(data + offset);
@@ -473,8 +490,8 @@ void *VoiceCloudLlmAudioPlayerThread(void *argument)
473 const uint32_t queueCount = (g_playerCtx.queue != nullptr)490 const uint32_t queueCount = (g_playerCtx.queue != nullptr)
474 ? osMessageQueueGetCount(g_playerCtx.queue) : 0U;491 ? osMessageQueueGetCount(g_playerCtx.queue) : 0U;
475 if (g_playerCtx.flushRequested.load() && queueCount == 0U) {492 if (g_playerCtx.flushRequested.load() && queueCount == 0U) {
476- g_playerCtx.ringEmptyTick.store(wCtx.pacing.lastWriteCompleteTick +493+ /* ringEmptyTick is already current from the last real write —
477- static_cast<uint32_t>(wCtx.ringEst.estMs));494+ * no new data can arrive while flushRequested is set. */
478 g_playerCtx.flushRequested.store(false);495 g_playerCtx.flushRequested.store(false);
479 g_playerCtx.flushAck.store(true);496 g_playerCtx.flushAck.store(true);
480 }497 }
@@ -498,6 +515,7 @@ void *VoiceCloudLlmAudioPlayerThread(void *argument)
498 515 
499 if (g_playerCtx.stopRequested.load()) {516 if (g_playerCtx.stopRequested.load()) {
500 VoiceCloudLlmFreeFrame(&frame);517 VoiceCloudLlmFreeFrame(&frame);
518+ g_playerCtx.pendingFrames.fetch_sub(1);
501 break;519 break;
502 }520 }
503 521 
@@ -505,6 +523,7 @@ void *VoiceCloudLlmAudioPlayerThread(void *argument)
505 VoiceCloudLlmPlayerProcessFrame(frame, gap, fState, cfg, wCtx);523 VoiceCloudLlmPlayerProcessFrame(frame, gap, fState, cfg, wCtx);
506 }524 }
507 VoiceCloudLlmFreeFrame(&frame);525 VoiceCloudLlmFreeFrame(&frame);
526+ g_playerCtx.pendingFrames.fetch_sub(1);
508 }527 }
509 return nullptr;528 return nullptr;
510}529}
@@ -536,6 +555,7 @@ void VoiceCloudLlmPlayerCleanup()
536 g_playerCtx.flushRequested.store(false);555 g_playerCtx.flushRequested.store(false);
537 g_playerCtx.flushAck.store(false);556 g_playerCtx.flushAck.store(false);
538 g_playerCtx.ringEmptyTick.store(0);557 g_playerCtx.ringEmptyTick.store(0);
558+ g_playerCtx.pendingFrames.store(0);
539 g_playerCtx.started.store(false);559 g_playerCtx.started.store(false);
540 g_playerCtx.chunkBytes = 0;560 g_playerCtx.chunkBytes = 0;
541 g_playerCtx.thread = nullptr;561 g_playerCtx.thread = nullptr;
@@ -584,6 +604,7 @@ static int32_t VoiceCloudLlmPlayerCreateStream(const VoiceCloudLlmConfig *config
584 g_playerCtx.flushRequested.store(false);604 g_playerCtx.flushRequested.store(false);
585 g_playerCtx.flushAck.store(false);605 g_playerCtx.flushAck.store(false);
586 g_playerCtx.ringEmptyTick.store(0);606 g_playerCtx.ringEmptyTick.store(0);
607+ g_playerCtx.pendingFrames.store(0);
587 MediaThreadattr attr = {"voice_cloud_llm_play", VOICE_CLOUD_LLM_PLAYER_STACK_SIZE,608 MediaThreadattr attr = {"voice_cloud_llm_play", VOICE_CLOUD_LLM_PLAYER_STACK_SIZE,
588 THREAD_SCHED_INVALID, VOICE_CLOUD_LLM_PLAYER_PRIORITY, false};609 THREAD_SCHED_INVALID, VOICE_CLOUD_LLM_PLAYER_PRIORITY, false};
589 g_playerCtx.thread = MediaThreadCreate(VoiceCloudLlmAudioPlayerThread, nullptr, &attr);610 g_playerCtx.thread = MediaThreadCreate(VoiceCloudLlmAudioPlayerThread, nullptr, &attr);
@@ -722,6 +743,27 @@ extern "C" bool VoiceCloudLlmAudioPlayerIsRunning(void)
722 return g_playerCtx.started.load() && !g_playerCtx.stopRequested.load();743 return g_playerCtx.started.load() && !g_playerCtx.stopRequested.load();
723}744}
724 745 
746+/*
747+ * True while real audio is still sounding. Feeding data returns long before
748+ * it is heard: frames may still sit in the player queue or be mid-write, and
749+ * bytes already handed to StreamWrite keep draining from the DAC ring for up
750+ * to ~192 ms afterwards. Covers all three stages, so a caller that loops on
751+ * this until false waits out the send-to-sound-out latency. Gap-bridging
752+ * silence does not count as playing.
753+ */
754+extern "C" bool VoiceCloudLlmAudioPlayerIsPlaying(void)
755+{
756+ if (!VoiceCloudLlmAudioPlayerIsRunning()) {
757+ return false;
758+ }
759+ if (g_playerCtx.pendingFrames.load() > 0U) {
760+ return true;
761+ }
762+ const int32_t ringRemainMs = static_cast<int32_t>(g_playerCtx.ringEmptyTick.load() +
763+ VOICE_CLOUD_LLM_PLAYER_RING_MARGIN_MS - osKernelGetTickCount());
764+ return ringRemainMs > 0;
765+}
766+ 
725extern "C" int32_t VoiceCloudLlmAudioPlayerStop(void)767extern "C" int32_t VoiceCloudLlmAudioPlayerStop(void)
726{768{
727 if (!g_playerCtx.started.load() && g_playerCtx.thread == nullptr) {769 if (!g_playerCtx.started.load() && g_playerCtx.thread == nullptr) {