利用directsound播放PCM流的封装类

分类: WINDOWS

北京理工大学 20981 陈罡
终于可以用了,原本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吧,懒得写了,整个工程文件太大,浪费偶的博客空间,暂时先不上传了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值