1 /*
2 ** Copyright 2022, The Android Open-Source Project
3 **
4 ** Licensed under the Apache License, Version 2.0 (the "License");
5 ** you may not use this file except in compliance with the License.
6 ** You may obtain a copy of the License at
7 **
8 **     http://www.apache.org/licenses/LICENSE-2.0
9 **
10 ** Unless required by applicable law or agreed to in writing, software
11 ** distributed under the License is distributed on an "AS IS" BASIS,
12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 ** See the License for the specific language governing permissions and
14 ** limitations under the License.
15 */
16 
17 #define LOG_TAG "hal_smoothness"
18 
19 #include <audio_utils/hal_smoothness.h>
20 #include <errno.h>
21 #include <float.h>
22 #include <log/log.h>
23 #include <math.h>
24 #include <stdlib.h>
25 
26 typedef struct hal_smoothness_internal {
27   struct hal_smoothness itfe;
28 
29   struct hal_smoothness_metrics metrics;
30 
31   // number of “total_writes” before flushing smoothness data to system (ie.
32   // logcat) A flush will also reset all numeric values in the "metrics" field.
33   unsigned int num_writes_to_log;
34 
35   // Client defined function to flush smoothness metrics.
36   void (*client_flush_cb)(struct hal_smoothness_metrics *smoothness_metrics,
37                           void *private_data);
38 
39   // Client provided pointer.
40   void *private_data;
41 } hal_smoothness_internal;
42 
reset_metrics(struct hal_smoothness_metrics * metrics)43 static void reset_metrics(struct hal_smoothness_metrics *metrics) {
44   metrics->underrun_count = 0;
45   metrics->overrun_count = 0;
46   metrics->total_writes = 0;
47   metrics->total_frames_written = 0;
48   metrics->total_frames_lost = 0;
49   metrics->timestamp = 0;
50   metrics->smoothness_value = 0.0;
51 }
52 
add_check_overflow(unsigned int * data,unsigned int add_amount)53 static bool add_check_overflow(unsigned int *data, unsigned int add_amount) {
54   return __builtin_add_overflow(*data, add_amount, data);
55 }
56 
increment_underrun(struct hal_smoothness * smoothness,unsigned int frames_lost)57 static int increment_underrun(struct hal_smoothness *smoothness,
58                               unsigned int frames_lost) {
59   if (smoothness == NULL) {
60     return -EINVAL;
61   }
62 
63   hal_smoothness_internal *smoothness_meta =
64       (hal_smoothness_internal *)smoothness;
65 
66   if (add_check_overflow(&smoothness_meta->metrics.underrun_count, 1)) {
67     return -EOVERFLOW;
68   }
69 
70   if (add_check_overflow(&smoothness_meta->metrics.total_frames_lost,
71                          frames_lost)) {
72     return -EOVERFLOW;
73   }
74 
75   return 0;
76 }
77 
increment_overrun(struct hal_smoothness * smoothness,unsigned int frames_lost)78 static int increment_overrun(struct hal_smoothness *smoothness,
79                              unsigned int frames_lost) {
80   if (smoothness == NULL) {
81     return -EINVAL;
82   }
83 
84   hal_smoothness_internal *smoothness_meta =
85       (hal_smoothness_internal *)smoothness;
86 
87   if (add_check_overflow(&smoothness_meta->metrics.overrun_count, 1)) {
88     return -EOVERFLOW;
89   }
90 
91   if (add_check_overflow(&smoothness_meta->metrics.total_frames_lost,
92                          frames_lost)) {
93     return -EOVERFLOW;
94   }
95 
96   return 0;
97 }
98 
calc_smoothness_value(unsigned int total_frames_lost,unsigned int total_frames_written)99 static double calc_smoothness_value(unsigned int total_frames_lost,
100                                     unsigned int total_frames_written) {
101   // If error checks are correct in this library, this error shouldn't be
102   // possible.
103   if (total_frames_lost == 0 && total_frames_written == 0) {
104     ALOGE("total_frames_lost + total_frames_written shouldn't = 0");
105     return -EINVAL;
106   }
107 
108   // No bytes dropped, so audio smoothness is perfect.
109   if (total_frames_lost == 0) {
110     return DBL_MAX;
111   }
112 
113   unsigned int total_frames = total_frames_lost;
114 
115   if (add_check_overflow(&total_frames, total_frames_written)) {
116     return -EOVERFLOW;
117   }
118 
119   // Division by 0 shouldn't be possible.
120   double lost_frames_ratio = (double)total_frames_lost / total_frames;
121 
122   // log(0) shouldn't be possible.
123   return -log(lost_frames_ratio);
124 }
125 
flush(struct hal_smoothness * smoothness)126 static int flush(struct hal_smoothness *smoothness) {
127   if (smoothness == NULL) {
128     return -EINVAL;
129   }
130 
131   hal_smoothness_internal *smoothness_meta =
132       (hal_smoothness_internal *)smoothness;
133 
134   smoothness_meta->metrics.smoothness_value =
135       calc_smoothness_value(smoothness_meta->metrics.total_frames_lost,
136                             smoothness_meta->metrics.total_frames_written);
137   smoothness_meta->client_flush_cb(&smoothness_meta->metrics,
138                                    smoothness_meta->private_data);
139   reset_metrics(&smoothness_meta->metrics);
140 
141   return 0;
142 }
143 
increment_total_writes(struct hal_smoothness * smoothness,unsigned int frames_written,unsigned long timestamp)144 static int increment_total_writes(struct hal_smoothness *smoothness,
145                                   unsigned int frames_written,
146                                   unsigned long timestamp) {
147   if (smoothness == NULL) {
148     return -EINVAL;
149   }
150 
151   hal_smoothness_internal *smoothness_meta =
152       (hal_smoothness_internal *)smoothness;
153 
154   if (add_check_overflow(&smoothness_meta->metrics.total_writes, 1)) {
155     return -EOVERFLOW;
156   }
157 
158   if (add_check_overflow(&smoothness_meta->metrics.total_frames_written,
159                          frames_written)) {
160     return -EOVERFLOW;
161   }
162   smoothness_meta->metrics.timestamp = timestamp;
163 
164   // "total_writes" count has met a value where the client's callback function
165   // should be called
166   if (smoothness_meta->metrics.total_writes >=
167       smoothness_meta->num_writes_to_log) {
168     flush(smoothness);
169   }
170 
171   return 0;
172 }
173 
hal_smoothness_initialize(struct hal_smoothness ** smoothness,unsigned int version,unsigned int num_writes_to_log,void (* client_flush_cb)(struct hal_smoothness_metrics *,void *),void * private_data)174 int hal_smoothness_initialize(
175     struct hal_smoothness **smoothness, unsigned int version,
176     unsigned int num_writes_to_log,
177     void (*client_flush_cb)(struct hal_smoothness_metrics *, void *),
178     void *private_data) {
179   if (num_writes_to_log == 0) {
180     ALOGE("num_writes_to_logs must be > 0");
181 
182     return -EINVAL;
183   }
184 
185   if (client_flush_cb == NULL) {
186     ALOGE("client_flush_cb can't be NULL");
187 
188     return -EINVAL;
189   }
190 
191   hal_smoothness_internal *smoothness_meta;
192   smoothness_meta =
193       (hal_smoothness_internal *)calloc(1, sizeof(hal_smoothness_internal));
194 
195   if (smoothness_meta == NULL) {
196     int ret_err = errno;
197     ALOGE("failed to calloc hal_smoothness_internal.");
198     return ret_err;
199   }
200 
201   smoothness_meta->itfe.version = version;
202   smoothness_meta->itfe.increment_underrun = increment_underrun;
203   smoothness_meta->itfe.increment_overrun = increment_overrun;
204   smoothness_meta->itfe.increment_total_writes = increment_total_writes;
205   smoothness_meta->itfe.flush = flush;
206 
207   smoothness_meta->num_writes_to_log = num_writes_to_log;
208   smoothness_meta->client_flush_cb = client_flush_cb;
209   smoothness_meta->private_data = private_data;
210 
211   *smoothness = &smoothness_meta->itfe;
212 
213   return 0;
214 }
215 
hal_smoothness_free(struct hal_smoothness ** smoothness)216 void hal_smoothness_free(struct hal_smoothness **smoothness) {
217   if (smoothness == NULL || *smoothness == NULL) {
218     return;
219   }
220 
221   free(*smoothness);
222   *smoothness = NULL;
223 }
224