今天我只讨论不到30秒的音频以及音效和短循环音。在iPhone上播放的音频需要正确的格式(或者说audiotoolbox可以处理的多种音频格式中的一种,但如果你从正确的格式声音开始,iPhone在播放时就不需要进行处理了)。
所以,打开你的命令行终端输入:
/usr/bin/afconvert-fcaff-dLEI16
inputSoundFile.aiffoutputSoundFile.caf你可能会问这到底是在干什么?这是将文件转换为Little-Endian(低地址低字节)16位,采样率44,的格式。通常存储为.caf。
好了!我们现在有了正确格式的.caf文件,可以开始做些什么了。
有多种方法在iPhone上播放音频,有一种’简单’的方式,还有有一些’难’的方法……我先快速地讨论一下简单的方式,然后再讨论一下使用OpenAL的’难的方法’。
让iPhone发声的最快(最容易)的方法是使用音频系统服务:
NSString*path=[[NSBundlemainBundle]pathForResource:
"soundEffect1"ofType:"caf"];NSURL*afUrl=[NSURLfileURLWithPath:path];UInt32soundID;AudioServicesCreateSystemSoundID((CFURLRef)afUrl,soundID);AudioServicesPlaySystemSound(soundID);这对于象点击按钮和简单UI互动之类的任务已经足够好用。但是,对于任何更复杂一点的任务(比如:游戏)而言,这简直是毫无用处。它确实会立即开始播放,但若要指定的音效与游戏的特定帧相配合的话,这种方法基本上是没有用处。(我确实曾经使用上述方法实现过我的声音引擎,然而当我将其加载到iPhone播放声音时,要么声音会晚许多帧,要么干脆整个程序会停顿,等待audiotoolbox将音频加载到缓存中,实在太糟糕了)。
为了更好地控制音频,你需要使用OpenAL,audioUnits或audioQueue。
我决定使用OpenAL以便我的代码具有更好的跨平台能力,并且通过学习怎样使用OpenAL,我还可以将这些技术运用在iPhone外的其他平台。(而且象我这样的代码雇佣兵,我觉得具有OpenAL经验比audioQueue经验更有市场)(另外由于我已经熟悉了openGL,openAL十分类似,而audiounits和audioqueue的代码有那么点丑陋)。
所以,这是个超快速的openAL教程,但绝对是实现由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上就是如此)。
好了,我们看看代码:
//definethesesomewhere,likeinyour.hfileALCcontext*mContext;ALCdevice*mDevice;//startupopenAL-(void)initOpenAL{//InitializationmDevice=alcOpenDevice(NULL);//selectthe"preferreddevice"if(mDevice){//usethedevicetomakeacontextmContext=alcCreateContext(mDevice,NULL);//setmycontexttothecurrentlyactiveonealcMakeContextCurrent(mContext);}}
很容易理解吧。获得“缺省”device,然后用它建立一个Context!完成。
下一步:将数据放入buffer,这有一点复杂:
首先:打开音频文件
//getthefullpathofthefileNSString*fileName=[[NSBundlemainBundle]pathForResource:
"neatoEffect"ofType:"caf"];//first,openthefileAudioFileIDfileID=[selfopenAudioFile:fileName];等一下!什么是openAudioFile:方法?
这里就是:
//opentheaudiofile//returnsabigaudioIDstruct-(AudioFileID)openAudioFile:(NSString*)filePath{AudioFileIDoutAFID;//usetheNSURlinsteadofacfurlrefcuzitiseasierNSURL*afUrl=[NSURLfileURLWithPath:filePath];//dosomeplatformspecificstuff..#ifTARGET_OS_IPHONEOSStatusresult=AudioFileOpenURL((CFURLRef)afUrl,kAudioFileReadPermission,0,outAFID);#elseOSStatusresult=AudioFileOpenURL((CFURLRef)afUrl,fsRdPerm,0,outAFID);#endifif(result!=0)NSLog(
"cannotopenffile:%",filePath);returnoutAFID;}这很简单:我们从主资源包获得文件路径,然后将其传递给本方法,本方法检查运行的平台并使用audiotoolkit的方法AudioFileOpenURL()来产生一个AudioFileID。
下面做什么?对,从文件中获取实际音频数据。我们要先计算出有多少数据在文件中:
//findouthowbigtheactualaudiodataisUInt32fileSize=[selfaudioFileSize:fileID];我们需要用到的另一个很实用的方法://findtheaudioportionofthefile//returnthesizeinbytes-(UInt32)audioFileSize:(AudioFileID)fileDescriptor{UInt64outDataSize=0;UInt32thePropSize=sizeof(UInt64);OSStatusresult=AudioFileGetProperty(fileDescriptor,kAudioFilePropertyAudioDataByteCount,thePropSize,outDataSize);if(result!=0)NSLog(
"cannotfindfilesize");return(UInt32)outDataSize;}它使用了一个神秘的方法AudioFileGetProperty()来计算出文件中有多少数据并将其放入outDataSize变量。太棒了,下一步!
现在我们已准备好将数据从文件复制到openAL缓存中:
//thisiswheretheaudiodatawillliveforthemomentunsignedchar*outData=malloc(fileSize);//thiswhereweactuallygetthebytesfromthefileandputthem//intothedatabufferOSStatusresult=noErr;result=AudioFileReadBytes(fileID,false,0,fileSize,outData);//这一步要关闭文件AudioFileClose(fileID);//closethefileif(result!=0)NSLog(
"cannotloadeffect:%",fileName);NSUIntegerbufferID;//这是为了生成有效的bufferID//grababufferIDfromopenALalGenBuffers(1,bufferID);//jamtheaudiodataintothenewbufferalBufferData(bufferID,AL_FORMAT_STEREO16,outData,fileSize,);//这里是频率,savethebuffersoIcanreleaseitlater[bufferStorageArrayaddObject:[NSNumbernumberWithUnsignedInteger:bufferID]];好了,上面我们做了很多事情(实际上,并不多)。分配一下空间给数据,使用audiotoolkit中的AudioFileReadBytes()函数从文件中读取字节到分配好的内存块中。然后调用alGenBuffers()产生一个有效的bufferID,再调用alBufferData()加载数据块到openALbuffer的缓存中。
这里我硬编码了格式和频率等数据。如果你是像文章开始介绍的那样使用afconvert命令生成的音频文件,那么你已经知道它们的格式和采样率了。然而,如果你想要支持各种音频格式和频率,你最好要构建一个类似于audioFileSize:的方法,但使用kAudioFilePropertyDataFormat获取格式然后转换为适当的AL_FORMAT,,而获得频率可能更复杂些(译者注:常用的频率无非就是,,几种了)。我太懒了所以只确定我使用的文件格式正确就可以了。下面我将此ID放入一个NSArray以备参考,你可以以后随时使用。
好,我们现在准备好了缓存区。是将它连到source的时候了。
NSUIntegersourceID;//grabasourceIDfromopenALalGenSources(1,sourceID);//attachthebuffertothesourcealSourcei(sourceID,AL_BUFFER,bufferID);//setsomebasicsourceprefsalSourcef(sourceID,AL_PITCH,1.0f);alSourcef(sourceID,AL_GAIN,1.0f);if(loops)alSourcei(sourceID,AL_LOOPING,AL_TRUE);//storethisforfutureuse[soundDictionarysetObject:[NSNumbernumberWithUnsignedInt:sourceID]forKey:
"neatoSound"];//cleanupthebufferif(outData){free(outData);outData=NULL;}像缓存一样,我们也需要从openAL获取一个有效的sourceID。一旦我们获得了sourceID我们就可以将source和缓存联系起来了。最后我们还要进行一些缓存基本设定。如果我们想循环播放,还要设定AL_LOOPING为true。缺省时,播放是不循环的,所以忽略它就好。然后我将此ID存入到字典数据结构中,以便可以根据名称查找ID。
最后,清除临时内存。
大功即将告成!现在我们只剩下播放功能了:
//themainmethod:grabthesoundIDfromthelibrary//andstartthesourceplaying-(void)playSound:(NSString*)soundKey{NSNumber*numVal=[soundDictionaryobjectForKey:soundKey];if(numVal==nil)return;NSUIntegersourceID=[numValunsignedIntValue];alSourcePlay(sourceID);}
就是它了,alSourcePlay()……很简单吧。如果声音不循环,那么它将会自然停止。如果是循环的,你可能需要停止它:
-(void)stopSound:(NSString*)soundKey{NSNumber*numVal=[soundDictionaryobjectForKey:soundKey];if(numVal==nil)return;NSUIntegersourceID=[numValunsignedIntValue];alSourceStop(sourceID);}
以上基本上就是使用openAL在iPhone播放声音的最快速和简单的方法(至少我是这样认为的)。
最后,我们要做些清理工作:
-(void)cleanUpOpenAL:(id)sender{//deletethesourcesfor(NSNumber*sourceNumberin[soundDictionaryallValues]){NSUIntegersourceID=[sourceNumberunsignedIntegerValue];alDeleteSources(1,sourceID);}[soundDictionaryremoveAllObjects];//deletethebuffersfor(NSNumber*bufferNumberinbufferStorageArray){NSUIntegerbufferID=[bufferNumberunsignedIntegerValue];alDeleteBuffers(1,bufferID);}[bufferStorageArrayremoveAllObjects];//destroythecontextalcDestroyContext(mContext);//closethedevicealcCloseDevice(mDevice);}
注意:在实际应用中你可能有不只一个source(我的每个buffer都有一个source,但我只有8组声音所以不会有什么问题)。而可以使用的source数目是有上限的。我不知道iPhone上的实际数字,但可能是16或32之类。(译者注:我有一个应用程序使用了30个source,没有什么问题)。处理此类问题的方法是加载你的缓存,然后动态分配给下一个可用的source(即没有正在进行播放的source)。
北京白癜风医院地址北京治疗白癜风的医院在哪