谈谈Windows下的音频采集

  对于Windows的音频采集来讲,方法有不少。能够经过较为上层的多媒体框架去采集,如DirectShow、MediaFoundation这些;也能够经过较为底层的API去采集,如WASAPI(Windows Audio Session API)。这里我介绍的是使用WASAPI的方法,毕竟采集和渲染均可以在这里去实现了。缓存

WASAPI的简单介绍

  这里简单介绍一下使用这个API来采集须要了解的两个接口:bash

  • IAudioClient:音频使用端,用于建立并初始化音频流
  • IAudioCaptureClient:音频采集端,提供了采集音频的接口

独占模式和共享模式

  咱们先来看看Windows下的音频框架关系图:框架

从图中咱们能够看到,音频流会有两条路,一条是走向shared mode,一条则是exclusive mode,分别表明着共享模式和独占模式。对于独占模式这条路线,咱们能够看到它直接连到了Audio Driver,也就是音频驱动中了,而共享模式还须要通过一个Audio Engine的过程,这个Audio Engine最终会把当前的音频流,还有其它来自其它应用的音频流进行混音,最后才传送到Audio Driver中去。
  对于混音,若是两条音频流的采样率不一样的话,确定要先对某些音频流去作重采样。当高采样率向低采样率转换时,就会发生精度的丢失,而对于独占模式来讲,因为独占了一路音频流,所以无需进行这一步重采样的过程。所以,对于独占模式来讲,通常音质效果确定是要好一些的。
  至于如何选择,仍是得看本身的实际需求。对于音频采集来讲,咱们大部分状况下都会去选择共享模式,这样咱们能够同时采集到游戏里的声音,音乐播放器所播放的声音等等。

采集模式

  WASAPI有两种采集模式,一种是输入设备的采集,如带麦克风的耳机,麦克风这些;还有一种则是声卡输出的采集,也称做loop back mode。因为两种采集模式是分开的,若是须要同时听到输入设备的声音和声卡输出的声音,则须要对采集到的两路音频流进行混音。oop

WASAPI的简单使用

获取音频设备枚举实例:

  首先,咱们须要经过COM接口来获取音频设备枚举实例,代码以下:ui

HRSULT hr;
IMMDeviceEnumerator* pEnumerator = NULL;
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pEnumerator));
复制代码

枚举音频设备

  枚举音频设备,咱们能够经过EnumAudioEndPoints来一个个进行枚举并选择合适的,但更多状况下咱们是直接使用GetDefaultAudioEndpoint来获取一个默认的,且处于active状态的音频设备。代码以下:spa

IMMDevice* pDevice = NULL;
hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice);
复制代码

  获取完音频设备以后,咱们能够直接调用其activate方法(相似于工厂模式的用法),来获取IAudioClient:code

IAudioClient* pAudioClient = NULL;
hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL,(void**) &pAudioClient);
复制代码

获取音频格式

  因为咱们使用的是共享模式,所以咱们须要得到Audio Engine的混音格式,经过调用GetMixFormat来得到,代码以下:orm

WAVEFORMATEX* pwfx = NULL;
hr = pAudioClient->GetMixFormat(&pwfx);
复制代码

其中,WAVEFORMATEX的结构体就包含了咱们所须要的混音格式,里面有音频格式,声道数,采样率,样本大小等信息。其中音频格式还须要将这个结构体扩展(也就是强转)成WAVEFORMATEXTENSIBLE这个结构体才能得到,这部分具体可参阅文档。cdn

初始化

  接下来就是初始化了,即调用Initialize接口。这里面的参数仍是须要特别注意的,咱们先来看看有哪些参数:blog

HRESULT Initialize(
  AUDCLNT_SHAREMODE  ShareMode,
  DWORD              StreamFlags,
  REFERENCE_TIME     hnsBufferDuration,
  REFERENCE_TIME     hnsPeriodicity,
  const WAVEFORMATEX *pFormat,
  LPCGUID            AudioSessionGuid
);
复制代码

  对于第一个参数,以前咱们也说了,咱们是在共享模式下进行采集的,所以这里填AUDCLNT_SHAREMODE_SHARED。
  对于第二个参数,若是是初始化输入设备的采集的话,直接填NULL便可。若是是采集声卡输出的,即loop back模式的话,则须要填入AUDCLNT_STREAMFLAGS_LOOPBACK。
  第三个是咱们的缓存大小,对于采集到的音乐,WASAPI会帮咱们放到这个缓存中。至于要设置成多大,其实通常是根据你的采集周期来肯定的,你能够根据实际状况简单计算一下,设置成稍微比实际的大一点就好,这样能够避免溢出致使数据丢失的状况。
  第四个参数是用于独占模式的,咱们这里用不到,直接填0。
  第五个参数就是咱们以前得到的混音格式。
  最后一个参数是指定音频Session,这里咱们不关心这个,直接填NULL,让它本身帮咱们建立一个Session,并把咱们的音频流加入到其中。

获取音频采集接口,开始采集

  最后一步即是获取咱们的采集接口,并开始采集,代码以下:

IAudioCaptureClient* pCaptureClient = NULL;
hr = pAudioClient->GetService(
	IID_IAudioCaptureClient,
	(void**)&pCaptureClient);
hr = pAudioClient->Start();
复制代码

获取采集到的数据

  咱们经过GetNextPacketSize来判断下一个数据包的大小,若是不为0的话,咱们就经过GetBuffer来获取一个数据包,依此类推,直到GetNextPacketSize返回的大小为0,说明咱们这一个周期的数据已经获取完了,此时咱们能够休眠一个周期,而后再去获取数据。代码以下:

while(1)
{
    //休眠一个周期
    Sleep(duration);
    hr = pCaptureClient->GetNextPacketSize(&packetLength);
    while(packetLength)
    {
    	hr = pCaptureClient->GetBuffer(
	    &pData,
	    &numFramesAvailable,
	    &flags, NULL, NULL);
	    //DoSomething()....
	    hr = pCaptureClient->ReleaseBuffer(numFramesAvailable);
	    hr = pCaptureClient->GetNextPacketSize(&packetLength);
    }
}
复制代码

中止采集

  结束采集后调用stop方法便可:

hr = pAudioClient->Stop();
复制代码

输入设备与loop back混音

  若是要同时听到人说话的声音和电脑里的声音,就须要对这两路音频流进行混音,即对两路音频流按必定的比例进行叠加。这一部分能够由第三方的媒体库来完成,如ffmpeg,咱们能够利用amix滤镜来实现。若是要调整音量的话,能够加上volume滤镜来去调节。具体细节不属于本节范畴,所以就不展开细讲了。   须要注意的是,当电脑的中没有产生声音时,咱们是获取不到音频数据的,也就是GetNextPacketSize返回的长度是0.对于混音来讲,所传入的音频数据的长度应该是要对等的,传入采集到的输入设备的一秒钟的音频数据,对应loop back来讲对应的确定也得是一秒钟的音频数据。所以,对于loop back的采集来讲,若此刻只有麦克风的输入,咱们能够适当的填充一些silence字节,来表示这部分是没有声音的,而后再做为输入去混音便可。

相关文章
相关标签/搜索