Author Topic: WASAPI and Recording Volume Adjustment  (Read 573 times)

VorTechS

  • Posts: 295
WASAPI and Recording Volume Adjustment
« on: 3 Aug '23 - 12:11 »
Hi all,

Long time no see!  After leaving things settled for a while, I'm working on my audio broadcasting tool where I'm battling an issue with Windows Volume on a Microphone Input, which is receiving non-voice content from a mixer, and tweaking the streamed result.  Up until now, I've just been tweaking the volume of the Mixer output but it doesn't seem granular enough.

Before changing anything my code setup is:

- WASAPI Input Channel (a BASSWASAPIHandler taking the recording input)
- DSP Stream Buffer (a DSP_BufferStream monitoring the recording input)
- Various output channels receiving the BASSWASAPIHandler Input Channel

...and this is working prefectly.

Now I want to introduce a volume control to increase / reduce the input volume before it hits any outputs, and I'm struggling to get a consistent result.
So I'm aware that I need to use BASS_ChannelSetFX, like so:

_bassFXChannel = Bass.BASS_ChannelSetFX(_currentRecordingChannel, BASSFXType.BASS_FX_VOLUME, 0);

(_currentRecordingChannel being the BASSWASAPIHandler .InputChannel)

...but quite often this seems to result in a random error being thrown: 3221226356 (0xc0000374) which can't be handled inside a managed environment (Visual Studio).

IF it does happen to work, then most of the time when I try to use BASS_FXSetParameters setting a volume (between 0.1 and 1.0) on the FX channel, I get 'BASS_ERROR_ILLPARAM'.
(For context I'm using a slider with values 0 - 100, which is used in the SetInputVolume function below)

My first question is: 

Am I, from a technical stance, doing it right by trying to apply the FX on the Input channel?

I've tried using the BufferStream (which is just the input channel anyway) and the InputHandler.OutputChannel all with the same results.  The minute I remove the BASS_ChannelSetFX calls, the application behaves as it did. 
Which suggests that I am doing something wrong....

The ultimate aim (for me right now) is to:

- Set the Windows Microphone Volume to 100% (not code related)
- Reduce the Microphone Input volume within the application, which is reflected in any other channels using that input
- Monitor the result of reducing the Microphone Volume

Any and all help is gratefully received!

Code: [Select]
        internal async void CreateWasapiDevice(List<OutputPlugin> outputTargets)
        {
            ConfigurationsHelper.DeviceType sourceType = ConfigurationsHelper.DeviceType.Unknown;

            int deviceIndex = ConfigurationsHelper.GetWasapiDeviceIndex(_sourceDevice, out sourceType);

            bool streamInit = false;

            streamInit = Bass.BASS_Init(0, 48000, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero);
            var loopBackInit = Bass.BASS_Init(-1, 48000, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero);
            var wasapiInit = BassWasapi.BASS_WASAPI_Init(0, 44100, 2, BASSWASAPIInit.BASS_WASAPI_AUTOFORMAT, 0f, 0f, null, IntPtr.Zero);

            if (_sourceDevice.IsLoopback)
            {
                _wasapiInputHandler = new BassWasapiHandler(deviceIndex, false, _sourceDevice.mixfreq, _sourceDevice.mixchans, 0f, 0f);
            }
            else
            {
                _wasapiInputHandler = new BassWasapiHandler(deviceIndex, false, true, _sourceDevice.mixfreq, _sourceDevice.mixchans, 0f, 0f);
            }

            // Define a WASAPI out channel handler
            _wasapiOutputHandler = new BassWasapiHandler(deviceIndex, false, 48000, 2, 0f, 0f);

            // Create a buffer for analyzing when needed
            _wasapiStreamBuffer = new DSP_BufferStream();

            // setup a full-duplex stream
            _wasapiInputHandler.SetFullDuplex(0, BASSFlag.BASS_STREAM_DECODE, false);

            if (outputTargets != null)
            {
                foreach (IOutputPlugin outputTarget in outputTargets)
                {
                    if (outputTarget is IExtendedPlugin)
                    {
                        ((IExtendedPlugin) outputTarget).WasapiStreamHandle = _wasapiInputHandler.InputChannel;
                    }

                    outputTarget.StreamChannel = _wasapiInputHandler.OutputChannel;
                }
            }
        }


Code: [Select]
        internal async void StartInputStream(List<OutputPlugin> outputTargets)
        {
            if (_sourceDevice != null)
            {
                await StopInputStream();
            }

            lock (_wasapiInputLock)
            {
                _isReadingCancelled = false;
                _onChannelReceiveDataProc = new DSPPROC(OnChannelDataReceived);

                CreateWasapiDevice(outputTargets);

                if (_wasapiInputHandler.Init())
                {
                    _wasapiStreamBuffer.ChannelHandle = _wasapiInputHandler.InputChannel;

                    _wasapiStreamBuffer.Start();

                    _currentRecordingChannel = _wasapiInputHandler.InputChannel;

                    //Start the input / output streams
                    _wasapiOutputHandler.Init();
                    _wasapiOutputHandler.Start();

                    _wasapiInputHandler.Start();

                    _channelDSP = Bass.BASS_ChannelSetDSP(_currentRecordingChannel, _onChannelReceiveDataProc, IntPtr.Zero, 0);
                    _bassFXChannel = Bass.BASS_ChannelSetFX(_currentRecordingChannel, BASSFXType.BASS_FX_VOLUME, 0);

                    if (_bassFXChannel == 0)
                    {
                        var fxErrorCode = Bass.BASS_ErrorGetCode();
                    }

                    SetInputVolume(_inputVolume);
                }
                else
                {
                    var initErrorCode = Bass.BASS_ErrorGetCode();
                }

                if (_currentRecordingChannel != 0)
                {
                    Bass.BASS_ChannelPlay(_currentRecordingChannel, true);
                }
            }
        }

Code: [Select]
        public void SetInputVolume(int volume)
        {
            // Find the Global volume
            Un4seen.Bass.AddOn.Fx.BASS_BFX_VOLUME volumeParam = new Un4seen.Bass.AddOn.Fx.BASS_BFX_VOLUME()
            {
                lChannel = 0
            };

            if (_bassFXChannel != 0)
            {
                if (Bass.BASS_FXGetParameters(_bassFXChannel, volumeParam))
                {
                    float newVolume = (float)volume / 100;

                    volumeParam.fVolume = newVolume;

                    var wasSet = Bass.BASS_FXSetParameters(_bassFXChannel, volumeParam);

                    if (!wasSet)
                    {
                        var setErrorCode = Bass.BASS_ErrorGetCode();
                    }
                }
            }
        }


Code: [Select]
        private void OnChannelDataReceived(int handle, int channel, IntPtr buffer, int length, IntPtr user)
        {
            if (length > 0)
            {
                ProcessInput(channel, buffer, length);
            }
            else
            {
                _onVULevelsReceived?.Invoke(new VULevel());
            }
        }

        private void ProcessInput(int channel, IntPtr buffer, int length)
        {
            int levelData = Bass.BASS_ChannelGetLevel((_wasapiInputHandler != null) ? _wasapiStreamBuffer.BufferStream : _currentRecordingChannel);
            int leftLevel = Utils.LowWord32(levelData);
            int rightLevel = Utils.HighWord32(levelData);

            _onVULevelsReceived?.BeginInvoke(new VULevel() {LeftChannel = leftLevel, RightChannel = rightLevel}, null, null);

            //foreach (DSP_BufferStream splitStream in _splitStreams)
            //{
            //    splitStream.DSPProc(splitStream.OutputHandle, channel, buffer, length, IntPtr.Zero);
            //}
        }

Code: [Select]
        internal async Task StopInputStream()
        {
            lock (_wasapiInputLock)
            {
                bool wasBufferStopped = false;
                bool wasInputStopped = false;
                bool wasOutputStopped = false;
                bool wasRecordingDSPRemoved = false;
                bool wasBassFxRemoved = false;

                if (_currentRecordingChannel != 0)
                {
                    if (_bassFXChannel != 0)
                    {
                        wasBassFxRemoved = Bass.BASS_ChannelRemoveFX(_currentRecordingChannel, _bassFXChannel);
                    }

                    if (_channelDSP != 0)
                    {
                        wasRecordingDSPRemoved = Bass.BASS_ChannelRemoveDSP(_currentRecordingChannel, _channelDSP);
                    }
                }

                if (_wasapiInputHandler != null)
                {
                    wasInputStopped = _wasapiInputHandler.Stop();

                    if (wasInputStopped)
                    {
                        _wasapiInputHandler.Dispose();
                    }
                }

                if (_wasapiStreamBuffer != null)
                {
                    wasBufferStopped = _wasapiStreamBuffer.Stop();

                    if (wasBufferStopped)
                    {
                        _wasapiStreamBuffer.Dispose();
                    }
                }

                if (_wasapiOutputHandler != null)
                {
                    wasOutputStopped = _wasapiOutputHandler.Stop();
                    if (wasOutputStopped)
                    {
                        _wasapiOutputHandler.Dispose();
                    }
                }
                _onChannelReceiveDataProc = null;
                _isReadingCancelled = true;
            }

            Bass.BASS_RecordFree();

            _currentRecordingChannel = 0;

            _onVULevelsReceived?.Invoke(new VULevel() { LeftChannel=0, RightChannel=0});
        }

Ian @ un4seen

  • Administrator
  • Posts: 26047
Re: WASAPI and Recording Volume Adjustment
« Reply #1 on: 3 Aug '23 - 16:55 »
Welcome back!

Now I want to introduce a volume control to increase / reduce the input volume before it hits any outputs, and I'm struggling to get a consistent result.
So I'm aware that I need to use BASS_ChannelSetFX, like so:

_bassFXChannel = Bass.BASS_ChannelSetFX(_currentRecordingChannel, BASSFXType.BASS_FX_VOLUME, 0);

(_currentRecordingChannel being the BASSWASAPIHandler .InputChannel)

...but quite often this seems to result in a random error being thrown: 3221226356 (0xc0000374) which can't be handled inside a managed environment (Visual Studio).

An 0xc0000374 exception means heap corruption, typically caused by writing beyond the end of an allocated memory block. If you enable mixed-mode debugging in VS, then it should show where the problem was detected, but the actual cause of the problem will be somewhere before that.

IF it does happen to work, then most of the time when I try to use BASS_FXSetParameters setting a volume (between 0.1 and 1.0) on the FX channel, I get 'BASS_ERROR_ILLPARAM'.
(For context I'm using a slider with values 0 - 100, which is used in the SetInputVolume function below)

In the posted code, it looks like you're currently using the wrong parameter class for the BASS_FX_VOLUME effect. It should be BASS_FX_VOLUME_PARAM rather than BASS_BFX_VOLUME. Using the wrong class in a BASS_FXGetParameters call could be what's causing the heap corruption (0xc0000374 exception).

Since BASS 2.4.17, there is actually a simpler way to adjust the level if you only need instant changes (not transitions). The BASS_ATTRIB_VOLDSP option, which you can use like this:

Code: [Select]
BASS_ChannelSetAttribute(handle, BASS_ATTRIB_VOLDSP, volume);

VorTechS

  • Posts: 295
Re: WASAPI and Recording Volume Adjustment
« Reply #2 on: 4 Aug '23 - 10:19 »
Thanks for that Ian!  It makes sense there'd be a corruption if a completely different object is being populated and passed around!

I'll re-visit the code over the next few days and see if it starts behaving as I'd expect with the correct object being used!

Much appreciated!

VorTechS

  • Posts: 295
Re: WASAPI and Recording Volume Adjustment
« Reply #3 on: 7 Aug '23 - 09:45 »
As you say, I suspect a trail of BASS_ChannelSetFX and BASS_ChannelRemoveFX calls along with BASS_FXSetParameters/BASS_FXGetParameters was probably at fault when using the wrong structure, unsurprisingly when using the correct structure all the crashes have miraculously dissapeared! ;)

After that, I managed to follow my code through and worked out that whilst volume changes were being applied to the BassFX Channel, I wasn't actually monitoring the correct channel to see any affected changes.  So for anyone using BassWASAPIHandler wanting to achieve similar results, make sure to:

- Set an FX channel on the handlers OutputChannel
- Use BASS_FXSetParameters / BASS_FXGetParameters on the returned FX Channel
- Use BASS_ChannelGetLevel on the handlers OutputChannel to monitor the output levels if you want to display those levels

...and you'll visually get what you are expecting!

Thanks Ian!
« Last Edit: 7 Aug '23 - 11:41 by VorTechS »

VorTechS

  • Posts: 295
Re: WASAPI and Recording Volume Adjustment
« Reply #4 on: 7 Aug '23 - 10:13 »

Ian @ un4seen

  • Administrator
  • Posts: 26047
Re: WASAPI and Recording Volume Adjustment
« Reply #5 on: 7 Aug '23 - 11:53 »
Good to see you've got it all working now!

VorTechS

  • Posts: 295
Re: WASAPI and Recording Volume Adjustment
« Reply #6 on: 10 Aug '23 - 15:00 »
Ian it was a massive step forwards!

However, not all is well with the implementation!  I just did a broadcast stream and the resultant audio from the encoder doesn't sound good. 
It sounds like a combination of white noise and/or distortion is being introduced into the output.  It's very odd.  Volume reductions are being reflected in the encoded output though, and the associated additional noise volume appears to reduce too.

I checked the previous version (without the volume manipulation / additional monitoring) and it all sounds good.

Any ideas what may be causing that sort of issue?

This is what's happening code-wise:

Setting the input volume

Code: [Select]
        public void SetInputVolume(int volume)
        {
            if (_bassFXChannel != 0)
            {
                float newVolume = (float)volume / 100;

                _volumeParam.fTarget = newVolume;
                _volumeParam.fTime = 0;

                var wasSet = Bass.BASS_FXSetParameters(_bassFXChannel, _volumeParam);

                if (!wasSet)
                {
                    var setErrorCode = Bass.BASS_ErrorGetCode();
                }
            }
        }

Monitoring of the relevant Input / Output

Code: [Select]
        private void ProcessInput(int channel, IntPtr buffer, int length)
        {
            int inputLevelData = Bass.BASS_ChannelGetLevel(_wasapiInputHandler != null ? _wasapiStreamBuffer.BufferStream : _currentRecordingChannel);
            int outputLevelData = Bass.BASS_ChannelGetLevel(_wasapiInputHandler.OutputChannel);

            bool wasVolumeRetrieved = false;

            wasVolumeRetrieved = Bass.BASS_FXGetParameters(_bassFXChannel, _volumeParam);

            int outputLeftLevel = Utils.LowWord32(outputLevelData);
            int outputRightLevel = Utils.HighWord32(outputLevelData);

            int inputLeftLevel = Utils.LowWord32(inputLevelData);
            int inputRightLevel = Utils.HighWord32(inputLevelData);

            int volumePercent = 0;

            if (wasVolumeRetrieved)
            {
                volumePercent = (int)(_volumeParam.fCurrent * 100);
            }
            else
            {
                var errorCode = Bass.BASS_ErrorGetCode();
            }

            _onVULevelsReceived?.BeginInvoke(new VULevel() { LeftChannel = inputLeftLevel, RightChannel = inputRightLevel }, new VULevel() {LeftChannel = outputLeftLevel, RightChannel = outputRightLevel}, volumePercent, null, null);
        }

« Last Edit: 10 Aug '23 - 15:52 by VorTechS »

Ian @ un4seen

  • Administrator
  • Posts: 26047
Re: WASAPI and Recording Volume Adjustment
« Reply #7 on: 10 Aug '23 - 17:58 »
Is the noise definitely coming from the BASS_FX_VOLUME effect, ie. it doesn't happen if you just remove that and make no other changes? If so, is the noise still present if you keep the BASS_FX_VOLUME effect but leave the default parameters, ie. don't call BASS_FXSetParameters?

VorTechS

  • Posts: 295
Re: WASAPI and Recording Volume Adjustment
« Reply #8 on: 18 Aug '23 - 11:57 »
Sorry for the late reply, it's been a mad few weeks developing systems under a very tight schedule for clients (completely unrelated to BASS) so I have little free time at the moment!

I'm going to add a separate feature for enabling this volume adjustment, which should essentially run the code as it was before the change, to isolate whether it's something else I have done having an unexpected knock-on effect.  [The reality being the only other thing I have done from the original source, is removed the initialization of the Input from a separate configuration screen to the main UI ... but we all know how development has such knock-on effects!]

From there, I'll try and isolate whether it's specific calls interfering with the output going to other streams! 

It might take me a while!