自己封裝的在Windows平臺上的錄音類
支持:
- 打開錄音設備
- 關閉錄音設備
- 開始錄音
- 停止錄音
- 重置錄音
- 保存錄音爲wav音頻文件
- 可回調錄音狀態與錄音數據
1 頭文件 AudioRecordWindows.h
#ifndef _AUDIO_RECORD_WINDOWS_H_
#define _AUDIO_RECORD_WINDOWS_H_
#include <Windows.h>
#include <iostream>
#include <array>
#include <vector>
#pragma comment(lib,"winmm.lib")
#define CHANNEL_NUM 1 // 聲道數
#define SAMPLE_RATE 16000 // 每秒採樣率
#define SAMPLE_BITS 16 // 採樣位深
#define AUDIO_DATA_BLOCK_SIZE (SAMPLE_RATE*SAMPLE_BITS / 8 * 1) // 緩存數據塊大小 = 採樣率*位深/2*秒(字節)
#define BUFFER_NUM 10 // 緩衝區層數
//!
//! @brief 錄音狀態枚舉
//!
enum RecordStatus
{
OpenDevice,
RecordStart,
RecordWriteData,
RecordStop,
CloseDevice
};
//!
//! @brief 回調函數
//!
typedef void( *AudioRecordCallback)(std::array <char, AUDIO_DATA_BLOCK_SIZE> audioDataBlock, RecordStatus recordStatus);
namespace AudioRecordSpace
{
//!
//! @brief wav文件頭
//!
typedef struct WavHeader
{
char riff[4]; // = "RIFF"
UINT32 size_8; // = FileSize - 8
char wave[4]; // = "WAVE"
char fmt[4]; // = "fmt "
UINT32 fmt_size; // = PCMWAVEFORMAT的大小 :
//PCMWAVEFORMAT
UINT16 format_tag; // = PCM : 1
UINT16 channels; // = 通道數 : 1
UINT32 samples_per_sec; // = 採樣率 : 8000 | 6000 | 11025 | 16000
UINT32 avg_bytes_per_sec; // = 每秒平均字節數 : samples_per_sec * bits_per_sample / 8
UINT16 block_align; // = 每採樣點字節數 : wBitsPerSample / 8
UINT16 bits_per_sample; // = 量化精度: 8 | 16
char data[4]; // = "data";
//DATA
UINT32 data_size; // = 裸數據長度
};
class AudioRecordWindows
{
public:
AudioRecordWindows();
virtual~AudioRecordWindows();
//!
//! @brief 打開錄音設備
//!
bool OpenAudioDevice();
//!
//! @brief 關閉錄音設備
//!
void CloseAudioDevice();
//!
//! @brief 初始化錄音參數
//!
void InitWaveFormat(LPWAVEFORMATEX WaveFormat, WORD ChannelNum, DWORD SampleRate, WORD BitsPerSample);
//!
//! @brief 開始錄音
//!
void StartRecord();
//!
//! @brief 停止錄音
//!
void StopRecord();
//!
//! @brief 重置錄音
//!
void ResetRecord();
//!
//! @brief 設置需要錄音wav文件名稱
//!
void SetFileName(const char* filePath);
//!
//! @brief 寫錄音wav文件
//!
void WriteWavFile();
//!
//! @brief 註冊回調函數
//!
void RegisterCallback(AudioRecordCallback audioCallback);
//!
//! @brief 系統錄音回調函數
//!
static DWORD(CALLBACK WaveAPI_Callback)( // WaveAPI回調函數
HWAVEIN hWaveIn, // 輸入設備
UINT uMsg, // 消息
DWORD_PTR dwInstance, // 保留
DWORD_PTR dwParam1, // 剛填充好的緩衝區指針
DWORD_PTR dwParam2 // 保留
);
private:
HWAVEIN m_AudioDevice; // 音頻輸入設備
WAVEHDR m_WaveHdrBuffer[BUFFER_NUM]; // 聲明緩衝區
static std::array<char, AUDIO_DATA_BLOCK_SIZE> m_AudioDataBlock; // 當前錄製一幀音頻數據
static std::vector<std::array<char, AUDIO_DATA_BLOCK_SIZE>> m_AudioData; // 存儲所有錄製的音頻數據
static bool m_bStopRecord; // 是否停止錄音
static bool m_bPushData; // 是否向m_AudioDataBlock中push數據
std::string m_FilePath; // 錄音文件名稱
bool m_bSaveFile; // 是否保存文件
FILE* m_FileOpen; // 錄音wav文件指針
WavHeader m_WavHeader; // wav文件頭
static AudioRecordCallback m_Callback; // 外部回調函數
static bool m_bCallback; // 是否回調外部回調函數
};
}
#endif // !_AUDIO_RECORD_WINDOWS_H_
2 實現文件 AudioRecordWindows.cpp
#include "AudioRecordWindows.h"
namespace AudioRecordSpace
{
// 靜態變量初始化
std::array <char, AUDIO_DATA_BLOCK_SIZE> AudioRecordWindows::m_AudioDataBlock = {};
std::vector<std::array<char, AUDIO_DATA_BLOCK_SIZE>> AudioRecordWindows::m_AudioData = { {} };
bool AudioRecordWindows::m_bStopRecord = false;
bool AudioRecordWindows::m_bPushData = true;
bool AudioRecordWindows::m_bCallback = false;;
AudioRecordCallback AudioRecordWindows::m_Callback = NULL;
AudioRecordWindows::AudioRecordWindows()
{
m_FileOpen = NULL;
m_WavHeader =
{
{ 'R', 'I', 'F', 'F' },
0,
{ 'W', 'A', 'V', 'E' },
{ 'f', 'm', 't', ' ' },
sizeof(PCMWAVEFORMAT) ,
WAVE_FORMAT_PCM,
1,
SAMPLE_RATE,
SAMPLE_RATE*(SAMPLE_BITS / 8),
SAMPLE_BITS / 8,
SAMPLE_BITS,
{ 'd', 'a', 't', 'a' },
0
};
}
AudioRecordWindows::~AudioRecordWindows()
{
}
bool AudioRecordWindows::OpenAudioDevice()
{
int audioDeviceNum = waveInGetNumDevs();
if (audioDeviceNum <= 0)
{
std::cout << "Windows沒有找到錄音設備,請確認Windows找到了錄音設備" << std::endl;
return false;
}
else
{
for (unsigned int i = 0; i < audioDeviceNum; ++i)
{
WAVEINCAPS waveInCaps;
MMRESULT mmResult = waveInGetDevCaps(i, &waveInCaps, sizeof(WAVEINCAPS));
if (i == 0)
{
std::cout << "當前默認的錄音設備信息描述:" << waveInCaps.szPname << std::endl;
}
else
{
std::cout << "其他錄音設備信息描述" << waveInCaps.szPname << std::endl;
}
}
}
WAVEFORMATEX waveFormate;
InitWaveFormat(&waveFormate, CHANNEL_NUM, SAMPLE_RATE, SAMPLE_BITS);
//#ifndef _WIN64
// waveInOpen(&m_AudioDevice, WAVE_MAPPER, &waveFormate, (DWORD)WaveAPI_Callback, DWORD(this), CALLBACK_FUNCTION);
//#else
// waveInOpen(&m_AudioDevice, WAVE_MAPPER, &waveFormate, (DWORD_PTR)WaveAPI_Callback, DWORD_PTR(this), CALLBACK_FUNCTION);
//#endif // !1
waveInOpen(&m_AudioDevice, WAVE_MAPPER, &waveFormate, (DWORD_PTR)WaveAPI_Callback, DWORD_PTR(this), CALLBACK_FUNCTION);
if (m_bCallback)
{
m_Callback(m_AudioDataBlock, RecordStatus::OpenDevice);
}
return true;
}
void AudioRecordWindows::CloseAudioDevice()
{
if (m_bCallback)
{
m_Callback(m_AudioDataBlock, RecordStatus::CloseDevice);
}
waveInClose(m_AudioDevice);
}
void AudioRecordWindows::InitWaveFormat(LPWAVEFORMATEX WaveFormat, WORD ChannelNum, DWORD SampleRate, WORD BitsPerSample)
{
// 配置音頻波形參數
WaveFormat->wFormatTag = WAVE_FORMAT_PCM;
WaveFormat->nChannels = ChannelNum;
WaveFormat->nSamplesPerSec = SampleRate;
WaveFormat->nAvgBytesPerSec = SampleRate * ChannelNum * BitsPerSample / 8;
WaveFormat->nBlockAlign = ChannelNum * BitsPerSample / 8;
WaveFormat->wBitsPerSample = BitsPerSample;
WaveFormat->cbSize = 0;
std::cout << "採樣參數:" << std::endl;
std::cout << "聲道數" << ChannelNum << std::endl;
std::cout << "採樣率" << SampleRate << "Hz" << std::endl;
std::cout << "位深" << BitsPerSample << std::endl;
}
void AudioRecordWindows::StartRecord()
{
for (unsigned int i = 0; i < BUFFER_NUM; ++i)
{
m_WaveHdrBuffer[i].lpData = new char[AUDIO_DATA_BLOCK_SIZE];
m_WaveHdrBuffer[i].dwBufferLength = AUDIO_DATA_BLOCK_SIZE;
m_WaveHdrBuffer[i].dwBytesRecorded = 0;
m_WaveHdrBuffer[i].dwUser = i;
m_WaveHdrBuffer[i].dwFlags = 0;
m_WaveHdrBuffer[i].dwLoops = 0;
m_WaveHdrBuffer[i].lpNext = NULL;
m_WaveHdrBuffer[i].reserved = 0;
// 排進緩衝區
waveInPrepareHeader(m_AudioDevice, &m_WaveHdrBuffer[i], sizeof(WAVEHDR));
waveInAddBuffer(m_AudioDevice, &m_WaveHdrBuffer[i], sizeof(WAVEHDR));
}
// 清除視頻緩衝
m_AudioData.clear();
m_AudioData.shrink_to_fit();
m_AudioData.resize(0);
// 開始錄音
waveInStart(m_AudioDevice);
if (m_bCallback)
{
m_Callback(m_AudioDataBlock, RecordStatus::RecordStart);
}
}
void AudioRecordWindows::StopRecord()
{
m_bStopRecord = true;
// 停止錄音設備
waveInStop(m_AudioDevice);
waveInReset(m_AudioDevice);
if (m_bCallback)
{
m_Callback(m_AudioDataBlock, RecordStatus::RecordStop);
}
// 釋放緩衝區
for (unsigned int i = 0; i < BUFFER_NUM; ++i)
{
waveInUnprepareHeader(m_AudioDevice,&m_WaveHdrBuffer[i], sizeof(WAVEHDR));
delete m_WaveHdrBuffer[i].lpData;
m_WaveHdrBuffer[i].lpData = NULL;
}
// 保存wav文件
if (m_bSaveFile)
{
WriteWavFile();
}
}
void AudioRecordWindows::ResetRecord()
{
m_AudioData.clear();
m_AudioData.shrink_to_fit();
m_bSaveFile = false;
m_bPushData = true;
m_bStopRecord = false;
m_bCallback = false;
m_Callback = NULL;
m_FileOpen = NULL;
}
void AudioRecordWindows::SetFileName(const char * filePath)
{
m_bSaveFile = true;
m_FilePath = filePath;
// 嘗試打開文件,創建文件
errno_t err = fopen_s(&m_FileOpen, m_FilePath.c_str(), "wb");
if (err > 0)
{
std::cout << "文件創建失敗:" << err << " 檢查文件名和佔用" << std::endl;
m_bSaveFile = false;
}
}
void AudioRecordWindows::WriteWavFile()
{
// 編輯並寫入Wave頭信息
m_WavHeader.data_size = AUDIO_DATA_BLOCK_SIZE * m_AudioData.size();
m_WavHeader.size_8 = m_WavHeader.data_size + 32;
fwrite(&m_WavHeader, sizeof(m_WavHeader), 1, m_FileOpen);
// 追加RawData
fwrite(m_AudioData.data(), AUDIO_DATA_BLOCK_SIZE * m_AudioData.size(), 1, m_FileOpen);
// 寫入結束
fclose(m_FileOpen);
}
void AudioRecordWindows::RegisterCallback(AudioRecordCallback audioCallback)
{
m_bCallback = true;
m_Callback = audioCallback;
}
DWORD(AudioRecordWindows::WaveAPI_Callback)(HWAVEIN hWaveIn, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
// 消息switch
switch (uMsg)
{
case WIM_OPEN: // 設備成功已打開
std::cout << "設備成功打開" << std::endl;
break;
case WIM_DATA: // 緩衝區數據填充完畢
// 停止後會頻繁發出WIM_DATA,已經將數據轉移所以不必理會後繼數據【後繼數據在這裏來看是是重複的】
if (m_bPushData)
{
std::cout << "緩衝區數據填充完畢" << std::endl;
// 把緩衝區數據拷貝出來
memcpy(m_AudioDataBlock.data(), ((LPWAVEHDR)dwParam1)->lpData, AUDIO_DATA_BLOCK_SIZE);
// 沒有錄進去的被填充爲0xcd,改成0來避免末尾出現爆音【只在結束錄音時進行,不影響添加緩存效率】
if (((LPWAVEHDR)dwParam1)->dwBytesRecorded < AUDIO_DATA_BLOCK_SIZE)
{
for (size_t i = ((LPWAVEHDR)dwParam1)->dwBytesRecorded; i < AUDIO_DATA_BLOCK_SIZE; i++)
{
m_AudioDataBlock.at(i) = 0;
}
}
// 添加這一幀
m_AudioData.push_back(m_AudioDataBlock);
// 如果你設置了回調函數
if (m_bCallback)
{
m_Callback(m_AudioDataBlock,RecordStatus::RecordWriteData);
}
}
// 如果需要停止錄音則不繼續添加緩存
if (!m_bStopRecord)
{
waveInAddBuffer(hWaveIn, (LPWAVEHDR)dwParam1, sizeof(WAVEHDR));//添加到緩衝區
}
else
{
// 如果已經停止了錄製,就不要再寫入數據
m_bPushData = false;
}
break;
case WIM_CLOSE:
// 操作成功完成
std::cout << "錄音設備已經關閉..." << std::endl;
break;
default:
break;
}
return 0;
}
}
3 調用Demo AudioRecordWindowsTest.cpp
#include "AudioRecordWindows.h"
#include "conio.h"
using namespace std;
using namespace AudioRecordSpace;
void Callback(std::array <char, AUDIO_DATA_BLOCK_SIZE> audioDataBlock, RecordStatus recordStatus)
{
if (recordStatus == RecordStatus::OpenDevice)
{
cout << "回調 打開設備" << endl;
}
else if (recordStatus == RecordStatus::RecordStart)
{
cout << "回調 開始錄音" << endl;
}
else if (recordStatus == RecordStatus::RecordWriteData)
{
cout << "回調 正在寫入數據" << endl;
}
else if (recordStatus == RecordStatus::RecordStop)
{
cout << "回調 停止錄音" << endl;
}
else if (recordStatus == RecordStatus::CloseDevice)
{
cout << "回調 關閉設備" << endl;
}
}
int main()
{
char ch = 0;
AudioRecordWindows sound_gbr;
while (ch != 'q')
{
ch = _getch();
switch (ch)
{
case 's':
cout << "開始錄音" << endl;
sound_gbr.RegisterCallback(Callback);
sound_gbr.OpenAudioDevice();
sound_gbr.SetFileName("test.wav");
sound_gbr.StartRecord();
break;
case 'q':
cout << "結束錄音" << endl;
sound_gbr.StopRecord();
sound_gbr.CloseAudioDevice();
sound_gbr.ResetRecord();
break;
default:
break;
}
}
getchar();
return 0;
}