/* * Copyright (c) Hisilicon Technologies Co., Ltd. 2020-2020. All rights reserved. * Description: audio hal primary output source file. * Author: Hisilicon * Create: 2020-05-11 * Notes: NA * History: 2020-05-11 yinyingcai for CodingStyle */ #define LOG_TAG "audio_hal_hpo" #define LOG_NDEBUG 0 #include "hpo.h" #include #include #include #include #include "vnaudio_dbg.h" #include "audio_custom.h" #ifdef TV_TYPE #include "aiao_wrap.h" #endif #include "audio_utils.h" #define MAX_ALSA_NAME_LEN 1024 #define ALSA_PROC_FILE "/proc/asound/cards" #define AIAO_NAME "AUDIOAIAO" #define ALSA_CARD_NUM_OFFSET 3 #define PRINT_CYCLE_US 200000 static void find_alsa_card(struct hpo_data *hpo) { int ret; FILE* fp = NULL; char str[MAX_ALSA_NAME_LEN] = {0}; char *ptr = NULL; hpo->out_card = AO_CARD_ID; fp = fopen(ALSA_PROC_FILE, "r"); if (fp == NULL) { ALOGE("open /proc/asound/cards fail"); return; } while (fgets(str, MAX_ALSA_NAME_LEN, fp) != NULL) { ptr = strstr(str, AIAO_NAME); if (ptr != NULL) { if ((ptr - str) >= ALSA_CARD_NUM_OFFSET) { int card_num = (int)(*(ptr - ALSA_CARD_NUM_OFFSET)) - (int)'0'; hpo->out_card = (card_num < 0) ? AO_CARD_ID : card_num; } ALOGD("%s:device(hw:%d:%d)", __func__, hpo->out_card, hpo->out_device); break; } } ret = fclose(fp); if (ret == EOF) { ALOGE("close /proc/asound/cards fail"); } } size_t hpo_get_buffer_size(const struct hpo_data* hpo) { size_t buffersize; if (hpo == NULL) { ALOGE("hpo is null"); return 0; } buffersize = hpo->out->config.period_size * hpo->out->config.channels * audio_bytes_per_sample(hpo->out->format); ALOGD("%s:buffersize=%d \n", __func__, buffersize); return buffersize; } int hpo_create(struct hpo_data **pphpo) { int ret; struct hpo_data* hpo = (struct hpo_data *)malloc(sizeof(struct hpo_data)); if (hpo == NULL) { ALOGE("%s: calloc size(%d) failed", __func__, sizeof(struct hpo_data)); return -ENOMEM; } ret = memset_s(hpo, sizeof(struct hpo_data), 0, sizeof(struct hpo_data)); if (ret != 0) { ALOGE("memset_s hpo failed(0x%x)", ret); free(hpo); return ret; } find_alsa_card(hpo); *pphpo = hpo; return 0; } void hpo_destroy(struct hpo_data* hpo) { ALOGD("in function %s", __func__); if (hpo->fd != NULL) { fclose(hpo->fd); hpo->fd = NULL; } if (hpo->pcm != NULL) { pcm_close(hpo->pcm); hpo->pcm = NULL; } free(hpo); } int hpo_open(struct hpo_data *hpo) { char value[PROPERTY_VALUE_MAX]; hpo->open_time = utils_get_cur_time(); unsigned int flags = PCM_OUT | PCM_MONOTONIC; // player need CLOCK_MONOTONIC so flag with PCM_MONOTONIC ALOGD("%s:device(hw:%d:%d) flags(0x%x) \n", __func__, hpo->out_card, hpo->out_device, flags); hpo->pcm = pcm_open((unsigned int)hpo->out_card, (unsigned int)hpo->out_device, flags, &hpo->out->config); if ((hpo->pcm == NULL) || (pcm_is_ready(hpo->pcm) == 0)) { ALOGE("pcm_open() failed: %s", pcm_get_error(hpo->pcm)); if (hpo->pcm != NULL) { pcm_close(hpo->pcm); hpo->pcm = NULL; } return -ENOMEM; } hpo->underrun = false; hpo->max_underrun_update = false; hpo->first_frame_time = 0; hpo->last_print_time = 0; hpo->frames_written_standby = 0; hpo->max_underrun_frames = 0; hpo->max_overrun_frames = 0; hpo->alsa_delay = 0; property_get(PROPERTY_FILE_PCM, value, "false"); ALOGD("%s: %s is %s", __func__, PROPERTY_FILE_PCM, value); if (strncmp(value, "true", sizeof("true")) == 0) { if (hpo->fd == NULL) { hpo->fd = fopen(PCM_FILE, "ab"); if (hpo->fd == NULL) { ALOGD("%s: %s can not be created!", __func__, PCM_FILE); } } } else { if (hpo->fd != NULL) { fclose(hpo->fd); hpo->fd = NULL; } } return 0; } int hpo_close(struct hpo_data *hpo) { ALOGD("%s:device(hw:%d:%d)\n", __func__, hpo->out_card, hpo->out_device); if (hpo->pcm != NULL) { pcm_close(hpo->pcm); hpo->pcm = NULL; } if (hpo->fd != NULL) { fclose(hpo->fd); hpo->fd = NULL; } return 0; } uint32_t hpo_get_latency(const struct hpo_data *hpo) { uint32_t latency; if (hpo == NULL) { ALOGE("hpo is null"); return 0; } latency = (hpo->out->config.period_count * hpo->out->config.period_size * SECOND_TO_MS) / hpo->out->config.rate; ALOGD("%s:latency=%d", __func__, latency); return latency; } static bool need_print_frames(const struct hpo_data *hpo, uint64_t cur_time, bool underrun_now) { bool check_run = hpo_check_equal(XRUNCHECK_ENABLE, DEBUG_TRUE); bool check_delay = hpo_check_equal(DELAYCHECK_ENABLE, DEBUG_TRUE); if (check_delay || check_run || (hpo->last_print_time == 0)) { return true; } /* ensure that the max value of max_underrun_frames will be log out */ if ((underrun_now != hpo->underrun) && (hpo->max_underrun_update == true)) { return true; } if ((underrun_now || (underrun_now != hpo->underrun)) && ((cur_time - hpo->last_print_time) >= PRINT_CYCLE_US)) { return true; } return false; } static void check_print_xrun(struct hpo_data *hpo) { uint64_t cur_time = utils_get_cur_time(); uint64_t elapsed_time = cur_time - hpo->first_frame_time; uint64_t expect_frames = (elapsed_time / TIME_RADIX_1000) * (hpo->out->config.rate / TIME_RADIX_1000); uint64_t underrun_frames = 0; uint64_t overrun_frames = 0; bool underrun = false; if (hpo->first_frame_time == 0) { trace_call("%s device hw:%d:%d 0 frames written", __func__, hpo->out_card, hpo->out_device); return; } /* take frames_discarded in count here for there should be a log when frames are discarded */ if ((expect_frames + hpo->frames_discarded) > hpo->frames_written_standby) { underrun_frames = expect_frames + hpo->frames_discarded - hpo->frames_written_standby; if (underrun_frames > hpo->max_underrun_frames) { hpo->max_underrun_frames = underrun_frames; hpo->max_underrun_update = true; } underrun = ((underrun_frames > 0) ? true : false); } else { overrun_frames = hpo->frames_written_standby - expect_frames - hpo->frames_discarded; if (overrun_frames > hpo->max_overrun_frames) { hpo->max_overrun_frames = overrun_frames; } } if (need_print_frames(hpo, cur_time, underrun)) { hpo->last_print_time = cur_time; hpo->max_underrun_update = false; ALOGD("%s device hw:%d:%d %llu frames written, %llu expected, " "%llu underrun(max %llu), %llu overrun(max %llu), %llu discarded, alsa delay %d us", __func__, hpo->out_card, hpo->out_device, hpo->frames_written_standby, expect_frames, underrun_frames, hpo->max_underrun_frames, overrun_frames, hpo->max_overrun_frames, hpo->frames_discarded, hpo->alsa_delay); } hpo->underrun = underrun; } static void handle_written_statistics(struct hpo_data *hpo, size_t request_bytes, int written_ret) { if (hpo->alsa_delay == 0) { hpo->alsa_delay = (int)(utils_get_cur_time() - hpo->open_time); } if (hpo->out->config.channels != 0) { size_t frames = request_bytes / (hpo->out->config.channels * sizeof(short)); hpo->frames_written += frames; hpo->frames_written_standby += frames; if (written_ret != 0) { hpo->frames_discarded += frames; } else if ((hpo->first_frame_time == 0) && (request_bytes > 0)) { hpo->first_frame_time = utils_get_cur_time(); } check_print_xrun(hpo); } } static void check_record_to_file(const struct hpo_data *hpo, uint8_t *buffer, size_t bytes) { if ((hpo->fd != NULL) && (bytes > 0)) { size_t count; count = fwrite((const void *)buffer, 1, bytes, hpo->fd); if (count < bytes) { ALOGE("%s: not save all data! expect=%d actual=%d", __func__, bytes, count); } } } static inline bool need_print_first_frame(const struct hpo_data *hpo) { bool check_run = hpo_check_equal(XRUNCHECK_ENABLE, DEBUG_TRUE); bool check_delay = hpo_check_equal(DELAYCHECK_ENABLE, DEBUG_TRUE); if ((hpo->out->usecase != USECASE_AUDIO_PLAYBACK_LOW_LATENCY) || (hpo->first_frame_time != 0) || (!(check_run || check_delay))) { return false; } return true; } ssize_t hpo_write(struct hpo_data *hpo, const uint8_t* buffer, size_t bytes) { int ret; if (hpo == NULL) { ALOGE("hpo is null"); return -EINVAL; } if (hpo->pcm == NULL) { ALOGE("%s: pcm null pointer", __func__); return (ssize_t)bytes; } if (need_print_first_frame(hpo)) { ALOGD("%s: start to write first frame(%zu bytes) to low latency device(hw:%d:%d)", __func__, bytes, hpo->out_card, hpo->out_device); } /* the data written must be counted, no matter whether data wrote failed */ ret = pcm_write(hpo->pcm, (void *)buffer, bytes); handle_written_statistics(hpo, bytes, ret); if (ret == 0) { check_record_to_file(hpo, (uint8_t *)buffer, bytes); } else { ALOGE("%s:%d byte written failed", __func__, bytes); } return 0; } int hpo_get_tstamp(const struct hpo_data *hpo, uint64_t *frames) { int ret = -EPERM; /* if return value is not 0, Audioflinger will not update LOCATION_KERNEL timestamp */ if (hpo == NULL || frames == NULL) { return ret; } if (hpo->pcm == NULL) { return ret; } unsigned int avail; struct timespec timestamp; if (pcm_get_htimestamp(hpo->pcm, &avail, ×tamp) != 0) { return ret; } // it should be more acculate size_t buffer_size = hpo->out->config.period_size * hpo->out->config.period_count; // This adjustment accounts for buffering after data send. consider snd port delay. if (avail > buffer_size) { ALOGE("available frames(%u) > buffer size(%zu)", avail, buffer_size); } else { if (hpo->frames_written >= (buffer_size - avail)) { *frames = hpo->frames_written - (buffer_size - avail); ret = 0; } } return ret; } bool hpo_check_equal(int real_value, int expect_value) { return (real_value - expect_value == 0 ? true : false); }