1. 程式人生 > >C++ 播放音訊流(PCM裸流)

C++ 播放音訊流(PCM裸流)

    直接上程式碼,如果有需要可以直接建一個win32控制檯程式然後將程式碼拷過去改個檔名就可以用了(注意將聲道和頻率與你自己的檔案對應)。當然我自己也用VS2008寫了個例子上傳了,如果有需要下載地址如下:點選開啟連結

    這份程式碼是開啟檔案擷取一段資料然後播放的,可以輕鬆的經過加一條執行緒的方式改成網路傳輸的形式,但經過本人測試,因為沒有快取機制會有“噠噠”的噪聲,也就是說這份程式碼在網路實時音訊上的表現並不太好。為了解決這個問題,可以加上快取機制,本人因為一開始用的是事件響應方式,所以一直困在這個框架裡,不能很好的利用快取的機制解決上面提到的問題,後來嘗試了用回撥函式的方式來響應資料播放完成的訊息,問題就輕鬆的解決了。那部分的程式碼會在稍候放上去。


#include "stdafx.h"
#include <stdio.h>
#include <Windows.h>
#pragma comment(lib, "winmm.lib")

char buf[1024 * 1024 * 4];

int _tmain(int argc, _TCHAR* argv[]) {
	FILE*           thbgm;//檔案
	int             cnt;
	HWAVEOUT        hwo;
	WAVEHDR         wh;
	WAVEFORMATEX    wfx;
	HANDLE          wait;

	wfx.wFormatTag = WAVE_FORMAT_PCM;//設定波形聲音的格式
	wfx.nChannels = 1;//設定音訊檔案的通道數量
	wfx.nSamplesPerSec = 8000;//設定每個聲道播放和記錄時的樣本頻率
	wfx.nAvgBytesPerSec = 16000;//設定請求的平均資料傳輸率,單位byte/s。這個值對於建立緩衝大小是很有用的
	wfx.nBlockAlign = 2;//以位元組為單位設定塊對齊
	wfx.wBitsPerSample = 16;
	wfx.cbSize = 0;//額外資訊的大小
	wait = CreateEvent(NULL, 0, 0, NULL);
	waveOutOpen(&hwo, WAVE_MAPPER, &wfx, (DWORD_PTR)wait, 0L, CALLBACK_EVENT);//開啟一個給定的波形音訊輸出裝置來進行回放
	fopen_s(&thbgm, "paomo.pcm", "rb");
	cnt = fread(buf, sizeof(char), 1024 * 1024 * 4, thbgm);//讀取檔案4M的資料到記憶體來進行播放,通過這個部分的修改,增加執行緒可變成網路音訊資料的實時傳輸。當然如果希望播放完整的音訊檔案,也是要在這裡稍微改一改
	int dolenght = 0;
	int playsize = 1024;
	while (cnt) {//這一部分需要特別注意的是在迴圈回來之後不能花太長的時間去做讀取資料之類的工作,不然在每個迴圈的間隙會有“噠噠”的噪音
		wh.lpData = buf + dolenght;
		wh.dwBufferLength = playsize;
		wh.dwFlags = 0L;
		wh.dwLoops = 1L;
		waveOutPrepareHeader(hwo, &wh, sizeof(WAVEHDR));//準備一個波形資料塊用於播放
		waveOutWrite(hwo, &wh, sizeof(WAVEHDR));//在音訊媒體中播放第二個函式wh指定的資料
		WaitForSingleObject(wait, INFINITE);//用來檢測hHandle事件的訊號狀態,在某一執行緒中呼叫該函式時,執行緒暫時掛起,如果在掛起的INFINITE毫秒內,執行緒所等待的物件變為有訊號狀態,則該函式立即返回
		dolenght = dolenght + playsize;
		cnt = cnt - playsize;
	}
	waveOutClose(hwo);
	fclose(thbgm);
	return 0;
}

    離寫上面部分已經過了快一年,現在回看之前寫的程式碼感覺略為坑爹,或許是進步了吧。之前說要把雙快取的程式碼放出來,哪知道後來忙別的專案去了,這部分就丟到一邊,去老專案中提取程式碼感覺好煩一直沒弄。在這一年中不少人發私信問我關於這部分程式碼如何寫的事,沒想到現在做音訊的人還真不少呢。Ok,既然挖了坑就要填,今天乘著週末寫了一個雙快取的Demo工程,程式碼如下:

#include <stdio.h>
#include <Windows.h>

#pragma comment(lib, "winmm.lib")

#define DATASIZE 1024*512 //分次擷取資料大小
FILE*			pcmfile;  //音訊檔案
HWAVEOUT        hwo;

void CALLBACK WaveCallback(HWAVEOUT hWave, UINT uMsg, DWORD dwInstance, DWORD dw1, DWORD dw2)//回撥函式
{
	switch (uMsg)
	{
		case WOM_DONE://上次快取播放完成,觸發該事件
		{
			LPWAVEHDR pWaveHeader = (LPWAVEHDR)dw1;
			pWaveHeader->dwBufferLength = fread(pWaveHeader->lpData, 1, DATASIZE, pcmfile);;
			waveOutPrepareHeader(hwo, pWaveHeader, sizeof(WAVEHDR));
			waveOutWrite(hwo, pWaveHeader, sizeof(WAVEHDR));
			break;
		}
	}
}

void main() 
{
	int             cnt;
	WAVEHDR         wh1;
	WAVEHDR         wh2;
	WAVEFORMATEX    wfx;

	fopen_s(&pcmfile, "paomo.pcm", "rb");//開啟檔案

	wfx.wFormatTag = WAVE_FORMAT_PCM;//設定波形聲音的格式
	wfx.nChannels = 1;//設定音訊檔案的通道數量
	wfx.nSamplesPerSec = 8000;//設定每個聲道播放和記錄時的樣本頻率
	wfx.nAvgBytesPerSec = 16000;//設定請求的平均資料傳輸率,單位byte/s。這個值對於建立緩衝大小是很有用的
	wfx.nBlockAlign = 2;//以位元組為單位設定塊對齊
	wfx.wBitsPerSample = 16;
	wfx.cbSize = 0;//額外資訊的大小

	waveOutOpen(&hwo, WAVE_MAPPER, &wfx, (DWORD)WaveCallback, 0L, CALLBACK_FUNCTION);//開啟一個給定的波形音訊輸出裝置來進行聲音播放,方式為回撥函式方式。如果是對話方塊程式,可以將第五個引數改為(DWORD)this,操作跟本Demo程式相似

	wh1.dwLoops = 0L;//播放區一
	wh1.lpData = new char[DATASIZE];
	wh1.dwBufferLength = DATASIZE; 
	fread(wh1.lpData, 1, DATASIZE, pcmfile);
	wh1.dwFlags = 0L;
	waveOutPrepareHeader(hwo, &wh1, sizeof(WAVEHDR));//準備一個波形資料塊用於播放
	waveOutWrite(hwo, &wh1, sizeof(WAVEHDR));//在音訊媒體中播放第二個引數指定的資料,也相當於開啟一個播放區的意思

	wh2.dwLoops = 0L;//播放區二,基本同上
	wh2.lpData = new char[DATASIZE];
	wh2.dwBufferLength = DATASIZE;
	fread(wh2.lpData, 1, DATASIZE, pcmfile);
	wh2.dwFlags = 0L;
	waveOutPrepareHeader(hwo, &wh2, sizeof(WAVEHDR));
	waveOutWrite(hwo, &wh2, sizeof(WAVEHDR));

	while (wh1.dwBufferLength != 0 || wh2.dwBufferLength != 0)//如果檔案還在沒播放完則等待500ms
	{
		Sleep(500);
	}
	waveOutUnprepareHeader(hwo, &wh1, sizeof(WAVEHDR));//清理資料
	waveOutUnprepareHeader(hwo, &wh2, sizeof(WAVEHDR));

	delete []wh1.lpData;
	delete []wh2.lpData;
	fclose(pcmfile);//關閉檔案
	return;
}

    同上面一樣,如果想要這個工程的可以到這個連結去下載。不過提醒下,本人已然拋棄了VS2008,直接用VS2013,如果還在用老平臺的話要用還是要折騰一會的。