diff --git a/AUTHORS.h b/AUTHORS.h index c7f18ccc72..730e2c86e6 100644 --- a/AUTHORS.h +++ b/AUTHORS.h @@ -481,6 +481,7 @@ vaguerant Val Packett (valpackett) Valerio Proietti (kamicane) vgmoose +Viachaslau Khalikn (viachaslavic) Vicki Pfau (endrift) Vicky C Lau (vickychenglau) vin (suseme) diff --git a/CHANGES.md b/CHANGES.md index 6b97a80902..59d7e0249c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ # Future - 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 - 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 155faf7938..f337321f98 100644 --- a/Makefile.common +++ b/Makefile.common @@ -921,6 +921,12 @@ ifeq ($(HAVE_PULSE), 1) DEF_FLAGS += $(PULSE_CFLAGS) endif +ifeq ($(HAVE_PIPEWIRE), 1) + OBJ += audio/drivers/pipewire.o + LIBS += $(PIPEWIRE_LIBS) + DEF_FLAGS += $(PIPEWIRE_CFLAGS) +endif + ifeq ($(HAVE_OSS_LIB), 1) LIBS += -lossaudio endif @@ -2192,7 +2198,7 @@ ifeq ($(HAVE_NETWORKING), 1) ifneq ($(findstring Linux,$(OS)),) HAVE_CLOUDSYNC = 1 endif - + ifneq ($(findstring Win,$(OS)),) HAVE_CLOUDSYNC = 1 endif diff --git a/audio/audio_driver.c b/audio/audio_driver.c index 8c3ba0aac6..7efa920007 100644 --- a/audio/audio_driver.c +++ b/audio/audio_driver.c @@ -132,6 +132,9 @@ audio_driver_t *audio_drivers[] = { #ifdef HAVE_PULSE &audio_pulse, #endif +#ifdef HAVE_PIPEWIRE + &audio_pipewire, +#endif #if defined(__PSL1GHT__) || defined(__PS3__) &audio_ps3, #endif diff --git a/audio/audio_driver.h b/audio/audio_driver.h index 3ef284d1ce..4dcb6a6593 100644 --- a/audio/audio_driver.h +++ b/audio/audio_driver.h @@ -420,6 +420,7 @@ extern audio_driver_t audio_jack; extern audio_driver_t audio_sdl; extern audio_driver_t audio_xa; extern audio_driver_t audio_pulse; +extern audio_driver_t audio_pipewire; extern audio_driver_t audio_dsound; extern audio_driver_t audio_wasapi; extern audio_driver_t audio_coreaudio; diff --git a/audio/drivers/pipewire.c b/audio/drivers/pipewire.c new file mode 100644 index 0000000000..f1bbb8cecd --- /dev/null +++ b/audio/drivers/pipewire.c @@ -0,0 +1,679 @@ +/* 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 +#include + +#include "../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; + +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; +} + +static void on_process(void *data) +{ + pw_t *pw = (pw_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); + + if ((b = pw_stream_dequeue_buffer(pw->stream)) == NULL) + { + RARCH_WARN("[PipeWire]: Out of buffers: %s", strerror(errno)); + return; + } + + buf = b->buffer; + p = buf->datas[0].data; + if (p == NULL) + return; + + /* calculate the total no of bytes to read data from buffer */ + req = b->requested * pw->frame_size; + + if (req == 0) + req = pw->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); + + if (avail < (int32_t)n_bytes) + n_bytes = avail; + + spa_ringbuffer_read_data(&pw->ring, + pw->buffer, RINGBUFFER_SIZE, + index & RINGBUFFER_MASK, p, n_bytes); + + index += n_bytes; + spa_ringbuffer_read_update(&pw->ring, index); + + buf->datas[0].chunk->offset = 0; + buf->datas[0].chunk->stride = pw->frame_size; + buf->datas[0].chunk->size = n_bytes; + + /* queue the buffer for playback */ + pw_stream_queue_buffer(pw->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; + + RARCH_DBG("[PipeWire]: New state for Node %d : %s\n", + pw_stream_get_node_id(pw->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); + break; + case PW_STREAM_STATE_ERROR: + case PW_STREAM_STATE_PAUSED: + pw->is_paused = true; + pw_thread_loop_signal(pw->thread_loop, false); + break; + default: + break; + } +} + +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, +}; + +static int wait_resync(pw_t *pw) +{ + retro_assert(pw != NULL); + + int res; + pw->pending_seq = pw_core_sync(pw->core, PW_ID_CORE, pw->pending_seq); + + while (true) + { + 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; + const struct spa_dict_item *item; + + RARCH_DBG("[PipeWire]: client: id:%u\n", info->id); + RARCH_DBG("[PipeWire]: \tprops:\n"); + 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); +} + +static const struct pw_client_events client_events = { + 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; + + 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); +} + +static void on_core_done(void *data, uint32_t id, int seq) +{ + pw_t *pw = (pw_t*)data; + + retro_assert(id == PW_ID_CORE); + + pw->last_seq = seq; + if (pw->pending_seq == seq) + { + /* stop and exit the thread loop */ + pw_thread_loop_signal(pw->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) +{ + pw_t *pw = (pw_t*)data; + union string_list_elem_attr attr; + const char* media = NULL; + const char* sink = NULL; + + if (!pw) + return; + + if (!pw->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); + } + else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) + { + const char* media = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); + if (media && strcmp(media, "Audio/Sink") == 0) + { + sink = spa_dict_lookup(props, PW_KEY_NODE_NAME); + attr.i = id; + string_list_append(pw->devicelist, sink, attr); + RARCH_LOG("[PipeWire]: Found Sink: %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, +}; + +static void pipewire_free(void *data); + +static void *pipewire_init(const char *device, unsigned rate, + unsigned latency, + unsigned block_frames, + unsigned *new_rate) +{ + int res; + const struct spa_pod *params[1]; + uint8_t buffer[1024]; + struct pw_properties *props = NULL; + const char *error = NULL; + pw_t *pw = (pw_t*)calloc(1, sizeof(*pw)); + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + uint64_t buf_samples; + + if (!pw) + goto error; + + pw_init(NULL, NULL); + + pw->devicelist = string_list_new(); + if (!pw->devicelist) + goto error; + + pw->thread_loop = pw_thread_loop_new("audio_driver", NULL); + if (!pw->thread_loop) + goto error; + + pw->context = pw_context_new(pw_thread_loop_get_loop(pw->thread_loop), NULL, 0); + if (!pw->context) + goto error; + + if (pw_thread_loop_start(pw->thread_loop) < 0) + goto error; + + pw_thread_loop_lock(pw->thread_loop); + + pw->core = pw_context_connect(pw->context, NULL, 0); + if(!pw->core) + goto unlock_error; + + if (pw_core_add_listener(pw->core, + &pw->core_listener, + &core_events, pw) < 0) + goto unlock_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; + + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Playback", + 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); + + pw->stream = pw_stream_new(pw->core, APPNAME, props); + + if (!pw->stream) + goto unlock_error; + + pw_stream_add_listener(pw->stream, &pw->stream_listener, &playback_stream_events, pw); + + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &pw->info); + + /* Now connect this stream. We ask that our process function is + * called in a realtime thread. */ + res = pw_stream_connect(pw->stream, + PW_DIRECTION_OUTPUT, + 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->highwater_mark = MIN(RINGBUFFER_SIZE, + latency? (latency * 1000): 46440 * (uint64_t)rate / 1000000 * pw->frame_size); + + RARCH_DBG("[PipeWire]: Bufer size: %u, RingBuffer size: %u\n", pw->highwater_mark, RINGBUFFER_SIZE); + + 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); + + /* unlock, run the loop and wait, this will trigger the callbacks */ + if (wait_resync(pw) < 0) + { + pw_thread_loop_unlock(pw->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); + + return pw; + +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); + +error: + pipewire_free(pw); + RARCH_ERR("[PipeWire]: Failed to initialize driver\n"); + return NULL; +} + +static bool pipewire_start(void *data, bool is_shutdown); + +static ssize_t pipewire_write(void *data, const void *buf_, size_t size) +{ + pw_t *pw = (pw_t*)data; + const char *error = NULL; + int32_t writable; + int32_t avail; + uint32_t index; + + /* 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)) + return -1; + + pw_thread_loop_lock(pw->thread_loop); + + if (pw_stream_get_state(pw->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; + +#if 0 /* Useful for tracing */ + RARCH_DBG("[PipeWire]: Playback progress: written %d, avail %d, index %d, size %d\n", + writable, avail, index, size); +#endif + + if (size > (size_t)avail) + size = avail; + + if (writable < 0) + RARCH_ERR("%p: underrun write:%u filled:%d\n", pw, 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); + } + } + + spa_ringbuffer_write_data(&pw->ring, + pw->buffer, RINGBUFFER_SIZE, + index & RINGBUFFER_MASK, buf_, size); + index += size; + spa_ringbuffer_write_update(&pw->ring, index); + +unlock: + pw_thread_loop_unlock(pw->thread_loop); + return size; +} + +static bool pipewire_stop(void *data) +{ + pw_t *pw = (pw_t*)data; + if (pw->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; +} + +static bool pipewire_start(void *data, bool is_shutdown) +{ + pw_t *pw = (pw_t*)data; + if (!pw->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; +} + +static bool pipewire_alive(void *data) +{ + pw_t *pw = (pw_t*)data; + + if (!pw) + return false; + return !pw->is_paused; +} + +static void pipewire_set_nonblock_state(void *data, bool state) +{ + pw_t *pw = (pw_t*)data; + if (pw) + pw->nonblock = state; +} + +static void pipewire_free(void *data) +{ + pw_t *pw = (pw_t*)data; + + if (!pw) + return pw_deinit(); + + if (pw->thread_loop) + pw_thread_loop_stop(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) + { + spa_hook_remove(&pw->core_listener); + spa_zero(pw->core_listener); + pw_core_disconnect(pw->core); + } + + if (pw->context) + pw_context_destroy(pw->context); + + pw_thread_loop_destroy(pw->thread_loop); + + free(pw); + pw_deinit(); +} + +static bool pipewire_use_float(void *data) +{ + (void)data; + return true; +} + +static void *pipewire_device_list_new(void *data) +{ + pw_t *pw = (pw_t*)data; + if (!pw) + return NULL; + + if (pw->devicelist) + return string_list_clone(pw->devicelist); + + return NULL; +} + +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); +} + +static size_t pipewire_write_avail(void *data) +{ + uint32_t index, written, length; + pw_t *pw = (pw_t*)data; + const char *error = NULL; + + pw_thread_loop_lock(pw->thread_loop); + + if (pw_stream_get_state(pw->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); + +unlock: + pw_thread_loop_unlock(pw->thread_loop); + return length; +} + +static size_t pipewire_buffer_size(void *data) +{ + pw_t *pw = (pw_t*)data; + return pw->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, +}; diff --git a/configuration.c b/configuration.c index 2f604d05ec..f187e98be4 100644 --- a/configuration.c +++ b/configuration.c @@ -149,6 +149,7 @@ enum audio_driver_enum AUDIO_PS2, AUDIO_CTR, AUDIO_SWITCH, + AUDIO_PIPEWIRE, AUDIO_NULL }; @@ -509,6 +510,8 @@ static const enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_ALSA; static const enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_AL; #elif defined(HAVE_PULSE) static const enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_PULSE; +#elif defined(HAVE_PIPEWIRE) +static const enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_PIPEWIRE; #elif defined(HAVE_ALSA) && defined(HAVE_THREADS) static const enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_ALSATHREAD; #elif defined(HAVE_ALSA) @@ -921,6 +924,8 @@ const char *config_get_default_audio(void) return "xaudio"; case AUDIO_PULSE: return "pulse"; + case AUDIO_PIPEWIRE: + return "pipewire"; case AUDIO_EXT: return "ext"; case AUDIO_XENON360: diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index c38c8881b0..05b4672494 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -355,6 +355,8 @@ int msg_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_WASAPI), len); else if (string_is_equal(lbl, msg_hash_to_str(MENU_ENUM_LABEL_AUDIO_DRIVER_PULSE))) strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_PULSE), len); + else if (string_is_equal(lbl, msg_hash_to_str(MENU_ENUM_LABEL_AUDIO_DRIVER_PIPEWIRE))) + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_PIPEWIRE), len); else if (string_is_equal(lbl, msg_hash_to_str(MENU_ENUM_LABEL_AUDIO_DRIVER_JACK))) strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_JACK), len); else diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 03bf55f05a..f81441b8d8 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -810,6 +810,10 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_PULSEAUDIO_SUPPORT, "PulseAudio Support" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_PIPEWIRE_SUPPORT, + "PipeWire Support" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT, "CoreAudio Support" @@ -1765,6 +1769,10 @@ MSG_HASH( MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_PULSE, "PulseAudio driver. If the system uses PulseAudio, make sure to use this driver instead of e.g. ALSA." ) +MSG_HASH( + MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_PIPEWIRE, + "PipeWire driver. If the system uses PipeWire, make sure to use this driver instead of e.g. PulseAudio." + ) MSG_HASH( MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_JACK, "Jack Audio Connection Kit driver." diff --git a/msg_hash.h b/msg_hash.h index 9e492fec86..10a118fd9e 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -585,7 +585,7 @@ enum msg_hash_enums MSG_ADD_TO_PLAYLIST_FAILED, MENU_ENUM_LABEL_ADD_ENTRY_TO_PLAYLIST, MENU_ENUM_LABEL_DEFERRED_ADD_TO_PLAYLIST_LIST, - MENU_LABEL(ADD_TO_PLAYLIST), + MENU_LABEL(ADD_TO_PLAYLIST), MENU_LABEL(CREATE_NEW_PLAYLIST), MENU_LABEL(MENU_XMB_ANIMATION_HORIZONTAL_HIGHLIGHT), @@ -2629,6 +2629,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_AUDIO_DRIVER_WASAPI, MENU_ENUM_LABEL_AUDIO_DRIVER_XAUDIO, MENU_ENUM_LABEL_AUDIO_DRIVER_PULSE, + MENU_ENUM_LABEL_AUDIO_DRIVER_PIPEWIRE, MENU_ENUM_LABEL_AUDIO_DRIVER_EXT, MENU_ENUM_LABEL_AUDIO_DRIVER_XENON360, MENU_ENUM_LABEL_AUDIO_DRIVER_PS3, @@ -2659,6 +2660,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_WASAPI, MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_XAUDIO, MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_PULSE, + MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_PIPEWIRE, MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_EXT, MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_XENON360, MENU_ENUM_LABEL_HELP_AUDIO_DRIVER_PS3, @@ -3528,6 +3530,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_ROARAUDIO_SUPPORT, MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_JACK_SUPPORT, MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_PULSEAUDIO_SUPPORT, + MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_PIPEWIRE_SUPPORT, MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_DSOUND_SUPPORT, MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_WASAPI_SUPPORT, MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_XAUDIO2_SUPPORT, diff --git a/qb/config.libs.sh b/qb/config.libs.sh index 79584792e7..6d5e9afab4 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -269,6 +269,7 @@ check_pkgconf RSOUND rsound 1.1 check_pkgconf ROAR libroar 1.0.12 check_val '' JACK -ljack '' jack 0.120.1 '' false check_val '' PULSE -lpulse '' libpulse '' '' false +check_val '' PIPEWIRE -lpipewire-0.3 '' libpipewire-0.3 '' '' false check_val '' SDL -lSDL SDL sdl 1.2.10 '' false check_val '' SDL2 -lSDL2 SDL2 sdl2 2.0.0 '' false diff --git a/qb/config.params.sh b/qb/config.params.sh index 1f1ea15637..963a755ec3 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -124,6 +124,7 @@ HAVE_AL=no # OpenAL support HAVE_JACK=auto # JACK support HAVE_COREAUDIO=auto # CoreAudio support HAVE_COREAUDIO3=no # CoreAudio3 support +HAVE_PIPEWIRE=auto # PipeWire support HAVE_PULSE=auto # PulseAudio support C89_PULSE=no HAVE_FREETYPE=auto # FreeType support