Be more robust with to-mono channel conversions
This commit is contained in:
+53
-17
@@ -1556,18 +1556,6 @@ HRESULT WasapiCapture::resetProxy()
|
|||||||
if(wfx != nullptr)
|
if(wfx != nullptr)
|
||||||
{
|
{
|
||||||
TraceFormat("Got capture format", wfx);
|
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))
|
if(!MakeExtensible(&InputType, wfx))
|
||||||
{
|
{
|
||||||
CoTaskMemFree(wfx);
|
CoTaskMemFree(wfx);
|
||||||
@@ -1575,9 +1563,47 @@ HRESULT WasapiCapture::resetProxy()
|
|||||||
}
|
}
|
||||||
CoTaskMemFree(wfx);
|
CoTaskMemFree(wfx);
|
||||||
wfx = nullptr;
|
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(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
|
||||||
{
|
{
|
||||||
if(InputType.Format.wBitsPerSample == 8)
|
if(InputType.Format.wBitsPerSample == 8)
|
||||||
@@ -1608,10 +1634,20 @@ HRESULT WasapiCapture::resetProxy()
|
|||||||
return E_FAIL;
|
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};
|
ALuint chanmask{(1u<<InputType.Format.nChannels) - 1u};
|
||||||
TRACE("Created %s stereo-to-mono converter\n", DevFmtTypeString(srcType));
|
/* 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
|
/* The channel converter always outputs float, so change the input type
|
||||||
* for the resampler/type-converter.
|
* for the resampler/type-converter.
|
||||||
*/
|
*/
|
||||||
@@ -1619,7 +1655,7 @@ HRESULT WasapiCapture::resetProxy()
|
|||||||
}
|
}
|
||||||
else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1)
|
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));
|
TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
|
||||||
srcType = DevFmtFloat;
|
srcType = DevFmtFloat;
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-8
@@ -135,14 +135,24 @@ void Mono2Stereo(float *RESTRICT dst, const void *src, const size_t frames) noex
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<DevFmtType T>
|
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;
|
using SampleType = typename DevFmtTypeTraits<T>::Type;
|
||||||
|
|
||||||
const SampleType *ssrc = static_cast<const SampleType*>(src);
|
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++)
|
for(size_t i{0u};i < frames;i++)
|
||||||
dst[i] = (LoadSample<T>(ssrc[i*2 + 0])+LoadSample<T>(ssrc[i*2 + 1])) *
|
dst[i] *= scale;
|
||||||
0.707106781187f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // 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
|
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)
|
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(DevFmtByte);
|
||||||
HANDLE_FMT(DevFmtUByte);
|
HANDLE_FMT(DevFmtUByte);
|
||||||
HANDLE_FMT(DevFmtShort);
|
HANDLE_FMT(DevFmtShort);
|
||||||
@@ -343,7 +354,7 @@ void ChannelConverter::convert(const void *src, float *dst, ALuint frames) const
|
|||||||
#undef HANDLE_FMT
|
#undef HANDLE_FMT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(mSrcChans == DevFmtMono && mDstChans == DevFmtStereo)
|
else if(mChanMask == 0x1 && mDstChans == DevFmtStereo)
|
||||||
{
|
{
|
||||||
switch(mSrcType)
|
switch(mSrcType)
|
||||||
{
|
{
|
||||||
@@ -358,6 +369,4 @@ void ChannelConverter::convert(const void *src, float *dst, ALuint frames) const
|
|||||||
#undef HANDLE_FMT
|
#undef HANDLE_FMT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
LoadSamples(dst, src, 1u, mSrcType, frames * ChannelsFromDevFmt(mSrcChans, 0));
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -49,11 +49,12 @@ SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType,
|
|||||||
|
|
||||||
|
|
||||||
struct ChannelConverter {
|
struct ChannelConverter {
|
||||||
DevFmtType mSrcType;
|
DevFmtType mSrcType{};
|
||||||
DevFmtChannels mSrcChans;
|
ALuint mSrcStep{};
|
||||||
DevFmtChannels mDstChans;
|
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;
|
void convert(const void *src, float *dst, ALuint frames) const;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user