C++使用waveIn实现声音采集
作者:CodeOfCC
在Windows上实现录音比较简单的方法是使用winmm,其中的waveIn模块就可以打开录音设备,这篇文章主要为大家介绍了C++如何使用waveIn实现声音采集,需要的可以了解下
前言
在Windows上实现录音比较简单的方法是使用winmm,其中的waveIn模块就可以打开录音设备,获取PCM数据,进行声音录制。本文将介绍waveIn录音的具体实现,以及如何避免死锁。
一、需要的对象及方法
需要用到的头文件
#include"windows.h" #include <mmsystem.h> #pragma comment(lib, "winmm.lib ")
1.对象
//声音采集对象 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 #include<vector> #include<string> #include<functional> /************************************************************************ * @Project: AC::SoundCollector * @Decription: 音频采集工具 * @Verision: v1.0.0.1 * @Author: Xin Nie * @Create: 2021/12/30 13:34:00 * @LastUpdate: 2021/1/7 23:06:00 ************************************************************************ * Copyright @ 2020. All rights reserved. ************************************************************************/ namespace AC { /// <summary> /// 声音格式 /// </summary> class SoundFormat { public: /// <summary> /// 声道数 /// </summary> int Channels; /// <summary> /// 采样率 /// </summary> int SampleRate; /// <summary> /// 位深 /// </summary> int BitsPerSample; }; /// <summary> /// 声音采集设备 /// </summary> class SoundDevice { public: /// <summary> /// 设备Id /// </summary> int Id; /// <summary> /// 设备名称 /// </summary> std::string Name; /// <summary> /// 声道数 /// </summary> int Channels; /// <summary> /// 支持的格式 /// </summary> std::vector<SoundFormat> SupportedFormats; }; /// <summary> /// 声音采集对象 /// </summary> /// <summary> /// 声音采集对象 ///这是一个功能完整声音采集对象,所有接口通过了测试。 ///SoundCollectorTest方法包含了所有接口的测试,修改代码后,用其验证功能是否正常。 ///非线程安全,所有方法需确保在单线程中调用,即比如:Start和Stop不能在两个线程中同时调用。 /// </summary> class SoundCollector { public: /// <summary> /// 采集开始事件参数 /// </summary> class StartedEventArgs { public: /// <summary> /// 采集声音数据的格式 /// </summary> SoundFormat Format; }; /// <summary> /// 采集数据到达事件 /// </summary> class DataArrivedEventArgs :public StartedEventArgs { public: /// <summary> /// 声音数据 /// </summary> unsigned char* Data; /// <summary> /// 数据长度 /// </summary> int DataLength; }; /// <summary> /// 错误事件参数 /// </summary> class ErrorEventArgs { public: /// <summary> /// 错误内容 /// </summary> std::string Message; }; /// <summary> /// 采集开始事件 /// </summary> std::function<void(void*, StartedEventArgs*)> Started; /// <summary> /// 采集数据到达事件 /// </summary> std::function<void(void*, DataArrivedEventArgs*)> DataArrived; /// <summary> /// 采集结束事件 /// </summary> std::function<void(void*, void*)>Stoped; /// <summary> /// 错误事件 /// </summary> std::function<void(void*, ErrorEventArgs*)> Error; /// <summary> /// 构造方法 /// </summary> SoundCollector(); /// <summary> /// 构造方法 /// </summary> /// <param name="deviceId">声音设备Id,0为默认设备</param> SoundCollector(int deviceId); /// <summary> /// 构造方法 /// </summary> /// <param name="deviceId">声音设备Id,0为默认设备</param> /// <param name="channels">采集的声道数</param> /// <param name="sampleRate">采集的采样率</param> /// <param name="bitsPerSample">采集的位深</param> SoundCollector(int deviceId, int channels, int sampleRate, int bitsPerSample); /// <summary> /// 析构方法 /// </summary> ~SoundCollector(); /// <summary> /// 开始采集 /// </summary> bool Start(int channels, int sampleRate, int bitsPerSample); /// <summary> /// 停止采集 /// </summary> void Stop(); /// <summary> /// 异步停止采集 /// </summary> void BeginStop(); /// <summary> /// 设置采集速率 /// </summary> /// <param name="timesPerSecond">采集速率单位:次/秒</param> void SetFrequency(int timesPerSecond); /// <summary> /// 获取采集速率,数据回调频率。 /// </summary> /// <returns>采集速率,单位:次/秒</returns> int GetFrequency(); /// <summary> /// 获取声道数 /// </summary> /// <returns>声道数</returns> int GetChannels(); /// <summary> /// 获取采样率 /// </summary> /// <returns>采样率,单位:hz</returns> int GetSampleRate(); /// <summary> /// 获取位深 /// </summary> /// <returns>位深,单位:bits</returns> int GetBitsPerSample(); /// <summary> /// 获取当前是否在采集中 /// </summary> /// <returns>是否已停止</returns> bool GetIsStoped(); /// <summary> /// 获取声音设备列表 /// </summary> /// <returns></returns> static std::vector<SoundDevice> GetDeives(); private: void* _implement = nullptr; }; }
五、使用示例
采集声音并保存为wav文件,其中的WavWapper对象参考《C++ 将音频PCM数据封装成wav文件》
#include"SoundCollector.h" #include "WavWapper.h" #include<Windows.h> int main() { //运行测试程序 //AC::SoundCollectorTest(); //获取设备列表 auto devices = AC::SoundCollector::GetDeives(); if (devices.size() > 0) { printf("设备名称:%s\n", devices[0].Name.c_str()); //初始化采集对象 AC::SoundCollector sc(devices[0].Id); //wav封装对象 AC::WavWapper ww; //注册事件 sc.Started = [&](auto s, auto e) { printf("开始录制:Channels %d SampleRate %d BitsPerSample %d\n", 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.Error = [&](auto s, auto e) { printf("%s\n",e->Message.c_str()); }; //开始采集 if (sc.Start(2, 44100, 16)) { Sleep(20000); //结束采集 sc.Stop(); } } }
总结
使用waveIn实现声音采集,实现过程还是相对较简单的,但还是有些细节需要注意,比如使用方法回调的方式打开设备,关闭时很容易造成死锁,经过一番尝试发现子线程中打开设备才是比较好的方式。总的来说,waveIn实现的声音采集模块是能够支持音频实时流和录制开发的。
以上就是C++使用waveIn实现声音采集的详细内容,更多关于C++ waveIn声音采集的资料请关注脚本之家其它相关文章!