本人有若干成套學(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)單呢?