Fix 1-sample channel shift (L/R swap) bug when rapidly appending stereo sources to an empty player queue#870
Closed
axel10 wants to merge 3 commits intoRustAudio:masterfrom
Closed
Fix 1-sample channel shift (L/R swap) bug when rapidly appending stereo sources to an empty player queue#870axel10 wants to merge 3 commits intoRustAudio:masterfrom
axel10 wants to merge 3 commits intoRustAudio:masterfrom
Conversation
Member
|
Thanks for the detailed report! I went in a slightly different direction for the fix in PR #872, which also covers multichannel audio correctly. If you would test that one out and report there? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR resolves an obscure but highly reproducible bug where playing a stereo source immediately after connecting a Player to a Mixer causes the left and right audio channels to be permanently swapped for the entire duration of the track.
Root Cause When a Player is initialized, its internal queue defaults to an Empty source which reports channels() == 1. When the player is appended to the Mixer, it is wrapped in a UniformSourceIterator. The iterator caches this initial metadata and prepares a ChannelCountConverter(from=1, to=2) along with a strict Take(1) reading window for its very first frame block. If the developer decodes the audio file before creating the player, calling player.append(stereo_source) occurs practically instantly after connection. The background audio thread pulls its very first sample through the Take(1) window, which perfectly pops the newly appended stereo_source. It reads exactly 1 sample (the Left channel of the stereo file) and treats it as a mono sample, redundantly duplicating it to both Left and Right outputs. Once this Take(1) block exhausts itself, the iterator re-evaluates the queue, correctly identifies the source as stereo (channels() == 2), and switches to stereo-to-stereo passthrough. Because exactly one sample of the interleaved buffer is now missing, the entire remainder of the stream is desynchronized by 1 sample. The Right sample is played on the Left speaker, and the Left sample is played on the Right speaker continuously.
The Fix Changed the default channel count in src/source/empty.rs from 1 to 2. By defaulting to a stereo (2-channel) dummy frame for sequence initialization, UniformSourceIterator safely allocates Take(2) buffering frames when idle. When an energetic append() occurs synchronously, it cleanly maps both the L and R channels of the fresh frame before re-evaluating metadata, successfully blocking the irreversible 1-sample desync trap.
Steps to Reproduce (Before Fix) To trigger the L/R channel flip consistently:
test.mp3
本 PR 解决了一个虽然冷门但极易复现的 Bug:当 Player(播放器)连接到 Mixer(混音器)后立即播放立体声源时,会导致左右声道在整个曲目播放期间永久性反转。
根本原因
当 Player 初始化时,其内部队列默认为一个 Empty(空)源,该源报告的声道数 channels() == 1。当播放器被追加到 Mixer 时,它被包装在一个 UniformSourceIterator 中。该迭代器会缓存初始元数据,并为第一个帧块准备一个 ChannelCountConverter(from=1, to=2) 以及一个严格的 Take(1)(取 1 个采样)读取窗口。
如果开发者在创建播放器 之前 就完成了音频解码,那么在连接后调用 player.append(stereo_source) 几乎是瞬间发生的。后台音频线程通过 Take(1) 窗口拉取其第一个采样,这正好触发了新追加的 stereo_source。
具体过程如下:
它读取了正好 1 个采样(立体声文件的 左 声道),并将其视为单声道采样,冗余地复制到左、右输出。
一旦这个 Take(1) 块耗尽,迭代器会重新评估队列,正确识别出源为立体声(channels() == 2),并切换到立体声到立体声的直通模式。
由于交织缓冲区(interleaved buffer)中正好丢失了 一个 采样,导致流的剩余部分全部产生 1 个采样的脱节。
结果是:右 采样被播放到了 左 扬声器,而 左 采样被播放到了 右 扬声器,持续不断。
修复方案
将 src/source/empty.rs 中的默认声道数从 1 修改为 2。通过在空闲时默认使用立体声(2 声道)哑帧(dummy frame)进行序列初始化,UniformSourceIterator 在空闲时会安全地分配 Take(2) 缓冲帧。当同步发生快速 append() 时,它能在重新评估元数据之前,完整且干净地映射新帧的 左(L) 和 右(R) 声道,从而成功规避不可逆的 1 采样脱节陷阱。