Files
keyboard/keyBoard/Class/AiTalk/VM/AudioSessionManager.m
2026-01-21 17:59:12 +08:00

269 lines
7.7 KiB
Objective-C

//
// AudioSessionManager.m
// keyBoard
//
// Created by Mac on 2026/1/15.
//
#import "AudioSessionManager.h"
@interface AudioSessionManager ()
@property(nonatomic, assign) BOOL isSessionActive;
@end
@implementation AudioSessionManager
#pragma mark - Singleton
+ (instancetype)sharedManager {
static AudioSessionManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[AudioSessionManager alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_isSessionActive = NO;
[self setupNotifications];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Notifications
- (void)setupNotifications {
// 监听音频会话中断通知
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(handleInterruption:)
name:AVAudioSessionInterruptionNotification
object:nil];
// 监听音频路由变化通知
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(handleRouteChange:)
name:AVAudioSessionRouteChangeNotification
object:nil];
}
- (void)handleInterruption:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
AVAudioSessionInterruptionType type =
[info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
if (type == AVAudioSessionInterruptionTypeBegan) {
// 中断开始:来电、闹钟等
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate
respondsToSelector:@selector(audioSessionManagerDidInterrupt:)]) {
[self.delegate audioSessionManagerDidInterrupt:
KBAudioSessionInterruptionTypeBegan];
}
});
} else if (type == AVAudioSessionInterruptionTypeEnded) {
// 中断结束
AVAudioSessionInterruptionOptions options =
[info[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
if (options & AVAudioSessionInterruptionOptionShouldResume) {
// 可以恢复播放
[self activateSession:nil];
}
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate
respondsToSelector:@selector(audioSessionManagerDidInterrupt:)]) {
[self.delegate audioSessionManagerDidInterrupt:
KBAudioSessionInterruptionTypeEnded];
}
});
}
}
- (void)handleRouteChange:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
AVAudioSessionRouteChangeReason reason =
[info[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];
switch (reason) {
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
// 旧设备不可用(如耳机拔出)或新设备可用(如耳机插入)
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector
(audioSessionManagerRouteDidChange)]) {
[self.delegate audioSessionManagerRouteDidChange];
}
});
break;
}
default:
break;
}
}
#pragma mark - Microphone Permission
- (void)requestMicrophonePermission:(void (^)(BOOL))completion {
AVAudioSession *session = [AVAudioSession sharedInstance];
[session requestRecordPermission:^(BOOL granted) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!granted) {
if ([self.delegate respondsToSelector:@selector
(audioSessionManagerMicrophonePermissionDenied)]) {
[self.delegate audioSessionManagerMicrophonePermissionDenied];
}
}
if (completion) {
completion(granted);
}
});
}];
}
- (BOOL)hasMicrophonePermission {
AVAudioSession *session = [AVAudioSession sharedInstance];
return session.recordPermission == AVAudioSessionRecordPermissionGranted;
}
#pragma mark - Session Configuration
- (BOOL)configureForConversation:(NSError **)error {
AVAudioSession *session = [AVAudioSession sharedInstance];
// 配置为录音+播放模式
// Category: PlayAndRecord - 同时支持录音和播放
// Mode: VoiceChat - 允许系统进行 AGC/降噪,提升输入音量
// Options:
// - DefaultToSpeaker: 默认使用扬声器
// - AllowBluetooth: 允许蓝牙设备
NSError *categoryError = nil;
BOOL success =
[session setCategory:AVAudioSessionCategoryPlayAndRecord
mode:AVAudioSessionModeVoiceChat
options:(AVAudioSessionCategoryOptionDefaultToSpeaker |
AVAudioSessionCategoryOptionAllowBluetooth)
error:&categoryError];
if (!success) {
if (error) {
*error = categoryError;
}
NSLog(@"[AudioSessionManager] Failed to configure session: %@",
categoryError.localizedDescription);
return NO;
}
// 优先使用后端要求的采样率和缓冲时长,减少重采样误差
NSError *sampleRateError = nil;
[session setPreferredSampleRate:16000 error:&sampleRateError];
if (sampleRateError) {
NSLog(@"[AudioSessionManager] Failed to set sample rate: %@",
sampleRateError.localizedDescription);
}
NSError *bufferError = nil;
[session setPreferredIOBufferDuration:0.02 error:&bufferError];
if (bufferError) {
NSLog(@"[AudioSessionManager] Failed to set IO buffer: %@",
bufferError.localizedDescription);
}
if ([session respondsToSelector:@selector(setPreferredInputNumberOfChannels:
error:)]) {
NSError *channelsError = nil;
[session setPreferredInputNumberOfChannels:1 error:&channelsError];
if (channelsError) {
NSLog(@"[AudioSessionManager] Failed to set input channels: %@",
channelsError.localizedDescription);
}
}
return YES;
}
- (BOOL)configureForPlayback:(NSError **)error {
AVAudioSession *session = [AVAudioSession sharedInstance];
// 仅播放模式
NSError *categoryError = nil;
BOOL success =
[session setCategory:AVAudioSessionCategoryPlayback
mode:AVAudioSessionModeDefault
options:AVAudioSessionCategoryOptionDefaultToSpeaker
error:&categoryError];
if (!success) {
if (error) {
*error = categoryError;
}
NSLog(@"[AudioSessionManager] Failed to configure playback: %@",
categoryError.localizedDescription);
return NO;
}
return YES;
}
- (BOOL)activateSession:(NSError **)error {
if (self.isSessionActive) {
return YES;
}
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *activationError = nil;
BOOL success = [session setActive:YES error:&activationError];
if (!success) {
if (error) {
*error = activationError;
}
NSLog(@"[AudioSessionManager] Failed to activate session: %@",
activationError.localizedDescription);
return NO;
}
if (session.isInputGainSettable) {
NSError *gainError = nil;
[session setInputGain:1.0 error:&gainError];
if (gainError) {
NSLog(@"[AudioSessionManager] Failed to set input gain: %@",
gainError.localizedDescription);
}
}
self.isSessionActive = YES;
return YES;
}
- (void)deactivateSession {
if (!self.isSessionActive) {
return;
}
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
// 使用 NotifyOthersOnDeactivation 通知其他应用可以恢复播放
[session setActive:NO
withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
error:&error];
if (error) {
NSLog(@"[AudioSessionManager] Failed to deactivate session: %@",
error.localizedDescription);
}
self.isSessionActive = NO;
}
@end