| /* |
| ** Copyright 2022, The Android Open-Source Project |
| ** |
| ** Licensed under the Apache License, Version 2.0 (the "License"); |
| ** you may not use this file except in compliance with the License. |
| ** You may obtain a copy of the License at |
| ** |
| ** http://www.apache.org/licenses/LICENSE-2.0 |
| ** |
| ** Unless required by applicable law or agreed to in writing, software |
| ** distributed under the License is distributed on an "AS IS" BASIS, |
| ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| ** See the License for the specific language governing permissions and |
| ** limitations under the License. |
| */ |
| |
| #define LOG_TAG "hal_smoothness" |
| |
| #include <audio_utils/hal_smoothness.h> |
| #include <errno.h> |
| #include <float.h> |
| #include <log/log.h> |
| #include <math.h> |
| #include <stdlib.h> |
| |
| typedef struct hal_smoothness_internal { |
| struct hal_smoothness itfe; |
| |
| struct hal_smoothness_metrics metrics; |
| |
| // number of “total_writes” before flushing smoothness data to system (ie. |
| // logcat) A flush will also reset all numeric values in the "metrics" field. |
| unsigned int num_writes_to_log; |
| |
| // Client defined function to flush smoothness metrics. |
| void (*client_flush_cb)(struct hal_smoothness_metrics *smoothness_metrics, |
| void *private_data); |
| |
| // Client provided pointer. |
| void *private_data; |
| } hal_smoothness_internal; |
| |
| static void reset_metrics(struct hal_smoothness_metrics *metrics) { |
| metrics->underrun_count = 0; |
| metrics->overrun_count = 0; |
| metrics->total_writes = 0; |
| metrics->total_frames_written = 0; |
| metrics->total_frames_lost = 0; |
| metrics->timestamp = 0; |
| metrics->smoothness_value = 0.0; |
| } |
| |
| static bool add_check_overflow(unsigned int *data, unsigned int add_amount) { |
| return __builtin_add_overflow(*data, add_amount, data); |
| } |
| |
| static int increment_underrun(struct hal_smoothness *smoothness, |
| unsigned int frames_lost) { |
| if (smoothness == NULL) { |
| return -EINVAL; |
| } |
| |
| hal_smoothness_internal *smoothness_meta = |
| (hal_smoothness_internal *)smoothness; |
| |
| if (add_check_overflow(&smoothness_meta->metrics.underrun_count, 1)) { |
| return -EOVERFLOW; |
| } |
| |
| if (add_check_overflow(&smoothness_meta->metrics.total_frames_lost, |
| frames_lost)) { |
| return -EOVERFLOW; |
| } |
| |
| return 0; |
| } |
| |
| static int increment_overrun(struct hal_smoothness *smoothness, |
| unsigned int frames_lost) { |
| if (smoothness == NULL) { |
| return -EINVAL; |
| } |
| |
| hal_smoothness_internal *smoothness_meta = |
| (hal_smoothness_internal *)smoothness; |
| |
| if (add_check_overflow(&smoothness_meta->metrics.overrun_count, 1)) { |
| return -EOVERFLOW; |
| } |
| |
| if (add_check_overflow(&smoothness_meta->metrics.total_frames_lost, |
| frames_lost)) { |
| return -EOVERFLOW; |
| } |
| |
| return 0; |
| } |
| |
| static double calc_smoothness_value(unsigned int total_frames_lost, |
| unsigned int total_frames_written) { |
| // If error checks are correct in this library, this error shouldn't be |
| // possible. |
| if (total_frames_lost == 0 && total_frames_written == 0) { |
| ALOGE("total_frames_lost + total_frames_written shouldn't = 0"); |
| return -EINVAL; |
| } |
| |
| // No bytes dropped, so audio smoothness is perfect. |
| if (total_frames_lost == 0) { |
| return DBL_MAX; |
| } |
| |
| unsigned int total_frames = total_frames_lost; |
| |
| if (add_check_overflow(&total_frames, total_frames_written)) { |
| return -EOVERFLOW; |
| } |
| |
| // Division by 0 shouldn't be possible. |
| double lost_frames_ratio = (double)total_frames_lost / total_frames; |
| |
| // log(0) shouldn't be possible. |
| return -log(lost_frames_ratio); |
| } |
| |
| static int flush(struct hal_smoothness *smoothness) { |
| if (smoothness == NULL) { |
| return -EINVAL; |
| } |
| |
| hal_smoothness_internal *smoothness_meta = |
| (hal_smoothness_internal *)smoothness; |
| |
| smoothness_meta->metrics.smoothness_value = |
| calc_smoothness_value(smoothness_meta->metrics.total_frames_lost, |
| smoothness_meta->metrics.total_frames_written); |
| smoothness_meta->client_flush_cb(&smoothness_meta->metrics, |
| smoothness_meta->private_data); |
| reset_metrics(&smoothness_meta->metrics); |
| |
| return 0; |
| } |
| |
| static int increment_total_writes(struct hal_smoothness *smoothness, |
| unsigned int frames_written, |
| unsigned long timestamp) { |
| if (smoothness == NULL) { |
| return -EINVAL; |
| } |
| |
| hal_smoothness_internal *smoothness_meta = |
| (hal_smoothness_internal *)smoothness; |
| |
| if (add_check_overflow(&smoothness_meta->metrics.total_writes, 1)) { |
| return -EOVERFLOW; |
| } |
| |
| if (add_check_overflow(&smoothness_meta->metrics.total_frames_written, |
| frames_written)) { |
| return -EOVERFLOW; |
| } |
| smoothness_meta->metrics.timestamp = timestamp; |
| |
| // "total_writes" count has met a value where the client's callback function |
| // should be called |
| if (smoothness_meta->metrics.total_writes >= |
| smoothness_meta->num_writes_to_log) { |
| flush(smoothness); |
| } |
| |
| return 0; |
| } |
| |
| int 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) { |
| if (num_writes_to_log == 0) { |
| ALOGE("num_writes_to_logs must be > 0"); |
| |
| return -EINVAL; |
| } |
| |
| if (client_flush_cb == NULL) { |
| ALOGE("client_flush_cb can't be NULL"); |
| |
| return -EINVAL; |
| } |
| |
| hal_smoothness_internal *smoothness_meta; |
| smoothness_meta = |
| (hal_smoothness_internal *)calloc(1, sizeof(hal_smoothness_internal)); |
| |
| if (smoothness_meta == NULL) { |
| int ret_err = errno; |
| ALOGE("failed to calloc hal_smoothness_internal."); |
| return ret_err; |
| } |
| |
| smoothness_meta->itfe.version = version; |
| smoothness_meta->itfe.increment_underrun = increment_underrun; |
| smoothness_meta->itfe.increment_overrun = increment_overrun; |
| smoothness_meta->itfe.increment_total_writes = increment_total_writes; |
| smoothness_meta->itfe.flush = flush; |
| |
| smoothness_meta->num_writes_to_log = num_writes_to_log; |
| smoothness_meta->client_flush_cb = client_flush_cb; |
| smoothness_meta->private_data = private_data; |
| |
| *smoothness = &smoothness_meta->itfe; |
| |
| return 0; |
| } |
| |
| void hal_smoothness_free(struct hal_smoothness **smoothness) { |
| if (smoothness == NULL || *smoothness == NULL) { |
| return; |
| } |
| |
| free(*smoothness); |
| *smoothness = NULL; |
| } |