Skip to content

Reused channel slot records the previous occupant's audio #3901

Description

@mcfnord

🤖 AI: A channel slot reused by a new client can have the previous occupant's audio attributed to it. On an -R server the stale frame lands in the new client's own recording, at full amplitude.

Root cause. Between disconnect and the new occupant negotiating transport properties a channel sits at CT_NONE, so decoder selection leaves CurOpusDecoder null. In that window nothing writes the channel's decode buffer: the OPUS decode is skipped for want of a decoder, and bIsRawAudio is false because iCeltNumCodedBytes has been reset to CELT_MINIMUM_NUM_BYTES. vecvecsData is indexed by position in the active-channel list rather than by channel ID, so it still holds the previous occupant's decoded audio — which is then passed to the recorder and read by the mix.

Reproduced 3 of 3 on 5146a072. Client A sends a 440 Hz tone and disconnects; after a gap client B takes the freed slot and sends only silence. B's own pre-identification recording is exactly 128 samples of A's tone — one OPUS frame, DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES — at 440 Hz energy fraction 0.999 or above. A's own stub is silent in the same runs, so this is not a recorder artifact. Reconnect gaps of 100, 250 and 500 ms all reproduce it.

Scope: recordings. vecvecsData also feeds the mix, so the same stale samples are a candidate for the live path — but they do not survive there audibly. On a server run without -R, what a passive listener receives is statistically unchanged by the fix below: the residual artifact at slot handover measures about 59 dB under the source (peak sample 29 of 32767) both with it and without.

Fix — contribute silence in the no-decoder branch:

else
{
  CurOpusDecoder = nullptr;

  // CT_NONE: nothing else writes this buffer, and it is indexed by position in
  // the active-channel list, so it still holds the previous occupant's audio
  memset ( &vecvecsData[iChanCnt][0], 0, vecvecsData[iChanCnt].Size() * sizeof ( int16_t ) );
}

With that applied, B's stub reads 0.0 in 3 of 3 runs and A's own tone still records normally.

One subtlety: zero the whole worst-case buffer rather than iClientFrameSizeSamples worth. That variable is still zero in this branch, since it is only set in the CT_OPUS and CT_OPUS64 arms, so a memset scaled by it writes no bytes at all.


🤖 This message was written by AI and reviewed by @mcfnord.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions