Repository navigation
fix: play and record on Windows devices that mix in more than two channels - #358
Merged
Merged
Conversation
…nnels WebRTC's Windows audio device module asks a device only for mono or stereo. A shared-mode stream must have the channel count of the device's mix format, so a device that mixes in 7.1, as some gaming headsets do, rejects every format, and initializing playout fails with no sound. A WebRTC patch (patches/windows/wasapi_mix_format.patch) handles that case the way Chrome and Firefox do: when no mono or stereo format is accepted, the stream is opened in the device's mix format (16-bit PCM in its channel count, speaker mask and rate), and the module converts between it and WebRTC's mono or stereo at the WASAPI buffer, with WebRTC's port of Chrome's channel mixing matrix. WebRTC above the module keeps getting one or two channels, which is all it accepts. Devices that accept mono or stereo are opened exactly as before. The WebRTC cache key on CI now covers the patches, so a changed patch rebuilds WebRTC instead of restoring a build without it. PlayoutIntegrationTest plays out on the default speaker and checks that WebRTC is asked for mono or stereo 10 ms blocks; it is skipped on hosts without a playout device.
…s them When a device is opened in its mix format, the module converted every sample through the full channel matrix. For playout to more channels than WebRTC's, which is the case of a 7.1 headset, that matrix only routes channels: left and right go to front left and right, the rest is silent, and mono goes to both. ChannelMixingMatrix reports this, and Chrome's ChannelMixer then copies instead of mixing. The module does the same now, with a map of the input channel of each output channel, since its samples are interleaved. The output is the same. On 10 ms at 48 kHz, stereo to 7.1 drops from about 10 to about 1 us per block. Capture from 7.1 to stereo mixes, and keeps the matrix.
Channels that have to be mixed, rather than only routed, went through the full float matrix for every sample: every coefficient, zeros included, then a rounding call per output sample. They are now mixed the way Firefox's cubeb mixer mixes 16-bit audio: each output channel keeps only its non-zero terms, as Q15 integer coefficients, with loops for none, one, two and more terms, rounding by (sum + 16384) >> 15, and clamping only when a matrix can leave the 16-bit range. Unlike cubeb, sums are taken in 64 bits. A row of WebRTC's 7.1 or 5.1 downmix sums to 3.1 to 6 in coefficients, so full-scale input on every channel reaches 3.4 to 6.4 billion, past what cubeb's 32-bit int holds. The matrix and the channel map become one CoreAudioChannelConversion per direction, which ConvertChannels() applies: a copy when the matrix only routes channels, as before, the terms otherwise. On 10 ms at 48 kHz, capture from 7.1 to stereo drops from about 8.5 to 4.3 us, 5.1 to stereo from 7.3 to 3.8, 7.1 to mono from 3.8 to 3.0, and playout from stereo to mono from 2.9 to 0.4. The output is that of the float matrix within 1 to 2 of 32768, and the same at full scale.
The mix took Q15 coefficients from Firefox's cubeb mixer, which its 32-bit sums require. The sums here are 64-bit, so the coefficients now have 24 fractional bits. A float coefficient from 1/2 to 1, as all of WebRTC's downmix coefficients are, fits them exactly, and the sum of full-scale input over a row of the 7.1 downmix stays below 2^42. Against the exact mix, Q15 coefficients put 15 to 29% of the samples of a 7.1 or 5.1 downmix one or two off, at 1 to 5 dB less signal to noise than the float matrix had. With 24 bits, every sample of every case is the exact mix rounded half up, the float matrix is matched to the dB, and the speed is the same. Only the tie of a stereo to mono mix, half a step, rounds up rather than to even.
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.
Addresses #240: There is no sound when using gaming headphones (7.1).
Summary
On Windows, some headsets play no sound: those whose shared-mode mix format has more than two channels, as gaming headsets that mix in 7.1 have (discuss-webrtc thread). WebRTC's Windows audio device module asks such a device only for mono or stereo, at a few rates. A shared-mode stream must have the channel count of the mix format, so the device rejects every one of them and
InitPlayout()fails.Chrome and Firefox open such a device in its own format and convert the channels themselves: Chrome opens its output in the device's channel layout and mixes into it, and takes the closest match on input with its own converter; Firefox's cubeb starts from the mix format and up- or downmixes with its own mixer. Neither leaves it to Windows (
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM). This does the same, as a WebRTC patch,patches/windows/wasapi_mix_format.patch, applied by the existing per-OS patch step:AudioTransportImplaccepts.The conversion is built from WebRTC's port of Chrome's channel mixing matrix (
audio/utility/channel_mixing_matrix) and applied in one of two ways, inCoreAudioChannelConversion:ChannelMixerdoes.int. That also lets the coefficients have 24 fractional bits rather than cubeb's 15, which holds WebRTC's coefficients exactly.The WebRTC cache key on CI now covers the patches, in the part that
restore-keysmatches on, as the FFmpeg key does; otherwise a changed patch would restore a build without it. The first run after this rebuilds WebRTC on every platform.PlayoutIntegrationTestplays silence on the default speaker through the platform module and checks that WebRTC is asked for mono or stereo 10 ms blocks. It runs on Windows only and is skipped without a playout device, as on CI.macOS and Linux need none of this: WebRTC's Core Audio module puts an
AudioConverterbetween the device's format and WebRTC's, and its PulseAudio module opens a stereo stream that the sound server remixes to the device.Commits
Testing
The patch applies the way the CMake step applies it, to the CRLF files of a checkout with
core.autocrlf=true, as here and on Windows CI, both as an LF and as a CRLF patch.WebRTC builds with it (an incremental
ninjaofout/x64), without warnings.mvn -pl webrtc-jni,webrtc packageagainst that build: 286 tests, no failures, 3 skipped (the hardware codec ones that skip here anyway).PlayoutIntegrationTestruns on the stereo speakers here.The new path, on those speakers: with a local change that forced it and asked for mono, WebRTC was asked for mono 48 kHz blocks and the render thread converted them to the device's stereo; 20 blocks arrived in half a second.
Speed, 10 ms at 48 kHz, the patch's code on WebRTC's matrices (median of five runs):
At most about 0.04% of a core.
Precision, against the exact mix in double precision, on one second of noise, a sine and full-scale input: every output sample is the exact mix rounded half up, in every case, and the signal to noise is that of the float matrix (100.1 dB for 7.1 to stereo on noise), set by the 16-bit output itself. With Q15 coefficients, 15 to 29% of the samples of a downmix were one or two off.
Not verified