iOS後臺喚醒實戰:微信收款到賬語音提醒技術總結

jsjsjjs發表於2018-02-20

本文引用自騰訊大講堂公眾號的技術分享,感謝原作者。

1、前言

微信為了解決小商戶老闆們在頻繁交易中不方便核對、確認到賬的功能痛點,產品MM提出了新版本需要支援收款到賬語音提醒功能。本文藉此總結了iOS平臺上的APP後臺喚醒和語音合成、播放等一系列技術開發過程中遇到的坑和小技巧,希望與您分享。

(本文同步釋出於:http://www.52im.net/thread-1404-1-1.html

2、技術方案

2.1 後臺喚醒App

收款到賬語音提醒需要收款方在收到款後,播放一段TTS合成語音播報金額,微信在前臺時可以通過模板訊息將需要播報的金額帶下來,再請求TTS資料並播放,但是app在掛起或者被kill掉的情況下要如何請求語音資料並播放呢?

iOS提供了兩種方式喚醒處於掛起或已經被kill掉的app。分別是Silent Notification和VoIP Push Notification,客戶端在被喚醒之後將獲得30s的後臺執行時間,這段執行時間足以請求合成語音資料並播放。

具體技術細節如下:

1)Silent NotificationSilent Notification在iOS7以上便可以支援,但是每小時能推送的Silent Notification次數有限制;

2)VoIP Push NotificationVoIP Push Notification則是在iOS8以上才支援的新Push型別,相比於Silent Notification,VoIP Push具有高優先順序、低延遲的優勢,並且沒有次數限制。

對比這兩種技術方案,VoIP Push Notification明顯更適合用於收款到賬語音提醒的喚醒方案。

2.2 TTS合成語音

TTS語音合成方案分為離線合成方案和線上合成方案,離線合成方案省去網路請求,合成速度更快,節省網路流量,但是合成音的聽起來比較機械,語速和停頓的處理較差一些。如果對合成音的效果要求不是特別高,可以考慮採用iOS自帶的AVSpeechSynthesis框架,免去語音庫的合入,減少安裝包大小。

線上合成方案的效果則相對更像人聲,富有感情。考慮到產品體驗,我們採用了搜尋產品部提供的線上語音合成方案,接入方式可以看這篇文章,合成音格式支援wav、mp3、silk,amr、speex。對比後發現,在合成相同文字的情況下,amr的壓縮率最高,但是能聽到音質下降明顯。silk格式壓縮率次高,且能保持相對清晰的音質,單條合成語音大小在2KB左右。

2.3 喚醒後播放音訊檔案

在請求到合成語音後,要在後臺或者鎖屏狀態下播放音訊檔案,AVAudio Session的Category值需要使用AVAudioSessionCategoryPlayback或是AVAudioSessionCategoryPlayAndRecord,CategoryOptions根據實際需要可選擇MixWithOthers(與其他聲音混音)或是DuckOthers(調低其他聲音的音量)。

需要注意的是:只有iOS10以上才支援app被喚醒後在後臺/鎖屏狀態下播放音訊。所以iOS10以下的裝置,在收到VoIP Push後只能在local push上設定一段固定鈴聲,這也是為什麼iOS10以下只有“微信支付收款到賬”,而沒有後面具體的金額數值。

3、靜音開關檢測

不幸的是,在產品釋出後沒多久就受到了某網際網路大佬的吐槽。

從產品體驗上來說,收款到賬的金額播報是隨著local push的彈出一起播放的,更像是一種特殊的push鈴聲,而蘋果對push鈴聲的處理是受到靜音開關控制的,所以講道理,這個吐槽是合理的。然而前面提到App在被VoIP Push喚醒之後,需要將AudioSessionCategory設定為AVAudioSessionCategoryPlayback或AVAudioSessionCategoryPlayAndRecord才可以在後臺播放音訊檔案,這兩種模式是不受靜音開關控制的。要實現這個需求,就必須獲取當前靜音開關的狀態。而蘋果在iOS5之後並沒有明確地提供一種方式讓開發獲取靜音開關的狀態,這就陷入了一個尷尬的局面。

蘋果在iOS5之前可以使用以下方式監聽靜音鍵開關:

– (BOOL)isMuted 

    CFStringRef route; 

    UInt32 routeSize = sizeof(CFStringRef); 

    OSStatus status = AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &routeSize, &route); 

    if(status == kAudioSessionNoError) 

    { 

        if(route == NULL|| !CFStringGetLength(route)) 

            returnYES; 

    } 

    returnNO; 

}

蘋果在iOS5之後便禁止了使用這種方式監聽靜音按鍵,背後的原因應該是蘋果希望開發者使用AVAudioSession來提供統一的音訊播放效果。

最後我在Reddit上找到了一種曲線救國的方式,實現起來也不復雜:使用AudioServicesPlaySystemSound播放一段0.2s的空白音訊,並監聽音訊播放完成事件,如果從開始播放到回撥完成方法的間隔時間小於0.1s,則意味當前靜音開關為開啟狀態。

void SoundMuteNotificationCompletionProc(SystemSoundID  ssID,void* clientData){

    MMSoundSwitchDetector* detecotr = (__bridge MMSoundSwitchDetector*)clientData;

    [detecotr complete];

}

– (instancetype)init {

    self= [superinit];  

     if(self) {

        NSURL*pathURL = [[NSBundlemainBundle] URLForResource:@”mute”withExtension:@”caf”];    

        if(AudioServicesCreateSystemSoundID((__bridge CFURLRef)pathURL, &_soundId) == kAudioServicesNoError){

            AudioServicesAddSystemSoundCompletion(self.soundId, CFRunLoopGetMain(), kCFRunLoopDefaultMode, SoundMuteNotificationCompletionProc,(__bridge void*)(self));

            UInt32 yes = 1;

            AudioServicesSetProperty(kAudioServicesPropertyIsUISound, sizeof(_soundId),&_soundId,sizeof(yes), &yes);

        } else{

            MMErrorWithModule(LOGMODULE, @”Create Sound Error.”);

            _soundId = 0;

        }

    }    returnself;

}

– (void)checkSoundSwitchStatus:(CheckSwitchStatusCompleteBlk)completHandler {  

         if(self.soundId == 0) {

        completHandler(YES);     

        return;

    }

    self.completeHandler = completHandler;

    self.beginTime = CACurrentMediaTime();

    AudioServicesPlaySystemSound(self.soundId);

}

– (void)complete {

    CFTimeInterval elapsed = CACurrentMediaTime() – self.beginTime;

    BOOLisSwitchOn = elapsed > 0.1;  

     if(self.completeHandler) {

        self.completeHandler(isSwitchOn);

    }

}

4、設定聲音閾值

另外一個使用者反饋較多的問題是聽不到播報聲音,通過檢視日誌發現是觸發語音播報時,使用者設定的系統音量過小所導致。首先想到的解決方案是直接設定AVAudioPlayer的volume(或者是AudioQueue中的kAudioQueueParam_Volume),然而實驗過後發現這樣行不通,volume屬性受制於系統音量(比如系統volume是0.5,AVAudioPlayer的音量是0.6,則最終的音量為0.5*0.6 =0.3)。

要解決音量過小的問題,還是需要通過調節系統音量。最終的解決方案借鑑了進入收付款展示二維碼時自動調節螢幕亮度的方案:如果螢幕亮度未達到閾值,則調高螢幕亮度到閾值,離開頁面時,將亮度設回原亮度。同理,播放提示音時,若使用者設定的系統音量小於閾值,則調節到閾值。提示音播放完畢後,將提示音調回原音量。

控制系統音量有以下兩種方式。

4.1 方式一:通過MPMusicPlayerController設定音量

//This property is deprecated — use MPVolumeView for volume control instead.mpc.volume = 0;  //0.0~1.0

MPMusicPlayerController *mpc = [MPMusicPlayerController applicationMusicPlayer];

第一種方式簡單粗暴,在設定的時候會彈出系統音量提示框,如果使用者在使用app的過程突然彈出音量框,會對使用者造成困擾,不建議使用這種方式,並且蘋果在iOS7.0以後已將該屬性標為deprecated。

4.2 方式二:通過MPVolumeView設定音量

第二種方式則是將一個看不見的MPVolumeView新增到當前檢視上,系統音量提示框就不會顯示了。

需要注意的是:在調節完系統音量需要將MPVolumeView移除,否則後續使用者手動調節音量會出現系統音量提示框不顯示的情況。

調節音量的方式,則是先取到MPVolumeView中名為MPVolumeSlider的子View,並對其傳送模擬使用者操作的事件。

– (void)setSystemVolume:(float)volume {

    UISlider* volumeViewSlider = nil; 

   for(UIView *view in [self.m_privateVoulmeView subviews]){    

      if([view.class.description isEqualToString:@”MPVolumeSlider”]){

            volumeViewSlider = (UISlider*)view;       

               break;

        }

    }    if(volumeViewSlider != nil) {

        [volumeViewSlider setValue:volume animated:NO];        //通過send

        [volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];

    }

}

附錄:有關微信、QQ的文章彙總

[1] QQ、微信的技術文章:

iOS後臺喚醒實戰:微信收款到賬語音提醒技術總結

騰訊技術分享:社交網路圖片的頻寬壓縮技術演進之路

微信團隊分享:視訊影像的超解析度技術原理和應用場景

微信團隊分享:微信每日億次實時音視訊聊天背後的技術解密

QQ音樂團隊分享:Android中的圖片壓縮技術詳解(上篇)

QQ音樂團隊分享:Android中的圖片壓縮技術詳解(下篇)

騰訊團隊分享:手機QQ中的人臉識別酷炫動畫效果實現詳解

騰訊團隊分享 :一次手Q聊天介面中圖片顯示bug的追蹤過程分享

微信團隊分享:微信Android版小視訊編碼填過的那些坑》 

微信手機端的本地資料全文檢索優化之路》 

企業微信客戶端中組織架構資料的同步更新方案優化實戰

微信團隊披露:微信介面卡死超級bug“15。。。。”的來龍去脈

QQ 18年:解密8億月活的QQ後臺服務介面隔離技術

月活8.89億的超級IM微信是如何進行Android端相容測試的

以手機QQ為例探討移動端IM中的“輕應用”

一篇文章get微信開源移動端資料庫元件WCDB的一切!

微信客戶端團隊負責人技術訪談:如何著手客戶端效能監控和優化

微信後臺基於時間序的海量資料冷熱分級架構設計實踐

微信團隊原創分享:Android版微信的臃腫之困與模組化實踐之路

微信後臺團隊:微信後臺非同步訊息佇列的優化升級實踐分享

微信團隊原創分享:微信客戶端SQLite資料庫損壞修復實踐》 

騰訊原創分享(一):如何大幅提升行動網路下手機QQ的圖片傳輸速度和成功率》 

騰訊原創分享(二):如何大幅壓縮行動網路下APP的流量消耗(下篇)》 

騰訊原創分享(二):如何大幅壓縮行動網路下APP的流量消耗(上篇)》 

微信Mars:微信內部正在使用的網路層封裝庫,即將開源》 

如約而至:微信自用的移動端IM網路層跨平臺元件庫Mars已正式開源》 

開源libco庫:單機千萬連線、支撐微信8億使用者的後臺框架基石 [原始碼下載]》 

微信新一代通訊安全解決方案:基於TLS1.3的MMTLS詳解》 

微信團隊原創分享:Android版微信後臺保活實戰分享(程式保活篇)》 

微信團隊原創分享:Android版微信後臺保活實戰分享(網路保活篇)》 

Android版微信從300KB到30MB的技術演進(PPT講稿) [附件下載]》 

微信團隊原創分享:Android版微信從300KB到30MB的技術演進》 

微信技術總監談架構:微信之道——大道至簡(演講全文)

微信技術總監談架構:微信之道——大道至簡(PPT講稿) [附件下載]》 

如何解讀《微信技術總監談架構:微信之道——大道至簡》

微信海量使用者背後的後臺系統儲存架構(視訊+PPT) [附件下載]

微信非同步化改造實踐:8億月活、單機千萬連線背後的後臺解決方案》 

微信朋友圈海量技術之道PPT [附件下載]》 

微信對網路影響的技術試驗及分析(論文全文)》 

一份微信後臺技術架構的總結性筆記》 

架構之道:3個程式設計師成就微信朋友圈日均10億釋出量[有視訊]》 

快速裂變:見證微信強大後臺架構從0到1的演進歷程(一)

快速裂變:見證微信強大後臺架構從0到1的演進歷程(二)》 

微信團隊原創分享:Android記憶體洩漏監控和優化技巧總結》 

全面總結iOS版微信升級iOS9遇到的各種“坑”》 

微信團隊原創資源混淆工具:讓你的APK立減1M》 

微信團隊原創Android資源混淆工具:AndResGuard [有原始碼]》 

Android版微信安裝包“減肥”實戰記錄》 

iOS版微信安裝包“減肥”實戰記錄》 

移動端IM實踐:iOS版微信介面卡頓監測方案》 

微信“紅包照片”背後的技術難題》 

移動端IM實踐:iOS版微信小視訊功能技術方案實錄》 

移動端IM實踐:Android版微信如何大幅提升互動效能(一)

移動端IM實踐:Android版微信如何大幅提升互動效能(二)

移動端IM實踐:實現Android版微信的智慧心跳機制》 

移動端IM實踐:WhatsApp、Line、微信的心跳策略分析》 

移動端IM實踐:谷歌訊息推送服務(GCM)研究(來自微信)

移動端IM實踐:iOS版微信的多裝置字型適配方案探討》 

信鴿團隊原創:一起走過 iOS10 上訊息推送(APNS)的坑

騰訊信鴿技術分享:百億級實時訊息推送的實戰經驗

>> 更多同類文章 ……

[2] QQ、微信的技術故事:

2017微信資料包告:日活躍使用者達9億、日發訊息380億條

騰訊開發微信花了多少錢?技術難度真這麼大?難在哪?

技術往事:創業初期的騰訊——16年前的冬天,誰動了馬化騰的程式碼》 

技術往事:史上最全QQ圖示變遷過程,追尋IM巨人的演進歷史》 

技術往事:“QQ群”和“微信紅包”是怎麼來的?》 

開發往事:深度講述2010到2015,微信一路風雨的背後》 

開發往事:微信千年不變的那張閃屏圖片的由來》 

開發往事:記錄微信3.0版背後的故事(距微信1.0釋出9個月時)》 

一個微信實習生自述:我眼中的微信開發團隊

首次揭祕:QQ實時視訊聊天背後的神祕組織

>> 更多同類文章 ……

(本文同步釋出於:http://www.52im.net/thread-1404-1-1.html


相關文章