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 //注銷(xiāo)緩沖區(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.子線(xiàn)程中打開(kāi)設(shè)備
使用子線(xiàn)程開(kāi)啟的方式可以有效避免死鎖問(wèn)題。
//子線(xiàn)程,通過(guò)CreateThread啟動(dòng)下列線(xiàn)程
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);
在子線(xiàn)程的消息循環(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è)備名稱(chēng)
/// </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)證功能是否正常。
///非線(xiàn)程安全,所有方法需確保在單線(xiàn)程中調(diào)用,即比如:Start和Stop不能在兩個(gè)線(xiàn)程中同時(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è)備名稱(chēng):%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)子線(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ǔ)言 詳細(xì)分析結(jié)構(gòu)體的內(nèi)存對(duì)齊
C 數(shù)組允許定義可存儲(chǔ)相同類(lèi)型數(shù)據(jù)項(xiàng)的變量,結(jié)構(gòu)是 C 編程中另一種用戶(hù)自定義的可用的數(shù)據(jù)類(lèi)型,它允許你存儲(chǔ)不同類(lèi)型的數(shù)據(jù)項(xiàng),本篇讓我們來(lái)了解C 的結(jié)構(gòu)體內(nèi)存對(duì)齊2022-03-03
C語(yǔ)言計(jì)算Robots機(jī)器人行走路線(xiàn)
這篇文章介紹了C語(yǔ)言計(jì)算Robots機(jī)器人行走路線(xiàn),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-12-12
詳細(xì)聊聊c語(yǔ)言中的緩沖區(qū)問(wèn)題
緩沖區(qū)又稱(chēng)為緩存,它是內(nèi)存空間的一部分,也就是說(shuō)在內(nèi)存空間中預(yù)留了一定的存儲(chǔ)空間,這些存儲(chǔ)空間用來(lái)緩沖輸入或輸出的數(shù)據(jù),這部分預(yù)留的空間就叫做緩沖區(qū),這篇文章主要給大家介紹了關(guān)于c語(yǔ)言中緩沖區(qū)問(wèn)題的相關(guān)資料,需要的朋友可以參考下2021-11-11
C++實(shí)現(xiàn)LeetCode(37.求解數(shù)獨(dú))
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(37.求解數(shù)獨(dú)),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
Windows系統(tǒng)下使用C語(yǔ)言編寫(xiě)單線(xiàn)程的文件備份程序
這篇文章主要介紹了Windows系統(tǒng)下使用C語(yǔ)言編寫(xiě)單線(xiàn)程的文件備份程序,文中給出了實(shí)現(xiàn)的幾個(gè)關(guān)鍵代碼片段,剩下的只要套上main和線(xiàn)程調(diào)用的相關(guān)函數(shù)即可,非常詳細(xì),需要的朋友可以參考下2016-02-02
C++圖文并茂講解類(lèi)型轉(zhuǎn)換函數(shù)
類(lèi)型轉(zhuǎn)換(type cast),是高級(jí)語(yǔ)言的一個(gè)基本語(yǔ)法。它被實(shí)現(xiàn)為一個(gè)特殊的運(yùn)算符,以小括號(hào)內(nèi)加上類(lèi)型名來(lái)表示,接下來(lái)讓我們一起來(lái)詳細(xì)了解2022-05-05

