DirectSound初步教程 -- 如何从第三发音乐播放器中HOOK音频数据然后跟自己麦采集数据做同步
2014-09-12 12:46
411 查看
DirectSound初步教程 -- 如何从第三发音乐播放器中HOOK音频数据然后跟自己麦采集数据做同步
首先看看基本知识:Directsound中常用的几个对象对象 | 数量 | 作用 | 主要接口 |
设备对象 | 每个应用程序只有一个设备对象 | 用来管理设备,创建辅助缓冲区 | IDirectSound8 |
辅助缓冲区对象 | 每一个声音对应一个辅助缓冲区,可以有多个辅助缓冲区 | 用来管理一个静态的或者动态的声音流,然后在主缓冲区中混音 | IDirectSoundBuffer8, IDirectSound3DBuffer8, IDirectSoundNotify8 |
主缓冲区对象 | 一个应用程序只有一个主缓冲区 | 将辅助缓冲区的数据进行混音,并且控制3D参数. | IDirectSoundBuffer, IDirectSound3DListener8 |
特技对象 | 没有 | 来辅助缓冲的声音数据进行处理 | 8个特技接口IDirectSoundFXChorus8 |
如果我们的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,W***EFILE_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,W***EFILE_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中的APIDECLARE_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) LPW***EFORMATEX 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 LPCW***EFORMATEX 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 就写到这里,有问题可以讨论啊!
相关文章推荐
- DirectSound初步教程 -- 如何从第三发音乐播放器中HOOK音频数据然后跟自己麦采集数据做同步
- DirectSound播放PCM(可播放实时采集的音频数据)
- DirectShow&DirectSound采集音频视频数据 vs2013
- DirectSound播放PCM(可播放实时采集的音频数据)
- Git教程(二)-如何上传和同步自己的git项目
- 如何实现用rsync来同步备份数据
- 如何在两个SQLSERVER之间数据同步
- 使用asp.net 2.0的CreateUserwizard控件如何向自己的数据表中添加数据
- Visual Studio 2008中如何比较二个数据库的架构【Schema】和数据【Data】并同步
- 如何将pictureBox里的图片保存到数据库,然后从数据读取显示
- 教你如何使用wifi来同步iPhone数据
- WIN32音频数据采集---WaveIn-Out实现
- 如何让采集的数据比原创还要原创!
- Visual Studio 2008中如何比较二个数据库的架构【Schema】和数据【Data】并同步
- 使用asp.net 2.0的CreateUserwizard控件如何向自己的数据表中添加数据
- 使用asp.net 2.0的CreateUserwizard控件如何向自己的数据表中添加数据
- 两台服务器一台北京一台上海,数据如何完全同步,用户访问时如何链接到速度最快的那台服务器?
- 局域网内两台SQL Server电脑如何实时同步数据
- 如何使用 PDI 和 Oracle CDC 来实现Oracle 数据库向其他数据库的数据同步