美文网首页iOS集合good人间不知德-iOS老本行
AVAudioSession-Category的正确使用姿势

AVAudioSession-Category的正确使用姿势

作者: 953e9bf34714 | 来源:发表于2018-10-10 11:26 被阅读629次

    最近,在开发一款音乐播放器类型项目中遇到的一些与AVAudioSession-Category设置的一些坑,以下是整个过程的一些经验总结。

    1.常规播放

    一般如果应用只有简单音乐播放功能,那么我们的AVAudioSession-Category只用像如下一样设置即可:

    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];     [[AVAudioSession sharedInstance] setActive:YES error:nil];
    

    此时如果我们只是播放音乐,而不需要独占锁屏界面时,还可以设置:

        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
                                         withOptions:AVAudioSessionCategoryOptionMixWithOthers
                                               error:nil];
    

    这样我们兼容其他后台播放的音乐一起进行播放,不过大部分场景下,我们是需要独占式后台播放。

    2.常规录音

    在录音的时候,我们一般如以下设置:

    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:nil];     
    [[AVAudioSession sharedInstance] setActive:YES error:nil];
    

    3.如果将录音和播放同时进行时,我们改选择何种Category?

    同时进行播放和录音时,我们需要这样设置:

    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord                                            error:nil];     
    [[AVAudioSession sharedInstance] setActive:YES error:nil];
    

    需要注意的是,设置成这样的情况下,如果,在录音未开启的情况下,直接进行播放,则会出现,播放音量特别小的情况,我们需要在播放之前,将录音打开。

    4.前后台切换

    上述的模式,在iOS系统下,是不允许录音和播放在后台状态下同时进行的(PS:语音视频通话是通过CallKit实现的,不用于常规的播放和录音功能)。由此,我们在应用进入后台时就需要关掉其中一个功能。

    以后台支持播放为例,在应用将要失活时,先切换模式,再关掉录音功能:

    // stopRecording...

    // 切换模式

      [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
      [[AVAudioSession sharedInstance] setActive:YES error:nil];
    

    应用即将进入前台时,切换模式,再开启录音功能:

        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord                                            error:nil];
        [[AVAudioSession sharedInstance] setActive:YES                                          error:nil];
    

    // 延迟恢复,否则会导致AVAudioSession的i/o错误

        [self performSelectorOnMainThread:@selector(startRecording) withObject:nil waitUntilDone:NO];
    

    5.电话中断

    电话闹钟的中断也会对,[AVAudioSession sharedInstance] 产生影响。

    我们一般场景下会用 下面这个通知进行监控并处理暂停和恢复的工作:

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:nil]; 
    - (void)handleInterruption:(NSNotification*)notification { NSLog(@"interruption info:%@",notification.userInfo); }
    

    但是,当我们在处理第四个场景前后台的情况下,这个通知,在中断的时候会进入,但是电话结束后,不会再接收到中断结束的通知。

    原因:

    有的app使用了AVCaptureDevice和AVCaptureSession,以进行录音录像操作。为了调优app设置,以更好的进行录音录像,从iOS7开始,在默认情况下,AVCaptureSession会使用app的AVAudioSession,并对其进行修改。这样,设置的中断监听方法会失效。

    而电话来电也会使我们的应用接收到 失活的通知,在失活的时候处理了AVAudioSession,就会导致上述通知失效。

    解决方案:我这里采用了比较折中的方案,因为我们的需求,对于第四条的处理是必要的。使用的是 CoreTelephony框架下的CTCallCenter对象,来监控电话的 拨入接通、挂断等状态。代码如下:

    self.center = [[CTCallCenter alloc] init];
    // TODO: 检测到来电后的处理
    self.center.callEventHandler = ^(CTCall * call){
        if (call.callState == CTCallStateIncoming ||
            call.callState == CTCallStateConnected ||
            call.callState == CTCallStateDialing)
        {
        }
        else if (call.callState == CTCallStateDisconnected)
        {
        }
    };
    

    通过各种打电话的场景测试后,可以实现电话中断恢复功能。

    ps:至于闹钟的中断以及siri等其他中断,暂时没有调研和实现。

    6.蓝牙车载

    终于来到了本文的最后一个部分了,也是最为曲折的一部分。

    本来以为车载的车机连接后对于iPhone的播放控制与锁屏控制类似,直接在系统媒体远程控制监控中就能够拿到相应的控制方法回调。

    在APPDelegate中加上如下代码:

    //监听远程交互方法
    - (void)remoteControlReceivedWithEvent:(UIEvent *)event
    {
        switch (event.subtype)
        {
                //播放
            case UIEventSubtypeRemoteControlPlay:
                break;
                //停止
            case UIEventSubtypeRemoteControlPause:
                break;
                //下一首
            case UIEventSubtypeRemoteControlNextTrack:
                break;
                //上一首
            case UIEventSubtypeRemoteControlPreviousTrack:
                break;
            default:
              break;
        }
    }
    

    事实上,当我们的应用只有简单的播放功能的时候,上述代码的确可以完美的实现车机对于播放的控制功能。但是当应用出于前台的情况下,我们添加上了一直录音的功能的时候,用车机控制播放,就完全没有任何响应了。可以注意到的是,我们看到车机的屏幕上,会显示通话中。查阅了各种资料和文章,都没有找到相关的解决办法和原理解释。

    最后,想到了看看有没有其他类似的语音识别及播放功能的应用(iOS)有没有类似的处理,结果调研到百度地图 中的小度 有相关的处理。在它的设置中,找到 语音设置有一个蓝牙连接设置 。两个模式设置 如下:

    a.蓝牙设备播报,小度无法唤醒使用(播放体验最佳)

    b.蓝牙设备播报,小度唤醒正常使用(车机显示通话中,播报音量可能变小)

    由此可以看出,a场景下 录音功能关闭,只有语音播报功能,b场景下,录音功能开启,车机就是会识别到手机设备在录音和播放中,认为就是在通话中,这个是车机本身的限制,无法从应用层进行优化。而且,百度地图的给用的默认选择就是,连接蓝牙的情况下,小度不能唤醒。

    综合上面我们协同产品,从交互层面上更改,保证,在连接车机的情况下,能够控制播放。具体处理交互如下:

    在应用进入到前台时,检测到连接了蓝牙设备,弹出弹框,让用户选择,继续开启唤醒功能开始,关闭唤醒功能(保证播放控制功能)。继续开启的情况下,车机无法控制播放。

    下面是检测是否有输出设备连接的代码(并未找到检查当前是否有连接蓝牙设备的方法):

    + (BOOL)checkIsConnectToBluetooth
    {
        BOOL isBluetooth = NO;
        // 找出当前所有支持输入的设备  availableInputs 这里面会出现 iPhone麦克风, 蓝牙耳机1, 蓝牙耳机2 , 三个对象, 在一个数组里.
        NSArray* inputArray = [[AVAudioSession sharedInstance] availableInputs];
        for (AVAudioSessionPortDescription* desc in inputArray)
        {
            if ([desc.portType isEqualToString:AVAudioSessionPortBluetoothLE] ||
                [desc.portType isEqualToString:AVAudioSessionPortBluetoothHFP] ||
                [desc.portType isEqualToString:AVAudioSessionPortBluetoothA2DP])
            {
                isBluetooth = YES;
            }
        }
        return isBluetooth;
    }
    

    同时,还需要配合Category的设置:

    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord                                          withOptions:AVAudioSessionCategoryOptionAllowBluetooth                                                error:&error];        
    [[AVAudioSession sharedInstance] setActive:YES error:&error1];
    

    AVAudioSessionCategoryOptionAllowBluetooth这是必须要添加的,否则上面的方法,连接蓝牙后,在应用即将活跃的监控的时候,是会返回NO,拿不到准确的值。

    最后,上面的所有的经验和总结,都是通过各种查阅资料和不断调试得来的,并没有较为科学严谨的理论依据,也没有相关的官方文档的支持。总结出来,只是希望给后续如果有人遇到与我一样的难题时,少走一些弯路,有一些启发,仅此而已。

    相关文章

      网友评论

        本文标题:AVAudioSession-Category的正确使用姿势

        本文链接:https://www.haomeiwen.com/subject/izkpaftx.html