简单来说,音频可以分为 2 种:

  • 音效:又称『短音频』,通常在程序中的播放时长为 1~2 秒,在应用程序中起到点缀效果,提升整体用户体验。
  • 音乐:比如游戏中的『背景音乐』,一般播放时间较长。

播放音频可以使用框架 AVFoundation.framework。

小 Tips:

运行程序的时候,打个断点(Breakpoints),然后在 Output(Xcode 右下角平时 NSLog 输出的地方)里输入 po NSHomeDirectory() 即可查看模拟器的 Home 目录(有自动提示的):

1
2
(lldb) po NSHomeDirectory()
/Users/apple/Library/Developer/CoreSimulator/Devices/1DF0CC85-C256-48B4-8569-99C5C0D14C9D/data/Containers/Data/Application/315D2225-0476-46FE-8B33-A827AC2D44A0

录音

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
@property (strong, nonatomic) AVAudioRecorder *audioRecorder;
@end
@implementation ViewController
- (IBAction)begin {
NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"Yiyi.caf"];
NSURL *url = [NSURL URLWithString:filePath]; // 保存格式不一定就是它的音频格式(这里也可以写 wav)
self.audioRecorder = [[AVAudioRecorder alloc] initWithURL:url settings:@{} error:nil];
BOOL prepared = [self.audioRecorder prepareToRecord];
if (prepared) {
NSLog(@"准备好了");
BOOL record = [self.audioRecorder record];
NSLog(@"record -> %d", record);
} else {
NSLog(@"准备失败");
}
}
- (IBAction)pause {
[self.audioRecorder pause];
}
- (IBAction)resume {
[self.audioRecorder record];
}
- (IBAction)stop {
[self.audioRecorder stop];
}
@end

播放音效(短音频)

在 iOS 中,音效播放常见函数总结:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 加载音效文件
AudioServicesCreateSystemSoundID(CFURLRef inFileURL, SystemSoundID *outSystemSoundID)
// 释放音效资源
AudioServicesDisposeSystemSoundID(SystemSoundID inSystemSoundID)
// 播放音效
void AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID);
// 播放音效带点震动
void AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID);
// 关于播放音效,上面两个方法在未来即将会废弃,使用下面两个方法
void AudioServicesPlaySystemSoundWithCompletion(SystemSoundID inSystemSoundID, void (^inCompletionBlock)(void));
void AudioServicesPlayAlertSoundWithCompletion(SystemSoundID inSystemSoundID, void (^inCompletionBlock)(void));

先导入音频(可以去其他的 App 找音频来测试):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (IBAction)play {
// 音效资源地址
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"buyao" withExtension:@"wav"]; // 后缀可以在第一个参数或者第二个参数写,当时候将 aac 格式的错写成 acc,播放不了!
CFURLRef fileURLRef = (__bridge CFURLRef)fileURL;
// 只需要创建 1 次,所以可以使用懒加载也可以把它装到一个数组里
SystemSoundID systemSoundID = 0;
// 创建 SystemSoundID
AudioServicesCreateSystemSoundID(fileURLRef, &systemSoundID);
// 根据 SystemSoundID 播放音效
AudioServicesPlaySystemSound(systemSoundID);
}

封装播放音效工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#import "ZLAudioPlayerUtil.h"
#import <AVFoundation/AVFoundation.h>
static NSMutableDictionary *_dict;
@implementation ZLAudioPlayerUtil
// 方法 1:在第一次使用这个类的时候初始化,以后用 _dict
//+ (void)initialize {
// _dict = [NSMutableDictionary dictionary];
//}
// 方法 2:懒加载(以后可以直接使用 self.dict)
+ (NSMutableDictionary *)dict {
if (!_dict) {
_dict = [NSMutableDictionary dictionary];
}
return _dict;
}
+ (void)playAudioWithSoundName:(NSString *)soundName {
SystemSoundID systemSoundID = [self.dict[soundName] unsignedIntValue];
if (systemSoundID == 0) {
// 音效资源地址
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:soundName withExtension:nil];
CFURLRef fileURLRef = (__bridge CFURLRef)fileURL;
// 创建 SystemSoundID
AudioServicesCreateSystemSoundID(fileURLRef, &systemSoundID);
// 保存到字典
[self.dict setValue:@(systemSoundID) forKey:soundName];;
}
// 根据 SystemSoundID 播放音效
AudioServicesPlaySystemSound(systemSoundID);
// 如果想监听播放完毕,可以使用
AudioServicesPlaySystemSoundWithCompletion(systemSoundID, ^{
NSLog(@"complete");
});
}
@end

播放音乐

播放音乐需要用到 AVAudioPlayer 这个类,在使用的时候要注意:

  • 需要导入 AVFoundation 框架
  • AVAudioPlayer 必须写为全局变量,才可以播放音乐(不然局部变量一创建就是腹死胎中了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
@property (strong, nonatomic) AVAudioPlayer *audioPlayer;
@property (weak, nonatomic) IBOutlet UIButton *controlButton;
@end
@implementation ViewController
- (AVAudioPlayer *)audioPlayer {
if (!_audioPlayer) {
NSURL *fileURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"汪东城 - 半个人" ofType:@"mp3"]];
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
}
return _audioPlayer;
}
- (IBAction)controlPlayOrPause {
if ([self.audioPlayer isPlaying]) {
// 暂停音乐
[self pauseMusic];
} else {
// 继续播放音乐
[self playMusic];
}
}
- (IBAction)stop {
[self.audioPlayer stop]; // 这个方法不会重置 currentTime 的值为 0,换句话来说,如果你在播放的时候调用了 stop 方法,再播放的时候会从播放进度开始(啊,看官方解释),所以如果停止后是重新从 0 开始播放,就要将播放对象置为 nil
self.audioPlayer = nil;
[self.controlButton setTitle:@"▶️" forState:UIControlStateNormal];
}
- (void)playMusic {
[self.audioPlayer prepareToPlay];
[self.audioPlayer play];
[self.controlButton setTitle:@"⏸" forState:UIControlStateNormal];
}
- (void)pauseMusic {
[self.audioPlayer pause];
[self.controlButton setTitle:@"▶️" forState:UIControlStateNormal];
}
@end

如果想切换音乐,是不可以直接改 AVAudioPlayer 的 url 属性的(readonly),需要重新创建一个 AVAudioPlayer 对象来播放。如果想封装一个音乐工具类,就可以把音乐和对应的 AVAudioPlayer 对象放到一个字典,和上面封装音效的差不多。