多媒体编程——声音播放(2)

多媒体编程——声音播放(2

第二部分 使用DirectSound进行声音播放。

前一节讲解了如何使用WaveOut进行声音播放,只是讲解个原理。由于缓存切换时有数据有卡顿,所以播放效果不流畅。下面讲讲怎么使用DirectSound进行声音播放,这也是通常实际开发中的选择,使用DirectSound进行开发,由于它API抽象得更好,并且采用异步事件通知的方式通知上层填充数据,所以可以避免数据输入不连续。

DirectSound是微软DirectX SDK的一部分,所以使用DirectSound需要下载DirectXSDK,版本不限。

使用DirectSound播放声音,需要HWND参数作为通知事件消息循环载体,所以例子代码是在MFC工程下的。

建立一个MFC工程,直接在OnInitDialog里面添加播放代码。

需要以下头文件和lib库生命。

#include "atlbase.h"
#include "mmsystem.h"
#pragma comment(lib,"winmm.lib")
#include "dsound.h"
#pragma comment(lib,"dsound.lib")
#pragma comment(lib,"dxguid.lib")


需要以下结构体定义

 

/* WAV Riff文件头*/
typedef struct _RIFF_HEADER
{
    char szRiffID[4]; //'R','I','F','F'
    DWORD dwRiffSize; //从下一字节,到文件结束的字节数。加上就是整个文件的大小
    char szRiffFormat[4]; //'W','A','V','E'
}RIFF_HEADER,*LP_RIFF_HEADER;
 
typedef struct _WAV_FORMAT
{
    WORD   wFormatTag ;   //格式种类1为PCM
    WORD   wTracks       ;   //声道数
    DWORD  dwSamplesPerSec   ;   //采样频率
    DWORD  dwAvgBytesPerSec; //每秒的字节数,是大B。
    WORD   wBlockAlign       ;   //数据的调整数,按B计算
    WORD   wBitsPerSample    ;   //样本采样位数
}WAV_FORMAT,*LP_WAV_FORMAT;
 
/* 数据块头*/
typedef struct _DATA_CHUNK
{
    char szDataID[4]; //'d','a','t','a'
    DWORD dwDataSize;// 接下来数据的长度
}DATA_CHUNK,*LP_DATA_CHUNK;
 
typedef struct _FMT_CHUNK
{
    char   szFmtID[4] ;   // 'f','m','t',' '
    DWORD  dwFmtSize  ;   //一般等于,表示WAVE_FORMAT的字节数
    WAV_FORMAT wavFormat;    //这个结构体大小刚好为
 
}FMT_CHUNK,*LP_FMT_CHUNK;



完整的OnInitDialog代码如下:

解释都在代码注释里。

对象释放就省略了,实际开发中可不能这么草率噢。

BOOL CDxSoundPlayerDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
 
    // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
    // 执行此操作
    SetIcon(m_hIcon, TRUE);         // 设置大图标
    SetIcon(m_hIcon, FALSE);    // 设置小图标
 
    ::CoInitialize(NULL);
 
    LPCTSTR pWavFilePath = _T("C:/Lenka - Trouble Is A Friend.wav") ;
 
    CFile file;
    file.Open(pWavFilePath,CFile::modeRead|CFile::shareDenyWrite);
 
    RIFF_HEADER riffHeader ;
    memset(&riffHeader,0,sizeof(RIFF_HEADER));
    file.Read(&riffHeader,sizeof(RIFF_HEADER));
 
    FMT_CHUNK  fmtBlock ;
    memset(&fmtBlock,0,sizeof(FMT_CHUNK));
    file.Read(&fmtBlock,sizeof(FMT_CHUNK));
 
    DATA_CHUNK dataBlock;
    memset(&dataBlock,0,sizeof(DATA_CHUNK));
    file.Read(&dataBlock,sizeof(DATA_CHUNK));
 
    //前面部分先读取WAV文件的头,得到音频格式信息。
 
    //初始化DirectSound。
    LPDIRECTSOUND8 pDirectSound = NULL;
    HRESULT hr = DirectSoundCreate8(NULL,&pDirectSound,NULL);
    if(FAILED(hr))
       return 0;
 
    //需要设置HWND作为参数。
    hr = pDirectSound->SetCooperativeLevel(this->m_hWnd, DSSCL_NORMAL);
    if(FAILED(hr))
       return hr ;
 
    DSBUFFERDESC stDirectDesc ;//DirectSound格式参数结构体
    WAVEFORMATEX stWaveFormat ;//标准WAVE,PCM音频格式结构体。
    stWaveFormat.cbSize = sizeof(WAVEFORMATEX);
    stWaveFormat.nAvgBytesPerSec = fmtBlock.wavFormat.dwAvgBytesPerSec;
    stWaveFormat.nBlockAlign = fmtBlock.wavFormat.wBlockAlign;
    stWaveFormat.nChannels = fmtBlock.wavFormat.wTracks;
    stWaveFormat.nSamplesPerSec = fmtBlock.wavFormat.dwSamplesPerSec;
    stWaveFormat.wBitsPerSample = fmtBlock.wavFormat.wBitsPerSample;
    stWaveFormat.wFormatTag = fmtBlock.wavFormat.wFormatTag; //其实这个值的含义是WAVE_FORMAT_PCM
 
    stDirectDesc.dwSize = sizeof(DSBUFFERDESC);
    stDirectDesc.lpwfxFormat = &stWaveFormat   ;
    stDirectDesc.lpwfxFormat->wFormatTag= WAVE_FORMAT_PCM ;
    stDirectDesc.lpwfxFormat->cbSize= sizeof(WAVEFORMATEX);
    stDirectDesc.dwBufferBytes = stWaveFormat.nAvgBytesPerSec / 8  ;//一个缓存的数据代表1/8秒的数据长度
    stDirectDesc.guid3DAlgorithm = DS3DALG_DEFAULT;
    stDirectDesc.dwReserved = 0;
    stDirectDesc.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY| DSBCAPS_GLOBALFOCUS |DSBCAPS_CTRLVOLUME | DSBCAPS_LOCSOFTWARE|DSBCAPS_CTRLFREQUENCY;
 
    LPDIRECTSOUNDBUFFER pSoundBuffer = NULL; //创建一个缓存。
    LPDIRECTSOUNDBUFFER8 pDirectSoundBuffer = NULL;
    hr = pDirectSound->CreateSoundBuffer(&stDirectDesc,&pSoundBuffer,NULL);
    if(FAILED(hr))
       return hr ;
    hr = pSoundBuffer->QueryInterface(IID_IDirectSoundBuffer8,(LPVOID*)&pDirectSoundBuffer);
    if(FAILED(hr))
       return hr ;
 
    /*
    DirectSound的工作方式是,一个缓存分成多个片段。
    可以分片段写入,并且为每一个片段设置一个事件通知。
    当该片段的缓存已经播放完毕,事件被通知出来。
    */
    LPDIRECTSOUNDNOTIFY pSoundNotify = NULL;//通知操作对象
    hr = pSoundBuffer->QueryInterface(IID_IDirectSoundNotify,(LPVOID*)&pSoundNotify);
    if(FAILED(hr))
       return hr ;
 
    DSBPOSITIONNOTIFY notifyPosition[4] ; //将缓存分成个片段。
    HANDLE          notifyEvent[4] ;
    int notifyCount = sizeof(notifyPosition)/sizeof(DSBPOSITIONNOTIFY);
    DWORD dwPieceLen= (stDirectDesc.dwBufferBytes/notifyCount) ; //每一个缓存片段的长度。
    for(int idx=0; idx<notifyCount;idx++)
    {
       notifyPosition[idx].dwOffset =(idx + 1) * dwPieceLen; //设置这个事件通知的位置
       notifyPosition[idx].hEventNotify= notifyEvent[idx]= CreateEvent(NULL,FALSE,FALSE,NULL) ;//事件对象句柄。
    }
    //注意,最后一个notify的位置,刚好等于总长度,会有一个问题
    //一个buffer 有两种播放模式,一种单此播放,一种循环播放。
    //我们是要循环往里面填写数据,所以一定是循环播放;但是循环播放模式下,最后一个事件通知位置永远不会被触发
    //所以我们需要得到最后一个缓存片段播放完成的消息,又不能刚好让这个片段的通知位置等于总长度,于是就有下面这句代码
    notifyPosition[notifyCount-1].dwOffset-= 2 ;//最后一个通知位置往前移动两个字节,这样它就会被触发了,并且差不多是最后缓存的位置。
 
    hr = pSoundNotify->SetNotificationPositions(notifyCount,(LPDSBPOSITIONNOTIFY)¬ifyPosition);
    if(FAILED(hr))
       return hr ;
 
    //播放前首先要一次性填充满缓存。
    LPBYTE pPlayBuffer = NULL ;
    DWORD  dwBufferLen= 0 ;
    //第一步,锁定整个缓存区域
    hr = pDirectSoundBuffer->Lock(0,0,(LPVOID*)&pPlayBuffer,&dwBufferLen,NULL, NULL ,DSBLOCK_ENTIREBUFFER);
    if(FAILED(hr))
       return hr ;
    //第二步,从文件读取这么多的数据,写入缓存。
    UINT readLen= file.Read(pPlayBuffer, dwBufferLen);
    if(readLen != dwBufferLen)
       return 0 ;
 
    //第三步,解锁
    hr = pDirectSoundBuffer->Unlock(pPlayBuffer,dwBufferLen, NULL,NULL);
    if(FAILED(hr))
       return hr ;
 
    //播放第二个大步骤,开始DirectSound播放线程
    hr = pDirectSoundBuffer->Play(0,0,DSBPLAY_LOOPING);
    if(FAILED(hr))
       return hr ;
 
    //播放第三大步骤,等待缓存事件通知
 
    do
    {
       int bufIndex = WaitForMultipleObjects(notifyCount, notifyEvent,FALSE, INFINITE)- WAIT_OBJECT_0 ;
       //WAIT函数会一直等待某个缓存片段被播放完毕,当这个函数返回时,index代表的缓存片段已经播放完毕了。
 
       hr = pDirectSoundBuffer->Lock(bufIndex * dwPieceLen,dwPieceLen, (LPVOID*)&pPlayBuffer,&dwBufferLen,NULL,NULL,0) ;
       if(FAILED(hr))
           break ;
 
       readLen = file.Read(pPlayBuffer, dwBufferLen); //读文件,同时写入缓存。
 
       hr = pDirectSoundBuffer->Unlock(pPlayBuffer, dwBufferLen,NULL, NULL);//解锁缓存。
       if(FAILED(hr))
           break ;
 
    } while (readLen == dwBufferLen);
 
    ::CoUninitialize();
 
    return TRUE;  // 除非将焦点设置到控件,否则返回TRUE
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值