本篇是
用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)