天行健 君子当自强而不息

用DirectX Audio和DirectShow播放声音和音乐(4)


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

posted on 2007-07-29 14:09 lovedday 阅读(3908) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


公告

导航

统计

常用链接

随笔分类(178)

3D游戏编程相关链接

搜索

最新评论