ConvAgent: add waveform-splice discontinuity debug detector
Diagnostic for the audio "pops": EnqueueAgentAudio compares the last sample of
the previous enqueued block with the first sample of the new one and logs
[AUDIO-DBG] with the delta + a running count when the jump exceeds a threshold
(gated behind ps.ai.ConvAgent.Debug.ElevenLabs; role-tagged). The host's raw-PCM
stream is continuous so its count stays ~0; a client whose count climbs at
message cadence still has a splice problem. Confirms the carry-over fix
(bcb6c44) at runtime.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1732,6 +1732,42 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::OnProceduralUnderflow(
|
||||
|
||||
void UPS_AI_ConvAgent_ElevenLabsComponent::EnqueueAgentAudio(const TArray<uint8>& PCMData)
|
||||
{
|
||||
// Debug: flag a waveform discontinuity at the splice between the previous
|
||||
// enqueued block and this one — the signature of the audio pops. The host's
|
||||
// raw-PCM stream is continuous so its count should stay ~0; if a client's
|
||||
// count climbs at message cadence, a splice problem remains.
|
||||
{
|
||||
const int32 DbgVal = CVarDebugElevenLabs.GetValueOnGameThread();
|
||||
const bool bAudioDbg = (DbgVal >= 0) ? (DbgVal > 0) : bDebug;
|
||||
if (bAudioDbg && PCMData.Num() >= static_cast<int32>(sizeof(int16)))
|
||||
{
|
||||
const int16* Samples = reinterpret_cast<const int16*>(PCMData.GetData());
|
||||
const int32 NumSamples = PCMData.Num() / static_cast<int32>(sizeof(int16));
|
||||
if (bHasLastEnqueuedSample)
|
||||
{
|
||||
const int32 Delta = FMath::Abs(static_cast<int32>(Samples[0]) - static_cast<int32>(LastEnqueuedSample));
|
||||
static constexpr int32 DiscontinuityThreshold = 3000; // |delta| on the int16 scale
|
||||
if (Delta > DiscontinuityThreshold)
|
||||
{
|
||||
++AudioSpliceDiscontinuityCount;
|
||||
UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Warning,
|
||||
TEXT("[AUDIO-DBG] Role=%d splice discontinuity: |delta|=%d (prev=%d first=%d) count=%d — pop likely here."),
|
||||
static_cast<int32>(GetOwnerRole()), Delta,
|
||||
static_cast<int32>(LastEnqueuedSample), static_cast<int32>(Samples[0]),
|
||||
AudioSpliceDiscontinuityCount);
|
||||
}
|
||||
else if (DbgVal >= 2)
|
||||
{
|
||||
UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Log,
|
||||
TEXT("[AUDIO-DBG] Role=%d splice ok: |delta|=%d"),
|
||||
static_cast<int32>(GetOwnerRole()), Delta);
|
||||
}
|
||||
}
|
||||
LastEnqueuedSample = Samples[NumSamples - 1];
|
||||
bHasLastEnqueuedSample = true;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
FScopeLock Lock(&AudioQueueLock);
|
||||
AudioQueue.Append(PCMData);
|
||||
@@ -1908,6 +1944,7 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::StopAgentAudio()
|
||||
bPreBuffering = false; // Clear pre-buffer state on stop.
|
||||
ClientReassemblyPCM.Reset(); // Client: drop any half-received message so it can't leak into the next turn.
|
||||
AgentAudioEncodeLeftover.Reset(); // Server: drop the carried sub-frame tail so it can't glue onto the next turn.
|
||||
bHasLastEnqueuedSample = false; // Debug: don't count the inter-turn splice as a discontinuity.
|
||||
{
|
||||
FScopeLock Lock(&AudioQueueLock);
|
||||
AudioQueue.Empty();
|
||||
|
||||
@@ -819,6 +819,14 @@ private:
|
||||
// per turn (new-turn guard in HandleAudioReceived + StopAgentAudio()).
|
||||
TArray<uint8> AgentAudioEncodeLeftover;
|
||||
|
||||
// Debug: detects waveform discontinuities at enqueue/message splices (the
|
||||
// signature of the audio "pops"). Tracks the last enqueued sample; a large
|
||||
// jump to the next block's first sample is flagged and counted. Toggle with
|
||||
// ps.ai.ConvAgent.Debug.ElevenLabs. Reset in StopAgentAudio().
|
||||
int16 LastEnqueuedSample = 0;
|
||||
bool bHasLastEnqueuedSample = false;
|
||||
int32 AudioSpliceDiscontinuityCount = 0;
|
||||
|
||||
// Pre-buffer state: delay playback start to absorb TTS inter-chunk gaps.
|
||||
bool bPreBuffering = false;
|
||||
double PreBufferStartTime = 0.0;
|
||||
|
||||
Reference in New Issue
Block a user