为了识别一个短元音,比如,啊(a),钨(u),诸如此类,抄来了别人的声音采集程序,visual c++和c#两个版本,在采集短元音的过程中,发现,c#版本不如c++版本好,一直未找到原因,耿耿于怀(人生的烦恼诞生于此,放不下!)。
当能进行mfcc语音识别这些短元音后,很想用自己的方式去存储一段自己的录音,而非.wav格式,语音识别不能总是停留在短元音识别是吧?
近期尝试了c++版本录音,是ok的,具体是这样,每次采集1920字节声音,满了,再添加1920字节buffer备用继续录音,已满的1920字节buffer写入磁盘,命名为.mch文件,然后重复以上过程,把已满的1920字节buffer源源不断的追加写入到这个.mch文件中。
可惜,c#尝试,始终不成功,再次想起前面的芥蒂,两重放不下,你现在是靠c#吃饭的,底层怎么可以不牢靠呢?
有一种精神叫做“要把书读薄了!”,七月初,机器视觉很多算法都在整理,就是在继承这种精神,只有这样,你的机器视觉才能稳定,准确,高效。
近期,抄了不下四个版本一步一步来精简这个c#版本的连续声音采集程序,终于等到了该来的,放下了该放下的。
抄别人的东西,就应该有所自己的收获,否则,掌控不了,而心生烦恼!
以下是c#版本精简后连续声音采集最好程序,c++版本不再累述。代码如下,你可以去翻看我前面博客有关于此,做个对比。
虽然你还能看到网络程序的影子,但他在你手中,已经脱胎换骨。(删掉了不必要的线程thread以及线程事件掌控AutoResetEvent,这是一种误导,他并不是一种好的方式,除非你更热衷于c#编程技术,这种方式对付短元音采集,虽然绰绰有余,但在连续采集上,反复debug,你会发现缺陷,只要你保证了录音buffer的添加不中断,微软已经保证了录音的连续性,不需要再来一个线程去掌控)
第一,创建一个form,声明变量
public const short device = (-1);
public WaveFormat format = new WaveFormat();
private WaveInRecorder m_WIR;
第二,在formload函数中初始化
m_WIR = new WaveInRecorder(device, format, 1920, 1, new BufferDoneEventHandler(DataArrived));//委托函数
好,结束了!
不懂向下看:(保证最精简,无关全删掉)
第一解释,WaveFormat
public enum WaveFormats
{
Pcm = 1,
Float = 3
}
[StructLayout(LayoutKind.Sequential)]
public class WaveFormat
{
public short wFormatTag;
public short nChannels;
public int nSamplesPerSec;
public int nAvgBytesPerSec;
public short nBlockAlign;
public short wBitsPerSample;
public short cbSize;
public WaveFormat()
{
wFormatTag = (short)WaveFormats.Pcm;
nChannels = 1;
nSamplesPerSec = 8000;
nAvgBytesPerSec = 8000;
nBlockAlign = 1;
wBitsPerSample = 8;
cbSize = 0;
}
}
第二解释,internal class WaveInRecorder
{
private IntPtr m_WaveIn;
private WaveInBuffer m_Buffers = null;
private WaveInBuffer m_CurrentBuffer = null;
private BufferDoneEventHandler m_DoneProc;
public WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveInBuffer.WaveInProc); //委托函数
int buffersize = 0;
public void wimdatacomplete(IntPtr data, int size)
{
m_DoneProc(data, size);//dataarrived
}
public WaveInRecorder(int device, WaveFormat format, int bufferSize, int bufferCount, BufferDoneEventHandler doneProc)
{
this.buffersize = bufferSize;
m_DoneProc = doneProc;//dataarrived传进来
WaveInHelper.Try(WaveNative.waveInOpen(out m_WaveIn, device, format, m_BufferProc, 0, WaveNative.CALLBACK_FUNCTION)); //录音第一步waveInOpen
if (bufferCount > 0)
{
m_Buffers = new WaveInBuffer(m_WaveIn, bufferSize);//录音第二步waveInPrepareHeader
WaveInBuffer Prev = m_Buffers;
//因为只有一个1920字节buffer,其他不相关删除了
Prev.NextBuffer = m_Buffers;
}
for (int i = 0; i < bufferCount; i++)
{
m_CurrentBuffer = m_CurrentBuffer == null ? m_Buffers : m_CurrentBuffer.NextBuffer;
bool temp= m_CurrentBuffer.Record();//waveinaddbuffer,录音第三步
m_CurrentBuffer.m_DoneProc = wimdatacomplete;
}
WaveInHelper.Try(WaveNative.waveInStart(m_WaveIn));//开始录音,录音第四步
}
}
第三解释,internal class WaveInBuffer
{
private IntPtr m_WaveIn;
private WaveNative.WaveHdr m_Header;
private byte[] m_HeaderData;
private GCHandle m_HeaderDataHandle;
public WaveInBuffer NextBuffer;
public BufferDoneEventdowith m_DoneProc;
public int Size
{
get { return m_Header.dwBufferLength; }
}
public IntPtr Data
{
get { return m_Header.lpData; }
}
public WaveInBuffer(IntPtr waveInHandle, int size)
{
m_WaveIn = waveInHandle;
m_Header.dwUser = (IntPtr)GCHandle.Alloc(this);
m_HeaderData = new byte[size];
m_HeaderDataHandle = GCHandle.Alloc(m_HeaderData, GCHandleType.Pinned);
m_Header.lpData = m_HeaderDataHandle.AddrOfPinnedObject();
m_Header.dwBufferLength = size;
WaveInHelper.Try(WaveNative.waveInPrepareHeader(m_WaveIn, ref m_Header, Marshal.SizeOf(m_Header)));
}
public bool Record()
{//录音第六步,添加一个1920字节buffer,若添加不上,录音失败
bool m_Recording = false;
lock (this)
{
m_Recording = WaveNative.waveInAddBuffer(m_WaveIn, ref m_Header, Marshal.SizeOf(m_Header)) == WaveNative.MMSYSERR_NOERROR;
}
return m_Recording;
}
private void OnCompleted(IntPtr Data, int size)
{
m_DoneProc(Data, size); //录音第七步 ,录满1920字节buffer 取出来存盘
}
internal static void WaveInProc(IntPtr hdrvr, int uMsg, int dwUser, ref WaveNative.WaveHdr wavhdr, int dwParam2)
{//录音第五步 , MM_WIM_DATA到了,说明录音满了一个1920字节buffer
if (uMsg == WaveNative.MM_WIM_DATA)
{
try
{
GCHandle h = (GCHandle)wavhdr.dwUser;
WaveInBuffer buf = (WaveInBuffer)h.Target;
bool ret = buf.Record();
buf.OnCompleted(buf.Data, buf.Size);
}
catch
{
}
}
}
}
第四解释,相关其他:
internal class WaveInHelper
{
public static void Try(int err)
{
if (err != WaveNative.MMSYSERR_NOERROR)
{
// throw new Exception(err.ToString());
Thread.Sleep(250);
}
}
}
internal class WaveNative
{
public const int MM_WIM_DATA = 0x3C0;
public const int CALLBACK_FUNCTION = 0x00030000; // dwCallback is a FARPROC
public delegate void WaveDelegate(IntPtr hdrvr, int uMsg, int dwUser, ref WaveHdr wavhdr, int dwParam2);
[StructLayout(LayoutKind.Sequential)]
public struct WaveHdr
{
public IntPtr lpData; // pointer to locked data buffer
public int dwBufferLength; // length of data buffer
public int dwBytesRecorded; // used for input only
public IntPtr dwUser; // for client's use
public int dwFlags; // assorted flags (see defines)
public int dwLoops; // loop control counter
public IntPtr lpNext; // PWaveHdr, reserved for driver
public int reserved; // reserved for driver
}
//尝试简单录音20200724
[DllImport("winmm.dll")]
public static extern int waveInOpen(out IntPtr phwi, int uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, int dwInstance, int dwFlags);
[DllImport("winmm.dll")]
public static extern int waveInPrepareHeader(IntPtr hWaveIn, ref WaveHdr lpWaveInHdr, int uSize);
[DllImport("winmm.dll")]
public static extern int waveInStart(IntPtr hwi);
[DllImport("winmm.dll")]
public static extern int waveInAddBuffer(IntPtr hwi, ref WaveHdr pwh, int cbwh);
}
另外就是两个委托函数的使用,把录音数据传到form层面,form下的委托函数是:
public void DataArrived(IntPtr data, int size)
{ //每写满一个1920字节buffer,都会给到这里来
bt2 = new byte[1920];
Marshal.Copy(data, bt2, 0, bt2.Length);
//取出即可,显示也好,存盘也好
}
所以要在form类之外,项目namespace空间之内声明:
// callbacks
public delegate void BufferDoneEventHandler(IntPtr data, int size);//dataarrived ,在WaveInRecorder中
public delegate void BufferDoneEventdowith(IntPtr data, int size);//testsucess,在waveinbuffer中
正如你所看到的,DataArrived函数的地址给了WaveInRecorder.m_DoneProc
而WaveInRecorder.m_DoneProc又给了 waveinbuffer.m_DoneProc (通过 wimdatacomplete函数);
所以WaveInProc中的录音数据(每一个录满的1920字节)都是通过 waveinbuffer.m_DoneProc给出的。
如果不这样,你还有什么好方法?
WaveInProc也是一个委托函数(回调函数),只不过函数格式微软已经定义好(waveinopen函数要求),这是源头活水。
在WaveInProc调用了waveinbuffer.m_DoneProc,故有机会取水泡茶,哈哈!