首页 > 其他分享 >声卡数据采集

声卡数据采集

时间:2023-11-29 17:24:09浏览次数:26  
标签:FALSE hr HRESULT 采集 声卡 RETURN NULL 数据 pAudioCaptureClient

Loopback 录制模式
在 loopback 模式下,WASAPI 的客户端可以捕获 rendering endpoint 设备(通常即声卡)正在播放的音频流。

客户端只能为共享模式流(AUDCLNT_SHAREMODE_SHARED)启用 loopback 模式。 独占模式(AUDCLNT_SHAREMODE_EXCLUSIVE)流不能在 loopback 模式下运行。

WASAPI 系统模块在软件中实现环回模式。在 loopback 模式下,WASAPI 将来自音频引擎的输出流复制到应用程序的捕获缓冲区中。

Windows 从 Vista 开始支持数字版权管理(DRM)。内容提供商依靠 DRM 来保护其专有音乐或其他内容免受未经授权的复制和其他非法使用。WASAPI 不允许 loopback 录制包含 DRM 保护内容的数字流。

无论音频源自哪个终端服务会话(session),WASAPI loopback 都包含正在播放的所有音频的混合。

Loopback 录制代码
以下是概要的 loopback 录制代码,省略类的具体实现和错误处理:

CWavFileHelper g_recWavFile;
void onAudioCaptured(BYTE* pData, DWORD len) 
{
    g_recWavFile.append((const char*)pData, len);
}

int _tmain(int argc, _TCHAR* argv[]) 
{
    HRESULT hr = E_FAIL;
    hr = CoInitialize(NULL);
    
    LoopackAudCap audCap;
    hr = audCap.init(onAudioCaptured);
    hr = g_recWavFile.create(argv[1], *audCap.getWavFormat());
    hr = audCap.start();
    
    _tprintf(_T("Started recording...press Enter to stop recording.\n"));   
    
    char ch = getchar(); // wait for keyboard input and then stop the recording
    hr = audCap.stop();
    
    audCap.finaize();
    g_recWavFile.close();
    CoUninitialize();
    return hr;
}


LoopackAudCap::init 函数
typedef void (*PFON_AUD_CAPTURED)(BYTE* pData, DWORD len);

HRESULT init(PFON_AUD_CAPTURED pCallback)
{
    HRESULT hr = E_FAIL;
    CComPtr<IMMDevice> pSpeaker = NULL;
    MMDeviceHelper device;
    WAVEFORMATEX *pwfx = NULL;
    
    m_pCallback = pCallback;
    m_hStartEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    
    hr = device.getDefaultSpeaker(&pSpeaker);
    hr = pSpeaker->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&m_audioClient);
    hr = m_audioClient->GetMixFormat(&pwfx);
    hr = m_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, RECORD_BUF_DURATION, 0, pwfx, NULL);
    if (hr == AUDCLNT_E_DEVICE_IN_USE) 
        DL_E0("The audio endpoint is in exclusive mode and can not be used now!");
    GOTO_LABEL_IF_FAILED(hr, OnErr);
    
    m_hThread = CreateThread(NULL, 0, _loopbackCapThread, this, 0, NULL);
    m_pWavFormat = pwfx;
    m_isDisposing = false;
    return S_OK;
OnErr:
    SAFE_CLOSE_HANDLE(m_hStartEvent);
    SAFE_CLOSE_HANDLE(m_hStopEvent);
    if (NULL != pwfx)
        CoTaskMemFree(pwfx);
    m_audioClient = NULL;
    return hr;
}


MMDeviceHelper::getDefaultSpeaker 函数
GetDefaultAudioEndpoint API 需要两个输入参数 dataFlow 和 role,用来指定要获取的 Audio Endpoint 设备。dataflow 包含两个选项 eRender 和 eCapture,role 包含三个选项 eConsole、eMultimedia 和 eCommunications,具体请参考 MSDN。

HRESULT getDefaultSpeaker(IMMDevice **ppMMDevice)
{
    HRESULT hr = S_OK;
    *ppMMDevice = NULL;

    CComPtr<IMMDeviceEnumerator> pMMDeviceEnumerator = NULL;
    hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, 
        __uuidof(IMMDeviceEnumerator), (void**)&pMMDeviceEnumerator);
    RETURN_IF_FAILED(hr);

    hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, ppMMDevice);
    RETURN_IF_FAILED(hr);

    RETURN_IF_NULL_EX(*ppMMDevice, HRESULT_LAST_ERROR());
    return S_OK;
}


LoopackAudCap::_loopbackCap 函数
上面 _loopbackCapThread 线程函数调用该函数实现具体的声卡数据捕获功能。
MMCSS 的说明请看 MSDN。

HRESULT _loopbackCap()
{
    // register with MMCSS
    DWORD nTaskIndex = 0;
    HANDLE hTask = AvSetMmThreadCharacteristics(_T("Audio"), &nTaskIndex);
    
    HANDLE hWakeUp = CreateWaitableTimer(NULL, FALSE, NULL);
    
    UINT32 bufferFrameCount = 0;
    hr = m_audioClient->GetBufferSize(&bufferFrameCount);
    REFERENCE_TIME hnsActualDuration = (REFERENCE_TIME)
        ((double)RECORD_BUF_DURATION * bufferFrameCount / m_pWavFormat->nSamplesPerSec);
        
    LARGE_INTEGER liFirstFire;
    liFirstFire.QuadPart = -m_hnsDefaultDevicePeriod / 2; // negative means relative time
    LONG lTimeBetweenFires = (LONG)(hnsActualDuration / REFTIMES_PER_MILLISEC / 2);
    BOOL bOK = SetWaitableTimer(hWakeUp, &liFirstFire, lTimeBetweenFires, NULL, NULL, FALSE);
    
    DWORD dwWaitResult = WaitForSingleObject(m_hStartEvent, INFINITE);
    
    hr = m_audioClient->Start();
    
    HANDLE waitArray[] = { m_hStopEvent, hWakeUp };
    CComPtr<IAudioCaptureClient> pAudioCaptureClient = NULL;
    hr = m_audioClient->GetService(__uuidof(IAudioCaptureClient), (void**)&pAudioCaptureClient);
    
    while (true) {
        hr = _capture(pAudioCaptureClient);
        dwWaitResult = WaitForMultipleObjects(ARRAYSIZE(waitArray), waitArray, FALSE, INFINITE);
        if (m_isDisposing)
            break;
            
        if (WAIT_OBJECT_0 == dwWaitResult)
            dwWaitResult = WaitForSingleObject(m_hStartEvent, INFINITE);
    }
    return hr;
}


LoopackAudCap::_capture 函数
只要有声卡数据就榨干 (ˉ^ˉ),回调函数负责写入文件。

HRESULT _capture(IAudioCaptureClient* pAudioCaptureClient)
{
    HRESULT hr = E_FAIL;
    
    // drain data while it is available
    UINT32 nNextPacketSize = 0;
    for (hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize);
        SUCCEEDED(hr) && nNextPacketSize > 0;
        hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize))
    {
        BYTE *pData = NULL;
        UINT32 nNumFramesToRead = 0;
        DWORD dwFlags = 0;
        hr = pAudioCaptureClient->GetBuffer(&pData, &nNumFramesToRead, &dwFlags, NULL, NULL);
        RETURN_IF_FAILED(hr);
        
        LONG lBytesToWrite = nNumFramesToRead * m_pWavFormat->nBlockAlign;
        if ((dwFlags & AUDCLNT_BUFFERFLAGS_SILENT) == AUDCLNT_BUFFERFLAGS_SILENT)
            memset(pData, 0, lBytesToWrite);
            
        m_pCallback(pData, lBytesToWrite);
        
        hr = pAudioCaptureClient->ReleaseBuffer(nNumFramesToRead);
        RETURN_IF_FAILED(hr);
    }
    
    return hr;
}


LoopackAudCap::start & stop 函数
Loopback 录制是由单独线程执行的,所以外部调用方可以随时 start 或 stop 录制,且 stop 之后可以重新 start 录制。

HRESULT start()
{
    RETURN_IF_NULL(m_hStartEvent);
    RETURN_IF_NULL(m_hThread);
    SetEvent(m_hStartEvent);
    return S_OK;
}

HRESULT stop()
{
    RETURN_IF_NULL(m_hStopEvent);
    SetEvent(m_hStopEvent);
    return S_OK;
}

CWavFileHelper::close 函数
录制结束以后需要对 wav 文件头做收尾工作,即填充 wav 文件的内容长度。

void close()
{
    if (NULL != m_hFile) {
        if (m_isWrite) {
            MMRESULT mRes = mmioAscend(m_hFile, &m_chunkData, 0);
            PRINT_ERROR_LOG_IF_FALSE(mRes == MMSYSERR_NOERROR, mRes);

            mRes = mmioAscend(m_hFile, &m_chunkRIFF, 0);
            PRINT_ERROR_LOG_IF_FALSE(mRes == MMSYSERR_NOERROR, mRes);
        }

        mmioClose(m_hFile, 0);
        m_hFile = NULL;
    }

    SAFE_DELETE_ARRAY(m_data);
}

标签:FALSE,hr,HRESULT,采集,声卡,RETURN,NULL,数据,pAudioCaptureClient
From: https://www.cnblogs.com/kn-zheng/p/17865367.html

相关文章

  • Pandas数据框操作进阶
    Pandas为Python营造了一个高水平的操作环境,还提供了便于操作的数据结构和分析工具。无需更多介绍,Pandas已经是Python中数据分析的常用工具了。作为一个数据科学家,Pandas是我日常使用的工具,我总会惊叹于它强大的功能,并且极大提升了工作效率的Pandas技巧。对于pandas新手而言,Pandas......
  • go数据类型-sync.map
    定义在runtime的sync.map包中有定义:typeMapstruct{ muMutex//锁 readatomic.Pointer[readOnly]//包含了readOnly类型的一个struct,下方把Pointer也贴了 dirtymap[any]*entry //一个map存储数据 missesint//错过、没有命中}//readOnlyisanim......
  • NET 元组(Tuple)数据结构
    .NET中的元组(Tuple)是一种数据结构,用于将多个不同类型的值组合成单个复合值。这使得你可以在没有创建专门的类或结构体的情况下,从方法中返回多个值,或者在多个部分之间传递一组值。.NET提供了两种主要的元组类型:System.Tuple类这是.NETFramework4.0中引入的早期元组类型。......
  • 锚索测力计与振弦采集仪组成桥梁安全监测
    锚索测力计与振弦采集仪组成桥梁安全监测在桥梁工程中,安全监测一直是一个重要的方面。桥梁安全监测可以及时发现桥梁的变形、裂缝、位移等问题,为及时修复或维修提供重要的依据。而锚索测力计和振弦采集仪作为桥梁安全监测的两个主要工具,发挥着至关重要的作用。 锚索测力计是......
  • 数据仓库理论
    数据仓库理论数仓是一种思想,数仓是一种规范,数仓是一种解决方案!1、数据处理方式数据处理大致可以分为两大类:联机事务处理:OLTP(On_LineTransactionProcessing)、联机分析处理OLAP(On_LineAnalyticalProcessing)OLTP(On_LineTransactionsProcessing):中文名称是联机......
  • 用C#实现的几种常用数据校验方法整理(CRC校验;LRC校验;BCC校验;累加和校验)
    CRC即循环冗余校验码(CyclicRedundancyCheck):是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完......
  • 3.2.7 数据列属性
    一、功能简介当单元格内容为数据列时,选中单元格,右侧「单元格元素」面板就会出现数据列属性设置框,或者双击单元格也可弹出数据列属性设置对话框,如下图所示:二、基本属性数据列的基本属性包括「选择数据列、父格设置、数据设置、扩展方向」四类设置:1.选择数据列「选择数据列......
  • python连接数据库(连MySQL)
    Python操作和连接数据库原创 阳阳 Python小例子 2023-10-1109:20 发表于湖北在Python中,你可以使用不同的库来操作和连接数据库,最常用的是sqlite3、MySQLdb和psycopg2。使用sqlite3连接和操作SQLite数据库:import sqlite3# 连接数据库conn = sqlite3.connect('......
  • 从一个txt文件中读取数据,并且再把读取到的数据修改为想要的格式,再逐行写入到另一个txt
    packageservice;importjava.io.BufferedWriter;importjava.io.File;importjava.io.FileWriter;importjava.io.IOException;importjava.nio.file.Files;importjava.nio.file.Paths;importjava.util.List;/***从一个txt文件中读取数据,并且再把读取到的数据修改为想要的格......
  • MySQL Shell连接数据库报MySQL Error 1045 (28000)错误浅析
    这里简单总结一下mysqlshell访问数据库时报MySQLError1045(28000):Accessdeniedforuser'root'@'::1'(usingpassword:YES)的原因以及如何解决这个问题这里测试的环境为MySQL8.0.35,我们先来看看报错案例:$mysqlsh-hlocalhost-P7306-uroot-pPleaseprovidethep......