Merge branch 'master' into frankk/add_ctrl_to_filter_graph

This commit is contained in:
Frank Krick 2025-08-15 20:52:42 -04:00
commit 9b81e7043e
11 changed files with 453 additions and 419 deletions

View file

@ -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

View file

@ -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"

View file

@ -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'

View file

@ -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()

View file

@ -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);
}
}

View file

@ -6,7 +6,6 @@
#include <array>
#include <cstddef>
#include <deque>
#include <limits>
#include <optional>
#include <type_traits>
@ -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> camera;
const std::unique_ptr<CameraConfiguration> config;
FrameBufferAllocator *allocator = nullptr;
FrameBufferAllocator allocator;
std::vector<std::unique_ptr<libcamera::Request>> requestPool;
std::deque<libcamera::Request *> pendingRequests;
spa_ringbuffer completed_requests_rb = SPA_RINGBUFFER_INIT();
std::array<libcamera::Request *, MAX_BUFFERS> completed_requests;
void requestComplete(libcamera::Request *request);
@ -173,6 +170,38 @@ struct impl {
std::unique_ptr<CameraConfiguration> 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<impl *>(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<const libcamera::FrameBuffer::Plane> 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> 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<std::unique_ptr<FrameBuffer>> &bufs =
impl->allocator->buffers(stream);
const std::vector<libcamera::FrameBuffer::Plane> &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<unsigned int>(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<std::unique_ptr<FrameBuffer>> &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<unsigned int>(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<struct impl *>(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<struct impl *>(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<Request> &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();
if (!try_only)
spa_libcamera_close(impl);
goto done;
} 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;
}
}
if (port->current_format && !(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) {
spa_libcamera_use_buffers(impl, port, nullptr, 0);
port->current_format.reset();
}
res = spa_libcamera_set_format(impl, port, &info, try_only);
if (res < 0)
return res;
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)) {
if (!try_only)
port->current_format = info;
}
done:
if (try_only)
return 0;
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);

View file

@ -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;

View file

@ -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.

View file

@ -9,7 +9,6 @@ pwtest_deps = [
pipewire_dep,
mathlib,
dl_lib,
cap_lib,
epoll_shim_dep
]

View file

@ -19,9 +19,6 @@
#ifdef HAVE_PIDFD_OPEN
#include <sys/syscall.h>
#endif
#ifdef HAVE_LIBCAP
#include <sys/capability.h>
#endif
#include <sys/epoll.h>
#include <sys/ptrace.h>
#include <sys/resource.h>
@ -33,6 +30,7 @@
#include <valgrind/valgrind.h>
#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)

View file

@ -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;