科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件利用Directsound编程实现实时混音

利用Directsound编程实现实时混音

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

将多个音频文件或多路音频数据同时输出到音频输出设备上,就可同时听到多个不同的声音,这就是混音。

作者:李强 来源:天极开发 2007年10月16日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
下面开始吧,让我们看看如何进行混音吧,假设我们的背景需要混音的素材是下面的三个wave文件,"test1 .wav" "test2.wav" "test3.wav"。首先定义一下我们需要的几个变量:

LPDIRECTSOUND8 g_pDS = NULL;
LPDIRECTSOUNDBUFFER g_pDsbuffer[3] = NULL;
CWaveFile* g_pWaveFile;//
WAVEFORMATEX g_wfxInput; //输入的音频格式

  这里简单介绍一下CWaveFile类,Directsound里封装了一个CWaveFile类用来操作wav文件,可以通过open来写入文件的头信息,write来写入文件的数据,Getsize函数获取文件的长度,close关闭文件。你可以在DirectSound的路径下找到这个类的定义(SDK root)\samples\C++\Common\Src\Dsutil.cpp。

  首先初始化Directsound

BOOL InitDirectSound()
{
 if ( FAILED( hr = DirectSoundCreate(NULL, & g_pDS, NULL ) ) )
  return FALSE;

 // Set cooperative level.
 if ( FAILED( hr = g_pDS ->SetCooperativeLevel( hwnd, DSSCL_PRIORITY ) ) )
  return FALSE;

 return TRUE;
}

  在初始化Directsound的时候,创建设备对象最简单的方法就是通过DirectSoundCreate8函数,这个函数的第一个参数指定了和这个对象邦定的设备的GUID,你可以通过枚举设备来获取这个设备的GUID, 如果这个参数也可以为NULL,缺省的系统的声音输出设备就是DSDEVID_DefaultPlayback 。当你创建完设备对象后,一定要调用IDirectSound8::SetCooperativeLevel来设置协作度,否则,你不会听到声音的。DirectSound定义了三种水平,DSSCL_NORMAL, DSSCL_PRIORITY, and DSSCL_WRITEPRIMARY

  在Priority层次的协作度下,应用程序可以有优先权使用硬件资源,比如使用硬件进行混音,当然也可以设置主缓冲区的媒体格式,游戏程序应该采用这个层次的协作度,这个层次的协作度在允许应用程序控制采用频率和位深度的同时,也给应用程序很大的权力,这个层次的协作度允许其他应用程序的声音和游戏的音频同时被听到,不影响。

  下面的函数加载wave文件,然后将音频数据读取到缓冲区中,然后通过Directsound创建了的静态辅助缓冲区,将音频数据copy到Directsound的静态辅助缓冲区,然后就可以play了。

LPDIRECTSOUNDBUFFER LoadWaveFile(LPSTR lpzFileName)
{
 DSBUFFERDESC dsbdesc;
 HRESULT hr;
 BYTE *pBuffer;
 DWORD dwSizeRead;
 LPDIRECTSOUNDBUFFER lpdsbStatic=NULL;
 if( FAILED( hr = g_pWaveFile->Open( lpzFileName, &g_wfxInput, WAVEFILE_WRITE ) ) )
 {
  return NULL;
 }
 DWORD dwSize = g_pWaveFile->GetSize();
 pBuffer = new BYTE[dwSize];
 g_pWaveFile->Read(pBuffer,dwSize,&dwSizeRead);
 if(dwSizeRead > 0)
 {
  memset(dsbdesc,0,sizeof(DSBUFFERDESC));
  dsbdesc.dwSize = sizeof(DSBUFFERDESC);
  dsbdesc.dwFlags =DSBCAPS_STATIC;
  dsbdesc.dwBufferBytes =dwSizeRead;
  dsbdesc.lpwfxFormat = g_wfxInput;
  if ( FAILED( g_pDS->CreateSoundBuffer(&dsbdesc, & lpdsbStatic, NULL ) ) )
  {
   g_pWaveFile->Close();
   delete pBuffer;
   return NULL
  }

  LPVOID lpvWrite;
  DWORD dwLength;
  if (DS_OK == lpdsbStatic ->Lock(
    0, // Offset at which to start lock.
    0, // Size of lock; ignored because of flag.
    &lpvWrite, // Gets address of first part of lock.
    &dwLength, // Gets size of first part of lock.
    NULL, // Address of wraparound not needed.
    NULL, // Size of wraparound not needed.
    DSBLOCK_ENTIREBUFFER)) // Flag.
 {
  memcpy(lpvWrite, pBuffer, dwLength);
  lpdsbStatic ->Unlock(
   lpvWrite, // Address of lock start.
   dwLength, // Size of lock.
   NULL, // No wraparound portion.
   0); // No wraparound size.
 }
}
delete pBuffer;
return lpdsbStatic;
}

  这里我想简单的讲一下Directsound的辅助缓冲区,在Directsound中,辅助缓冲区分两类,一种是Static Buffer,这种buffer主要用于播放那些比较短的音频,可以将文件中的音频数据全部copy到Static buffer中,如果音频文件比较大,未了限制内存的开销,就要用到Streaming buffer,一般来说,Streaming buffer只能包含几秒钟的数据量,然后在播放的过程中不断的更新streaming buffer中的数据。静态缓冲区的创建和管理和流缓冲区很相似,唯一的区别就是它们使用的方式不一样,静态缓冲区只填充一次数据,然后就可以play,然而,流缓冲区是一边play,一边填充数据。

  上面创建的就是得静态的buffer,如果你要播放比较长的音频文件,你就要使用streaming buffer了。流缓冲区用来播放那些比较长的声音,因为数据比较长,没法一次填充到缓冲区中,一边播放,一边将新的数据填充到buffer中。

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

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

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

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

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

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

  4、IDirectSoundBuffer8::Unlock.,解锁

  IDirectSoundBuffer8::Lock可能返回两个地址的原因在于你锁定内存的数量是随机的,有时你锁定的区域正好包含buffer的起始点,这时,就会给你返回两个地址,举个例子吧
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章