前言

  该篇整理的原始来源为http://blog.csdn.net/leixiaohua1020/article/details/40540147。非常感谢该博主的无私奉献,写了不少关于不同多媒体库的博文。让我这个小白学习到不少。现在将其整理是为了收录,以备自己查看。

一、DirectSound简介

  DirectSound是微软所开发DirectX的组件之一,可以在Windows 操作系统上录音,并且记录波形音效(waveform sound)。目前DirectSound 是一个成熟的API ,提供许多有用的功能,例如能够在较高的分辨率播放多声道声音。DirectSound3D(DS3D)最早是1993年与 DirectX 3 一起发表的。DirectX 8以后的DirectSound和DirectSound3D的(DS3D)被合称DirectX Audio。

  DirectSound有以下几种对象:

图1.DirectSound对象

二、DirectSound播放音频的流程

使用DirectSound播放音频一般情况下需要如下步骤:

1.初始化

  • 创建一个IDirectSound8接口的对象
  • 设置协作级
  • 创建一个主缓冲对象
  • 创建一个副缓冲对象
  • 创建通知对象
  • 设置通知位置
  • 开始播放

2.循环播放声音

  • 数据填充至副缓冲区
  • 等待播放完成

三、结合接口详细分析

1.初始化

1)创建一个IDirectSound8接口的对象

  通过DirectSoundCreate8()方法可以创建一个设备对象。这个对象通常代表缺省的播放设备。DirectSoundCreate8()函数原型如下。

 HRESULT DirectSoundCreate8(
LPCGUID lpcGuidDevice,
LPDIRECTSOUND8 * ppDS8,
LPUNKNOWN pUnkOuter
)

参数的含义如下:

lpcGuidDevice:要创建的设备对象的GUID。可以指定为NULL,代表默认的播放设备。
ppDS8:返回的IDirectSound8对象的地址。
pUnkOuter:必须设为NULL。

例如如下代码即可创建一个IDirectSound8接口的对象

 IDirectSound8 *m_pDS=NULL;
DirectSoundCreate8(NULL,&m_pDS,NULL);

2) 设置协作级

  Windows 是一个多任务环境,同一时间有多个应用程序去访问设备。通过使用协作级别,DirectSound可以确保应用程序不会在别的设备使用时去访问,每个 DirectSound应用程序都有一个协作级别,这个级别决定着访问硬件的权限。

  在创建一个设备对象以后,必须通过用IDirectSound8的SetCooperativeLevel()设置协作权限,否则将听不到声音。SetCooperativeLevel()的原型如下

 HRESULT SetCooperativeLevel(
HWND hwnd,
DWORD dwLevel
)

参数的含义如下:

hwnd:应用程序窗口句柄。
dwLevel:支持以下几种级别:
DSSCL_EXCLUSIVE:与DSSCL_PRIORITY具有相同的作用。
DSSCL_NORMAL:正常的协调层级标志,其他程序可共享声卡设备进行播放。
DSSCL_PRIORITY:设置声卡设备为当前程序独占。
DSSCL_WRITEPRIMAR:可写主缓冲区,此时副缓冲区就不能进行播放处理,即不能将次缓冲区的数据送进混声器,再输出到主缓冲区上。这是最完全控制声音播放的方式。

 3) 创建一个主缓冲对象

  使用IDirectSound8的CreateSoundBuffer()可以创建一个IDirectSoundBuffer接口的主缓冲区对象。CreateSoundBuffer()的原型如下。

 HRESULT CreateSoundBuffer(
LPCDSBUFFERDESC pcDSBufferDesc,
LPDIRECTSOUNDBUFFER * ppDSBuffer,
LPUNKNOWN pUnkOuter
)

参数的含义如下:
pcDSBufferDesc:描述声音缓冲的DSBUFFERDESC结构体的地址
ppDSBuffer:返回的IDirectSoundBuffer接口的对象的地址。
pUnkOuter:必须设置为NULL。

  其中涉及到一个描述声音缓冲的结构体DSBUFFERDESC,该结构体的定义如下:

 typedef struct _DSBUFFERDESC
{
DWORD dwSize;
DWORD dwFlags;
DWORD dwBufferBytes;
DWORD dwReserved;
LPWAVEFORMATEX lpwfxFormat;
} DSBUFFERDESC

简单解释一下其中的变量的含义:
dwSize:结构体的大小。必须初始化该值。
dwFlags:设置声音缓存的属性。有很多选项,可以组合使用,就不一一列出了。详细的参数可以查看文档。
dwBufferBytes:缓冲的大小。
dwReserved:保留参数,暂时没有用。
lpwfxFormat:指向一个WAVE格式文件头的指针。

  设置DSBUFFERDESC完毕后,就可以使用CreateSoundBuffer()创建主缓冲了。示例代码如下:

     DSBUFFERDESC dsbd;
memset(&dsbd,,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/)*channels;
/* for buffer estimation */
(dsbd.lpwfxFormat)->nBlockAlign=(bits_per_sample/)*channels;
/* block size of data */
(dsbd.lpwfxFormat)->wBitsPerSample=bits_per_sample;
/* number of bits per sample of mono data */
(dsbd.lpwfxFormat)->cbSize=; //Creates a sound buffer object to manage audio samples.
HRESULT hr1;
if( FAILED(m_pDS->CreateSoundBuffer(&dsbd,&m_pDSBuffer,NULL))){
return FALSE;
}

4) 创建一个副缓冲对象

  使用IDirectSoundBuffer的QueryInterface()可以得到一个IDirectSoundBuffer8接口的对象。IDirectSoundBuffer8的GUID为IID_IDirectSoundBuffer8。示例代码如下。

 IDirectSoundBuffer *m_pDSBuffer=NULL;
IDirectSoundBuffer8 *m_pDSBuffer8=NULL;
...
if( FAILED(m_pDSBuffer->QueryInterface(IID_IDirectSoundBuffer8,(LPVOID*)&m_pDSBuffer8))){
return FALSE ;
}

5) 创建通知对象

  使用IDirectSoundBuffer8的QueryInterface()可以得到一个IDirectSoundNotify8接口的对象。IDirectSoundBuffer8的GUID为IID_IDirectSoundNotify。示例代码如下。

 IDirectSoundBuffer8 *m_pDSBuffer8=NULL;
IDirectSoundNotify8 *m_pDSNotify=NULL;

if(FAILED(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*)&m_pDSNotify))){
return FALSE ;
}

  一句话概括一下通知对象的作用:当DirectSound缓冲区中的数据播放完毕后,告知系统应该填充新的数据。

6) 设置通知位置

  使用IDirectSoundNotify8的SetNotificationPositions()可以设置通知的位置。SetNotificationPositions()的原型如下。

 HRESULT SetNotificationPositions(
DWORD dwPositionNotifies,
LPCDSBPOSITIONNOTIFY pcPositionNotifies
)

参数含义如下。
dwPositionNotifies:DSBPOSITIONNOTIFY结构体的数量。既包含几个通知的位置。
pcPositionNotifies:指向DSBPOSITIONNOTIFY结构体数组的指针。

  在这里涉及到一个结构体DSBPOSITIONNOTIFY,它描述了通知的位置。DSBPOSITIONNOTIFY的定义如下。

 typedef struct DSBPOSITIONNOTIFY {
DWORD dwOffset;
HANDLE hEventNotify;
} DSBPOSITIONNOTIFY;

它的成员的含义如下。
dwOffset:通知事件触发的位置(距离缓冲开始位置的偏移量)。
hEventNotify:触发的事件的句柄。

7) 开始播放

  使用IDirectSoundBuffer8的SetCurrentPosition ()可以设置播放的位置。SetCurrentPosition ()原型如下

 HRESULT SetCurrentPosition(
DWORD dwNewPosition
)

其中dwNewPosition是播放点与缓冲区首个字节之间的偏移量。
  使用IDirectSoundBuffer8的Play ()可以开始播放音频数据。Play ()原型如下。

 HRESULT Play(
DWORD dwReserved1,
DWORD dwPriority,
DWORD dwFlags
)

参数含义:
dwReserved1:保留参数,必须取0。
dwPriority:优先级,一般情况下取0即可。
dwFlags:标志位。目前常见的是DSBPLAY_LOOPING。当播放至缓冲区结尾的时候,重新从缓冲区开始处开始播放。

2. 循环播放声音

1) 数据填充至副缓冲区

  数据填充至副缓冲区之前,需要先使用Lock()锁定缓冲区。然后就可以使用fread(),memcpy()等方法将PCM音频采样数据填充至缓冲区。数据填充完毕后,使用Unlock()取消对缓冲区的锁定。如果是实时采集的音频数据,只要将音频数据复制到Lock()获取到的ppvAudioPtr1指向的地址,大小为pdwAudioBytes1,就可以播放了。(我使用的方式就是如此,实现了实时音频的播放,下文中的例子数据是读取自文件。)

  Lock()函数的原型如下。

 HRESULT Lock(
DWORD dwOffset,
DWORD dwBytes,
LPVOID * ppvAudioPtr1,
LPDWORD pdwAudioBytes1,
LPVOID * ppvAudioPtr2,
LPDWORD pdwAudioBytes2,
DWORD dwFlags
)

参数的含义如下。
dwOffset:锁定的内存与缓冲区首地址之间的偏移量。
dwBytes:锁定的缓存的大小。
ppvAudioPtr1:获取到的指向缓存数据的指针。
pdwAudioBytes1:获取到的缓存数据的大小。
ppvAudioPtr2:没有用到,设置为NULL。
pdwAudioBytes2:没有用到,设置为0。
dwFlags:暂时没有研究。

  UnLock()函数的原型如下。

 HRESULT Unlock(
LPVOID pvAudioPtr1,
DWORD dwAudioBytes1,
LPVOID pvAudioPtr2,
DWORD dwAudioBytes2
)

参数含义如下。
pvAudioPtr1:通过Lock()获取到的指向缓存数据的指针。
dwAudioBytes1:写入的数据量。
pvAudioPtr2:没有用到。
dwAudioBytes2:没有用到。

2) 等待播放完成

  根据此前设置的通知机制,使用WaitForMultipleObjects()等待缓冲区中的数据播放完毕,然后进入下一个循环。

四、播放音频流程总结

  DirectSound播放PCM音频数据的流程如下图所示。

图2

  其中涉及到的几个结构体之间的关系如下图所示。

图3.结构体关系

五、使用示例代码

  该代码也是直接使用的来自原博主的代码,如下

 /**
* 最简单的DirectSound播放音频的例子(DirectSound播放PCM)
* Simplest Audio Play DirectSound (DirectSound play PCM)
*
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* 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 <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <dsound.h> #define MAX_AUDIO_BUF 4
#define BUFFERNOTIFYSIZE 192000 int sample_rate=; //PCM sample rate
int channels=; //PCM channel number
int bits_per_sample=; //bits per sample BOOL main(int argc,char * argv[])
{
int i;
FILE * fp;
if((fp=fopen("../out.pcm","rb"))==NULL){
printf("cannot open this file\n");
return -;
} IDirectSound8 *m_pDS=;
IDirectSoundBuffer8 *m_pDSBuffer8=NULL; //used to manage sound buffers.
IDirectSoundBuffer *m_pDSBuffer=NULL;
IDirectSoundNotify8 *m_pDSNotify=;
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,,sizeof(dsbd));
dsbd.dwSize=sizeof(dsbd);
dsbd.dwFlags=DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
dsbd.dwBufferBytes=MAX_AUDIO_BUF*BUFFERNOTIFYSIZE;
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/)*channels;
/* for buffer estimation */
(dsbd.lpwfxFormat)->nBlockAlign=(bits_per_sample/)*channels;
/* block size of data */
(dsbd.lpwfxFormat)->wBitsPerSample=bits_per_sample;
/* number of bits per sample of mono data */
(dsbd.lpwfxFormat)->cbSize=; //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 =;i<MAX_AUDIO_BUF;i++){
m_pDSPosNotify[i].dwOffset =i*BUFFERNOTIFYSIZE;
m_event[i]=::CreateEvent(NULL,false,false,NULL);
m_pDSPosNotify[i].hEventNotify=m_event[i];
}
m_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF,m_pDSPosNotify);
m_pDSNotify->Release(); //Start Playing
BOOL isPlaying =TRUE;
LPVOID buf=NULL;
DWORD buf_len=;
DWORD res=WAIT_OBJECT_0;
DWORD offset=BUFFERNOTIFYSIZE; m_pDSBuffer8->SetCurrentPosition();
m_pDSBuffer8->Play(,,DSBPLAY_LOOPING);
//Loop
while(isPlaying){
if((res >=WAIT_OBJECT_0)&&(res <=WAIT_OBJECT_0+)){
m_pDSBuffer8->Lock(offset,BUFFERNOTIFYSIZE,&buf,&buf_len,NULL,NULL,); // 如果是实时音频播放,那么下面的数据就可以把内存中buf_len大小的数据复制到buf指向的地址即可
if(fread(buf,,buf_len,fp)!=buf_len){
//File End
//Loop:
fseek(fp, , SEEK_SET);
fread(buf,,buf_len,fp);
//Close:
//isPlaying=0;
} offset+=buf_len;
offset %= (BUFFERNOTIFYSIZE * MAX_AUDIO_BUF);
printf("this is %7d of buffer\n",offset);
m_pDSBuffer8->Unlock(buf,buf_len,NULL,);
}
res = WaitForMultipleObjects (MAX_AUDIO_BUF, m_event, FALSE, INFINITE);
} return ;
}

结语

  最后,再次强调下,该博文是整理自http://blog.csdn.net/leixiaohua1020/article/details/40540147。我只是改变了一点点格式,其实改变的地方非常少。只是加了点注释,即播放实时内存数据怎么使用(这是我在项目中的使用方式)。我一再强调,是为了尊重原博主的工作,毕竟直接把别人的东西拿来当作自己的,那就是小偷了。

DirectSound播放PCM(可播放实时采集的音频数据)的更多相关文章

  1. 采用Flume实时采集和处理数据

    它已成功安装Flume在...的基础上.本文将总结使用Flume实时采集和处理数据,详细过程,如下面: 第一步,在$FLUME_HOME/conf文件夹下,编写Flume的配置文件,命名为flume_ ...

  2. (原)关于OpenSL ES播放音频数据的一个奇怪的问题

    关于OpenSL ES播放音频数据的一个奇怪的问题 Author:lihaiping1603@aliyun.com 最近用业余时间做了一个android平台的播放器sdk,其中视频用的opengl e ...

  3. 最简单的视音频播放示例8:DirectSound播放PCM

    本文记录DirectSound播放音频的技术.DirectSound是Windows下最常见的音频播放技术.目前大部分的音频播放应用都是通过DirectSound来播放的.本文记录一个使用Direct ...

  4. 最简单的视音频播放演示样例8:DirectSound播放PCM

    ===================================================== 最简单的视音频播放演示样例系列文章列表: 最简单的视音频播放演示样例1:总述 最简单的视音频 ...

  5. 通过WinAPI播放PCM声音

    在Windows平台上,播放PCM声音使用的API通常有如下两种. waveOut and waveIn:传统的音频MMEAPI,也是使用的最多的 xAudio2:C++/COM API,主要针对游戏 ...

  6. ffplay代码播放pcm数据

    摘抄雷兄 http://blog.csdn.net/leixiaohua1020/article/details/46890259 /** * 最简单的SDL2播放音频的例子(SDL2播放PCM) * ...

  7. 最简单的视音频播放示例9:SDL2播放PCM

    本文记录SDL播放音频的技术.在这里使用的版本是SDL2.实际上SDL本身并不提供视音频播放的功能,它只是封装了视音频播放的底层API.在Windows平台下,SDL封装了Direct3D这类的API ...

  8. Android 音视频深入 二 AudioTrack播放pcm(附源码下载)

    本篇项目地址,名字是录音和播放PCM,求starhttps://github.com/979451341/Audio-and-video-learning-materials 1.AudioTrack ...

  9. AudioRecord 录制播放PCM音频

    AudioRecord 与 MediaRecorder 区别 AudioRecord 基于字节流录制,输出的是pcm数据,未进行压缩,直接保存的pcm文件不能被播放器识别播放. 可以对音频文件进行实时 ...

随机推荐

  1. Linux C 文件与目录1 创建目录

    linux C    创建目录 创建目录函数:mkdir 函数原型:int mkdir(char * pathname , mode_t mode); pathname字符指针是表示需要创建的目录路径 ...

  2. c语言变量作用域问题

    c语言中的变量作用域总结 不管什么语言,main好像总是程序的入口,大括号是它的内容:变量的作用域总是困扰着我们,接下来,我们循序渐进的搞明白c语言中的变量作用域,首先得知道c是弱类型的语言,弱类型表 ...

  3. android动态增加控件时控制样式的方法

    在学习android的动画时,发现所谓的tween动画只是改变绘制效果并不改变原控件的位置时是颇为失望的,虽然3.0之后已经有了property animation,但是由于要兼容老版本的androi ...

  4. MarkdownPad2添加目录(输出为HTML时可用)

    平时看书的时候懒得上网写在线博客,就在电脑上用了很长时间的MarkDownPad2来记录自己的心得笔记,等那天高兴了再把他们贴出来.界面清爽,是我使用它最重要的原因,但是MarkdownPad2导出的 ...

  5. md RAID

    md: mdadm: 将任何块设备做成RAID 模式化的命令:1.创建模式 -C 专用选项: -l: 级别 -n #: 设备个数 -a {yes|no}: 是否自动为其创建设备文件 -c: CHUNK ...

  6. 移动端页面使用rem来做适配

    文/九彩拼盘(简书作者)原文链接:http://www.jianshu.com/p/eb05c775d3c6著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. rem介绍 rem(font ...

  7. Apache代理和反向代理

    服务器上安装了多个服务,包括apache的80端口,以及tomcat的8080和8090,为了访问使用方便,尝试了代理和反向代理.下面是部分配置以备参考: NameVirtualHost *:80 & ...

  8. Open Phone, SMS, Email, Skype and Browser apps of Android in Unity3d

    最近项目需要使用Android的一些基本功能,写插件各种悲剧,google了一下,如获至宝.Nice ! string url = String.Format("tel:{0}", ...

  9. Json.net/Newtonsoft 3.0 新特性JObject/Linq to Json

    原文:http://www.cnblogs.com/chsword/archive/2008/09/19/Newtonsoft_new_3_0.html http://www.cnblogs.com/ ...

  10. Silverlight弹出层(转载)

    ChildWindow为Silverlight中的弹出子窗口 可以在项目新建子窗口文件: 相互传值: //父窗体向子窗体传值,需要在ChildWindow中构造函数进行传值ChildWindowTes ...