北京理工大学 20981 陈罡
终于可以用了,原本directsound就是来播放声音的,怎么现在看来这么费劲呢?好多directsound的sample代码都是播放wav文件的,而我从一开始就打算把音频数据通过以太网以流的形式传输,这样必然就需要一个循环缓冲区,从网络接收数据,解码后写入这个缓冲区,然后另外一个线程,周期性的读取这个缓冲区,来实现声音播放。还是采用与directsound的抓取音频数据的思路,编写了一个封装类,很好用,目前也只是8KHz,16Bits,Mono的PCM码流,可以直接播放而已。
咱为人厚道,贴出代码,希望对后来者有用:
这个是CStreamAudio类的头文件:
#pragma once
#include
#include
#define NUM_REC_NOTIFICATIONS 16
class CAudioStreamHandler {
public:
virtual void AdoStreamData(unsigned char * pBuffer, int nBufferLen) = 0 ;
};
class CStreamAudio
{
protected:
IDirectSound8 * m_pDS; // DirectSound component
IDirectSoundBuffer8 * m_pDSBuf; // Sound Buffer object
IDirectSoundNotify8 * m_pDSNotify; // Notification object
WAVEFORMATEX m_wfxOutput ; // Wave format of output
// some codes from capture audio
DSBPOSITIONNOTIFY m_aPosNotify[NUM_REC_NOTIFICATIONS + 1]; //notify flag array
DWORD m_dwPlayBufSize; //play loop buffer size
DWORD m_dwNextPlayOffset;//offset in loop buffer
DWORD m_dwNotifySize; //notify pos when loop buffer need to emit the event
CAudioStreamHandler* m_stream_handler ; // caller stream buffer filler
public:
BOOL m_bPlaying ;
HANDLE m_hNotifyEvent; //notify event
BOOL LoadStreamData() ;
public:
static UINT notify_stream_thd(LPVOID data) ;
protected:
HRESULT InitDirectSound(HWND hWnd) ;
HRESULT FreeDirectSound() ;
IDirectSoundBuffer8 *CreateStreamBuffer(IDirectSound8* pDS, WAVEFORMATEX* wfx) ;
BOOL SetWavFormat(WAVEFORMATEX * wfx) ;
public:
CStreamAudio(void);
~CStreamAudio(void);
BOOL Open(HWND hWnd, CAudioStreamHandler * stream_handler) ;
BOOL Close() ;
BOOL CtrlStream(BOOL bPlaying) ;
};
下面是CStreamAudio的源文件:
#include "StdAfx.h"
#include ".\streamaudio.h"
#ifndef SAFE_RELEASE
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }
#endif
#ifndef MAX
#define MAX(a,b) ( (a) > (b) ? (a) : (b) )
#endif
CStreamAudio::CStreamAudio(void)
{
if(FAILED(CoInitialize(NULL))) /*, COINIT_APARTMENTTHREADED)))*/
{
AfxMessageBox("CStreamAudio CoInitialize Failed!\r\n");
return;
}
m_pDS = NULL ; // DirectSound component
m_pDSBuf = NULL ; // Sound Buffer object
m_pDSNotify = NULL ; // Notification object
m_hNotifyEvent = NULL ;
ZeroMemory(&m_wfxOutput, sizeof(m_wfxOutput)) ; // Wave format of output
m_wfxOutput.wFormatTag = WAVE_FORMAT_PCM ;
m_dwPlayBufSize = 0 ; //play loop buffer size
m_dwNextPlayOffset = 0 ; //offset in loop buffer
m_dwNotifySize = 0 ; //notify pos when loop buffer need to emit the event
m_bPlaying = FALSE ;
}
CStreamAudio::~CStreamAudio(void)
{
FreeDirectSound() ;
CoUninitialize() ;
}
HRESULT CStreamAudio::InitDirectSound(HWND hWnd)
{
if(FAILED(DirectSoundCreate8(NULL, &m_pDS, NULL))) {
MessageBox(NULL, "Unable to create DirectSound object", "Error", MB_OK);
return S_FALSE;
}
// sets the cooperative level of the application for this sound device
m_pDS->SetCooperativeLevel(hWnd, DSSCL_PRIORITY);
// use preset output wave format
SetWavFormat(&m_wfxOutput) ;
return S_OK ;
}
HRESULT CStreamAudio::FreeDirectSound()
{
// make sure the thread gone
m_bPlaying = FALSE ;
Sleep(500) ;
// stop sound play
if(m_pDSBuf) m_pDSBuf->Stop();
// Release the notify event handles
if(m_hNotifyEvent) {
CloseHandle(m_hNotifyEvent) ;
m_hNotifyEvent = NULL ;
}
// Release DirectSound objects
SAFE_RELEASE(m_pDSBuf) ;
SAFE_RELEASE(m_pDS) ;
return S_OK ;
}
BOOL CStreamAudio::Open(HWND hWnd, CAudioStreamHandler * stream_handler)
{
HRESULT hr ;
m_stream_handler = stream_handler ;
hr = InitDirectSound(hWnd) ;
return (FAILED(hr)) ? FALSE : TRUE ;
}
BOOL CStreamAudio::Close()
{
HRESULT hr ;
hr = FreeDirectSound() ;
return (FAILED(hr)) ? FALSE : TRUE ;
}
UINT CStreamAudio::notify_stream_thd(LPVOID data)
{
CStreamAudio * psmado = static_cast(data) ;
DWORD dwResult = 0 ;
DWORD Num = 0 ;
while(psmado->m_bPlaying) {
// Wait for a message
dwResult = MsgWaitForMultipleObjects(1, &psmado->m_hNotifyEvent,
FALSE, INFINITE, QS_ALLEVENTS);
// Get notification
switch(dwResult) {
case WAIT_OBJECT_0:
{
psmado->LoadStreamData();
}
break ;
default:
break ;
}
}
AfxEndThread(0, TRUE) ;
return 0 ;
}
IDirectSoundBuffer8 * CStreamAudio::CreateStreamBuffer(IDirectSound8* pDS, WAVEFORMATEX * wfx)
{
IDirectSoundBuffer * pDSB = NULL ;
IDirectSoundBuffer8 * pDSBuffer = NULL ;
DSBUFFERDESC dsbd;
// calculate play buffer size
// Set the notification size
m_dwNotifySize = MAX( 1024, wfx->nAvgBytesPerSec / 8 ) ;
m_dwNotifySize -= m_dwNotifySize % wfx->nBlockAlign ;
// Set the buffer sizes
m_dwPlayBufSize = m_dwNotifySize * NUM_REC_NOTIFICATIONS;
// create the sound buffer using the header data
ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
dsbd.dwSize = sizeof(DSBUFFERDESC);
// set DSBCAPS_GLOBALFOCUS to make sure event if the software lose focus could still
// play sound as well
dsbd.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_LOCSOFTWARE | DSBCAPS_GLOBALFOCUS;
dsbd.dwBufferBytes = m_dwPlayBufSize ;
dsbd.lpwfxFormat = wfx ;
if(FAILED(pDS->CreateSoundBuffer(&dsbd, &pDSB, NULL))) return NULL;
// get newer interface
if(FAILED(pDSB->QueryInterface(IID_IDirectSoundBuffer8, (void**)(&pDSBuffer)))) {
SAFE_RELEASE(pDSB) ;
return NULL;
}
// return the interface
return pDSBuffer;
}
BOOL CStreamAudio::LoadStreamData()
{
///////////////////////
HRESULT hr;
VOID* pvStreamData1 = NULL;
DWORD dwStreamLength1 = 0 ;
VOID* pvStreamData2 = NULL;
DWORD dwStreamLength2 = 0 ;
DWORD dwWritePos = 0 ;
DWORD dwPlayPos = 0 ;
LONG lLockSize = 0 ;
if( FAILED( hr = m_pDSBuf->GetCurrentPosition( &dwPlayPos, &dwWritePos ) ) )
return S_FALSE;
lLockSize = dwWritePos - m_dwNextPlayOffset;
if( lLockSize < 0 )
lLockSize += m_dwPlayBufSize;
// Block align lock size so that we are always write on a boundary
lLockSize -= (lLockSize % m_dwNotifySize);
if( lLockSize == 0 ) return S_FALSE;
// lock the sound buffer at position specified
if(FAILED(m_pDSBuf->Lock( m_dwNextPlayOffset, lLockSize,
&pvStreamData1, &dwStreamLength1,
&pvStreamData2, &dwStreamLength2, 0L)))
return FALSE;
// read in the data
if(m_stream_handler) m_stream_handler->AdoStreamData((BYTE *)pvStreamData1, dwStreamLength1) ;
// Move the capture offset along
m_dwNextPlayOffset += dwStreamLength1;
m_dwNextPlayOffset %= m_dwPlayBufSize; // Circular buffer
if(pvStreamData2 != NULL) {
if(m_stream_handler) m_stream_handler->AdoStreamData((BYTE *)pvStreamData2, dwStreamLength2) ;
// Move the capture offset along
m_dwNextPlayOffset += dwStreamLength2;
m_dwNextPlayOffset %= m_dwPlayBufSize; // Circular buffer
}
// unlock it
m_pDSBuf->Unlock(pvStreamData1, dwStreamLength1, pvStreamData2, dwStreamLength2) ;
// return a success
return TRUE;
}
BOOL CStreamAudio::CtrlStream(BOOL bPlaying)
{
HRESULT hr ;
int i;
m_bPlaying = bPlaying ;
if(m_bPlaying) {
// Create a 2 second buffer to stream in wave
m_pDSBuf = CreateStreamBuffer(m_pDS, &m_wfxOutput) ;
if(m_pDSBuf == NULL) return FALSE ;
// Create the notification interface
if(FAILED(m_pDSBuf->QueryInterface(IID_IDirectSoundNotify8, (void**)(&m_pDSNotify))))
return FALSE ;
// create auto notify event
m_hNotifyEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
// Setup the notification positions
for( i = 0; i < NUM_REC_NOTIFICATIONS; i++ ) {
m_aPosNotify[i].dwOffset = (m_dwNotifySize * i) + m_dwNotifySize - 1;
m_aPosNotify[i].hEventNotify = m_hNotifyEvent;
}
// Tell DirectSound when to notify us. the notification will come in the from
// of signaled events that are handled in WinMain()
if( FAILED( hr = m_pDSNotify->SetNotificationPositions( NUM_REC_NOTIFICATIONS, m_aPosNotify ) ) )
return S_FALSE ;
m_dwNextPlayOffset = 0 ;
// Fill buffer with some sound
LoadStreamData() ;
// Play sound looping
m_pDSBuf->SetCurrentPosition(0);
m_pDSBuf->SetVolume(DSBVOLUME_MAX);
m_pDSBuf->Play(0,0,DSBPLAY_LOOPING);
// create notify event recv thread
AfxBeginThread(CStreamAudio::notify_stream_thd, (LPVOID)(this)) ;
} else {
// stop play
// make sure the thread gone
Sleep(500) ;
// stop sound play
if(m_pDSBuf) m_pDSBuf->Stop();
// Release the notify event handles
if(m_hNotifyEvent) {
CloseHandle(m_hNotifyEvent) ;
m_hNotifyEvent = NULL ;
}
// Release DirectSound objects
SAFE_RELEASE(m_pDSBuf) ;
}
return TRUE ;
}
BOOL CStreamAudio::SetWavFormat(WAVEFORMATEX * wfx)
{
// get the default capture wave formate
ZeroMemory(wfx, sizeof(WAVEFORMATEX)) ;
wfx->wFormatTag = WAVE_FORMAT_PCM;
// 8KHz, 16 bits PCM, Mono
wfx->nSamplesPerSec = 8000 ;
wfx->wBitsPerSample = 16 ;
wfx->nChannels = 1 ;
wfx->nBlockAlign = wfx->nChannels * ( wfx->wBitsPerSample / 8 ) ;
wfx->nAvgBytesPerSec = wfx->nBlockAlign * wfx->nSamplesPerSec;
return TRUE ;
}
我想这些应该可以解决相当一部分人对于voip技术的神秘感吧。
调用方式:
(1)添加派生类
class CCapSvrDlg : public CDialog,
public CAudioStreamHandler // audio stream play handler
(2)重载纯虚函数
public: // override the CAudioStreamHandler
void AdoStreamData(unsigned char * pBuffer, int nBufferLen) ;
这里的pBuffer与前几篇博客的数据抓取类有所不同,这个pBuffer是需要写入的,毕竟这个类是用来播放流音频数据的嘛。
(3)声明播放对象
CStreamAudio m_strm_ado ;
(4)在OnInitDialog中初始化对象:
// create stream audio play
m_strm_ado.Open(GetSafeHwnd(), this) ;
(5)再某个按钮或是什么东西里面开始流数据播放:
m_strm_ado.CtrlStream(TRUE) ;
(6)停止流播放
m_strm_ado.CtrlStream(FALSE) ;
(7)关闭播放对象
m_strm_ado.Close() ;
好了这个样就一切ok了,在它需要周期性读取pcm码流的时候,会自动调用AdoStreamData函数的,在nBufferLen形参里面会指明需要读取多少个字节的pcm码流。直接把数据写入pBuffer即可。
呵呵,只要搞个循环的接收缓冲区就可以了。
原本是OpenGL的铁杆追随者,这段时间看看directx, direct3d,呵呵,到底是微软啊。还是怀念用OpenGL写视频模块的日子。。。
具体的依赖库,查查directx的help吧,懒得写了,整个工程文件太大,浪费偶的博客空间,暂时先不上传了。