【问题标题】:AVAudioRecorder - how to recover from incoming callAVAudioRecorder - 如何从来电中恢复
【发布时间】:2019-06-07 17:29:48
【问题描述】:

我正在尝试实现一些我认为非常容易的事情,但事实证明并非如此。

我正在使用“react-native-audio”库的源代码。但是你可以假设我为了这个问题而在本地工作。

这是我正在玩的reference for the source code

我的目标很简单,我正在使用AVAudioRecorder 录制会议(大约需要 30 分钟)。如果在录音过程中有来电,我希望我的应用能够通过执行以下选项之一“恢复”:

1) 当应用回到前台时,“暂停”“来电”和“恢复”记录。

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];            
        }
    }
}

请注意,我将为这个问题设置赏金,但唯一可接受的答案将是真实世界的工作代码,而不是“理论上应该工作”的东西。非常感谢您的帮助:)

【问题讨论】:

  • 此答案显示的第三种方式:1b)暂停中断并恢复您稍后合并的新文件:stackoverflow.com/a/5112943 N.B.这可以通过其他 API 实现。
  • 感谢@RhythmicFistman,首先这个答案来自 2011 年,希望从那时起情况有所改变。我不相信苹果有 8 年的开放错误。而且,根据我的实验,来电正在破坏当前的录音文件,即在来电之前无法收听录音。这就是为什么我特别要求提供真实世界的工作代码
  • Apple 的回应措辞听起来像是一个设计错误,所以没有代码给你!您对其他 API 持开放态度吗? AVAudioEngineCoreAudio 可以做到这一点。
  • 任何能解决我问题的解决方案对我来说都很好。我希望能够录制音频并能够从来电中恢复
  • 你的格式选项是什么?

标签: ios iphone react-native avfoundation


【解决方案1】:

我选择AVAudioEngineAVAudioFile 作为解决方案,因为代码很简短,AVFoundation 的中断处理是particularly simple(您的播放器/记录器对象已暂停,取消暂停会重新激活您的音频会话)。

NB AVAudioFile 没有明确的关闭方法,而是在 dealloc 期间写入标头并关闭文件,令人遗憾的是,这种选择会使原本简单的 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

注意您可能想要启用背景音频(当然,在 Info.plist 中添加一个NSMicrophoneUsageDescription 字符串)。

【讨论】:

  • 谢谢@Rhythmic,我成功运行并使用了你的代码。似乎“停止录制”部分没有按预期工作,至少对我来说是这样。当我停止录制(将 outputFile 设置为“nil”)时 - 我可以在 Documents 目录中找到音频文件,但无法播放它(我使用 iTunes 文件共享),文件似乎以某种方式损坏。附言我还没有测试来电,只是先尝试​​让基本的录音流程正常工作
  • 不幸的是,AVAudioFile 没有明确的关闭方法,而是在超出范围时关闭,这就是 m4a 标头被写出的时候。听起来您已经引入了另一个引用它正在停止文件关闭。或者你不是在等到 20 岁才会发生这种情况?或者忘记关闭文件并通过将文件后缀更改为 .aac 来使用无标题格式。或者为您的文件 io 使用更明确的 API,例如 ExtendedAudioFile。
  • 附注您可以从 Xcode > Window > Devices and Simulators > iPhone > your app > Download Container 获取您的文件,这让您无需使用 iTunes 或同步您的手机
  • 嗨,我试图准备一个像这样工作的“stopRecording”方法:{self.outputFile = nil;} 是的,我必须为 outputFile 设置一个属性才能关闭它(停止录制)来自另一个上下文。在现实生活中,我不想录制整整 20 秒
  • 我刚刚使用您的原始代码运行了另一个测试,但文件似乎已损坏
猜你喜欢
  • 2011-07-02
  • 1970-01-01
  • 2012-07-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多