Convert WasapiLoopbackCapture wav audio stream to MP3 file - c#

I am able to capture system audio which is generated by speaker with the help of WasapiLoopbackCapture (naudio). but the problem is it capture wav file and size of wav file very large (almost 10 to 15 MB/Min). I have to capture 2-3 hour audio and this will be too high.
I m looking for a solution that will convert wav stream which is capture by WasapiLoopbackCapture convert to MP3 and then save this to disk. I try a loat with LAME.exe or other solution but not get success. Any working code.
Here is My Code:
private void button1_Click(object sender, EventArgs e){
LoopbackRecorder obj = new LoopbackRecorder();
string a = textBox1.Text;
obj.StartRecording(#"e:\aman.mp3");
}
public class LoopbackRecorder
{
private IWaveIn _waveIn;
private Mp3WaveFormat _mp3format;
private WaveFormat _wavFormat;
private WaveFileWriter _writer;
private bool _isRecording = false;
/// <summary>
/// Constructor
/// </summary>
public LoopbackRecorder()
{
}
/// <summary>
/// Starts the recording.
/// </summary>
/// <param name="fileName"></param>
public void StartRecording(string fileName)
{
// If we are currently record then go ahead and exit out.
if (_isRecording == true)
{
return;
}
_fileName = fileName;
_waveIn = new WasapiLoopbackCapture();
// _waveIn.WaveFormat = new WaveFormat(16000, 16 , 2);
_writer = new WaveFileWriter(fileName, _waveIn.WaveFormat);
_waveIn.DataAvailable += OnDataAvailable;
// _waveIn.RecordingStopped += OnRecordingStopped;
_waveIn.StartRecording();
_isRecording = true;
}
private void OnDataAvailable(object sender, WaveInEventArgs waveInEventArgs)
{
if (_writer == null)
{
_writer = new WaveFileWriter(#"e:\aman.mp3", _waveIn.WaveFormat);
}
_writer.Write(waveInEventArgs.Buffer, 0, waveInEventArgs.BytesRecorded);
byte[] by= Float32toInt16(waveInEventArgs.Buffer, 0, waveInEventArgs.BytesRecorded);
}
private string _fileName = "";
/// <summary>
/// The name of the file that was set when StartRecording was called. E.g. the current file being written to.
/// </summary>
public string FileName
{
get
{
return _fileName;
}
}
}

Here's an example of using NAudio.Lame (in a console application) to capture data from sound card loopback and write direct to an MP3 file:
using System;
using NAudio.Lame;
using NAudio.Wave;
namespace MP3Rec
{
class Program
{
static LameMP3FileWriter wri;
static bool stopped = false;
static void Main(string[] args)
{
// Start recording from loopback
IWaveIn waveIn = new WasapiLoopbackCapture();
waveIn.DataAvailable += waveIn_DataAvailable;
waveIn.RecordingStopped += waveIn_RecordingStopped;
// Setup MP3 writer to output at 32kbit/sec (~2 minutes per MB)
wri = new LameMP3FileWriter( #"C:\temp\test_output.mp3", waveIn.WaveFormat, 32 );
waveIn.StartRecording();
stopped = false;
// Keep recording until Escape key pressed
while (!stopped)
{
if (Console.KeyAvailable)
{
var key = Console.ReadKey(true);
if (key != null && key.Key == ConsoleKey.Escape)
waveIn.StopRecording();
}
else
System.Threading.Thread.Sleep(50);
}
// flush output to finish MP3 file correctly
wri.Flush();
// Dispose of objects
waveIn.Dispose();
wri.Dispose();
}
static void waveIn_RecordingStopped(object sender, StoppedEventArgs e)
{
// signal that recording has finished
stopped = true;
}
static void waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
// write recorded data to MP3 writer
if (wri != null)
wri.Write(e.Buffer, 0, e.BytesRecorded);
}
}
}
At the moment the NAudio.Lame package on NuGet is compiled for x86 only, so make sure your application is set to target that platform.

To convert the audio to MP3 on the fly, one of the easiest ways is to use the command line options that let you pass audio into LAME.exe via stdin. I describe how to do that in this article.
You may also be interested that Corey Murtagh has created a LAME package for NAudio. I haven't tried it myself, but it looks like it should also do the job. Documentation is here.

Related

How to Record silence without MIC

I have a desktop application in C# that records audio from MIC using NAudio library. The application starts recording from default input device if MIC is connected to the system. The output wave file is saved.
I want to record silent to the output file if MIC is not connected, How can I feed a dummy MIC or virtual MIC to starts recording from if real mic is not connected.
Thanks
Here's the Code:
class AudioRecorder
{
private WaveIn waveIn = null;
private WaveFileWriter waveWriter = null;
public AudioRecorder()
{
if (WaveIn.DeviceCount > 0) //Means there is a mic
{
waveIn = new WaveIn();
waveIn.DeviceNumber = 0; //default device
waveIn.DataAvailable += WaveIn_DataAvailable;
}
else
{
//I want to add the virtual mic that will send only silent signal
}
}
public void Start()
{
waveIn.StartRecording();
}
public void Stop()
{
waveIn.StopRecording();
}
private void WaveIn_DataAvailable(object sender, WaveInEventArgs e)
{
if (waveWriter == null)
waveWriter = new WaveFileWriter("output.wav", new WaveFormat(44100, 2));
waveWriter.Write(e.Buffer, 0, e.BytesRecorded);
}
}

System audio to Skype using Naudio

I've been at this for a while, but made no progress. So this is my last resort!
I am trying to send the system-audio (the audio I hear in my headphones) to Skype (making the persons in my call hear what I hear basically). And I thought I would do this using the Skype4comlib and naudio.
What I've done is to create a class which uses the WasapiLoopbackCapture and WaveFileWriter to write temporary data to a .wav file, and redirect audio using the SkypeSystemAudio.set_InputDevice method. But when I'm talking to somebody and I try to start recording, the person doesn't hear me anymore. I just go completely quiet and no sound is being played to the person.
I thought it would be best if I posted the whole class since it's easier to understand everything.
public class SkypeSystemAudio
{
public NAudio.Wave.WasapiLoopbackCapture capture;
NAudio.CoreAudioApi.MMDevice device;
NAudio.Wave.WaveFileWriter writer;
private Call CurrentCall = null;
private Skype SkypeApplet;
private const int SkypeProtocol = 9;
private bool IsRecording = false;
public string tempfilepath = System.IO.Directory.GetCurrentDirectory() + #"\temp.wav";
#region Public
public void Initialize()
{
device = NAudio.Wave.WasapiLoopbackCapture.GetDefaultLoopbackCaptureDevice();
Init();
}
public void Initialize(NAudio.CoreAudioApi.MMDevice device)
{
this.device = device;
Init();
}
public void StartRecording()
{
capture.StartRecording();
if (CurrentCall != null)
{
CurrentCall.set_OutputDevice(TCallIoDeviceType.callIoDeviceTypeFile, tempfilepath);
IsRecording = true;
}
}
public void StopRecording()
{
capture.StopRecording();
if (CurrentCall != null)
{
CurrentCall.set_OutputDevice(TCallIoDeviceType.callIoDeviceTypeFile, "");
}
}
#endregion
private void Init()
{
capture = new WasapiLoopbackCapture(device);
capture.ShareMode = NAudio.CoreAudioApi.AudioClientShareMode.Shared;
capture.DataAvailable += capture_DataAvailable;
capture.RecordingStopped += capture_RecordingStopped;
WaveFormat format = new WaveFormat(16000, 1); // skype wants 16 Bit samples, 16khz, mono WAV file
//tried using the standard waveformat in the device object too. Didn't work though.
writer = new WaveFileWriter(tempfilepath, format );
SkypeApplet = new Skype();
SkypeApplet.Attach(SkypeProtocol, true);
SkypeApplet.CallStatus += SkypeApplet_CallStatus;
}
void SkypeApplet_CallStatus(Call pCall, TCallStatus Status)
{
if (Status == TCallStatus.clsRinging)
{
CurrentCall = pCall;
pCall.Answer();
}
}
void capture_DataAvailable(object sender, WaveInEventArgs e)
{
if (writer != null)
writer.Write(e.Buffer, 0, e.BytesRecorded);
}
void capture_RecordingStopped(object sender, StoppedEventArgs e)
{
IsRecording = false;
}
}
Does anyone know why this isn't working? I have no clues anymore what to do next.
Any help will greatly appreciated!
I have actually done some something similar, but not using Skype4COM.
What I did was using "Virtual Cables" just like Sebastian L suggested, this way you can control whats going in and out off skype, the downside is that you need to install the virtual cables and configure Skype to use them.
The cables will appear in audio devices as standard input/output.
I have used these cables VAC and VB cable
Hope it helps.

Naudio record microphone for X amount of seconds

Currently I can record the microphone input using the Naudio .dll with this:
public void recordInput()
{
Console.WriteLine("Now recording...");
waveSource = new WaveIn();
waveSource.WaveFormat = new WaveFormat(16000, 1);
waveSource.DataAvailable += new EventHandler<WaveInEventArgs>(waveSource_DataAvailable);
waveSource.RecordingStopped += new EventHandler<StoppedEventArgs>(waveSource_RecordingStopped);
//string tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".wav");
string tempFile = Path.Combine(#"C:\Users\Nick\Desktop", "test.wav");
waveFile = new WaveFileWriter(tempFile, waveSource.WaveFormat);
waveSource.StartRecording();
myTimer.Interval = 5000;
myTimer.Tick += new EventHandler(myTimer_Tick);
myTimer.Start();
}
Currently I used an eventhandler to wait for 5 seconds and then onTick I stop recording. However this is causing problems because I am having trouble waiting for the recording task to finish in the main part of the code where I call:
public string voiceToText()
{
recordInput();
//Somehow wait here until record is done then proceed.
convertToFlac();
return "done";
}
I've tried putting the Naudio in a thread and using a waitOne(); but waveSource.StartRecording(); is put in an eventhandler and thread an expection. I've also tried using a Thread.Sleep(5000) and stop recording after that thread finishes but the audio only records the first 500mS of audio for some reason.
I'm pretty new to c# and don't fully understand threading so any help or seperate approach is welcome.
I know this is an old topic, but here is some code that I made for one of my projects and that records audio for x seconds (it uses NAudio.Lame to convert the wave file to mp3):
public class Recorder
{
/// <summary>
/// Timer used to start/stop recording
/// </summary>
private Timer _timer;
private WaveInEvent _waveSource;
private WaveFileWriter _waveWriter;
private string _filename;
private string _tempFilename;
public event EventHandler RecordingFinished;
/// <summary>
/// Record from the mic
/// </summary>
/// <param name="seconds">Duration in seconds</param>
/// <param name="filename">Output file name</param>
public void RecordAudio(int seconds, string filename)
{
/*if the filename is empty, throw an exception*/
if (string.IsNullOrEmpty(filename))
throw new ArgumentNullException("The file name cannot be empty.");
/*if the recording duration is not > 0, throw an exception*/
if (seconds <= 0)
throw new ArgumentNullException("The recording duration must be a positive integer.");
_filename = filename;
_tempFilename = $"{Path.GetFileNameWithoutExtension(filename)}.wav";
_waveSource = new WaveInEvent
{
WaveFormat = new WaveFormat(44100, 1)
};
_waveSource.DataAvailable += DataAvailable;
_waveSource.RecordingStopped += RecordingStopped;
_waveWriter = new WaveFileWriter(_tempFilename, _waveSource.WaveFormat);
/*Start the timer that will mark the recording end*/
/*We multiply by 1000 because the Timer object works with milliseconds*/
_timer = new Timer(seconds * 1000);
/*if the timer elapses don't reset it, stop it instead*/
_timer.AutoReset = false;
/*Callback that will be executed once the recording duration has elapsed*/
_timer.Elapsed += StopRecording;
/*Start recording the audio*/
_waveSource.StartRecording();
/*Start the timer*/
_timer.Start();
}
/// <summary>
/// Callback that will be executed once the recording duration has elapsed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void StopRecording(object sender, ElapsedEventArgs e)
{
/*Stop the timer*/
_timer?.Stop();
/*Destroy/Dispose of the timer to free memory*/
_timer?.Dispose();
/*Stop the audio recording*/
_waveSource.StopRecording();
}
/// <summary>
/// Callback executed when the recording is stopped
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RecordingStopped(object sender, StoppedEventArgs e)
{
_waveSource.DataAvailable -= DataAvailable;
_waveSource.RecordingStopped -= RecordingStopped;
_waveSource?.Dispose();
_waveWriter?.Dispose();
/*Convert the recorded file to MP3*/
ConvertWaveToMp3(_tempFilename, _filename);
/*Send notification that the recording is complete*/
RecordingFinished?.Invoke(this, null);
}
/// <summary>
/// Callback executed when new data is available
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DataAvailable(object sender, WaveInEventArgs e)
{
if (_waveWriter != null)
{
_waveWriter.Write(e.Buffer, 0, e.BytesRecorded);
_waveWriter.Flush();
}
}
/// <summary>
/// Converts the recorded WAV file to MP3
/// </summary>
private void ConvertWaveToMp3(string source, string destination)
{
using (var waveStream = new WaveFileReader(source))
using(var fileWriter = new LameMP3FileWriter(destination, waveStream.WaveFormat, 128))
{
waveStream.CopyTo(fileWriter);
waveStream.Flush();
}
/*Delete the temporary WAV file*/
File.Delete(source);
}
}
And here is how to use it in your code:
var rec = new Recorder();
/*This line allows us to be notified when the recording is complete and the callback 'OnRecordingFinished' will be executed*/
rec.RecordingFinished += OnRecordingFinished;
rec.RecordAudio(seconds, recPath);
private void OnRecordingFinished(object sender, RecordingContentArgs e)
{
//Put your code here to process the audio file
}
If you aren't running in a Windows Forms or WPF application, then you should use WaveInEvent instead, so that a background thread is set up to handle the callbacks. The default constructor for WaveIn uses Windows messages.

AxWindowsMediaPlayer Clip Will Sometimes Not Play

I have a simple one-threaded windows forms .NET 4.5 app where user listens to spoken words (wav files) and then selects the correct picture that represents the word.
The problem is that the clip will sometimes (very rarely - about 1% of the time and completelly at random) not play...
This is the method for playing clips:
public static void PlayWordAudio(Word word, AxWMPLib.AxWindowsMediaPlayer player)
{
string tempFile = Path.GetTempFileName() + ".wav";
MemoryStream stream = new MemoryStream(word.Audio);
using (Stream fileStream = File.OpenWrite(tempFile))
{
stream.WriteTo(fileStream);
}
player.URL = tempFile;
File.Delete(tempFile);
}
Can someone please suggest a solution to this problem? Maybe I shouldn't delete the file at the end of the method? But then temp files would pile up...
I am on Windows 7...
I guess the file is being deleted quicker than it can get played.
Can you try this in stead of File.Delete(tempFile); utilizing the PlayStateChange event
player.PlayStateChange += (snd, psce) => {
switch (psce.newState)
{
case 1: // Stopped (maybe use 12 => Last )
File.Delete(tempFile);
break;
default:
Debug.WriteLine(psce.newState);
break;
}
};
You might have to unsubscribe the event if you keep the player object around a long time.
It seems that I solved the problem... it was in fact the deletion of file that caused this...
solution:
public static void PlayWordAudio(Word word, AxWMPLib.AxWindowsMediaPlayer player)
{
string tempFile = Path.GetTempFileName() + ".wav";
MemoryStream stream = new MemoryStream(word.Audio);
using (Stream fileStream = File.OpenWrite(tempFile))
{
stream.WriteTo(fileStream);
}
player.URL = tempFile;
RunDelayed(5000, File.Delete, tempFile); //if we delete file immediately then clip sometimes would not be played
}
public delegate void DelayedFuncion(string param);
public static void RunDelayed(int delay, DelayedFuncion function, string param = null)
{
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
DelayedArgs args = new DelayedArgs() { delayedFunction = function, param = param };
timer.Tag = args;
timer.Tick += TimerElapsed;
timer.Interval = delay;
timer.Start();
}
private static void TimerElapsed(object sender, EventArgs e)
{
System.Windows.Forms.Timer timer = sender as System.Windows.Forms.Timer;
timer.Stop();
DelayedArgs args = timer.Tag as DelayedArgs;
args.delayedFunction(args.param);
}
class DelayedArgs
{
public Util.DelayedFuncion delayedFunction;
public string param;
}

NAudio.MmException

I'm experimenting on how to play mp3 using Naudio. My simple app has one windows form and one button to play/pause the music. The app however has two major problem:
While it was intended that if the music is playing and the play button is pressed, the app should stop playing. Instead when the button is re-pressed, the app restart the music and then (sometime) throw an exception
If the button is pressed two or three times (and without any delay) ,the app throw a NAudio.MmException (Message=InvalidParameter calling acmStreamClose)
Can someone tell me what's wrong with my code? Below is my code:
using System;
using System.Windows.Forms;
namespace NaudioTesting
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private NAudio.Wave.BlockAlignReductionStream stream = null;
private NAudio.Wave.DirectSoundOut output = null;
public void LoadFile(string filePath)
{
DisposeWave();
if (filePath.EndsWith(".mp3"))
{
NAudio.Wave.WaveStream pcm =
NAudio.Wave.WaveFormatConversionStream.CreatePcmStream(new NAudio.Wave.Mp3FileReader(filePath));
stream = new NAudio.Wave.BlockAlignReductionStream(pcm);
}
else if (filePath.EndsWith(".wav"))
{
NAudio.Wave.WaveStream pcm = new NAudio.Wave.WaveChannel32(new NAudio.Wave.WaveFileReader(filePath));
stream = new NAudio.Wave.BlockAlignReductionStream(pcm);
}
else throw new InvalidOperationException("Not a correct audio file type.");
output = new NAudio.Wave.DirectSoundOut();
output.Init(stream);
output.Play();
}
private void playPauseButton_Click(object sender, EventArgs e)
{
string filePath = "GetLoud.mp3";
LoadFile(filePath);
if (output != null)
{
if (output.PlaybackState == NAudio.Wave.PlaybackState.Playing) output.Pause();
else if (output.PlaybackState == NAudio.Wave.PlaybackState.Paused) output.Play();
}
}
private void DisposeWave()
{
try
{
if (output != null)
{
if (output.PlaybackState == NAudio.Wave.PlaybackState.Playing) output.Stop();
output.Dispose();
output = null;
}
if (stream != null)
{
stream.Dispose();
stream = null;
}
}
catch (NAudio.MmException)
{
throw;
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
DisposeWave();
}
}
}
Looking at the DirectSoundOut source, the implementation for Play and Pause doesn't support resuming. Namely, what happens to you is exactly what it should. Calling play will always start from begining of the stream.
You should use WaveOut instead. It supports resuming by calling Play again, just like what you have in your code.
output = new NAudio.Wave.WaveOut();

Categories

Resources