iOS开发基础62-音频开发:音效、音乐播放、录音、AVAudioSession 与后台播放
iOS 音频开发深度解析:音效、音乐播放、录音、AVAudioSession 与后台播放
本文系统梳理 iOS 音频开发:音效(System Sound Services)与音乐(AVAudioPlayer)的区别与播放、录音(AVAudioRecorder)的完整配置与权限、音频会话 AVAudioSession 的类别管理与中断处理、后台播放与远程控制、以及音频工具类封装。着重补充现代音频开发的核心细节。
一、音频简介
iOS 中音频按用途分为两类:
| 类型 | 别称 | 时长 | 作用 | 推荐框架 |
|---|---|---|---|---|
| 音效 | 短音频 | 1~2 秒 | 点缀效果(点击声、提示音、爆炸声) | System Sound Services / AVAudioPlayer |
| 音乐 | 长音频 | 较长(背景音乐) | 持续播放 | AVAudioPlayer / AVPlayer |
播放音频主要使用 AVFoundation.framework。
音效为什么不用 AVAudioPlayer:System Sound Services 更轻量、启动延迟更低(毫秒级),适合频繁触发的短音效;AVAudioPlayer 初始化有开销,适合较长的音乐。但 System Sound Services 有格式和时长限制(见下文)。
二、音效播放
1. 基本使用
音效播放使用 System Sound Services(基于 AudioToolbox 框架):
#import <AudioToolbox/AudioToolbox.h>
// 1. 获得音效文件路径
NSURL *url = [[NSBundle mainBundle] URLForResource:@"m_03" withExtension:@"wav"];
// 2. 加载音效文件,得到音效 ID
SystemSoundID soundID = 0;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &soundID);
// 3. 播放音效
// 仅播放音效
AudioServicesPlaySystemSound(soundID);
// 播放音效同时震动(仅真机生效,模拟器无震动)
// AudioServicesPlayAlertSound(soundID);
音效文件只需加载一次:加载后 soundID 可重复使用,避免每次播放都重新加载(有性能开销)。
2. 常用函数
| 函数 | 作用 |
|---|---|
AudioServicesCreateSystemSoundID(CFURLRef, SystemSoundID *) |
加载音效文件,生成 soundID |
AudioServicesDisposeSystemSoundID(SystemSoundID) |
释放音效资源 |
AudioServicesPlaySystemSound(SystemSoundID) |
播放音效 |
AudioServicesPlayAlertSound(SystemSoundID) |
播放音效 + 震动 |
3. 注意事项
- 支持格式:仅支持
CAF、WAV、aif/aifc等线性 PCM 或 IMA4 格式,不支持 MP3。 - 时长限制:音效时长不超过 30 秒,超过会播放失败。
- 震动:
AudioServicesPlayAlertSound的震动仅在真机生效,模拟器无震动硬件。 - 音量:音效音量跟随系统铃声音量,不受 App 内 AVAudioPlayer 音量控制。
- iOS 9 废弃:
AudioServicesPlaySystemSound和AudioServicesPlayAlertSound在 iOS 9 被标记为 deprecated,推荐使用带 completion 的版本或 AVAudioPlayer:
// iOS 9+ 推荐:带完成回调
AudioServicesPlaySystemSoundWithCompletion(soundID, ^{
NSLog(@"音效播放完成");
});
// 或直接用 AVAudioPlayer 播放短音效(更灵活,支持 MP3)
三、音乐播放
1. AVAudioPlayer 简介
较长的音乐(背景音乐)使用 AVAudioPlayer,它是 AVFoundation 提供的音频播放器,支持本地音频文件的播放、暂停、循环、音量、速率等控制。
重要限制:AVAudioPlayer 只支持本地文件(NSURL 或 NSData),不支持远程流媒体 URL。远程音频流播放请使用 AVPlayer(AVFoundation 中更底层的播放器,支持本地和远程)。
2. 常用方法
#import <AVFoundation/AVFoundation.h>
// 加载音乐文件(本地 URL)
NSURL *url = [[NSBundle mainBundle] URLForResource:@"bgm" withExtension:@"mp3"];
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
// 或从 NSData 加载
// AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:data error:nil];
// 准备播放(预缓冲,提高播放流畅性,建议调用)
[player prepareToPlay];
// 播放(异步)
[player play];
// 暂停
[player pause];
// 停止(停止后 currentTime 归零,再次 play 从头开始)
[player stop];
3. 常用属性
| 属性 | 类型 | 说明 |
|---|---|---|
isPlaying |
BOOL(只读) | 是否正在播放 |
duration |
NSTimeInterval(只读) | 总时长(秒) |
currentTime |
NSTimeInterval | 当前播放位置(可设置,用于拖拽进度) |
numberOfLoops |
NSInteger | 循环次数:-1 无限循环,0 播放 1 次,n 播放 n+1 次 |
volume |
float | 音量(0.0 ~ 1.0) |
enableRate |
BOOL | 是否允许变速(必须先设 YES 才能设置 rate) |
rate |
float | 播放速率(1.0 正常,0.5 半速,2.0 双倍) |
numberOfChannels |
NSUInteger(只读) | 声道数 |
pan |
float | 声道平衡(-1.0 左声道,0 中间,1.0 右声道) |
meteringEnabled |
BOOL | 是否启用音量测量 |
delegate |
id |
代理(播放完成、解码错误、中断回调) |
4. 音量测量
player.meteringEnabled = YES; // 启用测量
// 播放过程中定时调用
[player updateMeters]; // 更新测量值
float avgPower = [player averagePowerForChannel:0]; // 平均音量(分贝,-160 ~ 0)
float peakPower = [player peakPowerForChannel:0]; // 峰值音量
音量单位是分贝(dB),范围 -160(静音)~ 0(最大),需要转换为线性值才能用于 UI 显示。
5. 代理方法
@interface ViewController () <AVAudioPlayerDelegate>
@end
// 播放完成
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
NSLog(@"播放完成");
}
// 解码错误
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error {
NSLog(@"解码错误: %@", error);
}
// 播放中断(电话、闹钟等)
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player {
NSLog(@"开始中断,暂停播放");
}
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flags {
if (flags == AVAudioSessionInterruptionOptionShouldResume) {
[player play]; // 中断结束,恢复播放
}
}
6. AVAudioPlayer vs AVPlayer
| 对比项 | AVAudioPlayer | AVPlayer |
|---|---|---|
| 本地文件 | 支持 | 支持 |
| 远程流媒体 | 不支持 | 支持 |
| 播放控制 | 简单(play/pause/stop) | 更底层(需自己管理 rate、timeControlStatus) |
| 进度监听 | currentTime 轮询 | addPeriodicTimeObserver(Block 回调) |
| 适用场景 | 本地背景音乐、短音效 | 网络音频、视频播放 |
四、录音
1. 录音权限(iOS 10+ 必须)
iOS 10 起,访问麦克风必须在 Info.plist 中添加 NSMicrophoneUsageDescription(麦克风使用说明),否则调用录音会直接崩溃:
<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风进行录音</string>
2. 音频会话设置
录音前需要配置 AVAudioSession 类别为 Record 或 PlayAndRecord,否则可能录音失败或无声:
#import <AVFoundation/AVFoundation.h>
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
// 设置类别为录音(只录音不播放)
[session setCategory:AVAudioSessionCategoryRecord error:&error];
// 或 PlayAndRecord(同时播放和录音,如 K 歌、语音通话)
// [session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
[session setActive:YES error:&error];
3. AVAudioRecorder 完整实现
@interface ViewController ()
@property (nonatomic, strong) AVAudioRecorder *recorder;
@end
@implementation ViewController
#pragma mark - 录音控制
- (IBAction)startRecord {
// 请求麦克风权限(iOS 8+)
[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
if (granted) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.recorder record];
});
} else {
NSLog(@"用户拒绝了麦克风权限");
}
}];
}
- (IBAction)stopRecord {
[self.recorder stop];
}
#pragma mark - 懒加载
- (AVAudioRecorder *)recorder {
if (!_recorder) {
// 1. 录音文件存放路径(Documents 目录)
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath = [docPath stringByAppendingPathComponent:@"record.caf"];
NSURL *url = [NSURL fileURLWithPath:filePath];
// 2. 录音设置(格式、采样率、声道、质量)
NSDictionary *settings = @{
AVFormatIDKey : @(kAudioFormatMPEG4AAC), // 格式:AAC(压缩,体积小)
// AVFormatIDKey : @(kAudioFormatLinearPCM), // 或 PCM(无损,体积大)
AVSampleRateKey : @(44100), // 采样率:44100(CD 音质)
AVNumberOfChannelsKey: @(1), // 声道数:1(单声道)
AVEncoderAudioQualityKey: @(AVAudioQualityHigh) // 编码质量:高
};
// 3. 创建录音对象
NSError *error = nil;
_recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:&error];
_recorder.meteringEnabled = YES; // 启用音量测量(可选)
if (error) {
NSLog(@"录音对象创建失败: %@", error);
}
}
return _recorder;
}
@end
格式选择:
kAudioFormatMPEG4AAC:AAC 压缩格式,体积小,适合语音备忘录、聊天语音。kAudioFormatLinearPCM:无损 PCM,体积大,适合后期音频处理。- 文件扩展名应与格式匹配:AAC 用
.m4a或.caf,PCM 用.caf或.wav。
4. 录音音量监控
// 启用测量后,定时调用
[self.recorder updateMeters];
float avgPower = [self.recorder averagePowerForChannel:0]; // 平均音量
float peakPower = [self.recorder peakPowerForChannel:0]; // 峰值音量
五、音频会话 AVAudioSession(现代音频核心)
1. 为什么需要 AVAudioSession
AVAudioSession 是 iOS 音频的"总开关",管理 App 与系统音频的交互:
- 决定 App 音频是否与其他 App 音频混音。
- 决定音频被中断(电话、闹钟、Siri)时的行为。
- 决定音频路由(扬声器、耳机、蓝牙)。
- 决定是否支持后台播放。
每个 App 只有一个 AVAudioSession 单例:[AVAudioSession sharedInstance]。
2. 常用类别(Category)
| 类别 | 作用 | 混音 | 后台播放 | 典型场景 |
|---|---|---|---|---|
AVAudioSessionCategoryAmbient |
静音键和锁屏时静音 | 是 | 否 | 游戏背景音、音效 |
AVAudioSessionCategorySoloAmbient |
默认类别,静音键和锁屏时静音 | 否(独占) | 否 | 普通音乐播放 |
AVAudioSessionCategoryPlayback |
静音键和锁屏不静音 | 可选 | 是(需配置) | 音乐播放器、视频音频 |
AVAudioSessionCategoryRecord |
只录音 | - | 是(需配置) | 录音、语音识别 |
AVAudioSessionCategoryPlayAndRecord |
同时播放和录音 | 可选 | 是 | K 歌、语音通话、VoIP |
AVAudioSessionCategoryMultiRoute |
多路由输出 | - | - | 复杂音频设备 |
AVAudioSession *session = [AVAudioSession sharedInstance];
// 音乐播放器:Playback 类别,支持后台播放
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
3. 中断处理
音频被电话、闹钟、Siri 等中断时,系统发送 AVAudioSessionInterruptionNotification:
// 注册中断通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleInterruption:)
name:AVAudioSessionInterruptionNotification
object:nil];
- (void)handleInterruption:(NSNotification *)note {
NSDictionary *info = note.userInfo;
AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
if (type == AVAudioSessionInterruptionTypeBegan) {
// 中断开始:暂停播放
[self.player pause];
} else if (type == AVAudioSessionInterruptionTypeEnded) {
// 中断结束:判断是否应恢复
AVAudioSessionInterruptionOptions options = [info[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
if (options == AVAudioSessionInterruptionOptionShouldResume) {
[self.player play];
}
}
}
4. 路由变化(耳机插拔、蓝牙连接)
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleRouteChange:)
name:AVAudioSessionRouteChangeNotification
object:nil];
- (void)handleRouteChange:(NSNotification *)note {
NSDictionary *info = note.userInfo;
AVAudioSessionRouteChangeReason reason = [info[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];
if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
// 耳机拔出:暂停播放(苹果官方推荐行为)
[self.player pause];
}
}
六、后台播放与远程控制(现代重点)
1. 开启后台播放
- 在 Info.plist 中添加后台模式:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
或在 Xcode → Target → Capabilities → Background Modes → 勾选 Audio, AirPlay, and Picture in Picture。
- 设置 AVAudioSession 类别为
Playback:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
完成后,App 进入后台时音频继续播放。
2. 锁屏信息(Now Playing Info)
在锁屏和控制中心显示歌曲信息:
#import <MediaPlayer/MediaPlayer.h>
MPNowPlayingInfoCenter *center = [MPNowPlayingInfoCenter defaultCenter];
NSMutableDictionary *info = [NSMutableDictionary dictionary];
info[MPMediaItemPropertyTitle] = @"歌曲名";
info[MPMediaItemPropertyArtist] = @"艺术家";
info[MPMediaItemPropertyAlbumTitle] = @"专辑名";
info[MPMediaItemPropertyPlaybackDuration] = @(self.player.duration);
info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = @(self.player.currentTime);
info[MPNowPlayingInfoPropertyPlaybackRate] = @(self.player.rate);
// 专辑封面
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:@"cover"]];
info[MPMediaItemPropertyArtwork] = artwork;
center.nowPlayingInfo = info;
3. 远程控制(锁屏按钮、耳机线控)
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
// 播放/暂停
[commandCenter.playCommand addTarget:self action:@selector(remotePlay)];
[commandCenter.pauseCommand addTarget:self action:@selector(remotePause)];
// 上一首/下一首
[commandCenter.nextTrackCommand addTarget:self action:@selector(remoteNext)];
[commandCenter.previousTrackCommand addTarget:self action:@selector(remotePrevious)];
// 耳机线控的播放/暂停切换
[commandCenter.togglePlayPauseCommand addTarget:self action:@selector(remoteToggle)];
实现远程控制后,锁屏界面的播放/暂停/上一首/下一首按钮、耳机线控、控制中心都能控制 App 音频。
七、音频工具类封装
1. 工具类接口
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@interface CHGAudioTool : NSObject
/// 播放音效(根据文件名)
+ (void)playSoundWithName:(NSString *)soundName;
/// 播放音乐(根据文件名),返回播放器
+ (AVAudioPlayer *)playMusicWithName:(NSString *)musicName;
/// 暂停音乐
+ (void)pauseMusicWithName:(NSString *)musicName;
/// 停止音乐
+ (void)stopMusicWithName:(NSString *)musicName;
/// 停止所有音乐
+ (void)stopAllMusic;
@end
2. 工具类实现(改进版)
#import "CHGAudioTool.h"
#import <AudioToolbox/AudioToolbox.h>
static NSMutableDictionary *_soundIDs; // 音效 ID 缓存
static NSMutableDictionary *_players; // 音乐播放器缓存
@implementation CHGAudioTool
+ (void)initialize {
_soundIDs = [NSMutableDictionary dictionary];
_players = [NSMutableDictionary dictionary];
}
#pragma mark - 音效
+ (void)playSoundWithName:(NSString *)soundName {
// 1. 从缓存取 soundID
SystemSoundID soundID = [[_soundIDs objectForKey:soundName] unsignedIntValue];
// 2. 未加载则加载
if (soundID == 0) {
NSURL *url = [[NSBundle mainBundle] URLForResource:soundName withExtension:nil];
if (!url) {
NSLog(@"音效文件不存在: %@", soundName);
return;
}
AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &soundID);
_soundIDs[soundName] = @(soundID);
}
// 3. 播放
AudioServicesPlaySystemSound(soundID);
}
#pragma mark - 音乐
+ (AVAudioPlayer *)playMusicWithName:(NSString *)musicName {
// 1. 从缓存取播放器
AVAudioPlayer *player = _players[musicName];
// 2. 未创建则创建
if (!player) {
NSURL *url = [[NSBundle mainBundle] URLForResource:musicName withExtension:nil];
if (!url) {
NSLog(@"音乐文件不存在: %@", musicName);
return nil;
}
NSError *error = nil;
player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
if (error) {
NSLog(@"音乐播放器创建失败: %@", error);
return nil;
}
[player prepareToPlay]; // 预缓冲
_players[musicName] = player;
}
// 3. 如果正在播放,先重置到开头再播放(或根据需求改为不操作)
if (player.isPlaying) {
player.currentTime = 0;
}
[player play];
return player;
}
+ (void)pauseMusicWithName:(NSString *)musicName {
AVAudioPlayer *player = _players[musicName];
if (player.isPlaying) {
[player pause];
}
}
+ (void)stopMusicWithName:(NSString *)musicName {
AVAudioPlayer *player = _players[musicName];
if (player) {
[player stop];
[_players removeObjectForKey:musicName];
}
}
+ (void)stopAllMusic {
for (NSString *key in _players.allKeys) {
AVAudioPlayer *player = _players[key];
[player stop];
}
[_players removeAllObjects];
}
@end
3. 使用示例
#import "CHGAudioTool.h"
// 播放音效
[CHGAudioTool playSoundWithName:@"click.wav"];
// 播放背景音乐(无限循环)
AVAudioPlayer *bgmPlayer = [CHGAudioTool playMusicWithName:@"bgm.mp3"];
bgmPlayer.numberOfLoops = -1;
bgmPlayer.volume = 0.5;
// 暂停/停止
[CHGAudioTool pauseMusicWithName:@"bgm.mp3"];
[CHGAudioTool stopMusicWithName:@"bgm.mp3"];
八、Swift 版本对照
音效播放(Swift)
import AudioToolbox
var soundID: SystemSoundID = 0
let url = Bundle.main.url(forResource: "m_03", withExtension: "wav")!
AudioServicesCreateSystemSoundID(url as CFURL, &soundID)
AudioServicesPlaySystemSound(soundID)
// 带完成回调(iOS 9+)
AudioServicesPlaySystemSoundWithCompletion(soundID) {
print("音效播放完成")
}
音乐播放(Swift)
import AVFoundation
let url = Bundle.main.url(forResource: "bgm", withExtension: "mp3")!
let player = try! AVAudioPlayer(contentsOf: url)
player.numberOfLoops = -1
player.volume = 0.5
player.prepareToPlay()
player.play()
录音(Swift)
import AVFoundation
let session = AVAudioSession.sharedInstance()
try session.setCategory(.record)
try session.setActive(true)
session.requestRecordPermission { granted in
if granted {
let docPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let filePath = (docPath as NSString).appendingPathComponent("record.m4a")
let url = URL(fileURLWithPath: filePath)
let settings: [String: Any] = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
let recorder = try! AVAudioRecorder(url: url, settings: settings)
recorder.record()
}
}
AVAudioSession 与后台播放(Swift)
let session = AVAudioSession.sharedInstance()
try session.setCategory(.playback)
try session.setActive(true)
// 中断通知
NotificationCenter.default.addObserver(forName: AVAudioSession.interruptionNotification, object: nil, queue: .main) { note in
guard let info = note.userInfo,
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { return }
if type == .began {
// 暂停
} else if type == .ended {
// 恢复
}
}
锁屏信息与远程控制(Swift)
import MediaPlayer
let center = MPNowPlayingInfoCenter.default()
center.nowPlayingInfo = [
MPMediaItemPropertyTitle: "歌曲名",
MPMediaItemPropertyArtist: "艺术家",
MPMediaItemPropertyPlaybackDuration: player.duration,
MPNowPlayingInfoPropertyElapsedPlaybackTime: player.currentTime
]
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.addTarget { _ in
player.play()
return .success
}
commandCenter.pauseCommand.addTarget { _ in
player.pause()
return .success
}
九、总结
- 音频分类:音效(短音频,1~2 秒,System Sound Services,轻量低延迟)和音乐(长音频,AVAudioPlayer,功能丰富)。
- 音效播放:AudioServicesCreateSystemSoundID 加载 → AudioServicesPlaySystemSound 播放,只加载一次可重复使用;仅支持 CAF/WAV/aif,不支持 MP3,时长 ≤30 秒;震动仅真机生效;iOS 9 起 API 废弃,推荐带 completion 版本或 AVAudioPlayer。
- 音乐播放:AVAudioPlayer 只支持本地文件(远程流媒体用 AVPlayer);常用 play/pause/stop、numberOfLoops(-1 无限循环)、volume、rate(需先 enableRate=YES)、pan、currentTime、meteringEnabled + updateMeters + averagePower/peakPower;delegate 监听播放完成、解码错误、中断。
- 录音:AVAudioRecorder;iOS 10+ 必须在 Info.plist 添加 NSMicrophoneUsageDescription,否则崩溃;需配置 AVAudioSession 类别为 Record/PlayAndRecord 并 requestRecordPermission 请求权限;settings 包含格式(AAC/PCM)、采样率(44100)、声道数、编码质量。
- AVAudioSession(现代核心):单例管理 App 与系统音频交互;类别 Ambient/SoloAmbient/Playback/Record/PlayAndRecord/MultiRoute;中断通知 AVAudioSessionInterruptionNotification(电话/闹钟/Siri);路由变化通知 AVAudioSessionRouteChangeNotification(耳机插拔/蓝牙)。
- 后台播放:Info.plist 添加 UIBackgroundModes=audio + AVAudioSession 类别 Playback;MPNowPlayingInfoCenter 设置锁屏歌曲信息;MPRemoteCommandCenter 处理远程控制(锁屏按钮/耳机线控/控制中心)。
- 工具类封装:静态字典缓存 soundID 和 player,避免重复加载;playSoundWithName、playMusicWithName、pause/stop/stopAll;prepareToPlay 预缓冲;stop 后从缓存移除。

浙公网安备 33010602011771号