diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0c286ccf1..b8a703ab6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,6 +31,9 @@ include: - project: 'freedesktop/ci-templates' ref: *templates_sha file: '/templates/alpine.yml' + - project: 'freedesktop/ci-templates' + ref: *templates_sha + file: '/templates/debian.yml' .fedora: variables: @@ -102,36 +105,6 @@ include: # FDO_DISTRIBUTION_EXEC: >- # pip3 install meson -# This is a pruned down container with enough dependencies for a basic i686 -# build to make sure we've not broken anything. This can be extended if we want -# to cover more of the code. -.fedora_x86: - variables: - # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2025-05-29.1' - FDO_DISTRIBUTION_VERSION: '42' - FDO_DISTRIBUTION_PACKAGES: >- - git - gcc - gcc-c++ - meson - glibc-devel.i686 - systemd-devel.i686 - dbus-devel.i686 - alsa-lib-devel.i686 - bluez-libs-devel.i686 - libffi-devel.i686 - pcre2-devel.i686 - sysprof-devel.i686 - zlib-ng-compat-devel.i686 - libblkid-devel.i686 - libmount-devel.i686 - libselinux-devel.i686 - glib2-devel.i686 - alsa-lib-devel - avahi-devel - bluez-libs-devel - .ubuntu: variables: # Update this tag when you want to trigger a rebuild @@ -171,6 +144,30 @@ include: # FDO_DISTRIBUTION_EXEC: >- # pip3 install meson +.debian: + variables: + # Update this tag when you want to trigger a rebuild + BASE_TAG: '2025-08-10.0' + FDO_DISTRIBUTION_VERSION: 'trixie' + FDO_DISTRIBUTION_PACKAGES: >- + build-essential + dpkg-dev + findutils + git + meson + +.debian-archictectures: + parallel: + matrix: + - ARCH: + - amd64 + - arm64 + - armhf + - i386 + - ppc64el + - riscv64 + - s390x + .alpine: variables: # Update this tag when you want to trigger a rebuild @@ -243,14 +240,8 @@ include: - echo "Building with meson options $MESON_OPTIONS" - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS - meson compile -C "$BUILD_DIR" $COMPILE_ARGS - - | - if [ -z "$MESON_SKIP_TEST" ]; then - meson test -C "$BUILD_DIR" --no-rebuild - fi - - | - if [ -z "$MESON_SKIP_INSTALL" ]; then - meson install -C "$BUILD_DIR" --no-rebuild - fi + - meson test -C "$BUILD_DIR" --no-rebuild + - meson install -C "$BUILD_DIR" --no-rebuild artifacts: name: pipewire-$CI_COMMIT_SHA when: always @@ -265,17 +256,21 @@ container_ubuntu: variables: GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image -container_fedora: +container_debian: extends: - - .fedora - - .fdo.container-build@fedora + - .debian + - .debian-archictectures + - .fdo.container-build@debian stage: container variables: GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image + FDO_DISTRIBUTION_TAG: "$BASE_TAG-$ARCH" + FDO_DISTRIBUTION_EXEC: >- + ./.gitlab/ci/setup-debian-cross-container.sh "$ARCH" -container_fedora_x86: +container_fedora: extends: - - .fedora_x86 + - .fedora - .fdo.container-build@fedora stage: container variables: @@ -311,6 +306,44 @@ build_on_ubuntu: variables: MESON_OPTIONS: "-Dsession-managers=[] -Dsnap=enabled" +build_on_debian: + extends: + - .debian + - .debian-archictectures + - .not_coverity + - .fdo.distribution-image@debian + - .build + stage: build + needs: + - job: container_debian + artifacts: false + # ideally + # parallel: + # matrix: + # - ARCH: "$ARCH" + # however https://gitlab.com/gitlab-org/gitlab/-/issues/423553 + # ("Expand variables in `needs:parallel:matrix`") + variables: + FDO_DISTRIBUTION_TAG: "$BASE_TAG-$ARCH" + # see /.gitlab/ci/setup-debian-cross-container.sh for installed packages + MESON_OPTIONS: >- + --cross-file /opt/meson-$ARCH.cross + -D c_args=['-UFASTPATH'] + -D cpp_args=['-UFASTPATH'] + -D auto_features=enabled + -D session-managers=[] + -D bluez5-backend-native-mm=enabled + -D bluez5-codec-lc3plus=disabled + -D bluez5-codec-ldac=disabled + -D bluez5-codec-ldac-dec=disabled + -D libcamera=disabled + -D roc=disabled + -D snap=disabled + -D systemd-user-service=disabled + -D systemd-system-service=disabled + -D onnxruntime=disabled + -D vulkan=enabled + .build_on_fedora: extends: - .fedora @@ -406,21 +439,6 @@ build_on_fedora_html_docs: rules: - !reference [pages, rules] -build_on_fedora_x86: - extends: - - .fedora_x86 - - .not_coverity - - .fdo.distribution-image@fedora - - .build - stage: build - needs: - - job: container_fedora_x86 - artifacts: false - variables: - MESON_OPTIONS: "--cross-file=cross-x86.txt" - MESON_SKIP_TEST: "true" - MESON_SKIP_INSTALL: "true" - build_on_alpine: extends: - .alpine diff --git a/.gitlab/ci/setup-debian-cross-container.sh b/.gitlab/ci/setup-debian-cross-container.sh new file mode 100755 index 000000000..616971803 --- /dev/null +++ b/.gitlab/ci/setup-debian-cross-container.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +set -ex + +packages=( + # libapparmor-dev + libasound2-dev + libavahi-client-dev + libavcodec-dev + libavfilter-dev + libavformat-dev + libbluetooth-dev + libcanberra-dev + libdbus-1-dev + libebur128-dev + libfdk-aac-dev + libffado-dev + libfftw3-dev + libfreeaptx-dev + libglib2.0-dev + libgstreamer1.0-dev + libgstreamer-plugins-base1.0-dev + libjack-jackd2-dev + liblc3-dev + liblilv-dev + libmysofa-dev + libopus-dev + libpulse-dev + libreadline-dev + libsbc-dev + libsdl2-dev + # libsnapd-glib-dev + libsndfile1-dev + libspandsp-dev + libssl-dev + libsystemd-dev + libudev-dev + libusb-1.0-0-dev + libvulkan-dev + libwebrtc-audio-processing-dev + libx11-dev + modemmanager-dev +) + +arch="$1" + +export DEBIAN_FRONTEND=noninteractive + +sed -i \ + 's/^Components:.*$/Components: main contrib non-free non-free-firmware/' \ + /etc/apt/sources.list.d/debian.sources + +dpkg --add-architecture "$arch" +apt update -y + +pkgs=( "crossbuild-essential-$arch" ) +for pkg in "${packages[@]}"; do + pkgs+=( "$pkg:$arch" ) +done + +apt install -y "${pkgs[@]}" + +meson env2mfile --cross --debarch "$arch" -o "/opt/meson-$arch.cross" diff --git a/cross-x86.txt b/cross-x86.txt deleted file mode 100644 index 8ddd5ecc5..000000000 --- a/cross-x86.txt +++ /dev/null @@ -1,23 +0,0 @@ -[binaries] -c = 'gcc' -cpp = 'g++' -ld = 'ld' -cmake = 'cmake' -strip = 'strip' -pkg-config = 'pkg-config' - -[properties] -pkg_config_libdir = '/usr/lib/pkgconfig' -ld_args = '-m elf_i386' - -[built-in options] -c_args = '-m32 -msse' -c_link_args = '-m32 -msse' -cpp_args = '-m32 -msse' -cpp_link_args = '-m32 -msse' - -[host_machine] -system = 'linux' -cpu_family = 'x86' -cpu = 'i686' -endian = 'little' diff --git a/meson.build b/meson.build index 307f1155c..0a01a8f37 100644 --- a/meson.build +++ b/meson.build @@ -381,9 +381,6 @@ libusb_dep = dependency('libusb-1.0', required : get_option('libusb')) summary({'libusb (Bluetooth quirks)': libusb_dep.found()}, bool_yn: true, section: 'Backend') cdata.set('HAVE_LIBUSB', libusb_dep.found()) -cap_lib = dependency('libcap', required : false) -cdata.set('HAVE_LIBCAP', cap_lib.found()) - glib2_dep = dependency('glib-2.0', required : get_option('flatpak')) summary({'GLib-2.0 (Flatpak support)': glib2_dep.found()}, bool_yn: true, section: 'Misc dependencies') flatpak_support = glib2_dep.found() diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index af62887a3..044ad5bd6 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -411,12 +411,23 @@ static void volume_sync_stop_timer(struct rfcomm *rfcomm); static void rfcomm_free(struct rfcomm *rfcomm) { struct updated_call *updated_call; + struct rfcomm_cmd *cmd; spa_list_consume(updated_call, &rfcomm->updated_call_list, link) { spa_list_remove(&updated_call->link); free(updated_call); } + spa_list_consume(cmd, &rfcomm->cmd_send_queue, link) { + if (cmd->msg) { + telephony_send_dbus_method_reply(rfcomm->backend->telephony, cmd->msg, BT_TELEPHONY_ERROR_FAILED, 0); + spa_clear_ptr(cmd->msg, dbus_message_unref); + } + + spa_list_remove(&cmd->link); + free(cmd); + } + codec_switch_stop_timer(rfcomm); if (rfcomm->telephony_ag) { telephony_ag_destroy(rfcomm->telephony_ag); @@ -1900,6 +1911,12 @@ static const struct spa_bt_telephony_ag_callbacks telephony_ag_callbacks = { .set_microphone_volume = hfp_hf_set_microphone_volume, }; +#define hfp_hf_set_call_state(log, obj, new_state) \ +({ \ + spa_log_debug(log, "call id: %u, %u -> %u", obj->id, obj->state, new_state); \ + obj->state = new_state; \ +}) + static void hfp_hf_remove_disconnected_calls(struct rfcomm *rfcomm) { struct impl *backend = rfcomm->backend; @@ -1919,7 +1936,7 @@ static void hfp_hf_remove_disconnected_calls(struct rfcomm *rfcomm) spa_log_debug(backend->log, "call %d -> %s", call->id, found ? "updated" : "disconnected"); if (!found) { - call->state = CALL_STATE_DISCONNECTED; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } @@ -2046,7 +2063,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING || call->state == CALL_STATE_INCOMING || call->state == CALL_STATE_WAITING) { - call->state = CALL_STATE_DISCONNECTED; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } @@ -2090,7 +2107,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) struct spa_bt_telephony_call *call; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_DIALING) { - call->state = CALL_STATE_ALERTING; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_ALERTING); telephony_call_notify_updated_props(call); } } @@ -2107,7 +2124,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) struct spa_bt_telephony_call *call, *tcall; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_ACTIVE) { - call->state = CALL_STATE_DISCONNECTED; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } @@ -2117,7 +2134,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING || call->state == CALL_STATE_INCOMING) { - call->state = CALL_STATE_ACTIVE; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_ACTIVE); telephony_call_notify_updated_props(call); } } @@ -2135,7 +2152,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) bool found_waiting = false; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_WAITING) { - call->state = CALL_STATE_DISCONNECTED; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); found_waiting = true; @@ -2145,7 +2162,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) if (!found_waiting) { spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_HELD) { - call->state = CALL_STATE_DISCONNECTED; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } @@ -2156,10 +2173,10 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { bool changed = false; if (call->state == CALL_STATE_ACTIVE) { - call->state = CALL_STATE_HELD; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_HELD); changed = true; } else if (call->state == CALL_STATE_HELD) { - call->state = CALL_STATE_ACTIVE; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_ACTIVE); changed = true; } @@ -2171,7 +2188,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { bool changed = false; if (call->state == CALL_STATE_ACTIVE || call->state == CALL_STATE_WAITING) { - call->state = CALL_STATE_HELD; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_HELD); changed = true; } @@ -2420,14 +2437,14 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) struct spa_bt_telephony_call *call, *tcall; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_ACTIVE) { - call->state = CALL_STATE_DISCONNECTED; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_HELD) { - call->state = CALL_STATE_ACTIVE; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_ACTIVE); telephony_call_notify_updated_props(call); } } diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 4bdafebaa..a469ed377 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -54,7 +53,6 @@ namespace { #define MASK_BUFFERS 31 #define BUFFER_FLAG_OUTSTANDING (1<<0) -#define BUFFER_FLAG_MAPPED (1<<1) struct buffer { uint32_t id; @@ -80,8 +78,6 @@ struct port { struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers = 0; struct spa_list queue; - struct spa_ringbuffer ring = SPA_RINGBUFFER_INIT(); - uint32_t ring_ids[MAX_BUFFERS]; static constexpr uint64_t info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; @@ -155,9 +151,10 @@ struct impl { std::shared_ptr camera; const std::unique_ptr config; - FrameBufferAllocator *allocator = nullptr; + FrameBufferAllocator allocator; std::vector> requestPool; - std::deque pendingRequests; + spa_ringbuffer completed_requests_rb = SPA_RINGBUFFER_INIT(); + std::array completed_requests; void requestComplete(libcamera::Request *request); @@ -173,6 +170,38 @@ struct impl { std::unique_ptr config); struct spa_dll dll; + + void stop() + { + spa_loop_locked( + data_loop, + [](spa_loop *, bool, uint32_t, const void *, size_t, void *user_data) { + auto *self = static_cast(user_data); + + if (self->source.loop) + spa_loop_remove_source(self->data_loop, &self->source); + + return 0; + }, + 0, nullptr, 0, this + ); + + if (source.fd >= 0) + spa_system_close(system, std::exchange(source.fd, -1)); + + camera->requestCompleted.disconnect(this, &impl::requestComplete); + + if (int res = camera->stop(); res < 0) { + spa_log_warn(log, "failed to stop camera %s: %s", + camera->id().c_str(), spa_strerror(res)); + } + + completed_requests_rb = SPA_RINGBUFFER_INIT(); + active = false; + + for (auto& p : out_ports) + spa_list_init(&p.queue); + } }; #define CHECK_PORT(impl,direction,port_id) ((direction) == SPA_DIRECTION_OUTPUT && (port_id) == 0) @@ -214,7 +243,7 @@ int spa_libcamera_open(struct impl *impl) if (int res = impl->camera->acquire(); res < 0) return res; - impl->allocator = new FrameBufferAllocator(impl->camera); + spa_assert(!impl->allocator.allocated()); const ControlInfoMap &controls = impl->camera->controls(); setup_initial_controls(controls, impl->initial_controls); @@ -232,8 +261,8 @@ int spa_libcamera_close(struct impl *impl) return 0; spa_log_info(impl->log, "close camera %s", impl->camera->id().c_str()); - delete impl->allocator; - impl->allocator = nullptr; + + spa_assert(!impl->allocator.allocated()); impl->camera->release(); @@ -257,17 +286,7 @@ int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t return -EINVAL; } Request *request = impl->requestPool[buffer_id].get(); - Stream *stream = port->streamConfig.stream(); - FrameBuffer *buffer = impl->allocator->buffers(stream)[buffer_id].get(); - if ((res = request->addBuffer(stream, buffer)) < 0) { - spa_log_warn(impl->log, "can't add buffer %u for request: %s", - buffer_id, spa_strerror(res)); - return -ENOMEM; - } - if (!impl->active) { - impl->pendingRequests.push_back(request); - return 0; - } else { + if (impl->active) { request->controls().merge(impl->ctrls); impl->ctrls.clear(); if ((res = impl->camera->queueRequest(request)) < 0) { @@ -279,19 +298,57 @@ int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t return 0; } +void freeBuffers(struct impl *impl, struct port *port) +{ + impl->requestPool.clear(); + std::ignore = impl->allocator.free(port->streamConfig.stream()); +} + +[[nodiscard]] +std::size_t count_unique_fds(libcamera::Span planes) +{ + std::size_t c = 0; + int fd = -1; + + for (const auto& plane : planes) { + const int current_fd = plane.fd.get(); + if (current_fd >= 0 && current_fd != fd) { + c += 1; + fd = current_fd; + } + } + + return c; +} + int allocBuffers(struct impl *impl, struct port *port, unsigned int count) { + libcamera::Stream *stream = port->streamConfig.stream(); int res; - if ((res = impl->allocator->allocate(port->streamConfig.stream())) < 0) + if (!impl->requestPool.empty()) + return -EBUSY; + + if ((res = impl->allocator.allocate(stream)) < 0) return res; - for (unsigned int i = 0; i < count; i++) { + const auto& bufs = impl->allocator.buffers(stream); + if (bufs.empty() || bufs.size() != count) { + res = -ENOBUFS; + goto err; + } + + for (std::size_t i = 0; i < bufs.size(); i++) { std::unique_ptr request = impl->camera->createRequest(i); if (!request) { - impl->requestPool.clear(); - return -ENOMEM; + res = -ENOMEM; + goto err; } + + res = request->addBuffer(stream, bufs[i].get()); + if (res < 0) + goto err; + impl->requestPool.push_back(std::move(request)); } @@ -300,63 +357,38 @@ int allocBuffers(struct impl *impl, struct port *port, unsigned int count) * video frame has to be addressed using more than one memory. * address. Therefore, need calculate the number of discontiguous * memory and allocate the specified amount of memory */ - Stream *stream = impl->config->at(0).stream(); - const std::vector> &bufs = - impl->allocator->buffers(stream); - const std::vector &planes = bufs[0]->planes(); - int fd = -1; - uint32_t buffers_blocks = 0; - - for (const FrameBuffer::Plane &plane : planes) { - const int current_fd = plane.fd.get(); - if (current_fd >= 0 && current_fd != fd) { - buffers_blocks += 1; - fd = current_fd; - } + port->buffers_blocks = count_unique_fds(bufs.front()->planes()); + if (port->buffers_blocks <= 0) { + res = -ENOBUFS; + goto err; } - if (buffers_blocks > 0) { - port->buffers_blocks = buffers_blocks; - } + return 0; + +err: + freeBuffers(impl, port); + return res; } -void freeBuffers(struct impl *impl, struct port *port) +int spa_libcamera_clear_buffers(struct port *port) { - impl->pendingRequests.clear(); - impl->requestPool.clear(); - impl->allocator->free(port->streamConfig.stream()); -} + for (std::size_t i = 0; i < port->n_buffers; i++) { + buffer *b = &port->buffers[i]; + spa_buffer *sb = b->outbuf; -int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) -{ - uint32_t i; + for (std::size_t j = 0; j < sb->n_datas; j++) { + auto *d = &sb->datas[j]; - if (port->n_buffers == 0) - return 0; - - for (i = 0; i < port->n_buffers; i++) { - struct buffer *b; - struct spa_data *d; - - b = &port->buffers[i]; - d = b->outbuf->datas; - - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) { - spa_log_debug(impl->log, "queueing outstanding buffer %p", b); - spa_libcamera_buffer_recycle(impl, port, i); - } - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { - munmap(SPA_PTROFF(b->ptr, -d[0].mapoffset, void), - d[0].maxsize - d[0].mapoffset); + d->type = SPA_ID_INVALID; + d->data = nullptr; + d->fd = -1; } - d[0].type = SPA_ID_INVALID; + *b = {}; } - freeBuffers(impl, port); port->n_buffers = 0; - port->ring = SPA_RINGBUFFER_INIT(); return 0; } @@ -371,6 +403,7 @@ struct format_info { #define MAKE_FMT(pix,fmt,mt,mst) { pix, SPA_VIDEO_FORMAT_ ##fmt, SPA_MEDIA_TYPE_ ##mt, SPA_MEDIA_SUBTYPE_ ##mst } const struct format_info format_info[] = { /* RGB formats */ + MAKE_FMT(formats::R8, GRAY8, video, raw), MAKE_FMT(formats::RGB565, RGB16, video, raw), MAKE_FMT(formats::RGB565_BE, RGB16, video, raw), MAKE_FMT(formats::RGB888, BGR, video, raw), @@ -1005,14 +1038,110 @@ int spa_libcamera_apply_controls(struct impl *impl, libcamera::ControlList&& con ); } +void handle_completed_request(struct impl *impl, libcamera::Request *request) +{ + const auto request_id = request->cookie(); + struct port *port = &impl->out_ports[0]; + buffer *b = &port->buffers[request_id]; + + spa_log_trace(impl->log, "%p: request %p[%" PRIu64 "] process status:%u seq:%" PRIu32, + impl, request, request_id, static_cast(request->status()), + request->sequence()); + + if (request->status() == libcamera::Request::Status::RequestCancelled) { + spa_log_trace(impl->log, "%p: request %p[%" PRIu64 "] cancelled", + impl, request, request_id); + request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + spa_libcamera_buffer_recycle(impl, port, b->id); + return; + } + + const FrameBuffer *buffer = request->findBuffer(port->streamConfig.stream()); + if (buffer == nullptr) { + spa_log_warn(impl->log, "%p: request %p[%" PRIu64 "] has no buffer for stream %p", + impl, request, request_id, port->streamConfig.stream()); + return; + } + + const FrameMetadata &fmd = buffer->metadata(); + + if (impl->clock) { + double target = (double)port->info.rate.num / port->info.rate.denom; + double corr; + + if (impl->dll.bw == 0.0) { + spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MAX, port->info.rate.denom, port->info.rate.denom); + impl->clock->next_nsec = fmd.timestamp; + corr = 1.0; + } else { + double diff = ((double)impl->clock->next_nsec - (double)fmd.timestamp) / SPA_NSEC_PER_SEC; + double error = port->info.rate.denom * (diff - target); + corr = spa_dll_update(&impl->dll, SPA_CLAMPD(error, -128., 128.)); + } + /* FIXME, we should follow the driver clock and target_ values. + * for now we ignore and use our own. */ + impl->clock->target_rate = port->rate; + impl->clock->target_duration = 1; + + impl->clock->nsec = fmd.timestamp; + impl->clock->rate = port->rate; + impl->clock->position = fmd.sequence; + impl->clock->duration = 1; + impl->clock->delay = 0; + impl->clock->rate_diff = corr; + impl->clock->next_nsec += (uint64_t) (target * SPA_NSEC_PER_SEC * corr); + } + + if (b->h) { + b->h->flags = 0; + if (fmd.status != libcamera::FrameMetadata::Status::FrameSuccess) + b->h->flags |= SPA_META_HEADER_FLAG_CORRUPTED; + b->h->offset = 0; + b->h->seq = fmd.sequence; + b->h->pts = fmd.timestamp; + b->h->dts_offset = 0; + } + + for (std::size_t i = 0; i < b->outbuf->n_datas; i++) { + auto *d = &b->outbuf->datas[i]; + + d->chunk->flags = 0; + + if (fmd.status != libcamera::FrameMetadata::Status::FrameSuccess) + d->chunk->flags |= SPA_CHUNK_FLAG_CORRUPTED; + } + + request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); + + spa_list_append(&port->queue, &b->link); + + spa_io_buffers *io = port->io; + if (io == nullptr) { + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + spa_libcamera_buffer_recycle(impl, port, b->id); + } else if (io->status != SPA_STATUS_HAVE_DATA) { + if (io->buffer_id < port->n_buffers) + spa_libcamera_buffer_recycle(impl, port, io->buffer_id); + + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + + io->buffer_id = b->id; + io->status = SPA_STATUS_HAVE_DATA; + spa_log_trace(impl->log, "%p: now queued %" PRIu32, impl, b->id); + } + + spa_node_call_ready(&impl->callbacks, SPA_STATUS_HAVE_DATA); +} void libcamera_on_fd_events(struct spa_source *source) { struct impl *impl = (struct impl*) source->data; - struct spa_io_buffers *io; - struct port *port = &impl->out_ports[0]; - uint32_t index, buffer_id; - struct buffer *b; + uint32_t index; uint64_t cnt; if (source->rmask & SPA_IO_ERR) { @@ -1032,35 +1161,13 @@ void libcamera_on_fd_events(struct spa_source *source) return; } - if (spa_ringbuffer_get_read_index(&port->ring, &index) < 1) { - spa_log_error(impl->log, "nothing is queued"); - return; + auto avail = spa_ringbuffer_get_read_index(&impl->completed_requests_rb, &index); + for (; avail > 0; avail--, index++) { + auto *request = impl->completed_requests[index & MASK_BUFFERS]; + + spa_ringbuffer_read_update(&impl->completed_requests_rb, index + 1); + handle_completed_request(impl, request); } - buffer_id = port->ring_ids[index & MASK_BUFFERS]; - spa_ringbuffer_read_update(&port->ring, index + 1); - - b = &port->buffers[buffer_id]; - spa_list_append(&port->queue, &b->link); - - io = port->io; - if (io == nullptr) { - b = spa_list_first(&port->queue, struct buffer, link); - spa_list_remove(&b->link); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); - spa_libcamera_buffer_recycle(impl, port, b->id); - } else if (io->status != SPA_STATUS_HAVE_DATA) { - if (io->buffer_id < port->n_buffers) - spa_libcamera_buffer_recycle(impl, port, io->buffer_id); - - b = spa_list_first(&port->queue, struct buffer, link); - spa_list_remove(&b->link); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); - - io->buffer_id = b->id; - io->status = SPA_STATUS_HAVE_DATA; - spa_log_trace(impl->log, "libcamera %p: now queued %d", impl, b->id); - } - spa_node_call_ready(&impl->callbacks, SPA_STATUS_HAVE_DATA); } int spa_libcamera_use_buffers(struct impl *impl, struct port *port, @@ -1102,7 +1209,7 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, Stream *stream = impl->config->at(0).stream(); const std::vector> &bufs = - impl->allocator->buffers(stream); + impl->allocator.buffers(stream); if (n_buffers > 0) { if (bufs.size() != n_buffers) @@ -1112,10 +1219,8 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, if (d[0].type != SPA_ID_INVALID && d[0].type & (1u << SPA_DATA_DmaBuf)) { port->memtype = SPA_DATA_DmaBuf; - } else if (d[0].type != SPA_ID_INVALID && d[0].type & (1u << SPA_DATA_MemFd)) { + } else if (d[0].type & (1u << SPA_DATA_MemFd)) { port->memtype = SPA_DATA_MemFd; - } else if (d[0].type & (1u << SPA_DATA_MemPtr)) { - port->memtype = SPA_DATA_MemPtr; } else { spa_log_error(impl->log, "can't use buffers of type %d", d[0].type); return -EINVAL; @@ -1133,7 +1238,7 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; - b->flags = BUFFER_FLAG_OUTSTANDING; + b->flags = 0; b->h = (struct spa_meta_header*)spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); b->videotransform = (struct spa_meta_videotransform*)spa_buffer_find_meta_data( @@ -1186,28 +1291,11 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, d[j].fd = bufs[i]->planes()[j].fd.get(); spa_log_debug(impl->log, "Got fd = %" PRId64 " for buffer: #%d", d[j].fd, i); d[j].data = nullptr; - } - else if (port->memtype == SPA_DATA_MemPtr) { - d[j].fd = -1; - d[j].data = mmap(nullptr, - d[j].maxsize + d[j].mapoffset, - PROT_READ, MAP_SHARED, - bufs[i]->planes()[j].fd.get(), - 0); - if (d[j].data == MAP_FAILED) { - spa_log_error(impl->log, "mmap: %m"); - continue; - } - b->ptr = d[j].data; - SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); - spa_log_debug(impl->log, "mmap ptr:%p", d[j].data); } else { spa_log_error(impl->log, "invalid buffer type"); return -EIO; } } - - spa_libcamera_buffer_recycle(impl, port, i); } port->n_buffers = n_buffers; @@ -1220,89 +1308,22 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, void impl::requestComplete(libcamera::Request *request) { struct impl *impl = this; - struct port *port = &impl->out_ports[0]; - Stream *stream = port->streamConfig.stream(); - uint32_t index, buffer_id; - struct buffer *b; + uint32_t index; - spa_log_debug(impl->log, "request complete"); + spa_log_trace(impl->log, "%p: request %p[%" PRIu64 "] completed status:%u seq:%" PRIu32, + impl, request, request->cookie(), + static_cast(request->status()), + request->sequence()); - buffer_id = request->cookie(); - b = &port->buffers[buffer_id]; - - if ((request->status() == Request::RequestCancelled)) { - spa_log_debug(impl->log, "Request was cancelled"); - request->reuse(); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); - spa_libcamera_buffer_recycle(impl, port, b->id); - return; - } - FrameBuffer *buffer = request->findBuffer(stream); - if (buffer == nullptr) { - spa_log_warn(impl->log, "unknown buffer"); - return; - } - const FrameMetadata &fmd = buffer->metadata(); - - if (impl->clock) { - double target = (double)port->info.rate.num / port->info.rate.denom; - double corr; - - if (impl->dll.bw == 0.0) { - spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MAX, port->info.rate.denom, port->info.rate.denom); - impl->clock->next_nsec = fmd.timestamp; - corr = 1.0; - } else { - double diff = ((double)impl->clock->next_nsec - (double)fmd.timestamp) / SPA_NSEC_PER_SEC; - double error = port->info.rate.denom * (diff - target); - corr = spa_dll_update(&impl->dll, SPA_CLAMPD(error, -128., 128.)); - } - /* FIXME, we should follow the driver clock and target_ values. - * for now we ignore and use our own. */ - impl->clock->target_rate = port->rate; - impl->clock->target_duration = 1; - - impl->clock->nsec = fmd.timestamp; - impl->clock->rate = port->rate; - impl->clock->position = fmd.sequence; - impl->clock->duration = 1; - impl->clock->delay = 0; - impl->clock->rate_diff = corr; - impl->clock->next_nsec += (uint64_t) (target * SPA_NSEC_PER_SEC * corr); - } - if (b->h) { - b->h->flags = 0; - if (fmd.status != FrameMetadata::Status::FrameSuccess) - b->h->flags |= SPA_META_HEADER_FLAG_CORRUPTED; - b->h->offset = 0; - b->h->seq = fmd.sequence; - b->h->pts = fmd.timestamp; - b->h->dts_offset = 0; - } - request->reuse(); - - spa_ringbuffer_get_write_index(&port->ring, &index); - port->ring_ids[index & MASK_BUFFERS] = buffer_id; - spa_ringbuffer_write_update(&port->ring, index + 1); + spa_ringbuffer_get_write_index(&impl->completed_requests_rb, &index); + impl->completed_requests[index & MASK_BUFFERS] = request; + spa_ringbuffer_write_update(&impl->completed_requests_rb, index + 1); if (spa_system_eventfd_write(impl->system, impl->source.fd, 1) < 0) spa_log_error(impl->log, "Failed to write on event fd"); } -int do_remove_source(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - auto *impl = static_cast(user_data); - if (impl->source.loop) - spa_loop_remove_source(loop, &impl->source); - return 0; -} - int spa_libcamera_stream_on(struct impl *impl) { struct port *port = &impl->out_ports[0]; @@ -1316,76 +1337,60 @@ int spa_libcamera_stream_on(struct impl *impl) if (impl->active) return 0; + spa_log_info(impl->log, "starting camera %s", impl->camera->id().c_str()); + if ((res = impl->camera->start(&impl->initial_controls)) < 0) + return res == -EACCES ? -EBUSY : res; + + impl->camera->requestCompleted.connect(impl, &impl::requestComplete); + res = spa_system_eventfd_create(impl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); if (res < 0) - return res; + goto err_stop; impl->source.fd = res; impl->source.func = libcamera_on_fd_events; impl->source.data = impl; impl->source.mask = SPA_IO_IN | SPA_IO_ERR; impl->source.rmask = 0; - res = spa_loop_add_source(impl->data_loop, &impl->source); - if (res < 0) - goto err_close_source; - spa_log_info(impl->log, "starting camera %s", impl->camera->id().c_str()); - if ((res = impl->camera->start(&impl->initial_controls)) < 0) - goto err_remove_source; + for (auto& req : impl->requestPool) { + req->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); - impl->camera->requestCompleted.connect(impl, &impl::requestComplete); - - for (Request *req : impl->pendingRequests) { - if ((res = impl->camera->queueRequest(req)) < 0) - goto err_stop_camera; + if ((res = impl->camera->queueRequest(req.get())) < 0) + goto err_stop; } - impl->pendingRequests.clear(); impl->dll.bw = 0.0; impl->active = true; + res = spa_loop_locked( + impl->data_loop, + [](spa_loop *, bool, uint32_t, const void *, size_t, void *user_data) + { + auto *impl = static_cast(user_data); + return spa_loop_add_source(impl->data_loop, &impl->source); + }, + 0, nullptr, 0, impl + ); + if (res < 0) + goto err_stop; + return 0; -err_stop_camera: - impl->camera->stop(); - impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); -err_remove_source: - spa_loop_locked(impl->data_loop, do_remove_source, 0, nullptr, 0, impl); -err_close_source: - spa_system_close(impl->system, std::exchange(impl->source.fd, -1)); +err_stop: + impl->stop(); - return res == -EACCES ? -EBUSY : res; + return res; } int spa_libcamera_stream_off(struct impl *impl) { - struct port *port = &impl->out_ports[0]; - int res; - - if (!impl->active) { - for (std::unique_ptr &req : impl->requestPool) - req->reuse(); + if (!impl->active) return 0; - } - impl->active = false; spa_log_info(impl->log, "stopping camera %s", impl->camera->id().c_str()); - impl->pendingRequests.clear(); - if ((res = impl->camera->stop()) < 0) { - spa_log_warn(impl->log, "error stopping camera %s: %s", - impl->camera->id().c_str(), spa_strerror(res)); - } - - impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); - - spa_loop_locked(impl->data_loop, do_remove_source, 0, nullptr, 0, impl); - if (impl->source.fd >= 0) { - spa_system_close(impl->system, impl->source.fd); - impl->source.fd = -1; - } - - spa_list_init(&port->queue); + impl->stop(); return 0; } @@ -1824,21 +1829,24 @@ next: int port_set_format(struct impl *impl, struct port *port, uint32_t flags, const struct spa_pod *format) { - struct spa_video_info info; - int res; + const bool try_only = SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_TEST_ONLY); + + if (!try_only) { + spa_libcamera_stream_off(impl); + spa_libcamera_clear_buffers(port); + freeBuffers(impl, port); + port->current_format.reset(); + } if (format == nullptr) { - if (!port->current_format) - return 0; - - spa_libcamera_stream_off(impl); - spa_libcamera_clear_buffers(impl, port); - port->current_format.reset(); - - spa_libcamera_close(impl); - goto done; + if (!try_only) + spa_libcamera_close(impl); } else { + spa_video_info info; + int res; + spa_zero(info); + if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; @@ -1854,54 +1862,32 @@ int port_set_format(struct impl *impl, struct port *port, return -EINVAL; } - if (port->current_format && info.media_type == port->current_format->media_type && - info.media_subtype == port->current_format->media_subtype && - info.info.raw.format == port->current_format->info.raw.format && - info.info.raw.size.width == port->current_format->info.raw.size.width && - info.info.raw.size.height == port->current_format->info.raw.size.height && - info.info.raw.flags == port->current_format->info.raw.flags && - (!(info.info.raw.flags & SPA_VIDEO_FLAG_MODIFIER) || - (info.info.raw.modifier == port->current_format->info.raw.modifier))) - return 0; break; case SPA_MEDIA_SUBTYPE_mjpg: if (spa_format_video_mjpg_parse(format, &info.info.mjpg) < 0) return -EINVAL; - if (port->current_format && info.media_type == port->current_format->media_type && - info.media_subtype == port->current_format->media_subtype && - info.info.mjpg.size.width == port->current_format->info.mjpg.size.width && - info.info.mjpg.size.height == port->current_format->info.mjpg.size.height) - return 0; break; case SPA_MEDIA_SUBTYPE_h264: if (spa_format_video_h264_parse(format, &info.info.h264) < 0) return -EINVAL; - if (port->current_format && info.media_type == port->current_format->media_type && - info.media_subtype == port->current_format->media_subtype && - info.info.h264.size.width == port->current_format->info.h264.size.width && - info.info.h264.size.height == port->current_format->info.h264.size.height) - return 0; break; default: return -EINVAL; } + + res = spa_libcamera_set_format(impl, port, &info, try_only); + if (res < 0) + return res; + + if (!try_only) + port->current_format = info; } - if (port->current_format && !(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { - spa_libcamera_use_buffers(impl, port, nullptr, 0); - port->current_format.reset(); - } + if (try_only) + return 0; - if (spa_libcamera_set_format(impl, port, &info, flags & SPA_NODE_PARAM_FLAG_TEST_ONLY) < 0) - return -EINVAL; - - if (!(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { - port->current_format = info; - } - - done: impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->current_format) { @@ -1961,7 +1947,7 @@ int impl_node_port_use_buffers(void *object, if (port->n_buffers) { spa_libcamera_stream_off(impl); - if ((res = spa_libcamera_clear_buffers(impl, port)) < 0) + if ((res = spa_libcamera_clear_buffers(port)) < 0) return res; } if (n_buffers > 0 && !port->current_format) @@ -2167,7 +2153,8 @@ impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system, out_ports{{this}}, manager(std::move(manager)), camera(std::move(camera)), - config(std::move(config)) + config(std::move(config)), + allocator(this->camera) { libcamera_log_topic_init(log); diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index e6f7ff9d0..e5c0bc7ee 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -1206,7 +1206,7 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc) gst_pipewire_clock_reset (GST_PIPEWIRE_CLOCK (pwsrc->stream->clock), 0); - GST_DEBUG_OBJECT (pwsrc, "set format %" GST_PTR_FORMAT, negotiated_caps); + GST_INFO_OBJECT (pwsrc, "set format %" GST_PTR_FORMAT, negotiated_caps); result = gst_base_src_set_caps (GST_BASE_SRC (pwsrc), negotiated_caps); if (!result) goto no_caps; diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 098563833..e64535938 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -49,7 +49,7 @@ * ## Module Options * * - `jack.library`: the libjack to load, by default libjack.so.0 is searched in - * JACK_PATH directories and then some standard library paths. + * LIBJACK_PATH directories and then some standard library paths. * Can be an absolute path. * - `jack.server`: the name of the JACK server to tunnel to. * - `jack.client-name`: the name of the JACK client. diff --git a/test/meson.build b/test/meson.build index b4f24cf1b..c5671149d 100644 --- a/test/meson.build +++ b/test/meson.build @@ -9,7 +9,6 @@ pwtest_deps = [ pipewire_dep, mathlib, dl_lib, - cap_lib, epoll_shim_dep ] diff --git a/test/pwtest.c b/test/pwtest.c index 7094a59e1..89a385f53 100644 --- a/test/pwtest.c +++ b/test/pwtest.c @@ -19,9 +19,6 @@ #ifdef HAVE_PIDFD_OPEN #include #endif -#ifdef HAVE_LIBCAP -#include -#endif #include #include #include @@ -33,6 +30,7 @@ #include #include "spa/utils/ansi.h" +#include "spa/utils/cleanup.h" #include "spa/utils/string.h" #include "spa/utils/defs.h" #include "spa/utils/list.h" @@ -1298,39 +1296,20 @@ static void list_tests(struct pwtest_context *ctx) static bool is_debugger_attached(void) { - bool rc = false; -#ifdef HAVE_LIBCAP - int status; - int pid = fork(); + spa_autofree char *line = NULL; + size_t length = 0; - if (pid == -1) - return 0; + spa_autoptr(FILE) f = fopen("/proc/self/status", "re"); + if (!f) + return false; - if (pid == 0) { - int ppid = getppid(); - cap_t caps = cap_get_pid(ppid); - cap_flag_value_t cap_val; - - if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_EFFECTIVE, &cap_val) == -1 || - cap_val != CAP_SET) - _exit(false); - - if (ptrace(PTRACE_ATTACH, ppid, NULL, 0) == 0) { - waitpid(ppid, NULL, 0); - ptrace(PTRACE_CONT, ppid, NULL, 0); - ptrace(PTRACE_DETACH, ppid, NULL, 0); - rc = false; - } else { - rc = true; - } - _exit(rc); - } else { - waitpid(pid, &status, 0); - rc = WEXITSTATUS(status); + while (getline(&line, &length, f) >= 0) { + unsigned int tracer_pid; + if (sscanf(line, "TracerPid: %u", &tracer_pid) == 1) + return tracer_pid > 0; } -#endif - return !!rc; + return false; } static void usage(FILE *fp, const char *progname) diff --git a/test/test-spa-utils.c b/test/test-spa-utils.c index 06b8f8d17..43c2ffa2c 100644 --- a/test/test-spa-utils.c +++ b/test/test-spa-utils.c @@ -853,9 +853,6 @@ PWTEST(utils_snprintf_abort_neg_size) size_t size = pwtest_get_iteration(current_test); char dest[8]; - if (RUNNING_ON_VALGRIND) - return PWTEST_SKIP; - spa_scnprintf(dest, size, "1234"); /* expected to abort() */ return PWTEST_FAIL;