主要通過學習AVAudioPlayer和AVAudioRecorder類來實現(xiàn)應用程序中添加音頻播放及音頻錄制的功能。
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的音頻是否繼續(xù) |
AllowBluetoothA2DP | PlayAndRecord | 支持藍牙A2DP耳機 |
AllowAirPlay | PlayAndRecord | 支持AirPlay |
AVAudioSessionMode
模式 | 適用的類別 | 場景 |
---|---|---|
Default | 所有類別 | 默認的模式 |
VoiceChat | PlayAndRecord | VoIP |
GameChat | PlayAndRecord | 游戲錄制,由GKVoiceChat自動設置瘫镇,無需手動調(diào)用 |
VideoRecording | PlayAndRecord鼎兽,Record | 錄制視頻時 |
MoviePlayback | Playback | 視頻播放 |
Measurement | PlayAndRecord,Record铣除,Playback | 最小系統(tǒng) |
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是回調(diào)
*/
- (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 ;
創(chuàng)建AVAudioPlayer
AVAudioPlayer構建與CoreAudio中的C-based Audio Queue Services的最頂層。它可以提供播放尚粘,循環(huán)择卦,音頻計量等簡單友好的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; // 無限循環(huán)
player.enableRate = YES;
[player prepareToPlay]; //相當于預加載Audio Queue的緩沖區(qū)
} else {
NSLog(@"Error creating player: %@", [error localizedDescription]);
}
return player;
}
播放控制
- 修改播放器的音量
- 修改播放器的pan值 允許使用立體聲播放聲音郎嫁,-1.0 到 1.0 之間
- 調(diào)整播放率rate 0.5表示半速秉继,1表示正常,2表示2倍速播放
- numberOfLoops 實現(xiàn)無縫循環(huán) -1表示無限循環(huán)泽铛,n > 0表示循環(huán)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
處理中斷事件
音頻播放過程中尚辑,遇到電話呼入、鬧鐘等事件盔腔,會中斷音頻的播放
設置監(jiān)聽中斷事件
初始化AVAudioPlayer之后設置中斷事件的監(jiān)聽
NSNotificationCenter *nsnc = [NSNotificationCenter defaultCenter];
[nsnc addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
//相應回調(diào)
-(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];
}
}
}
}
對線路改變的響應
線路是指輸出線路從揚聲器模式變?yōu)槎鷻C月褥,或者耳機變?yōu)閾P聲器模式。
線路改變原因的枚舉
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. 內(nèi)置揚聲器
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
創(chuàng)建AVAudioRecorder實例時需要為其提供數(shù)據(jù)的一些信息
- (nullable instancetype)initWithURL:(NSURL *)url settings:(NSDictionary<NSString *, id> *)settings error:(NSError **)outError;
- 音頻流寫入文件的本地url地址
- 配置錄音會話的設置信息
在 #import <AVFAudio/AVAudioSettings.h> 文件里有音頻的設置鍵信息瓢喉。
AVFormatIDKey 音頻格式
AVSampleRateKey; 采樣率
AVNumberOfChannelsKey; 通道數(shù)
格式:kAudioFormatLinearPCM會將未壓縮的音頻流寫入文件中吓坚。 這中格式保真度最高,kAudioFormatMPEG4AAC的壓縮格式會顯著縮小文件灯荧,還能保證高質(zhì)量的音頻內(nèi)容礁击。
采樣率:對輸入的音頻信號每一秒內(nèi)的采樣數(shù)。一般標準的采樣率為8000逗载、16000哆窿、22050和44100赫茲。
通道數(shù):定義記錄音頻內(nèi)容的通道數(shù)厉斟。默認值為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;
}
//錄音參數(shù)設置
-(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];
}
//實現(xiàn)錄制完成代理
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)success {
}
//實現(xiàn)代理方法 錄制中斷
- (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码荔。 在調(diào)用這兩個方法之前需要設置_audioRecorder.meteringEnabled = YES才能使用。