From 090cc44480e3694e8045ac166dbae55918b1dc01 Mon Sep 17 00:00:00 2001 From: mcfnord Date: Mon, 5 Oct 2026 17:56:46 +0000 Subject: [PATCH 1/6] Clear a reused channel's stale audio and decoder state While a channel has no negotiated codec (CT_NONE), DecodeReceiveData() skipped both decode branches and left vecvecsData[iChanCnt] unwritten. That buffer is indexed by position in the connected-channel list, so with -R a new client in a reused channel was recorded with the audio last decoded at that position. Separately, the per-channel Opus decoders kept the previous client's state, so the new client's recording opened with the end of the previous client's audio. Zero the buffer in the CT_NONE branch, and reset the channel's four decoders when the channel is freed. Both run on the decode path. Fixes #3901 Co-Authored-By: Claude Opus 5.5 --- src/server.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/server.cpp b/src/server.cpp index d24e5a142d..bf9f4f8290 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -894,6 +894,10 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) else { CurOpusDecoder = nullptr; + + // no codec yet (e.g. a new client in a reused channel): nothing writes this buffer, and it + // still holds the audio last decoded at this position in the connected-channel list + vecvecsData[iChanCnt].Reset ( 0 ); } // get gains and pannings of all connected channels, compacted to the @@ -940,6 +944,13 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) { emit ClientDisconnected ( iCurChanID ); // TODO do this outside the mutex lock? + // the decoders belong to the channel, not the client, so drop this client's state + // before the next client in this channel is decoded + opus_custom_decoder_ctl ( OpusDecoderMono[iCurChanID], OPUS_RESET_STATE ); + opus_custom_decoder_ctl ( OpusDecoderStereo[iCurChanID], OPUS_RESET_STATE ); + opus_custom_decoder_ctl ( Opus64DecoderMono[iCurChanID], OPUS_RESET_STATE ); + opus_custom_decoder_ctl ( Opus64DecoderStereo[iCurChanID], OPUS_RESET_STATE ); + FreeChannel ( iCurChanID ); // note that the channel is now not in use // note that no mutex is needed for this shared resource since it is a From 904cd1a47d06041a4d72b95a76f1679066f2b71e Mon Sep 17 00:00:00 2001 From: jrd Date: Wed, 7 Oct 2026 20:56:53 +0000 Subject: [PATCH 2/6] Say what the buffer clear and the decoder reset are for Reword the two comments from the mechanism to the intent, as asked in review. Co-Authored-By: Claude Fable 5.1 --- src/server.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index bf9f4f8290..67aebfa86a 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -895,8 +895,10 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) { CurOpusDecoder = nullptr; - // no codec yet (e.g. a new client in a reused channel): nothing writes this buffer, and it - // still holds the audio last decoded at this position in the connected-channel list + // the codec of this channel is not known yet (the new client has not sent its audio + // properties so far), so nothing below writes this buffer: clear it, otherwise the last + // audio decoded at this position, e.g. by the previous client in a reused channel, would + // be recorded as this client's audio (#3901) vecvecsData[iChanCnt].Reset ( 0 ); } @@ -944,8 +946,8 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) { emit ClientDisconnected ( iCurChanID ); // TODO do this outside the mutex lock? - // the decoders belong to the channel, not the client, so drop this client's state - // before the next client in this channel is decoded + // reset the decoders of this channel so that the next client using it starts from + // a clean state instead of continuing the previous client's audio (#3901) opus_custom_decoder_ctl ( OpusDecoderMono[iCurChanID], OPUS_RESET_STATE ); opus_custom_decoder_ctl ( OpusDecoderStereo[iCurChanID], OPUS_RESET_STATE ); opus_custom_decoder_ctl ( Opus64DecoderMono[iCurChanID], OPUS_RESET_STATE ); From 04869847425d354c2a169ff1043f71bbbf408350 Mon Sep 17 00:00:00 2001 From: jrd Date: Thu, 8 Oct 2026 17:28:15 +0000 Subject: [PATCH 3/6] Clear a reused channel's delay-panning history too Applies the review suggestion from CodeRabbit, raised by softins. Co-authored-by: Tony Mountifield <3224952+softins@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DusinJ4douBH5fCmdf93oL --- src/server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server.cpp b/src/server.cpp index 67aebfa86a..cfb94b8007 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -900,6 +900,7 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) // audio decoded at this position, e.g. by the previous client in a reused channel, would // be recorded as this client's audio (#3901) vecvecsData[iChanCnt].Reset ( 0 ); + vecvecsData2[iChanCnt].Reset ( 0 ); } // get gains and pannings of all connected channels, compacted to the From 215038aff7a6e8e1d73502adfa8fb57bcbd3dedb Mon Sep 17 00:00:00 2001 From: jrd Date: Thu, 8 Oct 2026 18:43:07 +0000 Subject: [PATCH 4/6] Say the intent of the clear and the reset in one breath Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01DusinJ4douBH5fCmdf93oL --- src/server.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index cfb94b8007..00cd32626e 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -895,10 +895,8 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) { CurOpusDecoder = nullptr; - // the codec of this channel is not known yet (the new client has not sent its audio - // properties so far), so nothing below writes this buffer: clear it, otherwise the last - // audio decoded at this position, e.g. by the previous client in a reused channel, would - // be recorded as this client's audio (#3901) + // no codec yet, so nothing below overwrites these buffers: clear them so that a new client + // does not inherit the audio of this channel's previous client (#3901) vecvecsData[iChanCnt].Reset ( 0 ); vecvecsData2[iChanCnt].Reset ( 0 ); } @@ -947,8 +945,8 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) { emit ClientDisconnected ( iCurChanID ); // TODO do this outside the mutex lock? - // reset the decoders of this channel so that the next client using it starts from - // a clean state instead of continuing the previous client's audio (#3901) + // reset the decoders so that the next client in this channel does not continue + // the previous client's audio (#3901) opus_custom_decoder_ctl ( OpusDecoderMono[iCurChanID], OPUS_RESET_STATE ); opus_custom_decoder_ctl ( OpusDecoderStereo[iCurChanID], OPUS_RESET_STATE ); opus_custom_decoder_ctl ( Opus64DecoderMono[iCurChanID], OPUS_RESET_STATE ); From 4113e7cf046c0e19b7bff8f092335b5a946ed470 Mon Sep 17 00:00:00 2001 From: John Dempsey <1750243+mcfnord@users.noreply.github.com> Date: Sat, 10 Oct 2026 08:21:49 -0700 Subject: [PATCH 5/6] Update src/server.cpp Co-authored-by: Tony Mountifield --- src/server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server.cpp b/src/server.cpp index 00cd32626e..f4ee5d0d74 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -899,6 +899,7 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) // does not inherit the audio of this channel's previous client (#3901) vecvecsData[iChanCnt].Reset ( 0 ); vecvecsData2[iChanCnt].Reset ( 0 ); + vecvecsData2[iChanCnt].Reset ( 0 ); } // get gains and pannings of all connected channels, compacted to the From 3da8281f7d0fb9ce6f9b88ff61fcbafc0a8fe20a Mon Sep 17 00:00:00 2001 From: jrd Date: Sat, 10 Oct 2026 18:21:03 +0000 Subject: [PATCH 6/6] Drop the duplicated vecvecsData2 clear Reverts 4113e7cf, a stale suggestion applied twice: 04869847 had already taken it. Co-Authored-By: Claude Fable 5.1 --- src/server.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server.cpp b/src/server.cpp index f4ee5d0d74..00cd32626e 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -899,7 +899,6 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) // does not inherit the audio of this channel's previous client (#3901) vecvecsData[iChanCnt].Reset ( 0 ); vecvecsData2[iChanCnt].Reset ( 0 ); - vecvecsData2[iChanCnt].Reset ( 0 ); } // get gains and pannings of all connected channels, compacted to the