Be more robust with to-mono channel conversions
parent
e7a44d3b70
commit
7fb6d64ca8
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue