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



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

相关文章推荐

Directsound 详解

关键词: Directsound stream buffer ,static buffer wave 文件播放   一、配置DirectDounf的开发环境   在进行DirectSoun...

IDirectSound8::CreateSoundBuffer要记得释放

如果持续使用IDirectSound8::CreateSoundBuffer创建缓冲区,播放声音,但是不释放缓冲区。这样会导致所有函数都执行成功,但是某些声音播放不出来!最好的办法就是等声音播放结束立...

DirectSound入门基础介绍 和 环境的配置

DirectSound应用程序开发快速入门   摘要:DirectSound编程的入门介绍,通过实例讲解了如何利用DirectSound最基本的功能:播放音频,并提供了DirectSound播放音...

DirectSound详细介绍

目录:   音效的表现   Play Sound using PlaySound()   使用DirectSound   相关事宜   末语 文档内容: □ 音效的表现 ...

Windows上的音频采集技术

AUG 14TH, 2013 | COMMENTS 前一段时间接到一个任务,需要采集到声卡的输出信号,以便与麦克风的输入信号进行混音。 之前一直没有研究过音频的相关技术,这次就顺便抽出...

DirectSound开发指南

1DirectSound简介(Introduction to DirectSound)     曾经学习过Directshow的开发,对于Dsound一直没有仔细的莱学习,以前只是知道Dsound是...

Hook Wav Out, Dump 原始的音频文件.

某音频文件经过优化编码,发现解码以后是通过Wav Out API播放的.不具体研究他是如何解密的,直接Hook Wav 播放的API得到原始数据: 数据头的格式: extern "C" __decl...
  • qffhq
  • qffhq
  • 2014年03月26日 15:09
  • 631

c++,在windows下调用子进程,并获得子进程的返回值。

win32 API真是相当复杂啊,为了调用一个函数,要读那么多参数,还要查阅相有关连的其它函数…以下是主进程代码 :#include #include int main(int argc, char*...

创建一个没有任何界面的后台程序并且设置为开机启动

Windows系统下常见的程序类型有3种,第一种是最常见的WinForm类型的,第二种是控制台Console类型的,第三种是Windows Service类型的。   在我们编写一个程序的时候,如果...

Android Jamendo开源在线音乐播放器源码分析七 数据缓存和图片缓存的分析

在醒目中对于进行数据缓存和图片缓存的对象是在public class JamendoApplication extends Application这个Application里面进行创建和获取的。 对...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:DirectSound初步教程 -- 如何从第三发音乐播放器中HOOK音频数据然后跟自己麦采集数据做同步
举报原因:
原因补充:

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