twonly-app-dependencies/audio_waveforms/ios/Classes/RecorderBytesStreamEngine.swift
2026-08-22 20:30:14 +02:00

98 lines
3.3 KiB
Swift

//
// RecorderBytesStreamHandler.swift
// audio_waveforms
//
// Created by Ujas Majithiya on 10/04/25.
//
import Foundation
import AVFAudio
import Accelerate
class RecorderBytesStreamEngine {
private var audioEngine = AVAudioEngine()
private var flutterChannel: FlutterMethodChannel
private var paused: Bool = false
private var totalFrames: AVAudioFramePosition = 0
init(channel: FlutterMethodChannel) {
flutterChannel = channel
}
func attach(result: @escaping FlutterResult, sampleRate: Int) {
let inputNode = audioEngine.inputNode
inputNode.installTap(onBus: 0, bufferSize: 1024, format: nil) { (buffer, time) in
if self.paused {
return
}
if let (convertedBytes, normalizedRms) = self.convertToFlutterType(buffer) {
self.totalFrames += AVAudioFramePosition(buffer.frameLength)
let effectiveSampleRate = buffer.format.sampleRate > 0 ? buffer.format.sampleRate : Double(sampleRate)
let duration = Double(self.totalFrames) / effectiveSampleRate
let milliseconds = Int(duration * 1000)
self.sendToFlutter(
convertedBytes,
normalizedRms: normalizedRms,
milliSeconds: milliseconds
)
}
}
do {
try audioEngine.start()
} catch {
result(FlutterError(code: Constants.audioWaveforms, message: "Error starting Audio Engine", details: error.localizedDescription))
}
}
func togglePause() {
paused = !paused
}
func detach() {
totalFrames = 0
audioEngine.inputNode.removeTap(onBus: 0)
audioEngine.stop()
}
private func convertToFlutterType(_ buffer: AVAudioPCMBuffer) -> (FlutterStandardTypedData, Double)? {
guard let channelData = buffer.floatChannelData?[0] else { return nil }
let frameLength = Int(buffer.frameLength)
// Convert Float32 buffer to UInt8 (byte array)
var audioSamples = [Float32](repeating: 0.0, count: frameLength)
var sumOfSquares: Float = 0.0
for i in 0..<frameLength {
audioSamples[i] = channelData[i]
sumOfSquares += channelData[i] * channelData[i]
}
// Calculate RMS
var rms: Float = 0.0
vDSP_rmsqv(
audioSamples, 1, &rms,
vDSP_Length(frameLength)
)
// Normalize RMS to 0-1 range (assuming max amplitude is 1.0 for Float32)
let normalizedRms = Double(min(rms, 1.0))
let byteBuffer = audioSamples.withUnsafeBufferPointer { bufferPointer in
return Data(buffer: bufferPointer)
}
let convertedBuffer = FlutterStandardTypedData(bytes: byteBuffer)
return (convertedBuffer, normalizedRms)
}
private func sendToFlutter(_ buffer: FlutterStandardTypedData, normalizedRms: Double, milliSeconds: Int) {
DispatchQueue.main.async { [weak self] in
guard let self else { return }
flutterChannel.invokeMethod(Constants.onAudioChunk, arguments: [
Constants.bytes: buffer,
Constants.normalisedRms: normalizedRms,
Constants.recordedDuration: milliSeconds,
])
}
}
}