需求背景:
最近有一個需求,需要新增一個系統(tǒng)鈴聲,并且可以支持調(diào)整鈴聲的大小。但是 iOS 系統(tǒng)其實是沒有直接提供 API 來直接調(diào)整系統(tǒng)鈴聲的大小。我發(fā)現(xiàn)可以調(diào)整音頻文件的聲音大小,所以我們可以先調(diào)整 wav 文件的聲音,然后再通過重復(fù)注冊系統(tǒng)鈴聲,再通過注冊的聲音 id 進(jìn)行播放就好了。
實現(xiàn)代碼
調(diào)整 wav 文件音量的代碼:
import Foundation
import AVFoundation
/// WAV 音頻音量調(diào)節(jié)工具類
class DDWavVolumeAdjuster {
/// 音量調(diào)節(jié)錯誤類型
enum VolumeAdjustError: Error {
case invalidURL
case invalidFileFormat
case readError
case writeError
case processingError
}
/// 調(diào)整 WAV 文件音量并生成新文件
/// - Parameters:
/// - inputPath: 輸入 WAV 文件路徑
/// - outputPath: 輸出 WAV 文件路徑
/// - volumeMultiplier: 音量倍數(shù) (0.0 ~ N.0,1.0 為原始音量)
/// - Throws: VolumeAdjustError
static func adjustVolume(inputURL: URL,
outputURL: URL,
volumeMultiplier: Float) throws {
// 創(chuàng)建音頻文件
guard let audioFile = try? AVAudioFile(forReading: inputURL) else {
throw VolumeAdjustError.invalidFileFormat
}
// 獲取音頻格式
let format = audioFile.processingFormat
let frameCount = UInt32(audioFile.length)
// 創(chuàng)建 PCM 緩沖區(qū)
guard let buffer = AVAudioPCMBuffer(pcmFormat: format,
frameCapacity: frameCount) else {
throw VolumeAdjustError.processingError
}
// 讀取音頻數(shù)據(jù)
do {
try audioFile.read(into: buffer)
} catch {
throw VolumeAdjustError.readError
}
// 調(diào)整音量
if let floatChannelData = buffer.floatChannelData {
let channelCount = Int(format.channelCount)
let frameLength = Int(buffer.frameLength)
for channel in 0..<channelCount {
let channelData = floatChannelData[channel]
for frame in 0..<frameLength {
channelData[frame] *= volumeMultiplier
// 確保音頻數(shù)據(jù)在有效范圍內(nèi) (-1.0 到 1.0)
if channelData[frame] > 1.0 {
channelData[frame] = 1.0
} else if channelData[frame] < -1.0 {
channelData[frame] = -1.0
}
}
}
}
// 創(chuàng)建輸出文件
do {
// 如果文件已存在,先刪除
try? FileManager.default.removeItem(at: outputURL)
let outputFile = try AVAudioFile(
forWriting: outputURL,
settings: audioFile.fileFormat.settings
)
// 寫入調(diào)整后的音頻數(shù)據(jù)
try outputFile.write(from: buffer)
} catch {
throw VolumeAdjustError.writeError
}
}
}
注冊系統(tǒng)鈴聲:
// 播放器
static var defaultSystemSoundID: SystemSoundID = 0
class func setupSystemAudio() {
if defaultSystemSoundID != 0 {
AudioServicesRemoveSystemSoundCompletion(defaultSystemSoundID)
defaultSystemSoundID = 0
}
let outputHitUrl = FileManager.appGroupSharedSupportDirectoryURL
.appendingPathComponent(DingDingConstants.hitWAVFile, isDirectory: false)
var soundID: SystemSoundID = 0
AudioServicesCreateSystemSoundID(outputHitUrl as CFURL, &soundID)
defaultSystemSoundID = soundID
let completion: AudioServicesSystemSoundCompletionProc = { (soundID, _) in
debugPrint("Sound \(soundID) finished playing!")
}
AudioServicesAddSystemSoundCompletion(soundID,
CFRunLoopGetMain(),
CFRunLoopMode.defaultMode.rawValue,
completion,
nil)
}
播放系統(tǒng)聲音:
DispatchQueue.global().async {
if 0 != StandardAudioFeedbackEngine.defaultSystemSoundID {
AudioServicesPlaySystemSound(StandardAudioFeedbackEngine.defaultSystemSoundID)
} else {
AudioServicesPlaySystemSound(id)
}
}