C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++ waveIn声音采集

C++使用waveIn实现声音采集

作者:CodeOfCC

在Windows上实现录音比较简单的方法是使用winmm,其中的waveIn模块就可以打开录音设备,这篇文章主要为大家介绍了C++如何使用waveIn实现声音采集,需要的可以了解下

前言

在Windows上实现录音比较简单的方法是使用winmm,其中的waveIn模块就可以打开录音设备,获取PCM数据,进行声音录制。本文将介绍waveIn录音的具体实现,以及如何避免死锁。

一、需要的对象及方法

需要用到的头文件

#include"windows.h"
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib ")  

1.对象

//声音采集对象
HWAVEIN _waveIn;
//声音数据的缓存
WAVEHDR  _wavehdrs[2];
//声音格式
WAVEFORMATEX _waveFormat;

2.方法

//打开声音输入设备
waveInOpen
//注册缓冲区
waveInPrepareHeader
//注销缓冲区
waveInUnprepareHeader
//缓冲区加入使用
waveInAddBuffer
//开始采集
waveInStart
//停止采集
waveInStop
//停止采集
waveInReset
//关闭设备
waveInClose

二、整体流程

整体流程大致如下:

三、关键实现

1.设置声音格式

 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.子线程中打开设备

使用子线程开启的方式可以有效避免死锁问题。

//子线程,通过CreateThread启动下列线程
DWORD WINAPI ThreadProc(LPVOID p)
{
MMRESULT result = waveInOpen(&_waveIn, 0, &waveFormat, GetCurrentThreadId(), (DWORD)NULL, CALLBACK_THREAD);
//开启消息循环
//略
//消息循环中监听3个消息
switch (msg.message){
case WIM_OPEN:break;
case WIM_DATA:break;
case WIM_CLOSE:break;
}
//开启消息循环--end
return 0;
}

3.停止采集

通过发送WIM_CLOSE消息停止采集。

PostThreadMessage(GetThreadId(_hThread), WIM_CLOSE, 0, 0);

在子线程的消息循环中:

case WIM_CLOSE:
waveInStop(_waveIn);
waveInReset(_waveIn);
waveInClose(_waveIn);
break;

四、封装成对象

将采集功能封装成一个通用工具,方便在任意地方使用。

1.接口设计

接口设计如下:

#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>
        /// 声道数
        /// </summary>
        int  Channels;
        /// <summary>
        /// 采样率
        /// </summary>
        int  SampleRate;
        /// <summary>
        /// 位深
        /// </summary>
        int  BitsPerSample;
    };
    /// <summary>
    /// 声音采集设备
    /// </summary>
    class  SoundDevice {
    public:
        /// <summary>
        /// 设备Id
        /// </summary>
        int Id;
        /// <summary>
        /// 设备名称
        /// </summary>
        std::string Name;
        /// <summary>
        /// 声道数
        /// </summary>
        int Channels;
        /// <summary>
        /// 支持的格式
        /// </summary>
        std::vector<SoundFormat> SupportedFormats;
    };
    /// <summary>
    /// 声音采集对象
    /// </summary>
    /// <summary>
    /// 声音采集对象
    ///这是一个功能完整声音采集对象,所有接口通过了测试。
    ///SoundCollectorTest方法包含了所有接口的测试,修改代码后,用其验证功能是否正常。
    ///非线程安全,所有方法需确保在单线程中调用,即比如:Start和Stop不能在两个线程中同时调用。
    /// </summary>
    class  SoundCollector {
    public:
        /// <summary>
        /// 采集开始事件参数
        /// </summary>
        class  StartedEventArgs
        {
        public:
            /// <summary>
            /// 采集声音数据的格式
            /// </summary>
            SoundFormat Format;
        };
        /// <summary>
        /// 采集数据到达事件
        /// </summary>
        class  DataArrivedEventArgs :public StartedEventArgs
        {
        public:
            /// <summary>
            /// 声音数据
            /// </summary>
            unsigned char* Data;
            /// <summary>
            /// 数据长度
            /// </summary>
            int DataLength;
        };
        /// <summary>
        /// 错误事件参数
        /// </summary>
        class  ErrorEventArgs 
        {
        public:
            /// <summary>
            /// 错误内容
            /// </summary>
            std::string Message;
        };
        /// <summary>
        /// 采集开始事件
        /// </summary>
        std::function<void(void*, StartedEventArgs*)> Started;
        /// <summary>
        /// 采集数据到达事件
        /// </summary>
        std::function<void(void*, DataArrivedEventArgs*)> DataArrived;
        /// <summary>
        /// 采集结束事件
        /// </summary>
        std::function<void(void*, void*)>Stoped;
        /// <summary>
        /// 错误事件
        /// </summary>
        std::function<void(void*, ErrorEventArgs*)> Error;
        /// <summary>
        /// 构造方法
        /// </summary>
        SoundCollector();
        /// <summary>
        /// 构造方法
        /// </summary>
        /// <param name="deviceId">声音设备Id,0为默认设备</param>
        SoundCollector(int deviceId);
        /// <summary>
        ///  构造方法
        /// </summary>
        /// <param name="deviceId">声音设备Id,0为默认设备</param>
        /// <param name="channels">采集的声道数</param>
        /// <param name="sampleRate">采集的采样率</param>
        /// <param name="bitsPerSample">采集的位深</param>
        SoundCollector(int deviceId, int channels, int  sampleRate, int  bitsPerSample);
        /// <summary>
        /// 析构方法
        /// </summary>
        ~SoundCollector();
        /// <summary>
        /// 开始采集
        /// </summary>        
        bool Start(int channels, int sampleRate, int bitsPerSample);
        /// <summary>
        /// 停止采集
        /// </summary>
        void Stop();
        /// <summary>
        /// 异步停止采集
        /// </summary>
        void BeginStop();
        /// <summary>
        /// 设置采集速率
        /// </summary>
        /// <param name="timesPerSecond">采集速率单位:次/秒</param>
        void SetFrequency(int timesPerSecond);
        /// <summary>
        /// 获取采集速率,数据回调频率。
        /// </summary>
        /// <returns>采集速率,单位:次/秒</returns>
        int GetFrequency();
        /// <summary>
        /// 获取声道数
        /// </summary>
        /// <returns>声道数</returns>
        int GetChannels();
        /// <summary>
        /// 获取采样率
        /// </summary>
        /// <returns>采样率,单位:hz</returns>
        int GetSampleRate();
        /// <summary>
        /// 获取位深
        /// </summary>
        /// <returns>位深,单位:bits</returns>
        int GetBitsPerSample();
        /// <summary>
        /// 获取当前是否在采集中
        /// </summary>
        /// <returns>是否已停止</returns>
        bool GetIsStoped();
        /// <summary>
        /// 获取声音设备列表
        /// </summary>
        /// <returns></returns>
        static std::vector<SoundDevice> GetDeives();
    private:
        void* _implement = nullptr;
    };
}

五、使用示例

采集声音并保存为wav文件,其中的WavWapper对象参考《C++ 将音频PCM数据封装成wav文件

#include"SoundCollector.h"
#include "WavWapper.h"
#include<Windows.h>
int main()
{
    //运行测试程序
    //AC::SoundCollectorTest();
    //获取设备列表
    auto devices = AC::SoundCollector::GetDeives();
    if (devices.size() > 0)
    {    
        printf("设备名称:%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);
            //根据声音格式创建wav文件
            ww.CreateWavFile("sound.wav", e->Format.Channels, e->Format.SampleRate, e->Format.BitsPerSample);
        };
        sc.DataArrived = [&](auto s, auto* e) {
            //采集的数据写入wav文件
            ww.WriteToFile(e->Data, e->DataLength);
        };
        sc.Stoped = [&](auto s, auto e) {
            //关闭文件
            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);
            //结束采集
            sc.Stop();
        }
    }
}

总结

使用waveIn实现声音采集,实现过程还是相对较简单的,但还是有些细节需要注意,比如使用方法回调的方式打开设备,关闭时很容易造成死锁,经过一番尝试发现子线程中打开设备才是比较好的方式。总的来说,waveIn实现的声音采集模块是能够支持音频实时流和录制开发的。

以上就是C++使用waveIn实现声音采集的详细内容,更多关于C++ waveIn声音采集的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文