需求背景:
最近有一个需求,需要新增一个系统铃声,并且可以支持调整铃声的大小。但是 iOS 系统其实是没有直接提供 API 来直接调整系统铃声的大小。我发现可以调整音频文件的声音大小,所以我们可以先调整 wav 文件的声音,然后再通过重复注册系统铃声,再通过注册的声音 id 进行播放就好了。
实现代码
调整 wav 文件音量的代码:
import Foundation
import AVFoundation
/// WAV 音频音量调节工具类
class DDWavVolumeAdjuster {
/// 音量调节错误类型
enum VolumeAdjustError: Error {
case invalidURL
case invalidFileFormat
case readError
case writeError
case processingError
}
/// 调整 WAV 文件音量并生成新文件
/// - Parameters:
/// - inputPath: 输入 WAV 文件路径
/// - outputPath: 输出 WAV 文件路径
/// - volumeMultiplier: 音量倍数 (0.0 ~ N.0,1.0 为原始音量)
/// - Throws: VolumeAdjustError
static func adjustVolume(inputURL: URL,
outputURL: URL,
volumeMultiplier: Float) throws {
// 创建音频文件
guard let audioFile = try? AVAudioFile(forReading: inputURL) else {
throw VolumeAdjustError.invalidFileFormat
}
// 获取音频格式
let format = audioFile.processingFormat
let frameCount = UInt32(audioFile.length)
// 创建 PCM 缓冲区
guard let buffer = AVAudioPCMBuffer(pcmFormat: format,
frameCapacity: frameCount) else {
throw VolumeAdjustError.processingError
}
// 读取音频数据
do {
try audioFile.read(into: buffer)
} catch {
throw VolumeAdjustError.readError
}
// 调整音量
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
// 确保音频数据在有效范围内 (-1.0 到 1.0)
if channelData[frame] > 1.0 {
channelData[frame] = 1.0
} else if channelData[frame] < -1.0 {
channelData[frame] = -1.0
}
}
}
}
// 创建输出文件
do {
// 如果文件已存在,先删除
try? FileManager.default.removeItem(at: outputURL)
let outputFile = try AVAudioFile(
forWriting: outputURL,
settings: audioFile.fileFormat.settings
)
// 写入调整后的音频数据
try outputFile.write(from: buffer)
} catch {
throw VolumeAdjustError.writeError
}
}
}
注册系统铃声:
// 播放器
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)
}
播放系统声音:
DispatchQueue.global().async {
if 0 != StandardAudioFeedbackEngine.defaultSystemSoundID {
AudioServicesPlaySystemSound(StandardAudioFeedbackEngine.defaultSystemSoundID)
} else {
AudioServicesPlaySystemSound(id)
}
}