/* * PCM - AMLOGIC Audio HAL plugin * * Copyright (c) 2022 by Wei Du * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define LOG_TAG "audio_ahal_pcm" #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "audio_if.h" #include #include #include #include #include "pcm.h" //#define VDEBUG #ifdef VDEBUG #define VDBG(f, ...) \ fprintf(stderr, "%s:%i %i "f"\n", __FUNCTION__, __LINE__, getpid(), ## __VA_ARGS__); #else #define VDBG(f, ...) #endif #define AM_LOGI(fmt, ...) ALOGI("[%s:%d] " fmt, __func__,__LINE__, ##__VA_ARGS__) #define AML_FRAME_SIZE 4 #define AML_DEVICE_COUNT 20 #define CAP_BUFFER_NAME "ahal_capture" #define CAPTURE_BUFFER_MIN_SIZE (4*48*32) #define ENCODER_CAP_DEV 11 #define AML_PCM_FD_COUNT 1 typedef enum _snd_enum_setting_type { SND_ENUM_SETTING_TYPE_DEEPBUFFER, SND_ENUM_SETTING_TYPE_AUX, SND_ENUM_SETTING_TYPE_TTS, SND_ENUM_SETTING_TYPE_HWMIXER, SND_ENUM_SETTING_TYPE_CAPTURE, SND_ENUM_SETTING_TYPE_TINYALSA, } snd_enum_setting_type; typedef struct _snd_pcm_setting_t { long device; long stream; long type; } snd_pcm_setting_t; typedef struct _snd_pcm_hal_t { snd_pcm_ioplug_t io; snd_pcm_setting_t setting[AML_DEVICE_COUNT]; bool stream_opened; bool use_ahal; unsigned channels; snd_pcm_uframes_t period_size; snd_pcm_uframes_t buffer_size; unsigned int period_count; unsigned int rate; size_t frame_size; void * transfer_buffer; int card; int snd_device; snd_pcm_stream_t snd_stream; struct pcm_config tiny_config; struct pcm *tiny_pcm; audio_hw_device_t *device; struct audio_stream_out *stream; struct audio_stream_in *in_stream; struct audio_config config; int flags; snd_enum_setting_type type; struct pollfd pfd; int event_fd; snd_pcm_sframes_t transfer_samples; } snd_pcm_hal_t; static int ahal_audio_sleep(uint64_t us) { struct timespec ts; if (us == 0) { return 0; } ts.tv_sec = (long)us / 1000000ULL; ts.tv_nsec = (long)(us - ts.tv_sec * 1000000ULL) * 1000; return nanosleep(&ts, NULL); } static int snd_pcm_hal_dump_data(const char *path, const void *buf, size_t bytes) { if (!path) { return -1; } int flen; FILE *fp = fopen(path, "ab+"); if (fp) { flen = fwrite((char *)buf, 1, bytes, fp); fclose(fp); return flen; } AM_LOGI("fail to open path=%s, errno=%d/%s", path, errno, strerror(errno)); return -1; } static void hal_free(snd_pcm_hal_t *ahal) { if (ahal->transfer_buffer) { free(ahal->transfer_buffer); } free(ahal); } static int snd_pcm_hal_get_flags(int type, int channel) { int flags = AUDIO_OUTPUT_FLAG_PRIMARY | AUDIO_OUTPUT_FLAG_DEEP_BUFFER; switch (type) { case SND_ENUM_SETTING_TYPE_DEEPBUFFER: flags = AUDIO_OUTPUT_FLAG_PRIMARY | AUDIO_OUTPUT_FLAG_DEEP_BUFFER; if (channel > 2) { //deep buffer doesn't support multi-ch flags = AUDIO_OUTPUT_FLAG_PRIMARY; } break; case SND_ENUM_SETTING_TYPE_AUX: flags = AUDIO_OUTPUT_FLAG_PRIMARY; break; case SND_ENUM_SETTING_TYPE_TTS: flags = AUDIO_OUTPUT_FLAG_MMAP_NOIRQ | AUDIO_OUTPUT_FLAG_PRIMARY; break; case SND_ENUM_SETTING_TYPE_HWMIXER: flags = AUDIO_OUTPUT_FLAG_FAST; break; default: break; } return flags; } static int snd_pcm_hal_open_stream(snd_pcm_hal_t *ahal) { int err; int ret = 0; err = audio_hw_load_interface(&ahal->device); if (err) { AM_LOGI("error:%d", err); return err; } int inited = ahal->device->init_check(ahal->device); if (inited) { AM_LOGI("device not inited, quit"); err = -1; goto exit; } memset(&ahal->config, 0, sizeof(struct audio_config)); ahal->config.sample_rate = ahal->rate; switch (ahal->channels) { case 1: //set as STEREO, transfer to STEREO before write!! ahal->config.channel_mask = AUDIO_CHANNEL_OUT_STEREO; break; case 2: ahal->config.channel_mask = AUDIO_CHANNEL_OUT_STEREO; break; case 6: ahal->config.channel_mask = AUDIO_CHANNEL_OUT_5POINT1; break; case 8: ahal->config.channel_mask = AUDIO_CHANNEL_OUT_7POINT1; break; default: ahal->config.channel_mask = AUDIO_CHANNEL_OUT_STEREO; break; } ahal->config.format = AUDIO_FORMAT_PCM_16_BIT; if (SND_ENUM_SETTING_TYPE_CAPTURE == ahal->type) { ahal->config.channel_mask = AUDIO_CHANNEL_IN_STEREO; audio_source_t src = AUDIO_SOURCE_MIXER_OUT; if (ahal->snd_device == ENCODER_CAP_DEV) { src = AUDIO_SOURCE_ENCODER_OUT; } ret = ahal->device->open_input_stream(ahal->device, 0, AUDIO_DEVICE_OUT_SPEAKER, &ahal->config, &ahal->in_stream, AUDIO_INPUT_FLAG_RAW, NULL, src); if (ret) { AM_LOGI("open input stream fail"); goto exit; } else { AM_LOGI("open input stream success"); } } else { char addr[64] = {0}; snprintf(addr, sizeof(addr), "pcm_ahal_%d", ahal->snd_device); err = ahal->device->open_output_stream(ahal->device, 0, AUDIO_DEVICE_OUT_SPEAKER, ahal->flags, &ahal->config, &ahal->stream, addr); if (err) { AM_LOGI("open output stream error: channel_mask(%#x) rate(%d) format(%#x) flags(%#x)", ahal->config.channel_mask, ahal->config.sample_rate, ahal->config.format, ahal->flags); goto exit; } } return 0; exit: audio_hw_unload_interface(ahal->device); return -1; } static int snd_pcm_hal_open_alsa(snd_pcm_hal_t *ahal) { struct pcm_config *config = &ahal->tiny_config; memset(config, 0, sizeof(struct pcm_config)); config->channels = ahal->channels; config->rate = ahal->rate; config->period_size = ahal->period_size; config->period_count = ahal->period_count; config->format = PCM_FORMAT_S16_LE; config->start_threshold = 0; config->stop_threshold = 0; config->silence_threshold = 0; ahal->tiny_pcm = pcm_open(ahal->card, ahal->snd_device, ahal->snd_stream == SND_PCM_STREAM_PLAYBACK? PCM_OUT: PCM_IN, config); if (!ahal->tiny_pcm || !pcm_is_ready(ahal->tiny_pcm)) { AM_LOGI("Unable to open PCM device %u (%s)\n", ahal->snd_device, pcm_get_error(ahal->tiny_pcm)); return -1; } return 0; } static int snd_pcm_hal_apply_conf(snd_pcm_hal_t *ahal) { int i = 0; ahal->use_ahal = false; snd_pcm_setting_t* setting = NULL; for (i = 0; i < AML_DEVICE_COUNT; i++) { setting = &ahal->setting[i]; if (ahal->snd_device == setting->device && ahal->snd_stream == setting->stream) { ahal->use_ahal = (setting->type != SND_ENUM_SETTING_TYPE_TINYALSA); ahal->type = setting->type; break; } } ahal->flags = snd_pcm_hal_get_flags(ahal->type, ahal->channels); AM_LOGI("type %d", ahal->type); return 0; } static int snd_pcm_hal_close(snd_pcm_ioplug_t *io) { snd_pcm_hal_t *ahal = io->private_data; if (ahal) { AM_LOGI("---in---stream opened: %d, ahal %p. event fd %d", ahal->stream_opened, ahal, ahal->event_fd); if (ahal->stream_opened) { if (ahal->use_ahal) { if (SND_ENUM_SETTING_TYPE_CAPTURE == ahal->type) { ahal->device->close_input_stream(ahal->device, ahal->in_stream); } else { ahal->device->close_output_stream(ahal->device, ahal->stream); } audio_hw_unload_interface(ahal->device); } else { pcm_close(ahal->tiny_pcm); ahal->tiny_pcm = NULL; } } if (ahal->event_fd > 0) { close(ahal->event_fd); } ahal->stream_opened = false; io->private_data = NULL; hal_free(ahal); } else { AM_LOGI("ahal is null"); } return 0; } static snd_pcm_sframes_t snd_pcm_hal_pointer(snd_pcm_ioplug_t *io) { snd_pcm_hal_t *ahal = io->private_data; snd_pcm_sframes_t hw_pointer = 0; switch (io->state) { case SND_PCM_STATE_RUNNING: if (ahal->snd_stream == SND_PCM_STREAM_PLAYBACK) { hw_pointer = ahal->transfer_samples; } else { hw_pointer = ahal->transfer_samples + ahal->period_size; } break; case SND_PCM_STATE_XRUN: hw_pointer = -EPIPE; break; default: hw_pointer = 0; break; } //AM_LOGI("---in---hw_pointer:%ld", hw_pointer); return hw_pointer; } static int snd_pcm_hal_prepare(snd_pcm_ioplug_t *io) { snd_pcm_hal_t *ahal = io->private_data; int ret = 0; if (!ahal) { return -1; } AM_LOGI("---in---stream opened: %d", ahal->stream_opened); if (!ahal->stream_opened) { snd_pcm_hal_apply_conf(ahal); if (ahal->use_ahal) { ret = snd_pcm_hal_open_stream(ahal); } else { ret = snd_pcm_hal_open_alsa(ahal); } if (ret < 0) { ahal->stream_opened = false; } else { ahal->stream_opened = true; } } AM_LOGI("---out---"); return ret; } static int snd_pcm_hal_start(snd_pcm_ioplug_t *io) { snd_pcm_hal_t *ahal = io->private_data; AM_LOGI("---in---"); return 0; } static int snd_pcm_hal_stop(snd_pcm_ioplug_t *io) { snd_pcm_hal_t *ahal = io->private_data; AM_LOGI("---in---"); return 0; } static snd_pcm_sframes_t snd_pcm_hal_write(snd_pcm_ioplug_t *io, const snd_pcm_channel_area_t *areas, snd_pcm_uframes_t offset, snd_pcm_uframes_t size) { snd_pcm_hal_t *ahal = io->private_data; char *buf; size_t writebytes; //AM_LOGI("ahal write in %d", size); buf = (char *) areas->addr + (areas->first + areas->step * offset) / 8; writebytes = size * ahal->channels * 2; if (io->format == SND_PCM_FORMAT_S16_BE) { int16_t *buffer = (int16_t *)buf; for (int i = 0; i < size * ahal->channels; i++) { buffer[i] = bswap_16(buffer[i]); // 16-bit be to le } } if (ahal->use_ahal) { if (ahal->channels == 1) { short * frame = (short *)ahal->transfer_buffer; short * ori_frame = (short *)buf; for (int i = 0; i < size; i ++) { frame[i*2] = frame[i*2+1] = ori_frame[i]; } buf = (char *)ahal->transfer_buffer; writebytes = writebytes *2; } ssize_t s = ahal->stream->write(ahal->stream, buf, writebytes); if (s < 0) { AM_LOGI("stream writing error %d", s); } } else { pcm_writei(ahal->tiny_pcm, buf, size); } ahal->transfer_samples += size; //AM_LOGI("ahal write out %d"); return size; } static snd_pcm_sframes_t snd_pcm_hal_read(snd_pcm_ioplug_t *io, const snd_pcm_channel_area_t *areas, snd_pcm_uframes_t offset, snd_pcm_uframes_t size) { snd_pcm_hal_t *ahal = io->private_data; snd_pcm_uframes_t read = 0; char * buf; //AM_LOGI("---in---size:%ld", size); buf = (char *) areas->addr + (areas->first + areas->step * offset) / 8; if (size) { if (ahal->use_ahal) { int read_len = 0; read_len = ahal->in_stream->read(ahal->in_stream, buf, size * AML_FRAME_SIZE); read = read_len / AML_FRAME_SIZE; } else { read = pcm_readi(ahal->tiny_pcm, buf, size); } } //AM_LOGI("---out---size:%ld", read); ahal->transfer_samples += read; return read; } static int snd_pcm_hal_drain(snd_pcm_ioplug_t * io) { int err = 0; AM_LOGI("--drain out---"); return err; } static int snd_pcm_hal_hw_params(snd_pcm_ioplug_t * io, snd_pcm_hw_params_t * params) { snd_pcm_hal_t *ahal = io->private_data; int err = 0; AM_LOGI("---in---"); if (!ahal) { return -1; } switch (io->format) { case SND_PCM_FORMAT_S16_LE: case SND_PCM_FORMAT_S16_BE: break; default: AM_LOGI("Ahal: Unsupported format %s\n", snd_pcm_format_name(io->format)); err = -EINVAL; goto finish; } ahal->rate = io->rate; ahal->channels = io->channels; ahal->period_size = io->period_size; ahal->buffer_size = io->buffer_size; ahal->period_count = io->buffer_size / io->period_size; ahal->frame_size = io->channels * 2; if (ahal->channels == 1) { ahal->transfer_buffer = calloc(1, ahal->buffer_size * ahal->frame_size); } AM_LOGI("rate:%d, channel:%d, period size: %lu, count: %d, buffer_size:%lu", ahal->rate, ahal->channels, ahal->period_size, ahal->period_count, ahal->buffer_size); finish: return err; } static int snd_pcm_hal_poll_revents(snd_pcm_ioplug_t * io, struct pollfd *pfd, unsigned int nfds, unsigned short *revents) { int err = 0; snd_pcm_hal_t *ahal = io->private_data; if (!ahal) { return -1; } *revents = ahal->snd_stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN; return err; } static int snd_pcm_hal_poll_descriptors_count(snd_pcm_ioplug_t * io) { return AML_PCM_FD_COUNT; } static int snd_pcm_hal_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfd, unsigned int space) { snd_pcm_hal_t *ahal = io->private_data; if (!ahal) { return -1; } if (space != AML_PCM_FD_COUNT) return -EINVAL; if (io->stream == SND_PCM_STREAM_PLAYBACK) { pfd[0].fd = ahal->event_fd; pfd[0].events = POLLOUT; } else { pfd[0].fd = ahal->event_fd; pfd[0].events = POLLIN; } return space; } static snd_pcm_ioplug_callback_t hal_playback_callback = { .close = snd_pcm_hal_close, .start = snd_pcm_hal_start, .stop = snd_pcm_hal_stop, .transfer = snd_pcm_hal_write, .pointer = snd_pcm_hal_pointer, .prepare = snd_pcm_hal_prepare, .drain = snd_pcm_hal_drain, .hw_params = snd_pcm_hal_hw_params, }; static snd_pcm_ioplug_callback_t hal_capture_callback = { .close = snd_pcm_hal_close, .start = snd_pcm_hal_start, .stop = snd_pcm_hal_stop, .transfer = snd_pcm_hal_read, .pointer = snd_pcm_hal_pointer, .prepare = snd_pcm_hal_prepare, .drain = snd_pcm_hal_drain, .hw_params = snd_pcm_hal_hw_params, .poll_revents = snd_pcm_hal_poll_revents, .poll_descriptors_count = snd_pcm_hal_poll_descriptors_count, .poll_descriptors = snd_pcm_hal_poll_descriptors, }; #define ARRAY_SIZE(ary) (sizeof(ary)/sizeof(ary[0])) static int hal_set_hw_constraint(snd_pcm_hal_t *ahal) { unsigned access_list[] = { SND_PCM_ACCESS_RW_INTERLEAVED, SND_PCM_ACCESS_MMAP_INTERLEAVED, }; unsigned format_list[] = { SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE, }; int err; if ((err = snd_pcm_ioplug_set_param_list(&ahal->io, SND_PCM_IOPLUG_HW_ACCESS, ARRAY_SIZE(access_list), access_list)) < 0 || (err = snd_pcm_ioplug_set_param_list(&ahal->io, SND_PCM_IOPLUG_HW_FORMAT, ARRAY_SIZE(format_list), format_list)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&ahal->io, SND_PCM_IOPLUG_HW_CHANNELS, 1, 6)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&ahal->io, SND_PCM_IOPLUG_HW_RATE, 16000, 48000)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&ahal->io, SND_PCM_IOPLUG_HW_BUFFER_BYTES, 256*4*4, 4*1024*16)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&ahal->io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, 256*2, 4096*4)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&ahal->io, SND_PCM_IOPLUG_HW_PERIODS, 4, 25)) < 0) return err; return 0; } static int snd_pcm_hal_parse_setting(snd_pcm_hal_t *ahal, snd_config_t *conf) { int idx = 0; snd_config_iterator_t i, next; snd_config_t *node; snd_config_t *sub_node; snd_config_iterator_t j, sub_next; snd_pcm_setting_t* setting = NULL; snd_config_for_each(i, next, conf) { node = snd_config_iterator_entry(i); //AM_LOGI("type %d", snd_config_get_type(node)); setting = &ahal->setting[idx]; snd_config_for_each(j, sub_next, node) { sub_node = snd_config_iterator_entry(j); //AM_LOGI("type %d", snd_config_get_type(sub_node)); const char * name; snd_config_get_id(sub_node, &name); //AM_LOGI("name %s", name); if (strcmp(name, "id") == 0) { if (snd_config_get_type(sub_node) != SND_CONFIG_TYPE_INTEGER) { AM_LOGI("Invalid type for %s", name); return -EINVAL; } snd_config_get_integer(sub_node, &setting->device); //AM_LOGI("device %d", setting->device); continue; } if (strcmp(name, "stream") == 0) { if (snd_config_get_type(sub_node) != SND_CONFIG_TYPE_INTEGER) { AM_LOGI("Invalid type for %s", name); return -EINVAL; } snd_config_get_integer(sub_node, &setting->stream); //AM_LOGI("playback %d", setting->playback); if (setting->stream) { setting->stream = 1; } continue; } if (strcmp(name, "type") == 0) { if (snd_config_get_type(sub_node) != SND_CONFIG_TYPE_INTEGER) { AM_LOGI("Invalid type for %s", name); return -EINVAL; } snd_config_get_integer(sub_node, &setting->type); //AM_LOGI("flag %d", setting->flag); continue; } } idx++; } if (idx > 0) { for (int k = 0; k < idx; k++) { //AM_LOGI("[%d] id:%ld, stream:%ld, type %ld", k, ahal->setting[k].device, ahal->setting[k].stream, ahal->setting[k].type); } } return 0; } static int snd_pcm_hal_parse_conf(snd_pcm_hal_t *ahal, snd_config_t *conf) { snd_config_iterator_t i, next; long device = 0; int card = -1; int err = -1; snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id; if (snd_config_get_id(n, &id) < 0) continue; if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0 || strcmp(id, "subdevice")== 0 || strcmp(id, "hint") == 0) continue; if (strcmp(id, "card") == 0) { card = snd_config_get_card(n); if (card < 0) { AM_LOGI("Invalid card '%s'", id); card = 0; } continue; } if (strcmp(id, "device") == 0) { err = snd_config_get_integer(n, &device); if (err < 0) { AM_LOGI("Invalid type for %s", id); return -EINVAL; } continue; } if (strcmp(id, "setting") == 0) { if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) { AM_LOGI("Invalid type for %s", id); return -EINVAL; } AM_LOGI("array %d", snd_config_is_array(n)); snd_pcm_hal_parse_setting(ahal, n); continue; } AM_LOGI("Unknown field %s", id); return -EINVAL; } AM_LOGI("card:%d device:%ld", card, device); ahal->card = card; ahal->snd_device = device; return 0; } static int snd_pcm_hal_open(snd_pcm_t **pcmp, const char *name, snd_config_t *conf, snd_pcm_stream_t stream, int mode) { snd_pcm_hal_t *ahal; int err; assert(pcmp); ahal = calloc(1, sizeof(*ahal)); if (!ahal) return -ENOMEM; ALOGI("name %s. stream %d, mode %d", name, stream, mode); snd_pcm_hal_parse_conf(ahal, conf); ahal->channels = 2; ahal->period_size = 1024; ahal->rate = 48000; ahal->snd_stream = stream; ahal->io.version = SND_PCM_IOPLUG_VERSION; ahal->io.name = "ALSA <-> AMLOGIC HAL PCM I/O Plugin"; ahal->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? &hal_playback_callback : &hal_capture_callback; ahal->io.private_data = ahal; ahal->io.mmap_rw = 0; ahal->io.poll_fd = ahal->pfd.fd; ahal->io.poll_events = stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN; err = snd_pcm_ioplug_create(&ahal->io, name, stream, mode); if (err < 0) { hal_free(ahal); return err; } ahal->event_fd = eventfd(1, EFD_NONBLOCK); if (ahal->event_fd == -1) { ALOGE("fail create event_fd:%s", strerror(errno)); } else { AM_LOGI("create event_fd:%d", ahal->event_fd); } err = hal_set_hw_constraint(ahal); if (err < 0) { snd_pcm_ioplug_delete(&ahal->io); return err; } *pcmp = ahal->io.pcm; return 0; } SND_PCM_PLUGIN_DEFINE_FUNC(ahal) { return snd_pcm_hal_open(pcmp, name, conf, stream, mode); } SND_PCM_PLUGIN_SYMBOL(ahal);