无论多媒体功能在您的应用程序中是处于中心地位,还是偶尔被使用,iPhone用户都期望有很高的品质。视频应该充分利用设备携带的高分辨率屏幕和高帧率,而引人注目的音频也会对应用程序的总体用户体验有不可估量的增强作用。
您可以利用iPhone OS的多媒体框架来为应用程序加入下面这些功能:
高品质的音频录制和回放
生动的游戏声音
实时的声音聊天
用户iPod音乐库内容的回放
在支持的设备上进行视频的回放和录制
本章将介绍iPhone OS上为应用程序添加音视频功能的多媒体技术。
在iPhone OS上使用声音
iPhone OS为应用程序提供一组丰富的声音处理工具。根据功能的不同,这些工具被安排到如下的框架中:
如果希望用简单的Objective-C接口进行音频的播放和录制,可以使用AV Foundation框架。
如果要播放和录制带有同步能力的音频、解析音频流、或者进行音频格式转换,可以使用Audio Toolbox框架。
如果要连接和使用音频处理插件,可以使用Audio Unit框架。
如果希望在游戏和其它应用程序中回放位置音频,需要使用OpenAL框架。iPhone OS对OpenAL 1.1的支持是建立在Core Audio基础上的。
如果希望播放iPod库中的歌曲、音频书、或音频播客,需要使用Media Player框架中的iPod媒体库访问接口。
Core Audio框架(和其它音频框架对等)中提供所有Core Audio服务需要使用的数据类型。
本部分将就如何着手实现各种音频功能提供一些指导,如下表所示:
播放用户iPod库中的歌曲、音频播客、以及音频书,请参见“用iPod媒体库访问接口播放媒体项”部分。
播放警告及用户界面声音效果,或者使具有震动功能的设备发生震动,可以使用系统声音服务,具体请参见“使用系统声音服务播放短声音及激活震动”部分。
如果要用最少量的代码播放和录制音频,可以使用AV Foundation框架,具体参见“通过AVAudioPlayer类轻松播放声音”及 “用AVAudioRecorder类进行音频录制”部分。
如果需要提供全功能的音频回放,包括立体声定位、音量控制、和同期声(simultaneous sounds),可以使用OpenAL,具体参见“使用OpenAL播放和定位声音”部分。
如果要提供最低延迟的音频,特别是需要同时进行音频输入输出(比如VoIP应用程序)时,请使用I/O音频单元,具体请参见“iPhone OS中的音频单元支持”部分。
如果您播放的声音需要精确的控制(包括同步),可以使用音频队列服务,具体参见“用音频队列服务播放和控制声音”部分,音频队列服务还支持音频录制,具体请见“用音频队列服务进行音频录制”部分的描述。
如果需要解析来自网络连接的音频流,请使用音频文件流服务,具体参见“解析音频流”部分。
请务必阅读本文接下来的部分,即“基础:硬件编解码器、音频格式、和音频会话”部分,以了解在基于iPhone OS的设备上音频工作机制的关键信息;而且也请您阅读“iPhone音频的最佳实践”部分,该部分提供了一些指导原则,并列举了一些能得到最好性能和最佳用户体验的音频和文件格式。
当您准备好进一步学习时,请访问iPhone Dev Center。这个开发者中心包含各种指南文档、实例代码、及更多其它信息。有关如何执行常见音频任务的贴士,请参见音频&视频编程的How-To's部分;如果需要iPhone OS音频开发的深入解释,则请参见Core Audio概述、音频队列服务编程指南、和音频会话编程指南。
基础:硬件编解码器、音频格式、和音频会话
在开始iPhone音频开发之前,了解iPhone OS设备的一些硬软件架构知识是很有帮助的。
iPhone音频硬件编解码
iPhone OS的应用程序可以使用广泛的音频数据格式。从iPhone OS 3.0开始,这些格式中的大多数都可以支持基于软件的编解码。您可以同时播放多路各种格式的声音,虽然出于性能的考虑,您应该针对给定的场景选择最佳的格式。通常情况下,硬件解码带来的性能影响比软件解码要小。
下面这些iPhone OS音频格式可以利用硬件解码进行回放:
AAC
ALAC (Apple Lossless)
MP3
通过硬件,设备每次只能播放这些格式中的一种。举例来说,如果您正在播放的是MP3立体声,则第二个同时播放的MP3声音就只能使用软件解码。类似地,您不能通过硬件同时播放一个AAC声音和一个ALAC声音。如果iPod应用程序正在后台播放AAC声音,则您的应用程序只能使用软件解码来播放AAC、ALAC、和MP3音频。
为了以最佳性能播放多种声音,或者为了在iPod程序播放音乐的同时能更有效地播放声音,可以使用线性PCM(无压缩)或者IMA4(有压缩)格式的音频。
如果需要了解如何检测设备硬软件编解码器是否可用,请查阅音频格式服务参考中有关
kAudioFormatProperty_HardwareCodecCapabilities
常量的讨论。
音频回放和录制格式
下面是一些iPhone OS支持的音频回放格式:
AAC
HE-AAC
AMR (Adaptive Multi-Rate,是一种语音格式)
ALAC (Apple Lossless)
iLBC (互联网Low Bitrate Codec,另一种语音格式)
IMA4 (IMA/ADPCM)
线性PCM (无压缩)
?-law和a-law
MP3 (MPEG-1 音频第3层)
下面是一些iPhone OS支持的音频录制格式:
ALAC (Apple Lossless)
iLBC (互联网Low Bitrate Codec,用于语音)
IMA/ADPCM (IMA4)
线性PCM
?-law和a-law
下面的列表总结了iPhone OS如何支持单路或多路音频格式:
线性PCM和IMA4 (IMA/ADPCM) 在iPhone OS上,您可以同时播放多路线性PCM或IMA4声音,而不会导致CPU资源的问题。这一点同样适用于AMR和iLBC语音品质格式,以及?-law和a-law压缩格式。在使用压缩格式时,请检查声音的品质,确保满足您的需要。
AAC、MP3、和ALAC (Apple Lossless) AAC、MP3、和ALAC声音的回放可以使用iPhone OS设备上高效的硬件解码,但是这些编解码器共用一个硬件路径,通过硬件,设备每次只能播放上述格式的一种。
AAC、MP3、和ALAC的回放共用同一硬件路径的事实会对“合作播放”风格的应用程序(比如虚拟钢琴)产生影响。如果用户在iPod程序上播放上述三种格式之一的音频,则您的应用程序—如果要和该音频一起播放声音—需要使用软件解码。
音频会话
Core Audio的音频会话接口(具体描述请见音频会话服务参考)使应用程序可以为自己定义一般的音频行为,并在更大的音频上下文中良好工作。您能够影响的行为有:
您的音频在Ring/Silent切换过程中是否变为无声
在屏幕锁定状态时您的音频是否停止
当您的音频开始播放时,iPod音频是继续播放,还是变为无声
更大的音频上下文包括用户所做的改变,比如用户插入耳机,处理Clock和Calendar这样的警告事件,或者处理呼入的电话。通过音频会话,您可以对这样的事件做出恰当的响应。
音频会话服务提供了三种编程特性,如表7-1所述。
音频会话特性 |
描述 |
---|---|
范畴 |
范畴是标识一组应用程序音频行为的键。您可以通过范畴的设置来指示自己希望得到的音频行为,比如希望在屏幕锁定状态时继续播放音频。 |
中断和路由变化 |
当您的音频发生中断或中断结束,以及当硬件音频路由发生变化时,音频会话会发出通告,使您可以优雅地响应发生在更大音频环境中的变化—比如由于电话呼入而导致的中断。 |
硬件特征 |
您可以通过查询音频会话来了解应用程序所在的设备的特征,比如硬件采样率,硬件通道数量,以及是否有音频输入。 |
AVAudioSession类参考和AVAudioSessionDelegate协议参考描述了一个管理音频会话的精简接口。如果要使音频会话支持中断,则可以直接使用基于C语言的音频会话服务接口,该接口的描述请见音频会话服务参考。在应用程序中,这两个接口的代码可以混用及互相匹配。
音频会话带有一些缺省的行为,可以作为开发的起点。但是,除了某些特殊的情况之外,采用缺省行为的音频应用程序并不适合发行。您需要通过配置和使用音频会话来表达自己使用音频的意图,响应OS级别的音频变化。
举例来说,在使用缺省的音频会话时,如果出现Auto-Lock超时或屏幕锁定,应用程序的音频就会停止。如果您希望在屏幕被锁定时继续播放音频,则必须将下面的代码包含到应用程序的初始化代码中:
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: nil];
|
[[AVAudioSession sharedInstance] setActive: YES error: nil];
|
AVAudioSessionCategoryPlayback
范畴确保音频的回放可以在屏幕锁定时继续。激活音频会话会使指定的范畴也被激活。范畴的详细信息请参见音频会话编程指南中的音频会话范畴部分。
如何处理呼入电话或时钟警告引起的中断取决于您使用的音频技术,如表7-2所示。
音频技术 |
中断如何工作 |
---|---|
系统声音服务 |
当中断开始时,系统声音和警告声音会变为无声。如果中断结束—当用户取消警告或选择忽略呼入电话时,会发生这种情况—它们就又自动变为可用。使用这种技术的应用程序无法影响声音中断的行为。 |
音频队列服务、OpenAL、I/O音频单元 |
这些技术为中断的处理提供最大的灵活性。您需要编写一个中断监听回调函数,具体描述请参见音频会话编程指南中的 “响应音频中断”部分。 |
|
|
每个iPhone OS应用程序—除了很少的例外—都应该采纳音频会话服务。如果需要了解具体的用法,请阅读音频会话编程指南。
播放音频
本部分将介绍如何用iPod媒体库访问接口、系统声音服务、音频队列服务、AV Foundation框架、和OpenAL来播放iPhone OS上的声音。
通过iPod媒体库访问接口播放媒体项
从iPhone OS 3.0开始,iPod媒体库访问接口使应用程序可以播放用户的歌曲、音频书,和音频播客。这个API的设计使基本回放变得非常简单,同时又支持高级的检索和回放控制。
如图7-1所示,您的应用程序有两种方式可以取得媒体项,一种是通过媒体项选择器,如图左所示,它是个易于使用、预先封装好的视图控制器,其行为和内置iPod程序的音乐选择接口类似。对于很多应用程序,这种方式就够用了。如果媒体选择器没有提供您需要的某种访问控制,则可以使用媒体查询接口,该接口支持以基于断言(predicate)的方式指定iPod媒体库中的项目。
图7-1 使用iPod媒体库访问接口
如上图所示,位于右边的应用程序在取得媒体项之后,可以通过这个API提供的音乐播放器进行播放。
有关如何在应用程序中加入媒体项回放功能的完整解释,请参见iPod媒体库访问接口指南。
使用系统声音服务播放短声音及触发震动
当您需要播放用户界面声音效果(比如触击按键)或警告声音,或者使支持震动的设备产生震动时,可以使用系统声音服务。这个简洁接口的描述请参见系统声音服务参考。您可以在iPhone Dev Center中找到SysSound实例代码。
请注意:通过系统声音服务播放的声音不受音频会话配置的控制。因此,您无法使系统声音服务的音频行为和应用程序的其它音频行为保持一致。这也是需要避免使用系统声音服务播放音频的最重要原因,除非您有意为之。
AudioServicesPlaySystemSound
函数使您可以非常简单地播放短声音文件。使用上的简单也带来一些限制。您的声音文件必须是:
长度小于30秒
采用PCM或者IMA4 (IMA/ADPCM) 格式
包装为
.caf
、
.aif
、或者
.wav
文件
此外,当您使用
AudioServicesPlaySystemSound
函数时:
声音会以当前系统音量播放,且无法控制音量
声音立即被播放
不支持环绕和立体效果
AudioServicesPlayAlertSound
是一个类似的函数,用于播放一个短声音警告。如果用户在声音设置中将设备配置为震动,则这个函数在播放声音文件之外还会产生震动。
请注意:系统和用户界面的声音效果并不提供给您的应用程序。举例来说,将
kSystemSoundID_UserPreferredAlert
常量作为参数传递给
AudioServicesPlayAlertSound
函数将不会播放任何声音。
在用
AudioServicesPlaySystemSound
或
AudioServicesPlayAlertSound
函数时,您需要首先创建一个声音ID对象,如程序清单7-1所示。
程序清单7-1 创建一个声音ID对象
// Get the main bundle for the app
|
CFBundleRef mainBundle = CFBundleGetMainBundle ();
|
|
// Get the URL to the sound file to play. The file in this case
|
// is "tap.aiff"
|
soundFileURLRef = CFBundleCopyResourceURL (
|
mainBundle,
|
CFSTR ("tap"),
|
CFSTR ("aif"),
|
NULL
|
);
|
|
// Create a system sound object representing the sound file
|
AudioServicesCreateSystemSoundID (
|
soundFileURLRef,
|
&soundFileObject
|
);
|
然后再播放声音,如清单7-2所示。
程序清单7-2 播放一个系统声音
- (IBAction) playSystemSound {
|
AudioServicesPlaySystemSound (self.soundFileObject);
|
}
|
这片代码经常用于偶尔或者反复播放声音。如果您希望反复播放,就需要保持声音ID对象,直到应用程序退出。如果您确定声音只用一次—比如程序启动的声音—则可以在播放完成后立即销毁声音ID,释放其占用的内存。
如果iPhone OS设备支持振动,则运行在该设备上的应用程序可以通过系统声音服务触发振动,振动的选项通过
kSystemSoundID_Vibrate
标识符来指定。
AudioServicesPlaySystemSound
函数可以用于触发振动,具体如程序清单7-3所示。
程序清单7-3 触发振动
#import <AudioToolbox/AudioToolbox.h>
|
#import <UIKit/UIKit.h>
|
- (void) vibratePhone {
|
AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
|
}
|
如果您的应用程序运行在iPod touch上,则上面的代码不执行任何操作。
通过AVAudioPlayer类轻松播放声音
AVAudioPlayer
类提供了一个简单的Objective-C接口,用于播放声音。如果您的应用程序不需要立体声或精确同步,且不播放来自网络数据流的音频,则我们推荐您使用这个类来回放声音。
通过音频播放器可以实现如下任务:
播放任意长度的声音
播放文件或内存缓冲区中的声音
循环播放声音
同时播放多路声音(虽然不能精确同步)
控制每个正在播放声音的相对音量
跳到声音文件的特定点上,这可以为需要快进和反绕的应用程序提供支持
取得音频强度数据,用于测量音量
AVAudioPlayer
类可以播放iPhone OS上有的所有音频格式,具体描述请参见“音频回放和录制格式”部分。或者。如果您需要该类接口的完整描述,请参见AVAudioPlayer类参考。
为了使音频播放器播放音频,您需要为其分配一个声音文件,使其做好播放的准备,并为其指定一个委托对象。程序清单7-4中的代码通常放在应用程序控制器类的初始化方法中。
程序清单7-4 配置AVAudioPlayer对象
// in the corresponding .h file:
|
// @property (nonatomic, retain) AVAudioPlayer *player;
|
|
@synthesize player; // the player object
|
|
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];
|
|
[player prepareToPlay];
|
[player setDelegate: self];
|
您可以通过委托对象(可能是您的控制器对象)来处理中断,以及在声音播放完成后更新用户界面。有关
AVAudioPlayer
类的委托对象的具体描述请参见AVAudioPlayerDelegate协议参考。程序清单7-5显示了一个委托方法的简单实现,其中的代码在声音播放完成时更新了播放/暂停切换按键的标题。
程序清单7-5 实现AVAudioPlayer类的委托方法
- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer *) player
|
successfully: (BOOL) flag {
|
if (flag == YES) {
|
[self.button setTitle: @"Play" forState: UIControlStateNormal];
|
}
|
}
|
调用回放控制方法可以使
AVAudioPlayer
对象执行播放、暂停、或者停止操作。您可以通过
playing
属性来检测当前是否正在播放。程序清单7-6显示了播放/暂停切换方法的基本实现,其功能是控制回放和更新
UIButton
对象的标题。
程序清单7-6 控制AVAudioPlayer对象
- (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];
|
}
|
}
|
AVAudioPlayer
类使用Objective-C的属性声明来管理声音信息—比如取得声音时间线上的回放点和访问回放选项(如音量和是否重复播放的设置)。举例来说,您可以通过如下的代码设置一个音频播放器的回放音量:
[self.player setVolume: 1.0]; // available range is 0.0 through 1.0
|
有关
AVAudioPlayer
类的更多信息,请参见AVAudioPlayer类参考。
用音频队列服务播放和控制声音
音频队列服务(Audio Queue Services)加入了一些
AVAudioPlayer
类不具有的回放能力。通过音频队列服务进行回放可以:
精确计划声音的播放,支持声音的同步。
精确控制音量—基于一个个的缓冲区。
通过音频文件流服务(Audio File Stream Services)来播放从流中捕捉的音频。
音频队列服务可以播放iPhone OS支持的所有音频格式,具体描述请见“音频回放和录制格式”部分;还支持录制,详见“录制音频”部分。
有关如何使用这个技术的详细信息,请参见音频队列服务编程指南和音频队列服务参考。如果需要实例代码,请见iPhone Dev Center网站的SpeakHere实例(Mac OS X系统上的实现则见Core Audio SDK的AudioQueueTools工程,在Mac OS X上安装Xcode工具之后,在
/Developer/Examples/CoreAudio/SimpleSDK/AudioQueueTools
路径下可以找到AudioQueueTools工程)。
创建一个音频队列对象
创建一个音频队列对象需要下面三个步骤:
创建管理音频队列所需的数据结构,比如您希望播放的音频格式。
定义管理音频队列缓冲区的回调函数。在回调函数中,您可以使用音频文件服务来读取希望播放的文件(在iPhone OS 2.1及更高版本中,您还可以用扩展音频文件服务来读取文件)。
通过
AudioQueueNewOutput
函数实例化回放音频队列。
程序清单7-7是上述步骤的ANSI C代码。SpeakHere示例工程中也有同样的步骤,只是它们位于Objective-C程序的上下文中。
程序清单7-7 创建一个音频队列对象
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
|
);
|
控制回放音量
音频队列对象为您提供两种控制回放音量的方法。
您可以通过调用
AudioQueueSetParameter
函数并传入
kAudioQueueParam_Volume
参数来直接设置回放的音量,如程序清单7-8所示,音量的变化会立即生效。
程序清单7-8 直接设置回放的音量
Float32 volume = 1; // linear scale, range from 0.0 through 1.0
|
AudioQueueSetParameter (
|
myAQstruct.audioQueueObject,
|
kAudioQueueParam_Volume,
|
volume
|
);
|
您还可以通过
AudioQueueEnqueueBufferWithParameters
函数来设置音频队列缓冲区的回放音量。这个函数可以指定音频队列缓冲区进入队列时携带的音频队列设置。通过这个函数做出的改变在音频队列缓冲区开始播放的时候生效。
在上述的两种情况下,对音频队列的音量所做的修改都会一直保持下来,直到再次被改变。
指示回放音量
您可以通过下面的方式得到音频队列对象的当前回放音量:
启用音频队列对象的音量计,具体方法是将其
kAudioQueueProperty_EnableLevelMetering
属性设置为
true
。
查询音频队列对象的
kAudioQueueProperty_CurrentLevelMeter
属性。
这个属性的值是一个
AudioQueueLevelMeterState
结构的数组,每个声道都有一个相对应的结构。程序清单7-9显示了这个结构的内容:
程序清单7-9
AudioQueueLevelMeterState
结构
typedef struct AudioQueueLevelMeterState {
|
Float32 mAveragePower;
|
Float32 mPeakPower;
|
}; AudioQueueLevelMeterState;
|
同时播放多路声音
为了同时播放多路声音,需要为每路声音创建一个回放音频队列对象,并对每个音频队列调用
AudioQueueEnqueueBufferWithParameters
函数,将第一个音频缓冲区排入队列,使之开始播放。
在基于iPhone OS的设备中同时播放声音时,音频格式是很关键的。如果要同时播放,您需要使用线性PCM (无压缩) 音频格式或特定的有压缩音频格式,具体描述请参见“音频回放和录制格式”部分。
使用OpenAL播放和定位声音
开源的OpenAL音频API位于iPhone OS系统的OpenAL框架中,它提供了一个优化接口,用于定位正在回放的立体声场中的声音。使用OpenAL进行声音的播放、定位、和移动是很简单的—其工作方式和其它平台一样。此外,OpenAL还可以进行混音。OpenAL使用Core Audio的I/O单元进行回放,从而使延迟最低。
由于所有的这些原因,OpenAL是iPhone OS设备中游戏程序的最好选择。当然,OpenAL也是一般的iPhone OS应用程序进行音频播放的良好选择。
iPhone OS对OpenAL 1.1的支持是构建在Core Audio之上的。更多的信息请参见iPhone OS系统的OpenAL FAQ。如果需要有关OpenAL的文档,请参见http://openal.org的OpenAL网站;如果需要演示如何播放OpenAL音频的示例程序,请参见oalTouch。
录制音频
在iPhone OS系统上,可以通过
AVAudioRecorder
类和音频队列服务来进行音频录制,而Core Audio则为其提供底层的支持。这些接口所做的工作包括连接音频硬件、管理内存、以及在需要时使用编解码器。您可以录制“音频的回放和录制格式”部分列出的所有格式的音频。
本部分将介绍如何通过
AVAudioRecorder
类和音频队列服务在iPhone OS系统上录制音频。
通过AVAudioRecorder类进行录制
iPhone OS上最简单的录音方法是使用
AVAudioRecorder
类,类的具体描述请参见AVAudioRecorder类参考。该类提供了一个高度精简的Objective-C接口。通过这个接口,您可以轻松实现诸如暂停/重启录音这样的功能,以及处理音频中断。同时,您还可以对录制格式保持完全的控制。
进行录制时,您需要提供一个声音文件的URL、建立音频会话、以及配置录音对象。进行这些准备工作的一个良好时机就是应用程序启动的时候,如程序清单7-10所示。诸如
soundFileURL
和
recording
这样的变量都在类接口文件中进行声明。
程序清单7-10 建立音频会话和声音文件的URL
- (void) viewDidLoad {
|
|
[super viewDidLoad];
|
|
NSString *tempDir = NSTemporaryDirectory ();
|
NSString *soundFilePath = [tempDir stringByAppendingString: @"sound.caf"];
|
|
NSURL *newURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
|
self.soundFileURL = newURL;
|
[newURL release];
|
|
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
audioSession.delegate = self;
|
[audioSession setActive: YES error: nil];
|
|
recording = NO;
|
playing = NO;
|
}
|
您需要在接口声明中加入
AVAudioSessionDelegate
、
AVAudioRecorderDelegate
、
AVAudioPlayerDelegate
(如果同时支持声音回放的话)协议。
然后,就可以实现如程序清单7-11所示的录制方法。
程序清单7-11 一个基于AVAudioRecorder类的录制/停止方法
-(IBAction) recordOrStop: (id) sender {
|
|
if (recording) {
|
|
[soundRecorder stop];
|
recording = NO;
|
self.soundRecorder = nil;
|
|
[recordOrStopButton setTitle: @"Record" forState: UIControlStateNormal];
|
[recordOrStopButton setTitle: @"Record" forState: UIControlStateHighlighted];
|
|
[[AVAudioSession sharedInstance] setActive: NO error: nil];
|
|
} else {
|
|
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryRecord error: nil];
|
|
NSDictionary *recordSettings =
|
[[NSDictionary alloc] initWithObjectsAndKeys:
|
[NSNumber numberWithFloat: 44100.0], AVSampleRateKey,
|
[NSNumber numberWithInt: kAudioFormatAppleLossless], AVFormatIDKey,
|
[NSNumber numberWithInt: 1], AVNumberOfChannelsKey,
|
[NSNumber numberWithInt: AVAudioQualityMax], AVEncoderAudioQualityKey,
|
nil];
|
|
AVAudioRecorder *newRecorder = [[AVAudioRecorder alloc] initWithURL: soundFileURL
|
settings: recordSettings
|
error: nil];
|
[recordSettings release];
|
self.soundRecorder = newRecorder;
|
[newRecorder release];
|
|
soundRecorder.delegate = self;
|
[soundRecorder prepareToRecord];
|
[soundRecorder record];
|
[recordOrStopButton setTitle: @"Stop" forState: UIControlStateNormal];
|
[recordOrStopButton setTitle: @"Stop" forState: UIControlStateHighlighted];
|
|
recording = YES;
|
}
|
}
|
有关
AVAudioRecorder
类的更多信息,请参见AVAudioRecorder类参考。
用音频队列服务进行录制
用音频队列服务进行录制时,您的应用程序需要配置音频会话、实例化一个录音音频队列对象,并为其提供一个回调函数。回调函数负责将音频数据存入内存以备随时使用,或者写入文件进行长期存储。
声音的录制发生在iPhone OS的系统定义级别(system-defined level)。系统会从用户选择的音频源取得输入—比如内置的麦克风、耳机麦克风(如果连接到iPhone上的话)、或者其它输入源。
和声音的回放一样,您可以通过查询音频队列对象的
kAudioQueueProperty_CurrentLevelMeter
属性来取得当前的录制音量,具体描述请见“指示回放音量”部分。
有关如何通过音频队列服务录制音频的详细实例,请参见音频队列服务编程指南的录制音频部分,实例代码则请见iPhone Dev Center网站上的SpeakHere。
解析音频流
为了播放音频流内容,比如来自网络连接的音频流,可以结合使用音频文件流服务和音频队列服务。音频文件流服务负责从常见的、采用网络位流格式的音频文件容器中解析出音频数据和元数据。您也可以用它来解析磁盘文件中的数据包和元数据。
iPhone OS可以解析的音频文件和位流格式和Mac OS X相同,具体如下:
MPEG-1 Audio Layer 3,用于.mp3文件
MPEG-2 ADTS,用于.aac音频数据格式
AIFC
AIFF
CAF
MPEG-4,用于.m4a、.mp4、和.3gp文件
NeXT
WAVE
在取得音频数据包之后,您就可以以任何iPhone OS系统支持的格式进行播放,这些格式在“音频回放和录制格式”部分中列出。
为了获得最好的性能,处理网络音频流的应用程序应该仅使用来自Wi-Fi连接的数据。您可以通过iPhone OS提供的System Configuration框架及其
SCNetworkReachability.h
头文件定义的接口来确定什么网络是可到达和可用的。如果需要实例代码,请参见iPhone Dev Center网站的Reachability工程。
为了连接网络音频流,可以使用iPhone OS系统中的Core Foundation框架中的接口,比如
CFHTTPMesaage
接口,具体描述请见CFHTTPMessage参考。通过音频文件流服务解析网络数据包,将它恢复为音频数据包,然后放入缓冲区,发送给负责回放的音频队列对象。
音频文件流服务依赖于音频文件服务定义的接口,比如
AudioFramePacketTranslation
结构和
AudioFilePacketTableInfo
结构,具体描述请见音频文件服务参考。
有关如何使用流的更多信息,请参见音频文件流服务参考。实例代码则请参见位于<Xcode>
/Examples/CoreAudio/Services/
目录下的AudioFileStream例子工程,其中<Xcode>是开发工具所在的目录。
iPhone OS系统上的音频单元支持
iPhone OS提供一组音频插件,称为音频单元,可以用于所有的应用程序。您可以通过Audio Unit框架提供的接口来打开、连接、和使用音频单元;还可以定义定制的音频单元,在自己的应用程序内部使用。由于应用程序必须静态连接定制的音频单元,所以iPhone OS系统上的其它应用程序不能使用您开发的音频单元。
表7-3列出了iPhone OS提供的音频单元。
音频单元 |
描述 |
---|---|
转换器单元 |
转换器单元,类型为 |
iPod均衡器单元 |
iPod EQ单元,类型为 |
3D混音器单元 |
3D混音器单元,类型为 |
多通道混音器单元 |
多通道混音器单元,类型为 |
一般输出单元 |
一般输出单元,类型为 |
I/O单元 |
I/O单元,类型为 |
语音处理I/O单元 |
语音处理I/O单元,类型为 |
有关系统音频单元的更多信息,请参见系统音频单元访问指南。
iPhone音频的最佳实践
操作音频的贴士
在操作iPhone OS系统上的音频内容时,您需要记住表7-4列出的基本贴士。
贴士 |
动作 |
---|---|
正确地使用压缩音频 |
对于AAC、MP3、和ALAC (Apple Lossless) 音频,解码过程是由硬件来完成的,虽然比较有效,但同时只能解码一个音频流。如果您需要同时播放多路声音,请使用IMA4 (压缩) 或者线性PCM (无压缩) 格式来存储那些文件。 |
将音频转换为您需要的数据格式和文件格式 |
Mac OS X的 |
评价音频的内存使用问题 |
当您使用音频队列服务播放音频时,需要编写一个回调函数,负责将较短的音频数据片断发送到音频队列的缓冲区。在某些情况下,将整个音频文件载入内存是最佳的选择,这样可以使播放时的磁盘访问尽最少;而在另外一些情况下,最好的方法则是每次只载入足够填满缓冲区的数据。请测试和评价哪种策略对您的应用程序最好。 |
限制音频的采样率和位深度,减少音频文件的尺寸 |
采样率和每个样本的位深度对无压缩音频的尺寸有直接的影响。如果您需要播放很多这样的声音,则应该考虑降低这些指标,以减少音频数据的内存开销。举例来说,相对于使用采样率为44.1 kHz的音频作为声音效果, 您可以使用采样率为32 kHz(或可能更低)的音频,仍然可以得到很合理的品质。 |
选择恰当的技术 |
使用Core Audio的系统声音服务来播放警告和用户界面声音效果。当您希望使用便利的高级接口来定位立体声场中的声音,或者要求很低的回放延迟时,则应该使用OpenAL。如果需要从文件或网络数据流中解析出音频数据,可以使用音频文件服务接口。如果只是简单回放一路或多路声音,则应该使用 |
低延迟编码 |
如果需要尽可能低的回放延迟,可以使用OpenAL,或者直接使用I/O单元。 |
iPhone OS偏好的音频格式
对于无压缩(最高品质)音频,请使用封装在CAF文件中的、16位、低位在前(little endian)的线性PCM音频数据。您可以用Mac OS X的
afconvert
命令行工具来将音频文件转换为上述格式:
/usr/bin/afconvert -f caff -d LEI16 {INPUT} {OUTPUT}
|
afconvert
工具可以进行广泛的音频数据格式和文件类型转换。您可以通过
afconvert
的手册页面,以及在shell提示符下键入
afconvert -h
命令获取更多信息。
对于压缩音频,当每次只需播放一个声音,或者当不需要和iPod同时播放音频时,适合使用AAC格式的CAF或m4a文件。
当您需要在同时播放多路声音时减少内存开销时,请使用IMA4 (IMA/ADPCM) 压缩格式,这样可以减少文件尺寸,同时在解压缩过程中对CPU的影响又最小。和线性PCM数据一样,请将IMA4数据封装在CAF文件中。
在iPhone OS使用视频
录制视频
从iPhone OS 3.0开始,您可以在具有录制支持的设备上录制视频,包括当时的音频。显示视频录制界面的方法是创建和推出一个
UIImagePickerController
对象,和显示静态图片照相机界面完全一样。
在录制视频时,您必须首先检查是否存在照相机源类型 (
UIImagePickerControllerSourceTypeCamera
) ,以及照相机是否支持电影媒体类型 (
kUTTypeMovie
) 。根据您为
mediaTypes
属性分配的媒体类型的不同,选择器对象可以直接显示静态图像照相机,或者视频摄像机,还可以显示一个选择界面,让用户选择。
使用
UIImagePickerControllerDelegate
协议,注册为图像选择器的委托。在视频录制完成时,您的委托对象的
imagePickerController:didFinishPickingMediaWithInfo:
方法会备调用。
对于支持录制的设备,您也可以从用户照片库中选择之前录制的视频。
有关如何使用图像选择器的更多信息,请参见UIImagePickerController类参考。
播放视频文件
在iPhone OS系统上,应用程序可以通过Media Player框架(
MediaPlayer.framework
)来播放视频文件。视频的回放只支持全屏模式,需要播放场景切换动画的游戏开发者或需要播放媒体文件的其它开发者可以使用。当应用程序开始播放视频时,媒体播放器界面就会接管,将屏幕渐变为黑色,然后渐渐显示视频内容。视频播放界面上可以显示或者不显示调整回放的用户控件。您可以通过部分或全部激活这些控件(如图7-2所示),使用户可以改变音量、改变回放点、开始或停止视频的播放。如果禁用所有的控件,视频会一直播放,直到结束。
图7-2 带有播放控制的媒体播放器界面
在开始播放前,您必须知道希望播放的URL。对于应用程序提供的文件,这个URL通常是指向应用程序包中某个文件的指针;但是,它也可以是指向远程服务器文件的指针。您可以用这个URL来实例化一个新的
MPMoviePlayerController
类的实例。这个类负责视频文件的回放和管理用户交互,比如响应用户对播放控制(如果显示的话)的触击动作。简单调用控制器的
play
方法,就可以开始播放了。
程序清单7-12显示一个实例方法,功能是播放位于指定URL的视频。play方法是异步的调用,在电影播放时会将控制权返回给调用者。电影控制器负责将电影载入一个全屏的视图,并通过动画效果将电影放到应用程序现有内容的上方。在视频回放完成后,电影控制器会向委托对象发出一个通告,该委托对象负责在不再需要时释放电影控制器。
程序清单7-12 播放全屏电影
-(void)playMovieAtURL:(NSURL*)theURL
|
{
|
MPMoviePlayerController* theMovie = [[MPMoviePlayerController alloc] initWithContentURL:theURL];
|
|
theMovie.scalingMode = MPMovieScalingModeAspectFill;
|
theMovie.movieControlMode = MPMovieControlModeHidden;
|
|
// Register for the playback finished notification.
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
selector:@selector(myMovieFinishedCallback:)
|
name:MPMoviePlayerPlaybackDidFinishNotification
|
object:theMovie];
|
|
// Movie playback is asynchronous, so this method returns immediately.
|
[theMovie play];
|
}
|
|
// When the movie is done, release the controller.
|
-(void)myMovieFinishedCallback:(NSNotification*)aNotification
|
{
|
MPMoviePlayerController* theMovie = [aNotification object];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
name:MPMoviePlayerPlaybackDidFinishNotification
|
object:theMovie];
|
|
// Release the movie instance created in playMovieAtURL:
|
[theMovie release];
|
}
|
有关Media Player框架的各个类的更多信息,请参见Media Player框架参考。有关它支持的视频格式列表,请参见iPhone OS技术概览。