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

三、關(guān)鍵實(shí)現(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.子線程中打開(kāi)設(shè)備
使用子線程開(kāi)啟的方式可以有效避免死鎖問(wèn)題。
//子線程,通過(guò)CreateThread啟動(dòng)下列線程
DWORD WINAPI ThreadProc(LPVOID p)
{
MMRESULT result = waveInOpen(&_waveIn, 0, &waveFormat, GetCurrentThreadId(), (DWORD)NULL, CALLBACK_THREAD);
//開(kāi)啟消息循環(huán)
//略
//消息循環(huán)中監(jiān)聽(tīng)3個(gè)消息
switch (msg.message){
case WIM_OPEN:break;
case WIM_DATA:break;
case WIM_CLOSE:break;
}
//開(kāi)啟消息循環(huán)--end
return 0;
}
3.停止采集
通過(guò)發(fā)送WIM_CLOSE消息停止采集。
PostThreadMessage(GetThreadId(_hThread), WIM_CLOSE, 0, 0);
在子線程的消息循環(huán)中:
case WIM_CLOSE: waveInStop(_waveIn); waveInReset(_waveIn); waveInClose(_waveIn); break;
四、封裝成對(duì)象
將采集功能封裝成一個(gè)通用工具,方便在任意地方使用。
1.接口設(shè)計(jì)
接口設(shè)計(jì)如下:
#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>
/// 聲音采集對(duì)象
/// </summary>
/// <summary>
/// 聲音采集對(duì)象
///這是一個(gè)功能完整聲音采集對(duì)象,所有接口通過(guò)了測(cè)試。
///SoundCollectorTest方法包含了所有接口的測(cè)試,修改代碼后,用其驗(yàn)證功能是否正常。
///非線程安全,所有方法需確保在單線程中調(diào)用,即比如:Start和Stop不能在兩個(gè)線程中同時(shí)調(diào)用。
/// </summary>
class SoundCollector {
public:
/// <summary>
/// 采集開(kāi)始事件參數(shù)
/// </summary>
class StartedEventArgs
{
public:
/// <summary>
/// 采集聲音數(shù)據(jù)的格式
/// </summary>
SoundFormat Format;
};
/// <summary>
/// 采集數(shù)據(jù)到達(dá)事件
/// </summary>
class DataArrivedEventArgs :public StartedEventArgs
{
public:
/// <summary>
/// 聲音數(shù)據(jù)
/// </summary>
unsigned char* Data;
/// <summary>
/// 數(shù)據(jù)長(zhǎng)度
/// </summary>
int DataLength;
};
/// <summary>
/// 錯(cuò)誤事件參數(shù)
/// </summary>
class ErrorEventArgs
{
public:
/// <summary>
/// 錯(cuò)誤內(nèi)容
/// </summary>
std::string Message;
};
/// <summary>
/// 采集開(kāi)始事件
/// </summary>
std::function<void(void*, StartedEventArgs*)> Started;
/// <summary>
/// 采集數(shù)據(jù)到達(dá)事件
/// </summary>
std::function<void(void*, DataArrivedEventArgs*)> DataArrived;
/// <summary>
/// 采集結(jié)束事件
/// </summary>
std::function<void(void*, void*)>Stoped;
/// <summary>
/// 錯(cuò)誤事件
/// </summary>
std::function<void(void*, ErrorEventArgs*)> Error;
/// <summary>
/// 構(gòu)造方法
/// </summary>
SoundCollector();
/// <summary>
/// 構(gòu)造方法
/// </summary>
/// <param name="deviceId">聲音設(shè)備Id,0為默認(rèn)設(shè)備</param>
SoundCollector(int deviceId);
/// <summary>
/// 構(gòu)造方法
/// </summary>
/// <param name="deviceId">聲音設(shè)備Id,0為默認(rèn)設(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>
/// 開(kāi)始采集
/// </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對(duì)象參考《C++ 將音頻PCM數(shù)據(jù)封裝成wav文件》
#include"SoundCollector.h"
#include "WavWapper.h"
#include<Windows.h>
int main()
{
//運(yùn)行測(cè)試程序
//AC::SoundCollectorTest();
//獲取設(shè)備列表
auto devices = AC::SoundCollector::GetDeives();
if (devices.size() > 0)
{
printf("設(shè)備名稱:%s\n", devices[0].Name.c_str());
//初始化采集對(duì)象
AC::SoundCollector sc(devices[0].Id);
//wav封裝對(duì)象
AC::WavWapper ww;
//注冊(cè)事件
sc.Started = [&](auto s, auto e) {
printf("開(kāi)始錄制: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ù)寫(xiě)入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());
};
//開(kāi)始采集
if (sc.Start(2, 44100, 16))
{
Sleep(20000);
//結(jié)束采集
sc.Stop();
}
}
}總結(jié)
使用waveIn實(shí)現(xiàn)聲音采集,實(shí)現(xiàn)過(guò)程還是相對(duì)較簡(jiǎn)單的,但還是有些細(xì)節(jié)需要注意,比如使用方法回調(diào)的方式打開(kāi)設(shè)備,關(guān)閉時(shí)很容易造成死鎖,經(jīng)過(guò)一番嘗試發(fā)現(xiàn)子線程中打開(kāi)設(shè)備才是比較好的方式??偟膩?lái)說(shuō),waveIn實(shí)現(xiàn)的聲音采集模塊是能夠支持音頻實(shí)時(shí)流和錄制開(kāi)發(fā)的。
以上就是C++使用waveIn實(shí)現(xiàn)聲音采集的詳細(xì)內(nèi)容,更多關(guān)于C++ waveIn聲音采集的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語(yǔ)言學(xué)習(xí)筆記之VS2022安裝使用教程
這篇文章主要介紹了C語(yǔ)言學(xué)習(xí)筆記之VS2022安裝使用教程,在VS2022中,在使用scanf函數(shù)編譯出錯(cuò),本文給大家提到了解決方法,需要的朋友可以參考下2022-05-05
C++使用遞歸方法求n階勒讓德多項(xiàng)式完整實(shí)例
這篇文章主要介紹了C++使用遞歸方法求n階勒讓德多項(xiàng)式,涉及C++遞歸算法與浮點(diǎn)數(shù)運(yùn)算的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-05-05
C語(yǔ)言練習(xí)題:求1到10的階乘之和簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇C語(yǔ)言練習(xí)題:求1到10的階乘之和簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-05-05
C/C++實(shí)現(xiàn)HTTP協(xié)議解析的示例代碼
基本上,HTTP?是一種基于?TCP/IP?的通信協(xié)議,用于通過(guò)?Web?傳遞?HTML?文件、圖像文件、查詢結(jié)果等數(shù)據(jù)。本文將用C/C++實(shí)現(xiàn)HTTP協(xié)議解析,感興趣的可以了解一下2022-07-07
C語(yǔ)言實(shí)現(xiàn)Fibonacci數(shù)列遞歸
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)Fibonacci數(shù)列遞歸,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02

