I'm trying to use NAudio to make a client to simulate a softphone to send telephony RTP packets in g.711 MuLaw format by capturing the local microphone/speaker devices, but this process is missing some steps that don't make sense with the outdated information available. MuLaw is incompatible with MediaFoundationResampler and WdlResampler, the ACM resampler garbles the audio quality completely, and the below code gets me to PCM but from there there's no information on how to go forward. Is there supposed to be a low-pass filter or something added here? Are you supposed to be converting the raw byte data from the WasapiCapture event to 16-bit according to a 2013 article (which is incompatible with the MFR anyway)?
I have no knowledge or experience working with audio, so this entire process is foreign to me and need some direction on where to proceed from here, as the only closest answer didn't actually post how they "solved" it.
private static IWaveIn ActiveMicrophone = new WasapiCapture(ActiveMicrophoneDevice);
ActiveMicrophone.DataAvailable += OnMicrophoneDataAvailableAsync;
...
private async void OnMicrophoneDataAvailableAsync(object sender, WaveInEventArgs e)
{
MemoryStream micStream = new MemoryStream();
micStream.Write(e.Buffer, 0, e.BytesRecorded);
micStream.Position = 0;
var inputStream = new RawSourceWaveStream(micStream, ActiveMicrophone.WaveFormat);
WaveFormat outputFormat = new WaveFormat(8000, 8, 1);
using (var resampler = new MediaFoundationResampler(inputStream, outputFormat))
{
MemoryStream outputStream = new MemoryStream();
WaveFileWriter.WriteWavFileToStream(outputStream, resampler);
// Do something with outputStream?
}
}
After struggling with this library and hacking together several different recommendations from various sources, I ended up with something that finally worked, so hopefully this saves others a lot of headache.
I essentially had to roll my own IWaveProvider and do various custom filters and whatnot until it worked.
public class MuLawResamplerProvider : IWaveProvider
{
public WaveFormat WaveFormat => WaveFormat.CreateMuLawFormat(8000, 1);
private BufferedWaveProvider waveBuffer;
private IWaveProvider ieeeToPcm;
private byte[] sourceBuffer;
/// <summary>
/// Converts from 32-bit Ieee Floating-point format to MuLaw 8khz 8-bit 1 channel.
/// Used for WasapiCapture and WasapiLoopbackCapture.
/// </summary>
/// <param name="audio">The raw audio stream.</param>
/// <param name="inputFormat">The input format.</param>
public MuLawResamplerProvider(byte[] stream, WaveFormat inputFormat)
{
// Root buffer provider.
waveBuffer = new BufferedWaveProvider(inputFormat);
waveBuffer.DiscardOnBufferOverflow = false;
waveBuffer.ReadFully = false;
waveBuffer.AddSamples(stream, 0, stream.Length);
var sampleStream = new WaveToSampleProvider(waveBuffer);
// Stereo to mono filter.
var monoStream = new StereoToMonoSampleProvider(sampleStream)
{
LeftVolume = 2.0f,
RightVolume = 2.0f
};
// Downsample to 8000 filter.
var resamplingProvider = new WdlResamplingSampleProvider(monoStream, 8000);
// Convert to 16-bit in order to use ACM or MuLaw tools.
ieeeToPcm = new SampleToWaveProvider16(resamplingProvider);
sourceBuffer = new byte[ieeeToPcm.WaveFormat.AverageBytesPerSecond];
}
/// <summary>
/// Reset the buffer to the starting position with a new stream.
/// </summary>
/// <param name="stream">New stream to initialize.</param>
public void Reset(byte[] stream)
{
waveBuffer.ClearBuffer();
waveBuffer.AddSamples(stream, 0, stream.Length);
}
/// <summary>
/// Converts the 16-bit ACM stream to 8-bit MuLaw on read.
/// </summary>
/// <param name="destinationBuffer">The destination buffer to output into.</param>
/// <param name="offset">The offset to store at.</param>
/// <param name="readingCount">The requested size to read.</param>
/// <returns></returns>
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 = ieeeToPcm.Read(sourceBuffer, 0, 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;
}
}
And then you can do something as simple as this with it
var resampler = new MuLawResamplerProvider(deviceStream.ToArray(), ActiveWasapiCaptureDevice.WaveFormat);
// Write it out to a local file
string path = @"C:\Temp\" + Guid.NewGuid().ToString() + ".wav";
WaveFileWriter.CreateWaveFile(path, resampler);
resampler.Reset(deviceStream.ToArray());
// Write it into memory for other uses
MemoryStream outputStream = new MemoryStream();
WaveFileWriter.WriteWavFileToStream(outputStream, resampler);