本篇是
用DirectX Audio和DirectShow播放声音和音乐(3)的续篇。
使用通告
“通告”是一种触发机制,当缓存中播放位置达到某个固定的位置时,就会向程序发出通知。有了通告,就可以知道播放什么时候结束,这种机制在比较长的声音中特别有效。通告使用一个叫做
IDirectSoundNotify8的对象,这个程序的作用就是在音频缓存中标记一个位置,然后触发事件通知应用程序,而应用程序可以通过消息循环或者单独的线程进行处理。
标记的位置可以是一个缓存中的偏移值,也可以是由宏指定的停止标记,这个表示停止的宏是
DSBPN_OFFSETSTOP。并不是任何偏移值都可以用来作为通告发生的位置,这个值必须和音频的数据块对齐,并且通告的偏移必须按照从小到大的顺序排列。偏移值是不能够共享的,如果使用
DSBPN_OFFSETSTOP宏,它必须被放在最后。举例来说,对于一个块大小为2的音频(单声道、16位),尝试对偏移为4和5的位置设通告会导致失败,因为偏移量位置4和5都在同一个数据块中。
如果要在缓存中使用通告,必须在创建缓存的时候使用
DSBCAPS_CTRLPOSITIONNOTIFY标志,并且如果在创建缓存的过程中使用了这个标志,就必须使用通告对象。如果希望获取
IDirectSoundNotify8对象,可以在IDirectSoundBuffer8对象中通过请求接口来获得。
IDirectSound8* g_ds; // directsound component
// create main sound buffer
if(FAILED(g_ds->CreateSoundBuffer(&ds_buffer_desc, &ds_buffer_primary, NULL)))
return NULL;
// get newer interface
if(FAILED(ds_buffer_primary->QueryInterface(IID_IDirectSoundBuffer8, (void**)&ds_buffer_second)))
{
ds_buffer_primary->Release();
return NULL;
}
如下图所示,通告可以放置在缓存中的任何位置:
通告对象只有一个成员函数SetNotificationPositions。
The SetNotificationPositions
method sets the notification positions. During capture or playback, whenever the
read or play cursor reaches one of the specified offsets, the associated event
is signaled.
HRESULT SetNotificationPositions(
DWORD dwPositionNotifies,
LPCDSBPOSITIONNOTIFY pcPositionNotifies
);
Parameters
- dwPositionNotifies
- Number of DSBPOSITIONNOTIFY
structures.
- pcPositionNotifies
- Pointer to an array of
DSBPOSITIONNOTIFY structures (the maximum array size is DSBNOTIFICATIONS_MAX).
Return Values
If the method succeeds, the
return value is DS_OK. If the method fails, the return value may be one of the
following error values:
Return code |
DSERR_INVALIDPARAM |
DSERR_OUTOFMEMORY |
Remarks
The value DSBPN_OFFSETSTOP can
be specified in the dwOffset member to tell DirectSound to signal the associated
event when the Stop or Stop method is called or when the end of the buffer has
been reached and the playback is not looping. If it is used, this should be the
last item in the position-notify array.
If a position-notify array has
already been set, the method replaces the previous array.
The buffer must be stopped when
this method is called.
pcPositionNotifies是一个DSBPOSITIONNOTIFY结构的数组。
The DSBPOSITIONNOTIFY structure
describes a notification position. It is used by
IDirectSoundNotify8::SetNotificationPositions.
typedef struct DSBPOSITIONNOTIFY {
DWORD dwOffset;
HANDLE hEventNotify;
} DSBPOSITIONNOTIFY;
Members
- dwOffset
- Offset from the beginning
of the buffer where the notify event is to be triggered, or
DSBPN_OFFSETSTOP.
- hEventNotify
- Handle to the event to be
signaled when the offset has been reached.
Remarks
The DSBPN_OFFSETSTOP value in
the dwOffset member causes the event to be signaled when playback or capture
stops, either because the end of the buffer has been reached (and playback or
capture is not looping) or because the application called the
IDirectSoundBuffer8::Stop or IDirectSoundCaptureBuffer8::Stop method.
>When a playback buffer was
created with DSBCAPS_LOCDEFER and DSBCAPS_CTRLPOSITIONNOTIFY along with any
voice management flag, it is possible that a sound that has notifications set,
but not yet reached, will be terminated by the voice manager. In this case, no
event is signaled.
在使用通告的时候,一般需要使用事件句柄,事件一般有两种状态存在,已发出信号和未发出信号,可以通过CreateEvent函数来创建事件。
The CreateEvent
function creates or opens a named or unnamed event object.
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
Parameters
- lpEventAttributes
- [in] Pointer to a
SECURITY_ATTRIBUTES structure that
determines whether the returned handle can be inherited by child
processes. If lpEventAttributes is NULL, the handle cannot be
inherited.
The
lpSecurityDescriptor member of the structure specifies a
security descriptor for the new event. If lpEventAttributes is
NULL, the event gets a default security descriptor. The ACLs in the
default security descriptor for an event come from the primary or
impersonation token of the creator.
- bManualReset
- [in] If this parameter
is TRUE, the function creates a manual-reset event object, which
requires the use of the ResetEvent function to set the
event state to nonsignaled. If this parameter is FALSE, the function
creates an auto-reset event object, and system automatically resets the
event state to nonsignaled after a single waiting thread has been
released.
- bInitialState
- [in] If this parameter
is TRUE, the initial state of the event object is signaled; otherwise,
it is nonsignaled.
- lpName
- [in] Pointer to a
null-terminated string specifying the name of the event object. The name
is limited to MAX_PATH characters. Name comparison is case sensitive.
If lpName
matches the name of an existing named event object, this function
requests the EVENT_ALL_ACCESS access right. In this case, the
bManualReset and bInitialState parameters are ignored
because they have already been set by the creating process. If the
lpEventAttributes parameter is not NULL, it determines whether the
handle can be inherited, but its security-descriptor member is ignored.
If lpName is
NULL, the event object is created without a name.
If lpName
matches the name of an existing semaphore, mutex, waitable timer, job,
or file-mapping object, the function fails and the
GetLastError function returns
ERROR_INVALID_HANDLE. This occurs because these objects share the same
name space.
Terminal
Services: The name can have a "Global\" or "Local\"
prefix to explicitly create the object in the global or session name
space. The remainder of the name can contain any character except
the backslash character (\). For more information, see
Kernel Object Namespaces.
Windows XP:
Fast user switching is implemented using Terminal Services sessions.
The first user to log on uses session 0, the next user to log on
uses session 1, and so on. Kernel object names must follow the
guidelines outlined for Terminal Services so that applications can
support multiple users.
Windows 2000:
If Terminal Services is not running, the "Global\" and "Local\"
prefixes are ignored. The remainder of the name can contain any
character except the backslash character.
Windows NT: The
name can contain any character except the backslash character.
Windows
Me/98/95: The name can contain any character except the
backslash character. The empty string ("") is a valid object name.
Return Values
If the function succeeds,
the return value is a handle to the event object. If the named event object
existed before the function call, the function returns a handle to the
existing object and GetLastError returns
ERROR_ALREADY_EXISTS.
If the function fails, the
return value is NULL. To get extended error information, call
GetLastError.
Remarks
The handle returned by
CreateEvent has the EVENT_ALL_ACCESS access right; it
can be used in any function that requires a handle to an event object,
provided that the caller has been granted access. If an event is created
from a service or a thread that is impersonating a different user, you can
either apply a security descriptor to the event when you create it, or
change the default security descriptor for the creating process by changing
its default DACL. For more information, see Synchronization Object Security
and Access Rights.
Any thread of the calling
process can specify the event-object handle in a call to one of the wait
functions. The single-object wait functions return when the state of the
specified object is signaled. The multiple-object wait functions can be
instructed to return either when any one or when all of the specified
objects are signaled. When a wait function returns, the waiting thread is
released to continue its execution.
The initial state of the
event object is specified by the bInitialState parameter. Use the
SetEvent function to set the state of an event object to
signaled. Use the ResetEvent function to reset the state of
an event object to nonsignaled.
When the state of a
manual-reset event object is signaled, it remains signaled until it is
explicitly reset to nonsignaled by the ResetEvent function.
Any number of waiting threads, or threads that subsequently begin wait
operations for the specified event object, can be released while the
object's state is signaled.
When the state of an
auto-reset event object is signaled, it remains signaled until a single
waiting thread is released; the system then automatically resets the state
to nonsignaled. If no threads are waiting, the event object's state remains
signaled.
Multiple processes can have
handles of the same event object, enabling use of the object for
interprocess synchronization. The following object-sharing mechanisms are
available:
- A child process created
by the CreateProcess function can inherit a handle to
an event object if the lpEventAttributes parameter of
CreateEvent enabled inheritance.
- A process can specify
the event-object handle in a call to the
DuplicateHandle function to create a
duplicate handle that can be used by another process.
- A process can specify
the name of an event object in a call to the OpenEvent
or CreateEvent function.
Use the
CloseHandle
function to close the handle. The system closes the handle automatically
when the process terminates. The event object is destroyed when its last
handle has been closed.
以下代码创建了4个事件,并且给pos_notify设置了4个事件触发的位置标记。
HANDLE g_events[4]; // notification handles
DSBPOSITIONNOTIFY pos_notify[4];
// create the event handles and set the notifications
for(long i = 0; i < 4; i++)
{
// create an unnamed event object
g_events[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
pos_notify[i].dwOffset = 16384 * (i+1) - 1;
pos_notify[i].hEventNotify = g_events[i];
}
// set the notification position
//
// During capture or playback, whenever the read or play cursor reached one of the specified offsets,
// the associated event is signaled.
g_ds_notify->SetNotificationPositions(4, pos_notify);
在缓存已经准备就绪之后,就可以开始播放了,播放时等待通告事件被触发,它是WaitForMultipleObjects函数的工作。
The
WaitForMultipleObjects function returns when any one or all of the
specified objects are in the signaled state or the time-out interval
elapses.
To enter an alertable wait
state, use the WaitForMultipleObjectsEx function.
DWORD WaitForMultipleObjects(
DWORD nCount,
const HANDLE* lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds
);
Parameters
- nCount
- [in] Number of object
handles in the array pointed to by lpHandles. The maximum
number of object handles is MAXIMUM_WAIT_OBJECTS.
- lpHandles
- [in] Pointer to an
array of object handles. For a list of the object types whose handles
can be specified, see the following Remarks section. The array can
contain handles to objects of different types. It may not contain the
multiple copies of the same handle.
If one of these handles
is closed while the wait is still pending, the function's behavior is
undefined.
The handles must have
the SYNCHRONIZE access right. For more information, see
Standard Access Rights.
Windows
Me/98/95: No handle may be a duplicate of another handle
created using
DuplicateHandle.
- bWaitAll
- [in] If this parameter
is TRUE, the function returns when the state of all objects in the
lpHandles array is signaled. If FALSE, the function returns when
the state of any one of the objects is set to signaled. In the latter
case, the return value indicates the object whose state caused the
function to return.
- dwMilliseconds
- [in] Time-out interval,
in milliseconds. The function returns if the interval elapses, even if
the conditions specified by the bWaitAll parameter are not met.
If dwMilliseconds is zero, the function tests the states of the
specified objects and returns immediately. If dwMilliseconds is
INFINITE, the function's time-out interval never elapses.
Return Values
If the function succeeds,
the return value indicates the event that caused the function to return. It
can be one of the following values. (Note that WAIT_OBJECT_0 is defined as 0
and WAIT_ABANDONED_0 is defined as 0x00000080L.)
Return
code/value |
Description |
WAIT_OBJECT_0 to (WAIT_OBJECT_0 + nCount– 1) |
If
bWaitAll is TRUE, the return value indicates that the state of
all specified objects is signaled.
If
bWaitAll is FALSE, the return value minus WAIT_OBJECT_0
indicates the lpHandles array index of the object that
satisfied the wait. If more than one object became signaled during
the call, this is the array index of the signaled object with the
smallest index value of all the signaled objects.
|
WAIT_ABANDONED_0 to (WAIT_ABANDONED_0 + nCount– 1) |
If
bWaitAll is TRUE, the return value indicates that the state of
all specified objects is signaled and at least one of the objects is
an abandoned mutex object.
If
bWaitAll is FALSE, the return value minus WAIT_ABANDONED_0
indicates the lpHandles array index of an abandoned mutex
object that satisfied the wait.
|
WAIT_TIMEOUT
0x00000102L |
The
time-out interval elapsed and the conditions specified by the
bWaitAll parameter are not satisfied. |
If the function fails, the
return value is WAIT_FAILED. To get extended error information, call
GetLastError.
WaitForMultipleObjects函数可以同时等待64个对象,请确定自己的对象数目小于这个最大值。在没有接收到任何事件的情况下,函数可能会返回WAIT_FAILED,遇到这种情况,只需要再调用一次这个函数,就能恢复正常。取得
WaitForMultipleObjects函数的返回值就能够获取事件号。获取到事件号后,用这个数字减去
WAIT_OBJECT_0,得到的结果就是触发的事件索引号,这个值的范围从0到事件总数减1。
以下代码示例了如何使用WaitForMultipleObjects,以及如何得到事件索引号。
HANDLE g_events[4]; // notification handles
// wait for a message
//
// return when any one or all of the specified objects are in the signaled state or the time-out
// interval elapses.
DWORD result = MsgWaitForMultipleObjects(4, g_events, FALSE, INFINITE, QS_ALLEVENTS);
// get notification #
DWORD thread_index = result - WAIT_OBJECT_0;
使用线程控制事件
创建线程可以通过调用CreateThread函数来实现。
The CreateThread
function creates a thread to execute within the virtual address space of the
calling process.
To create a thread that runs
in the virtual address space of another process, use the
CreateRemoteThread function.
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
Parameters
- lpThreadAttributes
- [in] Pointer to a
SECURITY_ATTRIBUTES structure that
determines whether the returned handle can be inherited by child
processes. If lpThreadAttributes is NULL, the handle cannot be
inherited.
The
lpSecurityDescriptor member of the structure specifies a
security descriptor for the new thread. If lpThreadAttributes
is NULL, the thread gets a default security descriptor. The ACLs in the
default security descriptor for a thread come from the primary token of
the creator.
Windows
XP/2000/NT: The ACLs in the default security descriptor
for a thread come from the primary or impersonation token of the
creator. This behavior changed with Windows XP SP2 and Windows
Server 2003.
- dwStackSize
- [in] Initial size of
the stack, in bytes. The system rounds this value to the nearest page.
If this parameter is zero, the new thread uses the default size for the
executable. For more information, see Thread Stack Size.
- lpStartAddress
- [in] Pointer to the
application-defined function to be executed by the thread and represents
the starting address of the thread. For more information on the thread
function, see ThreadProc.
- lpParameter
- [in] Pointer to a
variable to be passed to the thread.
- dwCreationFlags
- [in] Flags that control
the creation of the thread. If the CREATE_SUSPENDED flag is specified,
the thread is created in a suspended state, and will not run until the
ResumeThread function is called. If this value is zero,
the thread runs immediately after creation.
If the
STACK_SIZE_PARAM_IS_A_RESERVATION flag is specified, the dwStackSize
parameter specifies the initial reserve size of the stack. Otherwise,
dwStackSize specifies the commit size.
Windows 2000/NT
and Windows Me/98/95: The
STACK_SIZE_PARAM_IS_A_RESERVATION flag is not supported.
- lpThreadId
- [out] Pointer to a
variable that receives the thread identifier. If this parameter is NULL,
the thread identifier is not returned.
Windows
Me/98/95: This parameter may not be NULL.
Return Values
If the function succeeds,
the return value is a handle to the new thread.
If the function fails, the
return value is NULL. To get extended error information, call
GetLastError.
Note that
CreateThread may succeed even if lpStartAddress points
to data, code, or is not accessible. If the start address is invalid when
the thread runs, an exception occurs, and the thread terminates. Thread
termination due to a invalid start address is handled as an error exit for
the thread's process. This behavior is similar to the asynchronous nature of
CreateProcess, where the process is created even if it
refers to invalid or missing dynamic-link libraries (DLLs).
Windows Me/98/95: CreateThread
succeeds only when it is called in the context of a 32-bit program. A
32-bit DLL cannot create an additional thread when that DLL is being
called by a 16-bit program.
lpStartAddress是一个函数指针,该函数指针的定义如下:
The ThreadProc
function is an application-defined function that serves as the starting
address for a thread. Specify this address when calling the
CreateThread or CreateRemoteThread function. The
LPTHREAD_START_ROUTINE type defines a pointer to this
callback function. ThreadProc is a placeholder for
the application-defined function name.
DWORD WINAPI ThreadProc(
LPVOID lpParameter
);
Parameters
- lpParameter
- [in] Thread data passed
to the function using the lpParameter parameter of the
CreateThread or CreateRemoteThread function.
Return Values
The function should return a
value that indicates its success or failure.
Remarks
A process can determine when
a thread it created has completed by using one of the wait functions. It can
also obtain the return value of its ThreadProc by
calling the GetExitCodeThread function.
Each thread receives a
unique copy of the local variables of this function. Any static or global
variables are shared by all threads in the process. To provide unique data
to each thread using a global index, use thread local storage.
以下代码演示了如何使用函数CreateThread。
HANDLE g_thread_handle; // thread handle
DWORD g_thread_id; // thread id
// create a thread for notifications
g_thread_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) Handle_Notifications, NULL, 0, &g_thread_id);
......
DWORD Handle_Notifications(LPVOID thread_data)
{
// thread function to handle
notification event
......
}
音频流
音频流是一种很简单很容易理解的处理方式,就是循环播放一个缓冲区的音频数据,在播放的时候不断更新这个缓冲区的数据,使得播放能无缝播放。在播放音频缓冲区的时候设置一系列的标志位,然后需要一个指示器告诉应该在刚刚播放掉的部分填充一些新数据。在每次缓冲区播放完毕后,只需要从头开始继续播放,就能将音频连续地播放下去。
下面的这段音频缓冲区有4个标志位,当回放到某个标志位时就表示应该填充新数据到刚刚播放的部分中。
使用音频流的第一步是用数据填充整个缓冲区,然后开始播放声音,直到播放到达第一个标志位的时候,再读取一段新的数据,取代刚刚播放完成的数据。然后继续播放到达第二个标志位,再读取新数据取代刚刚播放完成的部分。持续这个过程,直到整段音频数据播放完成,最后一个播放到的标志触发一个停止播放的事件。如果需要循环播放音频,只需不触发停止事件,重新开始刚才的过程。在处理通告事件的线程中,使用加载数据的函数不断更新音频缓冲区的数据,保证缓冲区中拥有完成的音频信息。
下面是整个操作的全部过程:
1、创建音频缓冲区,65536字节大小。
2、设置4个通告位置,每个通告标志都在缓冲区的1/4处。
3、用数据填充整个缓冲区。
4、使用DSBPLAY_LOOP标志播放音频。
5、在每个通告被触发的时候,用新数据填充刚刚播放掉的部分,持续这个过程,直到有通告显示缓冲区中已经没有更多的数据可以放入缓冲区中,最后再播放剩余部分即可。
6、最后判断哪个事件表示已经到达了音频数据末端。
以下是代码示例:
IDirectSound8* g_ds; // directsound component
IDirectSoundBuffer8* g_ds_buffer; // sound buffer object
IDirectSoundNotify8* g_ds_notify; // notification object
HANDLE g_thread_handle; // thread handle
DWORD g_thread_id; // thread id
HANDLE g_events[4]; // notification handles
FILE* g_fp; // .WAV file handle
long g_ds_size; // directsound data buffer size
long g_ds_pos; // current directsound buffer position
long g_ds_leave; // leave directsound buffer data needed to be played
//--------------------------------------------------------------------------------
// Handle directsound nofification evenet, just stream load sound buffer.
//--------------------------------------------------------------------------------
DWORD Handle_Notifications(LPVOID thread_data)
{
while(1)
{
// wait for a message
//
// return when any one or all of the specified objects are in the signaled state or the time-out
// interval elapses.
DWORD result = MsgWaitForMultipleObjects(4, g_events, FALSE, INFINITE, QS_ALLEVENTS);
// get notification #
DWORD thread_index = result - WAIT_OBJECT_0;
// check for update #
if(thread_index >= 0 && thread_index < 4)
{
// stop sound and quit thread if no more
if(g_ds_leave == 0)
ExitThread(0);
// seek to read position in file
fseek(g_fp, g_ds_pos, SEEK_SET);
// stream in data based on amount of data left
if(g_ds_leave <= 16384)
{
// if this is reached, then end of sound is coming up.
Load_Sound_Data(g_ds_buffer, thread_index * 16384, g_ds_leave, g_fp);
g_ds_leave = 0;
}
else
{
Load_Sound_Data(g_ds_buffer, thread_index * 16384, 65536, g_fp);
// reset directsound buffer leave and current position
g_ds_leave -= 16384;
g_ds_pos += 16384;
}
}
}
return 0;
}
//--------------------------------------------------------------------------------
// Play WAVE file sound which specified by filename.
//--------------------------------------------------------------------------------
void Play_Stream_Sound(char* filename)
{
DSBPOSITIONNOTIFY pos_notify[4];
WAVE_HEADER wave_header;
// open the source file
if((g_fp = fopen(filename, "rb")) == NULL)
return;
// create a 2 second buffer to stream in wave
if((g_ds_buffer = Create_Buffer_From_WAV(g_fp, &wave_header)) == NULL)
{
fclose(g_fp);
return;
}
// get streaming size and pointer
g_ds_size = wave_header.data_size;
g_ds_pos = sizeof(WAVE_HEADER);
g_ds_leave = g_ds_size;
// create a thread for notifications
g_thread_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) Handle_Notifications, NULL, 0, &g_thread_id);
// create failed
if(g_thread_handle == NULL)
return;
// create the notification interface
if(FAILED(g_ds_buffer->QueryInterface(IID_IDirectSoundNotify8, (void**)&g_ds_notify)))
return;
// create the event handles and set the notifications
for(long i = 0; i < 4; i++)
{
// create an unnamed event object
g_events[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
pos_notify[i].dwOffset = 16384 * (i+1) - 1;
pos_notify[i].hEventNotify = g_events[i];
}
// set the notification position
//
// During capture or playback, whenever the read or play cursor reached one of the specified offsets,
// the associated event is signaled.
g_ds_notify->SetNotificationPositions(4, pos_notify);
// fill buffer completely with sound
// advance file pointer to data buffer's head
fseek(g_fp, sizeof(WAVE_HEADER), SEEK_SET);
// load sound buffer data with 65536 bytes
Load_Sound_Data(g_ds_buffer, 0, 65536, g_fp);
// reset leave sound buffer and current sound buffer position
g_ds_leave -= 65536;
g_ds_pos += 65536;
// play sound looping
g_ds_buffer->SetCurrentPosition(0);
g_ds_buffer->SetVolume(DSBVOLUME_MAX);
g_ds_buffer->Play(0, 0, DSBPLAY_LOOPING);
}
下面用一个例子来演示如何使用刚才介绍的那些知识。
点击下载源码和工程
完整代码示例:
/***************************************************************************************
PURPOSE:
Streaming Playback Demo
***************************************************************************************/
#include <windows.h>
#include <stdio.h>
#include <dsound.h>
#include "resource.h"
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dsound.lib")
#pragma warning(disable : 4996)
#define Safe_Release(p) if((p)) (p)->Release();
// .WAV file header
struct WAVE_HEADER
{
char riff_sig[4]; // 'RIFF'
long waveform_chunk_size; // 8
char wave_sig[4]; // 'WAVE'
char format_sig[4]; // 'fmt ' (notice space after)
long format_chunk_size; // 16;
short format_tag; // WAVE_FORMAT_PCM
short channels; // # of channels
long sample_rate; // sampling rate
long bytes_per_sec; // bytes per second
short block_align; // sample block alignment
short bits_per_sample; // bits per second
char data_sig[4]; // 'data'
long data_size; // size of waveform data
};
// window handles, class.
HWND g_hwnd;
char g_class_name[] = "StreamClass";
IDirectSound8* g_ds; // directsound component
IDirectSoundBuffer8* g_ds_buffer; // sound buffer object
IDirectSoundNotify8* g_ds_notify; // notification object
HANDLE g_thread_handle; // thread handle
DWORD g_thread_id; // thread id
HANDLE g_events[4]; // notification handles
FILE* g_fp; // .WAV file handle
long g_ds_size; // directsound data buffer size
long g_ds_pos; // current directsound buffer position
long g_ds_leave; // leave directsound buffer data needed to be played
//--------------------------------------------------------------------------------
// Create wave header information from wave file.
//--------------------------------------------------------------------------------
IDirectSoundBuffer8* Create_Buffer_From_WAV(FILE* fp, WAVE_HEADER* wave_header)
{
IDirectSoundBuffer* ds_buffer_primary;
IDirectSoundBuffer8* ds_buffer_second;
DSBUFFERDESC ds_buffer_desc;
WAVEFORMATEX wave_format;
// read in the header from beginning of file
fseek(fp, 0, SEEK_SET);
fread(wave_header, 1, sizeof(WAVE_HEADER), fp);
// check the sig fields. returning if an error.
if(memcmp(wave_header->riff_sig, "RIFF", 4) || memcmp(wave_header->wave_sig, "WAVE", 4) ||
memcmp(wave_header->format_sig, "fmt ", 4) || memcmp(wave_header->data_sig, "data", 4))
{
return NULL;
}
// setup the playback format
ZeroMemory(&wave_format, sizeof(WAVEFORMATEX));
wave_format.wFormatTag = WAVE_FORMAT_PCM;
wave_format.nChannels = wave_header->channels;
wave_format.nSamplesPerSec = wave_header->sample_rate;
wave_format.wBitsPerSample = wave_header->bits_per_sample;
wave_format.nBlockAlign = wave_format.wBitsPerSample / 8 * wave_format.nChannels;
wave_format.nAvgBytesPerSec = wave_format.nSamplesPerSec * wave_format.nBlockAlign;
// create the sound buffer using the header data
ZeroMemory(&ds_buffer_desc, sizeof(DSBUFFERDESC));
ds_buffer_desc.dwSize = sizeof(DSBUFFERDESC);
ds_buffer_desc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_LOCSOFTWARE;
ds_buffer_desc.dwBufferBytes = 65536;
ds_buffer_desc.lpwfxFormat = &wave_format;
// create main sound buffer
if(FAILED(g_ds->CreateSoundBuffer(&ds_buffer_desc, &ds_buffer_primary, NULL)))
return NULL;
// get newer interface
if(FAILED(ds_buffer_primary->QueryInterface(IID_IDirectSoundBuffer8, (void**)&ds_buffer_second)))
{
ds_buffer_primary->Release();
return NULL;
}
// return the interface
return ds_buffer_second;
}
//--------------------------------------------------------------------------------
// Load sound data from second directsound buffer.
//--------------------------------------------------------------------------------
BOOL Load_Sound_Data(IDirectSoundBuffer8* ds_buffer, long lock_pos, long lock_size, FILE* fp)
{
BYTE* ptr1;
BYTE* ptr2;
DWORD size1, size2;
if(lock_size == 0)
return FALSE;
// lock the sound buffer at position specified
if(FAILED(ds_buffer->Lock(lock_pos, lock_size, (void**)&ptr1, &size1, (void**)&ptr2, &size2, 0)))
return FALSE;
// read in the data
fread(ptr1, 1, size1, fp);
if(ptr2 != NULL)
fread(ptr2, 1, size2, fp);
// unlock it
ds_buffer->Unlock(ptr1, size1, ptr2, size2);
return TRUE;
}
//--------------------------------------------------------------------------------
// Handle directsound nofification evenet, just stream load sound buffer.
//--------------------------------------------------------------------------------
DWORD Handle_Notifications(LPVOID thread_data)
{
while(1)
{
// wait for a message
//
// return when any one or all of the specified objects are in the signaled state or the time-out
// interval elapses.
DWORD result = MsgWaitForMultipleObjects(4, g_events, FALSE, INFINITE, QS_ALLEVENTS);
// get notification #
DWORD thread_index = result - WAIT_OBJECT_0;
// check for update #
if(thread_index >= 0 && thread_index < 4)
{
// stop sound and quit thread if no more
if(g_ds_leave == 0)
ExitThread(0);
// seek to read position in file
fseek(g_fp, g_ds_pos, SEEK_SET);
// stream in data based on amount of data left
if(g_ds_leave <= 16384)
{
// if this is reached, then end of sound is coming up.
Load_Sound_Data(g_ds_buffer, thread_index * 16384, g_ds_leave, g_fp);
g_ds_leave = 0;
}
else
{
Load_Sound_Data(g_ds_buffer, thread_index * 16384, 65536, g_fp);
// reset directsound buffer leave and current position
g_ds_leave -= 16384;
g_ds_pos += 16384;
}
}
}
return 0;
}
//--------------------------------------------------------------------------------
// Play WAVE file sound which specified by filename.
//--------------------------------------------------------------------------------
void Play_Stream_Sound(char* filename)
{
DSBPOSITIONNOTIFY pos_notify[4];
WAVE_HEADER wave_header;
// open the source file
if((g_fp = fopen(filename, "rb")) == NULL)
return;
// create a 2 second buffer to stream in wave
if((g_ds_buffer = Create_Buffer_From_WAV(g_fp, &wave_header)) == NULL)
{
fclose(g_fp);
return;
}
// get streaming size and pointer
g_ds_size = wave_header.data_size;
g_ds_pos = sizeof(WAVE_HEADER);
g_ds_leave = g_ds_size;
// create a thread for notifications
g_thread_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) Handle_Notifications, NULL, 0, &g_thread_id);
// create failed
if(g_thread_handle == NULL)
return;
// create the notification interface
if(FAILED(g_ds_buffer->QueryInterface(IID_IDirectSoundNotify8, (void**)&g_ds_notify)))
return;
// create the event handles and set the notifications
for(long i = 0; i < 4; i++)
{
// create an unnamed event object
g_events[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
pos_notify[i].dwOffset = 16384 * (i+1) - 1;
pos_notify[i].hEventNotify = g_events[i];
}
// set the notification position
//
// During capture or playback, whenever the read or play cursor reached one of the specified offsets,
// the associated event is signaled.
g_ds_notify->SetNotificationPositions(4, pos_notify);
// fill buffer completely with sound
// advance file pointer to data buffer's head
fseek(g_fp, sizeof(WAVE_HEADER), SEEK_SET);
// load sound buffer data with 65536 bytes
Load_Sound_Data(g_ds_buffer, 0, 65536, g_fp);
// reset leave sound buffer and current sound buffer position
g_ds_leave -= 65536;
g_ds_pos += 65536;
// play sound looping
g_ds_buffer->SetCurrentPosition(0);
g_ds_buffer->SetVolume(DSBVOLUME_MAX);
g_ds_buffer->Play(0, 0, DSBPLAY_LOOPING);
}
//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}
//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
WNDCLASS win_class;
MSG msg;
// create window class and register it
win_class.style = CS_HREDRAW | CS_VREDRAW;
win_class.lpfnWndProc = Window_Proc;
win_class.cbClsExtra = 0;
win_class.cbWndExtra = DLGWINDOWEXTRA;
win_class.hInstance = inst;
win_class.hIcon = LoadIcon(inst, IDI_APPLICATION);
win_class.hCursor = LoadCursor(NULL, IDC_ARROW);
win_class.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
win_class.lpszMenuName = NULL;
win_class.lpszClassName = g_class_name;
if(! RegisterClass(&win_class))
return FALSE;
// create the main window
g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_STREAM), 0, NULL);
ShowWindow(g_hwnd, cmd_show);
UpdateWindow(g_hwnd);
// initialize and configure directsound
// creates and initializes an object that supports the IDirectSound8 interface
if(FAILED(DirectSoundCreate8(NULL, &g_ds, NULL)))
{
MessageBox(NULL, "Unable to create DirectSound object", "Error", MB_OK);
return 0;
}
// set the cooperative level of the application for this sound device
g_ds->SetCooperativeLevel(g_hwnd, DSSCL_NORMAL);
// play a streaming sound
Play_Stream_Sound("test.wav");
if(g_ds_buffer)
{
// play sound looping
g_ds_buffer->SetCurrentPosition(0);
// set volume
g_ds_buffer->SetVolume(DSBVOLUME_MAX);
// play sound
g_ds_buffer->Play(0, 0, DSBPLAY_LOOPING);
}
// start message pump, waiting for signal to quit.
ZeroMemory(&msg, sizeof(MSG));
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// stop sound
if(g_ds_buffer)
g_ds_buffer->Stop();
// kill the thread
if(g_thread_handle != NULL)
{
// terminates thread
TerminateThread(g_thread_handle, 0);
// closes an open object handle
CloseHandle(g_thread_handle);
}
// release the event handle
for(int i = 0; i < 4; i++)
CloseHandle(g_events[i]);
// close .WAV file
if(g_fp)
fclose(g_fp);
// release directsound objects
Safe_Release(g_ds_buffer);
Safe_Release(g_ds);
UnregisterClass(g_class_name, inst);
return (int) msg.wParam;
}
运行截图:
阅读下篇:
用DirectX
Audio和DirectShow播放声音和音乐(5)