98 lines
3.3 KiB
Swift
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,
|
|
])
|
|
}
|
|
}
|
|
}
|