播放和录制音频(AV Foundation开发秘籍)
2019-03-17 本文已影响0人
低吟浅唱1990
主要通过学习AVAudioPlayer和AVAudioRecorder类来实现应用程序中添加音频播放及音频录制的功能。
AV Foundation定义了7中分类来描述应用程序所使用的音频行为
AVAudioSessionCategory 7大类别
分类 | 作用 | 是否允许混音 | 音频输入 | 音频输出 | 说明 |
---|---|---|---|---|---|
Ambient | 游戏、效率应用程序 | ✔️ | ✔️ | 混音播放 | |
SoloAmbient(默认) | 游戏、效率应用程序 | ✔️ | 独占播放 | ||
Playback | 音频和视频播放器 | 可选 | ✔️ | 后台播放,也是独占的 | |
Record | 录音机、音频捕捉 | ✔️ | 录音模式,用于录音时使用 | ||
Play and Record | VoIP 语音聊天 | 可选 | ✔️ | ✔️ | 播放和录音,此时可以录音也可以播放 |
Audio Processing | 离线会话处理 | 硬件解码音频,此时不能播放和录制 | |||
Multi-Route | 使用外部硬件的高级A/V程序 | ✔️ | ✔️ | 多种输入输出,例如可以耳机、USB设备同时播放 |
AVAudioSessionCategoryOptions 类别的子选项
Options | 使用类别 | 说明 |
---|---|---|
MixWithOthers | PlayAndRecord, Playback, MultiRoute | 与其他可以混音 |
DuckOthers | Ambient, PlayAndRecord, Playback,MultiRoute | 压低其他声音 |
AllowBluetooth | Record,PlayAndRecord | 支持蓝牙耳机 |
DefaultToSpeaker | PlayAndRecord | 默认使用免提 |
InterruptSpokenAudioAndMixWithOthers(9.0) | PlayAndRecord,Playback, MultiRoute | 当其他app音频暂停是本app的音频是否继续 |
AllowBluetoothA2DP | PlayAndRecord | 支持蓝牙A2DP耳机 |
AllowAirPlay | PlayAndRecord | 支持AirPlay |
AVAudioSessionMode
模式 | 适用的类别 | 场景 |
---|---|---|
Default | 所有类别 | 默认的模式 |
VoiceChat | PlayAndRecord | VoIP |
GameChat | PlayAndRecord | 游戏录制,由GKVoiceChat自动设置,无需手动调用 |
VideoRecording | PlayAndRecord,Record | 录制视频时 |
MoviePlayback | Playback | 视频播放 |
Measurement | PlayAndRecord,Record,Playback | 最小系统 |
VideoChat | PlayAndRecord | 视频通话 |
配置音频会话
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error;
if (![session setCategory:AVAudioSessionCategoryPlayback error:&error]) {
NSLog(@"Category Error: %@", [error localizedDescription]);
}
// 配置激活
if (![session setActive:YES error:&error]) {
NSLog(@"Activation Error: %@", [error localizedDescription]);
}
/* returns singleton instance */
+ (AVAudioSession *)sharedInstance;
/* Set the session active or inactive.
*/
- (BOOL)setActive:(BOOL)active error:(NSError **)outError;
- (BOOL)setActive:(BOOL)active withOptions:(AVAudioSessionSetActiveOptions)options error:(NSError **)outError;
/* Asynchronously activate the session. 异步激活会话,handler是回调
*/
- (void)activateWithOptions:(AVAudioSessionActivationOptions)options completionHandler:(void (^)(BOOL activated, NSError * _Nullable error))handler;
// 获取设备可用的SessionCategory
@property (readonly) NSArray<AVAudioSessionCategory> *availableCategories;
/* 设置session的category */
- (BOOL)setCategory:(AVAudioSessionCategory)category error:(NSError **)outError ;
/* set session category with options */
- (BOOL)setCategory:(AVAudioSessionCategory)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError;
/* set session category and mode with options */
- (BOOL)setCategory:(AVAudioSessionCategory)category mode:(AVAudioSessionMode)mode options:(AVAudioSessionCategoryOptions)options error:(NSError **)outError ;
- (BOOL)setCategory:(AVAudioSessionCategory)category mode:(AVAudioSessionMode)mode routeSharingPolicy:(AVAudioSessionRouteSharingPolicy)policy options:(AVAudioSessionCategoryOptions)options error:(NSError **)outError;
@property (readonly) AVAudioSessionCategory category;
@property (readonly) AVAudioSessionCategoryOptions categoryOptions;
@property (readonly) AVAudioSessionRouteSharingPolicy routeSharingPolicy;
@property (readonly) NSArray<AVAudioSessionMode> *availableModes;
- (BOOL)setMode:(AVAudioSessionMode)mode error:(NSError **)outError; /* set session mode */
@property (readonly) AVAudioSessionMode mode; /* get session mode */
/* 返回权限*/
@property (readonly) AVAudioSessionRecordPermission recordPermission;
typedef void (^PermissionBlock)(BOOL granted);
- (void)requestRecordPermission:(PermissionBlock)response;
- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride error:(NSError **)outError;
@property (readonly, getter=isOtherAudioPlaying) BOOL otherAudioPlaying;
@property (readonly) BOOL secondaryAudioShouldBeSilencedHint;
@property (readonly) AVAudioSessionRouteDescription *currentRoute;
- (BOOL)setPreferredInput:(nullable AVAudioSessionPortDescription *)inPort error:(NSError **)outError API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos, macos);
@property (readonly, nullable) AVAudioSessionPortDescription *preferredInput API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos, macos); /* Get the preferred input port. Will be nil if no preference has been set */
@property (readonly, nullable) NSArray<AVAudioSessionPortDescription *> *availableInputs ;
创建AVAudioPlayer
AVAudioPlayer构建与CoreAudio中的C-based Audio Queue Services的最顶层。它可以提供播放,循环,音频计量等简单友好的Objective-C接口。
- (AVAudioPlayer *)playerForFile:(NSString *)name {
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:name
withExtension:@"caf"];
NSError *error;
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL
error:&error];
if (player) {
player.numberOfLoops = -1; // 无限循环
player.enableRate = YES;
[player prepareToPlay]; //相当于预加载Audio Queue的缓冲区
} else {
NSLog(@"Error creating player: %@", [error localizedDescription]);
}
return player;
}
播放控制
- 修改播放器的音量
- 修改播放器的pan值 允许使用立体声播放声音,-1.0 到 1.0 之间
- 调整播放率rate 0.5表示半速,1表示正常,2表示2倍速播放
- numberOfLoops 实现无缝循环 -1表示无限循环,n > 0表示循环n次
THPlayerController.h
@interface THPlayerController : NSObject
@property (nonatomic, getter = isPlaying) BOOL playing; //是否正在播放
// 播放
- (void)play;
//停止
- (void)stop;
//设置速率
- (void)adjustRate:(float)rate;
// 设置pan值
- (void)adjustPan:(float)pan forPlayerAtIndex:(NSUInteger)index;
//设置音量
- (void)adjustVolume:(float)volume forPlayerAtIndex:(NSUInteger)index;
@end
#import "THPlayerController.h"
#import <AVFoundation/AVFoundation.h>
@interface THPlayerController () <AVAudioPlayerDelegate>
@property (strong, nonatomic) NSArray *players;
@end
@implementation THPlayerController
#pragma mark - Initialization
- (id)init {
self = [super init];
if (self) {
AVAudioPlayer *guitarPlayer = [self playerForFile:@"guitar"];
AVAudioPlayer *bassPlayer = [self playerForFile:@"bass"];
AVAudioPlayer *drumsPlayer = [self playerForFile:@"drums"];
guitarPlayer.delegate = self;
_players = @[guitarPlayer, bassPlayer, drumsPlayer];
}
return self;
}
- (AVAudioPlayer *)playerForFile:(NSString *)name {
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:name
withExtension:@"caf"];
NSError *error;
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL
error:&error];
if (player) {
player.numberOfLoops = -1; // loop indefinitely
player.enableRate = YES;
[player prepareToPlay];
} else {
NSLog(@"Error creating player: %@", [error localizedDescription]);
}
return player;
}
#pragma mark - Global playback control methods
- (void)play {
if (!self.playing) {
NSTimeInterval delayTime = [self.players[0] deviceCurrentTime] + 0.01;
for (AVAudioPlayer *player in self.players) {
[player playAtTime:delayTime];
}
self.playing = YES;
}
}
- (void)stop {
if (self.playing) {
for (AVAudioPlayer *player in self.players) {
[player stop];
player.currentTime = 0.0f; //player.currentTime回到原点
}
self.playing = NO;
}
}
- (void)adjustRate:(float)rate {
for (AVAudioPlayer *player in self.players) {
player.rate = rate;
}
}
- (void)adjustPan:(float)pan forPlayerAtIndex:(NSUInteger)index {
if ([self isValidIndex:index]) {
AVAudioPlayer *player = self.players[index];
player.pan = pan;
}
}
- (void)adjustVolume:(float)volume forPlayerAtIndex:(NSUInteger)index {
if ([self isValidIndex:index]) {
AVAudioPlayer *player = self.players[index];
player.volume = volume;
}
}
- (BOOL)isValidIndex:(NSUInteger)index {
return index == 0 || index < self.players.count;
}
@end
处理中断事件
音频播放过程中,遇到电话呼入、闹钟等事件,会中断音频的播放
设置监听中断事件
初始化AVAudioPlayer之后设置中断事件的监听
NSNotificationCenter *nsnc = [NSNotificationCenter defaultCenter];
[nsnc addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
//相应回调
-(void)handleInterruption:(NSNotification *)notification{
NSDictionary *info = notification.userInfo;
AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
if (type == AVAudioSessionInterruptionTypeBegan) { //中断开始
[self stop];
}else{
AVAudioSessionInterruptionOptions options = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
if (options == AVAudioSessionInterruptionOptionShouldResume) { //恢复
[self play];
}
}
}
}
对线路改变的响应
线路是指输出线路从扬声器模式变为耳机,或者耳机变为扬声器模式。
线路改变原因的枚举
AVAudioSessionRouteChangeReasonUnknown = 0,
AVAudioSessionRouteChangeReasonNewDeviceAvailable = 1,
AVAudioSessionRouteChangeReasonOldDeviceUnavailable = 2,
AVAudioSessionRouteChangeReasonCategoryChange = 3,
AVAudioSessionRouteChangeReasonOverride = 4,
AVAudioSessionRouteChangeReasonWakeFromSleep = 6,
AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory = 7,
AVAudioSessionRouteChangeReasonRouteConfigurationChange = 8
音频输入
AVAudioSessionPortUSBAudio,
AVAudioSessionPortHeadsetMic,
AVAudioSessionPortBuiltInMic.
音频输出
AVAudioSessionPortUSBAudio, usb接口
AVAudioSessionPortLineOut,
AVAudioSessionPortHeadphones, 耳机输出
AVAudioSessionPortHDMI,
AVAudioSessionPortBuiltInSpeaker. 内置扬声器
NSNotificationCenter *nsnc = [NSNotificationCenter defaultCenter];
[nsnc addObserver:self
selector:@selector(handleRouteChange:)
name:AVAudioSessionRouteChangeNotification
object:[AVAudioSession sharedInstance]];
//线路的改变 比如耳机断开是停止播放
- (void)handleRouteChange:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
AVAudioSessionRouteChangeReason reason =
[info[AVAudioSessionRouteChangeReasonKey] unsignedIntValue];
//旧设备断开链接
if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
AVAudioSessionRouteDescription *previousRoute =
info[AVAudioSessionRouteChangePreviousRouteKey];
//端口描述
AVAudioSessionPortDescription *previousOutput = previousRoute.outputs[0];
NSString *portType = previousOutput.portType;
if ([portType isEqualToString:AVAudioSessionPortHeadphones]) {
[self stop];
}
}
}
音频录制AVAudioRecorder
创建AVAudioRecorder实例时需要为其提供数据的一些信息
- (nullable instancetype)initWithURL:(NSURL *)url settings:(NSDictionary<NSString *, id> *)settings error:(NSError **)outError;
- 音频流写入文件的本地url地址
- 配置录音会话的设置信息
在 #import <AVFAudio/AVAudioSettings.h> 文件里有音频的设置键信息。
AVFormatIDKey 音频格式
AVSampleRateKey; 采样率
AVNumberOfChannelsKey; 通道数
格式:kAudioFormatLinearPCM会将未压缩的音频流写入文件中。 这中格式保真度最高,kAudioFormatMPEG4AAC的压缩格式会显著缩小文件,还能保证高质量的音频内容。
采样率:对输入的音频信号每一秒内的采样数。一般标准的采样率为8000、16000、22050和44100赫兹。
通道数:定义记录音频内容的通道数。默认值为1意味着使用单声道录制,设置为2采用立体声录制。
-(NSURL *)getSavePath{
NSString *urlStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
urlStr = [urlStr stringByAppendingPathComponent:kRecordAudioFile];
NSLog(@"file path:%@",urlStr);
NSURL *url = [NSURL fileURLWithPath:urlStr];
return url;
}
//录音参数设置
-(NSDictionary *)getAudioSetting{
NSMutableDictionary *dicM = @{}.mutableCopy;
[dicM setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey];
[dicM setObject:@(44100) forKey:AVSampleRateKey];
[dicM setObject:@(1) forKey:AVNumberOfChannelsKey];
[dicM setObject:@(16) forKey:AVEncoderBitDepthHintKey];
[dicM setObject:@(AVAudioQualityMedium) forKey:AVEncoderAudioQualityKey];
return dicM;
}
//录音器
-(AVAudioRecorder *)audioRecorder{
if (!_audioRecorder) {
//保存路径
NSURL *url = [self getSavePath];
NSDictionary *setting = [self getAudioSetting];
NSError *error;
_audioRecorder = [[AVAudioRecorder alloc] initWithURL:url settings:setting error:&error];
_audioRecorder.delegate = self;
_audioRecorder.meteringEnabled = YES;
[_audioRecorder prepareToRecord];
}
return _audioRecorder;
}
//开始录制
- (BOOL)record {
return [self.audioRecorder record];
}
//中断
- (void)pause {
[self.audioRecorder pause];
}
// 停止
- (void)stop {
[self.audioRecorder stop];
}
//实现录制完成代理
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)success {
}
//实现代理方法 录制中断
- (void)audioRecorderBeginInterruption:(AVAudioRecorder *)recorder {
}
记录分贝等级
- (float)peakPowerForChannel:(NSUInteger)channelNumber; /* returns peak power in decibels for a given channel */
- (float)averagePowerForChannel:(NSUInteger)channelNumber; /* returns average power in decibels for a given channel */
返回用于表示分贝等级的浮点值,最大分贝的0dB到最小分贝的-160dB。 在调用这两个方法之前需要设置_audioRecorder.meteringEnabled = YES才能使用。