oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1import AVFoundation
2import Foundation
3import LiveKitWebRTC
4import os
5
6/// The level of one person's decoded audio, for the meter on their tile.
7///
8/// A renderer on their remote audio track: WebRTC hands it every 10 ms
9/// frame the moment it is decoded for playout, before the output volume
10/// is applied (the track's sink sits ahead of the gain in the receive
11/// channel). So it measures what arrived, whatever the app then does with
12/// the volume — including the interop harness, which plays at zero.
13///
14/// Chosen over inbound-rtp `audioLevel`, which is measured *after* that
15/// gain and would read silence at volume zero, and which would take a
16/// statistics walk per peer five times a second. The statistics are still
17/// read, once a second, for the other half of "No sound from": whether
18/// packets are arriving at all (MediaEngine.peerAudio).
19///
20/// Called on WebRTC's audio thread: nothing here blocks or waits.
21final class RemoteAudioTap: NSObject, RTCAudioRenderer, @unchecked Sendable {
22 private struct Reading {
23 var peak: Float = 0
24 var buffers = 0
25 }
26 private let lock = OSAllocatedUnfairLock(initialState: Reading())
27
28 func render(pcmBuffer: AVAudioPCMBuffer) {
29 let frames = Int(pcmBuffer.frameLength)
30 guard frames > 0 else { return }
31 var top: Float = 0
32 if let int16 = pcmBuffer.int16ChannelData {
33 // The adapter's buffers are interleaved int16: one pointer, every
34 // channel's samples in it.
35 let channels = pcmBuffer.format.isInterleaved ? Int(pcmBuffer.format.channelCount) : 1
36 let samples = int16[0]
37 var peak: Int32 = 0
38 for i in 0..<(frames * channels) { peak = max(peak, abs(Int32(samples[i]))) }
39 top = Float(peak) / 32768
40 } else if let float = pcmBuffer.floatChannelData {
41 let samples = float[0]
42 for i in 0..<frames { top = max(top, abs(samples[i])) }
43 } else {
44 return
45 }
46 let level = min(top, 1)
47 lock.withLock { r in
48 r.peak = max(r.peak, level)
49 r.buffers += 1
50 }
51 }
52
53 /// The loudest sample since the last call, and how many frames came.
54 func take() -> (peak: Float, buffers: Int) {
55 lock.withLock { r in
56 let out = (r.peak, r.buffers)
57 r = Reading()
58 return out
59 }
60 }
61}
62