在 iPhone 应用或者是游戏的开发过程中,对声音的支持是必不可少的。在我做过的几个应用中,每个都涉及到音效,所以在这里做个简单的归纳,很多都是引用自《iPhone Application Programming Guide》(需要有 Apple ID 才能打开链接),加了一些实际使用的经验。
iPhone OS 主要提供以下了几种播放音频的方法:
System Sound Services
AVAudioPlayer 类
Audio Queue Services
OpenAL
■ 声音长度要小于 30 秒
■ In linear PCM 或者 IMA4 (IMA/ADPCM) 格式的
■ 打包成 .caf, .aif, 或者 .wav 的文件
■ 不能控制播放的进度
■ 调用方法后立即播放声音
■ 没有循环播放和立体声控制
另外,它还可以调用系统的震动功能,方法也很简单。具体的代码可以参考官方的示例 SysSound
,但是官方的示例只有一些简单的用法,从文档中我们发现可以通过 AudioServicesAddSystemSoundCompletion 方法为音频播放添加 CallBack 函数,有了 CallBack 函数我们可以解决不少问题,比如可以克服 System Sound Services 本身不支持循环播放的问题。以下代码可以实现一个在程序中循环播放的背景音乐:
- (void)buttonPress:(id)sender
3、按钮关联的方法实现
// 自定义的c函数,用于清理音频对象
static void onSystemSoundCompletionCallBack(SystemSoundID ssID ,void *clientData)
{
// 丢弃
AudioServicesDisposeSystemSoundID(ssID);
}
-(void)buttonPress:(id)sender
{
//获取声音
NSString *path = [[NSBundle mainBundle]pathForResource:@"han"ofType:@"wav"];
NSURL *url = [NSURL fileURLWithPath:path];
// 获取系统声音的id
SystemSoundID ssID = 0;
//将指定的文件创建为系统声音
AudioServicesCreateSystemSoundID((CFURLRef)url, &ssID);
//注册回调函数,播放完后清理音频对象
AudioServicesAddSystemSoundCompletion(ssID,NULL,NULL, onSystemSoundCompletionCallBack, (void *)self);
// 播放
AudioServicesPlaySystemSound(ssID);
}
AVAudioPlayer 是 AVFoundation.framework 中定义的一个类,所以使用要先在工程中引入 AVFoundation.framework。我们可以把 AVAudioPlayer 看作是一个高级的播放器,它支持广泛的音频格式,主要是以下这些格式:
■ AAC
■ AMR(AdaptiveMulti-Rate, aformatforspeech)
■ ALAC(AppleLossless)
■ iLBC(internetLowBitrateCodec, anotherformatforspeech)
■ IMA4(IMA/ADPCM)
■ linearPCM(uncompressed)
■ μ-lawanda-law
■ MP3(MPEG-1audiolayer3
AVAudioPlayer 可以播放任意长度的音频文件、支持循环播放、可以同步播放多个音频文件、控制播放进度以及从音频文件的任意一点开始播放等,更高级的功能可以参考 AVAudioPlayer 的文档。要使用 AVAudioPlayer 的对象播放文件,你只需为其指定一个音频文件并设定一个实现了 AVAudioPlayerDelegate 协议的 delegate 对象。这里创建一个类似音乐播放器的界面,可以实现暂停/播放、快进/快退等功能。
#import <UIKit/UIKit.h>
@interface QQViewController :UIViewController<UITableViewDataSource,UITableViewDelegate>
@property(retain,nonatomic)NSArray *arr;
@end
在.m文件里代码:
#import "QQViewController.h"
#import "QQAAudioPlayerViewController.h"
@interface QQViewController ()
@end
@implementation QQViewController
- (void)dealloc
{
[_arrrelease];
[superdealloc];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [superinitWithNibName:nibNameOrNilbundle:nibBundleOrNil];
if (self) {
self.navigationItem.title =@"音乐列表";
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[superviewDidLoad];
UITableView *myTableView = [[UITableViewalloc]initWithFrame:self.view.framestyle:UITableViewStylePlain];
myTableView.delegate =self;
myTableView.dataSource =self;
myTableView.separatorStyle =UITableViewCellSelectionStyleBlue;
[self.viewaddSubview:myTableView];
[myTableViewrelease];
self.arr = [NSArrayarrayWithObjects:@"123",@"hinder",@"test",@"The Sound Of Slience",nil];
}
#pragma =================UITableView DataSource=================
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.arr.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
staticNSString *str =@"identify";
UITableViewCell *pCell = [tableViewdequeueReusableCellWithIdentifier:str];
if(pCell ==nil)
{
pCell = [[UITableViewCellalloc]initWithStyle:UITableViewCellStyleSubtitlereuseIdentifier:str];
}
pCell.textLabel.text = [self.arrobjectAtIndex:[indexPathrow]];
pCell.detailTextLabel.text =@"music";
return pCell;
}
#pragma tableview Delegate=============================
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
QQAAudioPlayerViewController *pAudio = [[QQAAudioPlayerViewControlleralloc]init];
[self.navigationControllerpushViewController:pAudioanimated:YES];
}
//音乐播放器
@property (nonatomic,retain)AVAudioPlayer *audioPlayer;
//选中音乐的名称
@property (nonatomic,retain)NSString *selectMusicName;
//播放状态
@property (nonatomic,assign)BOOL isPlayState;
//更新时间标签和进度指示器
@property (nonatomic,retain)NSTimer *timer;
//播放按钮事件
-(void)playAndPauseBtnPress:(id)sender;
//播放音乐
-(void)playMusic;
//暂停音乐
-(void)pauseMusic;
//将时间拆分的方法
-(NSString *)formateTime:(NSTimeInterval)_time;
//更新时间标签和进度指示器方法
-(void)updateMeters;
//播放进度指示器改变后触发方法
-(void)progressChange:(id)sender;
//播放进度指示器会改变完成后触发方法
-(void)progressDidChanged:(id)sender;
//音量调节器改变处罚方法
-(void)volumeChange:(id)sender;
#import "QQAAudioPlayerViewController.h"
enum{
BUTTON_TAG =1,
LABEL1_TAG,
LABEL2_TAG,
SLI_VOICE_TAG,
SLI_SONG_TAG,
};
@interface QQAAudioPlayerViewController ()
@end
@implementation QQAAudioPlayerViewController
- (void)dealloc
{
[_audioPlayer release];
[_selectMusicNamerelease];
[_timerrelease];
[superdealloc];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [superinitWithNibName:nibNameOrNilbundle:nibBundleOrNil];
if (self) {
self.navigationItem.title =@"正在播放";
// Custom initialization
}
return self;
}
- (void)viewWillAppear:(BOOL)animated
{
NSString *path = [[NSBundlemainBundle]pathForResource:self.selectMusicNameofType:@"mp3"];
NSURL *url = [NSURLfileURLWithPath:path];
self.audioPlayer = [[AVAudioPlayeralloc]initWithContentsOfURL:urlerror:nil];
self.audioPlayer.delegate =self;
self.audioPlayer.meteringEnabled =YES;
[self.audioPlayerplay];
self.isPlayeState =NO;
}
- (void)viewWillDisappear:(BOOL)animated
{
[self.audioPlayerstop];
[self.audioPlayerrelease];
self.audioPlayer =nil;
}
- (void)viewDidLoad
{
[superviewDidLoad];
self.title =self.selectMusicName;
//创建play/pause按钮
UIButton *audioButton = [UIButtonbuttonWithType:UIButtonTypeRoundedRect];
audioButton.frame =CGRectMake(120,80,80, 40);
audioButton.tag =BUTTON_TAG;
[audioButton setTitle:@"play"forState:UIControlStateNormal];
[audioButton addTarget:selfaction:@selector(playAndPauseBtnPress:)forControlEvents:UIControlEventTouchUpInside];
[self.viewaddSubview:audioButton];
//创建播放时间按钮
UILabel *label = [[UILabelalloc]initWithFrame:CGRectMake(100,160,120, 30)];
label.tag =LABEL1_TAG;
label.text =@"00:00 of 00:00";
[self.viewaddSubview: label];
[labelrelease];
//创建音量进度条
UISlider *voiceSlider = [[UISlideralloc]initWithFrame:CGRectMake(100,220,200, 20)];
voiceSlider.tag =SLI_VOICE_TAG;
[self.viewaddSubview:voiceSlider];
[voiceSliderrelease];
[voiceSlider addTarget:selfaction:@selector(volumeChange:)forControlEvents:UIControlEventValueChanged];
//创建音量标签
UILabel *voiceLabel = [[UILabelalloc]initWithFrame:CGRectMake(10,220,85, 20)];
voiceLabel.text =@"音量";
[self.viewaddSubview:voiceLabel];
[voiceLabelrelease];
//创建播放进度条
UISlider *songSider = [[UISlideralloc]initWithFrame:CGRectMake(100,280,210, 20)];
songSider.thumbTintColor = [UIColorgreenColor];
songSider.tintColor = [UIColorredColor];
[songSider setThumbImage:[UIImageimageNamed:@""]forState:UIControlStateNormal];
songSider.tag =SLI_SONG_TAG;
[self.viewaddSubview:songSider];
[songSiderrelease];
[songSider addTarget:selfaction:@selector(progressChange:)forControlEvents:UIControlEventValueChanged];
[songSider addTarget:selfaction:@selector(progressDidChang:)forControlEvents:UIControlEventValueChanged];
//创建播放标签
UILabel *songLabel = [[UILabelalloc]initWithFrame:CGRectMake(10,280,85, 20)];
songLabel.text =@"播放进度";
[self.viewaddSubview:songLabel];
[songLabelrelease];
// Do any additional setup after loading the view.
}
//播放/暂停按钮关联方法
- (void)playAndPauseBtnPress:(id)sender
{
UIButton *pBtn = (UIButton *)[self.viewviewWithTag:BUTTON_TAG];
self.isPlayeState = !self.isPlayeState;
if(self.isPlayeState)
{
[pBtn setTitle:@"pause"forState:UIControlStateNormal];
[selfplayeMusic];
}
else
{
[pBtn setTitle:@"play"forState:UIControlStateNormal];
[selfpauseMusic];
}
}
//播放状态关联的方法
- (void)playeMusic
{
[self.audioPlayerplay];
UISlider *voice = (UISlider *)[self.viewviewWithTag:SLI_VOICE_TAG];
UISlider *song = (UISlider *)[self.viewviewWithTag:SLI_SONG_TAG];
UILabel *timeLabel = (UILabel *)[self.viewviewWithTag:LABEL1_TAG];
[timeLabelsetEnabled:YES];
voice.enabled =YES;
song.enabled =YES;
voice.value =self.audioPlayer.volume;
self.timer = [NSTimerscheduledTimerWithTimeInterval:0.1ftarget:selfselector:@selector(updateMeters)userInfo:Nilrepeats:YES];
}
//暂停按钮关联的方法
- (void)pauseMusic
{
[self.audioPlayerpause];
UISlider *voice = (UISlider *)[self.viewviewWithTag:SLI_VOICE_TAG];
UISlider *song = (UISlider *)[self.viewviewWithTag:SLI_SONG_TAG];
[self.timerinvalidate];
voice.enabled =NO;
song.enabled =NO;
}
- (void)updateMeters
{
[self.audioPlayerupdateMeters];
UISlider *song = (UISlider *)[self.viewviewWithTag:SLI_SONG_TAG];
song.value = self.audioPlayer.currentTime/self.audioPlayer.duration;
UILabel *timerLbel = (UILabel *)[self.viewviewWithTag:LABEL1_TAG];
timerLbel.text = [NSStringstringWithFormat:@"%@ of %@",[selfformateTime:self.audioPlayer.currentTime],[selfformateTime:self.audioPlayer.duration]];
}
//分割时间的方法
- (NSString *)formateTime:(NSTimeInterval)_time
{
int num = (int)_time;
int min = num/60;
int sec = num%60;
if(num <60)
{
return [NSStringstringWithFormat:@"0:%2d",sec];
}
else
{
return [NSStringstringWithFormat:@"%2d:%2d",min,sec];
}
}
#pragma mark-指示器触发
- (void)progressChange:(id)sender
{
[self.audioPlayerpause];
UISlider *song = (UISlider *)[self.viewviewWithTag:SLI_SONG_TAG];
UILabel *timerLabel = (UILabel *)[self.viewviewWithTag:LABEL1_TAG];
self.audioPlayer.currentTime = song.value * self.audioPlayer.duration;
timerLabel.text = [NSStringstringWithFormat:@"%@ of %@",[selfformateTime:self.audioPlayer.currentTime],[selfformateTime:self.audioPlayer.duration]];
}
//播放进度指示条改变时关联的方法
- (void)progressDidChang:(id)sender
{
[self.audioPlayerplay];
}
//播放进度指示条改变完关联的方法
- (void)volumeChange:(id)sender
{
UISlider *voice = (UISlider *)[self.viewviewWithTag:SLI_VOICE_TAG];
self.audioPlayer.volume = voice.value;
}
#pragma mark AVAudioPlayerDelegate
//音乐播放完关联的方法
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
UISlider *voice = (UISlider *)[self.viewviewWithTag:SLI_VOICE_TAG];
UISlider *song = (UISlider *)[self.viewviewWithTag:SLI_SONG_TAG];
voice.enabled =NO;
song.enabled =NO;
UIButton *pBtn = (UIButton *)[self.viewviewWithTag:BUTTON_TAG];
[pBtn setTitle:@"plaay"forState:UIControlStateNormal];
}
选中某一行后:
点击“play”按钮,开始播放音乐,同时按钮标题变为pause。
3. Audio Queue Services
如果以上两种音频播放的解决方案都无法满足你的需求,那么我想你肯定需要使用 Audio Queue Services。使用 Audio Queue Services 对音频进行播放,你可以完全实现对声音的控制。例如,你可以在声音数据从文件读到内存缓冲区后对声音进行一定处理再进行播放,从而实现对音频的快速/慢速播放的功能。
因为 Audio Queue Services 相对复杂很多,Apple 官方已经把它整理为一本书了,具体可以参考 Audio Queue Services Programming Guide 和 SpeakHere 的程序示例。
4. OpenAL
OpenAL 是一套跨平台的开源的音频处理接口,与图形处理的 OpenGL 类似,它为音频播放提供了一套更加优化的方案。它最适合开发游戏的音效,用法也与其他平台下相同。
iPhone 支持 OpenAL 1.1,我没有在实际开发中使用过,具体的文档可以参考 OpenAL 的网站 http://openal.org和 oalTouch 的程序示例。