diff --git a/0001-multiple-cam-demo-Changes-on-top-of-working-PW-and-W.patch b/0001-multiple-cam-demo-Changes-on-top-of-working-PW-and-W.patch deleted file mode 100644 index f316b7aae67aca1223fbf2de949e58d0442d6223..0000000000000000000000000000000000000000 --- a/0001-multiple-cam-demo-Changes-on-top-of-working-PW-and-W.patch +++ /dev/null @@ -1,542 +0,0 @@ -From e71c2cef383ccafa40ef5fd88a3bca9bda128118 Mon Sep 17 00:00:00 2001 -From: Ashok Sidipotu <ashok.sidipotu@collabora.com> -Date: Fri, 29 Oct 2021 13:55:07 +0530 -Subject: [PATCH] multiple cam demo: Changes on top of working PW and WP - -- PW version: 0.3.36 -- WP version: 0.4.3 ---- - src/config/wireplumber.conf | 18 ++++- - src/scripts/alsa-minimal.lua | 48 ++++++++++++ - src/scripts/policy-access.lua | 34 ++++++++ - src/scripts/policy-item.lua | 97 +++++++++++++++++++++++ - src/scripts/policy-link.lua | 142 ++++++++++++++++++++++++++++++++++ - src/scripts/v4l2.lua | 139 +++++++++++++++++++++++++++++++++ - 6 files changed, 475 insertions(+), 3 deletions(-) - create mode 100644 src/scripts/alsa-minimal.lua - create mode 100644 src/scripts/policy-access.lua - create mode 100644 src/scripts/policy-item.lua - create mode 100644 src/scripts/policy-link.lua - create mode 100644 src/scripts/v4l2.lua - -diff --git a/src/config/wireplumber.conf b/src/config/wireplumber.conf -index 0ac65c7..e411a08 100644 ---- a/src/config/wireplumber.conf -+++ b/src/config/wireplumber.conf -@@ -73,7 +73,19 @@ wireplumber.components = [ - - # The lua configuration file(s) - # Other components are loaded from there -- { name = main.lua, type = config/lua } -- { name = policy.lua, type = config/lua } -- { name = bluetooth.lua, type = config/lua } -+ # { name = main.lua, type = config/lua } -+ # { name = policy.lua, type = config/lua } -+ # { name = bluetooth.lua, type = config/lua } -+ # { name = policy-access.lua, type = script/lua } -+ -+ # demo -+ { name = policy-access.lua, type = script/lua } -+ { name = libwireplumber-module-si-audio-adapter, type = module } -+ { name = libwireplumber-module-si-node, type = module } -+ { name = policy-item.lua, type = script/lua } -+ { name = libwireplumber-module-si-standard-link, type = module } -+ { name = policy-link.lua, type = script/lua } -+ { name = alsa-minimal.lua, type = script/lua } -+ { name = v4l2.lua, type = script/lua } - ] -+ -diff --git a/src/scripts/alsa-minimal.lua b/src/scripts/alsa-minimal.lua -new file mode 100644 -index 0000000..93f0574 ---- /dev/null -+++ b/src/scripts/alsa-minimal.lua -@@ -0,0 +1,48 @@ -+function createDevice(parent, id, type, factory, properties) -+ -- set device name -+ properties["device.name"] = "alsa_card." .. properties["device.bus-path"] -+ -+ -- set device description (user-friendly name) -+ properties["device.description"] = properties["device.product.name"] -+ -+ Log.info("dev name="..properties["device.name"].."dev descp"..properties["device.description"].."factory is"..factory) -+ local device = SpaDevice(factory, properties) -+ device:connect("create-object", createNode) -+ device:activate(Feature.SpaDevice.ENABLED | Feature.Proxy.BOUND) -+ parent:store_managed_object(id, device) -+end -+ -+function createNode(parent, id, type, factory, properties) -+ -- setup node properties -+ properties["device.id"] = parent["bound-id"] -+ properties["factory.name"] = factory -+ -+ local device_properties = parent.properties -+ -+ local dev = properties["api.alsa.pcm.device"] -+ local subdev = properties["api.alsa.pcm.subdevice"] -+ local stream = properties["api.alsa.pcm.stream"] -+ -+ -- set node name -+ properties["node.name"] = -+ (stream == "capture" and "alsa_input" or "alsa_output") -+ .. "." .. -+ (device_properties["device.name"]:gsub("^alsa_card%.(.+)", "%1") or -+ device_properties["device.name"] or -+ "unnamed-device") -+ .. "." .. -+ (properties["device.profile.name"] or -+ (stream .. "." .. dev .. "." .. subdev)) -+ -+ -- set node description -+ properties["node.description"] = device_properties["device.description"] -+ -+ -- create the node -+ local node = Node("adapter", properties) -+ node:activate(Feature.Proxy.BOUND) -+ parent:store_managed_object(id, node) -+end -+ -+monitor = SpaDevice("api.alsa.enum.udev", {}) -+monitor:connect("create-object", createDevice) -+monitor:activate(Feature.SpaDevice.ENABLED) -diff --git a/src/scripts/policy-access.lua b/src/scripts/policy-access.lua -new file mode 100644 -index 0000000..ec8bcba ---- /dev/null -+++ b/src/scripts/policy-access.lua -@@ -0,0 +1,34 @@ -+clients_om = ObjectManager { -+ Interest { type = "client" } -+} -+ -+fact_om = ObjectManager { -+ Interest { type = "factory"} -+} -+ -+clients_om:connect("object-added", function(om, client) -+ local id = client["bound-id"] -+ local properties = client["properties"] -+ -+ if properties["application.process.host"] == Core.get_info()["host_name"] then -+ -- Local client added grant full access -+ client:update_permissions { ["any"] = "rwxm" } -+ else -+ Log.info(client, "remote client added"..tostring(client)) -+ -- first grant access to pw core -+ client:update_permissions { [0] = "rwxm" } -+ -+ -- second grant access to client node factory, so that a stream node can be created. -+ local cnf = fact_om:lookup { Constraint { "factory.name", "matches", "client-node", type = "pw-global" }, } -+ if not cnf then -+ Log.warning("access cannot be granted: unable to locate client node factory object"); -+ return -+ end -+ -+ Log.info(cnf, "client node factory id ".. cnf["bound-id"]) -+ client:update_permissions { [cnf["bound-id"]] = "rwxm" } -+ end -+end) -+ -+fact_om:activate() -+clients_om:activate() -diff --git a/src/scripts/policy-item.lua b/src/scripts/policy-item.lua -new file mode 100644 -index 0000000..d3e6a7b ---- /dev/null -+++ b/src/scripts/policy-item.lua -@@ -0,0 +1,97 @@ -+items = {} -+ -+remote_client_media_role = "Stream/Input/Video" -+ -+clients_om = ObjectManager { -+ Interest { type = "client" } -+} -+ -+function isNodeFromRemoteClient(node) -+ local np = node.properties -+ local media_class = np["media.class"] -+ Log.info(node, "id is "..node["bound-id"].." and its media.class is "..media_class); -+ -+ if(string.find(media_class,"Stream")) then -+ local client_id = np["client.id"] -+ local client = clients_om:lookup { Constraint { "bound-id", "=", client_id, type = "gobject" }, } -+ Log.info("client id is "..client_id.. tostring(client)); -+ local cp = client["properties"] -+ if cp["application.process.host"] ~= Core.get_info()["host_name"] then -+ return true -+ end -+ end -+end -+ -+function addItem (node, item_type) -+ local id = node["bound-id"] -+ local media_class = node.properties["media.class"] -+ -+ if isNodeFromRemoteClient(node) then -+ if media_class ~= remote_client_media_role then -+ Log.warning("remote client doesnt have priviledge to create session item"); -+ return -+ end -+ end -+ -+ -- create item -+ items[id] = SessionItem ( item_type ) -+ -+ if not items[id] then -+ Log.warning("failed to create session item for node " .. tostring(id)) -+ return -+ end -+ -+ -- configure item -+ if not items[id]:configure { -+ ["item.node"] = node, -+ ["media.class"] = node.properties["media.class"], -+ } then -+ Log.warning(items[id], "failed to configure item for node " .. tostring(id)) -+ return -+ end -+ -+ -- activate item -+ items[id]:activate (Features.ALL, function(item) -+ Log.info(item, "activated item for node " .. tostring(id)) -+ item:register() -+ end) -+end -+ -+nodes_om = ObjectManager { -+ Interest { -+ type = "node", -+ Constraint { "media.class", "#", "Stream/*", type = "pw-global" }, -+ }, -+ Interest { -+ type = "node", -+ Constraint { "media.class", "#", "Video/*", type = "pw-global" }, -+ }, -+ Interest { -+ type = "node", -+ Constraint { "media.class", "#", "Audio/*", type = "pw-global" }, -+ -- Constraint { "wireplumber.is-endpoint", "-", type = "pw" }, -+ }, -+} -+ -+nodes_om:connect("object-added", function(om, node) -+ local media_class = node.properties['media.class'] -+ Log.info(node,"object-added " .. media_class) -+ if string.find (media_class, "Audio") then -+ addItem(node, "si-audio-adapter") -+ else -+ addItem(node, "si-node") -+ end -+end) -+ -+nodes_om:connect("object-removed", function(om, node) -+ local id = node["bound-id"] -+ if items[id] then -+ -- remove item from the global registry -+ items[id]:remove() -+ -- remove item from the local table -+ items[id] = nil -+ end -+end) -+ -+nodes_om:activate() -+clients_om:activate() -\ No newline at end of file -diff --git a/src/scripts/policy-link.lua b/src/scripts/policy-link.lua -new file mode 100644 -index 0000000..ac46d7f ---- /dev/null -+++ b/src/scripts/policy-link.lua -@@ -0,0 +1,142 @@ -+linkables_om = ObjectManager { -+ Interest { -+ type = "SiLinkable", -+ Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node", type = "pw-global" }, -+ }, -+} -+ -+links_om = ObjectManager { -+ Interest { -+ type = "SiLink", -+ } -+} -+ -+function findTarget(node) -+ local target_class_assoc = { -+ ["Stream/Input/Audio"] = "Audio/Source", -+ ["Stream/Output/Audio"] = "Audio/Sink", -+ ["Stream/Input/Video"] = "Video/Source", -+ } -+ -+ local target_media_class = target_class_assoc[node.properties["media.class"]] -+ -+-- check if node.target(camera preference) is set -+ local node_target = node.properties["node.target"] -+ if node_target then -+ Log.info(node,"node.target is set to "..node_target) -+ local node_target_found; -+ -+ for si in linkables_om:iterate() do -+ local node = si:get_associated_proxy("node") -+ Log.info(si,"node="..tostring(node).."node bound.id="..node["bound-id"]) -+ -+ if tostring(node["bound-id"]) == tostring(node_target) then -+ node_target_found = true; -+ Log.info(node,"node.target found") -+ return si -+ end -+ -+ end -+ if not node_target_found then -+ Log.info(node, "node.target="..node_target.." seems to be invalid".. -+ "are you sure it points to right cam source node?") -+ end -+ end -+ -+-- pick any available cam source as no explicit preferece is set. -+ for si in linkables_om:iterate() do -+ local node = si:get_associated_proxy("node") -+ if node.properties["media.class"] == target_media_class then -+ return si -+ end -+ end -+ return nil -+end -+ -+function createLink(item, target) -+ local node = item:get_associated_proxy ("node") -+ local target_node = target:get_associated_proxy ("node") -+ local media_class = node.properties["media.class"] -+ local target_media_class = target_node.properties["media.class"] -+ local out_item = nil -+ local in_item = nil -+ -+ if string.find (media_class, "Input") or -+ string.find (media_class, "Sink") then -+ -- capture -+ out_item = target -+ in_item = item -+ else -+ -- playback -+ out_item = item -+ in_item = target -+ end -+ -+ Log.info (string.format("link %s(%d) <-> %s(%d)", -+ tostring(node.properties["node.name"]), -+ node["bound-id"], -+ tostring(target_node.properties["node.name"]), -+ target_node["bound-id"])) -+ -+ -- create and configure link -+ local link = SessionItem("si-standard-link") -+ if not link:configure { -+ ["out.item"] = out_item, -+ ["in.item"] = in_item, -+ ["out.item.port.context"] = "output", -+ ["in.item.port.context"] = "input", -+ } then -+ Log.warning(link, "failed to configure si-standard-link") -+ return -+ end -+ -+ -- register & activate -+ link:register() -+ link:activate(Feature.SessionItem.ACTIVE, function (l, e) -+ if e ~= nil then -+ Log.warning (l, "failed to activate si-standard-link") -+ link:remove () -+ else -+ Log.info (l, "activated si-standard-link") -+ end -+ end) -+end -+ -+linkables_om:connect("object-added", function(om, item) -+ -- only handle session items that has a node associated proxy -+ local node = item:get_associated_proxy("node") -+ local media_class = node.properties["media.class"] -+ Log.info (node, "handling linkable item " .. tostring(node.properties["node.name"])) -+ -+ -- check all the links to see if the item is already linked -+ for link in links_om:iterate() do -+ local out_id = tonumber(link.properties["out.item.id"]) -+ local in_id = tonumber(link.properties["in.item.id"]) -+ if out_id == item.id or in_id == item.id then -+ -- already linked, nothing to do -+ return -+ end -+ end -+ -+ local target = findTarget(node) -+ if target then -+ createLink(item, target) -+ else -+ Log.info("unable to findTarget for "..tostring(node)); -+ end -+end) -+ -+linkables_om:connect("object-removed", function(om, item) -+ -- remove any links associated with this item -+ for link in links_om:iterate() do -+ local out_id = tonumber (link.properties["out.item.id"]) -+ local in_id = tonumber (link.properties["in.item.id"]) -+ if out_id == item.id or in_id == item.id then -+ link:remove () -+ Log.info(link, "link removed") -+ end -+ end -+end) -+ -+links_om:activate() -+linkables_om:activate() -diff --git a/src/scripts/v4l2.lua b/src/scripts/v4l2.lua -new file mode 100644 -index 0000000..fd9a20d ---- /dev/null -+++ b/src/scripts/v4l2.lua -@@ -0,0 +1,139 @@ -+-- WirePlumber -+-- -+-- Copyright © 2021 Collabora Ltd. -+-- @author George Kiagiadakis <george.kiagiadakis@collabora.com> -+-- -+-- SPDX-License-Identifier: MIT -+ -+local config = ... or {} -+ -+-- preprocess rules and create Interest objects -+for _, r in ipairs(config.rules or {}) do -+ r.interests = {} -+ for _, i in ipairs(r.matches) do -+ local interest_desc = { type = "properties" } -+ for _, c in ipairs(i) do -+ c.type = "pw" -+ table.insert(interest_desc, Constraint(c)) -+ end -+ local interest = Interest(interest_desc) -+ table.insert(r.interests, interest) -+ end -+ r.matches = nil -+end -+ -+-- applies properties from config.rules when asked to -+function rulesApplyProperties(properties) -+ for _, r in ipairs(config.rules or {}) do -+ if r.apply_properties then -+ for _, interest in ipairs(r.interests) do -+ if interest:matches(properties) then -+ for k, v in pairs(r.apply_properties) do -+ properties[k] = v -+ end -+ end -+ end -+ end -+ end -+end -+ -+function findDuplicate(parent, id, property, value) -+ for i = 0, id - 1, 1 do -+ local obj = parent:get_managed_object(i) -+ if obj and obj.properties[property] == value then -+ return true -+ end -+ end -+ return false -+end -+ -+function createNode(parent, id, type, factory, properties) -+ local dev_props = parent.properties -+ -+ -- set the device id and spa factory name; REQUIRED, do not change -+ properties["device.id"] = parent["bound-id"] -+ properties["factory.name"] = factory -+ -+ -- set the default pause-on-idle setting -+ properties["node.pause-on-idle"] = false -+ -+ -- set the node name -+ local name = -+ (factory:find("sink") and "v4l2_output") or -+ (factory:find("source") and "v4l2_input" or factory) -+ .. "." .. -+ (dev_props["device.name"]:gsub("^v4l2_device%.(.+)", "%1") or -+ dev_props["device.name"] or -+ dev_props["device.nick"] or -+ dev_props["device.alias"] or -+ "v4l2-device") -+ -- sanitize name -+ name = name:gsub("([^%w_%-%.])", "_") -+ -+ properties["node.name"] = name -+ -+ -- deduplicate nodes with the same name -+ for counter = 2, 99, 1 do -+ if findDuplicate(parent, id, "node.name", properties["node.name"]) then -+ properties["node.name"] = name .. "." .. counter -+ else -+ break -+ end -+ end -+ -+ -- set the node description -+ local desc = dev_props["device.description"] or "v4l2-device" -+ -- sanitize description, replace ':' with ' ' -+ properties["node.description"] = desc:gsub("(:)", " ") -+ -+ -- apply properties from config.rules -+ rulesApplyProperties(properties) -+ -+ -- create the node -+ local node = Node("spa-node-factory", properties) -+ node:activate(Feature.Proxy.BOUND) -+ parent:store_managed_object(id, node) -+end -+ -+function createDevice(parent, id, type, factory, properties) -+ -- ensure the device has an appropriate name -+ local name = "v4l2_device." .. -+ (properties["device.name"] or -+ properties["device.bus-id"] or -+ properties["device.bus-path"] or -+ tostring(id)):gsub("([^%w_%-%.])", "_") -+ -+ properties["device.name"] = name -+ -+ -- deduplicate devices with the same name -+ for counter = 2, 99, 1 do -+ if findDuplicate(parent, id, "device.name", properties["device.name"]) then -+ properties["device.name"] = name .. "." .. counter -+ else -+ break -+ end -+ end -+ -+ -- ensure the device has a description -+ properties["device.description"] = -+ properties["device.description"] -+ or properties["device.product.name"] -+ or "Unknown device" -+ -+ -- apply properties from config.rules -+ rulesApplyProperties(properties) -+ -+ -- create the device -+ local device = SpaDevice(factory, properties) -+ device:connect("create-object", createNode) -+ device:activate(Feature.SpaDevice.ENABLED | Feature.Proxy.BOUND) -+ parent:store_managed_object(id, device) -+end -+ -+monitor = SpaDevice("api.v4l2.enum.udev", config.properties or {}) -+if monitor then -+ monitor:connect("create-object", createDevice) -+ monitor:activate(Feature.SpaDevice.ENABLED) -+else -+ Log.message("PipeWire's V4L SPA missing or broken. Video4Linux not supported.") -+end --- -2.30.2 - diff --git a/Dockerfile b/Dockerfile index 208aa4215c198d5c18c29abf82c041c5dc1ff3a2..04a2a78299725bb13e76dfd6f85f581892228850 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,40 +1,15 @@ -FROM debian:stable +FROM debian:unstable ENV LANG C.UTF-8 RUN apt-get update && apt-get install -y \ - debhelper-compat \ - findutils \ - git \ - ninja-build meson \ - pkg-config \ - libdbus-1-dev \ - libglib2.0-dev \ - libgirepository1.0-dev \ - doxygen \ - python3-lxml \ - libsndfile1-dev \ - libgstreamer1.0-dev \ - libgstreamer-plugins-base1.0-dev \ - gstreamer1.0-tools \ - systemd \ - libgl1-mesa-glx \ - libgl1-mesa-dri \ - mesa-utils \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -RUN git clone --depth=1 --branch=master https://gitlab.freedesktop.org/pipewire/pipewire.git \ - && meson build-pw pipewire --prefix=/usr \ - -Dsession-managers="['wireplumber']" -Ddefault-session-manager=wireplumber \ - && ninja -C build-pw install \ - && rm -rf build-pw pipewire - -# RUN rm /bin/sh && ln -s /bin/bash /bin/sh -# SHELL ["/bin/bash", "-c"] -# RUN echo 'alias hi="echo hello"' >> ~/.bashrc - -# RUN echo 'alias s="echo hello"' >> ~/.bashrc -# RUN echo 'export PIPEWIRE_RUNTIME_DIR=/run/user/1000' >> ~/.bashrc -# RUN echo 'export XDG_RUNTIME_DIR=/run/user/$UID' >> ~/.bashrc -# # RUN /usr/bin/source ~/.bashrc + gstreamer1.0-tools \ + gstreamer1.0-pipewire \ + gstreamer1.0-plugins-good \ + gstreamer1.0-plugins-base \ + gstreamer1.0-gl \ + pipewire-audio-client-libraries \ + pipewire \ + wireplumber \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ No newline at end of file diff --git a/README.md b/README.md index 1e2d85a5671f3a02975660c4e1f560f24b3f6d2a..1e82f1cf91b8363d96babdd6ad17d3efc342b152 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ + # pipewire-multiple-camera-feed-access-demo A demo to showcase the capabilities of pipewire in letting multiple clients running in containers, access multiple cameras available in the system. +- [pipewire-multiple-camera-feed-access-demo](#pipewire-multiple-camera-feed-access-demo) +- [**Creating Container:**](#creating-container) +- [**Setting up pipewire:**](#setting-up-pipewire) +- [**Setting up wireplumber:**](#setting-up-wireplumber) + - [Method 1](#method-1) + - [Method 2](#method-2) -**Steps:** - -Below are the instructions to re-create the demo. - -**Creating Container:** +# **Creating Container:** Podman is chosen as the container for this demo. First step is to install podman and then. @@ -15,31 +18,60 @@ Podman is chosen as the container for this demo. First step is to install podman ``` cd multiple-camera-feed-access-demo -podman build --tag debian-pipewire . +podman build --tag debian-unstable-pipewire . ``` The Above build command places takes the dockerfile(included in the repositiory). - Run Podman: ``` -podman run -it --rm -e DISPLAY=$DISPLAY -e PIPEWIRE_RUNTIME_DIR=/run/user/1000 -e XDG_RUNTIME_DIR=/run/user/$UID -v /run/user/1000/pipewire-0:/run/user/1000/pipewire-0 -v /tmp/.X11-unix:/tmp/.X11-unix:rw -v ./aliases:/tmp/.bash_aliases pipewire-podman /bin/bash --init-file /tmp/.bash_aliases +podman run -it --rm -e WAYLAND_DISPLAY=wayland-0 \ + -e XDG_RUNTIME_DIR=/run/user/0 \ + -v /run/user/1000/pipewire-0:/run/user/0/pipewire-0:Z \ + -v /run/user/1000/wayland-0:/run/user/0/wayland-0:Z \ + -v /dev/dri:/dev/dri \ + --security-opt label=disable \ + -v ./aliases:/tmp/.bash_aliases:Z \ + debian-unstable-pipewire /bin/bash --init-file /tmp/.bash_aliases ``` -**Setting up pipewire:** +# **Setting up pipewire:** -This config file contains changes to open pipewire in restricted moded. +- configure pipewire in restricted acces mode. +- stop pipewire deamon +``` +systemctl --user stop pipewire.service pipewire.socket wireplumber.service pipewire-pulse.service pipewire-pulse.socket ``` -sudo cp pipewire.conf /etc/pipewire/pipewire.conf +- start pipewire with new config file +- restart the pipewire with custom conf file(the only diff is this conf file is the the pipewire access permissions). ``` +pipewire -c <path>/pipewire.conf 2>&1 | tee ~/pipewire.log +``` + +# **Setting up wireplumber:** + +## Method 1 -**Setting up wireplumber:** -- Update the wireplumber.conf +- stop wireplumber demon(when u kill pipewire wireplumber goes down as well) + +> $ systemctl --user stop wireplumber.service + +- Update the wireplumber.conf and start it from the source ``` -cp wireplumber.conf <pipewire>/subprojects/wireplumber/src/config/ +cp wireplumber.conf <wireplumber src path>/wireplumber/src/config/ ``` - Update the Lua config files ``` -cp lua-policy-files/* <pipewire>/subprojects/wireplumber/src/scripts/ +cp lua-policy-files/* <wireplumber src path>/src/scripts/ +``` +- run wireplumber from **source** ``` +cd $HOME/code/pipewire/subprojects/wireplumber/ +make run 2>$2 & +``` + +## Method 2 + +- apply wireplumber-0.4.5-22-gbe7b95c+cameras_demo.diff and then recompile and re-start the wireplumber. George has taken things in the Method 1 and then churned out Meathod 2 \ No newline at end of file diff --git a/demo-secure-video-cam-access.mp4 b/demo-secure-video-cam-access.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..da41892f54a7cc61fb04da294d9a8df3d955965e Binary files /dev/null and b/demo-secure-video-cam-access.mp4 differ diff --git a/pipewire.conf b/pipewire.conf index 28d0dc947b33014316199d6b22346d924a981d46..b2ae148b838cc63a1a4a36bdcce55bbc04116fe1 100644 --- a/pipewire.conf +++ b/pipewire.conf @@ -1,6 +1,6 @@ -# Daemon config file for PipeWire version "0.3.33" # +# Daemon config file for PipeWire version "0.3.40" # # -# Copy and edit this file in /etc/pipewire for systemwide changes +# Copy and edit this file in /etc/pipewire for system-wide changes # or in ~/.config/pipewire for local changes. context.properties = { @@ -15,9 +15,10 @@ context.properties = { #mem.mlock-all = false #clock.power-of-two-quantum = true #log.level = 2 + #cpu.zero.denormals = true - core.daemon = true # listening for socket connections - core.name = pipewire-0 # core name and socket name + core.daemon = true # listening for socket connections + core.name = pipewire-0 # core name and socket name ## Properties for the DSP configuration. #default.clock.rate = 48000 @@ -32,7 +33,7 @@ context.properties = { # # These overrides are only applied when running in a vm. vm.overrides = { - default.clock.min-quantum = 1024 + default.clock.min-quantum = 1024 } } @@ -56,8 +57,8 @@ context.spa-libs = { } context.modules = [ - #{ name = <module-name> - # [ args = { <key> = <value> ... } ] + #{ name = <module-name> + # [ args = { <key> = <value> ... } ] # [ flags = [ [ ifexists ] [ nofail ] ] #} # @@ -67,7 +68,7 @@ context.modules = [ # # Uses RTKit to boost the data thread priority. - { name = libpipewire-module-rtkit + { name = libpipewire-module-rtkit args = { #nice.level = -11 #rt.prio = 88 @@ -78,7 +79,7 @@ context.modules = [ } # Set thread priorities without using RTKit. - #{ name = libpipewire-module-rt + #{ name = libpipewire-module-rt # args = { # nice.level = -11 # rt.prio = 88 @@ -89,52 +90,50 @@ context.modules = [ #} # The native communication protocol. - { name = libpipewire-module-protocol-native } + { name = libpipewire-module-protocol-native } # The profile module. Allows application to access profiler # and performance data. It provides an interface that is used # by pw-top and pw-profiler. - { name = libpipewire-module-profiler } + { name = libpipewire-module-profiler } # Allows applications to create metadata objects. It creates # a factory for Metadata objects. - { name = libpipewire-module-metadata } + { name = libpipewire-module-metadata } # Creates a factory for making devices that run in the # context of the PipeWire server. - { name = libpipewire-module-spa-device-factory } + { name = libpipewire-module-spa-device-factory } # Creates a factory for making nodes that run in the # context of the PipeWire server. - { name = libpipewire-module-spa-node-factory } + { name = libpipewire-module-spa-node-factory } # Allows creating nodes that run in the context of the # client. Is used by all clients that want to provide # data to PipeWire. - { name = libpipewire-module-client-node } + { name = libpipewire-module-client-node } # Allows creating devices that run in the context of the # client. Is used by the session manager. - { name = libpipewire-module-client-device } + { name = libpipewire-module-client-device } # The portal module monitors the PID of the portal process # and tags connections with the same PID as portal # connections. - { name = libpipewire-module-portal + { name = libpipewire-module-portal flags = [ ifexists nofail ] } # The access module can perform access checks and block # new clients. - { name = libpipewire-module-access + { name = libpipewire-module-access args = { # access.allowed to list an array of paths of allowed # apps. access.allowed = [ - /home/ashok/Documents/code/pipewire/subprojects/wireplumber/build/src/wireplumber + wireplumber /usr/bin/gnome-shell - /usr/libexec/gsd-media-keys - /snap/vlc/2344/usr/bin/vlc ] # An array of rejected paths. @@ -151,17 +150,17 @@ context.modules = [ # Makes a factory for wrapping nodes in an adapter with a # converter and resampler. - { name = libpipewire-module-adapter } + { name = libpipewire-module-adapter } # Makes a factory for creating links between ports. - { name = libpipewire-module-link-factory } + { name = libpipewire-module-link-factory } # Provides factories to make session manager objects. - { name = libpipewire-module-session-manager } + { name = libpipewire-module-session-manager } ] context.objects = [ - #{ factory = <factory-name> + #{ factory = <factory-name> # [ args = { <key> = <value> ... } ] # [ flags = [ [ nofail ] ] #} @@ -169,7 +168,7 @@ context.objects = [ # Creates an object from a PipeWire factory with the given parameters. # If nofail is given, errors are ignored (and no object is created). # - #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc Spa:Pod:Object:Param:Props:patternType = 1 } } + #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc Spa:Pod:Object:Param:Props:patternType = 1 } } #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } @@ -178,7 +177,7 @@ context.objects = [ # A default dummy driver. This handles nodes marked with the "node.always-driver" # property when no other driver is currently active. JACK clients need this. - { factory = spa-node-factory + { factory = spa-node-factory args = { factory.name = support.node.driver node.name = Dummy-Driver @@ -186,7 +185,7 @@ context.objects = [ priority.driver = 20000 } } - { factory = spa-node-factory + { factory = spa-node-factory args = { factory.name = support.node.driver node.name = Freewheel-Driver @@ -197,7 +196,7 @@ context.objects = [ } # This creates a new Source node. It will have input ports # that you can link, to provide audio for this source. - #{ factory = adapter + #{ factory = adapter # args = { # factory.name = support.null-audio-sink # node.name = "my-mic" @@ -210,21 +209,21 @@ context.objects = [ # This creates a single PCM source device for the given # alsa device path hw:0. You can change source to sink # to make a sink in the same way. - #{ factory = adapter + #{ factory = adapter # args = { - # factory.name = api.alsa.pcm.source - # node.name = "alsa-source" - # node.description = "PCM Source" - # media.class = "Audio/Source" - # api.alsa.path = "hw:0" - # #api.alsa.period-size = 1024 - # #api.alsa.headroom = 0 - # #api.alsa.disable-mmap = false - # #api.alsa.disable-batch = false - # #audio.format = "S16LE" - # #audio.rate = 48000 - # #audio.channels = 2 - # #audio.position = "FL,FR" + # factory.name = api.alsa.pcm.source + # node.name = "alsa-source" + # node.description = "PCM Source" + # media.class = "Audio/Source" + # api.alsa.path = "hw:0" + # api.alsa.period-size = 1024 + # api.alsa.headroom = 0 + # api.alsa.disable-mmap = false + # api.alsa.disable-batch = false + # audio.format = "S16LE" + # audio.rate = 48000 + # audio.channels = 2 + # audio.position = "FL,FR" # } #} ] @@ -238,7 +237,7 @@ context.exec = [ # but it is better to start it as a systemd service. # Run the session manager with -h for options. # - #{ path = "/usr/bin/pipewire-media-session" args = "" } + #{ path = "/usr/bin/pipewire-media-session" args = "" } # # You can optionally start the pulseaudio-server here as well # but it is better to start it as a systemd service. diff --git a/secure-video-cam-access.odp b/secure-video-cam-access.odp new file mode 100644 index 0000000000000000000000000000000000000000..194349021bcad6c37f8a2e1524ca63262e8d5290 Binary files /dev/null and b/secure-video-cam-access.odp differ diff --git a/slides-secure-video-cam-access.mp4 b/slides-secure-video-cam-access.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..f0af065a2e4d7a38ba4d08f3bf9a61a69ae737cb Binary files /dev/null and b/slides-secure-video-cam-access.mp4 differ diff --git a/wireplumber-0.4.5-22-gbe7b95c+cameras_demo.diff b/wireplumber-0.4.5-22-gbe7b95c+cameras_demo.diff new file mode 100644 index 0000000000000000000000000000000000000000..5dcd19c6fb57e0cdcf77ea3c9bdd2df79e370731 --- /dev/null +++ b/wireplumber-0.4.5-22-gbe7b95c+cameras_demo.diff @@ -0,0 +1,101 @@ +diff --git a/src/config/main.lua.d/50-default-access-config.lua b/src/config/main.lua.d/50-default-access-config.lua +index 6cf18be..ed0ed92 100644 +--- a/src/config/main.lua.d/50-default-access-config.lua ++++ b/src/config/main.lua.d/50-default-access-config.lua +@@ -26,6 +26,6 @@ default_access.rules = { + { "pipewire.access", "=", "restricted" }, + }, + }, +- default_permissions = "rx", ++ default_permissions = "rwxm", + }, + } +diff --git a/src/scripts/access/access-default.lua b/src/scripts/access/access-default.lua +index 3c27e90..a7688d2 100644 +--- a/src/scripts/access/access-default.lua ++++ b/src/scripts/access/access-default.lua +@@ -37,17 +37,40 @@ end + clients_om = ObjectManager { + Interest { type = "client" } + } ++fact_om = ObjectManager { ++ Interest { type = "factory"} ++} + + clients_om:connect("object-added", function (om, client) + local id = client["bound-id"] + local properties = client["properties"] + +- local perms = rulesGetDefaultPermissions(properties) ++ if properties["application.process.host"] == Core.get_info()["host_name"] then ++ -- Local client added grant standard access ++ local perms = rulesGetDefaultPermissions(properties) ++ ++ if perms then ++ Log.info(client, "Granting permissions to client " .. id .. ": " .. perms) ++ client:update_permissions { ["any"] = perms } ++ end ++ else ++ -- Container client, limited access ++ Log.info(client, "container client added"..tostring(client)) ++ ++ -- first grant access to pw core ++ client:update_permissions { [0] = "rwxm" } ++ ++ -- second grant access to client node factory, so that a stream node can be created. ++ local cnf = fact_om:lookup { Constraint { "factory.name", "matches", "client-node", type = "pw-global" }, } ++ if not cnf then ++ Log.warning("access cannot be granted: unable to locate client node factory object"); ++ return ++ end + +- if perms then +- Log.info(client, "Granting permissions to client " .. id .. ": " .. perms) +- client:update_permissions { ["any"] = perms } ++ Log.info(cnf, "client node factory id ".. cnf["bound-id"]) ++ client:update_permissions { [cnf["bound-id"]] = "rwxm" } + end + end) + ++fact_om:activate() + clients_om:activate() +diff --git a/src/scripts/create-item.lua b/src/scripts/create-item.lua +index 5c22f60..2697328 100644 +--- a/src/scripts/create-item.lua ++++ b/src/scripts/create-item.lua +@@ -56,7 +56,35 @@ function configProperties(node) + return properties + end + ++clients_om = ObjectManager { ++ Interest { type = "client" } ++} ++clients_om:activate() ++ ++function containerCheck(node) ++ local np = node.properties ++ local media_class = np["media.class"] ++ if string.find(media_class,"Stream") then ++ local client_id = np["client.id"] ++ local client = clients_om:lookup { Constraint { "bound-id", "=", client_id, type = "gobject" }, } ++ Log.info("client id is "..client_id.. tostring(client)); ++ local cp = client["properties"] ++ if cp["application.process.host"] ~= Core.get_info()["host_name"] then ++ -- client is from container ++ if media_class ~= "Stream/Input/Video" then ++ client:send_error(node["bound-id"], -1, "not permitted") ++ return false ++ end ++ end ++ end ++ return true ++end ++ + function addItem (node, item_type) ++ if not containerCheck(node) then ++ return ++ end ++ + local id = node["bound-id"] + + -- create item diff --git a/wireplumber.conf b/wireplumber.conf index e411a0859f959d2e9ab3929390dbba3ee799753a..50c703306b33a4812f65ee6cb3dc351bb4b8befe 100644 --- a/wireplumber.conf +++ b/wireplumber.conf @@ -78,7 +78,7 @@ wireplumber.components = [ # { name = bluetooth.lua, type = config/lua } # { name = policy-access.lua, type = script/lua } - # demo + #pipewire-multiple-camera-feed-access-demo { name = policy-access.lua, type = script/lua } { name = libwireplumber-module-si-audio-adapter, type = module } { name = libwireplumber-module-si-node, type = module }