import AVFoundation import Foundation import LiveKitWebRTC import os /// The level of one person's decoded audio, for the meter on their tile. /// /// A renderer on their remote audio track: WebRTC hands it every 10 ms /// frame the moment it is decoded for playout, before the output volume /// is applied (the track's sink sits ahead of the gain in the receive /// channel). So it measures what arrived, whatever the app then does with /// the volume — including the interop harness, which plays at zero. /// /// Chosen over inbound-rtp `audioLevel`, which is measured *after* that /// gain and would read silence at volume zero, and which would take a /// statistics walk per peer five times a second. The statistics are still /// read, once a second, for the other half of "No sound from": whether /// packets are arriving at all (MediaEngine.peerAudio). /// /// Called on WebRTC's audio thread: nothing here blocks or waits. final class RemoteAudioTap: NSObject, RTCAudioRenderer, @unchecked Sendable { private struct Reading { var peak: Float = 0 var buffers = 0 } private let lock = OSAllocatedUnfairLock(initialState: Reading()) func render(pcmBuffer: AVAudioPCMBuffer) { let frames = Int(pcmBuffer.frameLength) guard frames > 0 else { return } var top: Float = 0 if let int16 = pcmBuffer.int16ChannelData { // The adapter's buffers are interleaved int16: one pointer, every // channel's samples in it. let channels = pcmBuffer.format.isInterleaved ? Int(pcmBuffer.format.channelCount) : 1 let samples = int16[0] var peak: Int32 = 0 for i in 0..<(frames * channels) { peak = max(peak, abs(Int32(samples[i]))) } top = Float(peak) / 32768 } else if let float = pcmBuffer.floatChannelData { let samples = float[0] for i in 0.. (peak: Float, buffers: Int) { lock.withLock { r in let out = (r.peak, r.buffers) r = Reading() return out } } }