音频捕获

DSound音频和系统声音钩子(Hooker)实践
因工作需要,折腾了一下DSound钩子技术。在折腾之前,我也尝试在网络上搜索相关的音频HOOk技术,但搜索到的,都是要收费,而老板不愿花钱。不用收费的,也只是说”立体声混音”的使用,且与我现有项目要求相差很大,类似这样的功能,我们软件本来就有支持,但XP和Win7下都有很大问题。

没有办法,追求完美的用户体验的话,就不能偷懒,只能花大力气写一个DSound的HOOK技术了。

在分享成果之前,先说一下需求:

  • 1.可以实现QQMusic、Kugou等专业播放器的音频截获。
  • 2.输入任意采样率的音频数据,像32k,44k,48k等立体声或单声道声音。
  • 3.保存成Wav文件。

需求很简单,实现很困难。首先这方面的资料非常少,也有很多人都问这方面问题,但几乎没有人知道怎样做才可行,也许有人知道,但不愿说出来,例如像YY语音就实现播放器的音频截取,只是它们不可能告诉你怎样做。

以下是我根据自已的实现过程,把它总结出来。

  • 1.首先要了解一下DSound的播放器实现过程。把握好DSound的接口调用顺序或其之间的使用关系,这一点对初学者是非常重要的,因为Dsound内部管理的是一个环状数据缓冲区,如果不了解这些接口的调用原则的话,你根本无法获取到正确的数据或者说你获取的数据有时会出现杂音或断音,一听就知道不正常。
  • 2.DSound中的几个核心接口有:Lock()\Unlock()\GetFormat()\GetCurrentPosition()\SetFormat()\GetCaps()这几个都比较重要,我都用到。
  • 3.必须HOOK的DSound接口有Lock()\UnLock()\SetFormat()\Release().Lock()和UnLock()是获取数据,SetFormat是音频数据格式,Release是对象管理。特别是Release这个接口。因为在播放器中可能会存在多个不同DS对象,都各自有不同的生存周期,那为了避免音频数据互相串错的可能性,你需要在截取到音频数据时,都要保存到各自队列中,最后再合成一个音频。例如有两个DSBuffer对象A和B,它们都会调用Lock和Unlock来填充音频数据。那你需要把这两个对象的声音各自保存起来,然后在恰当的时机混合成一个立体声或单声道数据。
  • 3.直接通过DirectSoundCreate或CoCreateInstance ().这两个对象是获取DSB对象IDirectSoundBuffer。
  • 4.这时Detours库是一个比较好的API重定向开源库,我们需要用它来修改IDirectSoundBuffer中所虚函数表,也即是lpVtbl。并非需要修改所有函数,只需要修改上述3中提到的几个接口就足够了。(Detours是微软的开源库,可以去它官网下载。)
  • 5.创建一条线程,专门处理多个DSB对象中截取到的数据,在混音前先将多个对象的数据一致化。假如我们最终要的音频数据是44.1K的立体声16位数据,则每DSB对象数据均需先转换成该类型音频数据后,才能进行混音。
    在实际的测试中QQMusic和Kugou在切换歌曲时都会有那么一瞬间是同时存在两个DSB对象。因为一个正在销废,另一个正在创建。如果想偷懒,可以不考虑多个DSB对象同时存在并混音的情况。只是我在实现过程中考虑得比较复杂并且也实现了。
  • 6.音频重采样和音频数据转换,例如:采样率为32K的32位的立体声数据转换成采样率44.1K的 16位的单声道数据。这个不是DSound捕获的范围,也比较简单,有需要的可以直接联系我,或直接谷歌或百度搜索”音频重采样算法”,这类方法网上比较多。
  • 7.保存成Wav文件,那网上更多,我也不说了。

补充:后来集成到项目中,在原有代码上增加,进程数据同步功能及系统声音捕获功能,以下DEMO已经集成了XP和WIN7的音频捕获,包括以下几种模式:
1.只捕获麦克风,
2.仅捕获伴奏(伴奏来源分:系统声音和播放器如酷狗)
3.支持卡拉OK模式(伴奏来源分:系统声音和播放器如酷狗)。

补充:xp的系统音频捕获比较复杂,主要是立体音混音模式依赖硬件比较大,而主流的硬件就有两大类,同时还要考虑多声卡的情况(例如部份主播采用的多声卡方式,系统声卡作为声音播放,USB声卡作为麦克风采集),这样处理的算法逻辑就更复杂了。

如果有缘者,有不懂的地方,可以加Q(442559691),大家交流交流。

百度网盘:链接:https://pan.baidu.com/s/1QkoQYvUlUHJdyrQd8pRkyA
提取码:ex1l
效果图