目录
接口头文件
#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);
}
}