Windows平臺錄音類封裝:AudioRecordWindows

自己封裝的在Windows平臺上的錄音類
支持:

  1. 打開錄音設備
  2. 關閉錄音設備
  3. 開始錄音
  4. 停止錄音
  5. 重置錄音
  6. 保存錄音爲wav音頻文件
  7. 可回調錄音狀態與錄音數據
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章