C++使用waveIn實現(xiàn)聲音采集
前言
在Windows上實現(xiàn)錄音比較簡單的方法是使用winmm,其中的waveIn模塊就可以打開錄音設(shè)備,獲取PCM數(shù)據(jù),進行聲音錄制。本文將介紹waveIn錄音的具體實現(xiàn),以及如何避免死鎖。
一、需要的對象及方法
需要用到的頭文件
#include"windows.h" #include <mmsystem.h> #pragma comment(lib, "winmm.lib ")
1.對象
//聲音采集對象 HWAVEIN _waveIn; //聲音數(shù)據(jù)的緩存 WAVEHDR _wavehdrs[2]; //聲音格式 WAVEFORMATEX _waveFormat;
2.方法
//打開聲音輸入設(shè)備 waveInOpen //注冊緩沖區(qū) waveInPrepareHeader //注銷緩沖區(qū) waveInUnprepareHeader //緩沖區(qū)加入使用 waveInAddBuffer //開始采集 waveInStart //停止采集 waveInStop //停止采集 waveInReset //關(guān)閉設(shè)備 waveInClose
二、整體流程
整體流程大致如下:

三、關(guān)鍵實現(xiàn)
1.設(shè)置聲音格式
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.子線程中打開設(shè)備
使用子線程開啟的方式可以有效避免死鎖問題。
//子線程,通過CreateThread啟動下列線程
DWORD WINAPI ThreadProc(LPVOID p)
{
MMRESULT result = waveInOpen(&_waveIn, 0, &waveFormat, GetCurrentThreadId(), (DWORD)NULL, CALLBACK_THREAD);
//開啟消息循環(huán)
//略
//消息循環(huán)中監(jiān)聽3個消息
switch (msg.message){
case WIM_OPEN:break;
case WIM_DATA:break;
case WIM_CLOSE:break;
}
//開啟消息循環(huán)--end
return 0;
}
3.停止采集
通過發(fā)送WIM_CLOSE消息停止采集。
PostThreadMessage(GetThreadId(_hThread), WIM_CLOSE, 0, 0);
在子線程的消息循環(huán)中:
case WIM_CLOSE: waveInStop(_waveIn); waveInReset(_waveIn); waveInClose(_waveIn); break;
四、封裝成對象
將采集功能封裝成一個通用工具,方便在任意地方使用。
1.接口設(shè)計
接口設(shè)計如下:
#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>
/// 聲道數(shù)
/// </summary>
int Channels;
/// <summary>
/// 采樣率
/// </summary>
int SampleRate;
/// <summary>
/// 位深
/// </summary>
int BitsPerSample;
};
/// <summary>
/// 聲音采集設(shè)備
/// </summary>
class SoundDevice {
public:
/// <summary>
/// 設(shè)備Id
/// </summary>
int Id;
/// <summary>
/// 設(shè)備名稱
/// </summary>
std::string Name;
/// <summary>
/// 聲道數(shù)
/// </summary>
int Channels;
/// <summary>
/// 支持的格式
/// </summary>
std::vector<SoundFormat> SupportedFormats;
};
/// <summary>
/// 聲音采集對象
/// </summary>
/// <summary>
/// 聲音采集對象
///這是一個功能完整聲音采集對象,所有接口通過了測試。
///SoundCollectorTest方法包含了所有接口的測試,修改代碼后,用其驗證功能是否正常。
///非線程安全,所有方法需確保在單線程中調(diào)用,即比如:Start和Stop不能在兩個線程中同時調(diào)用。
/// </summary>
class SoundCollector {
public:
/// <summary>
/// 采集開始事件參數(shù)
/// </summary>
class StartedEventArgs
{
public:
/// <summary>
/// 采集聲音數(shù)據(jù)的格式
/// </summary>
SoundFormat Format;
};
/// <summary>
/// 采集數(shù)據(jù)到達事件
/// </summary>
class DataArrivedEventArgs :public StartedEventArgs
{
public:
/// <summary>
/// 聲音數(shù)據(jù)
/// </summary>
unsigned char* Data;
/// <summary>
/// 數(shù)據(jù)長度
/// </summary>
int DataLength;
};
/// <summary>
/// 錯誤事件參數(shù)
/// </summary>
class ErrorEventArgs
{
public:
/// <summary>
/// 錯誤內(nèi)容
/// </summary>
std::string Message;
};
/// <summary>
/// 采集開始事件
/// </summary>
std::function<void(void*, StartedEventArgs*)> Started;
/// <summary>
/// 采集數(shù)據(jù)到達事件
/// </summary>
std::function<void(void*, DataArrivedEventArgs*)> DataArrived;
/// <summary>
/// 采集結(jié)束事件
/// </summary>
std::function<void(void*, void*)>Stoped;
/// <summary>
/// 錯誤事件
/// </summary>
std::function<void(void*, ErrorEventArgs*)> Error;
/// <summary>
/// 構(gòu)造方法
/// </summary>
SoundCollector();
/// <summary>
/// 構(gòu)造方法
/// </summary>
/// <param name="deviceId">聲音設(shè)備Id,0為默認設(shè)備</param>
SoundCollector(int deviceId);
/// <summary>
/// 構(gòu)造方法
/// </summary>
/// <param name="deviceId">聲音設(shè)備Id,0為默認設(shè)備</param>
/// <param name="channels">采集的聲道數(shù)</param>
/// <param name="sampleRate">采集的采樣率</param>
/// <param name="bitsPerSample">采集的位深</param>
SoundCollector(int deviceId, int channels, int sampleRate, int bitsPerSample);
/// <summary>
/// 析構(gòu)方法
/// </summary>
~SoundCollector();
/// <summary>
/// 開始采集
/// </summary>
bool Start(int channels, int sampleRate, int bitsPerSample);
/// <summary>
/// 停止采集
/// </summary>
void Stop();
/// <summary>
/// 異步停止采集
/// </summary>
void BeginStop();
/// <summary>
/// 設(shè)置采集速率
/// </summary>
/// <param name="timesPerSecond">采集速率單位:次/秒</param>
void SetFrequency(int timesPerSecond);
/// <summary>
/// 獲取采集速率,數(shù)據(jù)回調(diào)頻率。
/// </summary>
/// <returns>采集速率,單位:次/秒</returns>
int GetFrequency();
/// <summary>
/// 獲取聲道數(shù)
/// </summary>
/// <returns>聲道數(shù)</returns>
int GetChannels();
/// <summary>
/// 獲取采樣率
/// </summary>
/// <returns>采樣率,單位:hz</returns>
int GetSampleRate();
/// <summary>
/// 獲取位深
/// </summary>
/// <returns>位深,單位:bits</returns>
int GetBitsPerSample();
/// <summary>
/// 獲取當(dāng)前是否在采集中
/// </summary>
/// <returns>是否已停止</returns>
bool GetIsStoped();
/// <summary>
/// 獲取聲音設(shè)備列表
/// </summary>
/// <returns></returns>
static std::vector<SoundDevice> GetDeives();
private:
void* _implement = nullptr;
};
}五、使用示例
采集聲音并保存為wav文件,其中的WavWapper對象參考《C++ 將音頻PCM數(shù)據(jù)封裝成wav文件》
#include"SoundCollector.h"
#include "WavWapper.h"
#include<Windows.h>
int main()
{
//運行測試程序
//AC::SoundCollectorTest();
//獲取設(shè)備列表
auto devices = AC::SoundCollector::GetDeives();
if (devices.size() > 0)
{
printf("設(shè)備名稱:%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);
//根據(jù)聲音格式創(chuàng)建wav文件
ww.CreateWavFile("sound.wav", e->Format.Channels, e->Format.SampleRate, e->Format.BitsPerSample);
};
sc.DataArrived = [&](auto s, auto* e) {
//采集的數(shù)據(jù)寫入wav文件
ww.WriteToFile(e->Data, e->DataLength);
};
sc.Stoped = [&](auto s, auto e) {
//關(guān)閉文件
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);
//結(jié)束采集
sc.Stop();
}
}
}總結(jié)
使用waveIn實現(xiàn)聲音采集,實現(xiàn)過程還是相對較簡單的,但還是有些細節(jié)需要注意,比如使用方法回調(diào)的方式打開設(shè)備,關(guān)閉時很容易造成死鎖,經(jīng)過一番嘗試發(fā)現(xiàn)子線程中打開設(shè)備才是比較好的方式??偟膩碚f,waveIn實現(xiàn)的聲音采集模塊是能夠支持音頻實時流和錄制開發(fā)的。
以上就是C++使用waveIn實現(xiàn)聲音采集的詳細內(nèi)容,更多關(guān)于C++ waveIn聲音采集的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語言 詳細分析結(jié)構(gòu)體的內(nèi)存對齊
C 數(shù)組允許定義可存儲相同類型數(shù)據(jù)項的變量,結(jié)構(gòu)是 C 編程中另一種用戶自定義的可用的數(shù)據(jù)類型,它允許你存儲不同類型的數(shù)據(jù)項,本篇讓我們來了解C 的結(jié)構(gòu)體內(nèi)存對齊2022-03-03
C++實現(xiàn)LeetCode(37.求解數(shù)獨)
這篇文章主要介紹了C++實現(xiàn)LeetCode(37.求解數(shù)獨),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07
Windows系統(tǒng)下使用C語言編寫單線程的文件備份程序
這篇文章主要介紹了Windows系統(tǒng)下使用C語言編寫單線程的文件備份程序,文中給出了實現(xiàn)的幾個關(guān)鍵代碼片段,剩下的只要套上main和線程調(diào)用的相關(guān)函數(shù)即可,非常詳細,需要的朋友可以參考下2016-02-02

