iPhone上的OpenAL音频

iPhone上的OpenAL音频

iPhone上的OpenAL音频 OpenAL是由3个实体构成:Listener(听者),Source(音源)和Buffer(缓存)。 Listener就是你。任何可以被Listener听到的声音都是来自扬声器。openAL允许你指定Listener相对于

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

 

 

    // definethese somewhere, like in your .hfile  
    ALCcontext*mContext;  ALCdevice*mDevice;  
    // start upopenAL  
   -(void)initOpenAL  
    
       //Initialization   
       mDevice =alcOpenDevice(NULL);

       // select the"preferred device"   
       if(mDevice)
           
          // use thedevice to make a context       
         mContext=alcCreateContext(mDevice,NULL);      
          // set mycontext to the currently active one     
         alcMakeContextCurrent(mContext);  
        
    }

 

 

 

 

 

很容易理解吧。获得“缺省”device,然后用它建立一个Context!完成。

 

 

 

 

 

下一步:将数据放入buffer, 这有一点复杂:

 

 

 

 

 

首先:打开音频文件

 

 

1
2
3
4

 

 

  // get thefull path of the file  
  NSString* fileName= [[NSBundlemainBundle] pathForResource:@"neatoEffect"ofType:@"caf"];  
  // first, open thefile  
  AudioFileID fileID = [selfopenAudioFile: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 IDstruct  
-(AudioFileID)openAudioFile:(NSString*)filePath  
 
    AudioFileIDoutAFID;   
    // use the NSURl instead of acfurlref cuz it is easier    
    NSURL * afUrl =[NSURLfileURLWithPath:filePath];
    // do some platform specificstuff..  
#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(@"cannotopenf file: %@",filePath);  
   
    returnoutAFID;  
}

 

 

 

 

 

这很简单:我们从主资源包获得文件路径,然后将其传递给本方法,本方法检查运行的平台并使用audiotoolkit的方法AudioFileOpenURL()来产生一个AudioFileID。

 

 

 

 

 

下面做什么?对,从文件中获取实际音频数据。我们要先计算出有多少数据在文件中:

 

 

1
2

 

 

    // find outhow big the actual audio datais  
    UInt32 fileSize = [selfaudioFileSize:fileID];

 

 

我们需要用到的另一个很实用的方法:

 

 

1
2
3
4
5
6
7
8
9
10
11
12

 

 

//find the audio portion of thefile  
// return the size inbytes  
-(UInt32)audioFileSize:(AudioFileID)fileDescriptor  
 
    UInt64 outDataSize =0;    
    UInt32 thePropSize = sizeof(UInt64);  
    OSStatus result =AudioFileGetProperty(fileDescriptor,kAudioFilePropertyAudioDataByteCount, &thePropSize,&outDataSize);  
    if(result != 0)
       NSLog(@"cannotfind 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 themoment  
unsigned char * outData = malloc(fileSize);
// this where we actually get the bytes from the file and putthem  
// into the data buffer  
OSStatus result = noErr;  
result = AudioFileReadBytes(fileID, false, 0,&fileSize,outData);  
AudioFileClose(fileID);   //closethe file

if (result != 0)
    NSLog(@"cannot load effect:%@",fileName);  
   
NSUInteger bufferID;  
// grab a buffer ID fromopenAL  
alGenBuffers(1, &bufferID);  
// jam the audio data into the newbuffer  
alBufferData(bufferID,AL_FORMAT_STEREO16,outData,fileSize,44100);   // save the buffer so I can release itlater  
[bufferStorageArray addObject:[NSNumbernumberWithUnsignedInteger:bufferID]];

 

 

 

 

 

好了,上面我们做了很多事情(实际上,并不多)。 分配一下空间给数据,使用audiotoolkit中的AudioFileReadBytes()函数从文件中读取字节到分配好的内存块中。然后调用alGenBuffers()产生一个有效的bufferID,再调用alBufferData()加载数据块到openALbuffer的缓存中。

 

 

 

 

 

这里我硬编码了格式和频率等数据。如果你是像文章开始介绍的那样使用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 fromopenAL  
alGenSources(1, &sourceID);  
// attach the buffer to thesource  
alSourcei(sourceID, AL_BUFFER,bufferID);  
// set some basic sourceprefs  
alSourcef(sourceID, AL_PITCH,1.0f);  
alSourcef(sourceID, AL_GAIN,1.0f);  
if (loops)
    alSourcei(sourceID,AL_LOOPING, AL_TRUE);  

// store this for futureuse  
[soundDictionary setObject:[NSNumbernumberWithUnsignedInt: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 thelibrary  
// and start the sourceplaying  
- (void)playSound:(NSString*)soundKey  
 
    NSNumber* numVal =[soundDictionary objectForKey:soundKey];    
    if (numVal == nil)
       return;    
   
    NSUInteger sourceID = [numValunsignedIntValue];   
   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 = [numValunsignedIntValue];
   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 thesources   
    for (NSNumber*sourceNumber in [soundDictionary allValues])
       
       NSUIntegersourceID = [sourceNumber unsignedIntegerValue];       
       alDeleteSources(1,&sourceID);     
    
   
    [soundDictionaryremoveAllObjects];  
    // delete thebuffers   
    for (NSNumber*bufferNumber in bufferStorageArray)
       
       NSUIntegerbufferID = [bufferNumber unsignedIntegerValue];       
       alDeleteBuffers(1,&bufferID);     
    
    [bufferStorageArrayremoveAllObjects];  
   
    // destroy thecontext     
   alcDestroyContext(mContext);  
    // close thedevice    
   alcCloseDevice(mDevice);  
}

 

 

 

 

 

注意:在实际应用中你可能有不只一个source(我的每个buffer都有一个source,但我只有8组声音所以不会有什么问题)。而可以使用的source数目是有上限的。我不知道iPhone上的实际数字,但可能是16或32之类。(译者注:我有一个应用程序使用了30个source,没有什么问题)。处理此类问题的方法是加载你的缓存,然后动态分配给下一个可用的source(即没有正在进行播放的source)。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值