用DirectX Audio和DirectShow播放聲音和音樂(4)

使用通告

“通告”是一種觸發機制,當緩存中播放位置達到某個固定的位置時,就會向程序發出通知。有了通告,就可以知道播放什麼時候結束,這種機制在比較長的聲音中特別有效。通告使用一個叫做 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/valueDescription

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;
}
 


運行截圖:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章