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

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 就写到这里,有问题可以讨论啊!



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值