博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
openal播放裸数据
阅读量:4183 次
发布时间:2019-05-26

本文共 3963 字,大约阅读时间需要 13 分钟。

OpenAL作为音效库,并不包含编解码的功能,所以在使用OpenAL的时候,需要我们预先将音频数据解码成PCM格式裸数据,然后才能由OpenAL进行播放。这里我们的Demo演示了用OpenAL播放一个WAV文件的功能:

player_demo

点击播放按钮后开始播放,再次点击,暂停播放。

Demo的代码参见。

OpenAL设计的场景框架

OpenAL构建的音频场景是有一个或者多个的声源(Source)和一个或者多个接受端听众(Listener)来架构的,通过改变声源和收听者的物理位置,从而实现3+1、5+1、N+1等3D音频效果。

openal_scene

听众作为接受端,只要设置其物理位置就可以了。而作为声源是从哪里获得音频数据呢?答案是Buffer,通过Buffer将数据输送给数据源,从而进行播放。

具体的播放动作,还需要使用物理设备,所以声源和听众都是基于设备的。那如何组织声源和听众呢?OpenAL又抽象出一个Context的概念,一个Context可以认为是在一个具体的设备上(比如iPhone的扬声器)上的一个声音场景。所以最后的描述就是:

OpenAL 声源场景:在一个物理设备(Device)上通过若干个声音数据的Buffer和若干个声源(Source)以及若干个听众(Listener)组成的一个个发声场景(Context)

openal_struct

Device

在使用OpenAL之前,首先我们需要获得硬件设备,在iOS上,因为只能进行播放,所以也就只需要关心播放就可以了。标准的OpenAL提供了一个罗列所有设备的方法:

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

首先调用alcIsExtensionPresent这个方法判断当前的“OpenAL实现”是否支持后面的枚举操作,当然iOS是支持的,这里为了上下文完整也就加上了。然后调用 alcGetString获得类似"Default Speaker Microphone"类似这样的字符串,进行遍历逐个输出。

在iOS上的结果是只有一个“Default”的设备:

Default Audio Device

所以,在iOS上使用的时候,我们只需要调用:

defaultDevice = alcOpenDevice(NULL);

传入“NULL” 从而打开那个唯一的默认设备即可。

打开了设备,根据上面说的场景,我们需要构建一个播放声音的上下文场景:

mainContext = alcCreateContext(defaultDevice, NULL);if (NULL == defaultDevice) {    return NO;}if( !alcMakeContextCurrent(mainContext) ) {    return NO;}

首先调用alcCreateContext用上面的Device创建一个Context,然后在调用alcMakeContextCurrent将这个Context激活为当前的上下文。

出错处理

这里看OpenAL的API,还是比较类似C的风格,设备和上下文的接口通过判断返回值是否为NULl是可以判断调用是否成功的,但是OpenAL的API中,除了这种返回对象指针的接口,还有大量的无返回值的接口,这样的接口要如何判断成功与否呢?OpenAL提供了类似检查errno的方式来检查:

if (alGetError() != AL_NO_ERROR) {    return NO;

} 通过alGetError ()获得类似errno的值,然后进行判断:

错误码|值|意义 AL_INVALID_NAME|0xA001| 参数错误 AL_INVALID_ENUM|0xA002| 枚举参数错误 AL_INVALID_VALUE|0xA003 | 枚举值错误 AL_INVALID_OPERATION|0xA004|不适合的操作 AL_OUT_OF_MEMORY |0xA005| 内存越界

Buffer

接着我们要从WAV中读取PCM数据来填充到Buffer中供Source声源使用。Demo中我们借助了我们自己实现的WAV解析工具,从WAV中解析出来具体的PCM数据、采样率以及数据位深度值。

alGenBuffers(1, &buffers); // only one bufferif (alGetError() != AL_NO_ERROR) {    return NO;}

首先调用 alGenBuffers生成一个buffer对象,并检查错误,然后调用我们自己的WAV解析函数:

int load_wav_file(const char *file, void **data, size_t *size, size_t *freq)

获得WAV文件的PCM内容以及采样率。接着调用:

alBufferData(buffers, AL_FORMAT_MONO16, data, size, freq);ALenum error;if ((error=alGetError()) != AL_NO_ERROR) {    NSLog(@"alBufferData error");}

alBufferData将PCM数据装载到buffer中。

Source

设置好了音频数据Buffer以后,我们创建一个声源并为其装载上数据:

alGenSources(1, &sources); // only one sourceif (alGetError() != AL_NO_ERROR) {    return NO;}

这里调用 alGenSources 创建一个Source对象。接着调用:

alSourcei(sources, AL_BUFFER, buffers);if (alGetError() != AL_NO_ERROR) {    return NO;}

将Buffer绑定到Source之上。

Listener

默认Context情况下,OpenAL就已经提供了一个一个Listener,为了最简单跑通流程,我们可以先不用关心这个,直接使用默认对象。

播放和暂停

一切都准备好后,我们就可以开始进行播放了:

alSourcePlay(sources);ALenum error;if ((error = alGetError() )!= AL_NO_ERROR) {    NSLog(@"play error with %d", error);    return;}

通过调用 alSourcePlay 播放指定的声源。同样的,通过:

alSourceStop(sources);ALenum error;if ((error = alGetError() )!= AL_NO_ERROR) {    NSLog(@"stop error with %d", error);    return;}

调用alSourceStop停止指定声源的播放。

资源回收

最后因为OpenAL提供的是C接口,而不是OC接口,不能依赖ARC进行内存管理。所以当使用完之后还需要调用:

alDeleteSources(1, &sources);alDeleteBuffers(1, &buffers);ALCdevice *device = alcGetContextsDevice(mainContext);alcMakeContextCurrent(NULL);alcDestroyContext(mainContext);alcCloseDevice(device);

依次回收Source/Buffer/Context并关闭Device。

总结

这里通过一个最简单的例子,演示了OpenAL的设计模式以及使用方法。通过播放一段WAV文件,熟悉了OpenAL提供的Device/Buffer/Source/Listener这几个关键概念。虽然可以播放声音了,但是OpenAL的功能远不如此,通过设置Source和Listerner的位置、移动等属性,我们可以营造一个非常丰富的3D音效效果。

+

参考

转载地址:http://kjzoi.baihongyu.com/

你可能感兴趣的文章
Spring Data JPA |自定义非实体类的映射
查看>>
SpringBoot | 常用注解记录
查看>>
JavaBean对象转换EntityUtils工具类
查看>>
Maven常用命令
查看>>
SpringBoot | 运行报错,无法加载oracle连接驱动
查看>>
为什么阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作
查看>>
AWS EC2如何从普通用户切换为root用户
查看>>
click方法不生效的
查看>>
mysql排行榜并列与不并列
查看>>
SpringBoot | Mybatis申明为Mapper文件
查看>>
JPA主键生成策略
查看>>
byte数组和InputStream的相互转换
查看>>
InputStream,InputStreamReader和Reader之间的区别与关系
查看>>
Java中System.arraycopy方法的使用
查看>>
tk.mybatis的使用记录
查看>>
遍历获取目录下的所有文件
查看>>
从指定服务器路径下载文件
查看>>
EasyExcel读取和写入java model数据
查看>>
《C编译原理》共享库的动态加载和静态加载
查看>>
《Android系统学习》第二章:如何制作OTA U盘升级包
查看>>