iOS移动设备进行音频播放时,可以使用openal

注意:openal 默认开启的是手机听筒(有多个播放设备 :alcopendevice(null)),如果想让声音通过扬声器进行播放,可通过audiosession的audiosessionsetproperty进行设置:kaudiosessionproperty_overridecategorydefaulttospeaker,此时声音会即从扬声器出来,也从听筒出来。

使用openal播放声音的步骤:

1 alcopendevice(null)-----得到设备D

获取设备可以通过打开一个null直接打开听筒,也可以枚举后,选择一个使用。如下:

ios播放声音权限 苹果设备播放声音_数据

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

————————————————
版权声明:本段代码转载自「老衲不出家」

ios播放声音权限 苹果设备播放声音_数据

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, 采样率);将数据装载到缓存上去,只有一个就装一个,多个就循环装载

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); 如果长时间获取不到空闲缓存 或者说获取不到已经处理完的个数,那就不能老是卡在这获取,可以设置一定的时间,获取不到空闲缓存,这次播放就算了,或者让上层去决定这次的数据要不要播放,以缓解没有空闲播放缓存的尴尬。

这里可以借鉴:

ios播放声音权限 苹果设备播放声音_数据

该代码:来自 冬南风的博客
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;
}

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

ios播放声音权限 苹果设备播放声音_数据

播放声道数:albufferData

采集声道数:alcOpenCapDevice