The problem is that you're getting batches of data from the loopback capture interface in the default format (ie: PCM), then writing that to a wave file with a format block that claims that the data is in ALAW format. At no point do you actually do a conversion from the PCM data to ALAW data, resulting in a garbage file.
The WaveFileWriter
class doesn't do any form of recoding or resampling for you. It uses the format specifier to build a format block for the WAV file, and assumes that you are providing it with data in that format.
Your two options are:
Convert the incoming data from PCM-44100-Stereo (or whatever the default is) to ALAW-8000-Mono before writing to the
WaveFileWriter
instance.Initialize
_waveOutFile
with_waveOut.WaveFormat
to match the data formats.
Updated 26-Sep...
So after much messing around, I finally have a working solution to the original problem of correctly converting the wave format from the loopback capture into something that can be compressed.
Here's the code for the first stage of the conversion:
[StructLayout(LayoutKind.Explicit)]
internal struct UnionStruct
{
[FieldOffset(0)]
public byte[] bytes;
[FieldOffset(0)]
public float[] floats;
}
public static byte[] Float32toInt16(byte[] data, int offset, int length)
{
UnionStruct u = new UnionStruct();
int nSamples = length / 4;
if (offset == 0)
u.bytes = data;
else
{
u.bytes = new byte[nSamples * 4];
Buffer.BlockCopy(data, offset, u.bytes, 0, nSamples * 4);
}
byte[] res = new byte[nSamples * 2];
for (i = 0, o = 0; i < nSamples; i++, o+= 2)
{
short val = (short)(u.floats[i] * short.MaxValue);
res[o] = (byte)(val & 0xFF);
res[o + 1] = (byte)((val >> 8) & 0xFF);
}
u.bytes = null;
return res;
}
That will convert the 32-bit floating point samples to 16-bit signed integer samples that can be handled by most audio code. Fortunately, this includes the Yeti
MP3 code.
To encode on-the-fly and ensure that the MP3 output is valid, create the Mp3Writer
and its output Stream
(a FileStream
to write directly to disk for instance) at the same time and just keep feeding it data (run through the converter above) as it comes in from the loopback interface. Close the Mp3Writer
and the Stream
in the waveInStopRecording
event handler.
Stream _mp3Output;
Mp3Writer _mp3Writer;
private void InitAudioOut(DateTime recordMarker)
{
string pathOut = string.Format(@"C:\Rec\({0:HH-mm-ss dd-MM-yyyy} OUT).mp3", recordMarker);
_waveOut = new WasapiLoopbackCapture();
_waveOut.DataAvailable += WaveOutDataAvailable;
_waveOut.RecordingStopped += WaveOutRecordStopped;
_mp3Output = File.Create(pathIn);
var fmt = new WaveLib.WaveFormat(_waveOut.WaveFormat.SampleRate, 16, _waveOut.Channels);
var beconf = new Yeti.Lame.BE_CONFIG(fmt, 128);
_mp3Writer = new Mp3Writer(_mp3Stream, fmt, beconf);
_waveOut.StartRecording();
}
private void WaveOutDataAvailable(object sender, WaveInEventArgs e)
{
if (_mp3Writer != null)
{
byte[] data = Float32toInt16(e.Buffer, 0, e.BytesRecorded);
_mp3Writer.Write(data, 0, data.Length);
}
}
private void WaveOutRecordStopped(object sender, StoppedEventArgs e)
{
if (InvokeRequired)
BeginInvoke(new MethodInvoker(WaveOutStop));
else
WaveOutStop();
}
private void WaveOutStop()
{
if (_mp3Writer != null)
{
_mp3Writer.Close();
_mp3Writer.Dispose();
_mp3Writer = null;
}
if (_mp3Stream != null)
{
_mp3Stream.Dispose();
_mp3Stream = null;
}
_waveOut.Dispose();
_waveOut = null;
}
Incidentally, the Mp3Writer
class is all you need for this. Throw out the other Lame
code you've got there. It will just get in your way.