Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Pipewire unified wrappers #6669

Open
wants to merge 21 commits into
base: master
Choose a base branch
from

Conversation

columbarius
Copy link
Contributor

Description

Unify pipewire.c api in the linux-pipewire plugin

Motivation and Context

We want to create a basic pipewire wrapper to share code between multiple obs plugins. This MR should create the basis for discussions to create sth. useful for various video and audio plugins.

How Has This Been Tested?

Types of changes

  • Code cleanup (non-breaking change which makes code smaller or more readable)

Checklist:

  • My code has been run through clang-format.
  • I have read the contributing document.
  • My code is not on the master branch.
  • The code has been tested.
  • All commit messages are properly formatted and commits squashed where appropriate.
  • I have included updates to all appropriate documentation.

@columbarius
Copy link
Contributor Author

Relevant MRs:
#5377
#5728
#6207

@columbarius
Copy link
Contributor Author

CC: @GeorgesStavracas @dimtpap

@columbarius
Copy link
Contributor Author

Moved comment from #5728 (comment)

We separate camera plugin and registry.

  • camera-plugin: init_registry -> query_cameras -> user selects camera -> query_registry for fd and id -> create stream like screencast -> ... -> unref_registry
  • camera-registry: global refcounted struct, which deals with the camera/device-portal. Is created on first init_registry call and destroyed if the refcount is 0. It queries the portal for a filedescriptor and opens the connection and tracks all announced cameras.

In principle, I think separating different moving parts into their own unit is good. My idea with #6645 is that all the code under pipewire.c is generic and can be used by all source types - even audio sources.

I don't know if it would be feasible or even possible, but I think you can already make this camera-registry thing completely generic, so it can be reused by the audio source at #6207. It'll probably need to be implemented in pipewire.c directly, perhaps as follows:

* Split the code that actually initializes PipeWire from `obs_pipewire_create` into a new `obs_pipewire_start`

* Add a new vfunc struct `obs_pipewire_device_callbacks` with the regular `device_added`, `device_removed`, `device_changed`, etc callback (please feel free to suggest better naming here)

* Add `obs_pipewire_set_device_callbacks()`

That way the camera portal code will be able to react to PipeWire adding and removing cameras, without having to deal with PipeWire itself.

What do you think?

I like the idea to build a registry wrapper with these callbacks. I'm not sure a obs_pipewire_set_device_callbacks() is feasable but an obs_pipewire_registry_create() with these callbacks as an argument (see stream_events here) will be.

An alternative would be to not lazyload the registry, but start it when obs is launched. This way we can only add the camera plugin, if obs is allowed to access the portal.
If we lazy load I would suggest to work asynchronous with callbacks from the registry to the plugin after it's initialized, but that's just some idea.

I think the best approach here is to do what I've done with the screencasting code, and completely separate camera portal code from PipeWire itself. What the plugin needs to do is to expose the pw-camera sources all the time, and only query the Device Access portal when the user actually adds a pw-camera source. If camera access is denied, we show an error message in the properties dialog.

I think we might talk about different things here: The screencast portal creates for each stream a new pipewire_fd together with the pipewire_id which the client can use to access only this single stream. Iirc the camera portal only grants access to a single pipewire_fd, which can be used to access pipewire on the host with permissions on the connection only to show the client cameras, etc. In this case the client is responsible to set up a registry to get the pipewire_id of each camera. Can't we just use the provided pipewire_fd together with the pipewire_id, obtained from the registry, and just create a new stream (including it's own pw_core, pw_thread_loop, etc.) with those?

Anyways: should I just continue to rebase the current state, or should I continue with my new proposal?

Considering what I've suggested above, feel free to continue with your new proposal

This way we could create one wrapper each for pipewire_stream, pipewire_registry (with the suggested callbacks) and pipewire_node (@dimtpap could you extract such a wrapper from your audio work?)

@columbarius
Copy link
Contributor Author

Btw. creating pw_core and pw_thread_loop and such things should be done by static functions inside pipewire.c and shared between the wrapper APIs.

@columbarius columbarius changed the title Draft: Pipewire unified Draft: Pipewire unified wrappers Jun 28, 2022
@columbarius columbarius mentioned this pull request Jun 28, 2022
6 tasks
@columbarius
Copy link
Contributor Author

Added registry stuff, nothing tested and just a sketch.

@columbarius
Copy link
Contributor Author

Does anyone know how to do fa935cc in nice?

@dimtpap
Copy link

dimtpap commented Jun 29, 2022

Does anyone know how to do fa935cc in nice?

Why not make it a regular struct and pass it to pw_core_add_listener

Copy link
Member

@GeorgesStavracas GeorgesStavracas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this PR needs some work, but ultimately what it tries to achieve is desired for the maintainability of the plugin. Technical details aside, I think the internal API here is a bit odd in some places. What I had in mind with device registry callbacks and whatnot was something like this:

struct obs_pipewire_device_callbacks {
	void (*device_added)(void *user_data, obs_pipewire_device *device);
	void (*device_removed)(void *user_data, obs_pipewire_device *device);
};

void obs_pipewire_add_device_callbacks(
	obs_pipewire_data *obs_pw, 
	const struct obs_pipewire_device_callbacks *device_callbacks);

void obs_pipewire_remove_device_callbacks(
	obs_pipewire_data *obs_pw, 
	const struct obs_pipewire_device_callbacks *device_callbacks);

To properly do this, we would need to split the initialization of obs_pipewire in 2 steps:

obs_pipewire_data * obs_pipewire_create(int pipewire_fd);

obs_pipewire_stream *
obs_pipewire_connect_stream(
	obs_pipewire_data *obs_pw,
	uint32_t stream_node,
	const char *stream_name,
	struct pw_properties *stream_properties);

The screencast portal code would look more or less like this:

	capture->obs_pw = obs_pipewire_create(pipewire_fd);
	capture->pipewire_stream = obs_pipewire_connect_stream(capture->obs_pw,
		capture->pipewire_node, "OBS Studio",
		pw_properties_new(PW_KEY_MEDIA_TYPE, "Video",
				  PW_KEY_MEDIA_CATEGORY, "Capture",
				  PW_KEY_MEDIA_ROLE, "Screen", NULL)
	obs_pipewire_stream_set_cursor_visible(capture->pipewire_stream,
					capture->cursor_visible);

The camera portal code would look more or less like this:

static const struct obs_pipewire_device_callbacks camera_device_callbacks = {
	camera_portal_device_added,
	camera_portal_device_remove,
};

...

	camera->obs_pw = obs_pipewire_create(pipewire_fd);
	obs_pipewire_add_device_callbacks(camera->obs_pw, &camera_device_callbacks);

... (every time someone selects a camera) ...

	capture->pipewire_stream = obs_pipewire_connect_stream(camera->obs_pw,
		device->pipewire_node, "OBS Studio",
		pw_properties_new(PW_KEY_MEDIA_TYPE, ...,
				  PW_KEY_MEDIA_CATEGORY, ...,
				  PW_KEY_MEDIA_ROLE, "Screen", NULL));

plugins/linux-pipewire/pipewire.h Outdated Show resolved Hide resolved
plugins/linux-pipewire/pipewire.c Outdated Show resolved Hide resolved
plugins/linux-pipewire/pipewire.h Outdated Show resolved Hide resolved
plugins/linux-pipewire/pipewire.c Outdated Show resolved Hide resolved
@columbarius columbarius force-pushed the pipewire-unified branch 2 times, most recently from 7c806b3 to cd9951e Compare July 12, 2022 16:23
@columbarius
Copy link
Contributor Author

Added the case of an output stream to pipewire.c in planing of the virtualcamera pluging.

@columbarius
Copy link
Contributor Author

@GeorgesStavracas I'm pretty happy with your ideas! From my side it's ready for review.

@columbarius columbarius marked this pull request as ready for review July 12, 2022 23:32
GeorgesStavracas and others added 11 commits September 12, 2022 01:07
Instead of returning an opaque void* pointer, return the leaf
C type obs_pipewire_data*.

This commit introduces no functional changes.
When the linux-pipewire plugin is unloaded, make sure to cleanup
the D-Bus proxy for the ScreenCast portal too. This effectively
doesn't change anything, but it's always good to keep up with the
code hygiene.
This commit makes the stuff in pipewire.c more independent from the
actual plugin by providing a wrapper to create a stream with a set or
properties defined by the plugin.
This separation of obs_pipewire_create() and play_pipewire_stream()
was an artifact of how the original code was written, and there is
no reason to keep this separation anymore. Inlining it will help
future commits too.
We don't actually need to restore the node id anymore, since this
is handled by the screencast-portal.c code now.

Remove the pipewire_node field, and just create the stream directly
from the passed now.
This commit introduces an important distinction in the initialization
process that is essential to future camera and audio work: it splits
creating and connecting to the PipeWire socket, from connecting to
specific PipeWire nodes.

Right now, for the only consumer in existance - the screencast portal
code - this distinction is irrelevant, but from an API prespective it
just makes sense to model it this way.
Next commits will introduce new obs_pipewire_* types, so this
renaming will make it slightly easier to read the code with
different types.
This will make code easier to follow after next commit.
Sorry, this is a painful commit to review :(

Until now, the only consumer of the PipeWire code is the screen
cast portal code. This portal code only ever has one PipeWire
connection, and one PipeWire stream, every time a monitor or a
window is selected. This is reflected on obs_pipewire, which only
handles a single stream for any given connection.

For cameras and audio, however, a single PipeWire connection can
and most likely always will provide multiple streams. For example,
computers with multiple webcams will have one PipeWire connection
reporting multiple PipeWire nodes, one node for each camera.

This commit breaks this one-stream-per-connection assumption, and
makes each call to obs_pipewire_connect_stream() return a completely
separate stream object.

A new structure, obs_pipewire_stream, is introduced to represent
a PipeWire stream. The screencast portal code continues to only
ever have one connection and one stream, and that's made explicit
in the code.
Make obs_pipewire hold a list of streams created by itself, and
destroy these streams when closing the connection.
We'll need to peek information about the source to determine how
to process frames, so store it for later usage.
GeorgesStavracas and others added 10 commits September 12, 2022 01:09
So far we've been treating format info on a per-connection basis,
but now that a single connection is capable of hosting multiple
streams, and each stream might negotiate a different format, it
is necessary to move format info to each stream individually.
And use it instead of exhaustively listing each field of the
anonymous struct into the arguments of the lookup function.
The media API requires it's own format (enum video_format) and
bytes per pixel (bpp).
Cameras and audio streams will use the async output routines, but
not screencasting. This needs to be handled differently in the
processing callback.

Co-authored-by: Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
This will be reused by the camera portal code.
This will allow consumers of obs_pipewire to handle the device
registry themselves. This is not useful for the screencast code
since there's always only one node in the registry, but it will
be used by the camera and audio code to list hardware devices.
Generalize the async server roundtrip wait so that future consumers
can, for example, wait for all devices to be listed before acting
on the registry.
This struct tracks the related obs object. To share common code with the
virtualcamera plugin we need to be able to deal with obs_source_t and
obs_output_t.
This allows us to write a plugin exporting the buffers via PipeWire
@columbarius
Copy link
Contributor Author

@GeorgesStavracas I'm not sure about the api design. On one hand I'd like to have a single function for obs_pipewire_stream_pause on the other hand audio won't need a _get_width function.

Would you prefere one api for video_in, video_out, audio_in or one unified one, created with a

struct {
    type,
    union {
        video_in,
        ...,
    }
}

Thing and internally we do a lot of switch case?

@columbarius
Copy link
Contributor Author

I went this weekend on a totally different direction using a struct of function pointers holding the implementation of certain features for each backend which wants to implement the obs_pipewire_stream thing/interface. The results are quite nice.
Here is a branch containing rebased versions of #5377 and #5728.

@GeorgesStavracas Please take a glance at it, if we want to go with this kind of api (pipewire-internal.h)

@dimtpap Please look if extending the impl struct with audio functions would help you.

@columbarius columbarius mentioned this pull request Sep 19, 2022
6 tasks
@WizardCM WizardCM added the Code Cleanup Non-breaking change which makes code smaller or more readable label Oct 1, 2022
@wtay
Copy link
Contributor

wtay commented Oct 25, 2022

I rebased the branch and added some small fixes: https://github.com/wtay/obs-studio/tree/pipewire-unified-dev

@GeorgesStavracas
Copy link
Member

This pull request has become too chaotic and unmanageable. I'll start breaking it in smaller chunks, and will handle it.

I think something went wrong with my rebase, half of the commits don't even build. I'll fix that too.

@columbarius
Copy link
Contributor Author

@GeorgesStavracas please ping me when I should start rebasing this including fixes from @wtay onto #8153
Nit: you linked #5614 instead of this MR there.

Same is valid if a review from me is helpful.

@PatTheMav
Copy link
Member

@GeorgesStavracas What's the intended plan for this PR? Is there a roadmap of changes that need to be merged first and does this PR still make sense in its current form (I guess the change itself is desired, just not the way it's implemented in the PR)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Code Cleanup Non-breaking change which makes code smaller or more readable
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants