[来源天极网]

摘要:DirectSound编程的入门介绍,通过实例讲解了如何利用DirectSound最基本的功能-----播放音频,并提供了DirectSound播放音频文件的两种方式(Static buffer 和Streaming buffer ),

  关键词: Directsound stream buffer ,static buffer wave 文件播放

  一、配置DirectDounf的开发环境

  在进行DirectSound开发之前,一定要设置好开发环境,否则编译时会提示你很多东西都找不到定义,DirectSound的开发环境很好设置,简单的说就是包含一些头文件,将lib文件添加要工程中。仅仅包含dsound.h肯定是不够的,一般来说,在你的工程中包含下面两个文件就够了。

#include <mmsystem.h>
#include <dsound.h>

  如果你还想使用Dsound的API的话,那么你就要在你的vc开发环境中添加Dsound..lib库,如果你的程序还提示有很多的外部链接找不到,那么我建议你可以将下面的库都添加到你的工程中comctl32.lib dxerr9.lib winmm.lib dsound.lib dxguid.lib odbc32.lib odbccp32.lib,这些是我从Dsound提供的例子中得到的,肯定够你用的,如下图

 

  开发环境配置好了。你可以在你的工程中任意使用DirectSound提供的接口和函数了。下面简单介绍DirectSound开发中要用到的对象。

  二、DiectDound几个对象

  我们首先学习一下Directsound中常用的几个对象,简单学习一下哦DirectSound其实很简单的,主要有下面常用的几个对象。

对象 数量 作用 主要接口
设备对象 每个应用程序只有一个设备对象 用来管理设备,创建辅助缓冲区 IDirectSound8
辅助缓冲区对象 每一个声音对应一个辅助缓冲区,可以有多个辅助缓冲区 用来管理一个静态的或者动态的声音流,然后在主缓冲区中混音 IDirectSoundBuffer8,
IDirectSound3DBuffer8,
IDirectSoundNotify8
主缓冲区对象 一个应用程序只有一个主缓冲区 将辅助缓冲区的数据进行混音,并且控制3D参数. IDirectSoundBuffer, IDirectSound3DListener8
特技对象 没有 来辅助缓冲的声音数据进行处理 8个特技接口IDirectSoundFXChorus8

  首先,要创建一个设备对象,然后通过设备对象创建缓冲区对象。辅助缓冲区由应用程序创建和管理,DirectSound会自动地创建和管理主缓冲区,一般来说,应用程序即使没有获取这个主缓冲区对象的接口也可以播放音频数据,但是,如果应用程序要想得到IDirectSound3DListener8接口,就必须要自己创建一个主缓冲区。
三、播放音频文件开发的基本流程

  下面我们简单的来学习一下如果通过Directsound的API播放声音,既然是breif overview,那么详细的内容你可以参考下面的一节内容,这里只是简单的介绍一下播放声音的步骤。

第一步,创建一个设备对象,设置设备对象的协作度。

  在你的代码中你可以通过调用DirectSoundCreat8函数来创建一个支持IDirectSound8接口的对象,这个对象通常代表缺省的播放设备。当然你可以枚举可用的设备,然后将设备的GUID传递给DirectSoundCreat8函数。

  如果没有声音输出设备,这个函数就返回error,或者,在VXD驱动程序下,如果声音输出设备正被某个应用程序通过waveform格式的api函数所控制,该函数也返回error。

  下面是创建对象的代码,及其简单

LPDIRECTSOUND8 lpDirectSound;
HRESULT hr = DirectSoundCreate8(NULL, & lpDirectSound, NULL));

  注意,Directsound虽然基于COM,但是你并不需要初始化com库,这些Directsound都帮你做好了,当然,如果你使用DMOs特技,你就要自己初始化com库了,切记。

  因为Windows是一个多任务操作环境,在同一个时刻有可能多个应用程序共用同一个设备,通过协作水平,DirectX就可以保证这些应用程序在访问设备的时候不会冲突,每个Directsound应用程序都有一个协作度,用来确定来接近设备的程度,当你创建完设备对象后,一定要调用IDirectSound8::SetCooperativeLevel来设置协作度,否则,你不会听到声音的。

HRESULT hr = lpDirectSound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY);
if (FAILED(hr))
{
 ErrorHandler(hr); // Add error-handling here.
}

  第二步,创建一个辅助Buffer,也叫后备缓冲区

  你可以通过IDirectSound8::CreateSoundBuffer来创建buffer对象,这个对象主要用来获取处理数据,这种buffer称作辅助缓冲区,以和主缓冲区区别开来,Direcsound通过把几个后备缓冲区的声音混合到主缓冲区中,然后输出到声音输出设备上,达到混音的效果。

  第三步,获取PCM类型的数据

  将WAV文件或者其他资源的数据读取到缓冲区中。

  第四步,将数据读取到缓冲区

  你可以通过 IDirectSoundBuffer8::Lock.方法来准备一个辅助缓冲区来进行写操作,通常这个方法返回一个内存地址,见数据从你的私人buffer中复制到这个地址中,然后调用IDirectSoundBuffer8::Unlock.

  第五步,播放缓冲区中的数据

  你可以通过IDirectSoundBuffer8::Play方法来播放缓冲区中的音频数据,你可以通过IDirectSoundBuffer8::Stop来暂停播放数据,你可以反复的莱停止,播放,音频数据,如果你同时创建了几个buffer,那么你就可以同时来播放这些数据,这些声音会自动进行混音的。

  你可以通过IDirectSoundBuffer8::GetVolume and IDirectSoundBuffer8::SetVolume函数来获取或者设置正在播放的音频的音量的大小。

  如果设置主缓冲区的音量就会改变声卡的音频的声量大小。音量的大小,用分贝来表示,一般没法来增强缺省的音量,这里要提示一下,分贝的增减不是线形的,减少3分贝相当于减少1/2的能量。最大值衰减100分贝几乎听不到了。

  通过IDirectSoundBuffer8::GetFrequency and IDirectSoundBuffer8::SetFrequency方法可以获取设置音频播放的频率,主缓冲区的频率不允许改动,通过 IDirectSoundBuffer8::GetPan and IDirectSoundBuffer8::SetPan函数可以设置音频在左右声道播放的位置,具有3D特性的缓冲区没法调整声道。

四、使用静态的缓冲区

  如果我们的wave文件不是很大,那么我们就可以使用静态的缓冲区了。

  包含全部音频数据的缓冲区我们称为静态的缓冲区,尽管,不同的声音可能会反复使用同一个内存buffer,但严格来说,静态缓冲区的数据只写入一次。

静态缓冲区的创建和管理和流缓冲区很相似,唯一的区别就是它们使用的方式不一样,静态缓冲区只填充一次数据,然后就可以play,然而,流缓冲区是一边play,一边填充数据。

  给静态缓冲区加载数据分下面几个步骤

  1、调用IDirectSoundBuffer8::Lock函数来锁定所有的内存,你要指定你锁定内存中你开始写入数据的偏移位置,并且取回该偏移位置的地址。

  2、采用标准的数据copy方法,将音频数据复制到返回的地址。

  3、调用IDirectSoundBuffer8::Unlock.,解锁该地址。

  下面我给出使用static buffer 播放wav文件的完整代码,首先定义我们需要的一些对象:

LPDIRECTSOUNDBUFFER8 g_pDSBuffer8 = NULL; //buffer
LPDIRECTSOUND8 g_pDsd = NULL; //dsound
CWaveFile *g_pWaveFile= NULL;
//下面初始化DirectSound工作。
HRESULT hr;
if(FAILED(hr = DirectSoundCreate8(NULL,&g_pDsd,NULL)))
 return FALSE;
 //设置设备的协作度
if(FAILED(hr = g_pDsd->SetCooperativeLevel(m_hWnd,DSSCL_PRIORITY)))
 return FALSE;

g_pWaveFile = new CWaveFile;
g_pWaveFile->Open(_T("d:\\test.wav"),NULL,WAVEFILE_READ);
DSBUFFERDESC dsbd;
ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) );
dsbd.dwSize = sizeof(DSBUFFERDESC);
dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLFX| DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
dsbd.dwBufferBytes = g_pWaveFile->GetSize();//MAX_AUDIO_BUF * BUFFERNOTIFYSIZE ;
dsbd.lpwfxFormat = g_pWaveFile->m_pwfx;
LPDIRECTSOUNDBUFFER lpbuffer;
//创建辅助缓冲区对象
if(FAILED(hr = g_pDsd->CreateSoundBuffer(&dsbd,&lpbuffer,NULL)))
 return ;
if( FAILED( hr = lpbuffer->QueryInterface( IID_IDirectSoundBuffer8, (LPVOID*) &g_pDSBuffer8) ) )
 return ;
lpbuffer->Release();
//准备工作做完了,下面就开始播放了
LPVOID lplockbuf;
DWORD len;
DWORD dwWrite;

g_pDSBuffer8->Lock(0,0,&lplockbuf,&len,NULL,NULL,DSBLOCK_ENTIREBUFFER);
g_pWaveFile->Read((BYTE*)lplockbuf,len,&dwWrite);
g_pDSBuffer8->Unlock(lplockbuf,len,NULL,0);
g_pDSBuffer8->SetCurrentPosition(0);
g_pDSBuffer8->Play(0,0,DSBPLAY_LOOPING);

  五、使用流缓冲区播放超大型的wave文件

  流缓冲区用来播放那些比较长的音频文件,因为数据比较长,没法一次填充到缓冲区中,一边播放,一边将新的数据填充到DirectSound的缓冲区中。

  可以通过IDirectSoundBuffer8::Play函授来播放缓冲区中的内容,注意在该函数的参数中一定要设置DSBPLAY_LOOPING标志。

  通过IDirectSoundBuffer8::Stop方法中断播放,该方法会立即停止缓冲区播放,因此你要确保所有的数据都被播放,你可以通过拖动播放位置或者设置通知位置来实现。

  将音频流倒入缓冲区需要下面三个步骤

  1、确保你的缓冲区已经做好接收新数据的准备。你可以拖放播放的光标位置或者等待通知

  2、调用IDirectSoundBuffer8::Lock.函数锁住缓冲区的位置,这个函数返回一个或者两个可以写入数据的地址

  3、使用标准的copy数据的方法将音频数据写入缓冲区中

  4、IDirectSoundBuffer8::Unlock.,解锁

  这里我要讲一下DirectSound的通知机制。因为Stream buffer 大小只够容纳一部分数据,因此,在播放完缓冲区中的数据后,DirectSound就会通知应用程序,将新的数据填充到DirectSound的缓冲区中。假如我们设置DirectSound的buffersize 为1920*4,如下图

  我们可以给DirectSound设置一个事件,并且设置buffer通知大小,如下:

HANDLE g_event[MAX_AUDIO_BUF];
for(int i =0; i< MAX_AUDIO_BUF;i++)
{
 g_aPosNotify[i].dwOffset = i* BUFFERNOTIFYSIZE ;
 g_aPosNotify[i].hEventNotify = g_event[i];
}
if(FAILED(hr = g_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID *) &g_pDSNotify )))
 return ;
g_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF,g_aPosNotify);
g_pDSNotify->Release();

  当DirectSound播放到buffer的1920,3840,5760,7680等位置时,Directsound就会通知应用程序,将g_ event,设置为通知态,应用程序就可以通过WaitForMultipleObjects 函数等待DirectSound的通知,将数据填充到DirectSoun的辅助缓冲区。

  下面我给出Stream buffer 播放wave文件的代码。

#define MAX_AUDIO_BUF 4
#define BUFFERNOTIFYSIZE 1920
LPDIRECTSOUNDBUFFER8 g_pDSBuffer8 = NULL; //buffer
LPDIRECTSOUND8 g_pDsd = NULL; //dsound
CWaveFile *g_pWaveFile= NULL;
BOOL g_bPlaying = FALSE; //是否正在播放
LPDIRECTSOUNDNOTIFY8 g_pDSNotify = NULL;
DSBPOSITIONNOTIFY g_aPosNotify[MAX_AUDIO_BUF];//设置通知标志的数组
HANDLE g_event[MAX_AUDIO_BUF];
DWORD g_dwNextWriteOffset = 0;

//初始化DirectSound
HRESULT hr;
if(FAILED(hr = DirectSoundCreate8(NULL,&g_pDsd,NULL)))
return FALSE;
if(FAILED(hr = g_pDsd->SetCooperativeLevel(m_hWnd,DSSCL_PRIORITY)))
return FALSE;

g_pWaveFile = new CWaveFile;
g_pWaveFile->Open(_T("d:\\test.wav"),NULL,WAVEFILE_READ);

DSBUFFERDESC dsbd;
ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) );
dsbd.dwSize = sizeof(DSBUFFERDESC);
dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
dsbd.dwBufferBytes = MAX_AUDIO_BUF * BUFFERNOTIFYSIZE ;
dsbd.lpwfxFormat = g_pWaveFile->m_pwfx;
LPDIRECTSOUNDBUFFER lpbuffer;
//创建DirectSound辅助缓冲区
if(FAILED(hr = g_pDsd->CreateSoundBuffer(&dsbd,&lpbuffer,NULL)))
 return FALSE;
if( FAILED( hr = lpbuffer->QueryInterface( IID_IDirectSoundBuffer8, (LPVOID*) &g_pDSBuffer8) ) )
 return FALSE;
lpbuffer->Release();
//设置DirectSound通知 机制
for(int i =0; i< MAX_AUDIO_BUF;i++)
{
 g_aPosNotify[i].dwOffset = i* BUFFERNOTIFYSIZE ;
 g_aPosNotify[i].hEventNotify = g_event[i];
}
if(FAILED(hr=g_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*) g_pDSNotify )))
 return ;
g_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF,g_aPosNotify);
g_pDSNotify->Release();
ok,在下面的play函数中,我们就要单独启动一个线程,来播放了
void OnBnClickedButtonPlay()
{
 g_bPlaying =TRUE;
 g_pWaveFile->ResetFile();
 CreateThread(0,0,PlayThread,this,NULL,NULL);
}
//停止播放音频
void CDsoundEffectDemoDlg::OnBnClickedButtonStop()
{
 // TODO: 在此添加控件通知处理程序代码
 g_bPlaying =FALSE;
 Sleep(500);
 g_pDSBuffer8->Stop();
}

  下面我们看看我们的播放线程,在线程里,我们首先将音频数据填充到DirectSound的辅助缓冲区中,然后调用DirectSound buffer 的play方法,开始播放,然后就在WaitForMultipleObjects 等待DirectSound的通知吧,然后读取wave文件将数据填充到DirectSound的空buffer中。

DWORD WINAPI PlayThread(LPVOID lpParame)
{
 DWORD res;
 LPVOID lplockbuf;
 DWORD len;
 DWORD dwWrite;

 g_pDSBuffer8->Lock(0,0,&lplockbuf,&len,NULL,NULL,DSBLOCK_ENTIREBUFFER);
 g_pWaveFile->Read((BYTE*)lplockbuf,len,&dwWrite);
 g_pDSBuffer8->Unlock(lplockbuf,len,NULL,0);
 g_pDSBuffer8->SetCurrentPosition(0);
 g_pDSBuffer8->Play(0,0,DSBPLAY_LOOPING);
 g_dwNextWriteOffset = 0;
 while(g_bPlaying)
 {
  res = WaitForMultipleObjects (MAX_AUDIO_BUF, g_event, FALSE, INFINITE);
  if(res > WAIT_OBJECT_0)
   ProcessBuffer();
 }

 return 0;
}

  下面的函数主要是给空的DirectSound缓冲区填充 音频数据。

void ProcessBuffer()
{
 DWORD dwBytesWrittenToBuffer = 0;
 VOID* pDSLockedBuffer = NULL;
 VOID* pDSLockedBuffer2 = NULL;
 DWORD dwDSLockedBufferSize;
 DWORD dwDSLockedBufferSize2;
 HRESULT hr;
 g_pDSBuffer8->Lock(g_dwNextWriteOffset,BUFFERNOTIFYSIZE,&pDSLockedBuffer,&dwDSLockedBufferSize, &pDSLockedBuffer2,&dwDSLockedBufferSize2,0);
 if(hr == DSERR_BUFFERLOST)
 {
  g_pDSBuffer8->Restore();
  g_pDSBuffer8->Lock(g_dwNextWriteOffset,BUFFERNOTIFYSIZE,&pDSLockedBuffer,&dwDSLockedBufferSize,
&pDSLockedBuffer2,&dwDSLockedBufferSize2,0);
 }
 if(SUCCEEDED(hr))
 {
  //write
  g_pWaveFile->Read((BYTE*)pDSLockedBuffer,dwDSLockedBufferSize,&dwBytesWrittenToBuffer);
  g_dwNextWriteOffset += dwBytesWrittenToBuffer;

  if (NULL != pDSLockedBuffer2)
  {
   g_pWaveFile->Read((BYTE*)pDSLockedBuffer2,dwDSLockedBufferSize2,&dwBytesWrittenToBuffer);
   g_dwNextWriteOffset += dwBytesWrittenToBuffer;
  }
  g_dwNextWriteOffset %= (BUFFERNOTIFYSIZE * MAX_AUDIO_BUF);

  if(dwBytesWrittenToBuffer <BUFFERNOTIFYSIZE )
  {
   FillMemory( (BYTE*) pDSLockedBuffer + dwBytesWrittenToBuffer,
   BUFFERNOTIFYSIZE - dwBytesWrittenToBuffer,
(BYTE)(g_pWaveFile->m_pwfx->wBitsPerSample == 8 ? 128 : 0 ) );

   g_bPlaying = FALSE;
  }
  hr = g_pDSBuffer8->Unlock(pDSLockedBuffer,dwDSLockedBufferSize,
  pDSLockedBuffer2,dwDSLockedBufferSize2);
 }
}

DirectSound播放音频应用程序开发快速入门相关推荐

  1. 【C语言】C 程序开发快速入门

    文章目录 1. C 程序开发快速入门 2. C 程序运行机制流程 3. 编译.链接和运行详解 4. C 程序开发注意事项 5. 如果想只生成目标 exe 文件,不想执行结果 1. C 程序开发快速入门 ...

  2. 微信小程序开发快速入门

    最近整理文件,找到一个18年写的微信小程序开发快速入门,对于新手还是值得一看的,三年多过去了,可能一些接口已经更新了,不过,整体思想还是没变的. 如果你熟悉JavaScript,那你基本上看完这个文档 ...

  3. Go语言小程序开发快速入门——一、用Gin框架实现简单的信息获取

    下面主要介绍如何用Go语言的Gin框架把信息通过接口传到小程序前端 1.准备 (1)注册一个微信小程序 (2)下载微信开发者工具 (3)下载Go语言编辑器,配置Go语言环境 2.新建一个小程序项目 在 ...

  4. 【翻译】WPF应用程序模块化开发快速入门(使用Prism+MEF)【中】

    索引 [翻译]WPF应用程序模块化开发快速入门(使用Prism框架)[上] 编译并运行快速入门 需要在VisualStudio 2010上运行此快速入门示例 代码下载:ModularityWithMe ...

  5. Apple Watch开发快速入门教程

     Apple Watch开发快速入门教程  试读下载地址:http://pan.baidu.com/s/1eQ8JdR0 介绍:苹果为Watch提供全新的开发框架WatchKit.本教程是国内第一本A ...

  6. 海思HI35xx平台软件开发快速入门之H264解码实例

    前言 H264视频编码技术诞生于2003年,至今已有十余载,技术相当成熟,它的优势在于有高的视频的压缩率,利用帧间和帧内预测(Estimation).变换(Transform)和反变换.量化(Quan ...

  7. 《iOS9开发快速入门》——导读

    本节书摘来自异步社区<iOS9开发快速入门>一书中的目录,作者 刘丽霞 , 邱晓华,更多章节内容可以访问云栖社区"异步社区"公众号查看 目 录 前 言 第1章 iOS ...

  8. ​HealthKit开发快速入门教程之HealthKit数据的操作

    ​HealthKit开发快速入门教程之HealthKit数据的操作 数据的表示 在HealthKit中,数据是最核心的元素.通过分析数据,人们可以看到相关的健康信息.例如,通过统计步数数据,人们可以知 ...

  9. HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID

    HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID HealthKit开发准备工作 在开发一款HealthKit应用程序时,首先需要讲解HealthKit中有哪些类,在i ...

最新文章

  1. 2021年大数据HBase(十七):❤️HBase的360度全面调优❤️
  2. 好记性不如烂笔头:会议纪要本
  3. 全球及中国水深测量声呐行业应用前景及未来投资决策建议报告2022-2027年
  4. 【数据科学】鱼水说竞赛:如何做好「特征工程」?
  5. xCode 安装Mobile Device Framework出错的问题的解决方法
  6. Qt实现窗口跳转(类似于看图软件中下一张和上一张)
  7. 小白从零开发鸿蒙小游戏(1)“数字华容道”—【深鸿会学习小组学习心得及笔记】
  8. HBase 从下载到安装和运行
  9. 高强度聚焦超声系统市场现状及未来发展趋势
  10. centos安装net-speeder
  11. android原生组件,RN原生的安卓UI组件
  12. ipencil 无法与iPad配对
  13. c语言用分支结构判断最大字符,第3章C语言 分支结构PPT课件.ppt
  14. 计算机视觉知识点之RCNN/Fast RCNN/Faster RCNN
  15. Grammer -- 疑问句
  16. Linux内核移植和根文件系统制作(详细步骤精讲)
  17. 小程序学习从入门到熟练教程
  18. java 线程的构造函数_[c++11]多线程编程(二)——理解线程类的构造函数
  19. vueadmin-template应用1:安装入门
  20. java-net-php-python-jsp校园美食点评系统计算机毕业设计程序

热门文章

  1. 当技术人成长为 CEO,应该修改哪些“Bug”?
  2. 大学英语计算机统考机考,大学英语四级考试机考
  3. 如何高效的扩展定时/计数器?
  4. 支付宝如何生成及配置公钥证书
  5. 一篇项目走进生存分析(Survival Analysis)的世界【Python版
  6. matlab中将数据保存为txt文件_matlab中将数据输出保存为txt格式文件的方法
  7. 2022-2028年中国稀土永磁电机行业市场发展潜力及投资风险预测报告
  8. 斜拉桥主梁的预应力混凝土横梁计算midas
  9. 代码库_自协商SGMII
  10. 第三方库下载教程(三种方法)