windows 声音编程之waveout
本教程將幫助您了解如何使用Windows waveOut接口播放數字音頻。根據經驗,這些接口函數掌握起來有些困難。在本教程中,我們將會建立一個Windows命令行程序來原始數字音頻。注意:本教程假設您熟悉C程序及Windows API的使用。理解數字音頻的相關知識也是有益的,但不是必須的。
教程內容:
·?????????????????????????????獲取文檔
·?????????????????????????????什么是數字音頻
·?????????????????????????????打開聲音設備
·?????????????????????????????播放聲音
·?????????????????????????????播放流式音頻到設備
·?????????????????????????????緩存機制
·?????????????????????????????運行程序
·?????????????????????????????接下來該做什么?
獲取文檔?
首先,您需要有關waveOut接口的相關文檔。如果您有Microsoft Platform SDK或者VisualC++,那么它們已經提供了相關的信息。如果您還沒有這些,您可以通過MSDN在線查看。(http://msdn.microsoft.com)
什么是數字音頻?
這一部分是為那些對數字音頻如何存儲完全沒有概念的人準備的。如果您理解有關數字音頻的信息,同時了解“樣本(sample)”、“采樣頻率(samplerate)”、“樣本大小(samplesize)”及“聲道(channels)”的概念,您可以跳過此節。?
只要把字節碼發送到聲卡就可以播放聲音了,但是這些字節都是什么意思呢?音頻只是簡單的一系列運動的壓力波。在現實世界中,它們是一些相似的波形,但是在數字世界中,我們必須從這波形中采集一系列的樣本并存儲起來。“樣本”是表現某一時間點上波形振幅的一個值——它僅僅是一個數字。?
“采樣頻率”表明我們采集波形樣本的頻率。它的計量單位是赫茲(Hz)或每秒樣本數。顯然,采樣頻率越高,采樣的波形就越接近真實的波形,所以聲音的音質也就越好。?
另外一個有助于改善音質的參數是每個樣本的大小。當然,樣本越大音質也就越好。樣本大小用字節位數(bits)來計量。為何樣本越大音質越好?假設一個8bits的樣本,它有256(2的八次方)個可能的值,這意味著您不能精確地再現波形的振幅。而如果是一個16bits的樣本,它的可能值有65536(2的16次方)個,這樣它就擁有256倍于8bits的樣本的更精確表現波形的能力。?
最后是關于聲道。在多數機器上有兩個喇叭(左、右),那是兩個聲道。您需要同時把樣本數據存入左聲道和右聲道。?
幸運的是,操作兩個聲道是很容易的(您將在本教程中看到)。樣本總是交錯存儲的,它們將按左、右、左、右……的順序存儲。?
CD品質的音頻采樣頻率是44100Hz,樣本大小是16bits,意味著1M的音頻數據只能持續約6秒的時間。
打開聲音設備?
打開聲音設備需要使用waveOutOpen函數(可以在您的文檔中查到)。象其它許多Windows對象一樣,您可以簡單地使用一個句柄(Handle)調用該設備。如存儲Windows窗口句柄使用HWND類似,我們可以用HWAVEOUT句柄來調用聲音設備。?
下面的代碼段說明了如何打開一個CD標準音質的波形設備,然后關閉它。
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
HWAVEOUT hWaveOut; /* device handle */
WAVEFORMATEX wfx; /* look this up in your documentation */
MMRESULT result;/* for waveOut return values */
/*
* first we need to set up the WAVEFORMATEX structure.?
* the structure describes the format of the audio.
*/
wfx.nSamplesPerSec = 44100; /* sample rate */
wfx.wBitsPerSample = 16; /* sample size */
wfx.nChannels = 2; /* channels*/
/*
* WAVEFORMATEX also has other fields which need filling.
* as long as the three fields above are filled this should
* work for any PCM (pulse code modulation) format.
*/
wfx.cbSize = 0; /* size of _extra_ info */
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
/*
* try to open the default wave device. WAVE_MAPPER is
* a constant defined in mmsystem.h, it always points to the
* default wave device on the system (some people have 2 or
* more sound cards).
*/
if(waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL) !=MMSYSERR_NOERROR) {
fprintf(stderr, "unable to openWAVE_MAPPER device\n");
ExitProcess(1);
}
/*
* device is now open so print the success message
* and then close the device again.
*/
printf("The Wave Mapper device was opened successfully!\n");
waveOutClose(hWaveOut);
return 0;
}
注意:要編譯本程序,您需要添加winmm.lib到您的工程,否則將會鏈接失敗。?
好了,我們已經做好了第一步,現在聲音設備已經準備好,我們可以寫音頻數據進去了。
播放聲音?
打開和關閉聲音設備挺有意思的,但是上面的代碼并沒有真的做什么事情。我們想要的是能從設備聽到聲音。在這之前,我們有兩件事要做。
·?????????????????????????????獲得一個正確格式的原始音頻
·?????????????????????????????解決如何將數據寫入設備
問題一很好解決,您可以使用Winamp的DiskWriter插件來轉換一個音樂文件為原始音頻。比如您可以轉換\Windows\Media下的Windows聲音文件(比如Ding.wav)為原始音頻文件。如果您不能轉換這些文件,那么直接播放未經轉換的文件也是件很有意思的事。直接播放的話,聽起來會很快,因為這些文件大部分是用22kHz的采樣頻率存儲的。?
問題二就稍微復雜一些了。音頻是以塊(Block)的形式寫入設備的,每個塊都有它自己的頭(Header)。寫入一個塊(Block)是很容易的,但是大部分時候,我們需要建立一個隊列機制并寫入很多的塊(Blocks)。之所以用一個小的文件開始學習,是因為下面的例子我們將載入整個文件到一個塊中并寫入設備。?
首先,我們要寫個函數發送一塊數據到音頻設備中。函數命名為writeAudioBlock。要寫入音頻數據,我們需要三個接口函數:waveOutPrepareHeader,waveOutWrite和waveOutUnprepareHeader,并按這個順序調用它們。您可以在相關文檔中查找到并熟悉這些函數。?
下面的代碼是函數writeAudioBlock的初期版本。
void writeAudioBlock(HWAVEOUT hWaveOut, LPSTR block, DWORD size)
{
WAVEHDR header;
/*
* initialize the block header with the size
* and pointer.
*/
ZeroMemory(&header, sizeof(WAVEHDR));
header.dwBufferLength = size;
header.lpData = block;
/*
* prepare the block for playback
*/
waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
/*
* write the block to the device. waveOutWrite returns immediately
* unless a synchronous driver is used (not often).
*/
waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
/*
* wait a while for the block to play then start trying
* to unprepare the header. this will fail until the block has
* played.
*/
Sleep(500);
while(waveOutUnprepareHeader(hWaveOut,&header,sizeof(WAVEHDR)) ==WAVERR_STILLPLAYING)
Sleep(100);
}
現在我們有了一個寫入塊數據的函數。我們還需要一個函數來獲得音頻數據塊。這就是函數loadAudioBlock的任務了。函數loadAudioBlock讀取文件到內存并把指針返回。下面就是loadAudioBlock的代碼:
LPSTR loadAudioBlock(const char*filename, DWORD* blockSize)
{
HANDLE hFile= INVALID_HANDLE_VALUE;
DWORD size = 0;
DWORD readBytes = 0;
void* block = NULL;
/*
* open the file
*/
if((hFile = CreateFile(
filename,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0,
NULL
)) == INVALID_HANDLE_VALUE)
return NULL;
/*
* get it's size, allocate memory and read the file
* into memory. don't use this on large files!
*/
do {
if((size = GetFileSize(hFile, NULL)) ==0)
break;
if((block = HeapAlloc(GetProcessHeap(),0, size)) == NULL)
break;
ReadFile(hFile, block, size,&readBytes, NULL);
} while(0);
CloseHandle(hFile);
*blockSize = size;
return (LPSTR)block;
}
這部分的最后,是整個程序調用和main函數。
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
LPSTR loadAudioBlock(const char* filename, DWORD* blockSize);
void writeAudioBlock(HWAVEOUT hWaveOut, LPSTR block, DWORD size);
int main(int argc, char* argv[])
{
HWAVEOUT hWaveOut;?
WAVEFORMATEX wfx;?
LPSTR block;/* pointer to the block */
DWORD blockSize;/* holds the size of the block */
.
. (leave middle section as it was)?
.
printf("The Wave Mapper device was opened successfully!\n");
/*
* load and play the block of audio
*/
if((block = loadAudioBlock("c:\\temp\\ding.raw", &blockSize)) ==NULL) {
fprintf(stderr, "Unable to loadfile\n");
ExitProcess(1);
}
writeAudioBlock(hWaveOut, block, blockSize);?
waveOutClose(hWaveOut);
return 0;
}
將上面的代碼放到一個工程里進行編譯就可以播放小的聲音文件了。我們實現了類似PlaySound函數的功能。請試著做一些小的試驗:改變播放的采樣頻率(在main函數中)或者改變樣本大小(注意:一定要是8的倍數)看看會發生什么,甚至可以改變一下聲道的數量。我們會發現改變采樣頻率或者聲道數會加快或減慢播放的速度,而改變樣本大小可能會有毀滅性的影響!
播放流式音頻到設備?
您可能會注意到上面的代碼有幾個重要的缺陷(注意這些都是故意的:)),明顯的幾個缺陷如下:
·?????????????????????????????限于載入數據的方式,我們不能播放太大的文件。現在的方法緩存整個文件并一次播放完畢。而音頻本質上是很大的,所以我們需要找一種方法把音頻數據轉換為流式數據并一個塊(Block)接一個塊的寫入到設備中。
·?????????????????????????????現有的writeAudioBlock函數是同步執行的,所以一個位(bit)一個位地寫入多個塊會在兩個塊輸出之間有個間隔(即不能足夠快速地重新填充緩存(buffer))。微軟建議至少需要兩個buffer的體制,這樣我們可以在播放一個塊(block)的同時填充另一個塊,然后交換播放和填充的塊。實際上這樣也不完全能解決問題。即使交換數據塊播放也會引起一個非常小(但是很煩人)的間隔。
幸運的是數據塊的讀取很簡單,所以暫時不用管它。現在讓我們集中于如何建立一種緩存機制以避免出現音頻設備的聲音間隔吧。?
這個塊切換的問題并不象它聽起來那么嚴重。我們不能無間隔地切換兩個數據塊,但是接口有某種機制可以讓我們避開這個問題。接口管理著一個塊的隊列,我們用waveOutPrepareHeader傳送的每個數據塊都可以通過調用waveOutWrite插入到這個隊列中。這意味著我們可以寫2個(或者更多)的數據塊到設備中,當第一個數據塊播放時填充第三個數據塊,然后當第二個播放的時候再進行切換。這樣我們可以得到無間隔的音頻輸出了。?
在說明這個方法之還有最后一個問題,我們如何知道一個數據塊播放完了?前面writeAudioBlock的第一個例子中直到塊完成再調用waveOutUnprepareHeader的方式是非常不好的。我們在實際應用中不能這么做,因為我們還要繼續填充新的數據塊到設備中以繼續播放,關于這些,在waveOut接口中提供了更好的方法來實現。?
waveOut接口提供了4種回調機制來通知我們數據塊已經播放完成了。它們是:
·?????????????????????????????事件(Event)——數據塊播放完成的時候會觸發一個事件
·?????????????????????????????回調函數(CallbackFunction)——數據塊播放完成時會調用一個函數
·?????????????????????????????線程消息(Thread)——數據塊播放完成時會發送一個線程消息
·?????????????????????????????窗口消息(Window)——數據塊播放完成時會發送一個窗口消息
要指定使用哪種方式只需要在調用waveOutOpen函數時指定參數dwCallback的值就可以了。在我們下面的例子中將使用回調函數的方式。?
所以我們需要一個新的函數:waveOutProc。這個函數如何定義可以在相關文檔中查到。您可以看到,這個函數將在以下三種情況下被調用:
·?????????????????????????????設備打開時(Opened)
·?????????????????????????????設備關閉時(Closed)
·?????????????????????????????數據塊播放完成時
我們感興趣的只是數據塊播放完成時這種情況。
緩存機制?
我們將要實現的緩存機制如上面我們提到的一樣運行。它需要一個變量來隨時保存空閑緩存(buffer)的數量(你可能想到了使用信號量(Semaphore)來控制,但是我們不能使用它,后面將解釋原因)。這個變量初始化為緩存的數量,當數據塊寫入的時候減小并在數據塊完成時增加。如果沒有緩存可用,我們將等待直到該變量計數器為1以上然后再繼續寫入。這將可以讓我們有效地循環向任何數量的數據塊隊列中寫入數據。我們例子中沒有使用3個數據塊隊列,而是更多,比如20個,這樣每次可以處理大概8KB的數據。?
有些事情你可能猜中了:waveOutProc是在不同的線程中被調用的。Windows建立了一個特殊的線程來管理音頻播放。在此回調函數中你可以做的事情有很多限制。讓我們看一下微軟的文檔上是怎么說的吧:
"Applications should not call anysystem-defined functions from inside a callback function, except forEnterCriticalSection, LeaveCriticalSection, midiOutLongMsg, midiOutShortMsg,OutputDebugString, PostMessage, PostThreadMessage, SetEvent, timeGetSystemTime,timeGetTime, timeKillEvent, and timeSetEvent.?
Calling other wave functions will cause deadlock."?
應用程序不能在該回調函數中調用除下列以外的系統函數:EnterCriticalSection,LeaveCriticalSection, midiOutLongMsg, midiOutShortMsg, OutputDebugString,PostMessage, PostThreadMessage, SetEvent, timeGetSystemTime, timeGetTime,timeKillEvent, and timeSetEvent。?
調用其它的wave函數可能會引起死鎖。
這解釋了為什么我們不能使用信號量(Semaphore)——這將需要調用ReleaseSemaphore系統函數,而這是我們不能去做的。在實際應用中可能會靈活一些——我見過在回調函數中使用信號量的代碼,不過那樣的程序可能在某些版本的Windows上可以執行而不能執行在其它版本的機器上。同樣的,在回調函數中調用waveOut函數也將導致死鎖。實際上我們也將在回調函數中調用waveOutUnprepareHeader,不過我們不能那么做。(如果你不調用waveOutReset將不會發生死鎖)。?
您可能注意到waveOutOpen提供了一個傳遞實例數據到回調函數的方法(一個用戶定義的指針),我們將使用這個方法傳遞我們的計數器變量指針。?
另外需要注意的是,既然waveOutProc被在另外的線程中調用,所以會有兩個以上的線程操作此計數器變量。為了避免線程沖突,我們需要使用Critical Section對象(我們將使用一個靜態變量并命名為waveCriticalSection)。?
下面是waveOutProc函數的代碼:
static void CALLBACK waveOutProc(
HWAVEOUT hWaveOut,?
UINT uMsg,?
DWORD dwInstance,?
DWORD dwParam1,
DWORD dwParam2?
)
{
/*
* pointer to free block counter
*/
int* freeBlockCounter = (int*)dwInstance;
/*
* ignore calls that occur due to openining and closing the
* device.
*/
if(uMsg != WOM_DONE)
return;
EnterCriticalSection(&waveCriticalSection);
(*freeBlockCounter)++;
LeaveCriticalSection(&waveCriticalSection);
}
然后我們需要兩個函數分配和釋放數據塊的內存以及一個命名為writeAudio的新的writeAudioBlock的實現。下面的兩個函數allocateBlocks和freeBlocks實現了數據塊的分配和釋放。allocateBlocks分配了一組數據塊(Block),每個數據塊的頭(Header)是固定長度的。freeBlocks則釋放了數據塊的內存。如果allocateBlocks失敗將導致程序退出。這意味著我們不需要在main函數中檢查它的返回值。
WAVEHDR* allocateBlocks(int size, intcount)
{
unsigned char* buffer;
int i;
WAVEHDR* blocks;
DWORD totalBufferSize = (size + sizeof(WAVEHDR)) * count;
/*
* allocate memory for the entire set in one go
*/
if((buffer = HeapAlloc(
GetProcessHeap(),?
HEAP_ZERO_MEMORY,?
totalBufferSize
)) == NULL)
{
fprintf(stderr, "Memory allocationerror\n");
ExitProcess(1);
}
/*
* and set up the pointers to each bit
*/
blocks = (WAVEHDR*)buffer;
buffer += sizeof(WAVEHDR) * count;
for(i = 0; i < count; i++) {
blocks[i].dwBufferLength = size;
blocks[i].lpData = buffer;
buffer += size;
}
return blocks;
}
void freeBlocks(WAVEHDR* blockArray)
{
/*?
* and this is why allocateBlocks works the way it does
*/?
HeapFree(GetProcessHeap(), 0, blockArray);
}
新的writeAudio函數需要能夠把那些必須的數據塊(Block)寫入隊列中。基本的邏輯如下:
While there's data available
If the current free block is prepared
Unprepare it
End If
If there's space in the current free block
Write all the data to the block
Exit the function
Else
Write as much data as is possible tofill the block
Prepare the block
Write it
Decrement the free blocks counter
Subtract however many bytes were written from the data available
Wait for at least one block to become free
Update the current block pointer
End If
End While
這就產生了一個問題:我們如何知道什么時候一個數據塊(Block)準備好了而什么時候沒有準備好??
實際上這是一個相當簡單的事情。Windows使用結構體WAVEHDR的dwFlags成員變量來解決這個問題。waveOutPrepareHeader函數的功能中有一項就是設置dwFlags為WHDR_PREPARED。所以我們需要做的就是檢查dwFlags中的這個標識位。?
我們將使用結構體WAVEHDR中的dwUser成員變量來管理數據塊的計數器。下面是writeAudio函數的代碼:
void writeAudio(HWAVEOUT hWaveOut,LPSTR data, int size)
{
WAVEHDR* current;
int remain;
current = &waveBlocks[waveCurrentBlock];
while(size > 0) {
/*?
* first make sure the header we're going to use is unprepared
*/
if(current->dwFlags & WHDR_PREPARED)
waveOutUnprepareHeader(hWaveOut,current, sizeof(WAVEHDR));
if(size < (int)(BLOCK_SIZE -current->dwUser)) {
memcpy(current->lpData +current->dwUser, data, size);
current->dwUser += size;
break;
}
remain = BLOCK_SIZE - current->dwUser;
memcpy(current->lpData + current->dwUser, data, remain);
size -= remain;
data += remain;
current->dwBufferLength = BLOCK_SIZE;
waveOutPrepareHeader(hWaveOut, current, sizeof(WAVEHDR));
waveOutWrite(hWaveOut, current, sizeof(WAVEHDR));
EnterCriticalSection(&waveCriticalSection);
waveFreeBlockCount--;
LeaveCriticalSection(&waveCriticalSection);
/*
* wait for a block to become free
*/
while(!waveFreeBlockCount)
Sleep(10);
/*
* point to the next block
*/
waveCurrentBlock++;
waveCurrentBlock %= BLOCK_COUNT;
current = &waveBlocks[waveCurrentBlock];
current->dwUser = 0;
}
}
現在我們有了寫音頻的新的函數,因為不會再被用到所以你可以扔掉writeAudioBlock函數了。你也可以扔掉loadAudioBlock函數,因為下一部分我們將在main函數中實現新的方法不再需要loadAudioBlock函數了。
運行程序?
如果您按照本教程進行到這里,現在應該擁有了一個C文件包含下面的函數:
·?????????????????????????????main
·?????????????????????????????waveOutProc
·?????????????????????????????allocateBlocks
·?????????????????????????????freeBlocks
·?????????????????????????????writeAudio
下面讓我們完成main函數的新版本以實現把硬盤上的文件流式播放到waveOut設備上吧。下面的代碼當然也包括了程序運行所需要的模塊變量的聲明以及我們已經寫出的函數的原型。
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
/*
* some good values for block size and count
*/
#define BLOCK_SIZE 8192
#define BLOCK_COUNT 20
/*
* function prototypes
*/?
static void CALLBACK waveOutProc(HWAVEOUT, UINT, DWORD, DWORD, DWORD);
static WAVEHDR* allocateBlocks(int size, int count);
static void freeBlocks(WAVEHDR* blockArray);
static void writeAudio(HWAVEOUT hWaveOut, LPSTR data, int size);
/*
* module level variables
*/
static CRITICAL_SECTION waveCriticalSection;
static WAVEHDR* waveBlocks;
static volatile int waveFreeBlockCount;
static int waveCurrentBlock;
int main(int argc, char* argv[])
{
HWAVEOUT hWaveOut; /* device handle */
HANDLEhFile;/* file handle */
WAVEFORMATEX wfx; /* look this up in your documentation */
char buffer[1024]; /* intermediate buffer for reading */
int i;
/*
* quick argument check
*/
if(argc != 2) {
fprintf(stderr, "usage: %s\n", argv[0]);
ExitProcess(1);
}
/*
* initialise the module variables
*/?
waveBlocks = allocateBlocks(BLOCK_SIZE, BLOCK_COUNT);
waveFreeBlockCount = BLOCK_COUNT;
waveCurrentBlock= 0;
InitializeCriticalSection(&waveCriticalSection);
/*
* try and open the file
*/?
if((hFile = CreateFile(
argv[1],
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0,
NULL
)) == INVALID_HANDLE_VALUE)
{
fprintf(stderr, "%s: unable toopen file '%s'\n", argv[0], argv[1]);
ExitProcess(1);
}
/*
* set up the WAVEFORMATEX structure.
*/
wfx.nSamplesPerSec = 44100; /* sample rate */
wfx.wBitsPerSample = 16; /* sample size */
wfx.nChannels= 2; /* channels*/
wfx.cbSize = 0; /* size of _extra_ info */
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nBlockAlign = (wfx.wBitsPerSample * wfx.nChannels) >> 3;
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
/*
* try to open the default wave device. WAVE_MAPPER is
* a constant defined in mmsystem.h, it always points to the
* default wave device on the system (some people have 2 or
* more sound cards).
*/
if(waveOutOpen(
&hWaveOut,?
WAVE_MAPPER,?
&wfx,?
(DWORD_PTR)waveOutProc,?
(DWORD_PTR)&waveFreeBlockCount,?
CALLBACK_FUNCTION
) != MMSYSERR_NOERROR)
{
fprintf(stderr, "%s: unable toopen wave mapper device\n", argv[0]);
ExitProcess(1);
}
/*
* playback loop
*/
while(1) {
DWORD readBytes;
if(!ReadFile(hFile, buffer, sizeof(buffer), &readBytes, NULL))
break;
if(readBytes == 0)
break;
if(readBytes < sizeof(buffer)) {
printf("at end of buffer\n");
memset(buffer + readBytes, 0, sizeof(buffer) - readBytes);
printf("after memcpy\n");
}
writeAudio(hWaveOut, buffer, sizeof(buffer));
}
/*
* wait for all blocks to complete
*/
while(waveFreeBlockCount < BLOCK_COUNT)
Sleep(10);
/*
* unprepare any blocks that are still prepared
*/
for(i = 0; i < waveFreeBlockCount; i++)
if(waveBlocks[i].dwFlags &WHDR_PREPARED)
waveOutUnprepareHeader(hWaveOut,&waveBlocks[i], sizeof(WAVEHDR));
DeleteCriticalSection(&waveCriticalSection);
freeBlocks(waveBlocks);
waveOutClose(hWaveOut);
CloseHandle(hFile);
return 0;
}
接下來該做什么??
接下來要做的事就取決于你自己了。我有幾個您可能會有興趣的建議:
·?????????????????????????????試著修改原始音頻程序讓它可以從標準輸入讀取。您可以直接從命令行控制管道輸出聲音文件。
·?????????????????????????????重寫讀取部分讓它直接讀取Wave文件(*.wav)而不是RAW文件(原始音頻文件)。您會發現這令人吃驚的簡單。Wave文件使用結構體WAVEFORMATEX來定義數據格式,您可以在打開聲音設備后使用它。關于文件格式的有關信息可以參考WOTSIT's Format網站(http://www.wotsit.org)。
·?????????????????????????????看看自己能不能創建新的或更好的緩存機制
·?????????????????????????????試著把這些代碼加入某個開源解碼器中,如Vorbis解碼器或MP3解碼器。這樣您就可以得到一個屬于您自己的媒體播放器了:)
總結
以上是生活随笔為你收集整理的windows 声音编程之waveout的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开源游戏源码
- 下一篇: matlab求马尔可夫转移矩阵,matl