0xAA55 发表于 2014-6-30 19:06:07

【C】借助DirectSound进行流的形式无缝播放的例子

DirectSound是DirectX的一个组件。用于播放声音、BGM等。和DirectMusic不一样,DirectSound用于播放波形声音(WAV无损等),而不是midi音乐。
通常大家使用DirectSound是直接把一个声波数据全部载入到一个IDirectSoundBuffer或IDirectSoundBuffer8里面,然后直接调用IDirectSoundBuffer::Play或IDirectSoundBuffer8::Play进行播放。但是这样的话,要实现无缝播放和大文件播放就比较蛋疼。无缝播放的话就要自己计时,万一自己的表比声卡的表走着快了点或者慢了点,你就会听到奇怪的啪啪声。而大文件播放(比如一张CD专辑)也这样播放的话,这意味着你要把几十甚至几百兆的无损音乐全部载入到内存然后播放。简直是把用户当成内存超大的土豪了。
此外就是用这个你无法很好地实现类似网络电话这样的功能。(难道你recv一个8 KB的波形,你就播放它?还是把一堆8 KB的波形接起来凑够了再播放?那用户一定不爽,自己说话,对方听到的是断断续续的声音。)于是我们就需要用到流的形式来播放声音了。

原理就是建立一个IDirectSoundBuffer8缓冲区,然后让它循环播放,然后我们就不断往这个缓冲区里面写入波形,从而实现连续的流播放。这样的好处是在播放大文件的时候你不必把整个大文件全部读入内存。你只需要读取能填满整个缓冲区的声波就行了。坏处是,如果读文件的函数太慢(比如旧式的机械硬盘、记忆棒有问题的PSP等),你会听到奇怪的声音。

然后用这个就可以实现网络电话的功能。你只需要把自己recv得到的波形填入缓冲区就可以流畅播放了。当然网络很卡的话你还是会听到奇怪的声音。

代码在此。我用的是Microsoft DirectX SDK (March 2008)的头文件和lib。Src和Bin可以下载。Src有配置好的VC6工程。你可以把它升级为VS2012的工程等。//=============================================================================
//StreamSound:
//C语言写的借助DirectSound8进行流模式无缝播放的例子。
//只能在Windows上运行。控制台程序。
//用法:
//StreamSound 声音文件
//其中声音文件需要经过特殊处理(转换格式为标准CD音质的WAV,然后去掉WAV文件头)
//也可以直接播放。
//为了便于代码复用,我以面向对象的思想来编写,并且把写波形到缓冲区的函数独立出
//来以便于用户自定义。
//作者:0xAA55
//论坛:http://www.0xaa55.com/
//版权所有(C) 2013-2014 技术宅的结界
//请保留原作者信息,否则视为侵权。
//-----------------------------------------------------------------------------
#define DIRECTSOUND_VERSION 0x0800

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<dsound.h>

#define V(action)         if(FAILED(hr=(action))){SSCleanup(p);fprintf(stderr,#action"==0x%08X\n",hr);return hr;}
#define VN(action)          if(!(action)){SSCleanup(p);fputs(#action" failed\n",stderr);return E_FAIL;}

#define SS_Channels         2       //声道数
#define SS_SampleRate       44100   //采样率
#define SS_BytesPerSecond   (SS_SampleRate*SS_BytesPerSamples)
#define SS_BytesPerSamples(SS_Channels*SS_BitsPerSpl/8)//这是每个样本的字节数
#define SS_BitsPerSpl       16      //单个样本的位数

#define SS_BufDuration      100   //缓冲区时长(毫秒)
#define SS_BufSamples       (SS_SampleRate*SS_BufDuration/1000)
#define SS_BufSize          (SS_BytesPerSecond*SS_BufDuration/1000)

typedef void(*PFNWRITEBUFFERCALLBACK)(void*pBuffer,UINT uBufferSize);//写波形到缓冲区的函数原型

UINT    g_uFileSize=0;      //要播放的文件大小
UINT    g_uFilePointer=0;   //文件指针
FILE    *g_fp=NULL;         //文件流

int   g_Quit=0;         //是否Ctrl+C

//=============================================================================
//WriteBuffer:
//写波形到缓冲区
//-----------------------------------------------------------------------------
void WriteBuffer(void*pBuffer,UINT uBufferSize)
{
    UINT uBytesLast=g_uFileSize-g_uFilePointer;//从文件指针到文件尾之间的字节数
    UINT uBytesToFill=uBufferSize;//要填充的字节数
    while(uBytesToFill>0)
    {
      printf("Offset:0x%08X\tSize:0x%08X\n",g_uFilePointer,uBytesToFill);
      if(uBytesLast>=uBytesToFill)//文件指针之后的数据足够填写剩下要填充的字节数
      {
            fseek(g_fp,(long)g_uFilePointer,SEEK_SET);
            fread(pBuffer,1,uBytesToFill,g_fp);
            g_uFilePointer+=uBytesToFill;
            return;
      }
      else//不足,则返回到头部继续读取
      {
            fseek(g_fp,(long)g_uFilePointer,SEEK_SET);
            fread(pBuffer,1,uBytesLast,g_fp);
            uBytesToFill-=uBytesLast;
            (BYTE*)pBuffer+=uBytesLast;
            g_uFilePointer=0;
            uBytesLast=g_uFileSize;
      }
    }
}

//=============================================================================
//StreamSound:
//借助DirectSound以流的形式播放声音的对象。
//-----------------------------------------------------------------------------
typedef struct
{
    DSCAPS                  Caps;                   //能力表
    LPDIRECTSOUND8          pDS8;                   //中介
    LPDIRECTSOUNDBUFFER   pDSB;                   //声音缓冲区
    LPDIRECTSOUNDNOTIFY   pDSN;                   //事件产生器(提醒载入WAV)
    LPDIRECTSOUNDBUFFER8    pDSB8;                  //声音缓冲区(版本8)
    DSBPOSITIONNOTIFY       DSBPositionNotify;      //播放位置事件
    PFNWRITEBUFFERCALLBACKpfnWriteBufferCallBack; //写波形到缓冲区的回调函数
}StreamSound;

//=============================================================================
//SSCleanup:
//使用完毕后清理内存
//-----------------------------------------------------------------------------
void SSCleanup
(
    StreamSound   *p            //要清理的结构体
)
{
    CloseHandle(p->DSBPositionNotify.hEventNotify);
    if(p->pDSB8)
    {
      IDirectSoundBuffer8_Stop(p->pDSB8);
      IDirectSoundBuffer8_Release(p->pDSB8);
      p->pDSB8=NULL;
    }
    if(p->pDSN)
    {
      IDirectSoundNotify_Release(p->pDSN);
      p->pDSN=NULL;
    }
    if(p->pDSB)
    {
      IDirectSoundBuffer_Release(p->pDSB);
      p->pDSB=NULL;
    }
    if(p->pDS8)
    {
      IDirectSound8_Release(p->pDS8);
      p->pDS8=NULL;
    }
}

//=============================================================================
//SSFillBuffer:
//填充声音数据到缓冲区
//-----------------------------------------------------------------------------
HRESULT SSFillBuffer
(
    StreamSound   *p            //要填充的结构体
)
{
    HRESULT hr;
    LPVOID//两个缓冲区指针
      pBuf1=NULL,
      pBuf2=NULL;
    DWORD//两个缓冲区大小
      dwBuf1Size=0,
      dwBuf2Size=0;
    V(IDirectSoundBuffer8_Lock(p->pDSB8,0,0,&pBuf1,&dwBuf1Size,&pBuf2,&dwBuf2Size,DSBLOCK_FROMWRITECURSOR|DSBLOCK_ENTIREBUFFER));
    if(p->pfnWriteBufferCallBack)//如果用户给出了回调函数,则使用回调函数取得声波数据来填写缓冲区
    {
      p->pfnWriteBufferCallBack(pBuf1,dwBuf1Size);
      if(pBuf2)
            p->pfnWriteBufferCallBack(pBuf2,dwBuf2Size);
    }
    else//用户没有给出回调函数,填充白噪音。
    {
      short*pBuf=(short*)pBuf1;
      UINT uSamples=dwBuf1Size/(SS_BitsPerSpl/8),i;
      for(i=0;i<uSamples;i++)
            pBuf=(short)rand()-(short)rand();

      if(pBuf2)
      {
            pBuf=(short*)pBuf2;
            uSamples=dwBuf2Size/(SS_BitsPerSpl/8),i;
            for(i=0;i<uSamples;i++)
                pBuf=(short)rand()-(short)rand();
      }
    }
    V(IDirectSoundBuffer8_Unlock(p->pDSB8,pBuf1,dwBuf1Size,pBuf2,dwBuf2Size));
    return hr;
}

//=============================================================================
//SSInit:
//初始化StreamSound
//-----------------------------------------------------------------------------
HRESULT SSInit
(
    StreamSound             *p,                     //[输出]初始化得到的结构体
    HWND                  hWnd,                   //[输入]得到焦点的窗口
    DWORD                   dwBufferSize,         //[输入]缓冲区大小
    PCMWAVEFORMAT         *pFormat,               //[输入]波形的格式
                                                    //取得所需的参数。
    PFNWRITEBUFFERCALLBACKpfnWriteBufferCallBack
)
{
    HRESULT hr;
    DSBUFFERDESC DSBufferDesc;//缓冲区描述符
    WAVEFORMATEX WaveFormatEx;//WAV格式

    p->Caps.dwSize=sizeof(p->Caps);
    p->pfnWriteBufferCallBack=pfnWriteBufferCallBack;//填充波形数据用到的回调函数

    V(DirectSoundCreate8(&DSDEVID_DefaultPlayback,&(p->pDS8),NULL));
    V(IDirectSound8_SetCooperativeLevel(p->pDS8,hWnd,DSSCL_PRIORITY));//设置协作模式
    V(IDirectSound8_GetCaps(p->pDS8,&(p->Caps)));//取得硬件能力表

    DSBufferDesc.dwSize=sizeof(DSBufferDesc);
    DSBufferDesc.dwFlags=DSBCAPS_CTRLVOLUME|DSBCAPS_CTRLPOSITIONNOTIFY|DSBCAPS_GLOBALFOCUS;//全局播放
    DSBufferDesc.dwBufferBytes=dwBufferSize;//缓冲区大小
    DSBufferDesc.dwReserved=0;
    DSBufferDesc.lpwfxFormat=&WaveFormatEx;//波形格式

    WaveFormatEx.wFormatTag=pFormat->wf.wFormatTag;
    WaveFormatEx.nChannels=pFormat->wf.nChannels;
    WaveFormatEx.nSamplesPerSec=pFormat->wf.nSamplesPerSec;
    WaveFormatEx.nAvgBytesPerSec=pFormat->wf.nAvgBytesPerSec;
    WaveFormatEx.nBlockAlign=pFormat->wf.nBlockAlign;
    WaveFormatEx.wBitsPerSample=pFormat->wBitsPerSample;
    WaveFormatEx.cbSize=sizeof(WaveFormatEx);

    V(IDirectSound8_CreateSoundBuffer(p->pDS8,&DSBufferDesc,&(p->pDSB),NULL));//建立缓冲区
    V(IDirectSoundBuffer_QueryInterface(p->pDSB,&IID_IDirectSoundNotify,&(p->pDSN)));//建立提醒
    V(IDirectSoundBuffer_QueryInterface(p->pDSB,&IID_IDirectSoundBuffer8,&(p->pDSB8)));//取得版本8的缓冲区
    p->pDSB->lpVtbl->Release(p->pDSB);
    p->pDSB=NULL;

    p->DSBPositionNotify.dwOffset=0;//每次播放指针到缓冲区开头的时候填充数据
    VN(p->DSBPositionNotify.hEventNotify=CreateEvent(NULL,FALSE,FALSE,NULL));
    V(IDirectSoundNotify_SetNotificationPositions(p->pDSN,1,&(p->DSBPositionNotify)));
    V(SSFillBuffer(p));
    return hr;
}

//=============================================================================
//SSPlay:
//播放
//-----------------------------------------------------------------------------
HRESULT SSPlay
(
    StreamSound   *p
)
{
    HRESULT hr;
    V(IDirectSoundBuffer8_Play(p->pDSB8,0,0,DSBPLAY_LOOPING));//循环播放
    return hr;
}

//=============================================================================
//SSUpdate:
//检查是否需要填充新的数据,然后填充数据
//-----------------------------------------------------------------------------
HRESULT SSUpdate
(
    StreamSound   *p
)
{
    HRESULT hr;
    if(WaitForSingleObject(p->DSBPositionNotify.hEventNotify,0)!=WAIT_TIMEOUT)//检测状态(是否需要填充新的数据到缓冲区)
      V(SSFillBuffer(p));
    return hr;
}


void Signal(int sig)
{
    switch(sig)
    {
    case SIGINT:
      g_Quit=1;
      fputs("Ctrl+C\n",stderr);
      break;
    }
}

int main(int argc,char**argv)
{
    HRESULT         hr;
    StreamSound   SS={0};
    PCMWAVEFORMAT   WAVEfmt;

    //先找到窗口作为焦点窗口
    HWND hWnd=FindWindow(TEXT("ConsoleWindowClass"),NULL);
    if(!hWnd)
    {
      fputs("Could not get the console window handle.\n",stderr);
      return 2;
    }

    if(argc<2)
    {
      fputs(
            "Usage: StreamSound <FILE>\n"
            "The file should be 44100 Hz, 16 bit stereo PCM file. \n"
            "You can remove the header of a WAV file to get the PCM file.\n",stderr);
      return 1;
    }

    signal(SIGINT,Signal);

    g_fp=fopen(argv,"rb");
    if(!g_fp)
    {
      fprintf(stderr,"Could not read %s\n",argv);
      return 2;
    }
    fseek(g_fp,0,SEEK_END);
    g_uFileSize=(UINT)ftell(g_fp);
    fseek(g_fp,0,SEEK_SET);

    WAVEfmt.wf.wFormatTag=WAVE_FORMAT_PCM;
    WAVEfmt.wf.nChannels=SS_Channels;
    WAVEfmt.wf.nSamplesPerSec=SS_SampleRate;
    WAVEfmt.wf.nAvgBytesPerSec=SS_BytesPerSecond;
    WAVEfmt.wf.nBlockAlign=SS_BytesPerSamples;
    WAVEfmt.wBitsPerSample=SS_BitsPerSpl;

    //初始化播放器
    if(FAILED(hr=SSInit(&SS,hWnd,SS_BytesPerSecond*SS_BufDuration/1000,&WAVEfmt,WriteBuffer)))
    {
      SSCleanup(&SS);
      fclose(g_fp);
      return 2;
    }

    if(FAILED(hr=SSPlay(&SS)))//先开始循环播放
    {
      SSCleanup(&SS);
      fclose(g_fp);
      return 2;
    }

    while(!g_Quit)
    {
      hr=SSUpdate(&SS);//不断检查缓冲区是否需要填充新的数据。
    }

    SSCleanup(&SS);
    fclose(g_fp);
    return 0;
}BIN:
SRC:
话说用DirectSound录音也很简单,用Capture就行。也是差不多的原理。

0xAA55 发表于 2014-6-30 19:28:28

感觉用它来播放BMP貌似很喜感
页: [1]
查看完整版本: 【C】借助DirectSound进行流的形式无缝播放的例子