C++使用waveIn實現(xiàn)聲音采集
前言
在Windows上實現(xiàn)錄音比較簡單的方法是使用winmm,其中的waveIn模塊就可以打開錄音設(shè)備,獲取PCM數(shù)據(jù),進(jìn)行聲音錄制。本文將介紹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ù)到達(dá)事件 /// </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ù)到達(dá)事件 /// </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為默認(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> /// 開始采集 /// </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)過程還是相對較簡單的,但還是有些細(xì)節(jié)需要注意,比如使用方法回調(diào)的方式打開設(shè)備,關(guān)閉時很容易造成死鎖,經(jīng)過一番嘗試發(fā)現(xiàn)子線程中打開設(shè)備才是比較好的方式??偟膩碚f,waveIn實現(xiàn)的聲音采集模塊是能夠支持音頻實時流和錄制開發(fā)的。
以上就是C++使用waveIn實現(xiàn)聲音采集的詳細(xì)內(nèi)容,更多關(guān)于C++ waveIn聲音采集的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語言 詳細(xì)分析結(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-03C++實現(xiàn)LeetCode(37.求解數(shù)獨)
這篇文章主要介紹了C++實現(xiàn)LeetCode(37.求解數(shù)獨),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07Windows系統(tǒng)下使用C語言編寫單線程的文件備份程序
這篇文章主要介紹了Windows系統(tǒng)下使用C語言編寫單線程的文件備份程序,文中給出了實現(xiàn)的幾個關(guān)鍵代碼片段,剩下的只要套上main和線程調(diào)用的相關(guān)函數(shù)即可,非常詳細(xì),需要的朋友可以參考下2016-02-02