I’m utilizing AVAudioRecorder and utilizing its metering characteristic: AVAudioRecorder.averagePower(forChannel:)
In a simulator operating IOS 17.0.1, when a loud sound happens, the worth returned by this perform will be increased than zero, although the documentation states that the worth must be between 0 and -160. This drawback doesn’t appear to occur on an actual system, however sadly this doesn’t reassure me fully as a result of I solely have one actual system to check with, and when testing with it, there may be not likely a technique to confirm that I’m making a sound higher than which no different sound exists. Ya know?
Right here is a few code you possibly can confirm this with (problematic elements are marked “debug”):
/// Information audio to a temp file
/// and returns it as an in-memory AVAudioPCMBuffer on cease().
/// Whereas recording, three issues are revealed: degree, peak degree, and % of [peak hold time] remaining
enum AudioExtractionError: Error {
case fileNotFound
case bufferCreationFailed
case fileReadError(Error)
case unknown(Error)
}
@Observable // <---- take away this if testing with UIKit
class MemoRecorder {
non-public var randomPrefixForTempFile:String = UUID().uuidString
non-public var audioRecorder: AVAudioRecorder?
non-public var isRecording: Bool = false
non-public var meteringTimer: Timer?
non-public let METERING_TIMER_RESOLUTION_SECONDS:Double = 1.0/60.0
non-public let PEAK_HOLD_DURATION_SECONDS:Float = 1
var currentRecLevel:Float = 0 // from 0 to 1
var peakRecLevel:Float = 0 // from 0 to 1
var peakHoldTimeRemaining:Float = 0 // from 1 to 0 : % of peak maintain time remaining
static var shared = MemoRecorder()
non-public init() {
}
// caller should have already got permission
func startRecordingWithPermission() {
if (isRecording) {fatalError ("dsfsdfs 9345843534")}
isRecording = true
randomPrefixForTempFile = UUID().uuidString
let audioFilename = H.getDocumentsDirectory().appendingPathComponent("recording-(randomPrefixForTempFile).wav")
do {
audioRecorder = strive AVAudioRecorder(url: audioFilename, format:AT.vanillaFormat)
} catch {
fatalError("sdfsdsfsdfdgdsfg")
}
guard let audioRecorder = audioRecorder else {fatalError("sdfsdf")}
audioRecorder.isMeteringEnabled = true
audioRecorder.file()
currentRecLevel = 0
peakRecLevel = 0
peakHoldTimeRemaining = 0
startMeteringTimer()
}
// Referred to as when we have to cease recording however the caller doesn't need/want the buffer
func stopRecordingAndDiscard() {
meteringTimer?.invalidate()
audioRecorder?.cease()
if (isRecording) {
isRecording = false
}
}
func stopRecordingAndReturnBuffer() -> AVAudioPCMBuffer {
if (!isRecording) {fatalError("stopped recording when not recording")}
meteringTimer?.invalidate()
audioRecorder?.cease()
audioRecorder = nil
var end result:AVAudioPCMBuffer?
do {
end result = strive extractBufferFromFile()
} catch {
fatalError("sdalfh9898797")
}
isRecording = false
return end result!
}
// ------------------------------------------------------------------ INTERNAL
non-public func extractBufferFromFile() throws -> AVAudioPCMBuffer {
let recordedFile = H.getDocumentsDirectory().appendingPathComponent("recording-(randomPrefixForTempFile).wav")
guard FileManager.default.fileExists(atPath: recordedFile.path) else {
throw AudioExtractionError.fileNotFound
}
do {
let avAudioFile = strive AVAudioFile(forReading: recordedFile)
// Examine if buffer creation is feasible
guard let buffer = AVAudioPCMBuffer(pcmFormat: AT.vanillaFormat, frameCapacity: AVAudioFrameCount(avAudioFile.size)) else {
throw AudioExtractionError.bufferCreationFailed
}
// Try and learn the file into the buffer
do {
strive avAudioFile.learn(into: buffer)
} catch {
// Particular error for file learn failure
throw AudioExtractionError.fileReadError(error)
}
// Return the stuffed buffer
return buffer
} catch {
// Catch some other errors that weren't anticipated
throw AudioExtractionError.unknown(error)
}
}
// --------------- METERING TIMER
non-public func startMeteringTimer() {
meteringTimer?.invalidate() // Invalidate any current timer
meteringTimer = Timer.scheduledTimer(withTimeInterval: METERING_TIMER_RESOLUTION_SECONDS, repeats: true) { _ in
self.callThisFunctionWithTheTimer()
}
}
non-public func callThisFunctionWithTheTimer() {
guard let audioRecorder = audioRecorder else { return }
audioRecorder.updateMeters()
let averagePower = audioRecorder.averagePower(forChannel: 0)
// debug
if (averagePower > 0 || averagePower < -160) {
print("out of bounds: (averagePower)")
}
let newValue = convertDecibelsToLinear(audioRecorder.averagePower(forChannel: 0))
currentRecLevel = newValue
// debug
if (currentRecLevel > 1) {
print("overage: (currentRecLevel)")
}
if newValue > peakRecLevel {
peakRecLevel = newValue
peakHoldTimeRemaining = PEAK_HOLD_DURATION_SECONDS
} else if peakHoldTimeRemaining > 0 {
peakHoldTimeRemaining -= Float(METERING_TIMER_RESOLUTION_SECONDS)
} else {
peakRecLevel = 0 // Reset peak worth when maintain time has elapsed
}
}
non-public func convertDecibelsToLinear(_ decibels: Float) -> Float {
let clampedDecibels = max(decibels, -160.0) // Clamp the minimal decibel worth
return pow(10.0, clampedDecibels / 20.0) // Convert to linear scale
}
}
Why is that this taking place? Is that this a identified situation that solely results the simulator for some purpose? Can I assume it wont occur on any actual system?