You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

359 lines
10 KiB

/*
* 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 <time.h>
#include <log/log.h>
#include <cutils/properties.h>
#include <securec.h>
#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, &timestamp) != 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);
}