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>
* 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
#include
#include
//windows
#ifdef WIN32
#include
//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
#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;
}
#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还需完善。
如有错误请指正: