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才起作用。

举例:在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[iLoop]),缓冲加到数据源上去。

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; }

首次 先填充数据并启动,后边的(非第一次)就可以循环检测并送数据到声源操作。

整个播放过程可以检测声源的状态,以保证他在开启后是一个正在播放的状态。



猜你喜欢

转载自www.cnblogs.com/8335IT/p/12305662.html