Core Audio提供了数字音频服务为iOS与OS X, 它提供了一系列框架去处理音频.git
Core Audio中包含咱们最经常使用的Audio Toolbox与Audio Unit框架.编程
Core Audio在iOS中针对移动平台的计算资源做出了优化,同时,音频服务必须严格由系统进行管理,特别是HAL与I/O Kit,然而Apple也提供了只在iOS平台中才有的服务,如Audio Session Service将帮助咱们管理音频上下文. 性能优化
PCM是最经常使用的无损压缩数字音频格式数据,根据采样率以规则间隔测量模拟(真实世界)数字音频信号并将每一个采集到的样本转换为数值来建立PCM数据.如标准光盘(CD)音频使用44.1 kHz的采样率,16位整数描述每一个样本 - 构成分辨率或位深度。bash
iOS中使用integer与fixed-point音频数据,目的是在处理音频数据时增长计算速度,减少电池能耗.iOS也提供了来自Audio Converter Services的Converter audio unit服务.cookie
iOS与OS X中,Core Audio提供了最经常使用的文件格式用于存储域播放音频数据.网络
Apple针对移动平台对iOS的Audio Unit做出了效率与性能优化,在开发中咱们必须将audio unit静态编译进APP,因此没法使用别的APP中的Audio Unit.session
大多状况下,咱们没法直接与HAL进行交互,Apple提供了一个特别的audio unit,即OS X中的AUHAL, iOS中的AURemoteIO, 咱们能够经过它们让音频与硬件交互.app
Core Audio接口中使用property管理对象的行为与状态.框架
Core Audio中经常使用回调函数以实现音频数据通讯,回调函数常有一下功能less
为了去使用回调函数,咱们须要作如下两件事情
Note: 在OC中,回调函数是一个C语言形式的函数,咱们回调OC本类对象做为对象传入其中, 因此回调函数中不能直接引用
self.xxx
,须要借助传入的OC对象去实现本类的功能.
Core Audio封装了音频数据格式,咱们只须要对给定结构体赋正确的参数便可。
struct AudioStreamBasicDescription {
Float64 mSampleRate;
UInt32 mFormatID;
UInt32 mFormatFlags;
UInt32 mBytesPerPacket;
UInt32 mFramesPerPacket;
UInt32 mBytesPerFrame;
UInt32 mChannelsPerFrame;
UInt32 mBitsPerChannel;
UInt32 mReserved;
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;
struct AudioStreamPacketDescription {
SInt64 mStartOffset;
UInt32 mVariableFramesInPacket;
UInt32 mDataByteSize;
};
typedef struct AudioStreamPacketDescription AudioStreamPacketDescription;
复制代码
注意,上面结构体中mReserved
是Apple的保留参数,必须为0. 其余一些参数在特定状况下也需为0,如:压缩音频格式每一个sample使用不一样数量的bits。对于这些格式,mBitsPerChannel成员的值为0。
你能够手动为ASBD的成员赋值,若是有些值是你不知道的,能够赋0,Core Audio将自动选择适当的值。
iOS: 线性PCM 16bit integer, Noninterleaved linear PCM 8.24bit 定点samples
struct AudioStreamBasicDescription {
mSampleRate = 44100.0;
mFormatID = kAudioFormatLinearPCM;
mFormatFlags = kAudioFormatFlagsAudioUnitCanonical;
mBitsPerChannel = 8 * sizeof (AudioUnitSampleType); // 32 bits
mChannelsPerFrame = 2;
mBytesPerFrame = mChannelsPerFrame * sizeof (AudioUnitSampleType); // 8 bytes
mFramesPerPacket = 1;
mBytesPerPacket = mFramesPerPacket * mBytesPerFrame; // 8 bytes
mReserved = 0;
};
复制代码
在Core Audio中,magic cookie表示被附加到压缩音频数据(文件或流)中的元数据(metadata)。元数据为解码器提供了正确解码文件或流所须要的详细信息。Core Audio能够复制,读取,使用元数据包含的信息。
下面的例子展现了如何将一个文件中magic cookie拷贝提供给audio queue.
- (void) copyMagicCookieToQueue: (AudioQueueRef) queue fromFile: (AudioFileID) file {
UInt32 propertySize = sizeof (UInt32);
OSStatus result = AudioFileGetPropertyInfo (
file,
kAudioFilePropertyMagicCookieData,
&propertySize,
NULL
);
if (!result && propertySize) {
char *cookie = (char *) malloc (propertySize);
AudioFileGetProperty (
file,
kAudioFilePropertyMagicCookieData,
&propertySize,
cookie
);
AudioQueueSetProperty (
queue,
kAudioQueueProperty_MagicCookie,
cookie,
propertySize
);
free (cookie);
}
}
复制代码
音频数据包(packet)是一个或多个帧的集合,对于特定音频格式,它是有意义的最小帧集合,所以它是最佳表示一段时间音频数据的单位。
在CBR,VBR的格式中,对于给定的音频文件或流,每秒钟的包数是固定的,
使用audio converter能够改变音频采样率,交错或不交错,以及压缩与未压缩数据格式相互转换。
Core Audio中使用Audio File Service为建立与访问音频文件及包含在其中元数据提供了一个强大的抽象。咱们不只可使用文件的ID,type,数据格式,还能够添加标记,循环,回放等等功能。
AudioFileCreateWithURL (
audioFileURL,
kAudioFileCAFType,
&audioFormat,
kAudioFileFlags_EraseFile,
&audioFileID // the function provides the new file object here
);
复制代码
AudioFileOpenURL
函数打开一个文件,提供URL,文件类型,访问权限成功后返回一个文件ID,使用这个ID以及经常使用函数能够检索咱们须要的文件信息。下面列举了一些经常使用函数kAudioFilePropertyFileFormat
kAudioFilePropertyDataFormat
kAudioFilePropertyMagicCookieData
kAudioFilePropertyChannelLayout
复制代码
当一个VBR文件过大时,检索信息速度会较慢,可使用kAudioFilePropertyPacketSizeUpperBound and kAudioFilePropertyEstimatedDuration.
这两个函数快速获取近似值。
读写文件
扩展 Core Audio提供了一个的API,称为扩展音频文件服务。该接口包含音频文件服务和音频转换器服务中的基本功能,提供与线性PCM之间的自动数据转换
iPhone 支持的Audio file格式
Format name | Format filename extensions |
---|---|
AIFF | .aif,.aiff |
CAF | .caf |
MPEG-1,layer 3 | .mp3 |
MPEG-2 or MPEG-4 ADTS | .aac |
MPEG-4 | .m4a, .mp4 |
WAV | .wav |
AC-3 (Dolby Digital) | .ac3 |
Enhanced AC-3 (Dolby Digital Plus) | .ec3 |
iOS与OS X中原生音频文件格式为CAF(Core Audio Format),它能够支持平台中任意音频数据格式。它没有大小限制,能够支持多种元数据,如声道信息,文本注释等
与音频文件不一样,咱们没法肯定一个audio file stream(音频流)的开始与结束点.由于咱们每每是经过网络接受音频流数据,开始与结束的时机取决于用户的交互,而且,音频流数据也没法保证必定能够获取,由于网络传输中可能会存储在丢帧,暂停等等状况.
Audio File Service能够经过解析(parse)让咱们使用音频流.经过回调函数获取parse到的一系列音频数据.
在iOS中,有时咱们须要处理高优先级任务,如接电话,若是当前APP正在播放视频,咱们必须作出符合用户指望的事情以协调APP与系统电话.Audio Session对象充当了二者之间的一个中介.每一个iPhone应用程序只有一个audio session,经过配置其属性以使用.
开始以前,咱们要明确下面几个问题
为了解决上面的问题,咱们须要配置audio session使用以下特性
Audio Session feature | Description |
---|---|
Categories | 一个category标识着一组音频行为的键,经过设置分类,能够代表音频的行为,如锁屏时是否应该继续播放音频. |
Interruptions and route changes | 当音频被中断或音频线路发生改变时,audio session将发送一个通知,经过接收通知以做出相应响应. |
Hardware characteristics | 经过audio session能够查询当前设备的一些硬件信息,如采样率,声道数,输入源设备等 |
以上行为是audio session默认分类(kAudioSessionCategory_SoloAmbientSound)的行为
启动时,默认的audio session是激活状态,然而,若是有电话打进来(interruption),audio session立刻处于停用状态且应用程序音频中止.若是用户选择忽略当前电话,你的应用程序继续运行,可是audio session还是未激活状态,音频没法继续工做.
若是应用程序中使用OpenAL, I/O unit, Audio Queue Services,咱们必须写一个监听中断的回调函数,在中断结束后从新激活audio session.
使用录制功能的APP是否能录制取决于当前选择的硬件音频输入端,使用kAudioSessionProperty_AudioInputAvailable
能够测试当前输入端是否可用
UInt32 audioInputIsAvailable;
UInt32 propertySize = sizeof (audioInputIsAvailable);
AudioSessionGetProperty (
kAudioSessionProperty_AudioInputAvailable,
&propertySize,
&audioInputIsAvailable // A nonzero value on output means that
// audio input is available
);
复制代码
应用程序仅有一个audio session分类在同一时间(此规则的一个例外是使用System Sound Services播放的音频 - 用于警报和用户界面声音效果的API。此类音频始终使用最低优先级的音频会话类别),
若是你的应用程序不须要双声道,精确同步以及播放网络流音频,可使用AVAudioPlayer
类实现简单的音频播放.
如下使用范围
NSString *soundFilePath =
[[NSBundle mainBundle] pathForResource: @"sound"
ofType: @"wav"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
AVAudioPlayer *newPlayer =
[[AVAudioPlayer alloc] initWithContentsOfURL: fileURL
error: nil];
[fileURL release];
self.player = newPlayer;
[newPlayer release];
[self.player prepareToPlay];
[self.player setDelegate: self];
- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer *) player
successfully: (BOOL) flag {
if (flag == YES) {
[self.button setTitle: @"Play" forState: UIControlStateNormal];
}
}
- (IBAction) playOrPause: (id) sender {
// if already playing, then pause
if (self.player.playing) {
[self.button setTitle: @"Play" forState: UIControlStateHighlighted];
[self.button setTitle: @"Play" forState: UIControlStateNormal];
[self.player pause];
// if stopped or paused, start playing
} else {
[self.button setTitle: @"Pause" forState: UIControlStateHighlighted];
[self.button setTitle: @"Pause" forState: UIControlStateNormal];
[self.player play];
}
[self.player setVolume: 1.0]; // available range is 0.0 through 1.0
复制代码
Audio Queue Services提供了一种低开销,直接的方式去录制和播放音频,它使你的应用程序使用硬件(麦克风与扬声器)录制与播放而且无需了解硬件接口.它也让咱们使用复杂的编解码器而无需了解编解码器的工做原理.
Audio Queue提供了更精确的定时播放以支持预约播放与同步,你可使用它去同步多个音频播放队列,同时播放声音,独立控制每一个队里的音量以及循环播放.
Audio Queue与AVAudioPlayer二者是在iPhone上播放音频的惟一方式
经过属性与回调函数让咱们与audio queue对象间交互.对于录制,咱们经过回调函数接收音频数据.
对于播放回调,当你的音频播放队列须要播放一个音频数据时它将被调用.你的回调函数将从磁盘读取指定数量的音频数据包而后将它们封装在audio queue对象的buffer中.audio queue将按顺序播放这些buffer.
实现一个播放队列
a. 建立一个结构体管理audio queue须要的信息,如音频格式,采样率等等
b. 定义一个回调函数管理audio queue buffers,这个回调函数使用Audio File Services去读取你想要播放的文件.
c. 初始化audio queue而且使用AudioQueueNewOutput建立对象.
static const int kNumberBuffers = 3;
// Create a data structure to manage information needed by the audio queue
struct myAQStruct {
AudioFileID mAudioFile;
CAStreamBasicDescription mDataFormat;
AudioQueueRef mQueue;
AudioQueueBufferRef mBuffers[kNumberBuffers];
SInt64 mCurrentPacket;
UInt32 mNumPacketsToRead;
AudioStreamPacketDescription *mPacketDescs;
bool mDone;
};
// Define a playback audio queue callback function
static void AQTestBufferCallback(
void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inCompleteAQBuffer
) {
myAQStruct *myInfo = (myAQStruct *)inUserData;
if (myInfo->mDone) return;
UInt32 numBytes;
UInt32 nPackets = myInfo->mNumPacketsToRead;
AudioFileReadPackets (
myInfo->mAudioFile,
false,
&numBytes,
myInfo->mPacketDescs,
myInfo->mCurrentPacket,
&nPackets,
inCompleteAQBuffer->mAudioData
);
if (nPackets > 0) {
inCompleteAQBuffer->mAudioDataByteSize = numBytes;
AudioQueueEnqueueBuffer (
inAQ,
inCompleteAQBuffer,
(myInfo->mPacketDescs ? nPackets : 0),
myInfo->mPacketDescs
);
myInfo->mCurrentPacket += nPackets;
} else {
AudioQueueStop (
myInfo->mQueue,
false
);
myInfo->mDone = true;
}
}
// Instantiate an audio queue object
AudioQueueNewOutput (
&myInfo.mDataFormat,
AQTestBufferCallback,
&myInfo,
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes,
0,
&myInfo.mQueue
);
复制代码
Audio queue对象提供了两种方式控制播放音量,一种是直接设置,以下,设置后能够当即生效.
Float32 volume = 1;
AudioQueueSetParameter (
myAQstruct.audioQueueObject,
kAudioQueueParam_Volume,
volume
);
复制代码
另外一种是使用AudioQueueEnqueueBufferWithParameters
,设置后在audio queue buffer开始播放时生效.
经过查询audio queue对象的kAudioQueueProperty_CurrentLevelMeterDB
属性能够获取当前播放的级别.
typedef struct AudioQueueLevelMeterState {
Float32 mAveragePower;
Float32 mPeakPower;
}; AudioQueueLevelMeterState;
复制代码
为了同时播放多个音频,须要为每一个音频建立一个播放audio queue对象.对于每一个audio queue,使用AudioQueueEnqueueBufferWithParameters
函数安排第一个音频buffer同时启动。
同时播放多个音频,音频格式显得相当重要,由于iOS中某些音频格式使用了高效的硬件编解码器,只能在设备上播放如下格式之一的单个实例.
a. AAC
b. ALAC
c. MP3
若是要播放高质量同步的音频,须要使用线性PCM或IMA4格式.
a. 线性PCM和IMA / ADPCM(IMA4)音频您能够在iOS中同时播放多个线性PCM或IMA4格式声音,而不会产生CPU资源问题。
b. AAC,MP3和Apple Lossless(ALAC)一次只能播放一首此类声音
开源的OpenAL音频API(可在OpenAL框架中使用,构建于Core Audio之上)针对播放期间的声音定位进行了优化。使用OpenGL建模的界面,OpenAL能够轻松播放,定位,混合和移动声音,OpenAL和OpenGL共享一个通用坐标系统,使您能够同步音频和视频。OpenAL直接使用Core Audio的I / O audio unit),从而实现最低延迟播放。OpenAL是在iPhone和iPod touch上播放游戏应用中的声音效果的最佳选择。
Audio Toolbox中的AudioServices.h提供了系统的声音服务,当你仅仅想播放一个系统的短音频时,它将是最好的选择,iOS中播放系统声音最不不能超过30秒.
在iOS中,调用AudioServicesPlaySystemSound
能够当即播放,你也能够调用AudioServicesPlayAlertSound
提示用户是否播放.
调用AudioServicesPlaySystemSound
时使用kSystemSoundID_Vibrate
常量能够显式设置振动效果.
#include <AudioToolbox/AudioToolbox.h>
#include <CoreFoundation/CoreFoundation.h>
// Define a callback to be called when the sound is finished
// playing. Useful when you need to free memory after playing.
static void MyCompletionCallback (
SystemSoundID mySSID,
void * myURLRef
) {
AudioServicesDisposeSystemSoundID (mySSID);
CFRelease (myURLRef);
CFRunLoopStop (CFRunLoopGetCurrent());
}
int main (int argc, const char * argv[]) {
// Set up the pieces needed to play a sound.
SystemSoundID mySSID;
CFURLRef myURLRef;
myURLRef = CFURLCreateWithFileSystemPath (
kCFAllocatorDefault,
CFSTR ("../../ComedyHorns.aif"),
kCFURLPOSIXPathStyle,
FALSE
);
// create a system sound ID to represent the sound file
OSStatus error = AudioServicesCreateSystemSoundID (myURLRef, &mySSID);
// Register the sound completion callback.
// Again, useful when you need to free memory after playing.
AudioServicesAddSystemSoundCompletion (
mySSID,
NULL,
NULL,
MyCompletionCallback,
(void *) myURLRef
);
// Play the sound file.
AudioServicesPlaySystemSound (mySSID);
// Invoke a run loop on the current thread to keep the application
// running long enough for the sound to play; the sound completion
// callback later stops this run loop.
CFRunLoopRun ();
return 0;
}
复制代码
在iOS中,Audio Unit为应用程序提供了实现低延迟输入和输出的机制。它们还提供某些DSP功能.
iOS中Audio Unit输入输出使用8.24位定点线性PCM音频数据.惟一例外的是如下状况.
每一个Audio Unit的惟一标识符由类型,子类型,制造商代码(type, subtype, and manufacturer code)肯定.每种子类型更加精确的描述了audio unit的用途.Audio Unit使用属性配置音频信息,如 Properties, Scopes, and Elements.每种audio unit须要一些指定属性,
iOS中能够用的录制和播放编解码器来平衡音频质量,应用程序开发的灵活性,硬件功能和电池寿命。
AUGraph:定义了一组复杂的音频执行任务.