DirectSound初步教程 -- 如何从第三发音乐播放器中HOOK音频数据然后跟自己麦采集数据做同步

原创 2014年09月12日 12:46:06

DirectSound初步教程 -- 如何从第三发音乐播放器中HOOK音频数据然后跟自己麦采集数据做同步

首先看看基本知识:

Directsound中常用的几个对象

对象数量作用主要接口
设备对象每个应用程序只有一个设备对象用来管理设备,创建辅助缓冲区IDirectSound8
辅助缓冲区对象每一个声音对应一个辅助缓冲区,可以有多个辅助缓冲区用来管理一个静态的或者动态的声音流,然后在主缓冲区中混音IDirectSoundBuffer8, 
IDirectSound3DBuffer8,
IDirectSoundNotify8
主缓冲区对象一个应用程序只有一个主缓冲区将辅助缓冲区的数据进行混音,并且控制3D参数.IDirectSoundBuffer, IDirectSound3DListener8
特技对象没有来辅助缓冲的声音数据进行处理8个特技接口IDirectSoundFXChorus8

首先,要创建一个设备对象,然后通过设备对象创建缓冲区对象。辅助缓冲区由应用程序创建和管理,DirectSound会自动地创建和管理主缓冲区,一般来说,应用程序即使没有获取这个主缓冲区对象的接口也可以播放音频数据,但是,如果应用程序要想得到IDirectSound3DListener8接口,就必须要自己创建一个主缓冲区。

使用静态的缓冲区

  如果我们的wave文件不是很大,那么我们就可以使用静态的缓冲区了。 

包含全部音频数据的缓冲区我们称为静态的缓冲区,尽管,不同的声音可能会反复使用同一个内存buffer,但严格来说,静态缓冲区的数据只写入一次。

静态缓冲区的创建和管理和流缓冲区很相似,唯一的区别就是它们使用的方式不一样,静态缓冲区只填充一次数据,然后就可以play,然而,流缓冲区是一边play,一边填充数据。

给静态缓冲区加载数据分下面几个步骤

1、调用IDirectSoundBuffer8::Lock函数来锁定所有的内存,你要指定你锁定内存中你开始写入数据的偏移位置,并且取回该偏移位置的地址。

2、采用标准的数据copy方法,将音频数据复制到返回的地址。

3、调用IDirectSoundBuffer8::Unlock.,解锁该地址。

下面我给出使用static buffer 播放wav文件的完整代码,首先定义我们需要的一些对象:


LPDIRECTSOUNDBUFFER8 g_pDSBuffer8 = NULL; //buffer
LPDIRECTSOUND8 g_pDsd = NULL; //dsound
CWaveFile *g_pWaveFile= NULL;
//下面初始化DirectSound工作。
HRESULT hr;
if(FAILED(hr = DirectSoundCreate8(NULL,&g_pDsd,NULL)))
return FALSE;
//设置设备的协作度
if(FAILED(hr = g_pDsd->SetCooperativeLevel(m_hWnd,DSSCL_PRIORITY)))
return FALSE;


g_pWaveFile = new CWaveFile;
g_pWaveFile->Open(_T("d:\\test.wav"),NULL,WAVEFILE_READ);
DSBUFFERDESC dsbd;
ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) );
dsbd.dwSize = sizeof(DSBUFFERDESC);
dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLFX| DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
dsbd.dwBufferBytes = g_pWaveFile->GetSize();//MAX_AUDIO_BUF * BUFFERNOTIFYSIZE ; 
dsbd.lpwfxFormat = g_pWaveFile->m_pwfx;
LPDIRECTSOUNDBUFFER lpbuffer;
//创建辅助缓冲区对象
if(FAILED(hr = g_pDsd->CreateSoundBuffer(&dsbd,&lpbuffer,NULL)))
return ;
if( FAILED( hr = lpbuffer->QueryInterface( IID_IDirectSoundBuffer8, (LPVOID*) &g_pDSBuffer8) ) )
return ; 
lpbuffer->Release();
//准备工作做完了,下面就开始播放了
LPVOID lplockbuf;
DWORD len;
DWORD dwWrite;


g_pDSBuffer8->Lock(0,0,&lplockbuf,&len,NULL,NULL,DSBLOCK_ENTIREBUFFER);
g_pWaveFile->Read((BYTE*)lplockbuf,len,&dwWrite);
g_pDSBuffer8->Unlock(lplockbuf,len,NULL,0);
g_pDSBuffer8->SetCurrentPosition(0);
g_pDSBuffer8->Play(0,0,DSBPLAY_LOOPING);

流缓冲区用来播放那些比较长的音频文件,因为数据比较长,没法一次填充到缓冲区中,一边播放,一边将新的数据填充到DirectSound的缓冲区中。

可以通过IDirectSoundBuffer8::Play函授来播放缓冲区中的内容,注意在该函数的参数中一定要设置DSBPLAY_LOOPING标志。

通过IDirectSoundBuffer8::Stop方法中断播放,该方法会立即停止缓冲区播放,因此你要确保所有的数据都被播放,你可以通过拖动播放位置或者设置通知位置来实现。

将音频流倒入缓冲区需要下面三个步骤

1、确保你的缓冲区已经做好接收新数据的准备。你可以拖放播放的光标位置或者等待通知

2、调用IDirectSoundBuffer8::Lock.函数锁住缓冲区的位置,这个函数返回一个或者两个可以写入数据的地址

3、使用标准的copy数据的方法将音频数据写入缓冲区中

4、IDirectSoundBuffer8::Unlock.,解锁

这里我要讲一下DirectSound的通知机制。因为Stream buffer 大小只够容纳一部分数据,因此,在播放完缓冲区中的数据后,DirectSound就会通知应用程序,将新的数据填充到DirectSound的缓冲区中。假如我们设置DirectSound的buffersize 为1920*4,如下图


我们可以给DirectSound设置一个事件,并且设置buffer通知大小,如下:



HANDLE g_event[MAX_AUDIO_BUF];
for(int i =0; i< MAX_AUDIO_BUF;i++)
{
g_aPosNotify[i].dwOffset = i* BUFFERNOTIFYSIZE ;
g_aPosNotify[i].hEventNotify = g_event[i];
}
if(FAILED(hr = g_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID *) &g_pDSNotify )))
return ;
g_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF,g_aPosNotify);
g_pDSNotify->Release();


当DirectSound播放到buffer的1920,3840,5760,7680等位置时,Directsound就会通知应用程序,将g_ event,设置为通知态,应用程序就可以通过WaitForMultipleObjects 函数等待DirectSound的通知,将数据填充到DirectSoun的辅助缓冲区。

下面我给出Stream buffer 播放wave文件的代码。
#define MAX_AUDIO_BUF 4
#define BUFFERNOTIFYSIZE 1920
LPDIRECTSOUNDBUFFER8 g_pDSBuffer8 = NULL; //buffer
LPDIRECTSOUND8 g_pDsd = NULL; //dsound
CWaveFile *g_pWaveFile= NULL;
BOOL g_bPlaying = FALSE; //是否正在播放
LPDIRECTSOUNDNOTIFY8 g_pDSNotify = NULL; 
DSBPOSITIONNOTIFY g_aPosNotify[MAX_AUDIO_BUF];//设置通知标志的数组
HANDLE g_event[MAX_AUDIO_BUF];
DWORD g_dwNextWriteOffset = 0;


//初始化DirectSound
HRESULT hr;
if(FAILED(hr = DirectSoundCreate8(NULL,&g_pDsd,NULL)))
return FALSE;
if(FAILED(hr = g_pDsd->SetCooperativeLevel(m_hWnd,DSSCL_PRIORITY)))
return FALSE;


g_pWaveFile = new CWaveFile;
g_pWaveFile->Open(_T("d:\\test.wav"),NULL,WAVEFILE_READ);


DSBUFFERDESC dsbd;
ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) );
dsbd.dwSize = sizeof(DSBUFFERDESC);
dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
dsbd.dwBufferBytes = MAX_AUDIO_BUF * BUFFERNOTIFYSIZE ; 
dsbd.lpwfxFormat = g_pWaveFile->m_pwfx;
LPDIRECTSOUNDBUFFER lpbuffer;
//创建DirectSound辅助缓冲区
if(FAILED(hr = g_pDsd->CreateSoundBuffer(&dsbd,&lpbuffer,NULL)))
return FALSE;
if( FAILED( hr = lpbuffer->QueryInterface( IID_IDirectSoundBuffer8, (LPVOID*) &g_pDSBuffer8) ) )
return FALSE; 
lpbuffer->Release();
//设置DirectSound通知 机制
for(int i =0; i< MAX_AUDIO_BUF;i++)
{
g_aPosNotify[i].dwOffset = i* BUFFERNOTIFYSIZE ;
g_aPosNotify[i].hEventNotify = g_event[i];
}
if(FAILED(hr=g_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*) g_pDSNotify )))
return ;
g_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF,g_aPosNotify);
g_pDSNotify->Release();
ok,在下面的play函数中,我们就要单独启动一个线程,来播放了
void OnBnClickedButtonPlay()
{
g_bPlaying =TRUE;
g_pWaveFile->ResetFile();
CreateThread(0,0,PlayThread,this,NULL,NULL);
}
//停止播放音频
void CDsoundEffectDemoDlg::OnBnClickedButtonStop()
{
// TODO: 在此添加控件通知处理程序代码
g_bPlaying =FALSE; 
Sleep(500);
g_pDSBuffer8->Stop();
}

下面我们看看我们的播放线程,在线程里,我们首先将音频数据填充到DirectSound的辅助缓冲区中,然后调用DirectSound buffer 的play方法,开始播放,然后就在WaitForMultipleObjects 等待DirectSound的通知吧,然后读取wave文件将数据填充到DirectSound的空buffer中。



DWORD WINAPI PlayThread(LPVOID lpParame)
{
DWORD res;
LPVOID lplockbuf;
DWORD len;
DWORD dwWrite;


g_pDSBuffer8->Lock(0,0,&lplockbuf,&len,NULL,NULL,DSBLOCK_ENTIREBUFFER);
g_pWaveFile->Read((BYTE*)lplockbuf,len,&dwWrite);
g_pDSBuffer8->Unlock(lplockbuf,len,NULL,0);
g_pDSBuffer8->SetCurrentPosition(0);
g_pDSBuffer8->Play(0,0,DSBPLAY_LOOPING);
g_dwNextWriteOffset = 0;
while(g_bPlaying)
{
res = WaitForMultipleObjects (MAX_AUDIO_BUF, g_event, FALSE, INFINITE);
if(res > WAIT_OBJECT_0) 
ProcessBuffer();
}


return 0;
}

下面的函数主要是给空的DirectSound缓冲区填充 音频数据。

void ProcessBuffer()
{
DWORD dwBytesWrittenToBuffer = 0;
VOID* pDSLockedBuffer = NULL;
VOID* pDSLockedBuffer2 = NULL;
DWORD dwDSLockedBufferSize;
DWORD dwDSLockedBufferSize2;
HRESULT hr; 
g_pDSBuffer8->Lock(g_dwNextWriteOffset,BUFFERNOTIFYSIZE,&pDSLockedBuffer,&dwDSLockedBufferSize, &pDSLockedBuffer2,&dwDSLockedBufferSize2,0);
if(hr == DSERR_BUFFERLOST)
{
g_pDSBuffer8->Restore();
g_pDSBuffer8->Lock(g_dwNextWriteOffset,BUFFERNOTIFYSIZE,&pDSLockedBuffer,&dwDSLockedBufferSize,
&pDSLockedBuffer2,&dwDSLockedBufferSize2,0);
}
if(SUCCEEDED(hr))
{
//write
g_pWaveFile->Read((BYTE*)pDSLockedBuffer,dwDSLockedBufferSize,&dwBytesWrittenToBuffer);
g_dwNextWriteOffset += dwBytesWrittenToBuffer;


if (NULL != pDSLockedBuffer2) 

g_pWaveFile->Read((BYTE*)pDSLockedBuffer2,dwDSLockedBufferSize2,&dwBytesWrittenToBuffer);
g_dwNextWriteOffset += dwBytesWrittenToBuffer;

g_dwNextWriteOffset %= (BUFFERNOTIFYSIZE * MAX_AUDIO_BUF);


if(dwBytesWrittenToBuffer <BUFFERNOTIFYSIZE )
{
FillMemory( (BYTE*) pDSLockedBuffer + dwBytesWrittenToBuffer, 
BUFFERNOTIFYSIZE - dwBytesWrittenToBuffer, 
(BYTE)(g_pWaveFile->m_pwfx->wBitsPerSample == 8 ? 128 : 0 ) );


g_bPlaying = FALSE; 
}
hr = g_pDSBuffer8->Unlock(pDSLockedBuffer,dwDSLockedBufferSize,
pDSLockedBuffer2,dwDSLockedBufferSize2);
}
}

下面进入重点;

       如何从第三发音乐播放器中HOOK音频数据然后跟自己麦采集数据做同步:
分析DSOUND中的API
DECLARE_INTERFACE_(IDirectSoundBuffer, IUnknown)
{
    // IUnknown methods
    STDMETHOD(QueryInterface)       (THIS_ __in REFIID, __deref_out LPVOID*) PURE;
    STDMETHOD_(ULONG,AddRef)        (THIS) PURE;
    STDMETHOD_(ULONG,Release)       (THIS) PURE;


    // IDirectSoundBuffer methods
    STDMETHOD(GetCaps)              (THIS_ __out LPDSBCAPS pDSBufferCaps) PURE;
    STDMETHOD(GetCurrentPosition)   (THIS_ __out_opt LPDWORD pdwCurrentPlayCursor, __out_opt LPDWORD pdwCurrentWriteCursor) PURE;
    STDMETHOD(GetFormat)            (THIS_ __out_bcount_opt(dwSizeAllocated) LPWAVEFORMATEX pwfxFormat, DWORD dwSizeAllocated, __out_opt LPDWORD pdwSizeWritten) PURE;
    STDMETHOD(GetVolume)            (THIS_ __out LPLONG plVolume) PURE;
    STDMETHOD(GetPan)               (THIS_ __out LPLONG plPan) PURE;
    STDMETHOD(GetFrequency)         (THIS_ __out LPDWORD pdwFrequency) PURE;
    STDMETHOD(GetStatus)            (THIS_ __out LPDWORD pdwStatus) PURE;
    STDMETHOD(Initialize)           (THIS_ __in LPDIRECTSOUND pDirectSound, __in LPCDSBUFFERDESC pcDSBufferDesc) PURE;
    STDMETHOD(Lock)                 (THIS_ DWORD dwOffset, DWORD dwBytes,
                                           __deref_out_bcount(*pdwAudioBytes1) LPVOID *ppvAudioPtr1, __out LPDWORD pdwAudioBytes1,
                                           __deref_opt_out_bcount(*pdwAudioBytes2) LPVOID *ppvAudioPtr2, __out_opt LPDWORD pdwAudioBytes2, DWORD dwFlags) PURE;
    STDMETHOD(Play)                 (THIS_ DWORD dwReserved1, DWORD dwPriority, DWORD dwFlags) PURE;
    STDMETHOD(SetCurrentPosition)   (THIS_ DWORD dwNewPosition) PURE;
    STDMETHOD(SetFormat)            (THIS_ __in LPCWAVEFORMATEX pcfxFormat) PURE;
    STDMETHOD(SetVolume)            (THIS_ LONG lVolume) PURE;
    STDMETHOD(SetPan)               (THIS_ LONG lPan) PURE;
    STDMETHOD(SetFrequency)         (THIS_ DWORD dwFrequency) PURE;
    STDMETHOD(Stop)                 (THIS) PURE;
    STDMETHOD(Unlock)               (THIS_ __in_bcount(dwAudioBytes1) LPVOID pvAudioPtr1, DWORD dwAudioBytes1,
                                           __in_bcount_opt(dwAudioBytes2) LPVOID pvAudioPtr2, DWORD dwAudioBytes2) PURE;
    STDMETHOD(Restore)              (THIS) PURE;
};

发现只需要HOOK到其中几个函数,就可以拿到数据,并且还可以捕捉用户行为(比如暂停,切歌,拉动等动作)
Unlock  或者 Lock 可以拿到数据, GetCurrentPosition 可以获取缓冲长度,GetFormat可以拿到音频头信息等。

通过捕捉行为以及缓冲区数据长度,就能计算延迟时间,然后就可以达到完全同步, 捕捉到用户行为后自己的麦缓冲和hook的音频缓冲要做相应的清空操作。
比如我的读数据操作如下:  实现读取数据和同步操作
bool CVEPlayerSync::WriteFrame(CBuffer& buffer, long length, DWORD diff_num)
{
if (diff_num>=0)
_sync_diff_num = diff_num;
if (diff_num == 0)
flag_num++;
else
flag_num = 0;

if ((_sync_diff_num == 0) && (flag_num == 2))
{
length = 0;
_uCurTimestampWrite = 0;
_uCurTimestampRead = 0;
_bCanRead = false;
_uLengthWrite = 0;
_bSoftMixer = true;
}
if (flag_num == 1)
{
_bCanRead = false;
return true;
}

SyncEstimate(buffer);

if (length <= 0)
{
return false;
}

_uLengthWrite += length;
if (_uLengthWrite > (_sync_pos + _sync_diff_num))
{
_bCanRead = true;
}

if (_uCurTimestampWrite == 0)
{
_uCurTimestampWrite = webrtc::TickTime::MillisecondTimestamp();
}

FrameInfo info;
info.length = length;
info.uTimestamp = webrtc::TickTime::MillisecondTimestamp();
info.uTimestampSub = info.uTimestamp - _uCurTimestampWrite;
_uCurTimestampWrite = info.uTimestamp;

_frameInfoListWrite.push_back(info);
SyncEstimate(_frameInfoListWrite, _lAvgStatWrite);

return true;
}

OK 就写到这里,有问题可以讨论啊!



版权声明:本文为博主原创文章,未经博主允许不得转载。

hook 播放器注意事项

音频捕获 DSound音频和系统声音钩子(Hooker)实践 因工作需要,折腾了一下DSound钩子技术。在折腾之前,我也尝试在网络上搜索相关的音频HOOk技术,但搜索到的,都是要收费,而老板不愿...
  • guoer9973
  • guoer9973
  • 2017年12月27日 10:59
  • 50

DSound音频截取实践

DSound音频截取实践   2014-02-14 21:42:29|  分类: 流媒体|举报|字号 订阅      ...
  • xingzheouc
  • xingzheouc
  • 2015年07月01日 12:00
  • 699

DSound音频播放

DSound是directx中的用来处理声音特性的一部分,播放音频需要准备的工作其实和waveout也是差不多啊,创建directxobject也就是创建一个LPDIRECTSOUND,这个结构的定义...
  • T20091
  • T20091
  • 2013年11月29日 18:44
  • 1911

COM接口Hook的用法

// Dsound.h 中的定义 HRESULT CreateSoundBuffer(   LPCDSBUFFERDESC pcDSBufferDesc,   LPDIRECTSOUNDBUFF...
  • wxl1986622
  • wxl1986622
  • 2015年03月11日 14:51
  • 1461

DirectShow&DirectSound采集音频视频数据 vs2013

//AMCap.h#ifndef AMCAP__H#define AMCAP__H#include #include #include #include #include #include #incl...
  • kun199196
  • kun199196
  • 2015年10月30日 14:30
  • 678

音乐播放器实现歌词同步

项目中音乐播放器实现歌词同步思路: 1.读取LRC歌词文件信息 2.将歌词时间段和对应时间段歌词保存至两个数组中, 3.按时间顺序对两个数组进行排序 核心代码如下: public class Song...
  • yihu0817
  • yihu0817
  • 2014年12月16日 20:31
  • 1899

手把手教你做音乐播放器(一)功能规划

前言学习完“计算器” “视频播放器” “蓝牙聊天”以后,对安卓应用的开发我们基本上就入门70%了。现在,我们将在之前学习的基础上,进一步完善我们要掌握的安卓开发技术,开发一个“音乐播放器”。...
  • anddlecn
  • anddlecn
  • 2016年09月20日 13:25
  • 7869

DirectSound采集播放声音技术文档

DirectSound采集播放声音技术文档
  • zdy_ruoshui
  • zdy_ruoshui
  • 2013年12月12日 17:40
  • 2288

一个DirectSound的例子,即录即放

一个捕获音频并且播放的例子,可以用来唱歌^_^写了半天才发现Direct SDK有个类似的例子,所以到了最后几乎都是照抄了。声音效果不太好,修改一下加上网络传送功能做成语音聊天工具。不过这样肯定不行,...
  • benny5609
  • benny5609
  • 2008年02月09日 19:17
  • 1903

音频数据的处理

前言在研究android音频架,音频驱动等的时候,就有涉及到dump音频数据debug,重采样,downmixer,位深转换的处理,那这些的操作原理以及相关算法是如何实现的呢?带着这个问题,开始探讨音...
  • Joymine
  • Joymine
  • 2017年08月07日 19:25
  • 1516
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:DirectSound初步教程 -- 如何从第三发音乐播放器中HOOK音频数据然后跟自己麦采集数据做同步
举报原因:
原因补充:

(最多只允许输入30个字)