zoukankan      html  css  js  c++  java
  • DirectSound播放PCM(可播放实时采集的音频数据)

    前言

      该篇整理的原始来源为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()函数原型如下。

    1 HRESULT DirectSoundCreate8(
    2      LPCGUID lpcGuidDevice,
    3      LPDIRECTSOUND8 * ppDS8,
    4      LPUNKNOWN pUnkOuter
    5 )

    参数的含义如下:

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

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

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

    2) 设置协作级

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

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

    1 HRESULT SetCooperativeLevel(
    2  HWND hwnd,
    3  DWORD dwLevel
    4 )

    参数的含义如下:

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

     3) 创建一个主缓冲对象

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

    1 HRESULT CreateSoundBuffer(
    2  LPCDSBUFFERDESC pcDSBufferDesc,
    3  LPDIRECTSOUNDBUFFER * ppDSBuffer,
    4  LPUNKNOWN pUnkOuter
    5 )

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

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

    1 typedef struct _DSBUFFERDESC
    2 {
    3     DWORD           dwSize;
    4     DWORD           dwFlags;
    5     DWORD           dwBufferBytes;
    6     DWORD           dwReserved;
    7     LPWAVEFORMATEX  lpwfxFormat;
    8 } DSBUFFERDESC

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

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

     1     DSBUFFERDESC dsbd;
     2     memset(&dsbd,0,sizeof(dsbd));
     3     dsbd.dwSize=sizeof(dsbd);
     4     dsbd.dwFlags=DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
     5     dsbd.dwBufferBytes=MAX_AUDIO_BUF*BUFFERNOTIFYSIZE; 
     6     //WAVE Header
     7     dsbd.lpwfxFormat=(WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
     8     dsbd.lpwfxFormat->wFormatTag=WAVE_FORMAT_PCM;   
     9     /* format type */
    10     (dsbd.lpwfxFormat)->nChannels=channels;          
    11     /* number of channels (i.e. mono, stereo...) */
    12     (dsbd.lpwfxFormat)->nSamplesPerSec=sample_rate;     
    13     /* sample rate */
    14     (dsbd.lpwfxFormat)->nAvgBytesPerSec=sample_rate*(bits_per_sample/8)*channels; 
    15     /* for buffer estimation */
    16     (dsbd.lpwfxFormat)->nBlockAlign=(bits_per_sample/8)*channels;        
    17     /* block size of data */
    18     (dsbd.lpwfxFormat)->wBitsPerSample=bits_per_sample;     
    19     /* number of bits per sample of mono data */
    20     (dsbd.lpwfxFormat)->cbSize=0;
    21 
    22 
    23     //Creates a sound buffer object to manage audio samples. 
    24     HRESULT hr1;
    25     if( FAILED(m_pDS->CreateSoundBuffer(&dsbd,&m_pDSBuffer,NULL))){   
    26         return FALSE;
    27     }

    4) 创建一个副缓冲对象

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

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

    5) 创建通知对象

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

    1 IDirectSoundBuffer8 *m_pDSBuffer8=NULL;
    2 IDirectSoundNotify8 *m_pDSNotify=NULL;    
    3 4 if(FAILED(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*)&m_pDSNotify))){
    5     return FALSE ;
    6 }

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

    6) 设置通知位置

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

    1 HRESULT SetNotificationPositions(
    2          DWORD dwPositionNotifies,
    3          LPCDSBPOSITIONNOTIFY pcPositionNotifies
    4 )

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

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

    1 typedef struct DSBPOSITIONNOTIFY {
    2     DWORD dwOffset;
    3     HANDLE hEventNotify;
    4 } DSBPOSITIONNOTIFY;

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

    7) 开始播放

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

    1 HRESULT SetCurrentPosition(
    2          DWORD dwNewPosition
    3 )

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

    1 HRESULT Play(
    2          DWORD dwReserved1,
    3          DWORD dwPriority,
    4          DWORD dwFlags
    5 )

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

    2. 循环播放声音

    1) 数据填充至副缓冲区

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

      Lock()函数的原型如下。

    1 HRESULT Lock(
    2          DWORD dwOffset,
    3          DWORD dwBytes,
    4          LPVOID * ppvAudioPtr1,
    5          LPDWORD  pdwAudioBytes1,
    6          LPVOID * ppvAudioPtr2,
    7          LPDWORD pdwAudioBytes2,
    8          DWORD dwFlags
    9 )

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

      UnLock()函数的原型如下。

    1 HRESULT Unlock(
    2          LPVOID pvAudioPtr1,
    3          DWORD dwAudioBytes1,
    4          LPVOID pvAudioPtr2,
    5          DWORD dwAudioBytes2
    6 )

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

    2) 等待播放完成

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

    四、播放音频流程总结

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

    图2

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

    图3.结构体关系

    五、使用示例代码

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

      1 /**
      2  * 最简单的DirectSound播放音频的例子(DirectSound播放PCM)
      3  * Simplest Audio Play DirectSound (DirectSound play PCM) 
      4  *
      5  * 雷霄骅 Lei Xiaohua
      6  * leixiaohua1020@126.com
      7  * 中国传媒大学/数字电视技术
      8  * Communication University of China / Digital TV Technology
      9  * http://blog.csdn.net/leixiaohua1020
     10  *
     11  * 本程序使用DirectSound播放PCM音频采样数据。
     12  * 是最简单的DirectSound播放音频的教程。
     13  *
     14  * 函数调用步骤如下:
     15  *
     16  * [初始化]
     17  * DirectSoundCreate8():创建一个DirectSound对象。
     18  * SetCooperativeLevel():设置协作权限,不然没有声音。
     19  * IDirectSound8->CreateSoundBuffer():创建一个主缓冲区对象。
     20  * IDirectSoundBuffer->QueryInterface(IID_IDirectSoundBuffer8..):
     21  *            创建一个副缓冲区对象,用来存储要播放的声音数据文件。
     22  * IDirectSoundBuffer8->QueryInterface(IID_IDirectSoundNotify..):
     23  *            创建通知对象,通知应用程序指定播放位置已经达到。
     24  * IDirectSoundNotify8->SetNotificationPositions():设置通知位置。
     25  * IDirectSoundBuffer8->SetCurrentPosition():设置播放的起始点。
     26  * IDirectSoundBuffer8->Play():开始播放。
     27  *
     28  * [循环播放数据]
     29  * IDirectSoundBuffer8->Lock():锁定副缓冲区,准备写入数据。
     30  * fread():读取数据。
     31  * IDirectSoundBuffer8->Unlock():解锁副缓冲区。
     32  * WaitForMultipleObjects():等待“播放位置已经达到”的通知。
     33  *
     34  * This software plays PCM raw audio data using DirectSound.
     35  * It's the simplest tutorial about DirectSound.
     36  *
     37  * The process is shown as follows:
     38  *
     39  * [Init]
     40  * DirectSoundCreate8(): Init DirectSound object.
     41  * SetCooperativeLevel(): Must set, or we won't hear sound.
     42  * IDirectSound8->CreateSoundBuffer(): Create primary sound buffer.
     43  * IDirectSoundBuffer->QueryInterface(IID_IDirectSoundBuffer8..): 
     44  *            Create secondary sound buffer.
     45  * IDirectSoundBuffer8->QueryInterface(IID_IDirectSoundNotify..): 
     46  *            Create Notification object.
     47  * IDirectSoundNotify8->SetNotificationPositions():
     48  *            Set Notification Positions.
     49  * IDirectSoundBuffer8->SetCurrentPosition(): Set position to start.
     50  * IDirectSoundBuffer8->Play(): Begin to play.
     51  *
     52  * [Loop to play data]
     53  * IDirectSoundBuffer8->Lock(): Lock secondary buffer.
     54  * fread(): get PCM data.
     55  * IDirectSoundBuffer8->Unlock(): UnLock secondary buffer.
     56  * WaitForMultipleObjects(): Wait for Notifications.
     57  */
     58 #include <stdio.h>
     59 #include <stdlib.h>
     60 #include <windows.h>
     61 #include <dsound.h>
     62 
     63 
     64 #define MAX_AUDIO_BUF 4 
     65 #define BUFFERNOTIFYSIZE 192000 
     66 
     67 
     68 int sample_rate=8000;    //PCM sample rate
     69 int channels=1;            //PCM channel number
     70 int bits_per_sample=16;    //bits per sample
     71 
     72 BOOL main(int argc,char * argv[])
     73 {
     74     int i;
     75     FILE * fp;
     76     if((fp=fopen("../out.pcm","rb"))==NULL){
     77         printf("cannot open this file
    ");
     78         return -1;
     79     }
     80 
     81     IDirectSound8 *m_pDS=0;                    
     82     IDirectSoundBuffer8 *m_pDSBuffer8=NULL;    //used to manage sound buffers.
     83     IDirectSoundBuffer *m_pDSBuffer=NULL;    
     84     IDirectSoundNotify8 *m_pDSNotify=0;        
     85     DSBPOSITIONNOTIFY m_pDSPosNotify[MAX_AUDIO_BUF];
     86     HANDLE m_event[MAX_AUDIO_BUF];
     87 
     88     SetConsoleTitle(TEXT("Simplest Audio Play DirectSound"));//Console Title
     89     //Init DirectSound
     90     if(FAILED(DirectSoundCreate8(NULL,&m_pDS,NULL)))
     91         return FALSE;
     92     if(FAILED(m_pDS->SetCooperativeLevel(FindWindow(NULL,TEXT("Simplest Audio Play DirectSound")),DSSCL_NORMAL)))
     93         return FALSE;
     94 
     95 
     96     DSBUFFERDESC dsbd;
     97     memset(&dsbd,0,sizeof(dsbd));
     98     dsbd.dwSize=sizeof(dsbd);
     99     dsbd.dwFlags=DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
    100     dsbd.dwBufferBytes=MAX_AUDIO_BUF*BUFFERNOTIFYSIZE; 
    101     dsbd.lpwfxFormat=(WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
    102     dsbd.lpwfxFormat->wFormatTag=WAVE_FORMAT_PCM;   
    103     /* format type */
    104     (dsbd.lpwfxFormat)->nChannels=channels;          
    105     /* number of channels (i.e. mono, stereo...) */
    106     (dsbd.lpwfxFormat)->nSamplesPerSec=sample_rate;     
    107     /* sample rate */
    108     (dsbd.lpwfxFormat)->nAvgBytesPerSec=sample_rate*(bits_per_sample/8)*channels; 
    109     /* for buffer estimation */
    110     (dsbd.lpwfxFormat)->nBlockAlign=(bits_per_sample/8)*channels;        
    111     /* block size of data */
    112     (dsbd.lpwfxFormat)->wBitsPerSample=bits_per_sample;     
    113     /* number of bits per sample of mono data */
    114     (dsbd.lpwfxFormat)->cbSize=0;
    115 
    116     //Creates a sound buffer object to manage audio samples. 
    117     HRESULT hr1;
    118     if( FAILED(m_pDS->CreateSoundBuffer(&dsbd,&m_pDSBuffer,NULL))){   
    119         return FALSE;
    120     }
    121     if( FAILED(m_pDSBuffer->QueryInterface(IID_IDirectSoundBuffer8,(LPVOID*)&m_pDSBuffer8))){
    122         return FALSE ;
    123     }
    124     //Get IDirectSoundNotify8
    125     if(FAILED(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*)&m_pDSNotify))){
    126         return FALSE ;
    127     }
    128     for(i =0;i<MAX_AUDIO_BUF;i++){
    129         m_pDSPosNotify[i].dwOffset =i*BUFFERNOTIFYSIZE;
    130         m_event[i]=::CreateEvent(NULL,false,false,NULL); 
    131         m_pDSPosNotify[i].hEventNotify=m_event[i];
    132     }
    133     m_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF,m_pDSPosNotify);
    134     m_pDSNotify->Release();
    135 
    136     //Start Playing
    137     BOOL isPlaying =TRUE;
    138     LPVOID buf=NULL;
    139     DWORD  buf_len=0;
    140     DWORD res=WAIT_OBJECT_0;
    141     DWORD offset=BUFFERNOTIFYSIZE;
    142 
    143     m_pDSBuffer8->SetCurrentPosition(0);
    144     m_pDSBuffer8->Play(0,0,DSBPLAY_LOOPING);
    145     //Loop
    146     while(isPlaying){
    147         if((res >=WAIT_OBJECT_0)&&(res <=WAIT_OBJECT_0+3)){
    148             m_pDSBuffer8->Lock(offset,BUFFERNOTIFYSIZE,&buf,&buf_len,NULL,NULL,0);
    149 
    150             // 如果是实时音频播放,那么下面的数据就可以把内存中buf_len大小的数据复制到buf指向的地址即可
    151             if(fread(buf,1,buf_len,fp)!=buf_len){
    152                 //File End
    153                 //Loop:
    154                 fseek(fp, 0, SEEK_SET);
    155                 fread(buf,1,buf_len,fp);
    156                 //Close:
    157                 //isPlaying=0;
    158             }
    159 
    160             offset+=buf_len;
    161             offset %= (BUFFERNOTIFYSIZE * MAX_AUDIO_BUF);
    162             printf("this is %7d of buffer
    ",offset);
    163             m_pDSBuffer8->Unlock(buf,buf_len,NULL,0);
    164         }
    165         res = WaitForMultipleObjects (MAX_AUDIO_BUF, m_event, FALSE, INFINITE);
    166     }
    167 
    168     return 0;
    169 }

    结语

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

  • 相关阅读:
    Leetcode Binary Tree Level Order Traversal
    Leetcode Symmetric Tree
    Leetcode Same Tree
    Leetcode Unique Paths
    Leetcode Populating Next Right Pointers in Each Node
    Leetcode Maximum Depth of Binary Tree
    Leetcode Minimum Path Sum
    Leetcode Merge Two Sorted Lists
    Leetcode Climbing Stairs
    Leetcode Triangle
  • 原文地址:https://www.cnblogs.com/wangjzh/p/4243285.html
Copyright © 2011-2022 走看看