=====================================================
最简单的视音频播放示例系列文章列表:
最简单的视音频播放示例1:总述
最简单的视音频播放示例2:GDI播放YUV, RGB
最简单的视音频播放示例3:Direct3D播放YUV,RGB(通过Surface)
最简单的视音频播放示例4:Direct3D播放RGB(通过Texture)
最简单的视音频播放示例5:OpenGL播放RGB/YUV
最简单的视音频播放示例6:OpenGL播放YUV420P(通过Texture,使用Shader)
最简单的视音频播放示例7:SDL2播放RGB/YUV
最简单的视音频播放示例8:DirectSound播放PCM
最简单的视音频播放示例9:SDL2播放PCM
=====================================================
本文记录DirectSound播放音频的技术。DirectSound是Windows下最常见的音频播放技术。目前大部分的音频播放应用都是通过DirectSound来播放的。本文记录一个使用DirectSound播放PCM的例子。
注:一位仁兄已经提醒我DirectSound已经计划被XAudio2取代了。后来考证了一下发现确有此事。因此在下次更新中考虑加入XAudio2播放PCM的例子。本文仍然记录一下DirectSound这位“元老”。对象 |
数量 |
作用 |
主要接口 |
设备 |
每个应用程序只有一个设备对象 |
用来管理设备,创建辅助缓冲区 |
IDirectSound8 |
辅助缓冲区 |
每一个声音对应一个辅助缓冲区 |
用来管理一个静态的或者动态的声音流,然后在主缓冲区中混音 |
IDirectSoundBuffer8, IDirectSound3DBuffer8, IDirectSoundNotify8 |
主缓冲区 |
一个应用程序只有一个主缓冲区 |
将辅助缓冲区的数据进行混音,并且控制3D参数. |
IDirectSoundBuffer, IDirectSound3DListener8 |
使用DirectSound播放音频一般情况下需要如下步骤:
1. 初始化1) 创建一个IDirectSound8接口的对象2. 循环播放声音
2) 设置协作级
3) 创建一个主缓冲对象
4) 创建一个副缓冲对象
5) 创建通知对象
6) 设置通知位置
7) 开始播放
1) 数据填充至副缓冲区下面结合详细分析一下上文的流程。
2) 等待播放完成
HRESULT DirectSoundCreate8(
LPCGUID lpcGuidDevice,
LPDIRECTSOUND8 * ppDS8,
LPUNKNOWN pUnkOuter
)
IDirectSound8 *m_pDS=NULL;
DirectSoundCreate8(NULL,&m_pDS,NULL);
HRESULT SetCooperativeLevel(
HWND hwnd,
DWORD dwLevel
)
HRESULT CreateSoundBuffer(
LPCDSBUFFERDESC pcDSBufferDesc,
LPDIRECTSOUNDBUFFER * ppDSBuffer,
LPUNKNOWN pUnkOuter
)
typedef struct _DSBUFFERDESC
{
DWORD dwSize;
DWORD dwFlags;
DWORD dwBufferBytes;
DWORD dwReserved;
LPWAVEFORMATEX lpwfxFormat;
} DSBUFFERDESC
DSBUFFERDESC dsbd;
memset(&dsbd,0,sizeof(dsbd));
dsbd.dwSize=sizeof(dsbd);
dsbd.dwFlags=DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
dsbd.dwBufferBytes=MAX_AUDIO_BUF*BUFFERNOTIFYSIZE;
//WAVE Header
dsbd.lpwfxFormat=(WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
dsbd.lpwfxFormat->wFormatTag=WAVE_FORMAT_PCM;
/* format type */
(dsbd.lpwfxFormat)->nChannels=channels;
/* number of channels (i.e. mono, stereo...) */
(dsbd.lpwfxFormat)->nSamplesPerSec=sample_rate;
/* sample rate */
(dsbd.lpwfxFormat)->nAvgBytesPerSec=sample_rate*(bits_per_sample/8)*channels;
/* for buffer estimation */
(dsbd.lpwfxFormat)->nBlockAlign=(bits_per_sample/8)*channels;
/* block size of data */
(dsbd.lpwfxFormat)->wBitsPerSample=bits_per_sample;
/* number of bits per sample of mono data */
(dsbd.lpwfxFormat)->cbSize=0;
//Creates a sound buffer object to manage audio samples.
HRESULT hr1;
if( FAILED(m_pDS->CreateSoundBuffer(&dsbd,&m_pDSBuffer,NULL))){
return FALSE;
}
IDirectSoundBuffer *m_pDSBuffer=NULL;
IDirectSoundBuffer8 *m_pDSBuffer8=NULL;
...
if( FAILED(m_pDSBuffer->QueryInterface(IID_IDirectSoundBuffer8,(LPVOID*)&m_pDSBuffer8))){
return FALSE ;
}
IDirectSoundBuffer8 *m_pDSBuffer8=NULL;
IDirectSoundNotify8 *m_pDSNotify=NULL;
…
if(FAILED(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*)&m_pDSNotify))){
return FALSE ;
}
HRESULT SetNotificationPositions(
DWORD dwPositionNotifies,
LPCDSBPOSITIONNOTIFY pcPositionNotifies
)
typedef struct DSBPOSITIONNOTIFY {
DWORD dwOffset;
HANDLE hEventNotify;
} DSBPOSITIONNOTIFY;
HRESULT SetCurrentPosition(
DWORD dwNewPosition
)
HRESULT Play(
DWORD dwReserved1,
DWORD dwPriority,
DWORD dwFlags
)
HRESULT Lock(
DWORD dwOffset,
DWORD dwBytes,
LPVOID * ppvAudioPtr1,
LPDWORD pdwAudioBytes1,
LPVOID * ppvAudioPtr2,
LPDWORD pdwAudioBytes2,
DWORD dwFlags
)
HRESULT Unlock(
LPVOID pvAudioPtr1,
DWORD dwAudioBytes1,
LPVOID pvAudioPtr2,
DWORD dwAudioBytes2
)
dwAudioBytes2:没有用到。
2) 等待播放完成
根据此前设置的通知机制,使用WaitForMultipleObjects()等待缓冲区中的数据播放完毕,然后进入下一个循环。DirectSound播放PCM音频数据的流程如下图所示。
贴上源代码。
/**
* 最简单的DirectSound播放音频的例子(DirectSound播放PCM)
* Simplest Audio Play DirectSound (DirectSound play PCM)
*
* 雷霄骅 Lei Xiaohua
* [email protected]
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 本程序使用DirectSound播放PCM音频采样数据。
* 是最简单的DirectSound播放音频的教程。
*
* 函数调用步骤如下:
*
* [初始化]
* DirectSoundCreate8(): 创建一个DirectSound对象。
* SetCooperativeLevel(): 设置协作权限,不然没有声音。
* IDirectSound8->CreateSoundBuffer(): 创建一个主缓冲区对象。
* IDirectSoundBuffer->QueryInterface(IID_IDirectSoundBuffer8..):
* 创建一个副缓冲区对象,用来存储要播放的声音数据文件。
* IDirectSoundBuffer8->QueryInterface(IID_IDirectSoundNotify..):
* 创建通知对象,通知应用程序指定播放位置已经达到。
* IDirectSoundNotify8->SetNotificationPositions(): 设置通知位置。
* IDirectSoundBuffer8->SetCurrentPosition(): 设置播放的起始点。
* IDirectSoundBuffer8->Play(): 开始播放。
*
* [循环播放数据]
* IDirectSoundBuffer8->Lock(): 锁定副缓冲区,准备写入数据。
* fread(): 读取数据。
* IDirectSoundBuffer8->Unlock(): 解锁副缓冲区。
* WaitForMultipleObjects(): 等待“播放位置已经达到”的通知。
*
* This software plays PCM raw audio data using DirectSound.
* It's the simplest tutorial about DirectSound.
*
* The process is shown as follows:
*
* [Init]
* DirectSoundCreate8(): Init DirectSound object.
* SetCooperativeLevel(): Must set, or we won't hear sound.
* IDirectSound8->CreateSoundBuffer(): Create primary sound buffer.
* IDirectSoundBuffer->QueryInterface(IID_IDirectSoundBuffer8..):
* Create secondary sound buffer.
* IDirectSoundBuffer8->QueryInterface(IID_IDirectSoundNotify..):
* Create Notification object.
* IDirectSoundNotify8->SetNotificationPositions():
* Set Notification Positions.
* IDirectSoundBuffer8->SetCurrentPosition(): Set position to start.
* IDirectSoundBuffer8->Play(): Begin to play.
*
* [Loop to play data]
* IDirectSoundBuffer8->Lock(): Lock secondary buffer.
* fread(): get PCM data.
* IDirectSoundBuffer8->Unlock(): UnLock secondary buffer.
* WaitForMultipleObjects(): Wait for Notifications.
*/
#include
#include
#include
#include
#define MAX_AUDIO_BUF 4
#define BUFFERNOTIFYSIZE 192000
int sample_rate=44100; //PCM sample rate
int channels=2; //PCM channel number
int bits_per_sample=16; //bits per sample
BOOL main(int argc,char * argv[])
{
int i;
FILE * fp;
if((fp=fopen("../NocturneNo2inEflat_44.1k_s16le.pcm","rb"))==NULL){
printf("cannot open this file\n");
return -1;
}
IDirectSound8 *m_pDS=NULL;
IDirectSoundBuffer8 *m_pDSBuffer8=NULL; //used to manage sound buffers.
IDirectSoundBuffer *m_pDSBuffer=NULL;
IDirectSoundNotify8 *m_pDSNotify=NULL;
DSBPOSITIONNOTIFY m_pDSPosNotify[MAX_AUDIO_BUF];
HANDLE m_event[MAX_AUDIO_BUF];
SetConsoleTitle(TEXT("Simplest Audio Play DirectSound"));//Console Title
//Init DirectSound
if(FAILED(DirectSoundCreate8(NULL,&m_pDS,NULL)))
return FALSE;
if(FAILED(m_pDS->SetCooperativeLevel(FindWindow(NULL,TEXT("Simplest Audio Play DirectSound")),DSSCL_NORMAL)))
return FALSE;
DSBUFFERDESC dsbd;
memset(&dsbd,0,sizeof(dsbd));
dsbd.dwSize=sizeof(dsbd);
dsbd.dwFlags=DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
dsbd.dwBufferBytes=MAX_AUDIO_BUF*BUFFERNOTIFYSIZE;
//WAVE Header
dsbd.lpwfxFormat=(WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
dsbd.lpwfxFormat->wFormatTag=WAVE_FORMAT_PCM;
/* format type */
(dsbd.lpwfxFormat)->nChannels=channels;
/* number of channels (i.e. mono, stereo...) */
(dsbd.lpwfxFormat)->nSamplesPerSec=sample_rate;
/* sample rate */
(dsbd.lpwfxFormat)->nAvgBytesPerSec=sample_rate*(bits_per_sample/8)*channels;
/* for buffer estimation */
(dsbd.lpwfxFormat)->nBlockAlign=(bits_per_sample/8)*channels;
/* block size of data */
(dsbd.lpwfxFormat)->wBitsPerSample=bits_per_sample;
/* number of bits per sample of mono data */
(dsbd.lpwfxFormat)->cbSize=0;
//Creates a sound buffer object to manage audio samples.
HRESULT hr1;
if( FAILED(m_pDS->CreateSoundBuffer(&dsbd,&m_pDSBuffer,NULL))){
return FALSE;
}
if( FAILED(m_pDSBuffer->QueryInterface(IID_IDirectSoundBuffer8,(LPVOID*)&m_pDSBuffer8))){
return FALSE ;
}
//Get IDirectSoundNotify8
if(FAILED(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*)&m_pDSNotify))){
return FALSE ;
}
for(i =0;iSetNotificationPositions(MAX_AUDIO_BUF,m_pDSPosNotify);
m_pDSNotify->Release();
//Start Playing
BOOL isPlaying =TRUE;
LPVOID buf=NULL;
DWORD buf_len=0;
DWORD res=WAIT_OBJECT_0;
DWORD offset=BUFFERNOTIFYSIZE;
m_pDSBuffer8->SetCurrentPosition(0);
m_pDSBuffer8->Play(0,0,DSBPLAY_LOOPING);
//Loop
while(isPlaying){
if((res >=WAIT_OBJECT_0)&&(res <=WAIT_OBJECT_0+3)){
m_pDSBuffer8->Lock(offset,BUFFERNOTIFYSIZE,&buf,&buf_len,NULL,NULL,0);
if(fread(buf,1,buf_len,fp)!=buf_len){
//File End
//Loop:
fseek(fp, 0, SEEK_SET);
fread(buf,1,buf_len,fp);
//Close:
//isPlaying=0;
}
m_pDSBuffer8->Unlock(buf,buf_len,NULL,0);
offset+=buf_len;
offset %= (BUFFERNOTIFYSIZE * MAX_AUDIO_BUF);
printf("this is %7d of buffer\n",offset);
}
res = WaitForMultipleObjects (MAX_AUDIO_BUF, m_event, FALSE, INFINITE);
}
return 0;
}
代码运行之后,会弹出一个“控制台”对话框如下图所示。同时音频设备里面可以听到播放的声音。
代码位于“Simplest Media Play”中
SourceForge项目地址:https://sourceforge.net/projects/simplestmediaplay/
CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8054395
注:
该项目会不定时的更新并修复一些小问题,最新的版本请参考该系列文章的总述页面:
《最简单的视音频播放示例1:总述》