jamulussoftware / jamulussoftware/jamulus
Reused channel slot records the previous occupant's audio
- Dominant language
- C
- Stars
- 1.1k
- Forks
- 248
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 9
Description
**🤖 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](https://github.com/jamulussoftware/jamulus/blob/5146a072ee15425564b0d7845cd7616a24a271b1/src/server.cpp#L894-L897) leaves `CurOpusDecoder` null. In that window nothing writes the channel's decode buffer: [the OPUS decode](https://github.com/jamulussoftware/jamulus/blob/5146a072ee15425564b0d7845cd7616a24a271b1/src/server.cpp#L981-L992) 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](https://github.com/jamulussoftware/jamulus/blob/5146a072ee15425564b0d7845cd7616a24a271b1/src/server.cpp#L754-L758) 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](https://github.com/jamulussoftware/jamulus/blob/5146a072ee15425564b0d7845cd7616a24a271b1/src/server.cpp#L1099), 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:
```cpp
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](https://github.com/jamulussoftware/jamulus/blob/5146a072ee15425564b0d7845cd7616a24a271b1/src/server.cpp#L837) 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.*
Contributor guide
Research direction
Start in src/server.cpp around the decoder-selection branch at the referenced lines, then trace how vecvecsData is passed to the recorder. Reproduce the client A/client B handover with recording enabled and verify the new client's pre-identification frame is silent while the original tone still records normally.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- audio-video-rtc
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100