iPhone上的OpenAL音频(转自eggic.com)
来源:互联网 发布:地理信息大数据 编辑:程序博客网 时间:2024/05/01 03:32
iPhone上的OpenAL音频(转自eggic.com)
iPhone上的OpenAL音频
OpenAL是由3个实体构成:Listener(听者),Source(音源)和Buffer(缓存)。
Listener就是你。任何可以被Listener“听到”的声音都是来自扬声器。openAL允许你指定Listener相对于Source的位置,但是本例中我们忽略不计。我们只是针对最基本的静态声音。但是请记住“Listener”的概念,在你处理更复杂的情况时,你可以任意移动此对象。本文中就不做过多介绍。
Source:本质上类似与扬声器,它将产生Listener可以“听”到的声音。像Listener一样,你可以通过移动Source来获得groovy位置效应。本文的示例也没有涉及此部分。
Buffer:就是我们播放的声音。它保存原始的音频数据。
有两个很重要的对象:device(设备)和 context(环境)。Device实际上设播放声音的硬件。而Context是当前所有声音在其中播放的“会话(session)”(你可以将其想象成包括所有sources和listener的房间。或者声音通过其播放的空气,或其他……这就是Context。)
它们在一起是怎么运作的:(最基本)
1) 获取device
2) 将context关联到device
3) 将数据放入buffer
4) 将buffer链接到一个source
5) 播放source
就这么简单! 假定你的openAL实现对于listener是适当的缺省状态而且你不指定listener和source位置,上述流程工作得很好(在iPhone上就是如此)。
好了,我们看看代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// define these somewhere, like in your .h file
ALCcontext* mContext; ALCdevice* mDevice;
// start up openAL
-(void)initOpenAL
{
// Initialization
mDevice = alcOpenDevice(NULL);
// select the "preferred device"
if (mDevice)
{
// use the device to make a context
mContext=alcCreateContext(mDevice,NULL);
// set my context to the currently active one
alcMakeContextCurrent(mContext);
}
}
很容易理解吧。获得“缺省”device,然后用它建立一个Context!完成。
下一步:将数据放入buffer, 这有一点复杂:
首先:打开音频文件
1
2
3
4
// get the full path of the file
NSString* fileName = [[NSBundle mainBundle] pathForResource:@"neatoEffect" ofType:@"caf"];
// first, open the file
AudioFileID fileID = [self openAudioFile:fileName];
等一下!什么是openAudioFile: 方法?
这里就是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// open the audio file
// returns a big audio ID struct
-(AudioFileID)openAudioFile:(NSString*)filePath
{
AudioFileID outAFID;
// use the NSURl instead of a cfurlref cuz it is easier
NSURL * afUrl = [NSURL fileURLWithPath:filePath];
// do some platform specific stuff..
#if TARGET_OS_IPHONE
OSStatus result = AudioFileOpenURL((CFURLRef)afUrl, kAudioFileReadPermission, 0, &outAFID);
#else
OSStatus result = AudioFileOpenURL((CFURLRef)afUrl, fsRdPerm, 0, &outAFID);
#endif
if (result != 0)
NSLog(@"cannot openf file: %@",filePath);
return outAFID;
}
这很简单:我们从主资源包获得文件路径,然后将其传递给本方法,本方法检查运行的平台并使用audio toolkit的方法AudioFileOpenURL()来产生一个AudioFileID。
下面做什么?对,从文件中获取实际音频数据。我们要先计算出有多少数据在文件中:
1
2
// find out how big the actual audio data is
UInt32 fileSize = [self audioFileSize:fileID];
我们需要用到的另一个很实用的方法:
1
2
3
4
5
6
7
8
9
10
11
12
// find the audio portion of the file
// return the size in bytes
-(UInt32)audioFileSize:(AudioFileID)fileDescriptor
{
UInt64 outDataSize = 0;
UInt32 thePropSize = sizeof(UInt64);
OSStatus result = AudioFileGetProperty(fileDescriptor, kAudioFilePropertyAudioDataByteCount, &thePropSize, &outDataSize);
if(result != 0)
NSLog(@"cannot find file size");
return (UInt32)outDataSize;
}
它使用了一个神秘的方法AudioFileGetProperty()来计算出文件中有多少数据并将其放入outDataSize变量。太棒了, 下一步!
现在我们已准备好将数据从文件复制到openAL缓存中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// this is where the audio data will live for the moment
unsigned char * outData = malloc(fileSize);
// this where we actually get the bytes from the file and put them
// into the data buffer
OSStatus result = noErr;
result = AudioFileReadBytes(fileID, false, 0, &fileSize, outData);
AudioFileClose(fileID); //close the file
if (result != 0)
NSLog(@"cannot load effect: %@",fileName);
NSUInteger bufferID;
// grab a buffer ID from openAL
alGenBuffers(1, &bufferID);
// jam the audio data into the new buffer
alBufferData(bufferID,AL_FORMAT_STEREO16,outData,fileSize,44100); // save the buffer so I can release it later
[bufferStorageArray addObject:[NSNumber numberWithUnsignedInteger:bufferID]];
好了,上面我们做了很多事情(实际上,并不多)。分配一下空间给数据,使用audio toolkit中的AudioFileReadBytes()函数从文件中读取字节到分配好的内存块中。然后调用alGenBuffers()产生一个有效的bufferID,再调用alBufferData()加载数据块到openAL buffer的缓存中。
这里我硬编码了格式和频率等数据。如果你是像文章开始介绍的那样使用afconvert命令生成的音频文件,那么你已经知道它们的格式和采样率了。然而,如果你想要支持各种音频格式和频率,你最好要构建一个类似于audioFileSize:的方法,但使用kAudioFilePropertyDataFormat获取格式然后转换为适当的AL_FORMAT,,而获得频率可能更复杂些(译者注:常用的频率无非就是22050,44100,48000几种了)。我太懒了所以只确定我使用的文件格式正确就可以了。
下面我将此ID放入一个NSArray以备参考,你可以以后随时使用。
好,我们现在准备好了缓存区。是将它连到source的时候了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NSUInteger sourceID;
// grab a source ID from openAL
alGenSources(1, &sourceID);
// attach the buffer to the source
alSourcei(sourceID, AL_BUFFER, bufferID);
// set some basic source prefs
alSourcef(sourceID, AL_PITCH, 1.0f);
alSourcef(sourceID, AL_GAIN, 1.0f);
if (loops)
alSourcei(sourceID, AL_LOOPING, AL_TRUE);
// store this for future use
[soundDictionary setObject:[NSNumber numberWithUnsignedInt:sourceID] forKey:@"neatoSound"];
// clean up the buffer if (outData)
{
free(outData);
outData = NULL;
}
像缓存一样,我们也需要从openAL获取一个有效的sourceID。一旦我们获得了sourceID我们就可以将source和缓存联系起来了。最后我们还要进行一些缓存基本设定。如果我们想循环播放,还要设定AL_LOOPING为true。 缺省时,播放是不循环的,所以忽略它就好。然后我将此ID存入到字典数据结构中,以便可以根据名称查找ID。
最后,清除临时内存。
大功即将告成!现在我们只剩下播放功能了:
1
2
3
4
5
6
7
8
9
10
11
// the main method: grab the sound ID from the library
// and start the source playing
- (void)playSound:(NSString*)soundKey
{
NSNumber* numVal = [soundDictionary objectForKey:soundKey];
if (numVal == nil)
return;
NSUInteger sourceID = [numVal unsignedIntValue];
alSourcePlay(sourceID);
}
就是它了, alSourcePlay()…… 很简单吧。如果声音不循环,那么它将会自然停止。如果是循环的,你可能需要停止它:
1
2
3
4
5
6
7
8
9
- (void)stopSound:(NSString*)soundKey
{
NSNumber* numVal = [soundDictionary objectForKey:soundKey];
if (numVal == nil)
return;
NSUInteger sourceID = [numVal unsignedIntValue];
alSourceStop(sourceID);
}
以上基本上就是使用openAL在iPhone播放声音的最快速和简单的方法(至少我是这样认为的)。
最后,我们要做些清理工作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-(void)cleanUpOpenAL:(id)sender
{
// delete the sources
for (NSNumber* sourceNumber in [soundDictionary allValues])
{
NSUInteger sourceID = [sourceNumber unsignedIntegerValue];
alDeleteSources(1, &sourceID);
}
[soundDictionary removeAllObjects];
// delete the buffers
for (NSNumber* bufferNumber in bufferStorageArray)
{
NSUInteger bufferID = [bufferNumber unsignedIntegerValue];
alDeleteBuffers(1, &bufferID);
}
[bufferStorageArray removeAllObjects];
// destroy the context
alcDestroyContext(mContext);
// close the device
alcCloseDevice(mDevice);
}
注意:在实际应用中你可能有不只一个source(我的每个buffer都有一个source,但我只有8组声音所以不会有什么问题)。而可以使用的source数目是有上限的。我不知道iPhone上的实际数字,但可能是16或32之类。(译者注:我有一个应用程序使用了30个source,没有什么问题)。处理此类问题的方法是加载你的缓存,然后动态分配给下一个可用的source(即没有正在进行播放的source)。
- iPhone上的OpenAL音频(转自eggic.com)
- iPhone上的OpenAL音频
- iPhone上的OpenAL音频
- OpenAL系列之一 – iPhone上的OpenAL音频
- iPhone上的OpenAL音频 可播放音频流 音频文件
- iPhone上的OpenAL音频 可播放音频流 音频文件
- android 手机接入点设置与网络状态检查(转自:eggic.com)
- android 手机接入点设置与网络状态检查 (转自:eggic.com)
- 山东现网WEBSMAP页面显示异常案列分析(转自eggic.com)
- 关于OpenAL 音频采集的问题.
- OpenAL播放音频流
- OpenAl音频播放
- Openal播放音频.
- iOS 最简单的OpenAL播放PCM实时音频
- OpenAL 快速入门(无延迟播放音频)
- OpenAL学习笔记(一)---播放音频(*.wav)
- OpenAL学习笔记(二)---播放音频(*.wav)
- 使用openal播放WAV音频
- android 手机接入点设置与网络状态检查(转自:eggic.com)
- Linux下C编程 进程通信 (IPC)
- 插件3:友好的文本
- 简单工厂与策略模式的区别
- hdu 1718 Rank
- iPhone上的OpenAL音频(转自eggic.com)
- 算法导论之插入算法JAVA实现
- The Hadoop Distributed File System
- 插件4:删除空格
- ASCII 码的规律技巧
- MFC笔记——消息响应
- msgsnd与msgrcv函数出现invalid参数的问题
- php file_get_contents
- 做个有责任感的人