播放系統(tǒng)音的方法和遇到的坑

本人有若干成套學(xué)習(xí)視頻, 可試看! 可試看! 可試看, 重要的事情說(shuō)三遍 包含Java, 數(shù)據(jù)結(jié)構(gòu)與算法, iOS, 安卓, python, flutter等等, 如有需要, 聯(lián)系微信tsaievan.

今天說(shuō)個(gè)簡(jiǎn)單的.
在實(shí)際項(xiàng)目中, 我們經(jīng)常會(huì)遇到一些需要播放簡(jiǎn)短音效的地方, 比如我發(fā)送一條消息, 有提示音效, 我點(diǎn)擊某個(gè)按鈕, 有提示音效, 這種短的音效當(dāng)然也可以用播放音頻的AVAudioPlayer的方式播放出來(lái), 但是我們有更簡(jiǎn)單的方法.

我們可以封裝一個(gè)播放音效的管理類, 比如YFSoundEffectHelper什么的, 然后寫(xiě)一個(gè)類方法, 專門用來(lái)緩存soundID
static NSMutableDictionary *_soudIDs;
+ (NSMutableDictionary *)soudIDs {
    if (!_soudIDs) {
        _soudIDs = [NSMutableDictionary dictionary];
    }
    return _soudIDs;
}

因?yàn)槲覀兪褂玫倪@個(gè)播放音效的方法是C語(yǔ)言的, 我們需要將其封裝為OC的, 這樣調(diào)用就更方便

+ (void)playSoundEffect:(NSString *)effectName completionHandler:(void(^)())completionHandler{
    if (!effectName) {
        return;
    }
    SystemSoundID soundID = [[self soundIDs][effectName] unsignedIntValue];
    if (!soundID) {
        NSURL *url = [[NSBundle mainBundle] URLForResource:effectName withExtension:nil];
        if (!url) {
            return;
        }
        OSStatus status = AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(url), &soundID);
        NSLog(@"%d",(int)status);
        [self soundIDs][effectName] = @(soundID);
    }
    AudioServicesPlaySystemSoundWithCompletion(soundID, ^{
        if (completionHandler) {
            completionHandler();
        }
    });
}
這里面有兩個(gè)關(guān)鍵方法
  • AudioServicesCreateSystemSoundID(CFURLRef _Nonnull inFileURL, SystemSoundID * _Nonnull outSystemSoundID)
    第一個(gè)參數(shù)填url的地址, 當(dāng)然這里不是NSURL,而是需要用(__bridge CFURLRef _Nonnull)關(guān)鍵字進(jìn)行橋接, 當(dāng)然這個(gè)不用記,你填了OC的URL之后, 會(huì)自動(dòng)有一個(gè)紅色的大胖圓點(diǎn), 你一點(diǎn)就自動(dòng)給你改好了.
    第二個(gè)參數(shù)填soundID的地址.

  • AudioServicesPlaySystemSoundWithCompletion( SystemSoundID inSystemSoundID,void (^__nullable inCompletionBlock)(void))
    第一個(gè)參數(shù)填你需要播放音效的soundID,第二個(gè)參數(shù)填音效播放完成后需要執(zhí)行的代碼, 這是一個(gè)Block回調(diào).

也就是說(shuō), 播放音效只需要執(zhí)行以下步驟,

1.注冊(cè)soundID, 就是把音效文件的url(bundle)地址和soundID的指針地址傳給AudioServicesCreateSystemSoundID(_,_)這個(gè)函數(shù).
2.播放音效,將soundID和回調(diào)代碼傳給AudioServicesPlaySystemSoundWithCompletion(_,_)這個(gè)函數(shù)

框架同時(shí)還提供了一個(gè)方法,AudioServicesPlaySystemSound(_),只需要傳soundID這一個(gè)參數(shù)即可, 但是這是有坑的, 為什么呢?
因?yàn)樵诓シ怕曇舻臅r(shí)候, 可能會(huì)有其他打擾音效播放的事情, 比如你點(diǎn)擊按鈕準(zhǔn)備錄音, 這時(shí)候音效是無(wú)法從揚(yáng)聲器播放出來(lái)的, 也就是你只能從聽(tīng)筒聽(tīng)到, 聲音很小, 因?yàn)椴シ乓粜钱惒綀?zhí)行的, 可錄音同時(shí)進(jìn)行, 系統(tǒng)就默認(rèn)了聲音不從揚(yáng)聲器放出, 所以你必須使用帶block的方法 , 讓音頻完全播放完畢之后再執(zhí)行錄音代碼.

可能就是因?yàn)檫@一點(diǎn), 蘋(píng)果將在下一個(gè)版本廢棄掉AudioServicesPlaySystemSound(_)這個(gè)方法.

如果要保持始終從揚(yáng)聲器播放音效, 還需要添加如下代碼:
AVAudioSession *session = [AVAudioSession sharedInstance];
    NSError *setCategoryError = nil;
    if (![session setCategory:AVAudioSessionCategoryPlayback
                  withOptions:AVAudioSessionCategoryOptionMixWithOthers
                        error:&setCategoryError]) {
        NSLog(@"開(kāi)啟揚(yáng)聲器發(fā)生錯(cuò)誤:%@",setCategoryError.localizedDescription);
    }

之前我沒(méi)有添加以上代碼, 在真機(jī)上運(yùn)行時(shí) ,還是會(huì)出現(xiàn)不從揚(yáng)聲器,而從聽(tīng)筒播放聲音的現(xiàn)象, 在+ (void)playSoundEffect:(NSString *)effectName completionHandler:(void(^)())completionHandler添加了以上代碼之后,解決了問(wèn)題, 會(huì)話session在設(shè)置的時(shí)候, 有如下幾個(gè)選項(xiàng)

AVAudioSessionCategoryOptionMixWithOthers
AVAudioSessionCategoryOptionDuckOthers
AVAudioSessionCategoryOptionAllowBluetooth
AVAudioSessionCategoryOptionDefaultToSpeaker
AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers

  • AVAudioSessionCategoryOptionMixWithOthers: 音頻將和其他的已經(jīng)激活的音頻合并, 假設(shè)現(xiàn)在有2個(gè)激活的音頻會(huì)話, 那么這兩個(gè)會(huì)話會(huì)并存, 不會(huì)將另一個(gè)沉默掉

  • AVAudioSessionCategoryOptionDuckOthers : 這個(gè)選項(xiàng)會(huì)沉默掉其他音頻會(huì)話, 使其他音頻會(huì)話的音量減小
    我估計(jì)在項(xiàng)目中, 我的音頻會(huì)話就是被另一個(gè)音頻會(huì)話給沉默掉了

  • AVAudioSessionCategoryOptionAllowBluetooth: 這個(gè)選項(xiàng)是允許藍(lán)牙設(shè)備作為輸入音軌

  • AVAudioSessionCategoryOptionDefaultToSpeaker : 將會(huì)話中的音頻轉(zhuǎn)至默認(rèn)的內(nèi)置揚(yáng)聲器

  • AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers : 如果你的APP偶然要說(shuō)句話什么的, 比如導(dǎo)航類的APP或者運(yùn)動(dòng)類的APP, 就使用這個(gè)選項(xiàng), 將打斷連續(xù)的音頻, 比如音樂(lè)播放器或者語(yǔ)音電子書(shū)一類APP

如果一個(gè)錄音完成后又要播放提示音, 這時(shí)候使用AudioServicesPlaySystemSoundWithCompletion(_,_)也沒(méi)有用, 為什么呢?因?yàn)殇浺舻氖虑榭赡苓€沒(méi)有執(zhí)行完, 有個(gè)小技巧是延時(shí)0.1秒執(zhí)行播放代碼

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [YFSoundEffectHelper playSoundEffect:kZHLVoiceRecognizeEndSoundEffect completionHandler:nil];
        });

這個(gè)時(shí)候就不會(huì)有影響

還有一點(diǎn)需要注意的是, 在C語(yǔ)言中, 有creat就必須要釋放, 此時(shí), 框架給我提供的方法是AudioServicesDisposeSystemSoundID(SystemSoundID inSystemSoundID)

同樣的, 我們將其封裝為OC的代碼方便調(diào)用

+ (void)disposeSoundEffect:(NSString *)effectName {
    if (!effectName) {
        return;
    }
    SystemSoundID soundID = [[self soundIDs][effectName] unsignedIntValue];
    if (soundID) {
        AudioServicesDisposeSystemSoundID(soundID);
        [[self soundIDs]removeObjectForKey:effectName];
    }
}

以上就是播放系統(tǒng)音/音效的方法, 是不是很簡(jiǎn)單呢?

PS. 本人有若干成套學(xué)習(xí)視頻, 包含Java, 數(shù)據(jù)結(jié)構(gòu)與算法, iOS, 安卓, python, flutter等等, 如有需要, 聯(lián)系微信tsaievan.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末时捌,一起剝皮案震驚了整個(gè)濱河市城看,隨后出現(xiàn)的幾起案子支子,更是在濱河造成了極大的恐慌逸邦,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件或辖,死亡現(xiàn)場(chǎng)離奇詭異率挣,居然都是意外死亡浩村,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門护蝶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)华烟,“玉大人,你說(shuō)我怎么就攤上這事持灰】梗” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)喂链。 經(jīng)常有香客問(wèn)我返十,道長(zhǎng),這世上最難降的妖魔是什么椭微? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任洞坑,我火速辦了婚禮,結(jié)果婚禮上蝇率,老公的妹妹穿的比我還像新娘迟杂。我一直安慰自己,他們只是感情好本慕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布排拷。 她就那樣靜靜地躺著,像睡著了一般锅尘。 火紅的嫁衣襯著肌膚如雪攻泼。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,775評(píng)論 1 307
  • 那天鉴象,我揣著相機(jī)與錄音忙菠,去河邊找鬼。 笑死纺弊,一個(gè)胖子當(dāng)著我的面吹牛牛欢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播淆游,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼傍睹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了犹菱?” 一聲冷哼從身側(cè)響起拾稳,我...
    開(kāi)封第一講書(shū)人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎腊脱,沒(méi)想到半個(gè)月后访得,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡陕凹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年悍抑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杜耙。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搜骡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出佑女,到底是詐尸還是另有隱情记靡,我是刑警寧澤谈竿,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站摸吠,受9級(jí)特大地震影響空凸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜕便,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一劫恒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧轿腺,春花似錦两嘴、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仿荆,卻和暖如春贰您,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拢操。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工锦亦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人令境。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓杠园,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親舔庶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抛蚁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

推薦閱讀更多精彩內(nèi)容