OpenAL播放pcm或wav数据流-windows/ios/android(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhuweigangzwg/article/details/53286945

OpenAL播放pcm或wav数据流-windows/ios/android(一)


最近在研究渲染问题,本文采用openal做pcm和wav数据流播放,并非本地文件,demo是windows的,ios通用。网上都是ios的,ios需要引用OpenAl.framework框架,

Android平台需要做openal的jni,android的openal库可以参考

http://blog.csdn.net/matrix_laboratory/article/details/53319735这篇文章,各个平台需要做稍微处理。

下面是代码:

//.h

/** Copyright (c/c++) <2016.11.22> <zwg/>
* Function  
* OpenAL through the buffer queuing mechanism to support the streaming playback of sound. The buffer queue is a buffer associated with a single source contact mechanism.
* when audio playback, continuous rendering of each buffer, as if the buffer is composed of a continuous sound. This can be controlled by some special functions.
* flow is generally the source of the work. In a number of audio buffer by alSourceQueueBuffers () function to queue, and then play the sound source,
* next with property AL_BUFFERS_PROCESSED to query. This property obtains the number of buffers that have been processed,
* allows applications to use the alSourceUnqueueBuffers () function to delete the buffers that have been processed.
* alSourceUnqueueBuffers () function will start from the queue header will be processed in order to remove the buffer. Finally, the rest of the buffer queue in gear.
* Opanal for audio rendering related implementation and definition, etc.
* OpenAL通过缓冲器排队机制支持声音的流式播放。缓冲器排队是多个缓冲器与单一音源相关联的一种机制。
* 当音源播放时,连续对各个缓冲器进行渲染,就好象这些缓冲器组成了一个连续的声音。这可以通过一些特殊函数来控制。
* 流音源的工作一般是这样的。音源里的一批缓冲器通过alSourceQueueBuffers()函数进行排队,然后播放音源,
* 接下来用属性AL_BUFFERS_PROCESSED来查询。该属性得出已经处理好的缓冲器的数量,
* 从而允许应用程序使用alSourceUnqueueBuffers()函数删除那些已经处理好的缓冲器。
* alSourceUnqueueBuffers()函数将从队列头部开始依次将处理好的缓冲器删除。最后,其余的缓冲器在音源上排队。
* OpanAl 用于音频渲染相关实现及定义,等
*/

#ifndef __LVS_OPENAL_INTERFACE_H__
#define __LVS_OPENAL_INTERFACE_H__

#include <stdio.h>
#include <stdlib.h>
#include <string>

//windows
#ifdef WIN32
#include <Windows.h>
//openAl库
#include "alut.h"
#pragma comment(lib,"alut.lib")
#pragma comment(lib,"OpenAL32.lib")
//ios
#elif __APPLE__
#include "alut.h"
//ANDROID平台  
#elif __ANDROID__  
#include "alut.h"
//linux
#else
#include "alut.h"
#endif

//到处宏定义
//windows
#ifdef WIN32
#define LVS_DLLEXPORT __declspec(dllexport)
//ios
#elif __APPLE__
#define LVS_DLLEXPORT
//linux
#else
#define LVS_DLLEXPORT
#endif

using namespace std;

//接口初始化
int lvs_openal_interface_init();
//接口释放
void lvs_openal_interface_uninit();
//接口开始播放
void lvs_openal_interface_playsound();
//接口停止播放
void lvs_openal_interface_stopsound();
//接口设置音量
void lvs_openal_interface_setvolume(float volume);//volume取值范围(0~1)
//接口获取音量
float lvs_openal_interface_getvolume();
//接口传入pcm数据用于播放
int lvs_openal_interface_openaudiofromqueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel);
//更新队列数据,删除已经播放的buffer,这个在队列满的时候用
int lvs_openal_interface_updataQueueBuffer();
//获取当前时间戳
long long lvs_openal_interface_getrealpts();
//获取已经播放了多少个数据块
long long lvs_openal_interface_getIsplayBufferSize();
//获取缓存队列长度
int lvs_openal_getnumqueuedsize();

class cclass_openal_interface;

class cclass_openal_interface
{
public:
	cclass_openal_interface();
	virtual ~cclass_openal_interface();
	//开始播放
	void playSound();
	//停止播放
	void stopSound();
	//设置音量
	void SetVolume(float volume);//volume取值范围(0~1)
	//获取音量
	float GetVolume();
	//传入pcm数据用于播放
	int openAudioFromQueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel);
	//更新队列数据,删除已经播放的buffer
	int updataQueueBuffer();
private:
	//初始化openal
	int initOpenAL();
	//释放openal
	void cleanUpOpenAL();
public:
	int m_numprocessed;             //队列中已经播放过的数量
	int m_numqueued;                //队列中缓冲队列数量
	long long m_IsplayBufferSize;   //已经播放了多少个音频缓存数目
	double m_oneframeduration;      //一帧音频数据持续时间(ms)
	float m_volume;                 //当前音量volume取值范围(0~1)
	int m_samplerate;               //采样率
	int m_bit;                      //样本值
	int m_channel;                  //声道数
	int m_datasize;                 //一帧音频数据量
private:
	ALCdevice * m_Devicde;          //device句柄
	ALCcontext * m_Context;         //device context
	ALuint m_outSourceId;           //source id 负责播放
};


#endif

//.cpp

#include "Lvs_OpenAl_Interface.h"

static cclass_openal_interface * copenal_interface = NULL;

int lvs_openal_interface_init() 
{
	int ret = 0;
	printf("Device : lvs_openal_interface_init\n");
	if(copenal_interface == NULL)
	{
		copenal_interface = new cclass_openal_interface();
	}
	return ret;
}

void lvs_openal_interface_uninit()
{
	printf("Device : lvs_openal_interface_uninit\n");

	if(copenal_interface)
	{
		delete copenal_interface;
		copenal_interface = NULL;
	}
	return ;
}

void lvs_openal_interface_playsound()
{
	copenal_interface->playSound();
}

void lvs_openal_interface_stopsound()
{
	copenal_interface->stopSound();
}

void lvs_openal_interface_setvolume(float volume)//volume取值范围(0~1)
{
	copenal_interface->SetVolume(volume);
}

float lvs_openal_interface_getvolume()
{
	return copenal_interface->GetVolume();
}

int lvs_openal_interface_openaudiofromqueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel)
{
	return copenal_interface->openAudioFromQueue(data,dataSize,aSampleRate,aBit,aChannel);
}

long long lvs_openal_interface_getrealpts()
{
	long long time = (long long )((copenal_interface->m_IsplayBufferSize * copenal_interface->m_oneframeduration) + 0.5);
	printf("*****m_IsplayBufferSize : %ld",copenal_interface->m_IsplayBufferSize);
	printf("****************time : %lld(ms)\n",time);
	return time;
}

long long lvs_openal_interface_getIsplayBufferSize()
{
	return copenal_interface->m_IsplayBufferSize;
}

int lvs_openal_getnumqueuedsize()
{
	return copenal_interface->m_numqueued;
}

int lvs_openal_interface_updataQueueBuffer()
{
	return copenal_interface->updataQueueBuffer();
}

cclass_openal_interface::cclass_openal_interface()
{
	m_Devicde = NULL;  
	m_Context = NULL;      
	m_outSourceId = 0;      
	m_numprocessed = 0;          
	m_numqueued = 0;
	m_IsplayBufferSize = 0;
	m_oneframeduration = 0.0;
	m_volume = 1.0;
	m_samplerate = 0;
	m_bit = 0;
	m_channel = 0;
	m_datasize = 0;

	//init
	initOpenAL();
}

cclass_openal_interface::~cclass_openal_interface()
{
	cleanUpOpenAL();

	m_Devicde = NULL;  
	m_Context = NULL;      
	m_outSourceId = 0;      
	m_numprocessed = 0;          
	m_numqueued = 0;
	m_IsplayBufferSize = 0;
	m_oneframeduration = 0.0;
	m_volume = 1.0;
	m_samplerate = 0;
	m_bit = 0;
	m_channel = 0;
	m_datasize = 0;
}

int cclass_openal_interface::initOpenAL()
{
	int ret = 0;

	printf("=======initOpenAl===\n");

#ifdef WIN32
	//初始化 ALUT openal函数库
	int zwg_argc=1;
	//添加函数库名称
	char* zwg_argv[]={"ZWG_ALUT"};  
	ret= alutInit(&zwg_argc, zwg_argv); 
#else

#endif

	//打开device
	m_Devicde = alcOpenDevice(NULL);
	if (m_Devicde)
	{
#ifdef WIN32
		//windows 用这个context 声音不正常,以后处理
#else
		//建立声音文本描述
		m_Context = alcCreateContext(m_Devicde, NULL);

		//设置行为文本描述
		alcMakeContextCurrent(m_Context);
#endif
	}

	//创建一个source并设置一些属性
	alGenSources(1, &m_outSourceId);
	alSpeedOfSound(1.0);
	alDopplerVelocity(1.0);
	alDopplerFactor(1.0);
	alSourcef(m_outSourceId, AL_PITCH, 1.0f);
	alSourcef(m_outSourceId, AL_GAIN, 1.0f);
	alSourcei(m_outSourceId, AL_LOOPING, AL_FALSE);
	alSourcef(m_outSourceId, AL_SOURCE_TYPE, AL_STREAMING);

	return ret;
}

void cclass_openal_interface::cleanUpOpenAL()
{
	printf("=======cleanUpOpenAL===\n");

	alDeleteSources(1, &m_outSourceId);

#ifdef WIN32
	alcCloseDevice(m_Devicde);
	m_Devicde = NULL;
	alutExit();
#else
	ALCcontext * Context = alcGetCurrentContext();
	ALCdevice * Devicde = alcGetContextsDevice(Context);

	if (Context)
	{
		alcMakeContextCurrent(NULL);
		alcDestroyContext(Context);
		m_Context = NULL;
	}
	alcCloseDevice(m_Devicde);
	m_Devicde = NULL;
#endif
}

void cclass_openal_interface::playSound()
{
	int ret = 0;
	alSourcePlay(m_outSourceId);
	if((ret = alGetError()) != AL_NO_ERROR)
	{
		printf("error alcMakeContextCurrent %x : %s\n", ret,alutGetErrorString (ret));
	}
}

void cclass_openal_interface::stopSound()
{
	alSourceStop(m_outSourceId);
}

void cclass_openal_interface::SetVolume(float volume)//volume取值范围(0~1)
{
	m_volume = volume;
	alSourcef(m_outSourceId,AL_GAIN,volume);
}

float cclass_openal_interface::GetVolume()
{
	return m_volume;
}

int cclass_openal_interface::updataQueueBuffer()
{
	//播放状态字段
	ALint stateVaue = 0;

	//获取处理队列,得出已经播放过的缓冲器的数量
	alGetSourcei(m_outSourceId, AL_BUFFERS_PROCESSED, &m_numprocessed);
	//获取缓存队列,缓存的队列数量
	alGetSourcei(m_outSourceId, AL_BUFFERS_QUEUED, &m_numqueued);

	//获取播放状态,是不是正在播放
	alGetSourcei(m_outSourceId, AL_SOURCE_STATE, &stateVaue);

	//printf("===statevaue ========================%x\n",stateVaue);

	if (stateVaue == AL_STOPPED ||
		stateVaue == AL_PAUSED || 
		stateVaue == AL_INITIAL) 
	{
		//如果没有数据,或数据播放完了
		if (m_numqueued < m_numprocessed || m_numqueued == 0 ||(m_numqueued == 1 && m_numprocessed ==1))
		{
			//停止播放
			printf("...Audio Stop\n");
			stopSound();
			cleanUpOpenAL();
			return 0;
		}

		if (stateVaue != AL_PLAYING)
		{
			playSound();
		}
	}

	//将已经播放过的的数据删除掉
	while(m_numprocessed --)
	{
		ALuint buff;
		//更新缓存buffer中的数据到source中
		alSourceUnqueueBuffers(m_outSourceId, 1, &buff);
		//删除缓存buff中的数据
		alDeleteBuffers(1, &buff);

		//得到已经播放的音频队列多少块
		m_IsplayBufferSize ++;
	}
	long long time = (long long )((m_IsplayBufferSize * m_oneframeduration) + 0.5);
	//printf("*****m_IsplayBufferSize : %ld",m_IsplayBufferSize);
	//printf("****************time : %ld(ms)\n",time);
	return 1;
}

int cclass_openal_interface::openAudioFromQueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel)
{
	int ret = 0;
	//样本数openal的表示方法
	ALenum format = 0;
	//buffer id 负责缓存,要用局部变量每次数据都是新的地址
	ALuint bufferID = 0;

	if (m_datasize == 0 &&
		m_samplerate == 0 &&
		m_bit == 0 &&
		m_channel == 0)
	{
		if (dataSize != 0 &&
			aSampleRate != 0 &&
			aBit != 0 &&
			aChannel != 0)
		{
			m_datasize = dataSize;
			m_samplerate = aSampleRate;
			m_bit = aBit;
			m_channel = aChannel;
			m_oneframeduration = m_datasize * 1.0 /(m_bit/8) /m_channel /m_samplerate * 1000 ;   //计算一帧数据持续时间
		}
	}

	//创建一个buffer
	alGenBuffers(1, &bufferID);
	if((ret = alGetError()) != AL_NO_ERROR)
	{
		printf("error alGenBuffers %x : %s\n", ret,alutGetErrorString (ret));
		//AL_ILLEGAL_ENUM 
		//AL_INVALID_VALUE
		//#define AL_ILLEGAL_COMMAND                        0xA004
		//#define AL_INVALID_OPERATION                      0xA004
	}

	if (aBit == 8) 
	{
		if (aChannel == 1) 
		{
			format = AL_FORMAT_MONO8;
		}
		else if(aChannel == 2)
		{
			format = AL_FORMAT_STEREO8;
		}
	}

	if( aBit == 16 )
	{
		if( aChannel == 1 ) 
		{ 
			format = AL_FORMAT_MONO16;
		}
		if( aChannel == 2 ) 
		{
			format = AL_FORMAT_STEREO16;
		}
	}
	//指定要将数据复制到缓冲区中的数据
	alBufferData(bufferID, format, data, dataSize,aSampleRate);
	if((ret = alGetError()) != AL_NO_ERROR)
	{
		printf("error alBufferData %x : %s\n", ret,alutGetErrorString (ret));
		//AL_ILLEGAL_ENUM 
		//AL_INVALID_VALUE
		//#define AL_ILLEGAL_COMMAND                        0xA004
		//#define AL_INVALID_OPERATION                      0xA004
	}
	//附加一个或一组buffer到一个source上
	alSourceQueueBuffers(m_outSourceId, 1, &bufferID);
	if((ret = alGetError()) != AL_NO_ERROR)
	{
		printf("error alSourceQueueBuffers %x : %s\n", ret,alutGetErrorString (ret));
	}

	//更新队列数据
	ret = updataQueueBuffer();

	//删除一个缓冲 这里不应该删除缓冲,在source里面播放完毕删除
	//alDeleteBuffers(1, &bufferID);
	bufferID = 0;

	return 1;
}


//main.cpp

#include "Lvs_OpenAl_Interface.h"

//要显示的pcm/wav文件路径及名称
#define PCM_STREAM_PATH_NAME  "../pcm_stream/44100_2_16.pcm"

int main()
{
	int ret = 0;
	int nSampleRate = 44100;                   //采样率
	int nBit = 16;                             //样本数
	int nChannel = 2;                          //声道
	int ndatasize = 1024 * (nBit/8) *nChannel; //每次读取的数据大小 
	char ndata[4096 + 1] = {0};                //读取的数据         
	FILE * pFile_pcm = NULL;                   //读取pcm数据的文件句柄  

	//打开pcm文件
	if((pFile_pcm = fopen(PCM_STREAM_PATH_NAME, "rb")) == NULL)
	{
		printf("filed open file : %s\n",PCM_STREAM_PATH_NAME);
		return getchar();
	}
	else
	{
		printf("success open file : %s\n",PCM_STREAM_PATH_NAME);
	}

	//init
	lvs_openal_interface_init();

	//设置音量volume取值范围(0~1)
	lvs_openal_interface_setvolume(1.0);

	for(;;)
	{
		Sleep(23);
		//循环读取文件
		ret = fread(ndata, 1,ndatasize, pFile_pcm);
		if (ret != ndatasize)
		{
			//seek到文件开头
			fseek(pFile_pcm, 0, SEEK_SET);
			fread(ndata, 1,ndatasize, pFile_pcm);
		}
		//具体的处理在这里
		ret = lvs_openal_interface_openaudiofromqueue((char *)ndata,ndatasize,nSampleRate,nBit,nChannel);

		long long time = lvs_openal_interface_getrealpts();
	}

	//uinit
	lvs_openal_interface_uninit();

	//关闭pcm文件
	if (pFile_pcm != NULL)
	{
		fclose(pFile_pcm);
		pFile_pcm = NULL;
	}

	return 1;
}





程序运行效果并能听到声音:


本demo还需完善。

如有错误请指正:

交流请加QQ群:62054820
QQ:379969650.




猜你喜欢

转载自blog.csdn.net/zhuweigangzwg/article/details/53286945