利用directsound播放PCM流的封装类

终于可以用了,原本directsound就是来播放声音的,怎么现在看来这么费劲呢?好多directsound的sample代码都是播放wav文件的,而我从一开始就打算把音频数据通过以太网以流的形式传输,这样必然就需要一个循环缓冲区,从网络接收数据,解码后写入这个缓冲区,然后另外一个线程,周期性的读取这个缓冲区,来实现声音播放。还是采用与directsound的抓取音频数据的思路,编写了一个封装类,很好用,目前也只是8KHz,16Bits,Mono的PCM码流,可以直接播放而已。
咱为人厚道,贴出代码,希望对后来者有用:
这个是CStreamAudio类的头文件:
#pragma once
#include <mmsystem.h>
#include <dsound.h>
#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<CStreamAudio *>(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吧,懒得写了,整个工程文件太大,浪费偶的博客空间,暂时先不上传了。
posted @ 2009-05-19 17:02  龙仪  阅读(392)  评论(0编辑  收藏  举报