几种播放音频文件的方式(十三) —— OpenAL框架之分步解析
Posted YZFHKMS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了几种播放音频文件的方式(十三) —— OpenAL框架之分步解析相关的知识,希望对你有一定的参考价值。
https://blog.csdn.net/irainsa/article/details/129560569
1、版本记录
版本号 时间
V1.0 2017.12.29
2、前言
ios系统中有很多方式可以播放音频文件,这里我们就详细的说明下播放音乐文件的原理和实例。感兴趣的可以看我写的上面几篇。 1. 几种播放音频文件的方式(一) —— 播放本地音乐 2. 几种播放音频文件的方式(二) —— 音效播放 3. 几种播放音频文件的方式(三) —— 网络音乐播放 4. 几种播放音频文件的方式(四) —— 音频队列服务(Audio Queue Services)(一) 5. 几种播放音频文件的方式(五) —— 音频队列服务(Audio Queue Services)简介(二) 6. 几种播放音频文件的方式(六) —— 音频队列服务(Audio Queue Services)之关于音频队列(三) 7. 几种播放音频文件的方式(七) —— 音频队列服务(Audio Queue Services)之录制音频(四) 8. 几种播放音频文件的方式(八) —— 音频队列服务(Audio Queue Services)之播放音频(五) 9. 几种播放音频文件的方式(九) —— Media Player框架之基本概览(一) 10. 几种播放音频文件的方式(十) —— Media Player框架之简单播放音频示例(二) 11. 几种播放音频文件的方式(十一) —— AudioUnit框架之基本概览(一) 12. 几种播放音频文件的方式(十二) —— OpenAL框架之基本概览(一)
3、分步解析
这一篇文章主要就是对OpenAL播放音频的过程进行分步解析,文章内容来自别人,下面给出链接,致敬原作者—— IOS使用OpenAL播放音频文件。
其实,不仅可以参考这个作者所写的内容,我们还可以参考苹果官网给出的demo,OpenALExample 和 GLAirplay。
这里我们只谈最基本的实现,加载声音文件,播放声音。至于3D音效,多普勒效应环境音效设置,声音位置,收听位置等都不进行配置。
4、导入平台头文件
#include <stddef.h>
#include <Foundation/Foundation.h>
#include <AudioToolbox/AudioToolbox.h>
#include <OpenAL/OpenAL.h>
文件需要使用.m文件,因为需要使用Foundation.h的功能来加载Bundle的声音文件。m后缀文件是c和objc混编的文件类型。AudioToolbox可以对音频文件信息的解析和设置,以配合OpenAL的使用。
5、初始化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 API
alBufferDataStaticProc = (alBufferDataStaticProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alBufferDataStatic");
// create a new OpenAL Device
// pass NULL to specify the system’s default output device
device = alcOpenDevice(NULL);
if (device != NULL)
// create a new OpenAL Context
// the new context will render to the OpenAL Device just created
context = alcCreateContext(device, 0);
if (context != NULL)
// make the new context the Current OpenAL Context
alcMakeContextCurrent(context);
else
ALogE("Audio Init failed, OpenAL can not open device");
// clear any errors
alGetError();
OpenAL全局只需要一个ALCdevice和ALCcontext。
我们抽象了一个AudioPlayer,用来对应一个播放器,bufferId就是加载到内存的音频数据,sourceId是对应OpenAL播放器。
alBufferDataStatic是OpenAL的一个扩展,相对于alBufferData来说的。功能是加载音频数据到内存并关联到bufferId。只不过,alBufferData会拷贝音频数据所以调用后,我们可以free掉音频数据。而alBufferDataStatic并不会拷贝,所以音频数据data我们要一直保留并自己管理。
【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~
6、获取适合OpenAL使用的音频数据
我们需要加载声音文件,解析音频数据,修改音频数据格式为OpenAL需要的,获取最终的可以传递给OpenAL使用的音频数据。这几步封装了一个函数,先解释在看完整的代码。
首先我们要获取Bundle的文件路径。
然后,利用AudioToolBox的功能来读取并解析这个数据。OpenAL加载数据到Buffer,需要音频的采样频率,通道数,码率,数据大小等信息。
接着,OpenAL只能播放特定格式和属性的音频文件。再次使用AudioToolBox的功能来对音频数据进行设置,以达到需求。
最后,把处理好的数据和信息返回。
下面我们看代码。
static inline void* GetAudioData(char* filePath, ALsizei* outDataSize, ALenum* outDataFormat, ALsizei* outSampleRate)
AudioStreamBasicDescription fileFormat;
AudioStreamBasicDescription outputFormat;
SInt64 fileLengthInFrames = 0;
UInt32 propertySize = sizeof(fileFormat);
ExtAudioFileRef audioFileRef = NULL;
void* data = NULL;
NSString* path = [[NSBundle mainBundle] pathForResource:[NSString stringWithUTF8String:filePath] ofType:nil];
CFURLRef fileUrl = 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 format
error = 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 format
outputFormat.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 format
error = 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 count
propertySize = 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 memory
UInt32 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)
AudioBufferList dataBuffer;
dataBuffer.mNumberBuffers = 1;
dataBuffer.mBuffers[0].mDataByteSize = dataSize;
dataBuffer.mBuffers[0].mNumberChannels = outputFormat.mChannelsPerFrame;
dataBuffer.mBuffers[0].mData = data;
// read the data into an AudioBufferList
error = ExtAudioFileRead(audioFileRef, &framesToRead, &dataBuffer);
if(error != noErr)
free(data);
data = NULL; // make sure to return NULL
ALogE("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 needed
if (audioFileRef != 0)
ExtAudioFileDispose(audioFileRef);
return data;
这里,我使用了ArrayStrMap结构其实就是一个dictionary,用文件路径缓存了最终的data文件。因为,我会使用alBufferDataStatic,所以最终的data文件由我自己管理。并且同一个音频文件数据总是相同的,我就不再去频繁的free在malloc了。
outputFormat就是我们需要的音频格式,使用ExtAudioFileSetProperty能够让我们把原音频数据格式进行转换。这样,我们就可以使用各种音频文件格式来播放了,比如mp3,wav等等。
7、利用音频文件数据,生成我们的播放器对象
首先,生成bufferId,sourceId。
然后,把音频数据关联到bufferId,把bufferId关联到sourceId。
最后,sourceId代表就是OpenAL的播放器,可以设置各种属性。
那么,当我们需要销毁播放器的时候,主要也就是销毁sourceId和bufferId。
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 deleted
alBufferDataStaticProc(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 off
alSourcei(player->sourceId, AL_LOOPING, AL_FALSE);
// set Source Position
alSourcefv(player->sourceId, AL_POSITION, (const ALfloat[]) 0.0f, 0.0f, 0.0f);
// set source reference distance
alSourcef(player->sourceId, AL_REFERENCE_DISTANCE, 0.0f);
// attach OpenAL buffer to OpenAL Source
alSourcei(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);
8、设置播放器的各种属性
tatic 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;
9、OpenAL并没有播放完成的回调
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个播放器。所以播放器用完还是及时的删除比较好。
使用 OpenAL 将音频转换为 CAF 格式以便在 iPhone 上播放
【中文标题】使用 OpenAL 将音频转换为 CAF 格式以便在 iPhone 上播放【英文标题】:Converting audio to CAF format for playback on iPhone using OpenAL 【发布时间】:2010-09-20 05:23:36 【问题描述】:我在 CrashLanding 示例中使用来自 Apple 的 SoundEngine 示例代码来播放多个音频文件。使用 CrashLanding 中包含的示例 caf 文件一切正常,但是当我尝试使用我自己的示例转换为 CAF 使用 afconvert 时,我得到的只是石质的沉默;)
是否有人设置了 afconvert 以生成能够通过 OpenAL 播放的 CAF 文件?
【问题讨论】:
我想我会把你推到“999”的边缘,戴夫! 【参考方案1】:afconvert -f caff -d LEI16@44100 -c 1 in.wav out.caf
参考资料:
苹果的Multimedia Programming Guide: Using Audio: Preferred Audio Formats in iOS afconvert(1) man page(使用afconvert -h
获取完整信息)
【讨论】:
请问您是如何或在哪里找到答案的? @KristopherJohnson 两个链接都断开了。 错误无法打开输入文件(-43)我该怎么办? 你需要在终端运行上面的代码。in.wav
是声音的位置(只需拖放声音文件),out.caf
是带有.caf
扩展名的声音的输出路径
很好,但这将通道从 2 减少到 1,本质上:这是从立体声到单声道的转换!如果这不是您想要的,只需从命令中删除“-c 1”即可。【参考方案2】:
简单的 bash 脚本将文件夹中的 mp3 文件转换为 iphone 的 caf
#!/bin/bash
for f in *.mp3; do
echo "Processing $f file..."
afconvert -f caff -d LEI16@44100 -c 1 "$f" "$f/mp3/caf"
done
【讨论】:
为了让脚本更兼容不同的文件类型,我将 afconvert 行修改为:afconvert -f caff -d LEI16 "$f" "$f/mp3/caf" 否则我会得到错误 你能告诉我在哪里存储这个字符串吗?我对 bash 脚本一无所知。我必须以哪种格式存储它? Mac 或 windows 可以吗? @Devang 在 Mac 中很容易完成,只需打开 TextEdit,将文本保存到文件,然后打开终端,导航到保存的文件,输入:chmod +x这是最好的尺寸:
afconvert -f caff -d ima4 original.wav
【讨论】:
【参考方案4】:感谢您的信息。
另外,如果您正在研究使用 openAL 进行额外压缩,这可能会引起您的兴趣:
“iPhone、OpenAL 和 IMA4/ADPCM” http://www.wooji-juice.com/blog/iphone-openal-ima4-adpcm.html
【讨论】:
此方法是否与添加到 UILocalNotification 兼容?它比第一种方法压缩得多。【参考方案5】:今天 -c 似乎不起作用,我通过以下方式得到它:
afconvert -f caff -d LEI16 input.m4a output.caf
【讨论】:
以上是关于几种播放音频文件的方式(十三) —— OpenAL框架之分步解析的主要内容,如果未能解决你的问题,请参考以下文章