国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > iOS > 正文

AudioSession詳解

2019-11-07 23:42:06
字體:
來源:轉載
供稿:網友

摘要:

前言

在實施前一篇中所述的7個步驟步之前還必須面對一個麻煩的問題,Audiosession。 AudioSession簡介AudioSession這個玩意的主要功能包括以下幾點(圖片來自官方文檔): 1. 確定你的app如何使用音頻(是播放?還是錄音?)2. 為你的app選擇合適的輸入輸出設備(比如輸入用的麥克風,輸出是耳機、手機功放或者airplay)3. 協調你的app的音頻播放和系統以及其他app行為(例如有電話時需要打斷,電話結束時需要恢復,按下靜音按鈕時是否歌曲也要靜音等)AudioSession AudioSession相關的類有兩個:1. AudioToolBox中的AudioSession2. AVFoundation中的AVAudioSession 其中AudioSession在SDK 7中已經被標注為dePRacated,而AVAudioSession這個類雖然iOS 3開始就已經存在了,但其中很多方法和變量都是在iOS 6以后甚至是iOS 7才有的。所以各位可以依照以下標準選擇: * 如果最低版本支持iOS 5,可以使用AudioSession,也可以使用AVAudioSession;* 如果最低版本支持iOS 6及以上,請使用AVAudioSession 下面以AudioSession類為例來講述AudioSession相關功能的使用(很不幸我需要支持iOS 5。。T-T,使用AVAudioSession的同學可以在其頭文件中尋找對應的方法使用即可,需要注意的點我會加以說明)。 注意:在使用AVAudioPlayer/AVPlayer時可以不用關心AudioSession的相關問題,Apple已經把AudioSession的處理過程封裝了,但音樂打斷后的響應還是要做的(比如打斷后音樂暫停了UI狀態也要變化,這個應該通過KVO就可以搞定了吧。。我沒試過瞎猜的>_<)。 初始化AudioSession使用AudioSession類首先需要調用初始化方法:
extern OSStatus AudioSessionInitialize(CFRunLoopRef inRunLoop,                                        CFStringRef inRunLoopMode,                                        AudioSessionInterruptionListener inInterruptionListener,                                        void *inClientData); 前兩個參數一般填NULL表示AudioSession運行在主線程上(但并不代表音頻的相關處理運行在主線程上,只是AudioSession),第三個參數需要傳入一個一個AudioSessionInterruptionListener類型的方法,作為AudioSession被打斷時的回調,第四個參數則是代表打斷回調時需要附帶的對象(即回到方法中的inClientData,如下所示,可以理解為UIView animation中的context)。
typedef void (*AudioSessionInterruptionListener)(void * inClientData, UInt32 inInterruptionState); 這才剛開始,坑就來了。這里會有兩個問題: 第一,AudioSessionInitialize可以被多次執行,但AudioSessionInterruptionListener只能被設置一次,這就意味著這個打斷回調方法是一個靜態方法,一旦初始化成功以后所有的打斷都會回調到這個方法,即便下一次再次調用AudioSessionInitialize并且把另一個靜態方法作為參數傳入,當打斷到來時還是會回調到第一次設置的方法上。 這種場景并不少見,例如你的app既需要播放歌曲又需要錄音,當然你不可能知道用戶會先調用哪個功能,所以你必須在播放和錄音的模塊中都調用AudioSessionInitialize注冊打斷方法,但最終打斷回調只會作用在先注冊的那個模塊中,很蛋疼吧。。。所以對于AudioSession的使用最好的方法是生成一個類單獨進行管理,統一接收打斷回調并發送自定義的打斷通知,在需要用到AudioSession的模塊中接收通知并做相應的操作。 Apple也察覺到了這一點,所以在AVAudioSession中首先取消了Initialize方法,改為了單例方法sharedInstance。在iOS 5上所有的打斷都需要通過設置id<AVAudioSessionDelegate> delegate并實現回調方法來實現,這同樣會有上述的問題,所以在iOS 5使用AVAudioSession下仍然需要一個單獨管理AudioSession的類存在。在iOS 6以后Apple終于把打斷改成了通知的形式。。這下科學了。 第二,AudioSessionInitialize方法的第四個參數inClientData,也就是回調方法的第一個參數。上面已經說了打斷回調是一個靜態方法,而這個參數的目的是為了能讓回調時拿到context(上下文信息),所以這個inClientData需要是一個有足夠長生命周期的對象(當然前提是你確實需要用到這個參數),如果這個對象被dealloc了,那么回調時拿到的inClientData會是一個野指針。就這一點來說構造一個單獨管理AudioSession的類也是有必要的,因為這個類的生命周期和AudioSession一樣長,我們可以把context保存在這個類中。 監聽RouteChange事件如果想要實現類似于“拔掉耳機就把歌曲暫停”的功能就需要監聽RouteChange事件:
extern OSStatus AudioSessionAddPropertyListener(AudioSessionPropertyID inID,                                                 AudioSessionPropertyListener inProc,                                                 void *inClientData);                                                typedef void (*AudioSessionPropertyListener)(void * inClientData,                                              AudioSessionPropertyID inID,                                              UInt32 inDataSize,                                              const void * inData); 調用上述方法,AudioSessionPropertyID參數傳kAudioSessionProperty_AudioRouteChange,AudioSessionPropertyListener參數傳對應的回調方法。inClientData參數同AudioSessionInitialize方法。 同樣作為靜態回調方法還是需要統一管理,接到回調時可以把第一個參數inData轉換成CFDictionaryRef并從中獲取kAudioSession_AudioRouteChangeKey_Reason鍵值對應的value(應該是一個CFNumberRef),得到這些信息后就可以發送自定義通知給其他模塊進行相應操作(例如kAudioSessionRouteChangeReason_OldDeviceUnavailable就可以用來做“拔掉耳機就把歌曲暫停”)。
//AudioSession的AudioRouteChangeReason枚舉 enum {       kAudioSessionRouteChangeReason_Unknown = 0,       kAudioSessionRouteChangeReason_NewDeviceAvailable = 1,       kAudioSessionRouteChangeReason_OldDeviceUnavailable = 2,       kAudioSessionRouteChangeReason_CategoryChange = 3,       kAudioSessionRouteChangeReason_Override = 4,       kAudioSessionRouteChangeReason_WakeFromSleep = 6,       kAudioSessionRouteChangeReason_NoSuitableRouteForCategory = 7,       kAudioSessionRouteChangeReason_RouteConfigurationChange = 8   }; 
//AVAudioSession的AudioRouteChangeReason枚舉 typedef NS_ENUM(NSUInteger, AVAudioSessionRouteChangeReason) {   AVAudioSessionRouteChangeReasonUnknown = 0,   AVAudioSessionRouteChangeReasonNewDeviceAvailable = 1,   AVAudioSessionRouteChangeReasonOldDeviceUnavailable = 2,   AVAudioSessionRouteChangeReasonCategoryChange = 3,   AVAudioSessionRouteChangeReasonOverride = 4,   AVAudioSessionRouteChangeReasonWakeFromSleep = 6,   AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory = 7,   AVAudioSessionRouteChangeReasonRouteConfigurationChange NS_ENUM_AVAILABLE_IOS(7_0) = 8 } 注意:iOS 5下如果使用了AVAudioSession由于AVAudioSessionDelegate中并沒有定義相關的方法,還是需要用這個方法來實現監聽。iOS 6下直接監聽AVAudioSession的通知就可以了。 這里附帶兩個方法的實現,都是基于AudioSession類的(使用AVAudioSession的同學幫不到你們啦)。 1、判斷是否插了耳機:
+ (BOOL)usingHeadset { #if TARGET_ipHONE_SIMULATOR     return NO; #endif      CFStringRef route;     UInt32 propertySize = sizeof(CFStringRef);     AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &route);      BOOL hasHeadset = NO;     if((route == NULL) || (CFStringGetLength(route) == 0))     {         // Silent Mode     }     else     {         /* Known values of route:          * "Headset"          * "Headphone"          * "Speaker"          * "SpeakerAndMicrophone"          * "HeadphonesAndMicrophone"          * "HeadsetInOut"          * "ReceiverAndMicrophone"          * "Lineout"          */         NSString* routeStr = (__bridge NSString*)route;         NSRange headphoneRange = [routeStr rangeOfString : @"Headphone"];         NSRange headsetRange = [routeStr rangeOfString : @"Headset"];          if (headphoneRange.location != NSNotFound)         {             hasHeadset = YES;         }         else if(headsetRange.location != NSNotFound)         {             hasHeadset = YES;         }     }      if (route)     {         CFRelease(route);     }      return hasHeadset; }  2、判斷是否開了Airplay(來自StackOverflow):
+ (BOOL)isAirplayActived {     CFDictionaryRef currentRouteDescriptionDictionary = nil;     UInt32 dataSize = sizeof(currentRouteDescriptionDictionary);     AudioSessionGetProperty(kAudioSessionProperty_AudioRouteDescription, &dataSize, &currentRouteDescriptionDictionary);      BOOL airplayActived = NO;     if (currentRouteDescriptionDictionary)     {         CFArrayRef outputs = CFDictionaryGetValue(currentRouteDescriptionDictionary, kAudioSession_AudioRouteKey_Outputs);         if(outputs != NULL && CFArrayGetCount(outputs) > 0)         {             CFDictionaryRef currentOutput = CFArrayGetValueAtIndex(outputs, 0);             //Get the output type (will show airplay / hdmi etc             CFStringRef outputType = CFDictionaryGetValue(currentOutput, kAudioSession_AudioRouteKey_Type);              airplayActived = (CFStringCompare(outputType, kAudioSessionOutputRoute_AirPlay, 0) == kCFCompareEqualTo);         }         CFRelease(currentRouteDescriptionDictionary);     }     return airplayActived; }  設置類別下一步要設置AudioSession的Category,使用AudioSession時調用下面的接口
extern OSStatus AudioSessionSetProperty(AudioSessionPropertyID inID,                                         UInt32 inDataSize,                                         const void *inData); 如果我需要的功能是播放,執行如下代碼
UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback; AudioSessionSetProperty (kAudioSessionProperty_AudioCategory,                          sizeof(sessionCategory),                          &sessionCategory); 使用AVAudioSession時調用下面的接口
/* set session category */ - (BOOL)setCategory:(NSString *)category error:(NSError **)outError; /* set session category with options */ - (BOOL)setCategory:(NSString *)category withOptions: (AVAudioSessionCategoryOptions)options error:(NSError **)outError NS_AVAILABLE_IOS(6_0); 至于Category的類型在官方文檔中都有介紹,我這里也只羅列一下具體就不贅述了,各位在使用時可以依照自己需要的功能設置Category。
//AudioSession的AudioSessionCategory枚舉 enum {       kAudioSessionCategory_AmbientSound               = 'ambi',       kAudioSessionCategory_SoloAmbientSound           = 'solo',       kAudioSessionCategory_MediaPlayback              = 'medi',       kAudioSessionCategory_RecordAudio                = 'reca',       kAudioSessionCategory_PlayAndRecord              = 'plar',       kAudioSessionCategory_AudioProcessing            = 'proc'   }; 
//AudioSession的AudioSessionCategory字符串 /*  Use this category for background sounds such as rain, car engine noise, etc.    Mixes with other music. */ AVF_EXPORT NSString *const AVAudioSessionCategoryAmbient;    /*  Use this category for background sounds.  Other music will stop playing. */ AVF_EXPORT NSString *const AVAudioSessionCategorySoloAmbient;  /* Use this category for music tracks.*/ AVF_EXPORT NSString *const AVAudioSessionCategoryPlayback;  /*  Use this category when recording audio. */ AVF_EXPORT NSString *const AVAudioSessionCategoryRecord;  /*  Use this category when recording and playing back audio. */ AVF_EXPORT NSString *const AVAudioSessionCategoryPlayAndRecord;  /*  Use this category when using a hardware codec or signal processor while  not playing or recording audio. */ AVF_EXPORT NSString *const AVAudioSessionCategoryAudioProcessing;  啟用有了Category就可以啟動AudioSession了,啟動方法:
//AudioSession的啟動方法 extern OSStatus AudioSessionSetActive(Boolean active); extern OSStatus AudioSessionSetActiveWithFlags(Boolean active, UInt32 inFlags);  //AVAudioSession的啟動方法 - (BOOL)setActive:(BOOL)active error:(NSError **)outError; - (BOOL)setActive:(BOOL)active withFlags:(NSInteger)flags error:(NSError **)outError NS_DEPRECATED_IOS(4_0, 6_0); - (BOOL)setActive:(BOOL)active withOptions:(AVAudioSessionSetActiveOptions)options error:(NSError **)outError NS_AVAILABLE_IOS(6_0); 啟動方法調用后必須要判斷是否啟動成功,啟動不成功的情況經常存在,例如一個前臺的app正在播放,你的app正在后臺想要啟動AudioSession那就會返回失敗。 一般情況下我們在啟動和停止AudioSession調用第一個方法就可以了。但如果你正在做一個即時語音通訊app的話(類似于微信、易信)就需要注意在deactive AudioSession的時候需要使用第二個方法,inFlags參數傳入kAudioSessionSetActiveFlag_NotifyOthersOnDeactivation(AVAudioSession給options參數傳入AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation)。當你的app deactive自己的AudioSession時系統會通知上一個被打斷播放app打斷結束(就是上面說到的打斷回調),如果你的app在deactive時傳入了NotifyOthersOnDeactivation參數,那么其他app在接到打斷結束回調時會多得到一個參數kAudioSessionInterruptionType_ShouldResume否則就是ShouldNotResume(AVAudioSessionInterruptionOptionShouldResume),根據參數的值可以決定是否繼續播放。 大概流程是這樣的: 1. 一個音樂軟件A正在播放;2. 用戶打開你的軟件播放對話語音,AudioSession active;3. 音樂軟件A音樂被打斷并收到InterruptBegin事件;4. 對話語音播放結束,AudioSession deactive并且傳入NotifyOthersOnDeactivation參數;5. 音樂軟件A收到InterruptEnd事件,查看Resume參數,如果是ShouldResume控制音頻繼續播放,如果是ShouldNotResume就維持打斷狀態; 官方文檔中有一張很形象的圖來闡述這個現象: 然而現在某些語音通訊軟件和某些音樂軟件卻無視了NotifyOthersOnDeactivation和ShouldResume的正確用法,導致我們經常接到這樣的用戶反饋:“你們的app在使用xx語音軟件聽了一段話后就不會繼續播放了,但xx音樂軟件可以繼續播放啊。” 好吧,上面只是吐槽一下。請無視我吧。 補充: 發現即使之前已經調用過AudioSessionInitialize方法,在某些情況下被打斷之后可能出現AudioSession失效的情況,需要再次調用AudioSessionInitialize方法來重新生成AudioSession。否則調用AudioSessionSetActive會返回560557673(其他AudioSession方法也雷同,所有方法調用前必須首先初始化AudioSession),轉換成string后為”!ini”即kAudioSessionNotInitialized,這個情況在iOS 5.1.x上尤其頻繁,iOS 7.x也偶有發生具體的原因還不知曉。 所以每次在調用AudioSessionSetActive時應該判斷一下錯誤碼,如果是上述的錯誤碼需要重新初始化一下AudioSession。 附上OSStatus轉成string的方法:
#import <Endian.h>  NSString * OSStatusToString(OSStatus status) {     size_t len = sizeof(UInt32);     long addr = (unsigned long)&status;     char cstring[5];      len = (status >> 24) == 0 ? len - 1 : len;     len = (status >> 16) == 0 ? len - 1 : len;     len = (status >>  8) == 0 ? len - 1 : len;     len = (status >>  0) == 0 ? len - 1 : len;      addr += (4 - len);      status = EndianU32_NtoB(status);        // strings are big endian      strncpy(cstring, (char *)addr, len);     cstring[len] = 0;      return [NSString stringWithCString:(char *)cstring encoding:NSMacOSRomanStringEncoding]; }  打斷處理正常啟動AudioSession之后就可以播放音頻了,下面要講的是對于打斷的處理。之前我們說到打斷的回調在iOS 5下需要統一管理,在收到打斷開始和結束時需要發送自定義的通知。 使用AudioSession時打斷回調應該首先獲取kAudioSessionProperty_InterruptionType,然后發送一個自定義的通知并帶上對應的參數。
static void MyAudioSessionInterruptionListener(void *inClientData, UInt32 inInterruptionState) {     AudioSessionInterruptionType interruptionType = kAudioSessionInterruptionType_ShouldNotResume;     UInt32 interruptionTypeSize = sizeof(interruptionType);     AudioSessionGetProperty(kAudioSessionProperty_InterruptionType,                             &interruptionTypeSize,                             &interruptionType);      NSDictionary *userInfo = @{MyAudioInterruptionStateKey:@(inInterruptionState),                                MyAudioInterruptionTypeKey:@(interruptionType)};      [[NSNotificationCenter defaultCenter] postNotificationName:MyAudioInterruptionNotification object:nil userInfo:userInfo]; } 收到通知后的處理方法如下(注意ShouldResume參數):
- (void)interruptionNotificationReceived:(NSNotification *)notification {     UInt32 interruptionState = [notification.userInfo[MyAudioInterruptionStateKey] unsignedIntValue];     AudioSessionInterruptionType interruptionType = [notification.userInfo[MyAudioInterruptionTypeKey] unsignedIntValue];     [self handleAudioSessionInterruptionWithState:interruptionState type:interruptionType]; }  - (void)handleAudioSessionInterruptionWithState:(UInt32)interruptionState type:(AudioSessionInterruptionType)interruptionType {     if (interruptionState == kAudioSessionBeginInterruption)     {         //控制UI,暫停播放     }     else if (interruptionState == kAudioSessionEndInterruption)     {         if (interruptionType == kAudioSessionInterruptionType_ShouldResume)         {             OSStatus status = AudioSessionSetActive(true);             if (status == noErr)             {                 //控制UI,繼續播放             }         }     } }  小結關于AudioSession的話題到此結束(碼字果然很累。。)。小結一下: * 如果最低版本支持iOS 5,可以使用AudioSession也可以考慮使用AVAudioSession,需要有一個類統一管理AudioSession的所有回調,在接到回調后發送對應的自定義通知;* 如果最低版本支持iOS 6及以上,請使用AVAudioSession,不用統一管理,接AVAudioSession的通知即可;* 根據app的應用場景合理選擇Category;* 在deactive時需要注意app的應用場景來合理的選擇是否使用NotifyOthersOnDeactivation參數;* 在處理InterruptEnd事件時需要注意ShouldResume的值。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 抚远县| 弥渡县| 内乡县| 皮山县| 东阳市| 巫山县| 连云港市| 鄄城县| 通辽市| 政和县| 同江市| 阜新市| 德安县| 冀州市| 康保县| 铁力市| 阿坝| 富源县| 沐川县| 揭东县| 梅河口市| 石景山区| 恭城| 新巴尔虎右旗| 邵阳县| 南宫市| 巴彦淖尔市| 潼关县| 台北县| 蒙城县| 巴东县| 泽普县| 安塞县| 东平县| 丰城市| 墨竹工卡县| 自贡市| 城固县| 封开县| 大石桥市| 桐庐县|