From 026f096bb76ca42eba4354e14e2e7a230f201e97 Mon Sep 17 00:00:00 2001 From: brunosousa85 Date: Mon, 31 Jul 2023 14:34:21 -0300 Subject: [PATCH] Add audio_plugin --- audio_plugin/CMakeLists.txt | 13 + audio_plugin/README.md | 7 + audio_plugin/alsa.conf | 9 + audio_plugin/android_aserver.conf | 27 ++ audio_plugin/build.sh | 15 + audio_plugin/cross-arm64.cmake | 3 + audio_plugin/cross-armhf.cmake | 3 + audio_plugin/module_pcm_android_aserver.c | 316 ++++++++++++++++++++++ 8 files changed, 393 insertions(+) create mode 100644 audio_plugin/CMakeLists.txt create mode 100644 audio_plugin/README.md create mode 100644 audio_plugin/alsa.conf create mode 100644 audio_plugin/android_aserver.conf create mode 100644 audio_plugin/build.sh create mode 100644 audio_plugin/cross-arm64.cmake create mode 100644 audio_plugin/cross-armhf.cmake create mode 100644 audio_plugin/module_pcm_android_aserver.c diff --git a/audio_plugin/CMakeLists.txt b/audio_plugin/CMakeLists.txt new file mode 100644 index 0000000..897e96e --- /dev/null +++ b/audio_plugin/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.8) + +project(AlsaPlugins C) +message("Building ${PROJECT_NAME}") + +set(CMAKE_VERBOSE_MAKEFILE on) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -lasound -O2 -fPIC -DPIC") + +MESSAGE(STATUS "Compiler options: ${CMAKE_C_FLAGS}") + +add_library(asound_module_pcm_android_aserver SHARED module_pcm_android_aserver.c) + +target_link_libraries(asound_module_pcm_android_aserver "${CROSS_PATH}/libasound.so.2") \ No newline at end of file diff --git a/audio_plugin/README.md b/audio_plugin/README.md new file mode 100644 index 0000000..9229fa4 --- /dev/null +++ b/audio_plugin/README.md @@ -0,0 +1,7 @@ +This is the ALSA audio plugin used in Winlator + +## Build dependencies + + $ dpkg --add-architecture armhf + $ apt install build-essential make cmake g++-arm-linux-gnueabihf + $ apt install libasound2-dev:arm64 libasound2-dev:armhf \ No newline at end of file diff --git a/audio_plugin/alsa.conf b/audio_plugin/alsa.conf new file mode 100644 index 0000000..116f86c --- /dev/null +++ b/audio_plugin/alsa.conf @@ -0,0 +1,9 @@ +@hooks [ + { + func load + files [ + "/etc/alsa/conf.d/android_aserver.conf" + ] + errors false + } +] \ No newline at end of file diff --git a/audio_plugin/android_aserver.conf b/audio_plugin/android_aserver.conf new file mode 100644 index 0000000..2425db1 --- /dev/null +++ b/audio_plugin/android_aserver.conf @@ -0,0 +1,27 @@ +pcm.android_aserver { + type android_aserver + hint { + description "Android ALSA Server" + } +} + +ctl.android_aserver { + type android_aserver + hint { + description "Android ALSA Server" + } +} + +pcm.!default { + type android_aserver + hint { + description "Default" + } +} + +ctl.!default { + type android_aserver + hint { + description "Default" + } +} \ No newline at end of file diff --git a/audio_plugin/build.sh b/audio_plugin/build.sh new file mode 100644 index 0000000..eb6e9e9 --- /dev/null +++ b/audio_plugin/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +rm -r build64 +mkdir build64 +cd build64 +cmake .. -DCMAKE_TOOLCHAIN_FILE=../cross-arm64.cmake +make -j4 + +cd .. + +rm -r build +mkdir build +cd build +cmake .. -DCMAKE_TOOLCHAIN_FILE=../cross-armhf.cmake +make -j4 \ No newline at end of file diff --git a/audio_plugin/cross-arm64.cmake b/audio_plugin/cross-arm64.cmake new file mode 100644 index 0000000..1cc4db2 --- /dev/null +++ b/audio_plugin/cross-arm64.cmake @@ -0,0 +1,3 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CROSS_PATH "/usr/lib/aarch64-linux-gnu") +set(CMAKE_C_COMPILER "aarch64-linux-gnu-gcc") \ No newline at end of file diff --git a/audio_plugin/cross-armhf.cmake b/audio_plugin/cross-armhf.cmake new file mode 100644 index 0000000..201d43b --- /dev/null +++ b/audio_plugin/cross-armhf.cmake @@ -0,0 +1,3 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CROSS_PATH "/usr/lib/arm-linux-gnueabihf") +set(CMAKE_C_COMPILER "arm-linux-gnueabihf-gcc") \ No newline at end of file diff --git a/audio_plugin/module_pcm_android_aserver.c b/audio_plugin/module_pcm_android_aserver.c new file mode 100644 index 0000000..85a9889 --- /dev/null +++ b/audio_plugin/module_pcm_android_aserver.c @@ -0,0 +1,316 @@ +#include +#include +#include +#include +#include +#include + +#define MIN_REQUEST_LENGTH 5 + +#define REQUEST_CODE_CLOSE 0 +#define REQUEST_CODE_START 1 +#define REQUEST_CODE_STOP 2 +#define REQUEST_CODE_PAUSE 3 +#define REQUEST_CODE_PREPARE 4 +#define REQUEST_CODE_WRITE 5 +#define REQUEST_CODE_DRAIN 6 +#define REQUEST_CODE_POINTER 7 + +#define DATA_TYPE_U8 0 +#define DATA_TYPE_S16LE 1 +#define DATA_TYPE_S16BE 2 +#define DATA_TYPE_FLOATLE 3 +#define DATA_TYPE_FLOATBE 4 + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) + +typedef struct snd_pcm_android_aserver { + snd_pcm_ioplug_t io; + int fd; + int frame_bytes; + unsigned int position; + char* buffer; + int buffer_size; +} snd_pcm_android_aserver_t; + +static void grow_buffer_if_necessary(snd_pcm_android_aserver_t* android_aserver, int new_size) { + if (new_size > android_aserver->buffer_size) { + if (android_aserver->buffer) free(android_aserver->buffer); + android_aserver->buffer_size = new_size; + android_aserver->buffer = (char*)malloc(new_size); + } +} + +static int android_aserver_close(snd_pcm_ioplug_t* io) { + snd_pcm_android_aserver_t* android_aserver = io->private_data; + if (!android_aserver) return 0; + + if (android_aserver->fd >= 0) { + int request_length = 0; + char request_data[MIN_REQUEST_LENGTH]; + request_data[0] = REQUEST_CODE_CLOSE; + memcpy(request_data + 1, &request_length, 4); + + write(android_aserver->fd, request_data, sizeof(request_data)); + close(android_aserver->fd); + } + + if (android_aserver->buffer) { + free(android_aserver->buffer); + android_aserver->buffer_size = 0; + } + + free(android_aserver); + return 0; +} + +static int android_aserver_start(snd_pcm_ioplug_t* io) { + snd_pcm_android_aserver_t* android_aserver = io->private_data; + + grow_buffer_if_necessary(android_aserver, MIN_REQUEST_LENGTH); + + int request_length = 0; + android_aserver->buffer[0] = REQUEST_CODE_START; + memcpy(android_aserver->buffer + 1, &request_length, 4); + + int res = write(android_aserver->fd, android_aserver->buffer, MIN_REQUEST_LENGTH); + if (res < 0) return -EINVAL; + + return 0; +} + +static int android_aserver_stop(snd_pcm_ioplug_t* io) { + snd_pcm_android_aserver_t* android_aserver = io->private_data; + + grow_buffer_if_necessary(android_aserver, MIN_REQUEST_LENGTH); + + int request_length = 0; + android_aserver->buffer[0] = REQUEST_CODE_STOP; + memcpy(android_aserver->buffer + 1, &request_length, 4); + + int res = write(android_aserver->fd, android_aserver->buffer, MIN_REQUEST_LENGTH); + if (res < 0) return -EINVAL; + + return 0; +} + +static int android_aserver_pause(snd_pcm_ioplug_t* io) { + snd_pcm_android_aserver_t* android_aserver = io->private_data; + + grow_buffer_if_necessary(android_aserver, MIN_REQUEST_LENGTH); + + int request_length = 0; + android_aserver->buffer[0] = REQUEST_CODE_PAUSE; + memcpy(android_aserver->buffer + 1, &request_length, 4); + + int res = write(android_aserver->fd, android_aserver->buffer, MIN_REQUEST_LENGTH); + if (res < 0) return -EINVAL; + + return 0; +} + +static int android_aserver_prepare(snd_pcm_ioplug_t* io) { + snd_pcm_android_aserver_t* android_aserver = io->private_data; + android_aserver->frame_bytes = (snd_pcm_format_physical_width(io->format) * io->channels) / 8; + android_aserver->position = 0; + + char channels = (char)io->channels; + char data_type; + switch (io->format) { + case SND_PCM_FORMAT_U8: + data_type = DATA_TYPE_U8; + break; + case SND_PCM_FORMAT_S16_LE: + data_type = DATA_TYPE_S16LE; + break; + case SND_PCM_FORMAT_S16_BE: + data_type = DATA_TYPE_S16BE; + break; + case SND_PCM_FORMAT_FLOAT_LE: + data_type = DATA_TYPE_FLOATLE; + break; + case SND_PCM_FORMAT_FLOAT_BE: + data_type = DATA_TYPE_FLOATBE; + break; + default: + return -EINVAL; + } + + int request_length = 10; + grow_buffer_if_necessary(android_aserver, request_length + MIN_REQUEST_LENGTH); + android_aserver->buffer[0] = REQUEST_CODE_PREPARE; + memcpy(android_aserver->buffer + 1, &request_length, 4); + memcpy(android_aserver->buffer + 5, &channels, 1); + memcpy(android_aserver->buffer + 6, &data_type, 1); + memcpy(android_aserver->buffer + 7, &io->rate, 4); + memcpy(android_aserver->buffer + 11, &io->buffer_size, 4); + + int res = write(android_aserver->fd, android_aserver->buffer, request_length + MIN_REQUEST_LENGTH); + if (res < 0) return -EINVAL; + + return 0; +} + +static snd_pcm_sframes_t android_aserver_pointer(snd_pcm_ioplug_t* io) { + snd_pcm_android_aserver_t* android_aserver = io->private_data; + + grow_buffer_if_necessary(android_aserver, MIN_REQUEST_LENGTH); + + int request_length = 0; + android_aserver->buffer[0] = REQUEST_CODE_POINTER; + memcpy(android_aserver->buffer + 1, &request_length, 4); + + int res = write(android_aserver->fd, android_aserver->buffer, MIN_REQUEST_LENGTH); + if (res < 0) return android_aserver->position; + + int position; + res = read(android_aserver->fd, &position, 4); + if (res < 0) return android_aserver->position; + + android_aserver->position = position; + return android_aserver->position; +} + +static snd_pcm_sframes_t android_aserver_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_android_aserver_t* android_aserver = io->private_data; + + const char* src_data = (char*)areas->addr + (areas->first + areas->step * offset) / 8; + + int request_length = size * android_aserver->frame_bytes; + grow_buffer_if_necessary(android_aserver, request_length + MIN_REQUEST_LENGTH); + + android_aserver->buffer[0] = REQUEST_CODE_WRITE; + memcpy(android_aserver->buffer + 1, &request_length, 4); + memcpy(android_aserver->buffer + 5, src_data, request_length); + + int res = write(android_aserver->fd, android_aserver->buffer, request_length + MIN_REQUEST_LENGTH); + return (res >= MIN_REQUEST_LENGTH ? res - MIN_REQUEST_LENGTH : 0) / android_aserver->frame_bytes; +} + +static int android_aserver_drain(snd_pcm_ioplug_t* io) { + snd_pcm_android_aserver_t* android_aserver = io->private_data; + + grow_buffer_if_necessary(android_aserver, MIN_REQUEST_LENGTH); + + int request_length = 0; + android_aserver->buffer[0] = REQUEST_CODE_DRAIN; + memcpy(android_aserver->buffer + 1, &request_length, 4); + + int res = write(android_aserver->fd, android_aserver->buffer, MIN_REQUEST_LENGTH); + if (res < 0) return -EINVAL; + + return 0; +} + +static int android_aserver_set_hw_constraint(snd_pcm_ioplug_t* io) { + static const unsigned int access_list[] = {SND_PCM_ACCESS_RW_INTERLEAVED}; + static const unsigned int format_list[] = {SND_PCM_FORMAT_U8, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE, SND_PCM_FORMAT_FLOAT_LE, SND_PCM_FORMAT_FLOAT_BE}; + int err; + + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, ARRAY_SIZE(access_list), access_list); + if (err < 0) return err; + + err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, ARRAY_SIZE(format_list), format_list); + if (err < 0) return err; + + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS, 1, 2); + if (err < 0) return err; + + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE, 8000, 48000); + if (err < 0) return err; + + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, 64, 64 * 1024); + if (err < 0) return err; + + err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, 2, 64); + if (err < 0) return err; + return 0; +} + +static snd_pcm_ioplug_callback_t android_aserver_callback = { + .close = android_aserver_close, + .start = android_aserver_start, + .stop = android_aserver_stop, + .pause = android_aserver_pause, + .prepare = android_aserver_prepare, + .transfer = android_aserver_write, + .drain = android_aserver_drain, + .pointer = android_aserver_pointer, +}; + +static int android_aserver_connect() { + char* path = getenv("ANDROID_ALSA_SERVER"); + + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return -1; + + struct sockaddr_un server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sun_family = AF_LOCAL; + + strncpy(server_addr.sun_path, path, sizeof(server_addr.sun_path) - 1); + + int res = connect(fd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_un)); + if (res < 0) { + close(fd); + return -1; + } + + return fd; +} + +static int android_aserver_create(snd_pcm_t** pcmp, const char* name, snd_pcm_stream_t stream, int mode) { + snd_pcm_android_aserver_t* android_aserver; + + if (stream != SND_PCM_STREAM_PLAYBACK) return -ENOTSUP; + + android_aserver = calloc(1, sizeof(snd_pcm_android_aserver_t)); + if (!android_aserver) return -ENOMEM; + + android_aserver->buffer_size = 0; + android_aserver->io.version = SND_PCM_IOPLUG_VERSION; + android_aserver->io.name = "ALSA <-> Android AServer PCM Plugin"; + android_aserver->io.callback = &android_aserver_callback; + android_aserver->io.mmap_rw = 0; + android_aserver->io.private_data = android_aserver; + + int res = -EINVAL; + android_aserver->fd = android_aserver_connect(); + if (android_aserver->fd < 0) goto error; + + res = snd_pcm_ioplug_create(&android_aserver->io, name, stream, mode); + if (res < 0) goto error; + + res = android_aserver_set_hw_constraint(&android_aserver->io); + if (res < 0) { + snd_pcm_ioplug_delete(&android_aserver->io); + goto error; + } + + *pcmp = android_aserver->io.pcm; + return 0; + + error: + if (android_aserver->fd >= 0) close(android_aserver->fd); + free(android_aserver); + return res; +} + +SND_PCM_PLUGIN_DEFINE_FUNC(android_aserver) { + snd_config_iterator_t i, next; + + 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, "type") == 0 || strcmp(id, "hint") == 0) continue; + + return -EINVAL; + } + + int err = android_aserver_create(pcmp, name, stream, mode); + return err; +} + +SND_PCM_PLUGIN_SYMBOL(android_aserver); \ No newline at end of file