HardLuck HardLuck -4 years ago 266
C# Question

NAudio streaming to mp3 file throught yeti lame wrapper

I have the following code to record audio in and out

using System;
using System.Diagnostics;
using System.IO;
using NAudio.Wave;
using Yeti.MMedia.Mp3;

namespace SoundRecording
{
public class SoundManager
{
private WaveInEvent _waveIn;
private WaveFileWriter _waveInFile;
private WasapiLoopbackCapture _waveOut;
private WaveFileWriter _waveOutFile;
private Process _lameProcess;

public void StartRecording()
{
InitLame();
DateTime dtNow = DateTime.Now;

try
{
InitAudioOut(dtNow);
}
catch
{
}

try
{
InitAudioIn(dtNow);
}
catch
{
}
}

private void InitLame()
{
string outputFileName = @"c:\Rec\test.mp3";
_lameProcess = new Process();
_lameProcess.StartInfo.FileName = @"lame.exe";
_lameProcess.StartInfo.UseShellExecute = false;
_lameProcess.StartInfo.RedirectStandardInput = true;
_lameProcess.StartInfo.Arguments = "-r -s 44.1 -h -b 256 --bitwidth 32 - \"" + outputFileName + "\"";
_lameProcess.StartInfo.CreateNoWindow = true;
_lameProcess.Start();
}

private void InitAudioIn(DateTime dtNow)
{
string pathIn = @"C:\Rec\(" + dtNow.ToString("HH-mm-ss") + " " + dtNow.ToString("dd-MM-yyyy") + " IN).wav";

_waveIn = new WaveInEvent();
_waveIn.WaveFormat = new WaveFormat(8000, 1);
_waveIn.DataAvailable += WaveInDataAvailable;
_waveIn.RecordingStopped += WaveInRecordStopped;

_waveInFile = new WaveFileWriter(pathIn, _waveIn.WaveFormat);

_waveIn.StartRecording();
}

private void InitAudioOut(DateTime recordMarker)
{
string pathOut = @"C:\Rec\(" + recordMarker.ToString("HH-mm-ss") + " " + recordMarker.ToString("dd-MM-yyyy") + " OUT).mp3";

_waveOut = new WasapiLoopbackCapture();
//_waveOut.WaveFormat = new WaveFormat(44100, 1);
_waveOut.DataAvailable += WaveOutDataAvailable;
_waveOut.RecordingStopped += WaveOutRecordStopped;

_waveOutFile = new WaveFileWriter(pathOut, new Mp3WaveFormat(_waveOut.WaveFormat.SampleRate, _waveOut.WaveFormat.Channels, 0, 128));
_waveOut.StartRecording();
}

private void WaveInDataAvailable(object sender, WaveInEventArgs e)
{
if (_waveInFile != null)
{
_waveInFile.Write(e.Buffer, 0, e.BytesRecorded);
_waveInFile.Flush();
}
}

private void WaveOutDataAvailable(object sender, WaveInEventArgs e)
{
if (_waveInFile != null)
{
using (var memStream = new MemoryStream(e.Buffer))
{
using (WaveStream wStream = new RawSourceWaveStream(memStream, _waveOut.WaveFormat))
{
var format = new WaveFormat(_waveOut.WaveFormat.SampleRate, _waveOut.WaveFormat.Channels);
var transcodedStream = new ResamplerDmoStream(wStream, format);
var read = (int)transcodedStream.Length;
var bytes = new byte[read];
transcodedStream.Read(bytes, 0, read);

var fmt = new WaveLib.WaveFormat(transcodedStream.WaveFormat.SampleRate, transcodedStream.WaveFormat.BitsPerSample, transcodedStream.WaveFormat.Channels);
var beconf = new Yeti.Lame.BE_CONFIG(fmt, 128);

// Encode WAV to MP3
byte[] mp3Data;

using (var mp3Stream = new MemoryStream())
{
using (var mp3Writer = new Mp3Writer(mp3Stream, fmt, beconf))
{
int blen = transcodedStream.WaveFormat.AverageBytesPerSecond;

mp3Writer.Write(bytes, 0, read);
mp3Data = mp3Stream.ToArray();
}
}

_waveOutFile.Write(mp3Data, 0, mp3Data.Length);
_waveOutFile.Flush();
}
}
}
}

private byte[] WavBytesToMp3Bytes(IWaveProvider waveStream, uint bitrate = 128)
{
// Setup encoder configuration
var fmt = new WaveLib.WaveFormat(waveStream.WaveFormat.SampleRate, waveStream.WaveFormat.BitsPerSample, waveStream.WaveFormat.Channels);
var beconf = new Yeti.Lame.BE_CONFIG(fmt, bitrate);

// Encode WAV to MP3
int blen = waveStream.WaveFormat.AverageBytesPerSecond;
var buffer = new byte[blen];
byte[] mp3Data = null;

using (var mp3Stream = new MemoryStream())
{
using (var mp3Writer = new Mp3Writer(mp3Stream, fmt, beconf))
{
int readCount;

while ((readCount = waveStream.Read(buffer, 0, blen)) > 0)
{
mp3Writer.Write(buffer, 0, readCount);
}

mp3Data = mp3Stream.ToArray();
}
}
return mp3Data;
}

private void WaveInRecordStopped(object sender, StoppedEventArgs e)
{
if (_waveIn != null)
{
_waveIn.Dispose();
_waveIn = null;
}

if (_waveInFile != null)
{
_waveInFile.Dispose();
_waveInFile = null;
}

_lameProcess.StandardInput.BaseStream.Close();
_lameProcess.StandardInput.BaseStream.Dispose();

_lameProcess.Close();
_lameProcess.Dispose();
}

private void WaveOutRecordStopped(object sender, StoppedEventArgs e)
{
if (_waveOutFile != null)
{
_waveOutFile.Close();
_waveOutFile = null;
}

_waveOut = null;
}

public void StopRecording()
{
try
{
_waveIn.StopRecording();
}
catch
{
}

try
{
_waveOut.StopRecording();
}
catch
{
}
}
}
}


I'm using NAudio to capture audio in/out and yetis' lame wrapper to convert it to mp3 file on the fly, the problem is that the resulting audio out file is corrupted and unreadable, probably, missing mp3 headers or something other that i've missed...

Answer Source

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:

  1. Convert the incoming data from PCM-44100-Stereo (or whatever the default is) to ALAW-8000-Mono before writing to the WaveFileWriter instance.

  2. 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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download