基于OpenAL的音频采集模块

目录

接口头文件

接口实现文件

依赖模块头文件

依赖模块实现


接口头文件

#pragma once

#include <al.h>
#include <alc.h>
#include <string>
#include <map>
#include <vector>

namespace MyNamespace
{
	class AudioCapture
	{
	public:
		//FIXME: 添加函数注释
		/**
		 \brief 构造函数 
		 \param sDevName 设备名称
		 \param nSmplRate 采样率,默认为44100
		 \param eAudFmt 音频格式,默认为AL_FORMAT_STEREO16(16位立体声)
		 \note 本函数可能抛出异常
		*/
		AudioCapture(const std::string & sDevName,
					 const ALCuint nSmplRate = 44100,
					 const ALCenum eAudFmt = AL_FORMAT_STEREO16);

		/**
		 \brief 析构函数 
		 \note 本函数不会抛出异常
		*/
		~AudioCapture();

		/**
		 \brief 启动设备 
		 \note 本函数可能抛出异常
		*/
		void start();

		/**
		 \brief 停止设备 
		 \note 本函数可能抛出异常
		*/
		void stop();

		/**
		 \brief 获取设备采集的数据
		 \param nSmplNum (输出)设备采集到的样本数
		 \param pDataBuf (输出)数据保存缓冲区指针
		 \param nDataSize(输出)采集到的数据大小,单位是Byte
		 \param nBufSize  数据保存缓冲区大小,单位是Byte
		 \return 设备是否采集到数据
		 \note 本函数可能抛出异常
		*/
		bool getData(ALint & nSmplNum, ALbyte * pDataBuf, 
					 ALsizei & nDataSize, const size_t nBufSize);

		/**
		 \brief 查询设备是否已经启动
		 \return 设备是否已经启动
		 \note 本函数不会抛出异常
		*/
		bool isStart();
		
		/**
		 \brief 查询设备是否已经连接在操作系统中
		 \return 设备是否已经连接在操作系统中
		 \note 本函数可能抛出异常
		*/
		bool isConnect();

		/**
		 \brief 查询系统中可以进行音频采集的设备
		 \return 系统中可以进行音频采集的设备列表
		 \note 本函数可能抛出异常
		*/
		static size_t listCaptureDevices(std::vector<std::string> & devLst);

	private:
		static const std::string getErrStr(const ALenum nErrCode) noexcept;

	private:
		ALCdevice *m_pCapDev = nullptr;
		bool m_bStart = false;

		const std::string m_sDevName; /// 设备名称
		const ALCuint m_nSmplRate;    /// 音频采样率
		const ALCenum m_eAudFmt;	  /// 音频格式
		const int C_SAMPLE_BUFFER_SIZE = 1024;

		//音频格式列表
		const std::map<ALCenum, ALuint> C_AUDIO_FORMAT_DIC {
			{AL_FORMAT_STEREO16, 4},
			{AL_FORMAT_STEREO8, 2},
			{AL_FORMAT_MONO16, 2},
			{AL_FORMAT_MONO8, 1},
		};
	};
}

接口实现文件

#include "AudioCapture.h"
#include "Log.h"
#include "DeviceUtil.h"

#include <algorithm>

using namespace std;

#define ADD_LOC(sMsg) \
    (string(__FILE__).append("(Ln ").append(to_string(__LINE__)) \
     .append(")::").append(__FUNCTION__).append("()::").append(sMsg)).c_str() 

#define CALL_AL_FUN(fun, errMsg) { \
	alGetError(); \
	fun; \
	ALenum eErrCode = AL_NO_ERROR; \
	if ((eErrCode = alGetError()) != AL_NO_ERROR) { \
        string sMsg = string(errMsg) + ", error code is " + getErrStr(eErrCode); \
		throw runtime_error(ADD_LOC(sMsg)); \
	} else { /* 不进行任何操作 */ } \
}

namespace MyNamespace
{
	AudioCapture::AudioCapture(const string & sDevName, const ALCuint nSmplRate, 
							   const ALCenum eAudFmt) 
		: m_sDevName(sDevName), m_nSmplRate(nSmplRate), m_eAudFmt(eAudFmt)
	{
		FU_LOG_TRACE_SCOPE();
		if (sDevName.empty()) {
			throw invalid_argument("Microphone name is empty");
		} else if (0 == m_nSmplRate) {
			throw invalid_argument("Sample rate is zero");
		} else if (C_AUDIO_FORMAT_DIC.find(m_eAudFmt) == C_AUDIO_FORMAT_DIC.end()) {
			throw invalid_argument("Audio format is invald");
		} else { /* do nothing */ }

		FU_LOG_DEBUG("Create audio capture \"" + m_sDevName + "\"");
		//FU_LOG_DEBUG("m_sDevName = \"" + m_sDevName + "\"");
		//FU_LOG_DEBUG("m_nSmplRate = " + to_string(m_nSmplRate));
		//FU_LOG_DEBUG("m_eAudFmt = " + to_string(m_eAudFmt));
		//FU_LOG_DEBUG("C_SAMPLE_BUFFER_SIZE = " + to_string(C_SAMPLE_BUFFER_SIZE));
		CALL_AL_FUN(m_pCapDev = alcCaptureOpenDevice(m_sDevName.c_str(), 
			m_nSmplRate, m_eAudFmt, C_SAMPLE_BUFFER_SIZE), "Fail to open capture device");

		if (!m_pCapDev) {
			throw runtime_error(("Fail to open microphone whose name is " 
								+ sDevName).c_str());
		} else { /* do nothing */ }
	}

	AudioCapture::~AudioCapture()
	{
		FU_LOG_TRACE_SCOPE();
		FU_LOG_DEBUG("Destroy audio capture \"" + m_sDevName + "\"");
		if (m_bStart) {
			FU_LOG_TRACE("Stop audio capture \"" + m_sDevName + "\"");
			stop();
		} else { /* do nothing */ }

		FU_LOG_TRACE("Close audio capture \"" + m_sDevName + "\"");
		alcCaptureCloseDevice(m_pCapDev);
	}

	const string AudioCapture::getErrStr(const ALenum nErrCode) noexcept
	{
		FU_LOG_TRACE_SCOPE();
		switch (nErrCode) {
			case AL_NO_ERROR:
				return "No Error";
				break;
			case AL_INVALID_NAME:
				return "Invalid Name paramater passed to AL call";
				break;
			case AL_ILLEGAL_ENUM:
				return "Invalid parameter passed to AL call";
				break;
			case AL_INVALID_VALUE:
				return "Invalid enum parameter value";
				break;
			case AL_ILLEGAL_COMMAND:
				return "Illegal call";
				break;
			case AL_OUT_OF_MEMORY:
				return "Out of memory ";
				break;
			default:
				return "No known error code";
		}
	}

	void AudioCapture::start()
	{
		FU_LOG_TRACE_SCOPE();
		FU_LOG_DEBUG("Start capture device \"" + m_sDevName + "\"");
		CALL_AL_FUN(alcCaptureStart(m_pCapDev), "Fail to start capturing audio");
		m_bStart = true;
	}

	void AudioCapture::stop()
	{
		FU_LOG_TRACE_SCOPE();
		FU_LOG_DEBUG("Stop capture device \"" + m_sDevName + "\"");
		if (!m_bStart) {
			return;
		} else { /* do nothing */ }

		CALL_AL_FUN(alcCaptureStop(m_pCapDev), "Fail to stop capturing audio");
		m_bStart = false;
	}

	bool AudioCapture::getData(ALint & nSmplNum, ALbyte * pDataBuf, 
							   ALsizei & nDataSize, const size_t nBufSize)
	{
		FU_LOG_TRACE_SCOPE();
		if (0 == nBufSize) {
			throw invalid_argument("Buffer size is zero");
		} else if (nullptr == pDataBuf) {
			throw invalid_argument("Buffer pointer is null");
		} else { /* do nothing */ }

		if (!m_bStart) {
			throw runtime_error("It has not start capturing audio");
		} else { /* do nothing */ }

		CALL_AL_FUN(alcGetIntegerv(m_pCapDev, ALC_CAPTURE_SAMPLES, 
			                       static_cast<ALCsizei>(sizeof(ALint)), &nSmplNum), 
					"Fail to get number of captured samples");
		if (nSmplNum <= 0) {
			return false;
		} else { /* do nothing */ }

		nDataSize = nSmplNum * C_AUDIO_FORMAT_DIC.at(m_eAudFmt);

		if (static_cast<decltype(nBufSize)>(nDataSize) > nBufSize) {
			throw runtime_error("Buffer capacity is not enough");
		} else { /* do nothing */ }

		CALL_AL_FUN(alcCaptureSamples(m_pCapDev, (ALCvoid *)pDataBuf, nSmplNum),
					"Fail to get data of captured samples");

		return true;
	}

	bool AudioCapture::isStart()
	{
		return m_bStart;
	}

	bool AudioCapture::isConnect()
	{
		vector<string> micLst;
		listCaptureDevices(micLst);
		return micLst.end() != find(micLst.begin(), micLst.end(), m_sDevName);
	}

	size_t AudioCapture::listCaptureDevices(std::vector<std::string> & devLst)
	{
		DeviceUtil::getMicsName(devLst);
		return devLst.size();
	}
}

依赖模块头文件

#pragma once

#include <string>
#include <map>
#include <vector>

namespace MyNamespace
{
	class DeviceUtil
	{
	public:
		/**
		 \brief 查询系统中所有麦克风的名称
		 \param micsName(输出)系统中所有的麦克风名称
		 \note 本函数可能抛出异常
		*/
		static void getMicsName(std::vector<std::string>& micsName);

	private:
		/**
		 \brief 查询系统中所有麦克风
		 \param micsName(输出)系统中所有的麦克风
		 \note 本函数可能抛出异常
		*/
		static void getMicDevices(std::map<std::wstring, std::wstring>& deviceMap);
	};
}

依赖模块实现

#include "DeviceUtil.h"
#include "utility.hpp"
#include "UtilTools.h"

#include <mmdeviceapi.h>
#include <functiondiscoverykeys_devpkey.h>

#include <functional>

using namespace std;

namespace MyNamespace
{
	void checkCallRes(const HRESULT res, const string & sFunNm) {
		if (FAILED(res)) {
			throw runtime_error(("Fail to call " + sFunNm + ", error code is "
								 + to_string(res)).c_str());
		} else { /* do nothing */ }
	}

	void GetDevices(
		IMMDeviceCollection * pMMDeviceCollection, map<wstring, wstring>& deviceMap)
	{
		bool ret = true;
		char wszErrMsg[MAX_PATH] = { 0 };

		UINT count;
		HRESULT hr = pMMDeviceCollection->GetCount(&count);
		checkCallRes(hr, "IMMDeviceCollection::GetCount");

		for (UINT i = 0; i < count; i++) {
			IMMDevice *pMMDevice = nullptr;
			IPropertyStore *pPropertyStore = nullptr;
			PROPVARIANT pv;

			Utility::ScopeGuard scpGrd([]() {}, [&]() {
				if (pMMDevice != nullptr) {
					pMMDevice->Release();
				}

				if (pPropertyStore != nullptr) {
					pPropertyStore->Release();
				}

				if (pv.vt != VT_EMPTY) {
					hr = PropVariantClear(&pv);
					checkCallRes(hr, "PropVariantClear");
				}
			});

			PropVariantInit(&pv);

			// get the "n"th device
			hr = pMMDeviceCollection->Item(i, &pMMDevice);
			checkCallRes(hr, "IMMDeviceCollection::Item");

			LPWSTR pwszDeviceId = nullptr;
			hr = pMMDevice->GetId(&pwszDeviceId);
			checkCallRes(hr, "IMMDeviceCollection::GetId");

			// open the property store on that device
			hr = pMMDevice->OpenPropertyStore(STGM_READ, &pPropertyStore);
			checkCallRes(hr, "IMMDevice::OpenPropertyStore");

			// get the long name property
			hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
			checkCallRes(hr, "IPropertyStore::GetValue");

			if (VT_LPWSTR != pv.vt) {
				throw runtime_error(("PKEY_Device_FriendlyName variant type is "
									 + to_string(pv.vt) + " - expected VT_LPWSTR").c_str());
			} else { /* do nothing */ }

			deviceMap[pwszDeviceId] = pv.pwszVal;
		}
	}

	void DeviceUtil::getMicsName(std::vector<std::string>& micsName)
	{
		micsName.clear();
		map<wstring, wstring> micsMap;
		getMicDevices(micsMap);
		for (const auto & pair : micsMap) {
			micsName.emplace_back(UtilTools::wstr2str(pair.second));
		}
	}

	void DeviceUtil::getMicDevices(map<wstring, wstring>& deviceMap)
	{
		deviceMap.clear();

		bool ret = false;
		char wszErrMsg[MAX_PATH] = { 0 };
		IMMDeviceEnumerator *pMMDeviceEnumerator = nullptr;
		IMMDeviceCollection *pMMDeviceCollection = nullptr;

		Utility::ScopeGuard scpGrd([]() {}, [&]() {
			//释放资源
			if (pMMDeviceEnumerator != nullptr) {
				pMMDeviceEnumerator->Release();
			}

			if (pMMDeviceCollection != nullptr) {
				pMMDeviceCollection->Release();
			}
		});

		CoInitialize(nullptr);
		HRESULT hr = CoCreateInstance(
			__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
			__uuidof(IMMDeviceEnumerator),
			reinterpret_cast<void**>(&pMMDeviceEnumerator)
		);
		checkCallRes(hr, "CoCreateInstance");

		// get all the active capture endpoints
		hr = pMMDeviceEnumerator->EnumAudioEndpoints(
			eCapture, DEVICE_STATE_ACTIVE, &pMMDeviceCollection
		);
		checkCallRes(hr, "EnumAudioEndpoints");

		GetDevices(pMMDeviceCollection, deviceMap);
	}
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值