iOS OpenAL播放声音的流程
iOS移动设备进行音频播放时,可以使用openal
注意:openal 默认开启的是手机听筒(有多个播放设备 :alcopendevice(null)),如果想让声音通过扬声器进行播放,可通过audiosession的audiosessionsetproperty进行设置:kaudiosessionproperty_overridecategorydefaulttospeaker,此时声音会即从扬声器出来,也从听筒出来。
使用openal播放声音的步骤:
1 alcopendevice(null)-----得到设备D
获取设备可以通过打开一个null直接打开听筒,也可以枚举后,选择一个使用。如下:
- (void) enumDevices {
ALboolean enumeration;
enumeration = alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT");
if (enumeration == AL_FALSE) {
NSLog(@"iOS dosn't support ALC_ENUMERATION_EXT");
return;
}
ALCchar * devices = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
//ALCchar * devices = alcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
const ALCchar *device = devices, *next = devices + 1;
size_t len = 0;
NSLog(@"Devices list:\n");
while (device && *device != '\0' ) {
NSLog(@" -> %s", device);
len = strlen(device);
device += (len + 1);
next += (len + 2);
}
————————————————
版权声明:本段代码转载自「老衲不出家」
2 alccreateContext(D,hull)-----创建上下文context
3 alcMakeContextCurrent(context)----将上一步创建的上下文设置为当前上下文,注意“当前”两个字,如果创建两次上下文(并且两个context不是同一个,譬如地址不同,这算两个不一样的上下文),并且这两个都设置成当前上下文(makecontextcurrent),那第一次创建的上下文就会被最新的覆盖掉,第一次上下文下边的声源,也就不再播放声音了。只有最新的 currentcontext才起作用。设备,声源,收听者,上下文的关系,可参见openal官方文档。
举例:在Windows当中,功能1创建一个上下文1,用一个播放设备绑定上下文1,然后播放音乐A。然后开启功能2,也重新创建了上下文2,并绑定播放设备(功能2 可以和功能1用同一个设备;也可以是不同的设备,声卡;不同硬件不影响,影响的是相同硬件),功能2播放音乐B,只要功能2重新设置了当前上下文,功能2播放声音B时,功能1的声音A就不在播放了。(window 是如此的),要想功能1和功能2都有声音,可以在开启功能2时候获取当前上下文(getcurrentcontext),如果有,就不要再创建了,直接获取到当前上下文,并把当前上下文再次设置一次makecurrentcontext. 设备一般都是用的同一个,只要再打开一下就行。
4 申请播放缓存algenbuffers(个数,buffer个数),algenbuffers(6,p_buffer),定义 int p_buffer[6];也可以是1个,前边表示开了6块缓存;int型主要是用于存放地地址。
5 设置一个声源 alGenSources(1, &uisources); 给生源设置一些属性 alSourcei/f函数; 音源与buffer绑定alSourcei(uisources, AL_BUFFER, p_buffer);(绑定操作没有也可以);属性设置 alSourcei(uisources, AL_LOOPING, AL_FALSE); //设置多个缓存时,多个缓存播放 ,不能循环
6 alBufferData(p_buffer[i], AL_FORMAT_MONO16, data, size, 采样率);将数据装载到缓存上去,只有一个就装一个,多个就循环装载
alSourceQueueBuffers(uisources, 1, &p_buffer[n]),缓冲加到数据源上去。
7 播放声源 alSourcePlay(sources);
8 当使用多个缓冲播放时,通过不断获取声源空闲buffer个数,来不断地填充,algetsourcei(uisource,AL_BUFFERS_PROCESSED,&Buffernum);获取到了的话(即 Buffernum>0)->从队列中移除播放结束的buffer,并取得buffer的标志(或者说位置)进行数据填充,操作如下:如果Buffernum>0,说明有空闲或者已播放完的位置,则调用 alSourceUnqueueBuffers(uiSource, 1, &BufferPOS);【BufferPOS为获取到的空闲buffer的地址】,然后在刚获取的这块空闲buffer的地址(BufferPOS)上,放新的数据,alBufferData(BufferPOS, ulFormat, pData, size, samplerate); alSourceQueueBuffers(uiSource, 1, &BufferPOS); 如果长时间获取不到空闲缓存 或者说获取不到已经处理完的个数,那就不能老是卡在这获取,可以设置一定的时间,获取不到空闲缓存,这次播放就算了,或者让上层去决定这次的数据要不要播放,以缓解没有空闲播放缓存的尴尬。
这里可以借鉴:https://blog.csdn.net/little_tan/article/details/17028517
该代码:来自 冬南风的博客
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;
}
首次 先填充数据并启动,后边的(非第一次)就可以循环检测并送数据到声源操作。
播放声道数:albufferData
采集声道数:alcOpenCapDevice