mpg123解碼MP3,使用DirectSound播放樂曲


(非常不好意思,我前面傳的代碼有點小問題,已修正,VS2010+WINDOWS7旗艦版測試通過2013.10.29)

自己喜歡游戲,就想做游戲去,但是不想直接完全用人家現成的引擎。

游戲引擎的大頭光影和物理引擎部分咱不奢求了,但基本的編寫一個游戲的流程和原理得知道吧?

遂決定自學DirectX,前面的那些DirectX初始化啊,建立頂點緩存,世界變換什么的廢話我也不多說了,這篇文章的中心是講怎樣播放聲音的。我本人基礎很薄弱,所以在這里寫的詳細點,術語也經常弄不懂,求指正。


一、mpg123解碼器的選擇,編譯,以及如何添加到工程中

首先,就我所知,貌似沒有一個音頻播放lib或者dll能直接播放聲音文件的,必須得經過解碼(.mp3,.ogg,etc.),即使是.wav格式讀進來的二進制流要播放也得經過一番轉換

.mp3格式最普遍,我們就來講講.mp3格式。

解碼器有很多選擇,最近mpg123更新了,我就覺得它很與時俱進,聽聞千千靜聽(現百度音樂)用的就是mpg123,那我也就用了mpg123

官網見www.mpg123.de,我用的版本是1.16.0

mpg123是個開源的東西,下下來是一個tar.gz2的文件,用7-zip解壓

windows下不太好編譯,幸好它提供了VS的工程文件,在它的ports\MSVC++\里面選擇一個版本打開

里面提供有2005,2008,2008clr,2010幾個版本,不妨說一下我只有2008那個文件夾里面的文件可以編譯通過

在調試階段建議采用debug模式生成的libmpg123,這樣斷點調試時可以清晰的看到變量的當前值

debug模式生成一個libmpg123.lib

連同src\ibmpg123\文件夾下面的mpg123.h.in

以及ports\MSVC++\的mpg123.h一同拷貝到你的工程文件目錄,在VS里面包含到工程中

mpg123.h是為了在windows下調用所定義的一系列屬性以及unicode文件名的支持(mpg123_topen)

mpg123.h.in才是各種接口的聲明

如果為了Intellisense或者VA_X方便(直接用它提供的mpg123.h和mpg123.h.in沒有自動補全提示),可以直接把mpg123.h.in改成mpg123.h單獨使用,但是要小改兩個地方

#ifndef MPG123_NO_CONFIGURE /* Enable use of this file without configure. */
@INCLUDE_STDLIB_H@
@INCLUDE_SYS_TYPE_H@

這里要改成

#include <stdlib.h>

#include <sys/types.h>

然后還要定義一個類型

#define ssize_t SSIZE_T 

下面在你的預編譯頭里面加一句#include "mpg123.h"就行了

我們下面的程序代碼主要是用到了mpg123_read,其它的mpg123_init啊,open啊,close啊顧名思義就行

二、DirectSound的簡介和使用

DirectSound是DirectXAudio的一個較底層的部件,提供了豐富的接口函數,實現.wav格式的波形聲音數據的播放控制。 與一般的WindowsAPI提供的聲音播放函數不同,DirectSound可實現多個聲音的混合播放。DirectSound可充分使用聲卡的內存資源,同時也提供了3D聲效算法,模擬出真實的3D立體聲。 ……以上來自度娘 反正現在你要編個PC或家用機上的大型游戲怎么都得用到DirectX的吧,那干脆就用它提供的DirectSound 手機上的你可以試試其他的API,OPENGL配OPENAL什么的?我不太懂反正 先安裝DirectX SDK微軟官網下載地址 然后工程中#include<dsound.h>和#pragma comment (lib,"dsound.lib")一下 DirectSound要進行調用得進行初始化 用完了也得清空,防止內存泄露
LPDIRECTSOUND g_pD3DSound=NULL;                         //DirectSound的設備指針
LPDIRECTSOUNDBUFFER g_pD3DSoundBuffer=NULL; //指向音樂數據緩存的指針
DSBUFFERDESC dsbd; //音樂數據的緩存格式

void D3DInit(long n)
{
DirectSoundCreate(NULL,&g_pD3DSound,NULL); //創建播放設備
g_pD3DSound->SetCooperativeLevel(hwnd,DSSCL_NORMAL); //設定協同等級,就是緩存的寫入級別
memset(&dsbd,0,sizeof(dsbd));
dsbd.dwSize=sizeof(dsbd);
dsbd.dwFlags=DSBCAPS_GLOBALFOCUS; //這么設置保證在后台也能播放
dsbd.dwBufferBytes=n; //解碼后的數據大小,以字節計算
dsbd.lpwfxFormat=(WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
}

void CleanUp()
{
if(g_pD3DSoundBuffer){g_pD3DSoundBuffer->Release();g_pD3DSoundBuffer=NULL;};
if(g_pD3DSound){g_pD3DSound->Release();g_pD3DSound=NULL;}; //注意順序
}



三、完整例程
#include <iostream>
using namespace std;
#include <windows.h>
#include "mpg123.h"
#include <dsound.h>

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


BYTE *buffer;
int err = MPG123_OK; //mpg123的工作狀態
long rate=0;
bool hasFile=false;
int channels=0, enc=0;
mpg123_handle *mh=NULL;

LPDIRECTSOUND g_pD3DSound=NULL; //DirectSound的設備指針
LPDIRECTSOUNDBUFFER g_pD3DSoundBuffer=NULL; //指向音樂數據緩存的指針
DSBUFFERDESC dsbd; //音樂數據的緩存格式
WAVEFORMATEX waveform; //當前mp3的音樂格式
HWND hwnd=CreateWindowA("Button","aaa",0,0,0,0,0,0,0,0,0);//一定要創建一個窗口句柄,不然沒辦法播放

void D3DInit(long n)
{
DirectSoundCreate(NULL,&g_pD3DSound,NULL); //創建播放設備
g_pD3DSound->SetCooperativeLevel(hwnd,DSSCL_NORMAL); //設定協同等級,就是緩存的寫入級別
memset(&dsbd,0,sizeof(dsbd));
dsbd.dwSize=sizeof(dsbd);
dsbd.dwFlags=DSBCAPS_GLOBALFOCUS; //這么設置保證在后台也能播放
dsbd.dwBufferBytes=n; //解碼后的數據大小,以字節計算
dsbd.lpwfxFormat=(WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
}

void CleanUp()
{
//一些清理工作,順序不要顛倒
mpg123_close(mh);
mpg123_delete(mh);
mpg123_exit();

//此處順序也不能亂
if(g_pD3DSoundBuffer){g_pD3DSoundBuffer->Release();g_pD3DSoundBuffer=NULL;};
if(g_pD3DSound){g_pD3DSound->Release();g_pD3DSound=NULL;};
}

void Open(CHAR* FileName)
{
//mpg123初始化
mpg123_init();
mh = mpg123_new(NULL, &err);

//檢測格式是否正確
mpg123_open(mh,FileName);
//獲得每秒采樣率,聲道數,以及編碼格式
mpg123_getformat(mh,&rate,&channels,&enc);
//mpg123只支持這兩種MP3編碼格式,已經夠了,大部分就是這樣的
if(enc != MPG123_ENC_SIGNED_16 && enc != MPG123_ENC_FLOAT_32)
{
fprintf(stderr, "Bad encoding: 0x%x!\n", enc);
return;
}

//設置音頻的格式
waveform.wFormatTag =WAVE_FORMAT_PCM; //PCM類型的聲音
waveform.nChannels =channels; //聲道
waveform.nSamplesPerSec =rate; //每秒采樣率
waveform.nAvgBytesPerSec=rate*channels*2; //每秒多少字節
waveform.nBlockAlign =4; //我不太清楚,反正4沒錯
waveform.wBitsPerSample =16; //signed16編碼格式的每采樣大小
waveform.cbSize =0; //額外信息,一般是0

//告訴系統文件打開正常
hasFile=true;
}

void D3DPlay(long n)
{
LPVOID lpPtr1=new LPVOID;
LPVOID lpPtr2=new LPVOID;
DWORD length1=0,length2=0,state=0;

D3DInit(n); //傳遞解碼后的數據文件大小,便於緩存格式變量創建
dsbd.lpwfxFormat=&waveform; //將讀取到的格式傳給緩存格式變量
g_pD3DSound->CreateSoundBuffer(&dsbd,&g_pD3DSoundBuffer,NULL);

//打開緩存以便寫入,返回兩個緩存可寫入地址和他們的長度,一般來說第二個地址是沒有的
g_pD3DSoundBuffer->Lock(0,n,&lpPtr1,&length1,&lpPtr2,&length2,0);
memcpy(lpPtr1,buffer,n); //拷貝數據
g_pD3DSoundBuffer->Unlock(lpPtr1,length1,0,0); //關閉緩存
g_pD3DSoundBuffer->SetCurrentPosition(0); //設定播放位置從0開始
g_pD3DSoundBuffer->Play(0,0,DSBPLAY_LOOPING); //播放,循環
//等待播放完成
g_pD3DSoundBuffer->GetStatus(&state);

while ((state==1)||(state==5)) //1代表只放一次,5代表循環播放
{
Sleep(200); //每隔200ms檢測一次狀態
g_pD3DSoundBuffer->GetStatus(&state);
};
printf("Play Over\n");
}

void Play()
{
if(hasFile)
{
long n=mpg123_length(mh)*16*2/8; //通過采樣總數*每采樣比特數*聲道/8獲得字節數
size_t done=0;
buffer=(BYTE*)malloc(n);
err=mpg123_read(mh,buffer,n,&done); //讀取文件並解碼,是的,一句話就完成了
if (err==MPG123_ERR) return;
if ((err==MPG123_DONE)||(err==MPG123_OK)) D3DPlay(done); //若正常解碼結束,開始播放
}
}

int main()
{
//讀入文件名,只支持英文
CHAR path[30];
cout<<"請輸入要播放的mp3文件(帶后綴名),請和程序放在同一個目錄下"<<endl;
cin>>path;
Open(path);
Play();
CleanUp();

//不至於播放完了一閃而過
return system("pause");
}

四、總結和參考資料 我上面采取的是將整個音頻文件載入到緩存里再播放的方式 可能對大文件來說效率不高,但是播放一些小文件來說應該是夠了 邊載入邊播放的例程見http://bbs.csdn.net/topics/310095017
若要詳細了解DirectSound請見http://blog.csdn.net/aoosang/article/details/520903    (我作為一個新手看了挺頭大的)


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com