OpenAl音频播放

OpenAL(Open Audio Library)是自由软件界的跨平台音效API。它设计给多通道三维位置音效的特效表现。其 API 风格模仿自 OpenGL。刚开始接触的时候可以下载工具包Alut,里面提供丰富的处理接口文件加载等,不过建议在自己熟悉了整个流程之后研究下Alut的 源代码,之后尽量使用alc.c里面的东西自己实现Alut工具包的功能,这是一个机灵的程序员的想法!

废话不多说,OpenAl的初始化在一个进程中只能有一次,所以需要保证Al的初始化能够仅有的一次的正确性。设计模式不是有单例的嘛,要规范些可以参考一下。装好了OpenAl的SDk等之后会有很多小例子,那可是非常好的学习资料,本人也是从上面的例子学习过来的。OpenAl封装了很多底层的接口,这让我们在使用的时候非常的便利。基本的东西就不说了,相信只要有例子可依了解和掌握它还是非常的简单的。设置好了声音源以及声音buffer只需要调用alSourcePlay即可播放,播放的参数由alBufferData设定。

OpenAl的流缓冲是很容易的就可以实现,使用alSourceQueueBuffers将声音buffer加到声音源的source的队列中,用alGetSourcei(m_uiSource, AL_BUFFERS_PROCESSED,&m_iBuffersProcessed)取得缓冲队列中的空闲的buffer然后往里面添加数据即可。具体见代码吧。

上面说了必须保证OpenAl的初始化仅有一个。所以我把OpenAl的初始化类声明为全局变量。下面是初始化的过程:

CInitAl::CInitAl(void)
{
	m_iHasInit = 0;
	if(InitOpenAL() == 0)
	{
		m_iHasInit = 1;
	}
}

/** @fn      ~CInitAl(void)
 *
 *  @brief   al环境初始化类析构函数
 *
 *  @return  
 */
CInitAl::~CInitAl(void)
{
	if(m_iHasInit == 1)
	{
		ExitOpenAL();
	}
}

int CInitAl::InitOpenAL(void)
{
	ALCdevice   *pDevice;
	ALCcontext  *pContext;
	const char  *deviceSpecifier;
	char        deviceName[] = "DirectSound3D";

	//得到设备句柄
	pDevice = alcOpenDevice(deviceName);
	//得到设备说明
	deviceSpecifier = alcGetString(pDevice, ALC_DEVICE_SPECIFIER);

	//建立声音文本描述
	pContext = alcCreateContext(pDevice, NULL);
	//设置行为文本描述
	alcMakeContextCurrent(pContext);
	//检查错误
	if(alcGetError(pDevice) != ALC_NO_ERROR)
	{
		return -1;
	}
	return 0;
}

void CInitAl::ExitOpenAL(void)
{
	ALCcontext  *pCurContext;
	ALCdevice   *pCurDevice;

	//得到当前文本描述
	pCurContext = alcGetCurrentContext();
	//得到用于当前文本描述的设备
	pCurDevice  = alcGetContextsDevice(pCurContext);
	//重置当前文本描述为NULL
	alcMakeContextCurrent(NULL);
	//释放文本描述和设备
	alcDestroyContext(pCurContext);
	alcCloseDevice(pCurDevice);
}
声音源以及听者的信息

//源声音的位置.
static ALfloat SourcePos[] = {0.0f, 0.0f, 0.0f};
//源声音的速度.
static ALfloat SourceVel[] = {0.0f, 0.0f, 0.1f};
//听者的位置.
static ALfloat ListenerPos[] = {0.0f, 0.0f, 0.0f};
//听者的速度
static ALfloat ListenerVel[] = {0.0f, 0.0f, 0.0f};
//听者的方向 (first 3 elements are "at", second 3 are "up")
static ALfloat ListenerOri[] = {0.0f, 0.0f, -1.0f,   0.0f, 1.0f, 0.0f};
OpenAl播放类声明:

#define NUMBUFFERS 4
#define SERVICE_UPDATE_PERIOD (20)

class COpenAl
{
public:
	COpenAl(char* pFileName);
	~COpenAl(void);

	void			Play(void);
private:
	int				Initialize(void);
	int				LoadAudioFormat(void);
	int				GetFormat(const int nChannels, const int wBitsPerSample);
	static DWORD	WINAPI PlayThread(LPVOID lpParame);


	ALuint		    m_uiBuffers[NUMBUFFERS];
	ALuint		    m_uiSource;
	ALuint			m_uiBuffer;
	ALint			m_iState;
	ALint			m_iLoop;
	ALint			m_iBuffersProcessed, m_iTotalBuffersProcessed, m_iQueuedBuffers;
	WAVEFORMATEX	m_stWfx;
	unsigned long	m_ulDataSize;
	unsigned long	m_ulFrequency;
	unsigned long	m_ulFormat;
	unsigned long	m_ulBufferSize;
	unsigned long	m_ulBytesWritten;
	void*			m_pData;


	CWavInfo*       m_pWavInfo;
};
初始化函数:

int COpenAl::Initialize(void)
{
	int          iLoop;
	unsigned int nSize = 0;

	if(cInit.m_iHasInit == 0)
	{
		if(cInit.InitOpenAL() != 0)
		{
			return -1;
		}
	}

	//从文件中读取wav格式
	if(LoadAudioFormat() != 0)
	{
		return -1;
	}
	//设置播放的文件参数以及分配块内存
	m_ulFormat    = GetFormat(m_stWfx.nChannels, m_stWfx.wBitsPerSample);
	m_ulFrequency = m_stWfx.nSamplesPerSec;
	m_pData = malloc(BLOCKSIZE);
	if(m_pData == NULL)
	{
		return -1;
	}

	alGenBuffers(NUMBUFFERS, m_uiBuffers);
	alGenSources(1, &m_uiSource);

	alSourcef(m_uiSource, AL_PITCH, 1.0f);
	alSourcef(m_uiSource, AL_GAIN, 1.0f);
	alSourcefv(m_uiSource, AL_POSITION, SourcePos);
	alSourcefv(m_uiSource, AL_VELOCITY, SourceVel);
	alSourcei(m_uiSource, AL_LOOPING, AL_FALSE);
	alListenerfv(AL_POSITION, ListenerPos); 
	alListenerfv(AL_VELOCITY, ListenerVel); 
	alListenerfv(AL_ORIENTATION, ListenerOri); 
	if (alGetError() != AL_NO_ERROR) 
	{
		return false; 
	}

	//首次加载好缓冲区的数据,之后数据由播放线程填充
	for(iLoop = 0; iLoop < NUMBUFFERS; ++iLoop)
	{
		if(m_pWavInfo != NULL           && 
		   m_pWavInfo->m_nHasError == 0 &&
		   m_pWavInfo->ReadABlockData(m_pData, &nSize) == 0)
		{
			alBufferData(m_uiBuffers[iLoop], m_ulFormat, m_pData, nSize, m_ulFrequency);
			alSourceQueueBuffers(m_uiSource, 1, &m_uiBuffers[iLoop]);
		}
	}
	return 0;
}
播放线程,这个是参考SDK中的playstream的。

DWORD WINAPI COpenAl::PlayThread(LPVOID lpParame)
{
	COpenAl *cOpenAl = (COpenAl*)lpParame;
	unsigned int nBytesWritten = 0;
	while(true)
	{
		//openal buffers中已经播放过的个数
		cOpenAl->m_iBuffersProcessed = 0;
		//得到空闲的buffer个数
		alGetSourcei(cOpenAl->m_uiSource, AL_BUFFERS_PROCESSED, &cOpenAl->m_iBuffersProcessed);

		//对于每一个空闲的buffer,填充新的数据并添加到播放队列中
		while(cOpenAl->m_iBuffersProcessed)
		{
			//从队列中移除播放结束的buffer,并取得buffer的标志进行数据填充
			cOpenAl->m_uiBuffer = 0;
			alSourceUnqueueBuffers(cOpenAl->m_uiSource, 1, &cOpenAl->m_uiBuffer);

			//读取数据,并检查文件是否播放完成
			if(cOpenAl->m_pWavInfo != NULL &&
			   cOpenAl->m_pWavInfo->ReadABlockData(cOpenAl->m_pData, &nBytesWritten) == 0)
			{
				//拷贝数据到buffer中
				alBufferData(cOpenAl->m_uiBuffer, cOpenAl->m_ulFormat, cOpenAl->m_pData, 
					nBytesWritten, cOpenAl->m_ulFrequency);
				//将填充好的buffer添加到播放队列中
				alSourceQueueBuffers(cOpenAl->m_uiSource, 1, &cOpenAl->m_uiBuffer);
			}
			cOpenAl->m_iBuffersProcessed--;
		}

		// Check the status of the Source.  If it is not playing, then playback was completed,
		// or the Source was starved of audio data, and needs to be restarted.
		alGetSourcei(cOpenAl->m_uiSource, AL_SOURCE_STATE, &cOpenAl->m_iState);
		if(cOpenAl->m_iState != AL_PLAYING)
		{
			// If there are Buffers in the Source Queue then the Source was starved of audio
			// data, so needs to be restarted (because there is more audio data to play)
			alGetSourcei(cOpenAl->m_uiSource, AL_BUFFERS_QUEUED, &cOpenAl->m_iQueuedBuffers);
			if(cOpenAl->m_iQueuedBuffers)
			{
				alSourcePlay(cOpenAl->m_uiSource);
			}
			else
			{
				// Finished playing
				break;
			}
		}
	}
	return 0;
}

最后是播放的入口函数,这样就是结束了。

void COpenAl::Play(void)
{
	HANDLE	pHandle;
	DWORD	result; 
	MSG		msg;

	//创建播放线程
	pHandle = CreateThread(0, 0, PlayThread, this, NULL, NULL);
	if(pHandle == NULL)
	{
		return;
	}
	 
	while(true)
	{
		//等待播放线程结束并处理接收到的信号或者消息
		result = MsgWaitForMultipleObjects(1, &pHandle, false, INFINITE, QS_ALLINPUT); 
		if(result == (WAIT_OBJECT_0))
		{
			break;
		} 
		else 
		{ 
			PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
			DispatchMessage(&msg); 
		}
	}
	alSourceStop(m_uiSource);
	alSourcei(m_uiSource, AL_BUFFER, 0);
	return;
}
一周多学习的东西终于整理完成!希望大家可以多交流~~

As I am a beginner with this, please tell me if there are any mistakes in this article!

你可能感兴趣的:(C/C++)