1. 程式人生 > >AVAudioSession-Category與語音喚醒和音訊播放的恩怨糾葛

AVAudioSession-Category與語音喚醒和音訊播放的恩怨糾葛

最近,在開發一款音樂播放器型別專案中遇到的一些與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,拿不到準確的值。

最後,上面的所有的經驗和總結,都是通過各種查閱資料和不斷除錯得來的,並沒有較為科學嚴謹的理論依據,也沒有相關的官方文件的支援。總結出來,只是希望給後續如果有人遇到與我一樣的難題時,少走一些彎路,有一些啟發,僅此而已。