zoukankan      html  css  js  c++  java
  • Xp下麦克风设备及音量检测

    从Vista开始,windows底层的音频架构发生了改变:原本是底层API的waveXXX、mixerXXX等都在Core Audio APIs的基础上进行了重构,上升为了高层API;底层API变为Core Audio API。 由于这个原因,在利用遗留音频技术(waveXXX、mixerXXX等)进行开发的时候,在WinXp和其他系统上的表现会不太一致。

    但是如果要在Xp上进行开发的话,就必须要使用这些老旧的技术,没得选。

    Xp下音频开发选择

    在Xp下进行开发,大概只有DirectX、waveXXX和mixerXXX可选了。 这里我们简单描述它们的优缺点:

    优点:

    • DirectX: 功能强大、灵活。
    • waveXXX: 使用简单,对于输入音频设备,应用中的大部分功能需求都支持。
    • mixerXXX: 完全底层的音频控制。

    缺点:

    • DirectX: 概念多、不容易上手(灵活的代价)。
    • waveXXX: 对输入音频的控制处于应用层,无法控制系统层的音频输入(输出设备未测试)。
    • mixerXXX: 概念多并且比较抽象,API的使用很晦涩。

    Xp下输入音频开发实例

    我们选择waveXXX api来实现这个开发实例,因为waveXXX相对来说比较好用,这样我们不用花费过多的时间去了解其他概念上的细节。

    1. 设备枚举及打开

    先调用waveInGetNumDevs()获取设备总数,然后传入设备序号(0 ~ 总数-1),并选择设备支持的PCM数据格式中的一种打开设备,获取到设备句柄:

    auto inputAudioDeviceNum = waveInGetNumDevs();
    for (int i = 0; i < inputAudioDeviceNum; ++i) {
        WAVEINCAPS waveInCaps;
        auto returnValue = waveInGetDevCaps(i, &waveInCaps, sizeof(waveInCaps)) ;
    
        ......
    
        WAVEFORMATEX waveFormatEx = chooseAppropriateFormat();
        auto returnValue = waveInOpen((LPHWAVEIN)&deviceInfo.handle, index, &waveFormatEx,
                                      (DWORD_PTR)CoreAudioHelper::waveInProc,
                                      (DWORD_PTR)this, 
                                      CALLBACK_FUNCTION);
        ......
    }
    

    2. 获取输入音频数据

    为了获取音频数据,我们需要准备一个Buffer,并将这个Buffer添加到你想要获取数据的音频设备上,然后开始这个设备的音频捕获:

    bool CoreAudioHelper::startPeakGetter()
    {
        Q_ASSERT(m_currentDeviceIndex >= 0 && m_currentDeviceIndex < m_infos.size());
        auto& deviceInfo = m_infos[m_currentDeviceIndex];
    
        ZeroMemory(m_buffer, sizeof(m_buffer));
        m_waveHdr.dwFlags = 0;
        m_waveHdr.lpData = (LPSTR)m_buffer;
        m_waveHdr.dwBufferLength = sizeof(m_buffer);
    
        auto returnValue = waveInPrepareHeader(deviceInfo.handle, &m_waveHdr, sizeof(m_waveHdr));
        CHECK_RETURN(returnValue);
    
        returnValue = waveInAddBuffer(deviceInfo.handle, &m_waveHdr, sizeof(m_waveHdr));
        CHECK_RETURN(returnValue);
    
        returnValue = waveInStart(deviceInfo.handle);
        CHECK_RETURN(returnValue);
    
        deviceInfo.started = true;
        return true;
    }
    

    当这个Buffer被数据填满的时候,系统就会通知你,这时候我们需要先调用waveInUnprepareHeader()来取消先前准备的Buffer,然后就可以对数据进行操作了(这里我们计算了音频的音量大小)。在之前打开设备的时候,你可以选择多种通知方式:回调、窗口消息、事件或者线程,这里我选择使用回调方法。如果要连续的获取捕获到的数据,我们就要在Buffer被填满的时候不断添加新的Buffer。注意因为在回调中基本上不可以调用任何系统api,所以我们需要另一个线程来添加新Buffer,并利用信号量来进行同步

    void CoreAudioHelper::waveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
    {
        switch (uMsg) {
        case WIM_OPEN:
            break;
        case WIM_CLOSE:
        {
            ......
        }
        case WIM_DATA:
        {
            ......
            break;
        }
        default:
            Q_ASSERT(false && "never receive other msg!");
        }
    
    }
    
    //	non-qt thread have no qt event loop which causing signal/slot not working,
    //	we use a queue to keep the value and a semaphore to notify the internal thread
    //	to emit the signal.
    void CoreAudioHelper::appendPeakValue(qint16 value)
    {
        m_peakValueQueue.push(value);
        //	cannot call Win32 api inside a callback, so we notify the buffer waiter thread
        m_bufferFilled.release(1);
    }
    
    void CoreAudioHelper::BufferWaiterThread::run()
    {
        while (true) {
                m_helper->m_bufferFilled.acquire(1);
    
                m_helper->unprepareBuffer();
    
                if (m_helper->m_stopThread)		
                    break;
                if (m_helper->m_emitUnplugged) {
                    emit m_helper->currentDeviceUnplugged();
                    m_helper->m_emitUnplugged = false;
                    break;
                }						
                m_helper->emitPeakLevelAndContinue();
        }
    }
    
    bool CoreAudioHelper::unprepareBuffer()
    {
        auto deviceInfo = m_infos.at(m_currentDeviceIndex);
        auto returnValue = waveInUnprepareHeader(deviceInfo.handle, &m_waveHdr, sizeof(m_waveHdr));
        CHECK_RETURN(returnValue); 
        return true;
    }
    

    3. 音量大小计算

    根据PCM数据是8位还是16位,我们把Buffer中的比特数据转换成合适的变量并计算保存最小值和最大值。因为实际音频波形是以0点为水平上下波动的,
    wave-link

    • 8位PCM: 无符号数据,范围0~255, 水平值127。
    • 16位PCM: 有符号数据,范围-32767~32767,水平值0。

    我们只需要把最大波动值除以上限值就可以获得音量大小了(具体见下一小节)。

    //  buffer already filled with input audio data
    CoreAudioHelper* helper = reinterpret_cast<CoreAudioHelper*>(dwInstance);
    Q_ASSERT(helper->m_waveHdr.dwFlags & WHDR_DONE);
    
    qint32 peakMin = 255;
    qint32 peakMax = 0;
    for (char* ptr = helper->m_buffer; ptr < &helper->m_buffer[16]; ) {
        qint32 dataValue;
        if (helper->m_is8BitsSample) {
            dataValue = *(unsigned char*)ptr;
            ptr++;
        } else {
            dataValue = *(qint16*)ptr;	
            ptr += 2;
        }
        if (dataValue < peakMin)	peakMin = dataValue;
        if (dataValue > peakMax)	peakMax = dataValue;
    }
    
    helper->appendPeakValue(max(-peakMin, peakMax)); 
    

    4. 音量设置和静音

    waveXXX API只提供了音频数据捕获,因此我们需要自己来模拟音量和静音的控制,这里我们把这些控制应用在获取到的音量大小上:

    void CoreAudioHelper::emitPeakLevelAndContinue()
    {
        if (!m_peakValueQueue.empty()) {
            qint32 peakValue = m_peakValueQueue.front();
            m_peakValueQueue.pop();
    
            if (!m_infos.at(m_currentDeviceIndex).muted) {
                if (m_is8BitsSample) {
                    //	when 8-bit sample, the range is 0--255, the silence data value is 127
                    emit peakChanged(qint32(abs(peakValue - 127) / 1.27) * 
                                    m_infos.at(m_currentDeviceIndex).volumeFilterPercent);
                }
                else {
                    //	when 16-bit sample, the range  is -32767--32767, the silence data value is 0
                    emit peakChanged(qint32(abs(peakValue) / 327.67) * 
                                    m_infos.at(m_currentDeviceIndex).volumeFilterPercent);
                }
                startPeakGetter();
            }
        }
    }
    

    5. 运行结果

    结果就是这样啦,完整代码见此处

    result-link

  • 相关阅读:
    ELK 安装与配置
    redis参数与持久化原理
    python_way ,day26 django_admin 自定义
    python_way ,day25 wmi
    docker系列详解<一>之docker安装
    SpringCloud学习系列<一>版本介绍
    docker 安装MongoDB以及设置用户
    Caused by: java.lang.IllegalArgumentException
    tomcat启动时间5分钟左右org.apache.catalina.util.SessionIdGeneratorBase.createSecureRandom Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [342,445] milliseconds.
    Spring 全局异常拦截根据业务返回不同格式数据 自定义异常
  • 原文地址:https://www.cnblogs.com/lgxZJ/p/7670970.html
Copyright © 2011-2022 走看看