XAudio2是一个跨平台的API,在Xbox 360及Windows中得到支持。在Xbox 360上, XAudio2作为一个静态库编译到游戏可执行文件中。在Windows上,XAudio2提供一个动态链接库(DLL)。以下例子只使用了其中的一部分功能,并不全面。详情请看微软技术页的XAudio2编程相关(英文)。
使用XAudio2来播放未压缩的PCM音频数据的过程并不复杂,主要有以下几个步骤:
使用XAudio2Create函数,该函数的功能是创建一个XAudio2对象(IXAudio2接口)。
原型:HRESULT XAudio2Create(
IXAudio2 **ppXAudio2,//这里返回XAudio2对象的指针
UINT32 Flags,//此处必须为0
XAUDIO2_PROCESSOR XAudio2Processor//指定所用CPU,默认:XAUDIO2_DEFAULT_PROCESSOR
);
示例:XAudio2Create( &pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR );
使用IXAudio2成员函数CreateMasteringVoice,该函数功能是创建并设置一个MasteringVoice
原型:HRESULT CreateMasteringVoice(
IXAudio2MasteringVoice **ppMasteringVoice,//这里返回MasteringVoice对象指针
UINT32 InputChannels = XAUDIO2_DEFAULT_CHANNELS,//设置声道数,可选
UINT32 InputSampleRate = XAUDIO2_DEFAULT_SAMPLERATE, //设置采样率,可选
UINT32 Flags = 0,//必须是0,可选
LPCWSTR szDeviceId = NULL, //设备,可选,NULL代表全局默认输出设备
const XAUDIO2_EFFECT_CHAIN *pEffectChain = NULL, //音效,可选
AUDIO_STREAM_CATEGORY StreamCategory = AudioCategory_GameEffects//流类型,可选
);
示例:pXAudio2->CreateMasteringVoice(&pMasterVoice);
使用IXAudio2成员函数CreateSourceVoice,该函数功能是创建并设置一个SourceVoice
原型:HRESULT CreateSourceVoice(
IXAudio2SourceVoice **ppSourceVoice, //这里返回IXAudio2SourceVoice对象指针
const WAVEFORMATEX *pSourceFormat, //PCM音频格式(下面讲到)
UINT32 Flags = 0,//SourceVoide工作方式,可选
float MaxFrequencyRatio = XAUDIO2_DEFAULT_FREQ_RATIO,//声调,可选,默认为1
IXAudio2VoiceCallback *pCallback = NULL,//回调类指针,可选
const XAUDIO2_VOICE_SENDS *pSendList = NULL,//目标格式设置,可选
const XAUDIO2_EFFECT_CHAIN *pEffectChain = NULL//音效设置,可选
);
示例:pXAudio2->CreateSourceVoice(&pSourceVoice,&format,0,XAUDIO2_DEFAULT_FREQ_RATIO,NULL,NULL,NULL);
其中format这样设置:(位数为bits,声道数为channels,采样率为hz)
WAVEFORMATEX format;
format.wFormatTag = WAVE_FORMAT_PCM;//PCM格式
format.wBitsPerSample = bits;//位数
format.nChannels = channels;//声道数
format.nSamplesPerSec = hz;//采样率
format.nBlockAlign = bits*channels/8;//数据块调整
format.nAvgBytesPerSec = format.nBlockAlign*hz;//平均传输速率
format.cbSize = 0;//附加信息
使用IXAudio2SourceVoice的成员函数SubmitSourceBuffer,该函数功能是呈交一个XAUDIO2_BUFFER
原型:HRESULT SubmitSourceBuffer(
const XAUDIO2_BUFFER *pBuffer,// 结构体XAUDIO2_BUFFER的指针(下面讲到)
const XAUDIO2_BUFFER_WMA *pBufferWMA = NULL//wma格式Buffer的指针,可选,默认NULL
);
示例:pSourceVoice->SubmitSourceBuffer(&XAudio2Buffer,NULL);
其中XAudio2Buffer这样设置:
XAUDIO2_BUFFER XAudio2Buffer;
XAudio2Buffer.Flags = 0;//可以设为0或XAUDIO2_END_OF_STREAM,当设为后者时,将使XAudio2播放完该数据块后自动停止,不再播放下一个数据块
XAudio2Buffer.AudioBytes = BufferSize;// 音频数据的长度,按字节算
XAudio2Buffer.pAudioData = pBuffer;//具体音频数据的地址,unsigned char pBuffer[]
XAudio2Buffer.PlayBegin = 0;//起始播放地址
XAudio2Buffer.PlayLength = 0;//播放长度,0为整数据块
XAudio2Buffer.LoopBegin = 0;//循环起始位置
XAudio2Buffer.LoopLength = 0;//循环长度,按字节算
XAudio2Buffer.LoopCount = 0;//循环次数,0为不循环,255为无限循环
XAudio2Buffer.pContext = NULL;//这里的pContext用来标识该数据块,供回调用,可以是NULL
播放呈交的数据使用IXAudio2SourceVoice的成员函数Start,该函数功能是开始播放。
原型:HRESULT Start(
UINT32 Flags,//必须是0
UINT32 OperationSet = XAUDIO2_COMMIT_NOW//使用XAUDIO2_COMMIT_NOW将立即生效,使用XAUDIO2_COMMIT_ALL将挂起,等待其它的数值的OperationSet的处理完
);
示例:pSourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
第5步做完之后,XAudio2将一块接一块地播放呈交的数据块。我们只需不断重复第四步,就能不断地播放音频数据了。需要注意的是,在XAudio2播放完某个XAudio2Buffer之前,该XAudio2Buffer以及XAudio2Buffer.pAudioData所指向的内存不能被修改或删除,否则将发生错误。但是某个XAudio2Buffer一旦被播放完,就能被修改了。为此,我们可以创建一个数组XAUDIO2_BUFFER []来循环呈交和更新数据。那怎么知道XAudio2到底播放了几个XAudio2Buffer呢,可以使用IXAudio2SourceVoice的成员函数GetState
原型:GetState(
XAUDIO2_VOICE_STATE *pVoiceState,// 这里返回XAUDIO2_VOICE_STATE结构体指针
[optional] UINT32 Flags//获取方式,可选,默认0.设为XAUDIO2_VOICE_NOSAMPLESPLAYED将只获取挂起(包括正在播放)的XAudio2Buffer数量,速度较快。注意:DirectX SDK版本没有此参数
);
XAUDIO2_VOICE_STATE包含三个成员:
void * pCurrentBufferContext//对应XAUDIO2_BUFFER中的pContext
UINT32 BuffersQueued//挂起(包括正在播放)的XAudio2Buffer数量
UINT64 SamplesPlayed//已播放的样本数
示例: pSourceVoice->GetState(&state);
暂停播放使用IXAudio2SourceVoice的成员函数:Stop
原型:HRESULT Stop(
UINT32 Flags,// 设为0或XAUDIO2_PLAY_TAILS,后者代表等待音效放完
UINT32 OperationSet = XAUDIO2_COMMIT_NOW// XAUDIO2_COMMIT_NOW立即生效
);
如果设定XAUDIO2_PLAY_TAILS,应在音效输出完成后设定0,再Stop一次。
暂停后再次调用Start将在暂停的位置开始播放。
如果要完全停止,还需要使用IXAudio2SourceVoice的成员函数FlushSourceBuffers,该函数功能是清除挂起的XAudio2Buffer队列。
原型:HRESULT FlushSourceBuffers();
说明:该函数使用后要到XAudio2播放完一个XAudio2Buffer才生效,建议在回调中使用。使用该函数后,XAudio2Buffer队列计数将置0
原型:HRESULT SetFrequencyRatio(
float Ratio,//1.0为正常声调,>1.0为高声调快放,<1.0为低声调慢放
UINT32 OperationSet = XAUDIO2_COMMIT_NOW
);
IXAudio2SourceVoice继承自IXAudio2Voice,所以还有许多IXAudio2Voice的功能,比如设置音量用SetVolume等。
注意:以上IXAudio2SourceVoice的成员函数中, Stop、GetState、 FlushSourceBuffers可以在回调中使用
释放相关实例的顺序与创建他们的顺序相反。需要包含头文件Xaudio2.h和Objbase.h以及链接ole32.lib(而不是Microsoft网站上的Xaudio2.lib)
下面给出我写的一个Speaker类供大家参考,该类使用XAudio2来播放PCM数据,在Win7x64系统中使用VS2010、DirectX SDK按x64、/clr(Speaker中没有使用托管类)编译测试通过
其公开成员函数ReadData采用阻塞的方法来读取音频数据,一旦塞满缓冲区将自动开始播放,播放多少就读取多少,SetFormat设置PCM音频格式(不设置的话,默认是16位、2声道、44100采样率)。SetBufferSize设置缓冲块数和每块大小(不设置的话,默认是4块,每块40KB)
#include <Windows.h>
#include <xaudio2.h>
#include "Objbase.h"
#pragma comment(lib,"Ole32.lib")
#define DEF_MaxBufferCount4
#define DEF_StreamingBufferSize40960
class VoiceCallBack;
class Speaker;
class VoiceCallBack:public IXAudio2VoiceCallback
{//回调类
public:HANDLE hBufferEndEvent;
VoiceCallBack():hBufferEndEvent(CreateEvent(NULL,FALSE,FALSE,NULL))
{count =lastcontext = currentcontext= 0; }
~VoiceCallBack(){CloseHandle(hBufferEndEvent);}
Speaker *speaker;
XAUDIO2_VOICE_STATE state;
public:int count;
int lastcontext;
int currentcontext;
//播放一个数据块前后触发事件
void OnBufferEnd(void *pBufferContext);
//不需要的方法只保留声明
void OnBufferStart(void *pBufferContext){}
void OnVoiceProcessingPassEnd(){}
void OnVoiceProcessingPassStart(UINT32 SamplesRequired){}
void OnStreamEnd(){}
void OnLoopEnd(void *pBufferContext){}
void OnVoiceError(void *pBufferContext,HRESULT Error){}
};
class Speaker
{public:
//XAudio2数据
IXAudio2* pXAudio2;//XAudio2引擎
IXAudio2MasteringVoice* pMasterVoice;//MsterVoice
VoiceCallBack voiceCallBack;//回调处理
XAUDIO2_BUFFER *pXAudio2Buffer;//封装的用来呈交的音频缓冲数据块
WAVEFORMATEX WaveFormat;//音频格式
IXAudio2SourceVoice *pSourceVoice;//源音处理接口
int MaxBufferCount;//最大缓冲数据块数
int StreamingBufferSize;//缓冲数据块大小
unsigned char **ppAudioData;//音频缓冲数据
bool IsPlaying;//是否在播放
HRESULT State;//状态,0为正常,<0为错误,>0为信息或警告
const wchar_t *pState;//状态内容详细描述
//跟外部读取交换数据
int ReadingBufferNumber;//正在读取的Buffer
int WritingBufferNumber;//正在写入的Buffer
int UnReadingBufferCount;//未呈交Buffer数量
int UnProcessedBufferCount;//呈交但未处理的Buffer数量
int WritingPosition;//当前块准备写入位置
//控制函数部分
public:Speaker();//默认构造函数
public:~Speaker();//析构函数
private:void reset();//重置交换数据参数
private:HRESULT SetState(HRESULT state);//设置简单的状态信息
public:void StopPlaying();//停止播放
public:void Pause();//暂停
public:void StartPlaying();//开始播放
public:void SetFormat(short bits,short channels,int hz);//更改PCM格式
public:void SetBufferSize(int MaxBufferCount,int StreamingBufferSize);//分配音频数据缓冲区
public:static XAUDIO2_BUFFER MakeXAudio2Buffer(const BYTE *pBuffer,int BufferSize);//生成XAudio2缓冲区
public:static WAVEFORMATEX MakePCMSourceFormat(short bits,short channels,int hz);//生成PCM格式信息
public:void ReadData(unsigned char *pBuffer,int length);//从pBuffer指向的内存复制length字节的数据到缓冲区,不复制完不返回
public:int ReadDataFrom(unsigned char *pBuffer,int length);//从pBuffer指向的内存复制length字节的数据,立即返回实际复制的数据字节数
};
void VoiceCallBack::OnBufferEnd(void *pBufferContext)
{
if(pBufferContext == NULL)
{
speaker->pSourceVoice->GetState(&state);
speaker->UnProcessedBufferCount=state.BuffersQueued;
}
else
{
currentcontext = *((int *) pBufferContext)+1;
if(currentcontext > lastcontext)
count += currentcontext - lastcontext;
else if(currentcontext < lastcontext)
count += speaker->MaxBufferCount + currentcontext - lastcontext;
lastcontext = currentcontext;
speaker->UnProcessedBufferCount -= count;
}
count = 0;
while(speaker->UnReadingBufferCount)//提交
{
speaker->pSourceVoice->SubmitSourceBuffer(&speaker->pXAudio2Buffer[speaker->ReadingBufferNumber],NULL);
speaker->ReadingBufferNumber = (1 + speaker->ReadingBufferNumber) % speaker->MaxBufferCount;
speaker->UnProcessedBufferCount++;
speaker->UnReadingBufferCount --;
}
SetEvent(hBufferEndEvent);
}
Speaker::Speaker()
{
//初始化
IsPlaying = false;
MaxBufferCount = 0;
StreamingBufferSize = 0;
voiceCallBack.speaker = this;
reset();
//建立引擎
if(FAILED(SetState(XAudio2Create( &pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR ))))goto END;
//建立MasteringVoice
if(FAILED(SetState(pXAudio2->CreateMasteringVoice(&pMasterVoice))))goto END;
//分配缓冲内存并设置XAudio2Buffer
SetBufferSize(DEF_MaxBufferCount,DEF_StreamingBufferSize);//设置默认缓冲
//设置pcm格式
WaveFormat = Speaker::MakePCMSourceFormat(16,2,44100);
//建立SourceVoice
SetState(pXAudio2->CreateSourceVoice(&this->pSourceVoice,&this->WaveFormat,0,XAUDIO2_DEFAULT_FREQ_RATIO,&this->voiceCallBack,NULL,NULL));
END:;
};
Speaker::~Speaker()
{
SetBufferSize(0,0);//停止播放并释放缓冲区
if(pSourceVoice != NULL)pSourceVoice->DestroyVoice();
if(pMasterVoice != NULL)pMasterVoice->DestroyVoice();
if(pXAudio2 != NULL)pXAudio2->StopEngine();
}
void Speaker::reset()
{
SetState(0);
ReadingBufferNumber = 0;
UnReadingBufferCount = 0;
UnProcessedBufferCount = 0;
WritingBufferNumber = 0;
WritingPosition = 0;
voiceCallBack.count = 0;
voiceCallBack.lastcontext = 0;
}
void Speaker::StopPlaying()
{
if(pSourceVoice == NULL)return;
XAUDIO2_VOICE_STATE state;
if(IsPlaying)
{
IsPlaying = false;
pSourceVoice->GetState(&state);
while(state.BuffersQueued)
{
if(FAILED(SetState(pSourceVoice->Stop(0,XAUDIO2_COMMIT_NOW))))return;
if(FAILED(SetState(pSourceVoice->FlushSourceBuffers())))return;
Sleep(1);
pSourceVoice->GetState(&state);
}
}
reset();
}
void Speaker::Pause()
{
if(pSourceVoice == NULL)return;
if(IsPlaying)
{
SetState(pSourceVoice->Stop(0,XAUDIO2_COMMIT_NOW));
if(FAILED(State))return;
IsPlaying = false;
}
}
void Speaker::StartPlaying()
{
SetState(pSourceVoice->Start(0,XAUDIO2_COMMIT_NOW));
if(FAILED(State))return;
this->IsPlaying = true;
}
void Speaker::SetFormat(short bits,short channels,int hz)
{
if(WaveFormat.wBitsPerSample == bits && WaveFormat.nChannels == channels && WaveFormat.nSamplesPerSec == hz)
return;
StopPlaying();
if(pSourceVoice)pSourceVoice->DestroyVoice();
pSourceVoice = NULL;
WaveFormat = MakePCMSourceFormat(bits,channels,hz);
if(FAILED(SetState(pXAudio2->CreateSourceVoice(&pSourceVoice,&WaveFormat,0,XAUDIO2_DEFAULT_FREQ_RATIO,&this->voiceCallBack,NULL,NULL))))pSourceVoice = NULL;
}
void Speaker::SetBufferSize(int MaxBufferCount,int StreamingBufferSize)
{
if (this->MaxBufferCount!=MaxBufferCount || this->StreamingBufferSize!=StreamingBufferSize)
{
this->StopPlaying();
for(int i=0;i<this->MaxBufferCount;++i)
{
if(this->StreamingBufferSize)//删除块
{
delete [] this->ppAudioData[i];
if(this->pXAudio2Buffer[i].pContext)
delete this->pXAudio2Buffer[i].pContext;
}
}
if(this->MaxBufferCount)//删除所有
{
delete [] this->ppAudioData;
delete [] this->pXAudio2Buffer;
}
this->MaxBufferCount=MaxBufferCount;//赋值
this->StreamingBufferSize=StreamingBufferSize;
voiceCallBack.lastcontext = 0;
if(MaxBufferCount)//创建
{
reset();
this->ppAudioData = new unsigned char *[MaxBufferCount];
this->pXAudio2Buffer = new XAUDIO2_BUFFER[MaxBufferCount];
if (StreamingBufferSize)
{
this->pXAudio2Buffer = new XAUDIO2_BUFFER[MaxBufferCount];
for(int i=0;i<MaxBufferCount;++i)
{
this->ppAudioData[i] = new unsigned char[StreamingBufferSize];
this->pXAudio2Buffer[i] = Speaker::MakeXAudio2Buffer(ppAudioData[i],StreamingBufferSize);
*((int*)this->pXAudio2Buffer[i].pContext) = i;
}
}
}
}
}
XAUDIO2_BUFFER Speaker::MakeXAudio2Buffer(const BYTE *pBuffer,int BufferSize)
{
XAUDIO2_BUFFER XAudio2Buffer;
XAudio2Buffer.AudioBytes = BufferSize;
XAudio2Buffer.Flags = 0;
XAudio2Buffer.LoopBegin = 0;
XAudio2Buffer.LoopCount = 0;
XAudio2Buffer.LoopLength = 0;
XAudio2Buffer.pAudioData = pBuffer;
XAudio2Buffer.pContext = (void*)new int[1];
XAudio2Buffer.PlayBegin = 0;
XAudio2Buffer.PlayLength = 0;
return XAudio2Buffer;
}
WAVEFORMATEX Speaker::MakePCMSourceFormat(short bits,short channels,int hz)
{
WAVEFORMATEX format;
format.wFormatTag = WAVE_FORMAT_PCM;//PCM格式
format.wBitsPerSample = bits;//位数
format.nChannels = channels;//声道数
format.nSamplesPerSec = hz;//采样率
format.nBlockAlign = bits*channels/8;//数据块调整
format.nAvgBytesPerSec = format.nBlockAlign*hz;//平均传输速率
format.cbSize = 0;//附加信息
return format;
}
void Speaker::ReadData(unsigned char *pBuffer,int length)
{
int i=0;
while(length>i)//不读完不退出循环
{
while(UnProcessedBufferCount + UnReadingBufferCount
>= MaxBufferCount-1)
{
if(!IsPlaying)
{
StartPlaying();
Sleep(0);
continue;
}
voiceCallBack.OnBufferEnd(NULL);
Sleep(10);
}
if(StreamingBufferSize > length-i+WritingPosition)
{
while(i<length)
{
this->ppAudioData[WritingBufferNumber][WritingPosition] = pBuffer[i];
++WritingPosition;
++i;
}
break;
}
while(StreamingBufferSize>WritingPosition)
{
this->ppAudioData[WritingBufferNumber][WritingPosition] = pBuffer[i];
++WritingPosition;
++i;
}
WritingPosition = 0;
WritingBufferNumber = (1+WritingBufferNumber)%MaxBufferCount;
++UnReadingBufferCount;
}
}
int Speaker::ReadDataFrom(unsigned char *pBuffer,int length)//从pBuffer指向的内存复制length字节的数据,立即返回实际复制的数据字节数
{
int i=0;
//XAUDIO2_VOICE_STATE state;
//pSourceVoice->GetState(&state);
if(pBuffer==nullptr || !length)
return 0;
while(UnProcessedBufferCount + UnReadingBufferCount < this->MaxBufferCount)
{
if(this->StreamingBufferSize > length-i+WritingPosition)
{
while(i<length)
{
this->ppAudioData[WritingBufferNumber][WritingPosition] = pBuffer[i];
++WritingPosition;
++i;
}
return length;
}
while(WritingPosition<StreamingBufferSize)
{
this->ppAudioData[WritingBufferNumber][WritingPosition] = pBuffer[i];
++WritingPosition;
++i;
}
WritingPosition = 0;
WritingBufferNumber = (1+WritingBufferNumber)%MaxBufferCount;
++UnReadingBufferCount;
++UnProcessedBufferCount;
}
voiceCallBack.OnBufferEnd(NULL);//督促XAudio2快点放完
return i;
}
HRESULT Speaker::SetState(HRESULT state)
{
this->State = state;
switch(state)
{
case 0:pState = L"正常.";break;
case XAUDIO2_E_INVALID_CALL:pState = L"函数无效调用";break;
case XAUDIO2_E_XMA_DECODER_ERROR:pState = L"XMA设备损坏";break;
case XAUDIO2_E_XAPO_CREATION_FAILED:pState = L"XAPO效果初始化失败";break;
case XAUDIO2_E_DEVICE_INVALIDATED:pState = L"音频设备不可用";break;
default:if(state>0)pState = L"未知信息";
else pState = L"未知错误";
}
return state;
}
声明:不知道此文章原始出处是哪里,所以没有注明转载地址。网上可以搜到很多这篇文章相关的文档或者引用,如果原作者需要我注明地址的话,请联系我。
原文:http://blog.csdn.net/u011417605/article/details/51252441