Search code examples
c#naudiopcmwasapimu-law

Capture audio from WasapiLoopbackCapture, and convert to muLaw


I'm capturing audio with WasapiLoopbackCapture

- format        = IeeeFloat
- SampleRate    = 48000
- BitsPerSample = 32

I need to convert this to muLaw (8Khz, 8 bit, mono) - eventually it'll be sent to a phone via SIP trunking. I've tried 100s of samples (most of them with NAudio) and solutions but still have no clue how to do this ...


Solution

  • The Mu-Law tools in NAudio are limited so you might have to roll your own.

    You'll need to set up a chain of IWaveProvider filters to convert to mono, change bit-rate, and change bit-depth.

    waveBuffer = new BufferedWaveProvider(waveIn.WaveFormat);
    waveBuffer.DiscardOnBufferOverflow = true;
    waveBuffer.ReadFully = false;  // leave a buffer?
    
    sampleStream = new WaveToSampleProvider(waveBuffer);
    
    // Stereo to mono
    monoStream = new StereoToMonoSampleProvider(sampleStream)
        {
            LeftVolume = 1f,
            RightVolume = 1f
        };
    
    // Downsample to 8000
    resamplingProvider = new WdlResamplingSampleProvider(monoStream, 8000);
    
    // Convert to 16-bit in order to use ACM or MuLaw tools.
    ieeeToPcm = new SampleToWaveProvider16(resamplingProvider);
    

    Then create a custom IWaveProvider for the next step.

    // In MuLawConversionProvider
    public int Read(byte[] destinationBuffer, int offset, int readingCount)
    {
        // Source buffer has twice as many items as the output array.
        var sizeOfPcmBuffer = readingCount * 2;
        _sourceBuffer = BufferHelpers.Ensure(_sourceBuffer, sizeOfPcmBuffer);
        var sourceBytesRead = _sourceProvider.Read(_sourceBuffer, offset * 2, sizeOfPcmBuffer);
        var samplesRead = sourceBytesRead / 2;
    
        var outIndex = 0;
        for (var n = 0; n < sizeOfPcmBuffer; n += 2)
        {
            destinationBuffer[outIndex++] = MuLawEncoder.LinearToMuLawSample(BitConverter.ToInt16(_sourceBuffer, offset + n));
        }
    
        return samplesRead * 2;
    }
    

    The new provider can be sent directly to WaveOut

    outputStream = new MuLawConversionProvider(ieeeToPcm);
    waveOut.Init(outputStream);
    waveOut.Play();
    

    These filters remain in place with the BufferedWaveProvider as the "root". Whenever you call BufferedWaveProvider.AddSamples(), the data will go through all these filters.