DirectSound采集播放声音技术文档
2013-12-12 17:40
351 查看
Windows上的采集声音播放我们一般都用DirectSound来实现,下面我们重点来介绍一下使用DirectSound来实现音频采集播放技术。
1.音频采集部分:
首先我们需要枚举出系统里面的音频设备对象,我们用DirectSoundCaptureEnumerate()方面枚举出系统音频采集的设备,这个方法带有两个参数,一个指定枚举出设备执行的回调函数,一个上下文参数指针,首先我们定义枚举出设备执行的回调函数声明
IDirectSoundCapture接口创建好,我们就可以使用IDirectSoundCapture接口的方法了,主要有下面几个方法
2.音频播放部分:
音频的播放和采集大体上流程一致,不过是采集是从IDirectSoundCaptureBuffer获取数据,播放是需要向IDirectSoundBuffer写数据而已,同时播放时需要创建IDirectSound设备对象,采集是需要创建IDirectSoundCapture设备对象,下面我们来简单的介绍一下音频播放的过程,首先我们定义需要创建的设备对象,并且来创建扬声器设备
DSSCL_EXCLUSIVE设置独占模式
DSSCL_NORMAL
设置一般模式
DSSCL_PRIORITY设置优先模式
DSSCL_WRITEPRIMARY设置写入初级水平
MSDN建议一般选择DSSCL_PRIORITY模式即可
当我我们播放完成后,或需要停止播放的时候只要调用Stop方法就可以,然后释放相关的对象就完成声音的播放了。
1.音频采集部分:
首先我们需要枚举出系统里面的音频设备对象,我们用DirectSoundCaptureEnumerate()方面枚举出系统音频采集的设备,这个方法带有两个参数,一个指定枚举出设备执行的回调函数,一个上下文参数指针,首先我们定义枚举出设备执行的回调函数声明
//lpGuid 设备GUID //lpcstrDescription 设备描述信息 //lpcstrModule DirectSound的驱动程序对应于该设备的模块名称 //lpContext 之前设置上下文指针 BOOL CALLBACK DSEnumCallback( LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext )然后我们在定义一个结构体用户存放我们枚举出的设备信息
typedef struct _DevItem { CString strName; GUID guid; } DevItem; //用来保存枚举出来的采集设备对象; std::vector<DevItem> m_CapDevices;定义好这些我们就可以调用DirectSoundCaptureEnumerate()方法来枚举系统设备信息了
HRESULT hr = S_OK; hr = DirectSoundCaptureEnumerate(DSEnumCallback, (LPVOID)&m_CapDevices);然后我们在DSEnumCallback函数里面把枚举出来的设备保存到m_CapDevices里面
BOOL CALLBACK DSEnumCallback(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) { std::vector<DevItem> *pLst = (std::vector<DevItem> *) lpContext; if (pLst) { DevItem item; memset(&item, 0, sizeof(item)); item.strName = lpcstrDescription; if(lpGuid) item.guid = *lpGuid; else item.guid = GUID_NULL; pLst->push_back(item); return TRUE; } return FALSE; }枚举出设备后,我们就可以用枚举出设备GUID来创建采集设备IDirectSoundCapture接口对象了
//DS采集设备 CComPtr<IDirectSoundCapture> m_pMicDevice; //strGuid 刚刚枚举出设备出来的GUID,如果为NULL则创建系统默认音频设备 HRESULT hr = DirectSoundCaptureCreate(&strGuid, &m_pMicDevice, 0);
IDirectSoundCapture接口创建好,我们就可以使用IDirectSoundCapture接口的方法了,主要有下面几个方法
DECLARE_INTERFACE_(IDirectSoundCapture, IUnknown) { // IDirectSoundCapture methods STDMETHOD(CreateCaptureBuffer) (THIS_ LPCDSCBUFFERDESC pcDSCBufferDesc, LPDIRECTSOUNDCAPTUREBUFFER *ppDSCBuffer, LPUNKNOWN pUnkOuter) PURE; STDMETHOD(GetCaps) (THIS_ LPDSCCAPS pDSCCaps) PURE; STDMETHOD(Initialize) (THIS_ LPCGUID pcGuidDevice) PURE; };我们主要使用CreateCaptureBuffer方法,GetCaps方法主要是可以获取录音设备的性能,Initialize方法是做一些初始化的方面的工作,通过IDirectSoundCapture创建设备对象是不需要调用Initialize方法的,只有通过CoCreateInstance方法创建设备对象才需要调用Initialize方法,接着我们用已经创建m_pMicDevice设备对象来创建IDirectSoundCaptureBuffer接口,主要用到CreateCaptureBuffer方法来创建,看下这个方法说明:
//pcDSCBufferDesc 指向DSCBUFFERDESC结构体指针 //用来保存创建好的IDirectSoundCaptureBuffer对象的指针 //pUnkOuter 目前填NULL HRESULT CreateCaptureBuffer( LPCDSCBUFFERDESC pcDSCBufferDesc, LPDIRECTSOUNDCAPTUREBUFFER * ppDSCBuffer, LPUNKNOWN pUnkOuter )首先我们要了解WAVEFORMATEX结构体和DSCBUFFERDESC结构体,DSCBUFFERDESC结构体最后一个成员就是WAVEFORMATEX结构体,WAVEFORMATEX结构体主要用来设置采集生意的格式,采样率,通道数目,采样位深,码率等信息。DSCBUFFERDESC主要是增加描述采集缓冲区的大小的一些信息
typedef struct { WORD wFormatTag; //设置波形声音的格式,更多的信息请参考MSDN WORD nChannels; //设置音频文件的通道数量,对于单声道的声音,此此值为1。对于立体声,此值为2 DWORD nSamplesPerSec;//设置每个声道播放和记录时的样本频率 DWORD nAvgBytesPerSec;//设置请求的平均数据传输率,单位byte/s WORD nBlockAlign; //以字节为单位设置块对齐。块对齐是指最小数据的原子大小。 WORD wBitsPerSample; //每个样本的采样位深 WORD cbSize; //额外信息的大小 } WAVEFORMATEX; *PWAVEFORMATEX;
typedef struct _DSCBUFFERDESC { DWORD dwSize;//结构体大小用于区分不同版本 DWORD dwFlags;//指定设备的一些能力,默认为0 DWORD dwBufferBytes;//采集缓冲区大小 DWORD dwReserved;//保留字段 LPWAVEFORMATEX lpwfxFormat;//WAVEFORMATEX结构体指针 } DSCBUFFERDESC, *LPDSCBUFFERDESC;创建设备缓冲区对象示例
WAVEFORMATEX wfx; memset(&wfx, 0, sizeof(WAVEFORMATEX)); wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nSamplesPerSec = 48000; wfx.nChannels = 2; wfx.wBitsPerSample = 16; wfx.nBlockAlign = 4; wfx.nAvgBytesPerSec = 48000 * 4; int nDSIBufferSzie = 3 * 48000 * 2 * (16 / 8); //采集缓冲3秒数据; DSCBUFFERDESC bufProp; memset(&bufProp, 0, sizeof(DSCBUFFERDESC)); bufProp.dwSize = sizeof(DSCBUFFERDESC); bufProp.dwBufferBytes = nDSIBufferSzie; bufProp.lpwfxFormat = &wfx; hr = m_pMicDevice->CreateCaptureBuffer(&bufProp, &m_pMicBuffer, 0);获取到IDirectSoundCaptureBuffer缓冲区后,我们就可以用该接口提供的方法来控制采集开始,停止,获取位置,获取数据操作了,看下这个接口提供的方法
DECLARE_INTERFACE_(IDirectSoundCaptureBuffer, IUnknown) { // IDirectSoundCaptureBuffer methods STDMETHOD(GetCaps) (THIS_ LPDSCBCAPS pDSCBCaps) PURE; STDMETHOD(GetCurrentPosition) (THIS_ LPDWORD pdwCapturePosition, LPDWORD pdwReadPosition) PURE; STDMETHOD(GetFormat) (THIS_ LPWAVEFORMATEX pwfxFormat, DWORD dwSizeAllocated, LPDWORD pdwSizeWritten) PURE; STDMETHOD(GetStatus) (THIS_ LPDWORD pdwStatus) PURE; STDMETHOD(Initialize) (THIS_ LPDIRECTSOUNDCAPTURE pDirectSoundCapture, LPCDSCBUFFERDESC pcDSCBufferDesc) PURE; STDMETHOD(Lock) (THIS_ DWORD dwOffset, DWORD dwBytes, LPVOID *ppvAudioPtr1, LPDWORD pdwAudioBytes1, LPVOID *ppvAudioPtr2, LPDWORD pdwAudioBytes2, DWORD dwFlags) PURE; STDMETHOD(Start) (THIS_ DWORD dwFlags) PURE; STDMETHOD(Stop) (THIS) PURE; STDMETHOD(Unlock) (THIS_ LPVOID pvAudioPtr1, DWORD dwAudioBytes1, LPVOID pvAudioPtr2, DWORD dwAudioBytes2) PURE; };我们主要用到Start、Stop、GetCurrentPosition、Lock、Unlock几个方法,首先我们要启动采集,主要就是调用Start()方法
hr = m_pMicBuffer->Start(DSCBSTART_LOOPING);重点是要使用DSCBSTART_LOOPING这个参数,表示循环不断的采集,启动开始采集以后我们后续主要就是从缓冲取出采集到的数据了,首先我们要根据GetCurrentPosition()获取读指针的位置,这个函数有两个参数,主要用来获取当前采集数据的位置和可以读取数据的位置。
//pdwCapturePosition 获取从缓冲区起始现在采集数据的偏移位置 //pdwReadPosition 获取从缓冲器起始现在可以读取数据的偏移位置 HRESULT GetCurrentPosition(LPDWORD pdwCapturePosition,LPDWORD pdwReadPosition)这里我们应该记录上一次读取缓冲区的位置,然后判断本次可以读取的数据长度是否满足条件,如果满足读取条件就去读取数据,如果不满足条件就返回等待下次读取数据
int nBufLen = 192000/1000 * 40; //每次读取40毫秒的数据 unsigned char *pBuffer = new unsigned char[nBufLen+1]; int nDSIMicReadPtr = 0; //表示上次读取缓冲区的位置 int nDSIMicReadLen = 0; //本次读取缓冲区的长度 DWORD dwPtr = 0; DWORD dwReadPtr = 0; m_pMicBuffer->GetCurrentPosition(&dwPtr, &dwReadPtr); if(dwReadPtr < (DWORD)nDSIMicReadPtr) { //nDSIBufferSzie代表之前创建缓冲区的大小 nDSIMicReadLen = nDSIBufferSzie - (nDSIMicReadPtr - dwReadPtr); } else { nDSIMicReadLen = dwReadPtr - nDSIMicReadPtr; } if(nDSIMicReadLen < nBufLen) //数据不够读取长度,循环一下次读取 { delete[] pBuffer; return; }当可以去读取数据的时候,我们就可以用Lock函数先锁定数据然后读取数据了,该函数用法如下
//dwOffset 锁住缓冲区数据的起始位置 //dwBytes 锁定数据的长度 //ppvAudioPtr1 读取数据指针1 //pdwAudioBytes1 读取数据指针1的长度 //ppvAudioPtr2 读取数据指针2 //pdwAudioBytes2 读取数据指针2的长度 //dwFlags 标志位可以修改锁的一些事件,一般设置0 HRESULT Lock( DWORD dwOffset,DWORD dwBytes, LPVOID * ppvAudioPtr1,LPDWORD pdwAudioBytes1, LPVOID * ppvAudioPtr2,LPDWORD pdwAudioBytes2, DWORD dwFlags )调用这个函数,我们就可以锁定自己希望获取的数据缓冲区了,然后根据返回的数据指针1和数据指针1来从缓冲区复制数据了。有人可能会问为什么要返回两个缓冲区指针呢,根据我个人理解IDirectSoundCaptureBuffer的缓冲区用到的是环形buffer,这样当我们要拷取的数据刚好穿过环形buffer的头尾时必须要提供两个指针才能拷贝完成用户需要的数据,所以这里要返回两个缓冲区的指针,下面我看下获取数据的代码
void *ptr1 = 0; void *ptr2 = 0; DWORD len1 = 0; DWORD len2 = 0; HRESULT hr = m_pMicBuffer->Lock(nDSIMicReadPtr, nBufLen, &ptr1, &len1, &ptr2, &len2, 0); if(FAILED(hr)) return; nBufLen = 0; //pBuffer在上面获取数据位置时已经创建 if ((NULL != ptr1) && (len1 > 0)) { memcpy(pBuffer, ptr1, len1); nDSIMicReadPtr += len1; nBufLen = len1; } if ((NULL != ptr2) && (len2 > 0)) { memcpy(pBuffer+len1, ptr2, len2); nDSIMicReadPtr += len2; nBufLen += len2; } nDSIMicReadPtr = nDSIMicReadPtr % nDSIBufferSzie; m_pMicBuffer->Unlock(ptr1, len1, ptr2, len2); /* //对获取的采集的数据进行各种处理 */ delete[] pBuffer;获取到数据后,要及时的解锁之前的缓冲区,使用Unlock方法,参数是之前返回的数据指针1和数据指针2和数据的长度,要想不断地获取数据,只要不断地循环执行这样的步骤即可,获取到数据后我们就可以用这些数据做后续的操作了,一般编码写成文件等等。当不需要获取采集数据的时候,我们只要调用Stop方法,停止麦克风的采集就可以了
m_pMicBuffer->Stop();
2.音频播放部分:
音频的播放和采集大体上流程一致,不过是采集是从IDirectSoundCaptureBuffer获取数据,播放是需要向IDirectSoundBuffer写数据而已,同时播放时需要创建IDirectSound设备对象,采集是需要创建IDirectSoundCapture设备对象,下面我们来简单的介绍一下音频播放的过程,首先我们定义需要创建的设备对象,并且来创建扬声器设备
CComPtr<IDirectSound> m_pSpkDevice; //DS播放设备 CComPtr<IDirectSoundBuffer> m_pSpkBuffer; //DS播放缓冲区 hr = DirectSoundCreate(&strGuid, &m_pSpkDevice, 0);创建IDirectSoundBuffer接口要用到DirectSoundCreate函数,第一个参数是创建设备GUID,第二个参数是用来接收创建好设备对象的指针,和创建麦克风设备方法使用一样,创建完设备对象后,我们接下来先要设置播放平衡的优先级,这个是必须要设置的,先看下设置播放平衡优先级的方法
//hwnd 指向一个应用程序的窗口句柄 //dwLevel 设置协作级别 HRESULT SetCooperativeLevel( HWND hwnd, DWORD dwLevel) }dwLevel这个参数有下面几个值
DSSCL_EXCLUSIVE设置独占模式
DSSCL_NORMAL
设置一般模式
DSSCL_PRIORITY设置优先模式
DSSCL_WRITEPRIMARY设置写入初级水平
MSDN建议一般选择DSSCL_PRIORITY模式即可
//设置播放平衡优先级 hr = m_pSpkDevice->SetCooperativeLevel(m_hWnd, DSSCL_PRIORITY);设置好协作级别后我们就可以创建辅助缓冲去了
WAVEFORMATEX wfx; memset(&wfx, 0, sizeof(WAVEFORMATEX)); wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nSamplesPerSec = 48000; wfx.nChannels = 2; wfx.wBitsPerSample = 16; wfx.nBlockAlign = 4; wfx.nAvgBytesPerSec = 48000 * 4; DWORD dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STICKYFOCUS | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLVOLUME; DSBUFFERDESC dsBufDesc = {0}; dsBufDesc.dwSize = sizeof(DSBUFFERDESC); dsBufDesc.dwFlags = dwFlags; dsBufDesc.dwBufferBytes = nDSISpkBufferSzie; dsBufDesc.lpwfxFormat = &wfx; CComPtr<IDirectSoundBuffer> pSpkBuffer; hr = m_pSpkDevice->CreateSoundBuffer(&dsBufDesc, &pSpkBuffer, 0); pSpkBuffer->QueryInterface(IID_IDirectSoundBuffer, (void**)&m_pSpkBuffer); if(FAILED(hr) || (NULL == m_pSpkBuffer)) { return; }WAVEFORMATEX和DSBUFFERDESC这两个参数在采集时以介绍过,不在过多介绍。创建好IDirectSoundBuffer以后,我们就可以用这个接口的方法了,这个接口主要有以下方法
DECLARE_INTERFACE_(IDirectSoundBuffer, IUnknown) { // IDirectSoundBuffer methods STDMETHOD(GetCaps) (THIS_ LPDSBCAPS pDSBufferCaps) PURE; STDMETHOD(GetCurrentPosition) (THIS_ LPDWORD pdwCurrentPlayCursor, LPDWORD pdwCurrentWriteCursor) PURE; STDMETHOD(GetFormat) (THIS_ LPWAVEFORMATEX pwfxFormat, DWORD dwSizeAllocated, LPDWORD pdwSizeWritten) PURE; STDMETHOD(GetVolume) (THIS_ LPLONG plVolume) PURE; STDMETHOD(GetPan) (THIS_ LPLONG plPan) PURE; STDMETHOD(GetFrequency) (THIS_ LPDWORD pdwFrequency) PURE; STDMETHOD(GetStatus) (THIS_ LPDWORD pdwStatus) PURE; STDMETHOD(Initialize) (THIS_ LPDIRECTSOUND pDirectSound, LPCDSBUFFERDESC pcDSBufferDesc) PURE; STDMETHOD(Lock) (THIS_ DWORD dwOffset, DWORD dwBytes, LPVOID *ppvAudioPtr1, LPDWORD pdwAudioBytes1, LPVOID *ppvAudioPtr2, LPDWORD pdwAudioBytes2, DWORD dwFlags) PURE; STDMETHOD(Play) (THIS_ DWORD dwReserved1, DWORD dwPriority, DWORD dwFlags) PURE; STDMETHOD(SetCurrentPosition) (THIS_ DWORD dwNewPosition) PURE; STDMETHOD(SetFormat) (THIS_ 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_ LPVOID pvAudioPtr1, DWORD dwAudioBytes1, LPVOID pvAudioPtr2, DWORD dwAudioBytes2) PURE; STDMETHOD(Restore) (THIS) PURE; };我们主要还是用到GetCurrentPosition、Play、Lock、Unlock、Stop这几个方法,基本上和IDirectSoundCaptureBuffer方法使用一样,首先使用播放函数
hr = m_pSpkBuffer->Play(0, 0, DSBPLAY_LOOPING);第一个参数保留值未使用默认为0,第二个参数一般是使用DSBCAPS_LOCDEFER创建设备时使用,默认为0,重点是第三个参数要使用DSBPLAY_LOOPING这样才会一直不断的循环的播放。后面我们就可以把自己的音频数据不断地放到缓冲去了,如下
ptr1 = NULL; ptr2 = NULL; len1 = 0; len2 = 0; hr = m_pSpkBuffer->Lock(nDSISpkWritePtr, nBufLen, &ptr1, &len1, &ptr2, &len2, 0); if(FAILED(hr)) continue; //pBuffer为保存音频的pcm数据 if ((NULL != ptr1) && (NULL != pBuffer)) memcpy(ptr1, pBuffer, len1); if ((NULL != ptr2) && (NULL != pBuffer)) memcpy(ptr2, pBuffer+len1, len2); m_pSpkBuffer->Unlock(ptr1, len1, ptr2, len2); nDSISpkWritePtr = (nDSISpkWritePtr + len1 + len2) % nDSISpkBufferSzie;和采集缓冲区取出数据一样,不做过多说明,更准确的方法是需要使用GetCurrentPosition来判断缓冲区还有多少数据没有播放,根据数据多少来放数据,和采集那块道理是一样的,可以根据自己的需求来完善。这样只要不断源源的播放数据,声音就可以正常的播放了。
当我我们播放完成后,或需要停止播放的时候只要调用Stop方法就可以,然后释放相关的对象就完成声音的播放了。
m_pSpkBuffer->Stop();
相关文章推荐
- 声音采集播放以及I2S接口
- 声音采集播放及IIS接口介绍
- DirectSound 播放声音杂音问题.
- DirectSound 播放声音杂音问题.
- Android使用AudioRecord采集声音时声音播放很快解决方案
- 建立第一个directX程序——在C#下利用DirectSound实现声音播放
- 【iOS录音与播放】实现利用音频队列,通过缓存进行对声音的采集与播放
- ffmpeg文档3:播放声音
- Android直接播放麦克风采集到的声音
- 基于Nutch&Solr定向采集解析和索引搜索的整合技术指南文档
- Android直接播放麦克风采集到的声音
- 声卡录制:采集声卡播放的声音,并录制成mp3文件!
- mac os上视频采集与播放采用什么技术开发
- 基于Nutch&Solr定向采集解析和索引搜索的整合技术指南文档
- 关于DirectSound播放声音缓存的一些认识
- 声音采集播放程序
- 使用DirectSound进行PCM声音采集
- DirectX编程:[初级]C#中利用DirectSound播放WAV格式声音[最少只要4句话]
- DirectX.DirectSound声音播放资料
- DirectSound播放PCM(可播放实时采集的音频数据)