augmented_audio_metrics/audio_processor_metrics/
audio_thread.rs

1// Augmented Audio: Audio libraries and applications
2// Copyright (c) 2022 Pedro Tacla Yamada
3//
4// The MIT License (MIT)
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22// THE SOFTWARE.
23use std::sync::atomic::{AtomicU64, AtomicUsize};
24use std::time::{Duration, Instant};
25
26use audio_garbage_collector::{make_shared, Shared};
27use audio_processor_traits::AudioProcessorSettings;
28use augmented_atomics::{AtomicF32, AtomicValue};
29
30#[derive(Default)]
31pub struct AudioProcessorMetricsHandle {
32    /// Current processing time as a factor of the available time for the buffer size / sample rate
33    /// configured
34    duration_micros: AtomicU64,
35    sample_rate: AtomicF32,
36    buffer_size: AtomicUsize,
37}
38
39impl AudioProcessorMetricsHandle {
40    pub fn duration(&self) -> Duration {
41        let duration_micros = self.duration_micros.get();
42        Duration::from_micros(duration_micros)
43    }
44
45    pub fn cpu_percent(&self) -> f32 {
46        let time_per_sample = 1.0 / self.sample_rate.get();
47        let time_per_block = time_per_sample * self.buffer_size.get() as f32;
48        let duration = self.duration();
49        duration.as_secs_f32() / time_per_block
50    }
51
52    pub fn prepare(&self, settings: AudioProcessorSettings) {
53        self.sample_rate.set(settings.sample_rate());
54        self.buffer_size.set(settings.block_size());
55    }
56
57    pub fn set_duration(&self, duration: Duration) {
58        self.duration_micros.set(duration.as_micros() as u64);
59    }
60}
61
62pub struct AudioProcessorMetrics {
63    last_start_time: Instant,
64    handle: Shared<AudioProcessorMetricsHandle>,
65}
66
67impl Default for AudioProcessorMetrics {
68    fn default() -> Self {
69        Self {
70            last_start_time: Instant::now(),
71            handle: make_shared(Default::default()),
72        }
73    }
74}
75
76impl AudioProcessorMetrics {
77    pub fn from_handle(handle: Shared<AudioProcessorMetricsHandle>) -> Self {
78        Self {
79            last_start_time: Instant::now(),
80            handle,
81        }
82    }
83
84    pub fn handle(&self) -> Shared<AudioProcessorMetricsHandle> {
85        self.handle.clone()
86    }
87
88    pub fn prepare(&self, settings: AudioProcessorSettings) {
89        self.handle.prepare(settings);
90    }
91
92    pub fn on_process_start(&mut self) {
93        self.last_start_time = Instant::now();
94    }
95
96    pub fn on_process_end(&self) {
97        let duration = self.last_start_time.elapsed();
98        self.handle.set_duration(duration)
99    }
100}
101
102#[cfg(test)]
103mod test {
104    use std::ops::{Add, Sub};
105    use std::time::Duration;
106
107    use augmented_atomics::AtomicValue;
108
109    use super::*;
110
111    #[test]
112    fn test_handle_duration() {
113        let handle = AudioProcessorMetricsHandle::default();
114        handle
115            .duration_micros
116            .set(Duration::from_secs(10).as_micros() as u64);
117        assert_eq!(handle.duration().as_secs(), 10);
118    }
119
120    #[test]
121    fn test_handle_cpu_percent() {
122        let handle = AudioProcessorMetricsHandle::default();
123        handle.sample_rate.set(1000.0);
124        // 100ms per block is possible
125        handle.buffer_size.set(100);
126        // 50ms per block is taken
127        handle
128            .duration_micros
129            .set(Duration::from_millis(50).as_micros() as u64);
130        // 50% CPU usage is reported
131        assert!((handle.cpu_percent() - 0.5).abs() < f32::EPSILON);
132    }
133
134    #[test]
135    fn test_handle_prepare() {
136        let mut settings = AudioProcessorSettings::default();
137        settings.sample_rate = 100.0;
138        settings.block_size = 10;
139        let handle = AudioProcessorMetricsHandle::default();
140        handle.prepare(settings);
141        assert!((handle.sample_rate.get() - 100.0).abs() < f32::EPSILON);
142        assert_eq!(handle.buffer_size.get(), 10);
143    }
144
145    #[test]
146    fn test_processor_prepare() {
147        let processor = AudioProcessorMetrics::default();
148        let mut settings = AudioProcessorSettings::default();
149        settings.sample_rate = 100.0;
150        settings.block_size = 10;
151
152        let handle = processor.handle();
153        processor.prepare(settings);
154        assert!((handle.sample_rate.get() - 100.0).abs() < f32::EPSILON);
155        assert_eq!(handle.buffer_size.get(), 10);
156    }
157
158    #[test]
159    fn test_processor_start() {
160        let mut processor = AudioProcessorMetrics::default();
161        processor.last_start_time = Instant::now().add(Duration::from_secs(1000));
162        let prev_start = processor.last_start_time;
163        processor.on_process_start();
164        assert_ne!(processor.last_start_time, prev_start);
165    }
166
167    #[test]
168    fn test_processor_end_will_set_duration_on_handle() {
169        let mut processor = AudioProcessorMetrics::default();
170        processor.last_start_time = Instant::now().sub(Duration::from_micros(1000));
171        processor.handle().duration_micros.set(0);
172        processor.on_process_end();
173        assert_ne!(processor.handle().duration_micros.get(), 0);
174    }
175}