- 前言
- 一、需要的对象及方法
- 1.对象
- 2.方法
- 二、关键实现
- 1.设置声音格式
- 2.子线程中打开设备
- 3.停止采集
- 三、封装成对象
- 1.接口设计
- 2.具体实现
- 四、使用示例
- 总结
前言
在Windows上实现录音比较简单的方法是使用winmm,其中的waveIn模块就可以打开录音设备,获取PCM数据,进行声音录制。本文将介绍waveIn录音的具体实现,以及如何避免死锁。
一、需要的对象及方法
需要用到的头文件
#include"windows.h" #include1.对象#pragma comment(lib, "winmm.lib ")
//声音采集对象 HWAVEIN _waveIn; //声音数据的缓存 WAVEHDR _wavehdrs[2]; //声音格式 WAVEFORMATEX _waveFormat;2.方法
//打开声音输入设备 waveInOpen //注册缓冲区 waveInPrepareHeader //注销缓冲区 waveInUnprepareHeader //缓冲区加入使用 waveInAddBuffer //开始采集 waveInStart //停止采集 waveInStop //停止采集 waveInReset //关闭设备 waveInClose
二、关键实现 1.设置声音格式
WAVEFORMATEX WaveInitFormat(WORD nCh, DWORD nSampleRate, WORD bitsPerSample)
{
WAVEFORMATEX waveFormat;
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
waveFormat.nChannels = nCh;
waveFormat.nSamplesPerSec = nSampleRate;
waveFormat.nAvgBytesPerSec = nSampleRate * nCh * bitsPerSample / 8;
waveFormat.nBlockAlign = nCh * bitsPerSample / 8;
waveFormat.wBitsPerSample = bitsPerSample;
waveFormat.cbSize = 0;
return waveFormat;
}
2.子线程中打开设备
使用子线程开启的方式可以有效避免死锁问题。
//子线程,通过CreateThread启动下列线程
DWORD WINAPI ThreadProc(LPVOID p)
{
MMRESULT result = waveInOpen(&_waveIn, 0, &waveFormat, GetCurrentThreadId(), (DWORD)NULL, CALLBACK_THREAD);
//开启消息循环
//略
//消息循环中监听3个消息
switch (msg.message){
case WIM_OPEN:break;
case WIM_DATA:break;
case WIM_CLOSE:break;
}
//开启消息循环--end
return 0;
}
3.停止采集
通过发送WIM_CLOSE消息停止采集。
PostThreadMessage(GetThreadId(_hThread), WIM_CLOSE, 0, 0);
在子线程的消息循环中:
case WIM_CLOSE: waveInStop(_waveIn); waveInReset(_waveIn); waveInClose(_waveIn); break;
三、封装成对象
将采集功能封装成一个通用工具,方便在任意地方使用。
1.接口设计接口设计如下:
#pragma once #include2.具体实现#include #include namespace AC { /// /// 声音格式 /// class SoundFormat { public: ////// 声道数 /// int Channels; ////// 采样率 /// int SampleRate; ////// 位深 /// int BitsPerSample; }; ////// 声音采集设备 /// class SoundDevice { public: ////// 设备Id /// int Id; ////// 设备名称 /// std::string Name; ////// 声道数 /// int Channels; ////// 支持的格式 /// std::vectorSupportedFormats; }; /// /// 声音采集对象 /// class SoundCollector { public: ////// 采集开始事件参数 /// class StartedEventArgs { public: ////// 采集声音数据的格式 /// SoundFormat Format; }; ////// 采集数据到达事件 /// class DataArrivedEventArgs :public StartedEventArgs { public: ////// 声音数据 /// unsigned char* Data; ////// 数据长度 /// int DataLength; }; ////// 错误事件参数 /// class ErrorEventArgs { public: ////// 错误内容 /// std::string Message; }; ////// 采集开始事件 /// std::functionStarted; /// /// 采集数据到达事件 /// std::functionDataArrived; /// /// 采集结束事件 /// std::functionStoped; /// /// 错误事件 /// std::functionError; /// /// 构造方法 /// SoundCollector(); ////// 构造方法 /// /// 声音设备Id,0为默认设备 SoundCollector(int deviceId); ////// 构造方法 /// /// 声音设备Id,0为默认设备 /// 采集的声道数 /// 采集的采样率 /// 采集的位深 SoundCollector(int deviceId, int channels, int sampleRate, int bitsPerSample); ////// 析构方法 /// ~SoundCollector(); ////// 开始采集 /// void Start(); ////// 停止采集 /// void Stop(); ////// 异步停止采集 /// void BeginStop(); ////// 设置采集速率 /// /// 采集速率单位:次/秒 void SetFrequency(int timesPerSecond); ////// 获取采集速率,数据回调频率。 /// ///采集速率,单位:次/秒 int GetFrequency(); ////// 获取声道数 /// ///声道数 int GetChannels(); ////// 获取采样率 /// ///采样率,单位:hz int GetSampleRate(); ////// 获取位深 /// ///位深,单位:bits int GetBitsPerSample(); ////// 获取当前是否在采集中 /// ///是否已停止 bool GetIsStoped(); ////// 获取声音设备列表 /// ///static std::vector GetDeives(); private: void* _implement = nullptr; }; }
https://download.csdn.net/download/u013113678/72818898
四、使用示例
采集声音并保存为wav文件,其中的WavWapper对象参考《C++ 将音频PCM数据封装成wav文件》
#include"SoundCollector.h" #include "WavWapper.h" #includeint main() { //获取设备列表 auto devices = AC::SoundCollector::GetDeives(); if (devices.size() > 0) { printf("设备名称:%sn", devices[0].Name.c_str()); //初始化采集对象 AC::SoundCollector sc(devices[0].Id,2,44100,16); //wav封装对象 AC::WavWapper ww; //注册事件 sc.Started = [&](auto s, auto e) { printf("开始录制:Channels %d SampleRate %d BitsPerSample %dn", e->Format.Channels, e->Format.SampleRate, e->Format.BitsPerSample); //根据声音格式创建wav文件 ww.CreateWavFile("sound.wav", e->Format.Channels, e->Format.SampleRate, e->Format.BitsPerSample); }; sc.DataArrived = [&](auto s, auto* e) { //采集的数据写入wav文件 ww.WriteToFile(e->Data, e->DataLength); }; sc.Stoped = [&](auto s, auto e) { //关闭文件 ww.CloseFile(); printf("录制完成!n"); }; //开始采集 sc.Start(); Sleep(20000); //结束采集 sc.Stop(); } }
总结
以上就是今天要讲的内容,使用waveIn实现声音采集,实现过程还是相对较简单的,但还是有些细节需要注意,比如使用方法回调的方式打开设备,关闭时很容易造成死锁,经过一番尝试发现子线程中打开设备才是比较好的方式。总的来说,waveIn实现的声音采集模块是能够支持音频实时流和录制开发的。



