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
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, ×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);
|
|
}
|