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

本文介绍如何使用DirectSound8实现音频的流式无缝播放,通过循环播放缓冲区并持续写入波形数据,解决了大文件播放及无缝播放的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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的工程等。

[C]  纯文本查看  复制代码
?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
//=============================================================================
//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;
}
BIN:    BIN.7z (17.02 KB, 下载次数: 2, 售价: 1 个宅币) 
SRC:    SRC.7z (21.19 KB, 下载次数: 1, 售价: 10 个宅币) 
话说用DirectSound录音也很简单,用Capture就行。也是差不多的原理。

from:https://www.0xaa55.com/thread-693-1-1.html


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值