6个回答
展开全部
Microsoft DirectSound API是DirectX API的波形音频组件。DirectSound提供了低反应时间混合,硬件加速和直接访问音频设备。DirectSound在提供这些功能的同时保持与现有设备驱动程序的兼容。
DirectMusic是DirectX API的音乐组件。DirectMusic与DirectSound API不一样,DirectSound 用于数字音频采样的捕捉和回放,DirectMusic则与基于消息的音乐数据一起工作,将这些数据转换为硬件或软件合成器中的音频采样。默认的软件实现使用Microsoft软件合成器来创建音频采样,然后将采样数据以数据流的形式提交给DirectSound。根据可下载音频(DLS)标准用采样数据来合成乐器声音。
Microsoft DirectShow是一个用于Microsoft Windows平台上的流化媒体的架构。DirectShow提供了高质量多媒体流的捕捉与回放。DirectShow支持多种格式,包括高级流格式(ASF)、运动图像专家组(MPEG)、音频视频交叉存取(AVI)、MPEG音频层-3(MP3)和WAV文件。DirectShow支持用Windows 驱动程序模型(WDM)设备或更老的Video for Windows设备进行捕捉。DirectShow与其他DirectX技术集成在一起。DirectShow在可能的时候会自动检测与使用视频和音频加速硬件,但也支持没有加速硬件的系统。
15.2.1 DirectSound
这一节讲解怎样创建一个简单的用于播放任意尺寸的Wave文件的DirectSound应用程序。例子程序位于本书配套光盘的PlaySound目录下。
用于打开、读取和关闭Wave文件的函数在Wavread.cpp文件中,该文件是DirectX SDK中的DSShow3D工具应用程序的一个模块。为了实现本示例中体现出的技术,必须将Wavread.cpp和Wavread.h添加到项目中并链接到Winmm.lib。另外,还必须从同一个例子目录添加Debug.c和Debug.h或编辑Wavread.cpp 中的ASSERT宏调用使其调用标准的assert函数。
例子程序中的方法调用通过在Dsound.h中定义的宏来执行,这些宏对C和C++都是有效的。例子程序分为两个步骤。
1. 设置DirectSound
例子程序需要下列定义和全局变量:
#define NUMEVENTS 2
LPDIRECTSOUND lpds;
DSBUFFERDESC dsbdesc;
LPDIRECTSOUNDBUFFER lpdsb;
LPDIRECTSOUNDBUFFER lpdsbPrimary;
LPDIRECTSOUNDNOTIFY lpdsNotify;
WAVEFORMATEX *pwfx;
HMMIO hmmio;
MMCKINFO mmckinfoData, mmckinfoParent;
DSBPOSITIONNOTIFY rgdsbpn[NUMEVENTS];
HANDLE rghEvent[NUMEVENTS];
第一步是创建DirectSound对象,建立协同等级,以及设置主缓冲区格式。这些工作全部在InitDSound函数中执行,如下列代码所示:
BOOL InitDSound(HWND hwnd, GUID *pguid)
{
// 创建DirectSound
if FAILED(DirectSoundCreate(pguid, &lpds, NULL))
return FALSE;
// 设置协同等级
if FAILED(IDirectSound_SetCooperativeLevel(
lpds, hwnd, DSSCL_PRIORITY))
return FALSE;
InitDSound函数有两个参数:主窗口句柄和指向音频设备的GUID的指针。在大多数情况下会传递NULL用做第二个参数,以表示默认设备,但也可以通过枚举设备来得到一个GUID。
为了能够设置主缓冲区的格式,这里设置了优先协同等级。如果不改变默认格式,就以8位和22 kHz的格式输出,而不考虑输入格式。将主缓冲区的格式设置为更高的格式没有坏处,因为即使第二缓冲区采用了较低的格式,可通过DirectSound将采样自动转换。需要注意的是调用IDirectSoundBuffer::SetFormat方法失败也没有危险,因为硬件不支持更高的格式。DirectSound只是简单地设置最接近的可用格式。
设置主缓冲区的格式时,首先要在全局DSBUFFERDESC结构中描述主缓冲区的格式,然后将描述结果传递给IDirectSound::CreateSoundBuffer方法,如下列代码:
// 获得主缓冲区
ZeroMemory(&dsbdesc, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
if FAILED(lpds->CreateSoundBuffer(&dsbdesc, &lpdsbPrimary, NULL))
return FALSE;
一旦拥有了主缓冲区对象,就应描述所需的Wave格式并将描述传递给IdirectSound Buffer ::SetFormat方法,代码如下:
// 设置主缓冲区格式
WAVEFORMATEX wfx;
memset(&wfx, 0, sizeof(WAVEFORMATEX));
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 2;
wfx.nSamplesPerSec = 44100;
wfx.wBitsPerSample = 16;
wfx.nBlockAlign = wfx.wBitsPerSample / 8 * wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
hr = lpdsbPrimary->SetFormat(&wfx);
return TRUE;
} // InitDSound()
2. 打开Wave文件
例子程序是一个不要求声音数据以一种特殊的格式存在的通用Wave文件读取程序。由于不知道声音数据的格式,所以第二声音缓冲区的创建必须推迟到知道数据格式之后。
打开文件和创建第二缓冲区的步骤在SetupStreamBuffer函数中分组。此函数首先执行从最后一次播放声音以来所需的清除工作:
BOOL SetupStreamBuffer(LPSTR lpzFileName)
{
// 关闭任何已打开的文件并释放接口
Close(); // Wavread.cpp中的函数
if (lpdsNotify != NULL)
{
lpdsNotify->Release();
lpdsNotify = NULL;
}
if (lpdsb != NULL)
{
lpdsb->Release();
lpdsb = NULL;
}
然后需要打开一个Wave文件,获得格式并将文件指针移动到声音数据的开始处。通过使用Wavread.cpp中的两个函数来完成这一任务,如:
if (WaveOpenFile(lpzFileName, &hmmio, &pwfx,
&mmckinfoParent) != 0)
return FALSE;
if (WaveStartDataRead(&hmmio, &mmckinfoData,
&mmckinfoParent) != 0)
return FALSE;
WaveOpenFile函数初始化三个在例子程序开始处声明的全局变量:一个文件句柄、一个指向WAVEFORMATEX结构的指针和一个用于父块的MMCKINFO结构(也就是文件的整体信息)。
然后将文件句柄和块信息传递给WaveStartDataRead函数,该函数在mmckinfoData中返回数据块的信息。如果程序员正在创建一个足够大的用于提供所有数据字节的静态缓冲区,可能会对这个结构感兴趣。在这个例子中创建的是流化缓冲区,因此不需要知道数据块的尺寸。
3. 创建第二缓冲区
在SetupStreamBuffer函数中,现在应该用与Wave文件一样的格式来创建第二声音缓冲区。该过程类似于在第一步中创建主缓冲区时所使用的方法。首先在全局DSBUFFERDESC结构中描述缓冲区,然后将描述传递给IDirectSound::CreateSoundBuffer方法,如下列代码:
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags =
DSBCAPS_GETCURRENTPOSITION2 // 获得当前位置
| DSBCAPS_GLOBALFOCUS // 允许后台播放
| DSBCAPS_CTRLPOSITIONNOTIFY; // 用于建立通知
// 缓冲区的尺寸是任意的,但为了保持数据先于播放位置正常写入,其尺寸至少
// 应能存放两秒钟的流量(以字节为单位)
dsbdesc.dwBufferBytes = pwfx->nAvgBytesPerSec * 2;
dsbdesc.lpwfxFormat = pwfx;
if FAILED(IDirectSound_CreateSoundBuffer(
lpds, &dsbdesc, &lpdsb, NULL))
{
WaveCloseReadFile(&hmmio, &pwfx);
return FALSE;
}
4. 设置播放通知
既然已成功地创建了一个流缓冲区,请求在当前播放位置到达流中的某个点处得到通知,因此将能够知道什么时候去流化更多的数据。在例子程序中,这些位置在缓冲区的开始处和半途标记处设置。
首先要创建一个所需的事件编号并在rghEvent array中存储其句柄:
for (int i = 0; i < NUMEVENTS; i++)
{
rghEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
if (NULL == rghEvent[i]) return FALSE;
}
接下来初始化DSBPOSITIONNOTIFY结构数组,数组中的每一个元素都用一个句柄与缓冲区中的一个位置相关联:
rgdsbpn[0].dwOffset = 0;
rgdsbpn[0].hEventNotify = rghEvent[0];
rgdsbpn[1].dwOffset = (dsbdesc.dwBufferBytes/2);
rgdsbpn[1].hEventNotify = rghEvent[1];
然后从第二缓冲区获得IDirectSoundNotify接口并将DSBPOSITIONNOTIFY数组传递给SetNotificationPositions方法:
if FAILED(IDirectSoundBuffer_QueryInterface(lpdsb,
IID_IDirectSoundNotify, (VOID **)&lpdsNotify))
return FALSE;
if FAILED(IDirectSoundNotify_SetNotificationPositions(
lpdsNotify, NUMEVENTS, rgdsbpn))
{
IDirectSoundNotify_Release(lpdsNotify);
return FALSE;
}
到这里就完成了流化缓冲区的设置。因为已经打开了Wave文件并准备开始流化数据,所以可以动态设置缓冲区。由于需要继续运行直到播放完所有的声音,因此要设置DSBPLAY_LOOPING,如下列代码:
IDirectSoundBuffer_Play(lpdsb, 0, 0, DSBPLAY_LOOPING);
return TRUE;
} // 调用函数SetupStreamBuffer()结束
5. 处理播放通知
以WinMain函数消息循环中的信号事件的形式接收通知。下列代码片段说明为了像标准消息那样截取信号事件怎样重写消息循环:
BOOL Done = FALSE;
while (!Done)
{
DWORD dwEvt = MsgWaitForMultipleObjects(
NUMEVENTS, // 多少可能的事件
rghEvent, // 句柄的位置
FALSE, // 等待所有事件
INFINITE, // 等待多长时间
QS_ALLINPUT); // 每一个消息都是事件
// WAIT_OBJECT_0 == 0,但被当做指派给第一个事件的任意索引值,因此
// 从dwEvt中减去WAIT_OBJECT_0可得到从零开始的事件编号
dwEvt -= WAIT_OBJECT_0;
// 如果事件由缓冲区设置,则存在需要处理的输入数据
if (dwEvt < NUMEVENTS)
{
StreamToBuffer(dwEvt); // 将数据复制到输出流
}
// 如果是最后的事件,则是一条消息
else if (dwEvt == NUMEVENTS)
{
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
Done = TRUE;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
} // 结束消息处理
} // while (!Done)
6. 流化来自Wave文件的数据
在前面两步中建立并处理的通知的用途是在缓冲区的数据播放了一半以后发出警报。一旦当前播放位置通过了缓冲区开始处或终点,就向刚播放的缓冲区片段写入数据(在缓冲区的开始处,会立即收到一条通知并写入缓冲区的后面一半内)。 因为缓冲区保存了大小为可播放两秒钟的数据,数据写入比当前播放位置领先一秒,这样使得在播放位置抵达新数据之前有足够的时间来完成处理。
然后将文件句柄和块信息传递给WaveStartDataRead函数,该函数在mmckinfoData中返回数据块的信息。如果程序员正在创建一个足够大的用于提供所有数据字节的静态缓冲区,可能会对这个结构感兴趣。在这个例子中创建的是流化缓冲区,因此不需要知道数据块的尺寸。
3. 创建第二缓冲区
在SetupStreamBuffer函数中,现在应该用与Wave文件一样的格式来创建第二声音缓冲区。该过程类似于在第一步中创建主缓冲区时所使用的方法。首先在全局DSBUFFERDESC结构中描述缓冲区,然后将描述传递给IDirectSound::CreateSoundBuffer方法,如下列代码:
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags =
DSBCAPS_GETCURRENTPOSITION2 // 获得当前位置
| DSBCAPS_GLOBALFOCUS // 允许后台播放
| DSBCAPS_CTRLPOSITIONNOTIFY; // 用于建立通知
// 缓冲区的尺寸是任意的,但为了保持数据先于播放位置正常写入,其尺寸至少
// 应能存放两秒钟的流量(以字节为单位)
dsbdesc.dwBufferBytes = pwfx->nAvgBytesPerSec * 2;
dsbdesc.lpwfxFormat = pwfx;
if FAILED(IDirectSound_CreateSoundBuffer(
lpds, &dsbdesc, &lpdsb, NULL))
{
WaveCloseReadFile(&hmmio, &pwfx);
return FALSE;
}
4. 设置播放通知
既然已成功地创建了一个流缓冲区,请求在当前播放位置到达流中的某个点处得到通知,因此将能够知道什么时候去流化更多的数据。在例子程序中,这些位置在缓冲区的开始处和半途标记处设置。
首先要创建一个所需的事件编号并在rghEvent array中存储其句柄:
for (int i = 0; i < NUMEVENTS; i++)
{
rghEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
if (NULL == rghEvent[i]) return FALSE;
}
接下来初始化DSBPOSITIONNOTIFY结构数组,数组中的每一个元素都用一个句柄与缓冲区中的一个位置相关联:
rgdsbpn[0].dwOffset = 0;
rgdsbpn[0].hEventNotify = rghEvent[0];
rgdsbpn[1].dwOffset = (dsbdesc.dwBufferBytes/2);
rgdsbpn[1].hEventNotify = rghEvent[1];
然后从第二缓冲区获得IDirectSoundNotify接口并将DSBPOSITIONNOTIFY数组传递给SetNotificationPositions方法:
if FAILED(IDirectSoundBuffer_QueryInterface(lpdsb,
IID_IDirectSoundNotify, (VOID **)&lpdsNotify))
return FALSE;
if FAILED(IDirectSoundNotify_SetNotificationPositions(
lpdsNotify, NUMEVENTS, rgdsbpn))
{
IDirectSoundNotify_Release(lpdsNotify);
return FALSE;
}
到这里就完成了流化缓冲区的设置。因为已经打开了Wave文件并准备开始流化数据,所以可以动态设置缓冲区。由于需要继续运行直到播放完所有的声音,因此要设置DSBPLAY_LOOPING,如下列代码:
IDirectSoundBuffer_Play(lpdsb, 0, 0, DSBPLAY_LOOPING);
return TRUE;
} // 调用函数SetupStreamBuffer()结束
5. 处理播放通知
以WinMain函数消息循环中的信号事件的形式接收通知。下列代码片段说明为了像标准消息那样截取信号事件怎样重写消息循环:
BOOL Done = FALSE;
while (!Done)
{
DWORD dwEvt = MsgWaitForMultipleObjects(
NUMEVENTS, // 多少可能的事件
rghEvent, // 句柄的位置
FALSE, // 等待所有事件
INFINITE, // 等待多长时间
QS_ALLINPUT); // 每一个消息都是事件
// WAIT_OBJECT_0 == 0,但被当做指派给第一个事件的任意索引值,因此
// 从dwEvt中减去WAIT_OBJECT_0可得到从零开始的事件编号
dwEvt -= WAIT_OBJECT_0;
// 如果事件由缓冲区设置,则存在需要处理的输入数据
if (dwEvt < NUMEVENTS)
{
StreamToBuffer(dwEvt); // 将数据复制到输出流
}
// 如果是最后的事件,则是一条消息
else if (dwEvt == NUMEVENTS)
{
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
Done = TRUE;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
} // 结束消息处理
} // while (!Done)
6. 流化来自Wave文件的数据
在前面两步中建立并处理的通知的用途是在缓冲区的数据播放了一半以后发出警报。一旦当前播放位置通过了缓冲区开始处或终点,就向刚播放的缓冲区片段写入数据(在缓冲区的开始处,会立即收到一条通知并写入缓冲区的后面一半内)。 因为缓冲区保存了大小为可播放两秒钟的数据,数据写入比当前播放位置领先一秒,这样使得在播放位置抵达新数据之前有足够的时间来完成处理。
DirectMusic是DirectX API的音乐组件。DirectMusic与DirectSound API不一样,DirectSound 用于数字音频采样的捕捉和回放,DirectMusic则与基于消息的音乐数据一起工作,将这些数据转换为硬件或软件合成器中的音频采样。默认的软件实现使用Microsoft软件合成器来创建音频采样,然后将采样数据以数据流的形式提交给DirectSound。根据可下载音频(DLS)标准用采样数据来合成乐器声音。
Microsoft DirectShow是一个用于Microsoft Windows平台上的流化媒体的架构。DirectShow提供了高质量多媒体流的捕捉与回放。DirectShow支持多种格式,包括高级流格式(ASF)、运动图像专家组(MPEG)、音频视频交叉存取(AVI)、MPEG音频层-3(MP3)和WAV文件。DirectShow支持用Windows 驱动程序模型(WDM)设备或更老的Video for Windows设备进行捕捉。DirectShow与其他DirectX技术集成在一起。DirectShow在可能的时候会自动检测与使用视频和音频加速硬件,但也支持没有加速硬件的系统。
15.2.1 DirectSound
这一节讲解怎样创建一个简单的用于播放任意尺寸的Wave文件的DirectSound应用程序。例子程序位于本书配套光盘的PlaySound目录下。
用于打开、读取和关闭Wave文件的函数在Wavread.cpp文件中,该文件是DirectX SDK中的DSShow3D工具应用程序的一个模块。为了实现本示例中体现出的技术,必须将Wavread.cpp和Wavread.h添加到项目中并链接到Winmm.lib。另外,还必须从同一个例子目录添加Debug.c和Debug.h或编辑Wavread.cpp 中的ASSERT宏调用使其调用标准的assert函数。
例子程序中的方法调用通过在Dsound.h中定义的宏来执行,这些宏对C和C++都是有效的。例子程序分为两个步骤。
1. 设置DirectSound
例子程序需要下列定义和全局变量:
#define NUMEVENTS 2
LPDIRECTSOUND lpds;
DSBUFFERDESC dsbdesc;
LPDIRECTSOUNDBUFFER lpdsb;
LPDIRECTSOUNDBUFFER lpdsbPrimary;
LPDIRECTSOUNDNOTIFY lpdsNotify;
WAVEFORMATEX *pwfx;
HMMIO hmmio;
MMCKINFO mmckinfoData, mmckinfoParent;
DSBPOSITIONNOTIFY rgdsbpn[NUMEVENTS];
HANDLE rghEvent[NUMEVENTS];
第一步是创建DirectSound对象,建立协同等级,以及设置主缓冲区格式。这些工作全部在InitDSound函数中执行,如下列代码所示:
BOOL InitDSound(HWND hwnd, GUID *pguid)
{
// 创建DirectSound
if FAILED(DirectSoundCreate(pguid, &lpds, NULL))
return FALSE;
// 设置协同等级
if FAILED(IDirectSound_SetCooperativeLevel(
lpds, hwnd, DSSCL_PRIORITY))
return FALSE;
InitDSound函数有两个参数:主窗口句柄和指向音频设备的GUID的指针。在大多数情况下会传递NULL用做第二个参数,以表示默认设备,但也可以通过枚举设备来得到一个GUID。
为了能够设置主缓冲区的格式,这里设置了优先协同等级。如果不改变默认格式,就以8位和22 kHz的格式输出,而不考虑输入格式。将主缓冲区的格式设置为更高的格式没有坏处,因为即使第二缓冲区采用了较低的格式,可通过DirectSound将采样自动转换。需要注意的是调用IDirectSoundBuffer::SetFormat方法失败也没有危险,因为硬件不支持更高的格式。DirectSound只是简单地设置最接近的可用格式。
设置主缓冲区的格式时,首先要在全局DSBUFFERDESC结构中描述主缓冲区的格式,然后将描述结果传递给IDirectSound::CreateSoundBuffer方法,如下列代码:
// 获得主缓冲区
ZeroMemory(&dsbdesc, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
if FAILED(lpds->CreateSoundBuffer(&dsbdesc, &lpdsbPrimary, NULL))
return FALSE;
一旦拥有了主缓冲区对象,就应描述所需的Wave格式并将描述传递给IdirectSound Buffer ::SetFormat方法,代码如下:
// 设置主缓冲区格式
WAVEFORMATEX wfx;
memset(&wfx, 0, sizeof(WAVEFORMATEX));
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 2;
wfx.nSamplesPerSec = 44100;
wfx.wBitsPerSample = 16;
wfx.nBlockAlign = wfx.wBitsPerSample / 8 * wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
hr = lpdsbPrimary->SetFormat(&wfx);
return TRUE;
} // InitDSound()
2. 打开Wave文件
例子程序是一个不要求声音数据以一种特殊的格式存在的通用Wave文件读取程序。由于不知道声音数据的格式,所以第二声音缓冲区的创建必须推迟到知道数据格式之后。
打开文件和创建第二缓冲区的步骤在SetupStreamBuffer函数中分组。此函数首先执行从最后一次播放声音以来所需的清除工作:
BOOL SetupStreamBuffer(LPSTR lpzFileName)
{
// 关闭任何已打开的文件并释放接口
Close(); // Wavread.cpp中的函数
if (lpdsNotify != NULL)
{
lpdsNotify->Release();
lpdsNotify = NULL;
}
if (lpdsb != NULL)
{
lpdsb->Release();
lpdsb = NULL;
}
然后需要打开一个Wave文件,获得格式并将文件指针移动到声音数据的开始处。通过使用Wavread.cpp中的两个函数来完成这一任务,如:
if (WaveOpenFile(lpzFileName, &hmmio, &pwfx,
&mmckinfoParent) != 0)
return FALSE;
if (WaveStartDataRead(&hmmio, &mmckinfoData,
&mmckinfoParent) != 0)
return FALSE;
WaveOpenFile函数初始化三个在例子程序开始处声明的全局变量:一个文件句柄、一个指向WAVEFORMATEX结构的指针和一个用于父块的MMCKINFO结构(也就是文件的整体信息)。
然后将文件句柄和块信息传递给WaveStartDataRead函数,该函数在mmckinfoData中返回数据块的信息。如果程序员正在创建一个足够大的用于提供所有数据字节的静态缓冲区,可能会对这个结构感兴趣。在这个例子中创建的是流化缓冲区,因此不需要知道数据块的尺寸。
3. 创建第二缓冲区
在SetupStreamBuffer函数中,现在应该用与Wave文件一样的格式来创建第二声音缓冲区。该过程类似于在第一步中创建主缓冲区时所使用的方法。首先在全局DSBUFFERDESC结构中描述缓冲区,然后将描述传递给IDirectSound::CreateSoundBuffer方法,如下列代码:
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags =
DSBCAPS_GETCURRENTPOSITION2 // 获得当前位置
| DSBCAPS_GLOBALFOCUS // 允许后台播放
| DSBCAPS_CTRLPOSITIONNOTIFY; // 用于建立通知
// 缓冲区的尺寸是任意的,但为了保持数据先于播放位置正常写入,其尺寸至少
// 应能存放两秒钟的流量(以字节为单位)
dsbdesc.dwBufferBytes = pwfx->nAvgBytesPerSec * 2;
dsbdesc.lpwfxFormat = pwfx;
if FAILED(IDirectSound_CreateSoundBuffer(
lpds, &dsbdesc, &lpdsb, NULL))
{
WaveCloseReadFile(&hmmio, &pwfx);
return FALSE;
}
4. 设置播放通知
既然已成功地创建了一个流缓冲区,请求在当前播放位置到达流中的某个点处得到通知,因此将能够知道什么时候去流化更多的数据。在例子程序中,这些位置在缓冲区的开始处和半途标记处设置。
首先要创建一个所需的事件编号并在rghEvent array中存储其句柄:
for (int i = 0; i < NUMEVENTS; i++)
{
rghEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
if (NULL == rghEvent[i]) return FALSE;
}
接下来初始化DSBPOSITIONNOTIFY结构数组,数组中的每一个元素都用一个句柄与缓冲区中的一个位置相关联:
rgdsbpn[0].dwOffset = 0;
rgdsbpn[0].hEventNotify = rghEvent[0];
rgdsbpn[1].dwOffset = (dsbdesc.dwBufferBytes/2);
rgdsbpn[1].hEventNotify = rghEvent[1];
然后从第二缓冲区获得IDirectSoundNotify接口并将DSBPOSITIONNOTIFY数组传递给SetNotificationPositions方法:
if FAILED(IDirectSoundBuffer_QueryInterface(lpdsb,
IID_IDirectSoundNotify, (VOID **)&lpdsNotify))
return FALSE;
if FAILED(IDirectSoundNotify_SetNotificationPositions(
lpdsNotify, NUMEVENTS, rgdsbpn))
{
IDirectSoundNotify_Release(lpdsNotify);
return FALSE;
}
到这里就完成了流化缓冲区的设置。因为已经打开了Wave文件并准备开始流化数据,所以可以动态设置缓冲区。由于需要继续运行直到播放完所有的声音,因此要设置DSBPLAY_LOOPING,如下列代码:
IDirectSoundBuffer_Play(lpdsb, 0, 0, DSBPLAY_LOOPING);
return TRUE;
} // 调用函数SetupStreamBuffer()结束
5. 处理播放通知
以WinMain函数消息循环中的信号事件的形式接收通知。下列代码片段说明为了像标准消息那样截取信号事件怎样重写消息循环:
BOOL Done = FALSE;
while (!Done)
{
DWORD dwEvt = MsgWaitForMultipleObjects(
NUMEVENTS, // 多少可能的事件
rghEvent, // 句柄的位置
FALSE, // 等待所有事件
INFINITE, // 等待多长时间
QS_ALLINPUT); // 每一个消息都是事件
// WAIT_OBJECT_0 == 0,但被当做指派给第一个事件的任意索引值,因此
// 从dwEvt中减去WAIT_OBJECT_0可得到从零开始的事件编号
dwEvt -= WAIT_OBJECT_0;
// 如果事件由缓冲区设置,则存在需要处理的输入数据
if (dwEvt < NUMEVENTS)
{
StreamToBuffer(dwEvt); // 将数据复制到输出流
}
// 如果是最后的事件,则是一条消息
else if (dwEvt == NUMEVENTS)
{
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
Done = TRUE;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
} // 结束消息处理
} // while (!Done)
6. 流化来自Wave文件的数据
在前面两步中建立并处理的通知的用途是在缓冲区的数据播放了一半以后发出警报。一旦当前播放位置通过了缓冲区开始处或终点,就向刚播放的缓冲区片段写入数据(在缓冲区的开始处,会立即收到一条通知并写入缓冲区的后面一半内)。 因为缓冲区保存了大小为可播放两秒钟的数据,数据写入比当前播放位置领先一秒,这样使得在播放位置抵达新数据之前有足够的时间来完成处理。
然后将文件句柄和块信息传递给WaveStartDataRead函数,该函数在mmckinfoData中返回数据块的信息。如果程序员正在创建一个足够大的用于提供所有数据字节的静态缓冲区,可能会对这个结构感兴趣。在这个例子中创建的是流化缓冲区,因此不需要知道数据块的尺寸。
3. 创建第二缓冲区
在SetupStreamBuffer函数中,现在应该用与Wave文件一样的格式来创建第二声音缓冲区。该过程类似于在第一步中创建主缓冲区时所使用的方法。首先在全局DSBUFFERDESC结构中描述缓冲区,然后将描述传递给IDirectSound::CreateSoundBuffer方法,如下列代码:
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags =
DSBCAPS_GETCURRENTPOSITION2 // 获得当前位置
| DSBCAPS_GLOBALFOCUS // 允许后台播放
| DSBCAPS_CTRLPOSITIONNOTIFY; // 用于建立通知
// 缓冲区的尺寸是任意的,但为了保持数据先于播放位置正常写入,其尺寸至少
// 应能存放两秒钟的流量(以字节为单位)
dsbdesc.dwBufferBytes = pwfx->nAvgBytesPerSec * 2;
dsbdesc.lpwfxFormat = pwfx;
if FAILED(IDirectSound_CreateSoundBuffer(
lpds, &dsbdesc, &lpdsb, NULL))
{
WaveCloseReadFile(&hmmio, &pwfx);
return FALSE;
}
4. 设置播放通知
既然已成功地创建了一个流缓冲区,请求在当前播放位置到达流中的某个点处得到通知,因此将能够知道什么时候去流化更多的数据。在例子程序中,这些位置在缓冲区的开始处和半途标记处设置。
首先要创建一个所需的事件编号并在rghEvent array中存储其句柄:
for (int i = 0; i < NUMEVENTS; i++)
{
rghEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
if (NULL == rghEvent[i]) return FALSE;
}
接下来初始化DSBPOSITIONNOTIFY结构数组,数组中的每一个元素都用一个句柄与缓冲区中的一个位置相关联:
rgdsbpn[0].dwOffset = 0;
rgdsbpn[0].hEventNotify = rghEvent[0];
rgdsbpn[1].dwOffset = (dsbdesc.dwBufferBytes/2);
rgdsbpn[1].hEventNotify = rghEvent[1];
然后从第二缓冲区获得IDirectSoundNotify接口并将DSBPOSITIONNOTIFY数组传递给SetNotificationPositions方法:
if FAILED(IDirectSoundBuffer_QueryInterface(lpdsb,
IID_IDirectSoundNotify, (VOID **)&lpdsNotify))
return FALSE;
if FAILED(IDirectSoundNotify_SetNotificationPositions(
lpdsNotify, NUMEVENTS, rgdsbpn))
{
IDirectSoundNotify_Release(lpdsNotify);
return FALSE;
}
到这里就完成了流化缓冲区的设置。因为已经打开了Wave文件并准备开始流化数据,所以可以动态设置缓冲区。由于需要继续运行直到播放完所有的声音,因此要设置DSBPLAY_LOOPING,如下列代码:
IDirectSoundBuffer_Play(lpdsb, 0, 0, DSBPLAY_LOOPING);
return TRUE;
} // 调用函数SetupStreamBuffer()结束
5. 处理播放通知
以WinMain函数消息循环中的信号事件的形式接收通知。下列代码片段说明为了像标准消息那样截取信号事件怎样重写消息循环:
BOOL Done = FALSE;
while (!Done)
{
DWORD dwEvt = MsgWaitForMultipleObjects(
NUMEVENTS, // 多少可能的事件
rghEvent, // 句柄的位置
FALSE, // 等待所有事件
INFINITE, // 等待多长时间
QS_ALLINPUT); // 每一个消息都是事件
// WAIT_OBJECT_0 == 0,但被当做指派给第一个事件的任意索引值,因此
// 从dwEvt中减去WAIT_OBJECT_0可得到从零开始的事件编号
dwEvt -= WAIT_OBJECT_0;
// 如果事件由缓冲区设置,则存在需要处理的输入数据
if (dwEvt < NUMEVENTS)
{
StreamToBuffer(dwEvt); // 将数据复制到输出流
}
// 如果是最后的事件,则是一条消息
else if (dwEvt == NUMEVENTS)
{
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
Done = TRUE;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
} // 结束消息处理
} // while (!Done)
6. 流化来自Wave文件的数据
在前面两步中建立并处理的通知的用途是在缓冲区的数据播放了一半以后发出警报。一旦当前播放位置通过了缓冲区开始处或终点,就向刚播放的缓冲区片段写入数据(在缓冲区的开始处,会立即收到一条通知并写入缓冲区的后面一半内)。 因为缓冲区保存了大小为可播放两秒钟的数据,数据写入比当前播放位置领先一秒,这样使得在播放位置抵达新数据之前有足够的时间来完成处理。
上海风华峻极智能科技
2022-05-05 广告
2022-05-05 广告
上海风华峻极智能科技有限公司【远程会议视频系统】一体化解决方案,如同面对面开会一般,让远程会议更简单,多种会议场景布局,舞台设备厂家,高保真语音的传输,极速快捷远程会议体验,只为高效沟通,支持万人及多房间同时在线....
点击进入详情页
本回答由上海风华峻极智能科技提供
展开全部
非常简单使用API函数PlaySound即可。下面是我给你写的代码已经调试通过,你看看吧。
请自己找两个.wav文件,改名字为1.wav和2.wav放到C盘根目录下(Windows的Media目录下有很多.wav文件)
#include <stdio.h>
#include <Windows.h>
#pragma comment(lib, "winmm.lib")
void main()
{
int nChoose;
printf("Please Choose 1 or 2:\n");
scanf("%d", &nChoose);
if(nChoose==1)
{
PlaySound("C:\\1.wav", NULL, SND_FILENAME|SND_SYNC );
printf("OK!\n");
}
else if(nChoose==2)
{
PlaySound("C:\\2.wav", NULL, SND_FILENAME|SND_SYNC );
printf("OK!\n");
}
else
{
printf("Error!\n");
}
}
请自己找两个.wav文件,改名字为1.wav和2.wav放到C盘根目录下(Windows的Media目录下有很多.wav文件)
#include <stdio.h>
#include <Windows.h>
#pragma comment(lib, "winmm.lib")
void main()
{
int nChoose;
printf("Please Choose 1 or 2:\n");
scanf("%d", &nChoose);
if(nChoose==1)
{
PlaySound("C:\\1.wav", NULL, SND_FILENAME|SND_SYNC );
printf("OK!\n");
}
else if(nChoose==2)
{
PlaySound("C:\\2.wav", NULL, SND_FILENAME|SND_SYNC );
printf("OK!\n");
}
else
{
printf("Error!\n");
}
}
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
展开全部
sound(500); // 一直发出频率为500Hz的声音
delay(1); // 持续一秒
nosound(); // 停止发声
将光标停留在sound上,键入CTRL+F1,找到相应的库函数头文件,#include 之
好像是 stdlib.h 库
delay(1); // 持续一秒
nosound(); // 停止发声
将光标停留在sound上,键入CTRL+F1,找到相应的库函数头文件,#include 之
好像是 stdlib.h 库
参考资料: http://topic.csdn.net/t/20030224/08/1458190.html
本回答被提问者采纳
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
展开全部
楼上的我都看得晕了,好象说了要C语言吧?又不一定是要WAV或MP3的格式。
晚上你上线时我给你一个C语言写的音乐程序,不过是要先自己按一定规律编好谱,然后读取它以一定频率播放,如要WAV 的文件,用C写是很繁的,要用到硬件驱动。
晚上你上线时我给你一个C语言写的音乐程序,不过是要先自己按一定规律编好谱,然后读取它以一定频率播放,如要WAV 的文件,用C写是很繁的,要用到硬件驱动。
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
展开全部
必须用系统提供的api函数,才能完成!
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
推荐律师服务:
若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询