AVAudioRecorder - 如何从来电中恢复
AVAudioRecorder - how to recover from incoming call
我正在努力实现我认为非常容易但事实并非如此的事情。
我正在玩 "react-native-audio" 库的源代码。但是你可以假设我是为了这个问题而在本地工作。
这是我正在玩的 reference for the source code。
我的目标很简单,我正在使用 AVAudioRecorder
录制会议(大约需要 30 分钟)。如果在录音过程中有来电,我希望我的应用能够 "recover" 通过执行以下选项之一:
1) "pause" "incoming call" 和 "resume" 当应用回到前台时的记录。
2) 来电时 - 关闭当前文件,当应用程序返回前台时,使用新文件开始新录音(第 2 部分)。
显然选项 (1) 是首选。
请注意,我很清楚如何使用 AVAudioSessionInterruptionNotification
并在我的实验中使用它,但到目前为止运气不佳,例如:
- (void) receiveAudioSessionNotification:(NSNotification *) notification
{
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
NSLog(@"AVAudioSessionInterruptionNotification");
NSNumber *type = [notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey];
if ([type isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
NSLog(@"*** InterruptionTypeBegan");
[self pauseRecording];
} else {
NSLog(@"*** InterruptionTypeEnded");
[_recordSession setActive:YES error:nil];
}
}
}
请注意,我会为这个问题设置悬赏,但唯一可接受的答案是针对真实世界的工作代码,而不是 "should work in theory"。非常感谢您的帮助:)
我选择了AVAudioEngine
和AVAudioFile
作为解决方案,因为代码很简短,AVFoundation的中断处理是particularly simple(你的player/recorderobjects被暂停了,并且取消暂停会重新激活您的音频 session)。
N.B AVAudioFile
没有明确的关闭方法,而是写入 headers 并在 [=14= 期间关闭文件] 令人遗憾的是,这个选择使原本简单的 API.
复杂化了
@interface ViewController ()
@property (nonatomic) AVAudioEngine *audioEngine;
@property AVAudioFile *outputFile;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error;
if (![session setCategory:AVAudioSessionCategoryRecord error:&error]) {
NSLog(@"Failed to set session category: %@", error);
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioInterruptionHandler:) name:AVAudioSessionInterruptionNotification object:nil];
NSURL *outputURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0] URLByAppendingPathComponent:@"output.aac"];
__block BOOL outputFileInited = NO;
self.audioEngine = [[AVAudioEngine alloc] init];
AVAudioInputNode *inputNode = self.audioEngine.inputNode;
[inputNode installTapOnBus:0 bufferSize:512 format:nil block:^(AVAudioPCMBuffer *buffer, AVAudioTime * when) {
NSError *error;
if (self.outputFile == nil && !outputFileInited) {
NSDictionary *settings = @{
AVFormatIDKey: @(kAudioFormatMPEG4AAC),
AVNumberOfChannelsKey: @(buffer.format.channelCount),
AVSampleRateKey: @(buffer.format.sampleRate)
};
self.outputFile = [[AVAudioFile alloc] initForWriting:outputURL settings:settings error:&error];
if (!self.outputFile) {
NSLog(@"output file error: %@", error);
abort();
}
outputFileInited = YES;
}
if (self.outputFile && ![self.outputFile writeFromBuffer:buffer error:&error]) {
NSLog(@"AVAudioFile write error: %@", error);
}
}];
if (![self.audioEngine startAndReturnError:&error]) {
NSLog(@"engine start error: %@", error);
}
// To stop recording, nil the outputFile at some point in the future.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Finished");
self.outputFile = nil;
});
}
// https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/HandlingAudioInterruptions/HandlingAudioInterruptions.html
- (void)audioInterruptionHandler:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
switch (type) {
case AVAudioSessionInterruptionTypeBegan:
NSLog(@"Begin interruption");
break;
case AVAudioSessionInterruptionTypeEnded:
NSLog(@"End interruption");
// or ignore shouldResume if you're really keen to resume recording
AVAudioSessionInterruptionOptions endOptions = [info[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
if (AVAudioSessionInterruptionOptionShouldResume == endOptions) {
NSError *error;
if (![self.audioEngine startAndReturnError:&error]) {
NSLog(@"Error restarting engine: %@", error);
}
}
break;
}
}
@end
N.B。您可能想要启用背景音频(当然还要在 Info.plist 中添加一个 NSMicrophoneUsageDescription
字符串)。
我正在努力实现我认为非常容易但事实并非如此的事情。
我正在玩 "react-native-audio" 库的源代码。但是你可以假设我是为了这个问题而在本地工作。
这是我正在玩的 reference for the source code。
我的目标很简单,我正在使用 AVAudioRecorder
录制会议(大约需要 30 分钟)。如果在录音过程中有来电,我希望我的应用能够 "recover" 通过执行以下选项之一:
1) "pause" "incoming call" 和 "resume" 当应用回到前台时的记录。
2) 来电时 - 关闭当前文件,当应用程序返回前台时,使用新文件开始新录音(第 2 部分)。
显然选项 (1) 是首选。
请注意,我很清楚如何使用 AVAudioSessionInterruptionNotification
并在我的实验中使用它,但到目前为止运气不佳,例如:
- (void) receiveAudioSessionNotification:(NSNotification *) notification
{
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
NSLog(@"AVAudioSessionInterruptionNotification");
NSNumber *type = [notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey];
if ([type isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
NSLog(@"*** InterruptionTypeBegan");
[self pauseRecording];
} else {
NSLog(@"*** InterruptionTypeEnded");
[_recordSession setActive:YES error:nil];
}
}
}
请注意,我会为这个问题设置悬赏,但唯一可接受的答案是针对真实世界的工作代码,而不是 "should work in theory"。非常感谢您的帮助:)
我选择了AVAudioEngine
和AVAudioFile
作为解决方案,因为代码很简短,AVFoundation的中断处理是particularly simple(你的player/recorderobjects被暂停了,并且取消暂停会重新激活您的音频 session)。
N.B AVAudioFile
没有明确的关闭方法,而是写入 headers 并在 [=14= 期间关闭文件] 令人遗憾的是,这个选择使原本简单的 API.
@interface ViewController ()
@property (nonatomic) AVAudioEngine *audioEngine;
@property AVAudioFile *outputFile;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error;
if (![session setCategory:AVAudioSessionCategoryRecord error:&error]) {
NSLog(@"Failed to set session category: %@", error);
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioInterruptionHandler:) name:AVAudioSessionInterruptionNotification object:nil];
NSURL *outputURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0] URLByAppendingPathComponent:@"output.aac"];
__block BOOL outputFileInited = NO;
self.audioEngine = [[AVAudioEngine alloc] init];
AVAudioInputNode *inputNode = self.audioEngine.inputNode;
[inputNode installTapOnBus:0 bufferSize:512 format:nil block:^(AVAudioPCMBuffer *buffer, AVAudioTime * when) {
NSError *error;
if (self.outputFile == nil && !outputFileInited) {
NSDictionary *settings = @{
AVFormatIDKey: @(kAudioFormatMPEG4AAC),
AVNumberOfChannelsKey: @(buffer.format.channelCount),
AVSampleRateKey: @(buffer.format.sampleRate)
};
self.outputFile = [[AVAudioFile alloc] initForWriting:outputURL settings:settings error:&error];
if (!self.outputFile) {
NSLog(@"output file error: %@", error);
abort();
}
outputFileInited = YES;
}
if (self.outputFile && ![self.outputFile writeFromBuffer:buffer error:&error]) {
NSLog(@"AVAudioFile write error: %@", error);
}
}];
if (![self.audioEngine startAndReturnError:&error]) {
NSLog(@"engine start error: %@", error);
}
// To stop recording, nil the outputFile at some point in the future.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Finished");
self.outputFile = nil;
});
}
// https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/HandlingAudioInterruptions/HandlingAudioInterruptions.html
- (void)audioInterruptionHandler:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
switch (type) {
case AVAudioSessionInterruptionTypeBegan:
NSLog(@"Begin interruption");
break;
case AVAudioSessionInterruptionTypeEnded:
NSLog(@"End interruption");
// or ignore shouldResume if you're really keen to resume recording
AVAudioSessionInterruptionOptions endOptions = [info[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
if (AVAudioSessionInterruptionOptionShouldResume == endOptions) {
NSError *error;
if (![self.audioEngine startAndReturnError:&error]) {
NSLog(@"Error restarting engine: %@", error);
}
}
break;
}
}
@end
N.B。您可能想要启用背景音频(当然还要在 Info.plist 中添加一个 NSMicrophoneUsageDescription
字符串)。