diff --git a/CMakeLists.txt b/CMakeLists.txt index de86f90c1..56646f9b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -356,7 +356,6 @@ set(SDL_VENDOR_INFO "" CACHE STRING "Vendor name and/or version to add to SDL_RE set(SDL_OSS OFF) set(SDL_ALSA OFF) set(SDL_JACK OFF) -set(SDL_PIPEWIRE OFF) set(SDL_SNDIO OFF) cmake_dependent_option(SDL_SHARED "Build a shared version of the library" ${SDL_SHARED_DEFAULT} ${SDL_SHARED_AVAILABLE} OFF) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index e5faeafce..2baa2660b 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -634,7 +634,16 @@ void SDL_QuitAudio(void) } - +void SDL_AudioThreadFinalize(SDL_AudioDevice *device) +{ + if (SDL_AtomicGet(&device->condemned)) { + if (device->thread) { + SDL_DetachThread(device->thread); // no one is waiting for us, just detach ourselves. + device->thread = NULL; + } + DestroyPhysicalAudioDevice(device); + } +} // Output device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort. @@ -719,11 +728,7 @@ void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device) // Wait for the audio to drain. !!! FIXME: don't bother waiting if device is lost. SDL_Delay(((samples * 1000) / device->spec.freq) * 2); current_audio.impl.ThreadDeinit(device); - if (SDL_AtomicGet(&device->condemned)) { - SDL_DetachThread(device->thread); // no one is waiting for us, just detach ourselves. - device->thread = NULL; - DestroyPhysicalAudioDevice(device); - } + SDL_AudioThreadFinalize(device); } static int SDLCALL OutputAudioThread(void *devicep) // thread entry point @@ -810,9 +815,7 @@ void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device) SDL_assert(device->iscapture); current_audio.impl.FlushCapture(device); current_audio.impl.ThreadDeinit(device); - if (SDL_AtomicGet(&device->condemned)) { - DestroyPhysicalAudioDevice(device); - } + SDL_AudioThreadFinalize(device); } static int SDLCALL CaptureAudioThread(void *devicep) // thread entry point diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index c318f55ab..a72780549 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -99,6 +99,7 @@ extern void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device); extern void SDL_CaptureAudioThreadSetup(SDL_AudioDevice *device); extern SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device); extern void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device); +extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device); typedef struct SDL_AudioDriverImpl { diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c index 1d1b15625..5309e2131 100644 --- a/src/audio/pipewire/SDL_pipewire.c +++ b/src/audio/pipewire/SDL_pipewire.c @@ -327,7 +327,11 @@ static void io_list_remove(Uint32 id) spa_list_remove(&n->link); if (hotplug_events_enabled) { - SDL_RemoveAudioDevice(n->is_capture, PW_ID_TO_HANDLE(id)); + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle(PW_ID_TO_HANDLE(id)); + if (device) { + SDL_UnlockMutex(device->lock); // AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. + SDL_AudioDeviceDisconnected(device); + } } SDL_free(n); @@ -383,6 +387,7 @@ static struct io_node *io_list_get_by_id(Uint32 id) return NULL; } +#if 0 static struct io_node *io_list_get_by_path(char *path) { struct io_node *n, *temp; @@ -393,6 +398,7 @@ static struct io_node *io_list_get_by_path(char *path) } return NULL; } +#endif static void node_object_destroy(struct node_object *node) { @@ -833,7 +839,7 @@ static void hotplug_loop_destroy(void) } } -static void PIPEWIRE_DetectDevices(void) +static void PIPEWIRE_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { struct io_node *io; @@ -848,7 +854,10 @@ static void PIPEWIRE_DetectDevices(void) io_list_sort(); spa_list_for_each (io, &hotplug_io_list, link) { - SDL_AddAudioDevice(io->is_capture, io->name, &io->spec, PW_ID_TO_HANDLE(io->id)); + SDL_AudioDevice *device = SDL_AddAudioDevice(io->is_capture, io->name, &io->spec, PW_ID_TO_HANDLE(io->id)); +// !!! FIXME: obviously no +if (!io->is_capture && !*default_output) { *default_output = device; } +if (io->is_capture && !*default_capture) { *default_capture = device; } } hotplug_events_enabled = SDL_TRUE; @@ -936,167 +945,115 @@ static void initialize_spa_info(const SDL_AudioSpec *spec, struct spa_audio_info } } -static void output_callback(void *data) +static Uint8 *PIPEWIRE_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - struct pw_buffer *pw_buf; - struct spa_buffer *spa_buf; - Uint8 *dst; + // See if a buffer is available. If this returns NULL, SDL_OutputAudioThreadIterate will return SDL_FALSE, but since we own the thread, it won't kill playback. + // !!! FIXME: It's not clear to me if this ever returns NULL or if this was just defensive coding. - SDL_AudioDevice *_this = (SDL_AudioDevice *)data; - struct pw_stream *stream = _this->hidden->stream; - - /* Shutting down, don't do anything */ - if (SDL_AtomicGet(&_this->shutdown)) { - return; - } - - /* See if a buffer is available */ - pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); + struct pw_stream *stream = device->hidden->stream; + struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); if (pw_buf == NULL) { - return; + return NULL; } - spa_buf = pw_buf->buffer; - + struct spa_buffer *spa_buf = pw_buf->buffer; if (spa_buf->datas[0].data == NULL) { - return; + PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + return NULL; } - /* - * If the device is disabled, write silence to the stream buffer - * and run the callback with the work buffer to keep the callback - * firing regularly in case the audio is being used as a timer. - */ - SDL_LockMutex(_this->mixer_lock); - if (!SDL_AtomicGet(&_this->paused)) { - if (SDL_AtomicGet(&_this->enabled)) { - dst = spa_buf->datas[0].data; - } else { - dst = _this->work_buffer; - SDL_memset(spa_buf->datas[0].data, _this->spec.silence, _this->spec.size); - } - - if (!_this->stream) { - _this->callbackspec.callback(_this->callbackspec.userdata, dst, _this->callbackspec.size); - } else { - int got; - - /* Fire the callback until we have enough to fill a buffer */ - while (SDL_GetAudioStreamAvailable(_this->stream) < _this->spec.size) { - _this->callbackspec.callback(_this->callbackspec.userdata, _this->work_buffer, _this->callbackspec.size); - SDL_PutAudioStreamData(_this->stream, _this->work_buffer, _this->callbackspec.size); - } - - got = SDL_GetAudioStreamData(_this->stream, dst, _this->spec.size); - SDL_assert(got == _this->spec.size); - } - } else { - SDL_memset(spa_buf->datas[0].data, _this->spec.silence, _this->spec.size); - } - SDL_UnlockMutex(_this->mixer_lock); + device->hidden->pw_buf = pw_buf; + return (Uint8 *) spa_buf->datas[0].data; +} +static void PIPEWIRE_PlayDevice(SDL_AudioDevice *device, int buffer_size) +{ + struct pw_stream *stream = device->hidden->stream; + struct pw_buffer *pw_buf = device->hidden->pw_buf; + struct spa_buffer *spa_buf = pw_buf->buffer; spa_buf->datas[0].chunk->offset = 0; - spa_buf->datas[0].chunk->stride = _this->hidden->stride; - spa_buf->datas[0].chunk->size = _this->spec.size; + spa_buf->datas[0].chunk->stride = device->hidden->stride; + spa_buf->datas[0].chunk->size = buffer_size; PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + device->hidden->pw_buf = NULL; +} + +static void output_callback(void *data) +{ + SDL_OutputAudioThreadIterate((SDL_AudioDevice *)data); +} + +static void PIPEWIRE_FlushCapture(SDL_AudioDevice *device) +{ + struct pw_stream *stream = device->hidden->stream; + struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); + if (pw_buf != NULL) { // just requeue it without any further thought. + PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + } +} + +static int PIPEWIRE_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) +{ + struct pw_stream *stream = device->hidden->stream; + struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); + if (!pw_buf) { + return 0; + } + + struct spa_buffer *spa_buf = pw_buf->buffer; + if (!spa_buf) { + PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + return 0; + } + + const Uint8 *src = (const Uint8 *)spa_buf->datas[0].data; + const Uint32 offset = SPA_MIN(spa_buf->datas[0].chunk->offset, spa_buf->datas[0].maxsize); + const Uint32 size = SPA_MIN(spa_buf->datas[0].chunk->size, spa_buf->datas[0].maxsize - offset); + const int cpy = SDL_min(buflen, (int) size); + + SDL_assert(size <= buflen); // We'll have to reengineer some stuff if this turns out to not be true. + + SDL_memcpy(buffer, src + offset, cpy); + PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + + return cpy; } static void input_callback(void *data) { - struct pw_buffer *pw_buf; - struct spa_buffer *spa_buf; - Uint8 *src; - SDL_AudioDevice *_this = (SDL_AudioDevice *)data; - struct pw_stream *stream = _this->hidden->stream; - - /* Shutting down, don't do anything */ - if (SDL_AtomicGet(&_this->shutdown)) { - return; - } - - pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); - if (pw_buf == NULL) { - return; - } - - spa_buf = pw_buf->buffer; - (src = (Uint8 *)spa_buf->datas[0].data); - if (src == NULL) { - return; - } - - if (!SDL_AtomicGet(&_this->paused)) { - /* Calculate the offset and data size */ - const Uint32 offset = SPA_MIN(spa_buf->datas[0].chunk->offset, spa_buf->datas[0].maxsize); - const Uint32 size = SPA_MIN(spa_buf->datas[0].chunk->size, spa_buf->datas[0].maxsize - offset); - - src += offset; - - /* Fill the buffer with silence if the stream is disabled. */ - if (!SDL_AtomicGet(&_this->enabled)) { - SDL_memset(src, _this->callbackspec.silence, size); - } - - /* Pipewire can vary the latency, so buffer all incoming data */ - SDL_WriteToDataQueue(_this->hidden->buffer, src, size); - - while (SDL_GetDataQueueSize(_this->hidden->buffer) >= _this->callbackspec.size) { - SDL_ReadFromDataQueue(_this->hidden->buffer, _this->work_buffer, _this->callbackspec.size); - - SDL_LockMutex(_this->mixer_lock); - _this->callbackspec.callback(_this->callbackspec.userdata, _this->work_buffer, _this->callbackspec.size); - SDL_UnlockMutex(_this->mixer_lock); - } - } else if (_this->hidden->buffer) { /* Flush the buffer when paused */ - if (SDL_GetDataQueueSize(_this->hidden->buffer) != 0) { - SDL_ClearDataQueue(_this->hidden->buffer, _this->hidden->input_buffer_packet_size); - } - } - - PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + SDL_CaptureAudioThreadIterate((SDL_AudioDevice *)data); } static void stream_add_buffer_callback(void *data, struct pw_buffer *buffer) { - SDL_AudioDevice *_this = data; + SDL_AudioDevice *device = (SDL_AudioDevice *) data; - if (_this->iscapture == SDL_FALSE) { - /* - * Clamp the output spec samples and size to the max size of the Pipewire buffer. - * If they exceed the maximum size of the Pipewire buffer, double buffering will be used. - */ - if (_this->spec.size > buffer->buffer->datas[0].maxsize) { - _this->spec.samples = buffer->buffer->datas[0].maxsize / _this->hidden->stride; - _this->spec.size = buffer->buffer->datas[0].maxsize; + if (device->iscapture == SDL_FALSE) { + /* Clamp the output spec samples and size to the max size of the Pipewire buffer. + If they exceed the maximum size of the Pipewire buffer, double buffering will be used. */ + if (device->buffer_size > buffer->buffer->datas[0].maxsize) { + SDL_LockMutex(device->lock); + device->sample_frames = buffer->buffer->datas[0].maxsize / device->hidden->stride; + device->buffer_size = buffer->buffer->datas[0].maxsize; + SDL_UnlockMutex(device->lock); } - } else if (_this->hidden->buffer == NULL) { - /* - * The latency of source nodes can change, so buffering is always required. - * - * Ensure that the intermediate input buffer is large enough to hold the requested - * application packet size or a full buffer of data from Pipewire, whichever is larger. - * - * A packet size of 2 periods should be more than is ever needed. - */ - _this->hidden->input_buffer_packet_size = SPA_MAX(_this->spec.size, buffer->buffer->datas[0].maxsize) * 2; - _this->hidden->buffer = SDL_CreateDataQueue(_this->hidden->input_buffer_packet_size, _this->hidden->input_buffer_packet_size); } - _this->hidden->stream_init_status |= PW_READY_FLAG_BUFFER_ADDED; - PIPEWIRE_pw_thread_loop_signal(_this->hidden->loop, false); + device->hidden->stream_init_status |= PW_READY_FLAG_BUFFER_ADDED; + PIPEWIRE_pw_thread_loop_signal(device->hidden->loop, false); } static void stream_state_changed_callback(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { - SDL_AudioDevice *_this = data; + SDL_AudioDevice *device = (SDL_AudioDevice *) data; if (state == PW_STREAM_STATE_STREAMING) { - _this->hidden->stream_init_status |= PW_READY_FLAG_STREAM_READY; + device->hidden->stream_init_status |= PW_READY_FLAG_STREAM_READY; } if (state == PW_STREAM_STATE_STREAMING || state == PW_STREAM_STATE_ERROR) { - PIPEWIRE_pw_thread_loop_signal(_this->hidden->loop, false); + PIPEWIRE_pw_thread_loop_signal(device->hidden->loop, false); } } @@ -1109,7 +1066,7 @@ static const struct pw_stream_events stream_input_events = { PW_VERSION_STREAM_E .add_buffer = stream_add_buffer_callback, .process = input_callback }; -static int PIPEWIRE_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device) { /* * NOTE: The PW_STREAM_FLAG_RT_PROCESS flag can be set to call the stream @@ -1128,12 +1085,12 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *_this, const char *devname) struct SDL_PrivateAudioData *priv; struct pw_properties *props; const char *app_name, *app_id, *stream_name, *stream_role, *error; - Uint32 node_id = _this->handle == NULL ? PW_ID_ANY : PW_HANDLE_TO_ID(_this->handle); - SDL_bool iscapture = _this->iscapture; + Uint32 node_id = device->handle == NULL ? PW_ID_ANY : PW_HANDLE_TO_ID(device->handle); + const SDL_bool iscapture = device->iscapture; int res; /* Clamp the period size to sane values */ - const int min_period = PW_MIN_SAMPLES * SPA_MAX(_this->spec.freq / PW_BASE_CLOCK_RATE, 1); + const int min_period = PW_MIN_SAMPLES * SPA_MAX(device->spec.freq / PW_BASE_CLOCK_RATE, 1); /* Get the hints for the application name, stream name and role */ app_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME); @@ -1162,27 +1119,28 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *_this, const char *devname) } /* Initialize the Pipewire stream info from the SDL audio spec */ - initialize_spa_info(&_this->spec, &spa_info); + initialize_spa_info(&device->spec, &spa_info); params = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &spa_info); if (params == NULL) { return SDL_SetError("Pipewire: Failed to set audio format parameters"); } priv = SDL_calloc(1, sizeof(struct SDL_PrivateAudioData)); - _this->hidden = priv; + device->hidden = priv; if (priv == NULL) { return SDL_OutOfMemory(); } /* Size of a single audio frame in bytes */ - priv->stride = (SDL_AUDIO_BITSIZE(_this->spec.format) >> 3) * _this->spec.channels; + priv->stride = (SDL_AUDIO_BITSIZE(device->spec.format) / 8) * device->spec.channels; - if (_this->spec.samples < min_period) { - _this->spec.samples = min_period; - _this->spec.size = _this->spec.samples * priv->stride; + if (device->sample_frames < min_period) { + device->sample_frames = min_period; } - (void)SDL_snprintf(thread_name, sizeof(thread_name), "SDLAudio%c%ld", (iscapture) ? 'C' : 'P', (long)_this->handle); + SDL_UpdatedAudioDeviceFormat(device); + + (void)SDL_snprintf(thread_name, sizeof(thread_name), "SDLAudio%c%ld", (iscapture) ? 'C' : 'P', (long)device->handle); priv->loop = PIPEWIRE_pw_thread_loop_new(thread_name, NULL); if (priv->loop == NULL) { return SDL_SetError("Pipewire: Failed to create stream loop (%i)", errno); @@ -1213,8 +1171,8 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *_this, const char *devname) } PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_NAME, stream_name); PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, stream_name); - PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%i", _this->spec.samples, _this->spec.freq); - PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", _this->spec.freq); + PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%i", device->sample_frames, device->spec.freq); + PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", device->spec.freq); PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); /* @@ -1240,7 +1198,7 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *_this, const char *devname) /* Create the new stream */ priv->stream = PIPEWIRE_pw_stream_new_simple(PIPEWIRE_pw_thread_loop_get_loop(priv->loop), stream_name, props, - iscapture ? &stream_input_events : &stream_output_events, _this); + iscapture ? &stream_input_events : &stream_output_events, device); if (priv->stream == NULL) { return SDL_SetError("Pipewire: Failed to create stream (%i)", errno); } @@ -1268,39 +1226,38 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *_this, const char *devname) return SDL_SetError("Pipewire: Stream error: %s", error); } - /* If this is a capture stream, make sure the intermediate buffer was successfully allocated. */ - if (iscapture && priv->buffer == NULL) { - return SDL_SetError("Pipewire: Failed to allocate source buffer"); - } - return 0; } -static void PIPEWIRE_CloseDevice(SDL_AudioDevice *_this) +static void PIPEWIRE_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden->loop) { - PIPEWIRE_pw_thread_loop_stop(_this->hidden->loop); + if (!device->hidden) { + return; } - if (_this->hidden->stream) { - PIPEWIRE_pw_stream_destroy(_this->hidden->stream); + if (device->hidden->loop) { + PIPEWIRE_pw_thread_loop_stop(device->hidden->loop); } - if (_this->hidden->context) { - PIPEWIRE_pw_context_destroy(_this->hidden->context); + if (device->hidden->stream) { + PIPEWIRE_pw_stream_destroy(device->hidden->stream); } - if (_this->hidden->loop) { - PIPEWIRE_pw_thread_loop_destroy(_this->hidden->loop); + if (device->hidden->context) { + PIPEWIRE_pw_context_destroy(device->hidden->context); } - if (_this->hidden->buffer) { - SDL_DestroyDataQueue(_this->hidden->buffer); + if (device->hidden->loop) { + PIPEWIRE_pw_thread_loop_destroy(device->hidden->loop); } - SDL_free(_this->hidden); + SDL_free(device->hidden); + device->hidden = NULL; + + SDL_AudioThreadFinalize(device); } +#if 0 static int PIPEWIRE_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) { struct io_node *node; @@ -1338,6 +1295,7 @@ failed: PIPEWIRE_pw_thread_loop_unlock(hotplug_loop); return ret; } +#endif static void PIPEWIRE_Deinitialize(void) { @@ -1366,13 +1324,16 @@ static SDL_bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl) /* Set the function pointers */ impl->DetectDevices = PIPEWIRE_DetectDevices; impl->OpenDevice = PIPEWIRE_OpenDevice; - impl->CloseDevice = PIPEWIRE_CloseDevice; impl->Deinitialize = PIPEWIRE_Deinitialize; - impl->GetDefaultAudioInfo = PIPEWIRE_GetDefaultAudioInfo; + //impl->GetDefaultAudioInfo = PIPEWIRE_GetDefaultAudioInfo; + impl->PlayDevice = PIPEWIRE_PlayDevice; + impl->GetDeviceBuf = PIPEWIRE_GetDeviceBuf; + impl->CaptureFromDevice = PIPEWIRE_CaptureFromDevice; + impl->FlushCapture = PIPEWIRE_FlushCapture; + impl->CloseDevice = PIPEWIRE_CloseDevice; impl->HasCaptureSupport = SDL_TRUE; impl->ProvidesOwnCallbackThread = SDL_TRUE; - impl->SupportsNonPow2Samples = SDL_TRUE; return SDL_TRUE; } diff --git a/src/audio/pipewire/SDL_pipewire.h b/src/audio/pipewire/SDL_pipewire.h index 4ca3315b4..5a6772ab5 100644 --- a/src/audio/pipewire/SDL_pipewire.h +++ b/src/audio/pipewire/SDL_pipewire.h @@ -32,11 +32,12 @@ struct SDL_PrivateAudioData struct pw_thread_loop *loop; struct pw_stream *stream; struct pw_context *context; - struct SDL_DataQueue *buffer; - size_t input_buffer_packet_size; Sint32 stride; /* Bytes-per-frame */ int stream_init_status; + + // Set in GetDeviceBuf, filled in AudioThreadIterate, queued in PlayDevice + struct pw_buffer *pw_buf; }; #endif /* SDL_pipewire_h_ */