diff --git a/CHANGES.md b/CHANGES.md index 59d7e0249c..94f4ebb42e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ - AUDIO: Fix audio handling in case of RARCH_NETPLAY_CTL_USE_CORE_PACKET_INTERFACE - AUDIO: Include missing audio filters on some platforms - AUDIO/PIPEWIRE: Add PipeWire audio driver +- AUDIO/PIPEWIRE: Add PipeWire microphone driver - APPLE: Hide threaded video setting - APPLE: Use mfi joypad driver by default - APPLE: Include holani, noods, mrboom, yabause core in App Store builds diff --git a/Makefile.common b/Makefile.common index f337321f98..10997386dd 100644 --- a/Makefile.common +++ b/Makefile.common @@ -922,7 +922,13 @@ ifeq ($(HAVE_PULSE), 1) endif ifeq ($(HAVE_PIPEWIRE), 1) - OBJ += audio/drivers/pipewire.o + OBJ += audio/drivers/pipewire.o \ + audio/common/pipewire.o + + ifeq ($(HAVE_MICROPHONE), 1) + OBJ += audio/drivers_microphone/pipewire.o + endif + LIBS += $(PIPEWIRE_LIBS) DEF_FLAGS += $(PIPEWIRE_CFLAGS) endif diff --git a/audio/common/pipewire.c b/audio/common/pipewire.c new file mode 100644 index 0000000000..1fc213c23c --- /dev/null +++ b/audio/common/pipewire.c @@ -0,0 +1,116 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2024 Viachaslau Khalikin + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "pipewire.h" + +#include + +#include + +#include "verbosity.h" + +size_t calc_frame_size(enum spa_audio_format fmt, uint32_t nchannels) +{ + uint32_t sample_size = 1; + switch (fmt) + { + case SPA_AUDIO_FORMAT_S8: + case SPA_AUDIO_FORMAT_U8: + sample_size = 1; + break; + case SPA_AUDIO_FORMAT_S16_BE: + case SPA_AUDIO_FORMAT_S16_LE: + case SPA_AUDIO_FORMAT_U16_BE: + case SPA_AUDIO_FORMAT_U16_LE: + sample_size = 2; + break; + case SPA_AUDIO_FORMAT_S32_BE: + case SPA_AUDIO_FORMAT_S32_LE: + case SPA_AUDIO_FORMAT_U32_BE: + case SPA_AUDIO_FORMAT_U32_LE: + case SPA_AUDIO_FORMAT_F32_BE: + case SPA_AUDIO_FORMAT_F32_LE: + sample_size = 4; + break; + default: + RARCH_ERR("[PipeWire]: Bad spa_audio_format %d\n", fmt); + break; + } + return sample_size * nchannels; +} + +void set_position(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS]) +{ + memcpy(position, (uint32_t[SPA_AUDIO_MAX_CHANNELS]) { SPA_AUDIO_CHANNEL_UNKNOWN, }, + sizeof(uint32_t) * SPA_AUDIO_MAX_CHANNELS); + + switch (channels) + { + case 8: + position[6] = SPA_AUDIO_CHANNEL_SL; + position[7] = SPA_AUDIO_CHANNEL_SR; + /* fallthrough */ + case 6: + position[2] = SPA_AUDIO_CHANNEL_FC; + position[3] = SPA_AUDIO_CHANNEL_LFE; + position[4] = SPA_AUDIO_CHANNEL_RL; + position[5] = SPA_AUDIO_CHANNEL_RR; + /* fallthrough */ + case 2: + position[0] = SPA_AUDIO_CHANNEL_FL; + position[1] = SPA_AUDIO_CHANNEL_FR; + break; + case 1: + position[0] = SPA_AUDIO_CHANNEL_MONO; + break; + default: + RARCH_ERR("[PipeWire]: Internal error: unsupported channel count %d\n", channels); + } +} + +int pipewire_wait_resync(pipewire_core_t *pipewire) +{ + int res; + retro_assert(pipewire != NULL); + + pipewire->pending_seq = pw_core_sync(pipewire->core, PW_ID_CORE, pipewire->pending_seq); + + for (;;) + { + pw_thread_loop_wait(pipewire->thread_loop); + + res = pipewire->error; + if (res < 0) + { + pipewire->error = 0; + return res; + } + if (pipewire->pending_seq == pipewire->last_seq) + break; + } + return 0; +} + +bool pipewire_set_active(pipewire_core_t *pipewire, pipewire_device_handle_t *device, bool active) +{ + RARCH_LOG("[PipeWire]: %s.\n", active? "Unpausing": "Pausing"); + + pw_thread_loop_lock(pipewire->thread_loop); + pw_stream_set_active(device->stream, active); + pw_thread_loop_wait(pipewire->thread_loop); + pw_thread_loop_unlock(pipewire->thread_loop); + + return device->is_paused != active; +} diff --git a/audio/common/pipewire.h b/audio/common/pipewire.h new file mode 100644 index 0000000000..7c7ea323f9 --- /dev/null +++ b/audio/common/pipewire.h @@ -0,0 +1,70 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2024 The RetroArch team + * + * RetroArch is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Found- ation, either version 3 of the License, or (at your option) any later + * version. + * + * RetroArch 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * RetroArch. If not, see . + */ + +#ifndef _RETROARCH_PIPEWIRE +#define _RETROARCH_PIPEWIRE + +#include + +#include +#include + + +#define RINGBUFFER_SIZE (1u << 22) +#define RINGBUFFER_MASK (RINGBUFFER_SIZE - 1) + +typedef struct pipewire +{ + struct pw_thread_loop *thread_loop; + struct pw_context *context; + struct pw_core *core; + struct spa_hook core_listener; + int last_seq, pending_seq, error; + + struct pw_registry *registry; + struct spa_hook registry_listener; + struct pw_client *client; + struct spa_hook client_listener; + + bool nonblock; + struct string_list *devicelist; +} pipewire_core_t; + +typedef struct pipewire_device_handle +{ + pipewire_core_t *pw; + + struct pw_stream *stream; + struct spa_hook stream_listener; + struct spa_audio_info_raw info; + uint32_t highwater_mark; + uint32_t frame_size; + uint32_t req; + struct spa_ringbuffer ring; + uint8_t buffer[RINGBUFFER_SIZE]; + + bool is_paused; +} pipewire_device_handle_t; + +size_t calc_frame_size(enum spa_audio_format fmt, uint32_t nchannels); + +void set_position(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS]); + +int pipewire_wait_resync(pipewire_core_t *pipewire); + +bool pipewire_set_active(pipewire_core_t *pipewire, pipewire_device_handle_t *device, bool active); + +#endif /* _RETROARCH_PIPEWIRE */ diff --git a/audio/drivers/pipewire.c b/audio/drivers/pipewire.c index 049a6bf481..dbc4320bb7 100644 --- a/audio/drivers/pipewire.c +++ b/audio/drivers/pipewire.c @@ -30,127 +30,34 @@ #include #include -#include "../audio_driver.h" -#include "../../verbosity.h" +#include "audio/common/pipewire.h" +#include "audio/audio_driver.h" +#include "verbosity.h" #define APPNAME "RetroArch" #define DEFAULT_CHANNELS 2 #define QUANTUM 1024 /* TODO: detect */ -#define RINGBUFFER_SIZE (1u << 22) -#define RINGBUFFER_MASK (RINGBUFFER_SIZE - 1) - -typedef struct -{ - struct pw_thread_loop *thread_loop; - struct pw_context *context; - struct pw_core *core; - struct spa_hook core_listener; - int last_seq, pending_seq, error; - - struct pw_stream *stream; - struct spa_hook stream_listener; - struct spa_audio_info_raw info; - uint32_t highwater_mark; - uint32_t frame_size, req; - struct spa_ringbuffer ring; - uint8_t buffer[RINGBUFFER_SIZE]; - - struct pw_registry *registry; - struct spa_hook registry_listener; - struct pw_client *client; - struct spa_hook client_listener; - - bool nonblock; - bool is_paused; - struct string_list *devicelist; -} pw_t; - -void clear_buf(void *buf, int len) -{ - if (len) - memset(buf, 0x00, len); -} - -size_t calc_frame_size(enum spa_audio_format fmt, uint32_t nchannels) -{ - uint32_t sample_size = 1; - switch (fmt) - { - case SPA_AUDIO_FORMAT_S8: - case SPA_AUDIO_FORMAT_U8: - sample_size = 1; - break; - case SPA_AUDIO_FORMAT_S16_BE: - case SPA_AUDIO_FORMAT_S16_LE: - case SPA_AUDIO_FORMAT_U16_BE: - case SPA_AUDIO_FORMAT_U16_LE: - sample_size = 2; - break; - case SPA_AUDIO_FORMAT_S32_BE: - case SPA_AUDIO_FORMAT_S32_LE: - case SPA_AUDIO_FORMAT_U32_BE: - case SPA_AUDIO_FORMAT_U32_LE: - case SPA_AUDIO_FORMAT_F32_BE: - case SPA_AUDIO_FORMAT_F32_LE: - sample_size = 4; - break; - default: - RARCH_ERR("[PipeWire]: Bad spa_audio_format %d\n", fmt); - break; - } - return sample_size * nchannels; -} - -static void set_position(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS]) -{ - memcpy(position, (uint32_t[SPA_AUDIO_MAX_CHANNELS]) { SPA_AUDIO_CHANNEL_UNKNOWN, }, - sizeof(uint32_t) * SPA_AUDIO_MAX_CHANNELS); - - switch (channels) - { - case 8: - position[6] = SPA_AUDIO_CHANNEL_SL; - position[7] = SPA_AUDIO_CHANNEL_SR; - /* fallthrough */ - case 6: - position[2] = SPA_AUDIO_CHANNEL_FC; - position[3] = SPA_AUDIO_CHANNEL_LFE; - position[4] = SPA_AUDIO_CHANNEL_RL; - position[5] = SPA_AUDIO_CHANNEL_RR; - /* fallthrough */ - case 2: - position[0] = SPA_AUDIO_CHANNEL_FL; - position[1] = SPA_AUDIO_CHANNEL_FR; - break; - case 1: - position[0] = SPA_AUDIO_CHANNEL_MONO; - break; - default: - RARCH_ERR("[PipeWire]: Internal error: unsupported channel count %d\n", channels); - } -} - static void stream_destroy(void *data) { - pw_t *pw = (pw_t*)data; - spa_hook_remove(&pw->stream_listener); - pw->stream = NULL; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; + spa_hook_remove(&audio->stream_listener); + audio->stream = NULL; } static void on_process(void *data) { - pw_t *pw = (pw_t*)data; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; void *p; struct pw_buffer *b; struct spa_buffer *buf; uint32_t req, index, n_bytes; int32_t avail; - retro_assert(pw->stream); + retro_assert(audio->stream); - if ((b = pw_stream_dequeue_buffer(pw->stream)) == NULL) + if ((b = pw_stream_dequeue_buffer(audio->stream)) == NULL) { RARCH_WARN("[PipeWire]: Out of buffers: %s\n", strerror(errno)); return; @@ -162,58 +69,59 @@ static void on_process(void *data) return; /* calculate the total no of bytes to read data from buffer */ - req = b->requested * pw->frame_size; + req = b->requested * audio->frame_size; if (req == 0) - req = pw->req; + req = audio->req; n_bytes = SPA_MIN(req, buf->datas[0].maxsize); /* get no of available bytes to read data from buffer */ - avail = spa_ringbuffer_get_read_index(&pw->ring, &index); + avail = spa_ringbuffer_get_read_index(&audio->ring, &index); if (avail <= 0) - clear_buf(p, n_bytes); + /* fill rest buffer with silence */ + memset(p, 0x00, n_bytes); else { if (avail < (int32_t)n_bytes) n_bytes = avail; - spa_ringbuffer_read_data(&pw->ring, - pw->buffer, RINGBUFFER_SIZE, + spa_ringbuffer_read_data(&audio->ring, + audio->buffer, RINGBUFFER_SIZE, index & RINGBUFFER_MASK, p, n_bytes); index += n_bytes; - spa_ringbuffer_read_update(&pw->ring, index); + spa_ringbuffer_read_update(&audio->ring, index); } buf->datas[0].chunk->offset = 0; - buf->datas[0].chunk->stride = pw->frame_size; + buf->datas[0].chunk->stride = audio->frame_size; buf->datas[0].chunk->size = n_bytes; /* queue the buffer for playback */ - pw_stream_queue_buffer(pw->stream, b); + pw_stream_queue_buffer(audio->stream, b); } static void on_stream_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { - pw_t *pw = (pw_t*)data; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; - RARCH_DBG("[PipeWire]: New state for Node %d : %s\n", - pw_stream_get_node_id(pw->stream), + RARCH_DBG("[PipeWire]: New state for Sink Node %d : %s\n", + pw_stream_get_node_id(audio->stream), pw_stream_state_as_string(state)); switch(state) { case PW_STREAM_STATE_STREAMING: - pw->is_paused = false; - pw_thread_loop_signal(pw->thread_loop, false); + audio->is_paused = false; + pw_thread_loop_signal(audio->pw->thread_loop, false); break; case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_PAUSED: - pw->is_paused = true; - pw_thread_loop_signal(pw->thread_loop, false); + audio->is_paused = true; + pw_thread_loop_signal(audio->pw->thread_loop, false); break; default: break; @@ -221,39 +129,15 @@ static void on_stream_state_changed(void *data, } static const struct pw_stream_events playback_stream_events = { - PW_VERSION_STREAM_EVENTS, - .destroy = stream_destroy, - .process = on_process, - .state_changed = on_stream_state_changed, + PW_VERSION_STREAM_EVENTS, + .destroy = stream_destroy, + .process = on_process, + .state_changed = on_stream_state_changed, }; -static int wait_resync(pw_t *pw) -{ - int res; - - retro_assert(pw != NULL); - - pw->pending_seq = pw_core_sync(pw->core, PW_ID_CORE, pw->pending_seq); - - for (;;) - { - pw_thread_loop_wait(pw->thread_loop); - - res = pw->error; - if (res < 0) - { - pw->error = 0; - return res; - } - if (pw->pending_seq == pw->last_seq) - break; - } - return 0; -} - static void client_info(void *data, const struct pw_client_info *info) { - pw_t *pw = (pw_t*)data; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; const struct spa_dict_item *item; RARCH_DBG("[PipeWire]: client: id:%u\n", info->id); @@ -261,43 +145,43 @@ static void client_info(void *data, const struct pw_client_info *info) spa_dict_for_each(item, info->props) RARCH_DBG("[PipeWire]: \t\t%s: \"%s\"\n", item->key, item->value); - pw_thread_loop_signal(pw->thread_loop, false); + pw_thread_loop_signal(audio->pw->thread_loop, false); } static const struct pw_client_events client_events = { - PW_VERSION_CLIENT_EVENTS, - .info = client_info, + PW_VERSION_CLIENT_EVENTS, + .info = client_info, }; static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { - pw_t *pw = data; + pipewire_device_handle_t *audio = data; RARCH_ERR("[PipeWire]: error id:%u seq:%d res:%d (%s): %s\n", id, seq, res, spa_strerror(res), message); /* stop and exit the thread loop */ - pw_thread_loop_signal(pw->thread_loop, false); + pw_thread_loop_signal(audio->pw->thread_loop, false); } static void on_core_done(void *data, uint32_t id, int seq) { - pw_t *pw = (pw_t*)data; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; retro_assert(id == PW_ID_CORE); - pw->last_seq = seq; - if (pw->pending_seq == seq) + audio->pw->last_seq = seq; + if (audio->pw->pending_seq == seq) { /* stop and exit the thread loop */ - pw_thread_loop_signal(pw->thread_loop, false); + pw_thread_loop_signal(audio->pw->thread_loop, false); } } static const struct pw_core_events core_events = { - PW_VERSION_CORE_EVENTS, - .done = on_core_done, - .error = on_core_error, + PW_VERSION_CORE_EVENTS, + .done = on_core_done, + .error = on_core_error, }; static void registry_event_global(void *data, uint32_t id, @@ -305,20 +189,23 @@ static void registry_event_global(void *data, uint32_t id, const struct spa_dict *props) { union string_list_elem_attr attr; - pw_t *pw = (pw_t*)data; - const char *media = NULL; - const char *sink = NULL; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; + pipewire_core_t *pipewire = NULL; + const char *media = NULL; + const char *sink = NULL; - if (!pw) - return; + if (!audio || !audio->pw) + return; - if (!pw->client && spa_streq(type, PW_TYPE_INTERFACE_Client)) + pipewire = audio->pw; + + if (!pipewire->client && spa_streq(type, PW_TYPE_INTERFACE_Client)) { - pw->client = pw_registry_bind(pw->registry, - id, type, PW_VERSION_CLIENT, 0); - pw_client_add_listener(pw->client, - &pw->client_listener, - &client_events, pw); + pipewire->client = pw_registry_bind(audio->pw->registry, + id, type, PW_VERSION_CLIENT, 0); + pw_client_add_listener(audio->pw->client, + &audio->pw->client_listener, + &client_events, audio); } else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { @@ -328,23 +215,21 @@ static void registry_event_global(void *data, uint32_t id, if ((sink = spa_dict_lookup(props, PW_KEY_NODE_NAME)) != NULL) { attr.i = id; - string_list_append(pw->devicelist, sink, attr); - RARCH_LOG("[PipeWire]: Found Sink: %s\n", sink); + string_list_append(audio->pw->devicelist, sink, attr); + RARCH_LOG("[PipeWire]: Found Sink Node: %s\n", sink); } } } -#ifdef DEBUG const struct spa_dict_item *item; RARCH_DBG("[PipeWire]: Object: id:%u Type:%s/%d\n", id, type, version); spa_dict_for_each(item, props) RARCH_DBG("[PipeWire]: \t\t%s: \"%s\"\n", item->key, item->value); -#endif } static const struct pw_registry_events registry_events = { - PW_VERSION_REGISTRY_EVENTS, - .global = registry_event_global, + PW_VERSION_REGISTRY_EVENTS, + .global = registry_event_global, }; static void pipewire_free(void *data); @@ -360,46 +245,49 @@ static void *pipewire_init(const char *device, unsigned rate, uint8_t buffer[1024]; struct pw_properties *props = NULL; const char *error = NULL; - pw_t *pw = (pw_t*)calloc(1, sizeof(*pw)); + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)calloc(1, sizeof(*audio)); + pipewire_core_t *pipewire = NULL; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - if (!pw) + if (!audio) goto error; pw_init(NULL, NULL); - pw->devicelist = string_list_new(); - if (!pw->devicelist) + pipewire = audio->pw = (pipewire_core_t*)calloc(1, sizeof(*audio->pw)); + + pipewire->devicelist = string_list_new(); + if (!pipewire->devicelist) goto error; - pw->thread_loop = pw_thread_loop_new("audio_driver", NULL); - if (!pw->thread_loop) + pipewire->thread_loop = pw_thread_loop_new("audio_driver", NULL); + if (!pipewire->thread_loop) goto error; - pw->context = pw_context_new(pw_thread_loop_get_loop(pw->thread_loop), NULL, 0); - if (!pw->context) + pipewire->context = pw_context_new(pw_thread_loop_get_loop(pipewire->thread_loop), NULL, 0); + if (!pipewire->context) goto error; - if (pw_thread_loop_start(pw->thread_loop) < 0) + if (pw_thread_loop_start(pipewire->thread_loop) < 0) goto error; - pw_thread_loop_lock(pw->thread_loop); + pw_thread_loop_lock(pipewire->thread_loop); - pw->core = pw_context_connect(pw->context, NULL, 0); - if(!pw->core) - goto unlock_error; + pipewire->core = pw_context_connect(pipewire->context, NULL, 0); + if(!pipewire->core) + goto error; - if (pw_core_add_listener(pw->core, - &pw->core_listener, - &core_events, pw) < 0) - goto unlock_error; + if (pw_core_add_listener(pipewire->core, + &pipewire->core_listener, + &core_events, audio) < 0) + goto error; - pw->info.format = is_little_endian() ? SPA_AUDIO_FORMAT_F32_LE : SPA_AUDIO_FORMAT_F32_BE; - pw->info.channels = DEFAULT_CHANNELS; - set_position(DEFAULT_CHANNELS, pw->info.position); - pw->info.rate = rate; - pw->frame_size = calc_frame_size(pw->info.format, DEFAULT_CHANNELS); - pw->req = QUANTUM * rate * 1 / 2 / 100000 * pw->frame_size; + audio->info.format = is_little_endian() ? SPA_AUDIO_FORMAT_F32_LE : SPA_AUDIO_FORMAT_F32_BE; + audio->info.channels = DEFAULT_CHANNELS; + set_position(DEFAULT_CHANNELS, audio->info.position); + audio->info.rate = rate; + audio->frame_size = calc_frame_size(audio->info.format, DEFAULT_CHANNELS); + audio->req = QUANTUM * rate * 1 / 2 / 100000 * audio->frame_size; props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", @@ -412,7 +300,7 @@ static void *pipewire_init(const char *device, unsigned rate, PW_KEY_NODE_ALWAYS_PROCESS, "true", NULL); if (!props) - goto unlock_error; + goto error; if (device) pw_properties_set(props, PW_KEY_TARGET_OBJECT, device); @@ -424,18 +312,18 @@ static void *pipewire_init(const char *device, unsigned rate, pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", rate); - pw->stream = pw_stream_new(pw->core, APPNAME, props); + audio->stream = pw_stream_new(pipewire->core, APPNAME, props); - if (!pw->stream) - goto unlock_error; + if (!audio->stream) + goto error; - pw_stream_add_listener(pw->stream, &pw->stream_listener, &playback_stream_events, pw); + pw_stream_add_listener(audio->stream, &audio->stream_listener, &playback_stream_events, audio); - params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &pw->info); + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &audio->info); /* Now connect this stream. We ask that our process function is * called in a realtime thread. */ - res = pw_stream_connect(pw->stream, + res = pw_stream_connect(audio->stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | @@ -445,40 +333,33 @@ static void *pipewire_init(const char *device, unsigned rate, params, 1); if (res < 0) - goto unlock_error; + goto error; - pw->highwater_mark = MIN(RINGBUFFER_SIZE, - latency? (latency * 1000): 46440 * (uint64_t)rate / 1000000 * pw->frame_size); + audio->highwater_mark = MIN(RINGBUFFER_SIZE, + latency? (latency * 1000): 46440 * (uint64_t)rate / 1000000 * audio->frame_size); + RARCH_DBG("[PipeWire]: Bufer size: %u, RingBuffer size: %u\n", audio->highwater_mark, RINGBUFFER_SIZE); - RARCH_DBG("[PipeWire]: Bufer size: %u, RingBuffer size: %u\n", pw->highwater_mark, RINGBUFFER_SIZE); + audio->pw->registry = pw_core_get_registry(pipewire->core, PW_VERSION_REGISTRY, 0); - pw->registry = pw_core_get_registry(pw->core, PW_VERSION_REGISTRY, 0); - - spa_zero(pw->registry_listener); - pw_registry_add_listener(pw->registry, &pw->registry_listener, ®istry_events, pw); + spa_zero(pipewire->registry_listener); + pw_registry_add_listener(pipewire->registry, &pipewire->registry_listener, ®istry_events, audio); /* unlock, run the loop and wait, this will trigger the callbacks */ - if (wait_resync(pw) < 0) - { - pw_thread_loop_unlock(pw->thread_loop); - } + if (pipewire_wait_resync(audio->pw) < 0) + pw_thread_loop_unlock(pipewire->thread_loop); - if(pw_stream_get_state(pw->stream, &error) != PW_STREAM_STATE_STREAMING) - pw->is_paused = true; - pw_thread_loop_unlock(pw->thread_loop); + if(pw_stream_get_state(audio->stream, &error) != PW_STREAM_STATE_STREAMING) + audio->is_paused = true; - return pw; + pw_thread_loop_unlock(audio->pw->thread_loop); + *new_rate = audio->info.rate; -unlock_error: - if (pw->thread_loop) - pw_thread_loop_stop(pw->thread_loop); - pw_context_destroy(pw->context); - pw_thread_loop_destroy(pw->thread_loop); + return audio; error: - pipewire_free(pw); - RARCH_ERR("[PipeWire]: Failed to initialize driver\n"); + RARCH_ERR("[PipeWire]: Failed to initialize audio\n"); + pipewire_free(audio); return NULL; } @@ -486,28 +367,28 @@ static bool pipewire_start(void *data, bool is_shutdown); static ssize_t pipewire_write(void *data, const void *buf_, size_t size) { - int32_t writable; - int32_t avail; - uint32_t index; - pw_t *pw = (pw_t*)data; - const char *error = NULL; + int32_t writable; + int32_t avail; + uint32_t index; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; + const char *error = NULL; /* Workaround buggy menu code. * If a write happens while we're paused, we might never progress. */ - if (pw->is_paused) - if (!pipewire_start(pw, false)) + if (audio->is_paused) + if (!pipewire_start(audio, false)) return -1; - pw_thread_loop_lock(pw->thread_loop); + pw_thread_loop_lock(audio->pw->thread_loop); - if (pw_stream_get_state(pw->stream, &error) != PW_STREAM_STATE_STREAMING) + if (pw_stream_get_state(audio->stream, &error) != PW_STREAM_STATE_STREAMING) { /* wait for stream to become ready */ size = 0; goto unlock; } - writable = spa_ringbuffer_get_write_index(&pw->ring, &index); - avail = pw->highwater_mark - writable; + writable = spa_ringbuffer_get_write_index(&audio->ring, &index); + avail = audio->highwater_mark - writable; #if 0 /* Useful for tracing */ RARCH_DBG("[PipeWire]: Playback progress: written %d, avail %d, index %d, size %d\n", @@ -518,103 +399,98 @@ static ssize_t pipewire_write(void *data, const void *buf_, size_t size) size = avail; if (writable < 0) - RARCH_ERR("%p: underrun write:%u filled:%d\n", pw, index, writable); + RARCH_ERR("%p: underrun write:%u filled:%d\n", audio, index, writable); else { if ((uint32_t) writable + size > RINGBUFFER_SIZE) { RARCH_ERR("%p: overrun write:%u filled:%d + size:%zu > max:%u\n", - pw, index, writable, size, RINGBUFFER_SIZE); + audio, index, writable, size, RINGBUFFER_SIZE); } } - spa_ringbuffer_write_data(&pw->ring, - pw->buffer, RINGBUFFER_SIZE, + spa_ringbuffer_write_data(&audio->ring, + audio->buffer, RINGBUFFER_SIZE, index & RINGBUFFER_MASK, buf_, size); index += size; - spa_ringbuffer_write_update(&pw->ring, index); + spa_ringbuffer_write_update(&audio->ring, index); unlock: - pw_thread_loop_unlock(pw->thread_loop); + pw_thread_loop_unlock(audio->pw->thread_loop); return size; } static bool pipewire_stop(void *data) { - pw_t *pw = (pw_t*)data; - if (pw->is_paused) + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; + if (audio->is_paused) return true; - RARCH_LOG("[PipeWire]: Pausing.\n"); - - pw_thread_loop_lock(pw->thread_loop); - pw_stream_set_active(pw->stream, false); - pw_thread_loop_wait(pw->thread_loop); - pw_thread_loop_unlock(pw->thread_loop); - - return pw->is_paused; + return pipewire_set_active(audio->pw, audio, false); } static bool pipewire_start(void *data, bool is_shutdown) { - pw_t *pw = (pw_t*)data; - if (!pw->is_paused) + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; + if (!audio->is_paused) return true; - RARCH_LOG("[PipeWire]: Unpausing.\n"); - - pw_thread_loop_lock(pw->thread_loop); - pw_stream_set_active(pw->stream, true); - pw_thread_loop_wait(pw->thread_loop); - pw_thread_loop_unlock(pw->thread_loop); - - return !pw->is_paused; + return pipewire_set_active(audio->pw, audio, true); } static bool pipewire_alive(void *data) { - pw_t *pw = (pw_t*)data; - if (!pw) + const char *error = NULL; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; + if (!audio) return false; - return !pw->is_paused; + + return pw_stream_get_state(audio->stream, &error) == PW_STREAM_STATE_STREAMING; } static void pipewire_set_nonblock_state(void *data, bool state) { - pw_t *pw = (pw_t*)data; - if (pw) - pw->nonblock = state; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; + if (audio && audio->pw) + audio->pw->nonblock = state; } static void pipewire_free(void *data) { - pw_t *pw = (pw_t*)data; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; - if (!pw) + if (!audio) return pw_deinit(); - if (pw->thread_loop) - pw_thread_loop_stop(pw->thread_loop); + if (audio->pw->thread_loop) + pw_thread_loop_stop(audio->pw->thread_loop); - if (pw->client) - pw_proxy_destroy((struct pw_proxy *)pw->client); - - if (pw->registry) - pw_proxy_destroy((struct pw_proxy*)pw->registry); - - if (pw->core) + if (audio->stream) { - spa_hook_remove(&pw->core_listener); - spa_zero(pw->core_listener); - pw_core_disconnect(pw->core); + pw_stream_destroy(audio->stream); + audio->stream = NULL; } - if (pw->context) - pw_context_destroy(pw->context); + if (audio->pw->client) + pw_proxy_destroy((struct pw_proxy *)audio->pw->client); - pw_thread_loop_destroy(pw->thread_loop); + if (audio->pw->registry) + pw_proxy_destroy((struct pw_proxy*)audio->pw->registry); - free(pw); + if (audio->pw->core) + { + spa_hook_remove(&audio->pw->core_listener); + spa_zero(audio->pw->core_listener); + pw_core_disconnect(audio->pw->core); + } + + if (audio->pw->context) + pw_context_destroy(audio->pw->context); + + pw_thread_loop_destroy(audio->pw->thread_loop); + + free(audio->pw); + free(audio); pw_deinit(); } @@ -626,12 +502,10 @@ static bool pipewire_use_float(void *data) static void *pipewire_device_list_new(void *data) { - pw_t *pw = (pw_t*)data; - if (!pw) - return NULL; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; - if (pw->devicelist) - return string_list_clone(pw->devicelist); + if (audio && audio->pw->devicelist) + return string_list_clone(audio->pw->devicelist); return NULL; } @@ -640,54 +514,53 @@ static void pipewire_device_list_free(void *data, void *array_list_data) { struct string_list *s = (struct string_list*)array_list_data; - if (!s) - return; - - string_list_free(s); + if (s) + string_list_free(s); } static size_t pipewire_write_avail(void *data) { uint32_t index, written, length; - pw_t *pw = (pw_t*)data; - const char *error = NULL; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; + const char *error = NULL; - pw_thread_loop_lock(pw->thread_loop); + retro_assert(audio->pw); + pw_thread_loop_lock(audio->pw->thread_loop); - if (pw_stream_get_state(pw->stream, &error) != PW_STREAM_STATE_STREAMING) + if (pw_stream_get_state(audio->stream, &error) != PW_STREAM_STATE_STREAMING) { /* wait for stream to become ready */ length = 0; goto unlock; } - written = spa_ringbuffer_get_write_index(&pw->ring, &index); - length = pw->highwater_mark - written; - audio_driver_set_buffer_size(pw->highwater_mark); + written = spa_ringbuffer_get_write_index(&audio->ring, &index); + length = audio->highwater_mark - written; + audio_driver_set_buffer_size(audio->highwater_mark); unlock: - pw_thread_loop_unlock(pw->thread_loop); + pw_thread_loop_unlock(audio->pw->thread_loop); return length; } static size_t pipewire_buffer_size(void *data) { - pw_t *pw = (pw_t*)data; - return pw->highwater_mark; + pipewire_device_handle_t *audio = (pipewire_device_handle_t*)data; + return audio->highwater_mark; } audio_driver_t audio_pipewire = { - pipewire_init, - pipewire_write, - pipewire_stop, - pipewire_start, - pipewire_alive, - pipewire_set_nonblock_state, - pipewire_free, - pipewire_use_float, - "pipewire", - pipewire_device_list_new, - pipewire_device_list_free, - pipewire_write_avail, - pipewire_buffer_size, + pipewire_init, + pipewire_write, + pipewire_stop, + pipewire_start, + pipewire_alive, + pipewire_set_nonblock_state, + pipewire_free, + pipewire_use_float, + "pipewire", + pipewire_device_list_new, + pipewire_device_list_free, + pipewire_write_avail, + pipewire_buffer_size, }; diff --git a/audio/drivers_microphone/pipewire.c b/audio/drivers_microphone/pipewire.c new file mode 100644 index 0000000000..41e5600ef4 --- /dev/null +++ b/audio/drivers_microphone/pipewire.c @@ -0,0 +1,500 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2024 Viachaslau Khalikin + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "audio/common/pipewire.h" +#include "audio/microphone_driver.h" +#include "verbosity.h" + + +#define APPNAME "RetroArch" +#define DEFAULT_CHANNELS 1 +#define QUANTUM 1024 /* TODO: detect */ + +typedef pipewire_core_t pipewire_microphone_t; +typedef pipewire_device_handle_t pipewire_microphone_handle_t; + +static void on_stream_state_changed(void *data, + enum pw_stream_state old, enum pw_stream_state state, const char *error) +{ + pipewire_microphone_handle_t *microphone = (pipewire_microphone_handle_t*)data; + + RARCH_DBG("[PipeWire]: New state for Source Node %d : %s\n", + pw_stream_get_node_id(microphone->stream), + pw_stream_state_as_string(state)); + + switch(state) + { + case PW_STREAM_STATE_STREAMING: + microphone->is_paused = false; + pw_thread_loop_signal(microphone->pw->thread_loop, false); + break; + case PW_STREAM_STATE_ERROR: + case PW_STREAM_STATE_PAUSED: + microphone->is_paused = true; + pw_thread_loop_signal(microphone->pw->thread_loop, false); + break; + default: + break; + } +} + +static void stream_destroy(void *data) +{ + pipewire_microphone_handle_t *microphone = (pipewire_microphone_handle_t*)data; + spa_hook_remove(µphone->stream_listener); + microphone->stream = NULL; +} + +static void on_process(void *data) +{ + pipewire_microphone_handle_t *microphone = (pipewire_microphone_handle_t *)data; + void *p; + struct pw_buffer *b; + struct spa_buffer *buf; + int32_t filled; + uint32_t index, offs, n_bytes; + + assert(microphone->stream); + + b = pw_stream_dequeue_buffer(microphone->stream); + if (b == NULL) + { + RARCH_ERR("[PipeWire]: out of buffers: %s\n", strerror(errno)); + return; + } + + buf = b->buffer; + p = buf->datas[0].data; + if (p == NULL) + return; + + offs = SPA_MIN(buf->datas[0].chunk->offset, buf->datas[0].maxsize); + n_bytes = SPA_MIN(buf->datas[0].chunk->size, buf->datas[0].maxsize - offs); + + filled = spa_ringbuffer_get_write_index(µphone->ring, &index); + if (filled < 0) + RARCH_ERR("[PipeWire]: %p: underrun write:%u filled:%d\n", p, index, filled); + else + { + if ((uint32_t) filled + n_bytes > RINGBUFFER_SIZE) + RARCH_ERR("[PipeWire]: %p: overrun write:%u filled:%d + size:%u > max:%u\n", + p, index, filled, n_bytes, RINGBUFFER_SIZE); + } + spa_ringbuffer_write_data(µphone->ring, + microphone->buffer, RINGBUFFER_SIZE, + index & RINGBUFFER_MASK, + SPA_PTROFF(p, offs, void), n_bytes); + index += n_bytes; + spa_ringbuffer_write_update(µphone->ring, index); + + pw_stream_queue_buffer(microphone->stream, b); +} + +static const struct pw_stream_events capture_stream_events = { + PW_VERSION_STREAM_EVENTS, + .destroy = stream_destroy, + .state_changed = on_stream_state_changed, + .process = on_process +}; + +static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) +{ + pipewire_microphone_t *pipewire = data; + + RARCH_ERR("[PipeWire]: error id:%u seq:%d res:%d (%s): %s\n", + id, seq, res, spa_strerror(res), message); + + /* stop and exit the thread loop */ + pw_thread_loop_signal(pipewire->thread_loop, false); +} + +static void on_core_done(void *data, uint32_t id, int seq) +{ + pipewire_microphone_t *pipewire = (pipewire_microphone_t*)data; + + retro_assert(id == PW_ID_CORE); + + pipewire->last_seq = seq; + if (pipewire->pending_seq == seq) + { + /* stop and exit the thread loop */ + pw_thread_loop_signal(pipewire->thread_loop, false); + } +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .done = on_core_done, + .error = on_core_error, +}; + +static void registry_event_global(void *data, uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props) +{ + union string_list_elem_attr attr; + pipewire_microphone_t *pipewire = (pipewire_microphone_t*)data; + const char *media = NULL; + const char *sink = NULL; + + if (!pipewire) + return; + + if (spa_streq(type, PW_TYPE_INTERFACE_Node)) + { + media = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); + if (media && strcmp(media, "Audio/Source") == 0) + { + if ((sink = spa_dict_lookup(props, PW_KEY_NODE_NAME)) != NULL) + { + attr.i = id; + string_list_append(pipewire->devicelist, sink, attr); + RARCH_LOG("[PipeWire]: Found Source Node: %s\n", sink); + } + } + } +} + +static const struct pw_registry_events registry_events = { + PW_VERSION_REGISTRY_EVENTS, + .global = registry_event_global, +}; + +static void pipewire_microphone_free(void *driver_context); + +static void *pipewire_microphone_init(void) +{ + int res; + uint64_t buf_samples; + const struct spa_pod *params[1]; + uint8_t buffer[1024]; + struct pw_properties *props = NULL; + const char *error = NULL; + pipewire_microphone_t *pipewire = (pipewire_microphone_t*)calloc(1, sizeof(*pipewire)); + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + if (!pipewire) + goto error; + + pw_init(NULL, NULL); + + pipewire->devicelist = string_list_new(); + if (!pipewire->devicelist) + goto error; + + pipewire->thread_loop = pw_thread_loop_new("microphone_driver", NULL); + if (!pipewire->thread_loop) + goto error; + + pipewire->context = pw_context_new(pw_thread_loop_get_loop(pipewire->thread_loop), NULL, 0); + if (!pipewire->context) + goto error; + + if (pw_thread_loop_start(pipewire->thread_loop) < 0) + goto error; + + pw_thread_loop_lock(pipewire->thread_loop); + + pipewire->core = pw_context_connect(pipewire->context, NULL, 0); + if(!pipewire->core) + goto error; + + if (pw_core_add_listener(pipewire->core, + &pipewire->core_listener, + &core_events, pipewire) < 0) + goto error; + + pipewire->registry = pw_core_get_registry(pipewire->core, PW_VERSION_REGISTRY, 0); + + spa_zero(pipewire->registry_listener); + pw_registry_add_listener(pipewire->registry, &pipewire->registry_listener, ®istry_events, pipewire); + + if (pipewire_wait_resync(pipewire) < 0) + pw_thread_loop_unlock(pipewire->thread_loop); + + + pw_thread_loop_unlock(pipewire->thread_loop); + + return pipewire; + +error: + RARCH_ERR("[PipeWire]: Failed to initialize microphone\n"); + pipewire_microphone_free(pipewire); + return NULL; +} + +static void pipewire_microphone_close_mic(void *driver_context, void *microphone_context); +static void pipewire_microphone_free(void *driver_context) +{ + pipewire_microphone_t *pipewire = (pipewire_microphone_t*)driver_context; + + if (!pipewire) + return pw_deinit(); + + if (pipewire->thread_loop) + pw_thread_loop_stop(pipewire->thread_loop); + + if (pipewire->client) + pw_proxy_destroy((struct pw_proxy *)pipewire->client); + + if (pipewire->registry) + pw_proxy_destroy((struct pw_proxy*)pipewire->registry); + + if (pipewire->core) + { + spa_hook_remove(&pipewire->core_listener); + spa_zero(pipewire->core_listener); + pw_core_disconnect(pipewire->core); + } + + if (pipewire->context) + pw_context_destroy(pipewire->context); + + pw_thread_loop_destroy(pipewire->thread_loop); + + free(pipewire); + pw_deinit(); +} + +static bool pipewire_microphone_start_mic(void *driver_context, void *microphone_context); +static int pipewire_microphone_read(void *driver_context, void *microphone_context, void *buf_, size_t size_) +{ + int32_t readable; + uint32_t index; + const char *error = NULL; + pipewire_microphone_t *pipewire = (pipewire_microphone_t*)driver_context; + pipewire_microphone_handle_t *microphone = (pipewire_microphone_handle_t*)microphone_context; + + pw_thread_loop_lock(pipewire->thread_loop); + if (pw_stream_get_state(microphone->stream, &error) != PW_STREAM_STATE_STREAMING) + goto unlock; + + /* get no of available bytes to read data from buffer */ + readable = spa_ringbuffer_get_read_index(µphone->ring, &index); + + if (readable < (int32_t)size_) + size_ = readable; + + spa_ringbuffer_read_data(µphone->ring, + microphone->buffer, RINGBUFFER_SIZE, + index & RINGBUFFER_MASK, buf_, size_); + index += size_; + spa_ringbuffer_read_update(µphone->ring, index); + +unlock: + pw_thread_loop_unlock(pipewire->thread_loop); + return size_; +} + +static bool pipewire_microphone_mic_alive(const void *driver_context, const void *microphone_context) +{ + const char *error = NULL; + pipewire_microphone_handle_t *microphone = (pipewire_microphone_handle_t*)microphone_context; + (void)driver_context; + + if (!microphone) + return false; + + return pw_stream_get_state(microphone->stream, &error) == PW_STREAM_STATE_STREAMING; +} + +static void pipewire_microphone_set_nonblock_state(void *driver_context, bool nonblock) +{ + pipewire_microphone_t *pipewire = (pipewire_microphone_t*)driver_context; + if (pipewire) + pipewire->nonblock = nonblock; +} + +static struct string_list *pipewire_microphone_device_list_new(const void *driver_context) +{ + pipewire_microphone_t *pipewire = (pipewire_microphone_t*)driver_context; + + if (pipewire && pipewire->devicelist) + return string_list_clone(pipewire->devicelist); + + return NULL; +} + +static void pipewire_microphone_device_list_free(const void *driver_context, struct string_list *devices) +{ + (void)driver_context; + if (devices) + string_list_free(devices); +} + +static void *pipewire_microphone_open_mic(void *driver_context, + const char *device, + unsigned rate, + unsigned latency, + unsigned *new_rate) +{ + int res; + uint64_t buf_samples; + const struct spa_pod *params[1]; + uint8_t buffer[1024]; + struct pw_properties *props = NULL; + const char *error = NULL; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + pipewire_microphone_handle_t *microphone = calloc(1, sizeof(pipewire_microphone_handle_t)); + + retro_assert(driver_context); + + if (!microphone) + goto error; + + microphone->pw = (pipewire_microphone_t*)driver_context; + + pw_thread_loop_lock(microphone->pw->thread_loop); + + microphone->info.format = is_little_endian() ? SPA_AUDIO_FORMAT_F32_LE : SPA_AUDIO_FORMAT_F32_BE; + microphone->info.channels = DEFAULT_CHANNELS; + set_position(DEFAULT_CHANNELS, microphone->info.position); + microphone->info.rate = rate; + microphone->frame_size = calc_frame_size(microphone->info.format, DEFAULT_CHANNELS); + + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Game", + PW_KEY_NODE_NAME, APPNAME, + PW_KEY_NODE_DESCRIPTION, APPNAME, + PW_KEY_APP_NAME, APPNAME, + PW_KEY_APP_ID, APPNAME, + PW_KEY_APP_ICON_NAME, APPNAME, + PW_KEY_NODE_ALWAYS_PROCESS, "true", + NULL); + if (!props) + goto unlock_error; + + if (device) + pw_properties_set(props, PW_KEY_TARGET_OBJECT, device); + + buf_samples = QUANTUM * rate * 3 / 4 / 100000; + + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%" PRIu64 "/%u", + buf_samples, rate); + + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", rate); + + microphone->stream = pw_stream_new(microphone->pw->core, APPNAME, props); + + if (!microphone->stream) + goto unlock_error; + + pw_stream_add_listener(microphone->stream, µphone->stream_listener, &capture_stream_events, microphone); + + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, µphone->info); + + /* Now connect this stream. We ask that our process function is + * called in a realtime thread. */ + res = pw_stream_connect(microphone->stream, + PW_DIRECTION_INPUT, + PW_ID_ANY, + PW_STREAM_FLAG_AUTOCONNECT | + PW_STREAM_FLAG_INACTIVE | + PW_STREAM_FLAG_MAP_BUFFERS | + PW_STREAM_FLAG_RT_PROCESS, + params, 1); + + if (res < 0) + goto unlock_error; + + pw_thread_loop_wait(microphone->pw->thread_loop); + if(pw_stream_get_state(microphone->stream, &error) != PW_STREAM_STATE_STREAMING) + microphone->is_paused = true; + + pw_thread_loop_unlock(microphone->pw->thread_loop); + *new_rate = microphone->info.rate; + + return microphone; + +unlock_error: + pw_thread_loop_unlock(microphone->pw->thread_loop); +error: + RARCH_ERR("[PipeWire]: Failed to initialize microphone...\n"); + pipewire_microphone_close_mic(microphone->pw, microphone); + return NULL; +} + +static void pipewire_microphone_close_mic(void *driver_context, void *microphone_context) +{ + pipewire_microphone_t *pipewire = (pipewire_microphone_t*)driver_context; + pipewire_microphone_handle_t *microphone = (pipewire_microphone_handle_t*)microphone_context; + + if (pipewire && microphone) + { + pw_thread_loop_lock(pipewire->thread_loop); + pw_stream_destroy(microphone->stream); + microphone->stream = NULL; + pw_thread_loop_unlock(pipewire->thread_loop); + } +} + +static bool pipewire_microphone_start_mic(void *driver_context, void *microphone_context) +{ + pipewire_microphone_t *pipewire = (pipewire_microphone_t*)driver_context; + pipewire_microphone_handle_t *microphone = (pipewire_microphone_handle_t*)microphone_context; + + if (!microphone->is_paused) + return true; + + return pipewire_set_active(pipewire, microphone, true); +} + +static bool pipewire_microphone_stop_mic(void *driver_context, void *microphone_context) +{ + pipewire_microphone_t *pipewire = (pipewire_microphone_t*)driver_context; + pipewire_microphone_handle_t *microphone = (pipewire_microphone_handle_t*)microphone_context; + if (microphone->is_paused) + return true; + + return pipewire_set_active(pipewire, microphone, false); +} + +static bool pipewire_microphone_mic_use_float(const void *driver_context, const void *microphone_context) +{ + (void)driver_context; + (void)microphone_context; + return true; +} + +microphone_driver_t microphone_pipewire = { + pipewire_microphone_init, + pipewire_microphone_free, + pipewire_microphone_read, + pipewire_microphone_set_nonblock_state, + "pipewire", + pipewire_microphone_device_list_new, + pipewire_microphone_device_list_free, + pipewire_microphone_open_mic, + pipewire_microphone_close_mic, + pipewire_microphone_mic_alive, + pipewire_microphone_start_mic, + pipewire_microphone_stop_mic, + pipewire_microphone_mic_use_float +}; diff --git a/audio/microphone_driver.c b/audio/microphone_driver.c index 3bd13ae4d4..874704a3e8 100644 --- a/audio/microphone_driver.c +++ b/audio/microphone_driver.c @@ -57,6 +57,9 @@ microphone_driver_t *microphone_drivers[] = { #endif #ifdef HAVE_SDL2 µphone_sdl, /* Microphones are not supported in SDL 1 */ +#endif +#ifdef HAVE_PIPEWIRE + µphone_pipewire, #endif µphone_null, NULL, diff --git a/audio/microphone_driver.h b/audio/microphone_driver.h index d9304b4812..3997911b1e 100644 --- a/audio/microphone_driver.h +++ b/audio/microphone_driver.h @@ -641,6 +641,11 @@ extern microphone_driver_t microphone_sdl; */ extern microphone_driver_t microphone_wasapi; +/** + * The PipeWire-backed microphone driver. + */ +extern microphone_driver_t microphone_pipewire; + /** * @return Pointer to the global microphone driver state. */ diff --git a/configuration.c b/configuration.c index 13f81e3741..5d7c26e3b2 100644 --- a/configuration.c +++ b/configuration.c @@ -159,6 +159,7 @@ enum microphone_driver_enum MICROPHONE_ALSATHREAD, MICROPHONE_SDL2, MICROPHONE_WASAPI, + MICROPHONE_PIPEWIRE, MICROPHONE_NULL }; @@ -568,6 +569,8 @@ static const enum microphone_driver_enum MICROPHONE_DEFAULT_DRIVER = MICROPHONE_ static const enum microphone_driver_enum MICROPHONE_DEFAULT_DRIVER = MICROPHONE_ALSATHREAD; #elif defined(HAVE_ALSA) static const enum microphone_driver_enum MICROPHONE_DEFAULT_DRIVER = MICROPHONE_ALSA; +#elif defined(HAVE_PIPEWIRE) +static const enum microphone_driver_enum MICROPHONE_DEFAULT_DRIVER = MICROPHONE_PIPEWIRE; #elif defined(HAVE_SDL2) /* The default fallback driver is SDL2, if available. */ static const enum microphone_driver_enum MICROPHONE_DEFAULT_DRIVER = MICROPHONE_SDL2; @@ -983,6 +986,8 @@ const char *config_get_default_microphone(void) return "alsa"; case MICROPHONE_ALSATHREAD: return "alsathread"; + case MICROPHONE_PIPEWIRE: + return "pipewire"; case MICROPHONE_WASAPI: return "wasapi"; case MICROPHONE_SDL2: diff --git a/griffin/griffin.c b/griffin/griffin.c index 6decf2f8b9..570746d4d4 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -908,6 +908,11 @@ AUDIO #ifdef HAVE_PIPEWIRE #include "../audio/drivers/pipewire.c" +#include "../audio/common/pipewire.c" + +#ifdef HAVE_MICROPHONE +#include "../audio/drivers_microphone/pipewire.c" +#endif #endif #ifdef HAVE_ALSA