C++ 使用waveIn实现声音采集

24 篇文章 3 订阅


前言

在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;
	};
}

2.具体实现

https://download.csdn.net/download/u013113678/72818898


五、使用示例

采集声音并保存为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实现的声音采集模块是能够支持音频实时流和录制开发的。

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeOfCC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值