Calling a function in another thread C# - c#

I don't know how to put this but I'll try my best.
I have a Windows form application which uses a webcam to take a photo of a user which works fine, I'm using the directshownet library found here http://directshownet.sourceforge.net/ and have used the DxSnap sample to take the photo.
The next part of the application uses an RFID tag reader that once a tag is scanned, it calls the take photo method. And this is where the problem is coming in, because the RFID listen method is run in a separate thread to the GUI thread, because it is an infinite while loop.
The class that is used to take the picture takes an instance of a picturebox control as a parameter for it's constructor, which is created in the main thread, and i think this is where the problem is.
Each part of the application works fine separately but when i try to call the method to take the photo from the listener thread all hell breaks loose and the app crashes.
Does anybody know how I could call a method of the object that is initialized in the main thread (that takes the photo) from the listener thread?
I've tried delegate and invoke, but can't figure it out as i don't want to manipulate the control directly, but rather let the object do it in the main thread.
Here's some code:
private Capture cam;
private int portIndex = -1;
private ArrayList AlreadyOpenPortList = new ArrayList();
private byte readerAddr = 0;
private Thread listenThread;
IntPtr m_ip = IntPtr.Zero;
public podiumForm()
{
InitializeComponent();
// scanner and camera startup
startCam();
openComs();
openRF();
startListening();
}
private void startListening()
{
listenThread = new Thread(new ThreadStart(this.Listen));
listenThread.Start();
}
/// <summary>
/// Method of retrieving code tag details from reader
/// </summary>
private void Listen()
{
int fCmdRet = 0x30;
byte state = 1;
byte AFI = 00;
byte[] DSFIDAndUID = new byte[9];
byte cardNumber = 0;
string strDSFIDAndUID = "";
byte outputSet;
if (!GetCurrentUsePort())
{
MessageBox.Show("Open ComPort, Please");
return;
}
while (true)
{
fCmdRet = StaticClassReaderA.Inventory(ref readerAddr, ref state, ref AFI, DSFIDAndUID, ref cardNumber, portIndex);
if (fCmdRet == 0)
{
outputSet = 0;
fCmdRet = StaticClassReaderA.SetGeneralOutput(ref readerAddr, ref outputSet, portIndex);
strDSFIDAndUID = ByteArrayToHexString(DSFIDAndUID).Replace(" ", "");
outputSet = 3;
fCmdRet = StaticClassReaderA.SetGeneralOutput(ref readerAddr, ref outputSet, portIndex);
SavePic(strDSFIDAndUID.Substring(2, 16));
//MessageBox.Show(strDSFIDAndUID.Substring(2, 16));
//ShutDown();
}
}
}
private void SavePic(string text)
{
Cursor.Current = Cursors.WaitCursor;
// Release any previous buffer
if (m_ip != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(m_ip);
m_ip = IntPtr.Zero;
}
// here's where it crashes
// capture image
m_ip = cam.Click();
Bitmap b = new Bitmap(cam.Width, cam.Height, cam.Stride, PixelFormat.Format24bppRgb, m_ip);
cam.Dispose();
// If the image is upsidedown
b.RotateFlip(RotateFlipType.RotateNoneFlipY);
pbPic.Image = b;
Cursor.Current = Cursors.Default;
MessageBox.Show("Here " + text);
}
private void startCam()
{
const int VIDEODEVICE = 0; // zero based index of video capture device to use
const int VIDEOWIDTH = 640; // Depends on video device caps
const int VIDEOHEIGHT = 480; // Depends on video device caps
const int VIDEOBITSPERPIXEL = 24; // BitsPerPixel values determined by device
cam = new Capture(VIDEODEVICE, VIDEOWIDTH, VIDEOHEIGHT, VIDEOBITSPERPIXEL, pbPic);
}
// method in capture class
public IntPtr Click()
{
int hr;
// get ready to wait for new image
m_PictureReady.Reset();
m_ipBuffer = Marshal.AllocCoTaskMem(Math.Abs(m_stride) * m_videoHeight);
try
{
m_WantOne = true;
// If we are using a still pin, ask for a picture
if (m_VidControl != null)
{
// CRASHES HERE with : System.InvalidCastException was unhandled
// Tell the camera to send an image
hr = m_VidControl.SetMode(m_pinStill, VideoControlFlags.Trigger);
DsError.ThrowExceptionForHR(hr);
}
// Start waiting
if (!m_PictureReady.WaitOne(9000, false))
{
throw new Exception("Timeout waiting to get picture");
}
}
catch
{
Marshal.FreeCoTaskMem(m_ipBuffer);
m_ipBuffer = IntPtr.Zero;
throw;
}
// Got one
return m_ipBuffer;
}

Take a look at the SynchronizationContext class; it allows you to dispatch work back on the main (UI) thread in a manner that is agnostic of how that mechanism actually works (and therefore works with WinForms and WPF). Therefore you don't need to have a reference to a Control to call Invoke on.
It is what BackgroundWorker uses behind the scenes (you may also be able to use BackgroundWorker, depending on what you're trying to do, which is slightly easier to work with).

Image image = (Image)pictureBox.Invoke ((Func<Image>) delegate { return pictureBox.Image; });

Related

Implementing a "Continuous Capture" with single image snaps

I have some code below that can take the current Image and displaying it whenever I click a PushSnap Button. How can I go about having it continuously capturing and displaying the updated image in an interval (say 100 ms)
private void PushSnap_Click(object sender, EventArgs e)
{
if (mycam == null)
{
MessageBox.Show("Internal Error: mycam is null");
return; // internal error
}
string text = "";
if (IsMyFormStatus_Opened())
{
if (!mydcam.buf_alloc(3))
{
MessageBox.Show("Frame allocation failed");
return;
}
}
// start acquisition
mycam.m_capmode = CAMCAP_START.SNAP; //one time capturing where Acquisition will start after m_nFrameCount frames
if (!mycam.cap_start())
{
return;
}
MyFormStatus_Acquiring();
MyThreadCapture_Start();
}
The Following MyThreadCapture_Start()
private void MyThreadCapture_Start()
{
m_threadCapture = new Thread(new ThreadStart(OnThreadCapture));
m_threadCapture.IsBackground = true;
m_threadCapture.Start();
}
and the following OnThreadCapture()
private void OnThreadCapture()
{
using (mycamwait = new MycamWait())
{
while (True)
{
CAMWAIT eventmask = CAMWAIT.CAPEVENT.FRAMEREADY | CAMWAIT.CAPEVENT.STOPPED;
DCAMWAIT eventhappened = DCAMWAIT.NONE;
if (mycamwait.start(eventmask, ref eventhappened))
{
if (eventhappened & CAMWAIT.CAPEVENT.FRAMEREADY)
{
int NewestFrame = 0;
int FrameCount = 0;
if (mycam.cap_transferinfo(ref NewestFrame, ref FrameCount))
{
MyUpdateImage(iNewestFrame);
}
}
if (eventhappened & CAMWAIT.CAPEVENT.STOPPED)
{
bContinue = false;
if (m_cap_stopping == false && mycam.m_capmode == CAMCAP_START.SNAP)
{
//cap_stop() happens automatically, therefore update the main dialog
//MySnapCaptureFinished();
}
}
}
}
}
Afterwards the Display is updated in the following way
private void UpdateDisplay()
{
Image oldImg = PicDisplay.Image;
if (m_bitmap != null)
{
// Show center of image
Int32 y0 = (m_image.height - PicDisplay.Height) / 2;
Int32 x0 = (m_image.width - PicDisplay.Width) / 2;
Rectangle rc = new Rectangle(x0, y0, PicDisplay.Width, PicDisplay.Height);
Bitmap bmp = new Bitmap(PicDisplay.Width, PicDisplay.Height, PixelFormat.Format24bppRgb);
using (var gr = Graphics.FromImage(bmp))
{
gr.DrawImage(m_bitmap, 0, 0, rc, GraphicsUnit.Pixel);
}
PicDisplay.Image = bmp;
PicDisplay.Refresh();
}
else
{
PicDisplay.Image = null;
}
if (oldImg != null)
oldImg.Dispose();
}
I would really recommend using Tasks instead of Threads. That should let you update your methods to take parameters and return results:
Image CaptureImage(); // I.e. similar code to OnThreadCapture
UpdateDisplay(Image);
And capture an image like:
public async void PushSnap_Click(){
...
try{
var image = await Task.Run(CaptureImage);
DisplayImage(image);
}
catch{
// Handle exceptions
}
That should let you simply rewrite your capture code to use a loop if you want live capture:
while(showLiveImagesBool){
var image = await Task.Run(CaptureImage);
DisplayImage(image);
}
The await-part should ensure the UI thread is not blocked while waiting for an image to be captured. If you do not want to show every image you might use a timer instead of a loop, just pick a timer that runs on the UI thread.
Keep in mind that showing live images will require a bit more performance, so you might need to optimize display and/or capture code. Ideally you should reuse image buffers when doing the capturing and displaying, otherwise you will allocate a fair amount of large objects that require a 2 gen GC to clean up.

Saving a bitmap from a variable gives "invalid parameter"

I need to save an image to disk that came from a web-cam between 5 and 10 seconds ago from when the "save" command comes in via serial port.
To get there, I have the webcam going into a pictureBox.Image (using opencv4), and then 2 Bitmap variables. Every 5 seconds a timer ticks, and Stored_bitmap_2 = Stored_bitmap_1, then Stored_bitmap_1 = (bitmap) pictureBox.Image.
When the right serial command comes in, I try to
Stored_image_2.Save("C:\Users\GreenWorld\Desktop\test.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
and I get a run-time error of invalid parameter.
When I do the same thing in a stand-alone project with some buttons (inside the button-click event handler), it works every time.
When I do this inside the serialPort_DataReceived handler, I get all kinds of cross-thread errors. So, I moved the save attempt to its own subroutine, and that fixed that but now this.
I am by no means a professional programmer, I'm an engineer with a simple problem and I can usually write a little simplistic code to fix my immediate issue. Please go easy on me in the explanation :-)
Sample code:
using OpenCvSharp;
using OpenCvSharp.Extensions;
namespace Weld_picture
{
public partial class Form1 : Form
{
// Create class-level accessible variables
int Welding_camera_ID = 1;
VideoCapture capture;
Mat frame;
Bitmap image;
private Thread camera;
bool isCameraRunning = false;
string Serial_command = "";
Bitmap Stored_image_1;
Bitmap Stored_image_2;
bool Image_saved = false;
string Station_ID = "GWM-PWS-01";
string File_name = "";
private void CaptureCamera() // from someone else's sample code
{
camera = new Thread(new ThreadStart(CaptureCameraCallback));
camera.Start();
}
private void CaptureCameraCallback() // from someone else's sample code
{
frame = new Mat();
capture = new VideoCapture(Welding_camera_ID);
capture.Open(Welding_camera_ID);
if (capture.IsOpened())
{
while (isCameraRunning)
{
capture.Read(frame);
image = BitmapConverter.ToBitmap(frame);
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
}
pictureBox1.Image = image;
}
}
}
public Form1()
{
InitializeComponent();
SerialPort1.Open();
}
private void SerialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string dummy;
char CR = (char)0x0D;
while (SerialPort1.BytesToRead > 0)
{
Serial_command += (char)SerialPort1.ReadByte();
}
while (Serial_command.IndexOf(CR) > 0)
{
dummy = Serial_command.Substring(0, Serial_command.IndexOf(CR));
Serial_command = Serial_command.Substring(Serial_command.IndexOf(CR) + 1, (Serial_command.Length - (Serial_command.IndexOf(CR) + 1)));
Serial_command.Trim();
dummy.Trim();
proc_Process_serial_data(dummy);
}
}
//*************************************************************************************************************************************
/* the first timer is a 5-second interval. It's the "memory" function so that if/when the save-to-disk is triggered I can store the last-time-shutter-open image */
//*************************************************************************************************************************************
private void Timer_picture_interval_Tick(object sender, EventArgs e)
{
checkBox1.Checked = !checkBox1.Checked;
Timer_picture_interval.Stop();
Stored_image_2 = Stored_image_1;
Stored_image_1 = (Bitmap) pictureBox1.Image;
Timer_picture_interval.Start();
}
//*************************************************************************************************************************************
// the second timer is a 30-second interval. It's the way to turn capture off if the PLC/camera box somehow goes off-line
//*************************************************************************************************************************************
private void Timer_camera_powerdown_Tick(object sender, EventArgs e)
{
if (isCameraRunning)
capture.Release();
isCameraRunning = false;
Timer_picture_interval.Stop();
}
//*************************************************************************************************************************************
private void proc_Process_serial_data(string Serial_string)
{
if (Serial_string.IndexOf("Still here") > 0)
{
if (!isCameraRunning)
CaptureCamera();
isCameraRunning = true;
}
if (Serial_string.IndexOf("Sun's up") > 0)
{
Timer_picture_interval.Start();
Timer_camera_powerdown.Start();
}
if (Serial_string.IndexOf("It's dark") > 0)
{
if ((Stored_image_2 != null) && (!Image_saved)) // in case there's 2 subsequent requests to save the same thing (weld stutter)
{
File_name = "C:\\Users\\GreenWorld\\Desktop\\" + Station_ID + " D" + DateTime.Now.ToString("yyyy_MM_dd THH_mm_ss") + ".jpg";
Stored_image_2.Image.Save("C:\\Users\\GreenWorld\\Desktop\\test.bmp" , System.Drawing.Imaging.ImageFormat.Bmp );
Image_saved = true;
Timer_picture_interval.Stop();
}
Timer_camera_powerdown.Start();
}
}
}
}
You likely be able to fix the cros-threading error by simply calling your proc_Process_serial_data() method within an Invoke() call.
Change:
proc_Process_serial_data(dummy);
To:
this.Invoke((MethodInvoker)delegate ()
{
proc_Process_serial_data(dummy);
});
Also, these two lines aren't actually doing anything:
Serial_command.Trim();
dummy.Trim();
To Trim() the strings, you have capture the returned strings and re-assign them to the original variables:
Serial_command = Serial_command.Trim();
dummy = dummy.Trim();

Recording audio with NAudio and writing to a file when microphone input goes silent

I have these two functions written to record and write to a wave file using NAudio. I cannot find a tactic to stop the first function without executing the last line and going out of the local scope.
The audio_Rec method is called from the main function and audioSource_DataAvailable function executes when there is data buffered in the WaveInEvent. As I think, audioSource_DataAvailable function runs only within the scope of audio_Rec method and when the code comes to the last line of audio_Rec (below line),
Console.Write("Recording Starts...\n");
it goes out of the scope and next line of main function executes. (main is not mentioned). I want a method to hold out the audio_Rec method while recording audio until recording is stopped under the given condition in audioSource_DataAvailable function.
count and summation are public member variables.
audio_Rec method
public void audio_Rec()
{
audioSource = new WaveInEvent();
audioSource.DeviceNumber = 0;
audioSource.WaveFormat = new WaveFormat(16000, 16, 1);
audioSource.DataAvailable += new EventHandler<WaveInEventArgs>(audioSource_DataAvailable);
waveFile = new WaveFileWriter(#"d:/dev/csharp/wspeechrec/new_wave.wav", audioSource.WaveFormat);
audioSource.StartRecording();
Console.Write("Recording Starts...\n");
//Thread.Sleep(7000);
}
audioSource_DataAvailable function
void audioSource_DataAvailable(object sender, WaveInEventArgs e)
{
count++;
if (count%5 == 0)
{
if (summation < 150000) {
audioSource.StopRecording();
Console.Write("Recording Stopped!");
}
Console.WriteLine(count * 100 + "ms :"+summation);
summation = 0;
}
Console.WriteLine(count+" "+summation);
byte[] buf = new byte[e.BytesRecorded];
buf = e.Buffer;
int[] Ints = new int[e.BytesRecorded / 2];
for(int i=0;i<buf.Length;i+=2)
{
Ints[i/2] = BitConverter.ToInt16(buf, i);
}
int[] newInts = Ints.Select(x => Math.Abs(x)).ToArray();
summation = newInts.Sum();
if (waveFile != null)
{
waveFile.Write(e.Buffer, 0, e.BytesRecorded);
waveFile.Flush();
}
}
Free to disregard the algorithms in second function. They can be neglected.
Any help is highly appreciated. Thanks
You can try the following:
Handle the RecordingStopped event from the NAudio wave device.
Create an Event or Semaphore or some other synchronization primitive at the class level.
When the RecordingStopped handler is triggered, kick the synchronization primitive.
Before you main method exits, just do a WaitOne() on that primitive.
Your StopRecording call in the DataAvailable handler results in a call to the Stopped handler, which triggers the Semaphore, which then wakes up your main thread so that it can exit.
Example (I'm partial to Semaphores, so that's what I'm using):
using System.Threading;
Semaphore AudioSync = new Semaphore(0, int.MaxValue);
public void audio_Rec()
{
audioSource = new WaveInEvent();
audioSource.DeviceNumber = 0;
audioSource.WaveFormat = new WaveFormat(16000, 16, 1);
// Register for the 'RecordingStopped' event.
audioSource.PlaybackStopped += this.audioSource_PlaybackStopped;
audioSource.DataAvailable += new EventHandler<WaveInEventArgs>(audioSource_DataAvailable);
waveFile = new WaveFileWriter(#"d:/dev/csharp/wspeechrec/new_wave.wav", audioSource.WaveFormat);
audioSource.StartRecording();
Console.Write("Recording Starts...\n");
// Now just wait for the "PlaybackStopped" event handler to signal completion.
AudioSync.WaitOne(); // <-------------------------------------
}
Stopped event handler:
void audioSource_PlaybackStopped( object sender, StoppedEventArgs e )
{
// Wakes up the main thread.
AudioSync.Release();
}

NAudio: Using ASIO to record audio and output with a guitar

I am currently working on a SR. design project which is a windows forms app which will allow for users to plug in their guitar and create distortions in real time and play it with input and output, and auto tab it into ASCII tabs. Currently I am trying to get the real time listening portion working, I have the recording and implementation of distortions working just fine just having some issues using ASIO. I've looked at this post How to record and playback with NAudio using AsioOut but it was not of much help with my issue, here is my code:
private BufferedWaveProvider buffer;
private AsioOut input;
private AsioOut output;
private void listenBtn_Click(object sender, EventArgs e)
{
input = new AsioOut(RecordInCbox.SelectedIndex);
WaveFormat format = new WaveFormat();
buffer = new BufferedWaveProvider(format);
buffer.DiscardOnBufferOverflow = true;
input.InitRecordAndPlayback(buffer, 1, 44100);
input.AudioAvailable += new EventHandler<AsioAudioAvailableEventArgs>(AudioAvailable);
//output = new AsioOut(RecordInCbox.SelectedIndex);
//output.Init(buffer);
input.Play();
//output.Play();
}
public void AudioAvailable(object sender, AsioAudioAvailableEventArgs e)
{
byte[] buf = new byte[e.SamplesPerBuffer];
e.WrittenToOutputBuffers = true;
for (int i = 0; i < e.InputBuffers.Length; i++)
{
Array.Copy(e.InputBuffers, e.OutputBuffers, 1);
Marshal.Copy(e.InputBuffers[i], buf, 0, e.SamplesPerBuffer);
buffer.AddSamples(buf, 0, buf.Length);
}
}
Currently it is getting the audio and pushing it into the buffer but the output is not working. I am able to get it to play the guitar if I set the recording setting to Listen on windows but feel this is unnecessary as how I want to be able to perform my distortion and hear that as the output. Thanks!
You don't need to add samples in buffer. The buffer only serves to determine the number of output channels you want.
I've did it this way:
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
private static unsafe extern void MoveMemory(IntPtr dest, IntPtr src, int size);
private void OnAudioAvailable(object sender, AsioAudioAvailableEventArgs e)
{
for (int i = 0; i < e.InputBuffers.Length; i++)
{
MoveMemory(e.OutputBuffers[i], e.InputBuffers[i], e.SamplesPerBuffer * e.InputBuffers.Length);
}
e.WrittenToOutputBuffers = true;
}
But doing like this feels a bit latency and a bit of echo and I don't know how to solve them. So if you have any ideas I'm here to listen.
Unfortunately I could not find a way to get the ASIO to work, but I have come up with an alternative method which works just as well, as for the latency I got it down to 50 ms, but have been looking into the NAudio source to see if there might be a way to get it below that. (roughly around 20-30 ms) For a better realtime play.
private BufferedWaveProvider buffer;
private WaveOut waveOut;
private WaveIn sourceStream = null;
private bool listen = false;
private void listenBtn_Click(object sender, EventArgs e)
{
listen = !listen;
if (listen)
listenBtn.Text = "Stop listening";
else
{
listenBtn.Text = "Listen";
sourceStream.StopRecording();
return;
}
sourceStream = new WaveIn();
sourceStream.WaveFormat = new WaveFormat(44100, 1);
waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback());
sourceStream.DataAvailable += new EventHandler<WaveInEventArgs>(sourceStream_DataAvailable);
sourceStream.RecordingStopped += new EventHandler<StoppedEventArgs>(sourceStream_RecordingStopped);
buffer = new BufferedWaveProvider(sourceStream.WaveFormat);
buffer.DiscardOnBufferOverflow = true;
waveOut.DesiredLatency = 51;
waveOut.Volume = 1f;
waveOut.Init(buffer);
sourceStream.StartRecording();
}
private void sourceStream_DataAvailable(object sender, WaveInEventArgs e)
{
buffer.AddSamples(e.Buffer, 0, e.BytesRecorded);
waveOut.Play();
}
private void sourceStream_RecordingStopped(object sender, StoppedEventArgs e)
{
sourceStream.Dispose();
waveOut.Dispose();
}
Again I do understand that this is not using ASIO but it was a better alternative based on the resources I had available and the documentation. Instead of using ASIO I am just creating the waveIn and mocking a "recording" but instead of writing that to a file I am taking the stream and pushing it into a waveOut buffer which will allow for it play after I do some sound manipulation.
Probably I am wrong but I have successfully managed simultaneous Asio record and playback using NAudio with very low latencies (on very cheap USB audio hardware ;).
Instead of your event handler method used in your first example you may try this:
private float[] recordingBuffer = null;
private byte[] recordingByteBuffer = null;
private BufferedWaveProvider bufferedWaveProvider;
private BufferedSampleProvider bsp;
private SampleToWaveProvider swp;
// somewhere in e.g. constructor
// set up our signal chain
bufferedWaveProvider = new BufferedWaveProvider(waveFormat);
//bufferedWaveProvider.DiscardOnBufferOverflow = true;
bsp = new BufferedSampleProvider(waveFormat);
swp = new SampleToWaveProvider(bsp);
// ...
private void OnAudioAvailable(object sender, AsioAudioAvailableEventArgs e)
{
this.recordingBuffer = BufferHelpers.Ensure(this.recordingBuffer, e.SamplesPerBuffer * e.InputBuffers.Length);
this.recordingByteBuffer = BufferHelpers.Ensure(this.recordingByteBuffer, e.SamplesPerBuffer * 4 * e.InputBuffers.Length);
int count = e.GetAsInterleavedSamples(this.recordingBuffer);
this.bsp.CurrentBuffer = this.recordingBuffer;
int count2 = this.swp.Read(this.recordingByteBuffer, 0, count * 4);
bufferedWaveProvider.AddSamples(this.recordingByteBuffer, 0, this.recordingByteBuffer.Length);
}
with class BufferedSampleProvider.cs:
public class BufferedSampleProvider : ISampleProvider
{
private WaveFormat waveFormat;
private float[] currentBuffer;
public BufferedSampleProvider(WaveFormat waveFormat)
{
this.waveFormat = waveFormat;
this.currentBuffer = null;
}
public float[] CurrentBuffer
{
get { return this.currentBuffer; }
set { this.currentBuffer = value; }
}
public int Read(float[] buffer, int offset, int count)
{
if (this.currentBuffer != null)
{
if (count <= currentBuffer.Length)
{
for (int i = 0; i < count; i++)
{
buffer[i] = this.currentBuffer[i];
}
return count;
}
}
return 0;
}
public WaveFormat WaveFormat
{
get { return this.waveFormat; }
}
}
I have it done this (messy) way because otherwise I would have to copy the bytes from asio buffers dependent on sample byte count and so on (look at source code from GetAsInterleavedSamples(...) method). To keep it simple for me I have used a BufferedWaveProvider to be really sure there are enough (filled) buffers on the output side of my signal chain even when I'm not really needing it, but it's safe.
After several processing blocks following this provider the chain ends up in the last provider "output". The last provider was passed into
asioOut.InitRecordAndPlayback(output, this.InputChannels, this.SampleRate);
when initializing the whole objects.
Even when I use many processing blocks in my chain, I have no hearable drops or buzzy sounds with asio buffer size of 512 samples. But I think this is really depending on the Asio hardware used.
The most important for me was to be sure to have input and output in sync.
To compare: If I used WaveIn/WaveOutEvent in the same way, I can reach nearly the same latency (on same cheap hardware) but since my tests were on two separate sound devices too, the input buffer duration increases after some time due to some drops or nonsynchronous audio clocks ;) For reaching the very low latency even when using in a WPF application I had to patch WaveOutEvent class to increase priority of playing thread to highest possible, this helps 'against' most of the possible GC interruptions.
Currently it seems with using Asio interface I have sorted out this GC problem at all.
Hope this helps.

Live Microphone amplitude measurement in c#

I am looking for simple solution which will return integer value of microphone input in c#. I was already checking available samples on net, but none of them worked in a x64 environment. (VS2008 + W7 x64).
Is there any simple solution that will return value of amplitude (or frequency) of microphone input in c#?
I tried NAudio without results and this: http://www.codeproject.com/KB/audio-video/cswavrec.aspx?msg=2155497 without luck.
I reckon the easiest route to go is to use the old Windows multimedia API because it's really straight forward.
Here's the link to MSDN: http://msdn.microsoft.com/en-us/library/dd743586(v=VS.85).aspx
What you do is that you use the waveInOpen function to get an input device. To figure out what device to use you don't enumerate all the devices but you can query each and one of them. The number of installed devices is returned by calling waveInGetNumDevs. You can then call waveInGetDevCaps for each device and inspect those properties.
When you have your device handle you then repeatedly call waveInAddBuffer to get small chunks of data. Depending on the format you specified during waveInOpen the bytes represent the raw audio data. Amplitude in 8 or 16-bit signed or unisnged sampled at some frequency.
You could then apply a rolling average to smooth the signal and just print that.
C# doesn't have a sound API that I know of, so what you do is that you use P/Invoke to get at the Win32 API functions. This is fairly straight forward, you only need to port small versions of the Win32 headers to be able to call them from C# directly.
If you're more hardcore you could write a wrapper library in C++/CLI. That's not that bad of an idea because it let's you use existing Windows C/C++ header files and mix C++ and managed code in intresting ways. Just be careful with the unmanaged resources and you'll have a very powerful intropability library in no time.
But there's also more advanced audio APIs starting with Windows Vista, the Windows Core Audio components which could be more interesting further along the line. But for basic I/O operation the Windows multimedia functions will get you there faster.
I've used these functions on a number of occasions when building simple software synthesizers. Sadly that code is long gone.
I recommend SlimDX since it should work on just about any version of windows (x86 or x64) and provides the most features and flexibility. However, it is a pain to get up and running since there are no good complete code samples. I wrote a wrapper class to simplify its usage though so it can be called like this (I tested this code on Win7 x64):
public void CaptureAudio()
{
using (var source = new SoundCardSource())
{
source.SampleRateKHz = 44.1;
source.SampleDataReady += this.OnSampleDataReady;
source.Start();
// Capture 5 seconds of audio...
Thread.Sleep(5000);
source.Stop();
}
}
private void OnSampleDataReady(object sender, SampleDataEventArgs e)
{
// Do something with e.Data short array on separate thread...
}
Here is the source for the SlimDX wrapper class:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using SlimDX.DirectSound;
using SlimDX.Multimedia;
public class SampleDataEventArgs : EventArgs
{
public SampleDataEventArgs(short[] data)
{
this.Data = data;
}
public short[] Data { get; private set; }
}
public class SoundCardSource : IDisposable
{
private volatile bool running;
private int bufferSize;
private CaptureBuffer buffer;
private CaptureBufferDescription bufferDescription;
private DirectSoundCapture captureDevice;
private WaveFormat waveFormat;
private Thread captureThread;
private List<NotificationPosition> notifications;
private int bufferPortionCount;
private int bufferPortionSize;
private WaitHandle[] waitHandles;
private double sampleRate;
public SoundCardSource()
{
this.waveFormat = new WaveFormat();
this.SampleRateKHz = 44.1;
this.bufferSize = 2048;
}
public event EventHandler<SampleDataEventArgs> SampleDataReady = delegate { };
public double SampleRateKHz
{
get
{
return this.sampleRate;
}
set
{
this.sampleRate = value;
if (this.running)
{
this.Restart();
}
}
}
public void Start()
{
if (this.running)
{
throw new InvalidOperationException();
}
if (this.captureDevice == null)
{
this.captureDevice = new DirectSoundCapture();
}
this.waveFormat.FormatTag = WaveFormatTag.Pcm; // Change to WaveFormatTag.IeeeFloat for float
this.waveFormat.BitsPerSample = 16; // Set this to 32 for float
this.waveFormat.BlockAlignment = (short)(waveFormat.BitsPerSample / 8);
this.waveFormat.Channels = 1;
this.waveFormat.SamplesPerSecond = (int)(this.SampleRateKHz * 1000D);
this.waveFormat.AverageBytesPerSecond =
this.waveFormat.SamplesPerSecond *
this.waveFormat.BlockAlignment *
this.waveFormat.Channels;
this.bufferPortionCount = 2;
this.bufferDescription.BufferBytes = this.bufferSize * sizeof(short) * bufferPortionCount;
this.bufferDescription.Format = this.waveFormat;
this.bufferDescription.WaveMapped = false;
this.buffer = new CaptureBuffer(this.captureDevice, this.bufferDescription);
this.bufferPortionSize = this.buffer.SizeInBytes / this.bufferPortionCount;
this.notifications = new List<NotificationPosition>();
for (int i = 0; i < this.bufferPortionCount; i++)
{
NotificationPosition notification = new NotificationPosition();
notification.Offset = this.bufferPortionCount - 1 + (bufferPortionSize * i);
notification.Event = new AutoResetEvent(false);
this.notifications.Add(notification);
}
this.buffer.SetNotificationPositions(this.notifications.ToArray());
this.waitHandles = new WaitHandle[this.notifications.Count];
for (int i = 0; i < this.notifications.Count; i++)
{
this.waitHandles[i] = this.notifications[i].Event;
}
this.captureThread = new Thread(new ThreadStart(this.CaptureThread));
this.captureThread.IsBackground = true;
this.running = true;
this.captureThread.Start();
}
public void Stop()
{
this.running = false;
if (this.captureThread != null)
{
this.captureThread.Join();
this.captureThread = null;
}
if (this.buffer != null)
{
this.buffer.Dispose();
this.buffer = null;
}
if (this.notifications != null)
{
for (int i = 0; i < this.notifications.Count; i++)
{
this.notifications[i].Event.Close();
}
this.notifications.Clear();
this.notifications = null;
}
}
public void Restart()
{
this.Stop();
this.Start();
}
private void CaptureThread()
{
int bufferPortionSamples = this.bufferPortionSize / sizeof(float);
// Buffer type must match this.waveFormat.FormatTag and this.waveFormat.BitsPerSample
short[] bufferPortion = new short[bufferPortionSamples];
int bufferPortionIndex;
this.buffer.Start(true);
while (this.running)
{
bufferPortionIndex = WaitHandle.WaitAny(this.waitHandles);
this.buffer.Read(
bufferPortion,
0,
bufferPortionSamples,
bufferPortionSize * Math.Abs((bufferPortionIndex - 1) % bufferPortionCount));
this.SampleDataReady(this, new SampleDataEventArgs(bufferPortion));
}
this.buffer.Stop();
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.Stop();
if (this.captureDevice != null)
{
this.captureDevice.Dispose();
this.captureDevice = null;
}
}
}
}
It's fully multi-threaded to minimize latency. I originally wrote it for a real-time signal processing analysis tool and used float output instead of short but I modified the code sample to match your requested usage. If you need frequency data I would use http://www.mathdotnet.com/Neodym.aspx or http://www.exocortex.org/dsp/ for a good C# FFT library.

Categories

Resources