//=============================================================================
//StreamSound:
//C语言写的借助DirectSound8进行流模式无缝播放的例子。
//只能在Windows上运行。控制台程序。
//用法:
//StreamSound 声音文件
//其中声音文件需要经过特殊处理(转换格式为标准CD音质的WAV,然后去掉WAV文件头)
//也可以直接播放。
//为了便于代码复用,我以面向对象的思想来编写,并且把写波形到缓冲区的函数独立出
//来以便于用户自定义。
//作者:0xAA55
//版权所有(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;
//播放位置事件
PFNWRITEBUFFERCALLBACK pfnWriteBufferCallBack;
//写波形到缓冲区的回调函数
}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[i ]=(
short
)
rand
()-(
short
)
rand
();
if
(pBuf2)
{
pBuf=(
short
*)pBuf2;
uSamples=dwBuf2Size/(SS_BitsPerSpl/8),i;
for
(i=0;i<uSamples;i++)
pBuf[i ]=(
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,
//[输入]波形的格式
//取得所需的参数。
PFNWRITEBUFFERCALLBACK pfnWriteBufferCallBack
)
{
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[1],
"rb"
);
if
(!g_fp)
{
fprintf
(stderr,
"Could not read %s\n"
,argv[1]);
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;
}