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.
Related
My task is to voice automatic weapons. The problem is that fire sounds are much longer than the distance between shots, so I have to play them simultaneously. I came up with this code below, but first shots are buggy and loading sound as new every time doesn't look like a great solution (I cleared all not-sound relative logic):
Please drop me a hint on a better approach.
Thanks
public class MachineGun
{
private readonly Uri soundLocation
private readonly int firePeriod;
private int ticksFromLastFire;
public MachineGun()
{
var appDirectory = Path.GetDirectoryName(Application.ExecutablePath);
soundLocation = new Uri("file:///" + appDirectory + #"/Assets\Sounds\GunSounds\fire_MachineGun.wav");
firePeriod = 3;
ticksFromLastFire = 0;
}
public override void Fire()
{
if (ticksFromLastFire < firePeriod) return;
ticksFromLastFire = 0;
var sound = new MediaPlayer();
sound.Open(soundLocation));
sound.Play();
}
public override void IncrementTick() => ticksFromLastFire++;
}
I am very new to the using both I2C and C#/Windows IoT so apologies up front if any of this is a dumb question. I have a Raspberry Pi 3 master and Arduino slave. I am trying to send a value from a slider on my UI form over I2C to the Arduino which I will use to adjust my PWM duty cycle. There a couple of issues I am having and can't work out if its the Pi, Arduino or both.
Here is my Arduino Slave code:
#include <Wire.h>
#define MyAddress 0x03
byte ReceivedData;
int pass;
void setup() {
Wire.begin(MyAddress);
Wire.onReceive(I2CReceived);
Serial.begin(9600);
//Wire.onRequest(I2CRequest);
}
void loop() {
delay(100);
}
void I2CReceived(int NumberOfBytes)
{
/* WinIoT have sent data byte; read it */
byte ReceivedData = Wire.read();
Serial.println(ReceivedData);
if (ReceivedData <= 127){
Serial.println("Equal or under");
return;
}else{
Serial.println("over");
return;
}
}
And my Pi Master:
using System;
using Windows.Devices.Gpio;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Media;
using Windows.UI.Core;
using Windows.Devices.Enumeration;
using Windows.Devices.I2c;
using System.Diagnostics;
using System.Threading;
namespace I2COutput
{
public sealed partial class MainPage : Page
{
private I2cDevice TransPump;
private Timer periodicTimer;
private const byte pump = 0x03;
double pos;
public MainPage()
{
InitializeComponent();
initcomunica();
}
private async void initcomunica()
{
var pumpset = new I2cConnectionSettings(pump);
pumpset.BusSpeed = I2cBusSpeed.StandardMode;
string aqs = I2cDevice.GetDeviceSelector("I2C1");
var dis = await DeviceInformation.FindAllAsync(aqs);
TransPump = await I2cDevice.FromIdAsync(dis[0].Id, pumpset);
}
private async void SendChange()
{
byte[] sendpos;
try
{
sendpos = BitConverter.GetBytes(pos);
TransPump.Write(sendpos);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
}
private void tempLbl_SelectionChanged(object sender, RoutedEventArgs e)
{
}
private void slider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
pos = slider.Value;
temp2Lbl.Text = pos.ToString();
Convert.ToInt16(pos);
SendChange();
return;
}
}
}
The first issue I am having is that my ReceivedData on the Arduino is always 0 not matter what the value of sendpos is on the Pi (yes, it does change when I move the slider).
The second issue I am having is the first time the slider is moved I get the output on the Arduino serial but then nothing after. If I either reset or reload the Arduino I then get the output of the initial slider change again and nothing after.
Apologies if any of this is too vague or explained poorly, any help or nudge in the right direction would be greatly appreciated.
Thanks in advance.
you have to change the "Wire.onReceive(I2CReceived);" to the loop because when it's in the setup the arduino exute it only one, (sorry for my english)
I wrote an I2C slave for Arduino UNO based on Nick Gammon Web Site.
It worked but I could not get more than 10 K byte /second. You have some missing part in your own code.
Here is stripped down version of the Arduino Code
#include <Wire.h>
#define I2C_400K 1 // http://www.gammon.com.au/forum/?id=10896
bool receiveEventcommandReceived = false;
bool requestEventCommandReceived = false;
int _currentRequestInputParameterLen = 0;
void receiveEvent(int howMany) {
receiveEventcommandReceived = true;
while (Wire.available() > 0)
{
_cmd = Wire.read();
if (_cmd == ArduinoCommand_EpromWrite) {
// Some code
}
else if (_cmd == ArduinoCommand_EpromRead) {
_addr0 = Wire.read();
_addr1 = Wire.read();
_addr = (_addr0 * 256) + _addr1;
_len = Wire.read();
_EEPROMBuffer = NusbioEx.EEPROMRead(_addr, _len);
_r = 128+1;
}
else {
// Some code
}
_count++;
}
}
void requestEvent()
{
requestEventCommandReceived = true;
if (_cmd == ArduinoCommand_EpromRead) {
Wire.write(_EEPROMBuffer, strlen(_EEPROMBuffer));
}
else { // ArduinoCommand_EpromWrite or any other api
int v1 = _r >> 8;
int v2 = _r & 0xFF;
char buffer[2];
buffer[0] = v1;
buffer[1] = v2;
Wire.write(buffer, 2); // MUST BE SENT IN ONE BUFFER -> TO CREATE ONE I2C TRANSACTION
}
}
void SetupI2C() {
Wire.begin(I2C_SLAVE_ADDR); // join i2c bus with address #4
#if I2C_400K
TWBR = 12; // http://www.gammon.com.au/forum/?id=10896
#endif
Wire.onReceive(receiveEvent); // register event
Wire.onRequest(requestEvent); // register event
}
void setup() {
SetupI2C();
}
void loop() {
if (requestEventCommandReceived)
{
requestEventCommandReceived = false;
}
#endif
}
I want to use the volume of the audio that the user inputs with his/her microphone in Unity3d in a visual representation. So I'd like to get a value between 0 and 1 that tell how loud the user is. I went looking for a script, but the part that handles the volume doesn't work properly, that part is the method LevelMax(). For some reason micPosiotion never becomes higher than 0. I don't know what Microphone.GetPosition really does except for this:
http://docs.unity3d.com/ScriptReference/Microphone.GetPosition.html
Does anyone know what goes wrong in the method LevelMax()? I am getting no errors or anything. And it finds my microphone properly, and it is working. I tested it.
This is the code:
using UnityEngine;
using System.Collections;
public class MicInput : MonoBehaviour{
public float testSound;
public static float MicLoudness;
private string _device;
private AudioClip _clipRecord = new AudioClip();
private int _sampleWindow = 128;
private bool _isInitialized;
void InitMic()
{
if (_device == null) {
_device = Microphone.devices [0];
_clipRecord = Microphone.Start (_device, true, 999, 44100);
Debug.Log (_clipRecord);
}
}
void StopMicrophone()
{
Microphone.End (_device);
}
float LevelMax()
{
float levelMax = 0;
float[] waveData = new float[_sampleWindow];
int micPosition = Microphone.GetPosition (null) - (_sampleWindow + 1);
if (micPosition < 0) {
return 0;
}
_clipRecord.GetData (waveData, micPosition);
for (int i = 0; i < _sampleWindow; ++i) {
float wavePeak = waveData [i] * waveData [i];
if (levelMax < wavePeak) {
levelMax = wavePeak;
}
}
return levelMax;
}
void Update()
{
MicLoudness = LevelMax ();
testSound = MicLoudness;
}
void OnEnable()
{
InitMic ();
_isInitialized = true;
}
void OnDisable()
{
StopMicrophone ();
}
void OnDestory()
{
StopMicrophone ();
}
void OnApplicationFocus(bool focus)
{
if (focus) {
if (!_isInitialized) {
InitMic ();
_isInitialized = true;
}
}
if (!focus) {
StopMicrophone ();
_isInitialized = false;
}
}
}
This script works. I have just tested it and it shows the peak level of the mic in the inspector as the variable testSound. There is something going wrong on your end that is causing it to not begin recording into the audioclip. That is why it is always returning that the micPosition is less than zero.
The only thing that I can see that is slightly off is Microphone.GetPosition(null) inside the LevelMax method. Try changing this to Microphone.GetPosition(_device)
You might also want to try going through your different audio devices by changing the index passed in the line (in the InitMic method):
_device = Microphone.devices [0];
Try changing this to 1,2,3 etc and see if you are just finding the wrong audio device. If you have more than one mic or are not using the default mic then this could be the problem.
Also, I think you are misunderstanding how digital audio works. GetPosition gets the current sample that the microphone is recording into the audioclip (i.e the latest sample/current sample). This basically means that it gets the amount of samples that have been recorded. You are recording at 44.1Khz samples. That means that every second the audio is being checked 441000 times and a level is assigned to that individual sample. This is called the sample rate and it can be changed. For example CD's use the sample rate 44.1kHz where as digital video tends to use 48kHz. The accuracy of the the sample being recorded is defined by the bit-depth (but you don't have to worry about this). For example CD's use 16bit (which needs to be dithered) whereas digital media uses 24bit(generally). The line:
int micPosition = Microphone.GetPosition(null)-(_sampleWindow+1);
Says "find the amount samples we have recorded 129 samples ago". It then iterates through the values of the next 128 samples and finds the 'loudest' sample and returns it. This is then shown in the inspector. If you don't understand anything I've just said then look into how digital audio is recorded. It's not too complicated to understand the basics of it.
You should check out this thread, but here is the code that may help you:
public class MicInput : MonoBehaviour {
public static float MicLoudness;
private string _device;
//mic initialization
void InitMic(){
if(_device == null) _device = Microphone.devices[0];
_clipRecord = Microphone.Start(_device, true, 999, 44100);
}
void StopMicrophone()
{
Microphone.End(_device);
}
AudioClip _clipRecord = new AudioClip();
int _sampleWindow = 128;
//get data from microphone into audioclip
float LevelMax()
{
float levelMax = 0;
float[] waveData = new float[_sampleWindow];
int micPosition = Microphone.GetPosition(null)-(_sampleWindow+1); // null means the first microphone
if (micPosition < 0) return 0;
_clipRecord.GetData(waveData, micPosition);
// Getting a peak on the last 128 samples
for (int i = 0; i < _sampleWindow; i++) {
float wavePeak = waveData[i] * waveData[i];
if (levelMax < wavePeak) {
levelMax = wavePeak;
}
}
return levelMax;
}
void Update()
{
// levelMax equals to the highest normalized value power 2, a small number because < 1
// pass the value to a static var so we can access it from anywhere
MicLoudness = LevelMax ();
}
bool _isInitialized;
// start mic when scene starts
void OnEnable()
{
InitMic();
_isInitialized=true;
}
//stop mic when loading a new level or quit application
void OnDisable()
{
StopMicrophone();
}
void OnDestroy()
{
StopMicrophone();
}
// make sure the mic gets started & stopped when application gets focused
void OnApplicationFocus(bool focus) {
if (focus)
{
//Debug.Log("Focus");
if(!_isInitialized){
//Debug.Log("Init Mic");
InitMic();
_isInitialized=true;
}
}
if (!focus)
{
//Debug.Log("Pause");
StopMicrophone();
//Debug.Log("Stop Mic");
_isInitialized=false;
}
}
}
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.
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; });