600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > IOS使用OpenAL播放音频文件

IOS使用OpenAL播放音频文件

时间:2022-03-10 03:49:00

相关推荐

IOS使用OpenAL播放音频文件

本文介绍以下几点内容:

OpenAL API的使用介绍从IOS的mainBundle读取载入音频文件OpenAL结合平台音频解析类AudioToolbox实现播放声音遇到和解决的问题 首先,主要参考了,IOS开发官网的两个demo, OpenALExample 和 GLAirplay。这里我们只谈最基本的实现,加载声音文件,播放声音。至于3D音效,多普勒效应环境音效设置,声音位置,收听位置等都不进行配置。

第一,需要导入的平台头文件。

#include <stddef.h>#include <Foundation/Foundation.h>#include <AudioToolbox/AudioToolbox.h>#include <OpenAL/OpenAL.h>

文件需要使用.m文件,因为需要使用Foundation.h的功能来加载Bundle的声音文件。m后缀文件是c和objc混编的文件类型。AudioToolbox可以对音频文件信息的解析和设置,以配合OpenAL的使用。

第二,初始化OpenAL

static ALCdevice*device = NULL;static ALCcontext*context= NULL;static alBufferDataStaticProcPtr alBufferDataStaticProc = NULL;struct AudioPlayer{ALuint sourceId;ALuint bufferId;};static void Init(){// get static buffer data APIalBufferDataStaticProc = (alBufferDataStaticProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alBufferDataStatic");// create a new OpenAL Device// pass NULL to specify the system’s default output devicedevice = alcOpenDevice(NULL);if (device != NULL){// create a new OpenAL Context// the new context will render to the OpenAL Device just createdcontext = alcCreateContext(device, 0);if (context != NULL){// make the new context the Current OpenAL ContextalcMakeContextCurrent(context);}}else{ALogE("Audio Init failed, OpenAL can not open device");}// clear any errorsalGetError();}

OpenAL全局只需要一个ALCdevice和ALCcontext。我们抽象了一个AudioPlayer,用来对应一个播放器,bufferId就是加载到内存的音频数据,sourceId是对应OpenAL播放器。alBufferDataStatic是OpenAL的一个扩展,相对于alBufferData来说的。功能是加载音频数据到内存并关联到bufferId。只不过,alBufferData会拷贝音频数据所以调用后,我们可以free掉音频数据。而alBufferDataStatic并不会拷贝,所以音频数据data我们要一直保留并自己管理。

第三,我们需要加载声音文件,解析音频数据,修改音频数据格式为OpenAL需要的,获取最终的可以传递给OpenAL使用的音频数据。这几步封装了一个函数,先解释在看完整的代码。 首先我们要获取Bundle的文件路径。然后,利用AudioToolBox的功能来读取并解析这个数据。OpenAL加载数据到Buffer,需要音频的采样频率,通道数,码率,数据大小等信息。接着,OpenAL只能播放特定格式和属性的音频文件。再次使用AudioToolBox的功能来对音频数据进行设置,以达到需求。最后,把处理好的数据和信息返回。

static inline void* GetAudioData(char* filePath, ALsizei* outDataSize, ALenum* outDataFormat, ALsizei* outSampleRate){AudioStreamBasicDescriptionfileFormat;AudioStreamBasicDescriptionoutputFormat;SInt64fileLengthInFrames = 0;UInt32propertySize = sizeof(fileFormat);ExtAudioFileRef audioFileRef = NULL;void*data= NULL;NSString* path= [[NSBundle mainBundle] pathForResource:[NSString stringWithUTF8String:filePath] ofType:nil];CFURLReffileUrl = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef) path, NULL);OSStatus error = ExtAudioFileOpenURL(fileUrl, &audioFileRef);CFRelease(fileUrl);if (error != noErr){ALogE("Audio GetAudioData ExtAudioFileOpenURL failed, error = %x, filePath = %s", (int) error, filePath);goto label_exit;}// get the audio data formaterror = ExtAudioFileGetProperty(audioFileRef, kExtAudioFileProperty_FileDataFormat, &propertySize, &fileFormat);if (error != noErr){ALogE("Audio GetAudioData ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) failed, error = %x, filePath = %s", (int) error, filePath);goto label_exit;}if (fileFormat.mChannelsPerFrame > 2){ALogE("Audio GetAudioData unsupported format, channel count = %u is greater than stereo, filePath = %s", fileFormat.mChannelsPerFrame, filePath);goto label_exit;}// set the client format to 16 bit signed integer (native-endian) data// maintain the channel count and sample rate of the original source formatoutputFormat.mSampleRate = fileFormat.mSampleRate;outputFormat.mChannelsPerFrame = fileFormat.mChannelsPerFrame;outputFormat.mFormatID = kAudioFormatLinearPCM;outputFormat.mBytesPerPacket = outputFormat.mChannelsPerFrame * 2;outputFormat.mFramesPerPacket = 1;outputFormat.mBytesPerFrame = outputFormat.mChannelsPerFrame * 2;outputFormat.mBitsPerChannel = 16;outputFormat.mFormatFlags= kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;// set the desired client (output) data formaterror = ExtAudioFileSetProperty(audioFileRef, kExtAudioFileProperty_ClientDataFormat, sizeof(outputFormat), &outputFormat);if(error != noErr){ALogE("Audio GetAudioData ExtAudioFileSetProperty(kExtAudioFileProperty_ClientDataFormat) failed, error = %x, filePath = %s", (int) error, filePath);goto label_exit;}// get the total frame countpropertySize = sizeof(fileLengthInFrames);error = ExtAudioFileGetProperty(audioFileRef, kExtAudioFileProperty_FileLengthFrames, &propertySize, &fileLengthInFrames);if(error != noErr){ALogE("Audio GetAudioData ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) failed, error = %x, filePath = %s", (int) error, filePath);goto label_exit;}//--------------------------------------------------------------------------------------------------// read all the data into memoryUInt32 framesToRead = (UInt32) fileLengthInFrames;UInt32 dataSize= framesToRead * outputFormat.mBytesPerFrame;*outDataSize = (ALsizei) dataSize;*outDataFormat= outputFormat.mChannelsPerFrame > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;*outSampleRate= (ALsizei) outputFormat.mSampleRate;int index = AArrayStrMap->GetIndex(fileDataMap, filePath);if (index < 0){data = malloc(dataSize);if (data != NULL){AudioBufferListdataBuffer;dataBuffer.mNumberBuffers = 1;dataBuffer.mBuffers[0].mDataByteSize = dataSize;dataBuffer.mBuffers[0].mNumberChannels = outputFormat.mChannelsPerFrame;dataBuffer.mBuffers[0].mData = data;// read the data into an AudioBufferListerror = ExtAudioFileRead(audioFileRef, &framesToRead, &dataBuffer);if(error != noErr){free(data);data = NULL; // make sure to return NULLALogE("Audio GetAudioData ExtAudioFileRead failed, error = %x, filePath = %s", (int) error, filePath);goto label_exit;}}AArrayStrMapInsertAt(fileDataMap, filePath, -index - 1, data);}else{data = AArrayStrMapGetAt(fileDataMap, index, void*);}label_exit:// dispose the ExtAudioFileRef, it is no longer neededif (audioFileRef != 0){ExtAudioFileDispose(audioFileRef);}return data;}

这里,我使用了ArrayStrMap结构其实就是一个dictionary,用文件路径缓存了最终的data文件。因为,我会使用alBufferDataStatic,所以最终的data文件由我自己管理。并且同一个音频文件数据总是相同的,我就不再去频繁的free在malloc了。

outputFormat就是我们需要的音频格式,使用ExtAudioFileSetProperty能够让我们把原音频数据格式进行转换。这样,我们就可以使用各种音频文件格式来播放了,比如mp3,wav等等。

第四,利用音频文件数据,生成我们的播放器对象。

static inline void InitPlayer(char* filePath, AudioPlayer* player){ALenum error;ALsizei size;ALenum format;ALsizei freq;void* data = GetAudioData(filePath, &size, &format, &freq);if ((error = alGetError()) != AL_NO_ERROR){ALogE("Audio InitPlayer failed, error = %x, filePath = %s", error, filePath);}alGenBuffers(1, &player->bufferId);if((error = alGetError()) != AL_NO_ERROR){ALogE("Audio InitPlayer generate buffer failed, error = %x, filePath = %s", error, filePath);}// use the static buffer data API// the data will not copy in buffer so can not free data until buffer deletedalBufferDataStaticProc(player->bufferId, format, data, size, freq);if((error = alGetError()) != AL_NO_ERROR){ALogE("Audio InitPlayer attach audio data to buffer failed, error = %x, filePath = %s", error, filePath);}//--------------------------------------------------------------------------------------------------alGenSources(1, &player->sourceId);if((error = alGetError())!= AL_NO_ERROR){ALogE("Audio InitPlayer generate source failed, error = %x, filePath = %s", error, filePath);}// turn Looping offalSourcei(player->sourceId, AL_LOOPING, AL_FALSE);// set Source PositionalSourcefv(player->sourceId, AL_POSITION,(const ALfloat[]) {0.0f, 0.0f, 0.0f});// set source reference distancealSourcef(player->sourceId, AL_REFERENCE_DISTANCE, 0.0f);// attach OpenAL buffer to OpenAL SourcealSourcei(player->sourceId, AL_BUFFER, player->bufferId);if((error = alGetError()) != AL_NO_ERROR){ALogE("Audio InitPlayer attach buffer to source failed, error = %x, filePath = %s", error, filePath);}}

首先,生成bufferId,sourceId。然后,把音频数据关联到bufferId,把bufferId关联到sourceId。最后,sourceId代表就是OpenAL的播放器,可以设置各种属性。那么,当我们需要销毁播放器的时候,主要也就是销毁sourceId,和bufferId。

第五,设置播放器的各种属性。

static void SetLoop(AudioPlayer* player, bool isLoop){ALint isLoopEnabled;alGetSourcei(player->sourceId, AL_LOOPING, &isLoopEnabled);if (isLoopEnabled == isLoop){return;}alSourcei(player->sourceId, AL_LOOPING, (ALint) isLoop);}static void SetVolume(AudioPlayer* player, int volume){ALogA(volume >= 0 && volume <= 100, "Audio SetVolume volume %d not in [0, 100]", volume);alSourcef(player->sourceId, AL_GAIN, volume / 100.0f);ALenum error = alGetError();if(error != AL_NO_ERROR){ALogE("Audio SetVolume error = %x", error);}}static void SetPlay(AudioPlayer* player){alSourcePlay(player->sourceId);ALenum error = alGetError();if(error != AL_NO_ERROR){ALogE("Audio SetPlay error = %x", error);}}static void SetPause(AudioPlayer* player){alSourcePause(player->sourceId);ALenum error = alGetError();if(error != AL_NO_ERROR){ALogE("Audio SetPause error = %x", error);}}static bool IsPlaying(AudioPlayer* player){ALint state;alGetSourcei(player->sourceId, AL_SOURCE_STATE, &state);return state == AL_PLAYING;}

第六,OpenAL并没有播放完成的回调。随意我们需要在Update中不断的检测播放器的状态。如果不是循环播放的声音,我们就可以删除它,也可以回调给应用程序做其它操作。

static void Update(float deltaSeconds){for (int i = destroyList->size - 1; i > -1; i--){AudioPlayer* player = AArrayListGet(destroyList, i, AudioPlayer*);ALint state;alGetSourcei(player->sourceId, AL_SOURCE_STATE, &state);if (state == AL_STOPPED){alDeleteSources(1, &player->sourceId);alDeleteBuffers(1, &player->bufferId);AArrayList->Remove(destroyList, i);AArrayListAdd(cacheList, player);}}}

因为,我使用了alBufferDataStatic,并且缓存音频数据,所以这里只是删除播放器的关联id,并没有删除音频数据。再次播放同一个音频的时候继续使用。OpenAL通常一共只可以申请32个播放器。所以播放器用完还是及时的删除比较好。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。