Be more robust with to-mono channel conversions

master
Chris Robinson 2020-10-05 22:30:23 -07:00
parent e7a44d3b70
commit 7fb6d64ca8
3 changed files with 75 additions and 29 deletions

View File

@ -1556,18 +1556,6 @@ HRESULT WasapiCapture::resetProxy()
if(wfx != nullptr)
{
TraceFormat("Got capture format", wfx);
if(!(wfx->nChannels == InputType.Format.nChannels ||
(wfx->nChannels == 1 && InputType.Format.nChannels == 2) ||
(wfx->nChannels == 2 && InputType.Format.nChannels == 1)))
{
ERR("Failed to get matching format, wanted: %s %s %uhz, got: %d channel%s %d-bit %luhz\n",
DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
mDevice->Frequency, wfx->nChannels, (wfx->nChannels==1)?"":"s", wfx->wBitsPerSample,
wfx->nSamplesPerSec);
CoTaskMemFree(wfx);
return E_FAIL;
}
if(!MakeExtensible(&InputType, wfx))
{
CoTaskMemFree(wfx);
@ -1575,9 +1563,47 @@ HRESULT WasapiCapture::resetProxy()
}
CoTaskMemFree(wfx);
wfx = nullptr;
auto validate_fmt = [](ALCdevice *device, uint32_t chancount, DWORD chanmask) noexcept
-> bool
{
switch(device->FmtChans)
{
/* If the device wants mono, we can handle any input. */
case DevFmtMono:
return true;
/* If the device wants stereo, we can handle mono or stereo input. */
case DevFmtStereo:
return (chancount == 2 && (chanmask == 0 || (chanmask&StereoMask) == STEREO))
|| (chancount == 1 && (chanmask&MonoMask) == MONO);
/* Otherwise, the device must match the input type. */
case DevFmtQuad:
return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD));
/* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */
case DevFmtX51:
case DevFmtX51Rear:
return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1
|| (chanmask&X51RearMask) == X5DOT1REAR));
case DevFmtX61:
return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1));
case DevFmtX71:
return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1));
case DevFmtAmbi3D: return (chanmask == 0 && device->channelsFromFmt());
}
return false;
};
if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask))
{
ERR("Failed to match format, wanted: %s %s %uhz, got: 0x%08lx mask %d channel%s %d-bit %luhz\n",
DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels,
(InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample,
InputType.Format.nSamplesPerSec);
return E_FAIL;
}
}
DevFmtType srcType;
DevFmtType srcType{};
if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
{
if(InputType.Format.wBitsPerSample == 8)
@ -1608,10 +1634,20 @@ HRESULT WasapiCapture::resetProxy()
return E_FAIL;
}
if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels == 2)
if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels != 1)
{
mChannelConv = ChannelConverter{srcType, DevFmtStereo, mDevice->FmtChans};
TRACE("Created %s stereo-to-mono converter\n", DevFmtTypeString(srcType));
ALuint chanmask{(1u<<InputType.Format.nChannels) - 1u};
/* Exclude LFE from the downmix. */
if((InputType.dwChannelMask&SPEAKER_LOW_FREQUENCY))
{
constexpr auto lfemask = MaskFromTopBits(SPEAKER_LOW_FREQUENCY);
const int lfeidx{POPCNT32(InputType.dwChannelMask&lfemask) - 1};
chanmask &= ~(1u << lfeidx);
}
mChannelConv = ChannelConverter{srcType, InputType.Format.nChannels, chanmask,
mDevice->FmtChans};
TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType));
/* The channel converter always outputs float, so change the input type
* for the resampler/type-converter.
*/
@ -1619,7 +1655,7 @@ HRESULT WasapiCapture::resetProxy()
}
else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1)
{
mChannelConv = ChannelConverter{srcType, DevFmtMono, mDevice->FmtChans};
mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans};
TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
srcType = DevFmtFloat;
}

View File

@ -135,14 +135,24 @@ void Mono2Stereo(float *RESTRICT dst, const void *src, const size_t frames) noex
}
template<DevFmtType T>
void Stereo2Mono(float *RESTRICT dst, const void *src, const size_t frames) noexcept
void Multi2Mono(ALuint chanmask, const size_t step, const float scale, float *RESTRICT dst,
const void *src, const size_t frames) noexcept
{
using SampleType = typename DevFmtTypeTraits<T>::Type;
const SampleType *ssrc = static_cast<const SampleType*>(src);
std::fill_n(dst, frames, 0.0f);
for(size_t c{0};chanmask;++c)
{
if LIKELY((chanmask&1))
{
for(size_t i{0u};i < frames;i++)
dst[i] += LoadSample<T>(ssrc[i*step + c]);
}
chanmask >>= 1;
}
for(size_t i{0u};i < frames;i++)
dst[i] = (LoadSample<T>(ssrc[i*2 + 0])+LoadSample<T>(ssrc[i*2 + 1])) *
0.707106781187f;
dst[i] *= scale;
}
} // namespace
@ -328,11 +338,12 @@ ALuint SampleConverter::convert(const void **src, ALuint *srcframes, void *dst,
void ChannelConverter::convert(const void *src, float *dst, ALuint frames) const
{
if(mSrcChans == DevFmtStereo && mDstChans == DevFmtMono)
if(mDstChans == DevFmtMono)
{
const float scale{std::sqrt(1.0f / static_cast<float>(POPCNT32(mChanMask)))};
switch(mSrcType)
{
#define HANDLE_FMT(T) case T: Stereo2Mono<T>(dst, src, frames); break
#define HANDLE_FMT(T) case T: Multi2Mono<T>(mChanMask, mSrcStep, scale, dst, src, frames); break
HANDLE_FMT(DevFmtByte);
HANDLE_FMT(DevFmtUByte);
HANDLE_FMT(DevFmtShort);
@ -343,7 +354,7 @@ void ChannelConverter::convert(const void *src, float *dst, ALuint frames) const
#undef HANDLE_FMT
}
}
else if(mSrcChans == DevFmtMono && mDstChans == DevFmtStereo)
else if(mChanMask == 0x1 && mDstChans == DevFmtStereo)
{
switch(mSrcType)
{
@ -358,6 +369,4 @@ void ChannelConverter::convert(const void *src, float *dst, ALuint frames) const
#undef HANDLE_FMT
}
}
else
LoadSamples(dst, src, 1u, mSrcType, frames * ChannelsFromDevFmt(mSrcChans, 0));
}

View File

@ -49,11 +49,12 @@ SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType,
struct ChannelConverter {
DevFmtType mSrcType;
DevFmtChannels mSrcChans;
DevFmtChannels mDstChans;
DevFmtType mSrcType{};
ALuint mSrcStep{};
ALuint mChanMask{};
DevFmtChannels mDstChans{};
bool is_active() const noexcept { return mSrcChans != mDstChans; }
bool is_active() const noexcept { return mChanMask != 0; }
void convert(const void *src, float *dst, ALuint frames) const;
};