mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-12-17 08:56:49 -05:00
doc: reorganize files
Separate various autogen files from the documentation .dox files. Rename .dox files to match the intended tree structure.
This commit is contained in:
parent
eca773fc12
commit
77fad4ee13
41 changed files with 60 additions and 59 deletions
21
doc/dox/tutorial/index.dox
Normal file
21
doc/dox/tutorial/index.dox
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/** \page page_tutorial Tutorial
|
||||
|
||||
Welcome to the PipeWire API tutorial. The goal is to learn
|
||||
PipeWire API step-by-step with simple short examples.
|
||||
|
||||
- \subpage page_tutorial1
|
||||
- \subpage page_tutorial2
|
||||
- \subpage page_tutorial3
|
||||
- \subpage page_tutorial4
|
||||
- \subpage page_tutorial5
|
||||
- \subpage page_tutorial6
|
||||
|
||||
|
||||
# More Example Programs
|
||||
|
||||
- \ref audio-src.c "": \snippet{doc} audio-src.c title
|
||||
- \ref audio-dsp-filter.c "": \snippet{doc} audio-dsp-filter.c title
|
||||
- \ref video-play.c "": \snippet{doc} video-play.c title
|
||||
- \subpage page_examples
|
||||
|
||||
*/
|
||||
47
doc/dox/tutorial/tutorial1.dox
Normal file
47
doc/dox/tutorial/tutorial1.dox
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/** \page page_tutorial1 Tutorial - Part 1: Getting Started
|
||||
|
||||
|
||||
\ref page_tutorial "Index" | \ref page_tutorial2
|
||||
|
||||
In this tutorial we show the basics of a simple PipeWire application.
|
||||
Use this tutorial to get started and help you set up your development
|
||||
environment.
|
||||
|
||||
|
||||
# Initialization
|
||||
|
||||
Let get started with the simplest application.
|
||||
|
||||
\snippet tutorial1.c code
|
||||
|
||||
Before you can use any PipeWire functions, you need to call `pw_init()`.
|
||||
|
||||
|
||||
# Compilation
|
||||
|
||||
PipeWire provides a pkg-config file named `libpipewire-0.3` (note: the version
|
||||
suffix may change with future releases of PipeWire).
|
||||
To compile the simple test application, copy it into a test1.c file and
|
||||
use pkg-config to provide the required dependencies:
|
||||
|
||||
gcc -Wall test1.c -o test1 $(pkg-config --cflags --libs libpipewire-0.3)
|
||||
|
||||
then run it with:
|
||||
|
||||
# ./test1
|
||||
Compiled with libpipewire 0.3.5
|
||||
Linked with libpipewire 0.3.5
|
||||
#
|
||||
|
||||
Use your build system's pkg-config support to integrate it into your project.
|
||||
For example, a minimal [meson.build](https://mesonbuild.com/) entry would look
|
||||
like this:
|
||||
|
||||
project('test1', ['c'])
|
||||
pipewire_dep = dependency('libpipewire-0.3')
|
||||
executable('test1', 'test1.c',
|
||||
dependencies: [pipewire_dep])
|
||||
|
||||
\ref page_tutorial "Index" | \ref page_tutorial2
|
||||
|
||||
*/
|
||||
129
doc/dox/tutorial/tutorial2.dox
Normal file
129
doc/dox/tutorial/tutorial2.dox
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/** \page page_tutorial2 Tutorial - Part 2: Enumerating Objects
|
||||
|
||||
\ref page_tutorial1 | \ref page_tutorial "Index" | \ref page_tutorial3
|
||||
|
||||
In this tutorial we show how to connect to a PipeWire daemon and
|
||||
enumerate the objects that it has.
|
||||
|
||||
Let take a look at the following application to start.
|
||||
|
||||
\snippet tutorial2.c code
|
||||
|
||||
To compile the simple test application, copy it into a tutorial2.c file and
|
||||
use:
|
||||
|
||||
gcc -Wall tutorial2.c -o tutorial2 $(pkg-config --cflags --libs libpipewire-0.3)
|
||||
|
||||
Let's break this down:
|
||||
|
||||
First we need to initialize the PipeWire library with `pw_init()` as we
|
||||
saw in the previous tutorial. This will load and configure the right
|
||||
modules and setup logging and other tasks.
|
||||
|
||||
\code{.c}
|
||||
...
|
||||
pw_init(&argc, &argv);
|
||||
...
|
||||
\endcode
|
||||
|
||||
Next we need to create one of the `struct pw_loop` wrappers. PipeWire
|
||||
ships with 2 types of mainloop implementations. We will use the
|
||||
`struct pw_main_loop` implementation, we will see later how we can
|
||||
use the `struct pw_thread_loop` implementation as well.
|
||||
|
||||
The mainloop is an abstraction of a big poll loop, waiting for events
|
||||
to occur and things to do. Most of the PipeWire work will actually
|
||||
be performed in the context of this loop and so we need to make one
|
||||
first.
|
||||
|
||||
We then need to make a new context object with the loop. This context
|
||||
object will manage the resources for us and will make it possible for
|
||||
us to connect to a PipeWire daemon:
|
||||
|
||||
\code{.c}
|
||||
struct pw_main_loop *loop;
|
||||
struct pw_context *context;
|
||||
|
||||
loop = pw_main_loop_new(NULL /* properties */);
|
||||
context = pw_context_new(pw_main_loop_get_loop(loop),
|
||||
NULL /* properties */,
|
||||
0 /* user_data size */);
|
||||
\endcode
|
||||
|
||||
It is possible to give extra properties when making the mainloop or
|
||||
context to tweak its features and functionality. It is also possible
|
||||
to add extra data to the allocated objects for your user data. It will
|
||||
stay alive for as long as the object is alive. We will use this
|
||||
feature later.
|
||||
|
||||
A real implementation would also need to check if the allocation
|
||||
succeeded and do some error handling, but we leave that out to make
|
||||
the code easier to read.
|
||||
|
||||
With the context we can now connect to the PipeWire daemon:
|
||||
|
||||
\code{.c}
|
||||
struct pw_core *core;
|
||||
core = pw_context_connect(context,
|
||||
NULL /* properties */,
|
||||
0 /* user_data size */);
|
||||
\endcode
|
||||
|
||||
This creates a socket between the client and the server and makes
|
||||
a proxy object (with ID 0) for the core. Don't forget to check the
|
||||
result here, a NULL value means that the connection failed.
|
||||
|
||||
At this point we can send messages to the server and receive events.
|
||||
For now we're not going to handle events on this core proxy but
|
||||
we're going to handle them on the registry object.
|
||||
|
||||
|
||||
\code{.c}
|
||||
struct pw_registry *registry;
|
||||
struct spa_hook registry_listener;
|
||||
|
||||
registry = pw_core_get_registry(core, PW_VERSION_REGISTRY,
|
||||
0 /* user_data size */);
|
||||
|
||||
spa_zero(registry_listener);
|
||||
pw_registry_add_listener(registry, ®istry_listener,
|
||||
®istry_events, NULL);
|
||||
\endcode
|
||||
|
||||
From the core we get the registry proxy object and when we use
|
||||
`pw_registry_add_listener()` to listen for events. We need a
|
||||
small `struct spa_hook` to keep track of the listener and a
|
||||
reference to the `struct pw_registry_events` that contains the
|
||||
events we want to listen to.
|
||||
|
||||
This is how we define the event handler and the function to
|
||||
handle the events:
|
||||
|
||||
\code{.c}
|
||||
static const struct pw_registry_events registry_events = {
|
||||
PW_VERSION_REGISTRY_EVENTS,
|
||||
.global = registry_event_global,
|
||||
};
|
||||
|
||||
static void registry_event_global(void *data, uint32_t id,
|
||||
uint32_t permissions, const char *type, uint32_t version,
|
||||
const struct spa_dict *props)
|
||||
{
|
||||
printf("object: id:%u type:%s/%d\n", id, type, version);
|
||||
}
|
||||
\endcode
|
||||
|
||||
Now that everything is set up we can start the mainloop and let
|
||||
the communication between client and server continue:
|
||||
|
||||
\code{.c}
|
||||
pw_main_loop_run(loop);
|
||||
\endcode
|
||||
|
||||
Since we don't call `pw_main_loop_quit()` anywhere, this loop will
|
||||
continue forever. In the next tutorial we'll see how we can nicely
|
||||
exit our application after we received all server objects.
|
||||
|
||||
\ref page_tutorial1 | \ref page_tutorial "Index" | \ref page_tutorial3
|
||||
|
||||
*/
|
||||
119
doc/dox/tutorial/tutorial3.dox
Normal file
119
doc/dox/tutorial/tutorial3.dox
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/** \page page_tutorial3 Tutorial - Part 3: Forcing A Roundtrip
|
||||
|
||||
\ref page_tutorial2 | \ref page_tutorial "Index" | \ref page_tutorial4
|
||||
|
||||
In this tutorial we show how to force a roundtrip to the server
|
||||
to make sure an action completed.
|
||||
|
||||
We'll change our example from \ref page_tutorial2 "Tutorial 2" slightly
|
||||
and add the extra code to implement the roundtrip.
|
||||
|
||||
Let's take the following small method first:
|
||||
|
||||
\snippet tutorial3.c roundtrip
|
||||
|
||||
Let's take a look at what this method does.
|
||||
|
||||
\code{.c}
|
||||
struct spa_hook core_listener;
|
||||
|
||||
pw_core_add_listener(core, &core_listener, &core_events, &d);
|
||||
\endcode
|
||||
|
||||
First of all we add a listener for the events of the core
|
||||
object. We are only interested in the `done` event in this
|
||||
tutorial. This is the event handler:
|
||||
|
||||
\code{.c}
|
||||
static void on_core_done(void *data, uint32_t id, int seq)
|
||||
{
|
||||
struct roundtrip_data *d = data;
|
||||
|
||||
if (id == PW_ID_CORE && seq == d->pending)
|
||||
pw_main_loop_quit(d->loop);
|
||||
}
|
||||
\endcode
|
||||
|
||||
When the done event is received for an object with id `PW_ID_CORE` and
|
||||
a certain sequence number `seq`, this function will call `pw_main_loop_quit()`.
|
||||
|
||||
Next we do:
|
||||
|
||||
\code{.c}
|
||||
d.pending = pw_core_sync(core, PW_ID_CORE, 0);
|
||||
\endcode
|
||||
|
||||
This triggers the `sync` method on the core object with id
|
||||
`PW_ID_CORE` and sequence number 0.
|
||||
|
||||
Because this is a method on a proxy object, it will be executed
|
||||
asynchronously and the return value will reflect this. PipeWire
|
||||
uses the return values of the underlying SPA (Simple Plugin API)
|
||||
helper objects (See also \ref page_spa_design ).
|
||||
|
||||
Because all messages on the PipeWire server are handled sequentially,
|
||||
the sync method will be executed after all previous methods are
|
||||
completed. The PipeWire server will emit a `done` event with the
|
||||
same ID and the return value of the original `pw_core_sync()`
|
||||
method in the sequence number.
|
||||
|
||||
We then run the mainloop to send the messages to the server and
|
||||
receive the events:
|
||||
|
||||
\code{.c}
|
||||
pw_main_loop_run(loop);
|
||||
\endcode
|
||||
|
||||
When we get the done event, we can compare it to the sync method
|
||||
and then we know that we did a complete roundtrip and there are no
|
||||
more pending methods on the server. We can quit the mainloop and
|
||||
remove the listener:
|
||||
|
||||
\code{.c}
|
||||
spa_hook_remove(&core_listener);
|
||||
\endcode
|
||||
|
||||
If we add this roundtrip method to our code and call it instead of the
|
||||
`pw_main_loop_run()` we will exit the program after all previous methods
|
||||
are finished. This means that the `pw_core_get_registry()` call
|
||||
completed and thus that we also received all events for the globals
|
||||
on the server.
|
||||
|
||||
\snippet tutorial3.c code
|
||||
|
||||
To compile the simple test application, copy it into a tutorial3.c file and
|
||||
use:
|
||||
|
||||
gcc -Wall tutorial3.c -o tutorial3 $(pkg-config --cflags --libs libpipewire-0.3)
|
||||
|
||||
Now that our program completes, we can take a look at how we can destroy
|
||||
the objects we created. Let's destroy each of them in reverse order that we
|
||||
created them:
|
||||
|
||||
\code{.c}
|
||||
pw_proxy_destroy((struct pw_proxy*)registry);
|
||||
\endcode
|
||||
|
||||
The registry is a proxy and can be destroyed with the generic proxy destroy
|
||||
method. After destroying the object, you should not use it anymore. It is
|
||||
an error to destroy an object more than once.
|
||||
|
||||
We can disconnect from the server with:
|
||||
|
||||
\code{.c}
|
||||
pw_core_disconnect(core);
|
||||
\endcode
|
||||
|
||||
This will also destroy the core proxy object and will remove the proxies
|
||||
that might have been created on this connection.
|
||||
|
||||
We can finally destroy our context and mainloop to conclude this tutorial:
|
||||
|
||||
\code{.c}
|
||||
pw_context_destroy(context);
|
||||
pw_main_loop_destroy(loop);
|
||||
\endcode
|
||||
|
||||
\ref page_tutorial2 | \ref page_tutorial "Index" | \ref page_tutorial4
|
||||
|
||||
*/
|
||||
158
doc/dox/tutorial/tutorial4.dox
Normal file
158
doc/dox/tutorial/tutorial4.dox
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/** \page page_tutorial4 Tutorial - Part 4: Playing A Tone
|
||||
|
||||
\ref page_tutorial3 | \ref page_tutorial "Index" | \ref page_tutorial5
|
||||
|
||||
In this tutorial we show how to use a stream to play a tone.
|
||||
|
||||
Let's take a look at the code before we break it down:
|
||||
|
||||
\snippet tutorial4.c code
|
||||
|
||||
Save as tutorial4.c and compile with:
|
||||
|
||||
gcc -Wall tutorial4.c -o tutorial4 -lm $(pkg-config --cflags --libs libpipewire-0.3)
|
||||
|
||||
We start with the usual boilerplate, `pw_init()` and a `pw_main_loop_new()`.
|
||||
We're going to store our objects in a structure so that we can pass them
|
||||
around in callbacks later.
|
||||
|
||||
\code{.c}
|
||||
struct data {
|
||||
struct pw_main_loop *loop;
|
||||
struct pw_stream *stream;
|
||||
double accumulator;
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct data data = { 0, };
|
||||
|
||||
pw_init(&argc, &argv);
|
||||
|
||||
data.loop = pw_main_loop_new(NULL);
|
||||
\endcode
|
||||
|
||||
Next we create a stream object. It takes the mainloop as first argument and
|
||||
a stream name as the second. Next we provide some properties for the stream
|
||||
and a callback + data.
|
||||
|
||||
\code{.c}
|
||||
data.stream = pw_stream_new_simple(
|
||||
pw_main_loop_get_loop(data.loop),
|
||||
"audio-src",
|
||||
pw_properties_new(
|
||||
PW_KEY_MEDIA_TYPE, "Audio",
|
||||
PW_KEY_MEDIA_CATEGORY, "Playback",
|
||||
PW_KEY_MEDIA_ROLE, "Music",
|
||||
NULL),
|
||||
&stream_events,
|
||||
&data);
|
||||
\endcode
|
||||
|
||||
We are using `pw_stream_new_simple()` but there is also a `pw_stream_new()` that
|
||||
takes an existing `struct pw_core` as the first argument and that requires you
|
||||
to add the event handle manually, for more control. The `pw_stream_new_simple()`
|
||||
is, as the name implies, easier to use because it creates a `struct pw_context`
|
||||
and `struct pw_core` automatically.
|
||||
|
||||
In the properties we need to give as much information about the stream as we
|
||||
can so that the session manager can make good decisions about how and where
|
||||
to route this stream. There are three important properties to configure:
|
||||
|
||||
- `PW_KEY_MEDIA_TYPE`: The media type; like Audio, Video, MIDI.
|
||||
- `PW_KEY_MEDIA_CATEGORY`: The category; like Playback, Capture, Duplex, Monitor.
|
||||
- `PW_KEY_MEDIA_ROLE`: The media role; like Movie, Music, Camera, Screen,
|
||||
Communication, Game, Notification, DSP, Production, Accessibility, Test.
|
||||
|
||||
The properties are owned by the stream and freed when the stream is destroyed
|
||||
later.
|
||||
|
||||
This is the event structure that we use to listen for events:
|
||||
|
||||
\code{.c}
|
||||
static const struct pw_stream_events stream_events = {
|
||||
PW_VERSION_STREAM_EVENTS,
|
||||
.process = on_process,
|
||||
};
|
||||
\endcode
|
||||
|
||||
We are for the moment only interested now in the `process` event. This event
|
||||
is called whenever we need to produce more data. We'll see how that function
|
||||
is implemented but first we need to setup the format of the stream:
|
||||
|
||||
\code{.c}
|
||||
const struct spa_pod *params[1];
|
||||
uint8_t buffer[1024];
|
||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
|
||||
#define DEFAULT_RATE 44100
|
||||
#define DEFAULT_CHANNELS 2
|
||||
|
||||
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
|
||||
&SPA_AUDIO_INFO_RAW_INIT(
|
||||
.format = SPA_AUDIO_FORMAT_S16,
|
||||
.channels = DEFAULT_CHANNELS,
|
||||
.rate = DEFAULT_RATE ));
|
||||
\endcode
|
||||
|
||||
This is using a `struct spa_pod_builder` to make a `struct spa_pod *` object
|
||||
in the buffer array on the stack. The parameter is of type `SPA_PARAM_EnumFormat`
|
||||
which means that it enumerates the possible formats for this stream. We have
|
||||
only one, a Signed 16 bit stereo format at 44.1KHz.
|
||||
|
||||
We use `spa_format_audio_raw_build()` which is a helper function to make the param
|
||||
with the builder. See \ref page_spa_pod for more information about how to
|
||||
make these POD objects.
|
||||
|
||||
Now we're ready to connect the stream and run the main loop:
|
||||
|
||||
\code{.c}
|
||||
pw_stream_connect(data.stream,
|
||||
PW_DIRECTION_OUTPUT,
|
||||
PW_ID_ANY,
|
||||
PW_STREAM_FLAG_AUTOCONNECT |
|
||||
PW_STREAM_FLAG_MAP_BUFFERS |
|
||||
PW_STREAM_FLAG_RT_PROCESS,
|
||||
params, 1);
|
||||
|
||||
pw_main_loop_run(data.loop);
|
||||
\endcode
|
||||
|
||||
To connect we specify that we have a `PW_DIRECTION_OUTPUT` stream. The third argument
|
||||
is always `PW_ID_ANY`. Next we set some flags:
|
||||
|
||||
- `PW_STREAM_FLAG_AUTOCONNECT`: Automatically connect this stream. This instructs
|
||||
the session manager to link us to some consumer.
|
||||
- `PW_STREAM_FLAG_MAP_BUFFERS`: mmap the buffers for us so we can access the
|
||||
memory. If you don't set these flags you have either work with the fd or mmap
|
||||
yourself.
|
||||
- `PW_STREAM_FLAG_RT_PROCESS`: Run the process function in the realtime thread.
|
||||
Only use this if the process function only uses functions that are realtime
|
||||
safe, this means no allocation or file access or any locking.
|
||||
|
||||
And last we pass the extra parameters for our stream. Here we only have the
|
||||
allowed formats (`SPA_PARAM_EnumFormat`).
|
||||
|
||||
Running the mainloop will then start processing and will result in our
|
||||
`process` callback to be called. Let's have a look at that function now.
|
||||
|
||||
The main program flow of the process function is:
|
||||
|
||||
- `pw_stream_dequeue_buffer()` to obtain a buffer to write into.
|
||||
- Get pointers in buffer memory to write to.
|
||||
- Write data into buffer.
|
||||
- Adjust buffer with number of written bytes, offset, stride.
|
||||
- `pw_stream_queue_buffer()` to queue the buffer for playback.
|
||||
|
||||
\snippet tutorial4.c on_process
|
||||
|
||||
Check out the docs for \ref page_spa_buffer for more information
|
||||
about how to work with buffers.
|
||||
|
||||
Try to change the number of channels, samplerate or format; the stream
|
||||
will automatically convert to the format on the server.
|
||||
|
||||
|
||||
\ref page_tutorial3 | \ref page_tutorial "Index" | \ref page_tutorial5
|
||||
|
||||
*/
|
||||
223
doc/dox/tutorial/tutorial5.dox
Normal file
223
doc/dox/tutorial/tutorial5.dox
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
/** \page page_tutorial5 Tutorial - Part 5: Capturing Video Frames
|
||||
|
||||
\ref page_tutorial4 | \ref page_tutorial "Index" | \ref page_tutorial6
|
||||
|
||||
In this tutorial we show how to use a stream to capture a
|
||||
stream of video frames.
|
||||
|
||||
Even though we are now working with a different media type and
|
||||
we are capturing instead of playback, you will see that this
|
||||
example is very similar to \ref page_tutorial4.
|
||||
|
||||
Let's take a look at the code before we break it down:
|
||||
|
||||
\snippet tutorial5.c code
|
||||
|
||||
Save as tutorial5.c and compile with:
|
||||
|
||||
gcc -Wall tutorial5.c -o tutorial5 -lm $(pkg-config --cflags --libs libpipewire-0.3)
|
||||
|
||||
Most of the application is structured like \ref page_tutorial4.
|
||||
|
||||
We create a stream object with different properties to make it a Camera
|
||||
Video Capture stream.
|
||||
|
||||
\code{.c}
|
||||
props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video",
|
||||
PW_KEY_MEDIA_CATEGORY, "Capture",
|
||||
PW_KEY_MEDIA_ROLE, "Camera",
|
||||
NULL);
|
||||
if (argc > 1)
|
||||
pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]);
|
||||
|
||||
data.stream = pw_stream_new_simple(
|
||||
pw_main_loop_get_loop(data.loop),
|
||||
"video-capture",
|
||||
props,
|
||||
&stream_events,
|
||||
&data);
|
||||
\endcode
|
||||
|
||||
We also optionally allow the user to pass the name of the target node where the session
|
||||
manager is supposed to connect the node. The user may also give the value of the
|
||||
unique target node serial (`PW_KEY_OBJECT_SERIAL`) as the value.
|
||||
|
||||
In addition to the `process` event, we are also going to listen to a new event,
|
||||
`param_changed`:
|
||||
|
||||
\code{.c}
|
||||
static const struct pw_stream_events stream_events = {
|
||||
PW_VERSION_STREAM_EVENTS,
|
||||
.param_changed = on_param_changed,
|
||||
.process = on_process,
|
||||
};
|
||||
\endcode
|
||||
|
||||
Because we capture a stream of a wide range of different
|
||||
video formats and resolutions, we have to describe our accepted formats in
|
||||
a different way:
|
||||
|
||||
\code{.c}
|
||||
const struct spa_pod *params[1];
|
||||
uint8_t buffer[1024];
|
||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
|
||||
params[0] = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
|
||||
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
|
||||
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
|
||||
SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7,
|
||||
SPA_VIDEO_FORMAT_RGB,
|
||||
SPA_VIDEO_FORMAT_RGB,
|
||||
SPA_VIDEO_FORMAT_RGBA,
|
||||
SPA_VIDEO_FORMAT_RGBx,
|
||||
SPA_VIDEO_FORMAT_BGRx,
|
||||
SPA_VIDEO_FORMAT_YUY2,
|
||||
SPA_VIDEO_FORMAT_I420),
|
||||
SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
|
||||
&SPA_RECTANGLE(320, 240),
|
||||
&SPA_RECTANGLE(1, 1),
|
||||
&SPA_RECTANGLE(4096, 4096)),
|
||||
SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(
|
||||
&SPA_FRACTION(25, 1),
|
||||
&SPA_FRACTION(0, 1),
|
||||
&SPA_FRACTION(1000, 1)));
|
||||
\endcode
|
||||
|
||||
This is using a `struct spa_pod_builder` to make a `struct spa_pod *` object
|
||||
in the buffer array on the stack. The parameter is of type `SPA_PARAM_EnumFormat`
|
||||
which means that it enumerates the possible formats for this stream.
|
||||
|
||||
In this example we use the builder to create some `CHOICE` entries for
|
||||
the format properties.
|
||||
|
||||
We have an enumeration of formats, we need to first give the amount of enumerations
|
||||
that follow, then the default (preferred) value, followed by alternatives in order
|
||||
of preference:
|
||||
|
||||
\code{.c}
|
||||
SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7,
|
||||
SPA_VIDEO_FORMAT_RGB, /* default */
|
||||
SPA_VIDEO_FORMAT_RGB, /* alternative 1 */
|
||||
SPA_VIDEO_FORMAT_RGBA, /* alternative 2 */
|
||||
SPA_VIDEO_FORMAT_RGBx, /* .. etc.. */
|
||||
SPA_VIDEO_FORMAT_BGRx,
|
||||
SPA_VIDEO_FORMAT_YUY2,
|
||||
SPA_VIDEO_FORMAT_I420),
|
||||
\endcode
|
||||
|
||||
We also have a `RANGE` of values for the size. We need to give a default (preferred)
|
||||
size and then a min and max value:
|
||||
|
||||
\code{.c}
|
||||
SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
|
||||
&SPA_RECTANGLE(320, 240), /* default */
|
||||
&SPA_RECTANGLE(1, 1), /* min */
|
||||
&SPA_RECTANGLE(4096, 4096)), /* max */
|
||||
\endcode
|
||||
|
||||
We have something similar for the framerate.
|
||||
|
||||
Note that there are other video parameters that we don't specify here. This
|
||||
means that we don't have any restrictions for their values.
|
||||
|
||||
See \ref page_spa_pod for more information about how to make these
|
||||
POD objects.
|
||||
|
||||
Now we're ready to connect the stream and run the main loop:
|
||||
|
||||
\code{.c}
|
||||
pw_stream_connect(data.stream,
|
||||
PW_DIRECTION_INPUT,
|
||||
PW_ID_ANY,
|
||||
PW_STREAM_FLAG_AUTOCONNECT |
|
||||
PW_STREAM_FLAG_MAP_BUFFERS,
|
||||
params, 1);
|
||||
|
||||
pw_main_loop_run(data.loop);
|
||||
\endcode
|
||||
|
||||
To connect we specify that we have a `PW_DIRECTION_INPUT` stream. The third
|
||||
argument is always `PW_ID_ANY`.
|
||||
|
||||
We're setting the `PW_STREAM_FLAG_AUTOCONNECT` flag to make an automatic
|
||||
connection to a suitable camera and `PW_STREAM_FLAG_MAP_BUFFERS` to let the
|
||||
stream mmap the data for us.
|
||||
|
||||
And last we pass the extra parameters for our stream. Here we only have the
|
||||
allowed formats (`SPA_PARAM_EnumFormat`).
|
||||
|
||||
Running the mainloop will start the connection and negotiation process.
|
||||
First our `param_changed` event will be called with the format that was
|
||||
negotiated between our stream and the camera. This is always something that
|
||||
is compatible with what we enumerated in the EnumFormat param when we
|
||||
connected.
|
||||
|
||||
Let's take a look at how we can parse the format in the `param_changed`
|
||||
event:
|
||||
|
||||
\code{.c}
|
||||
static void on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param)
|
||||
{
|
||||
struct data *data = userdata;
|
||||
|
||||
if (param == NULL || id != SPA_PARAM_Format)
|
||||
return;
|
||||
\endcode
|
||||
|
||||
First check if there is a param. A NULL param means that it is cleared. The ID
|
||||
of the param tells you what param it is. We are only interested in Format
|
||||
param (`SPA_PARAM_Format`).
|
||||
|
||||
We can parse the media type and subtype as below and ensure that it is
|
||||
of the right type. In our example this will always be true but when your
|
||||
EnumFormat contains different media types or subtypes, this is how you can
|
||||
parse them:
|
||||
|
||||
\code{.c}
|
||||
if (spa_format_parse(param,
|
||||
&data->format.media_type,
|
||||
&data->format.media_subtype) < 0)
|
||||
return;
|
||||
|
||||
if (data->format.media_type != SPA_MEDIA_TYPE_video ||
|
||||
data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
|
||||
return;
|
||||
\endcode
|
||||
|
||||
For the `video/raw` media type/subtype there is a utility function to
|
||||
parse out the values into a `struct spa_video_info`. This makes it easier
|
||||
to deal with.
|
||||
|
||||
\code{.c}
|
||||
if (spa_format_video_raw_parse(param, &data->format.info.raw) < 0)
|
||||
return;
|
||||
|
||||
printf("got video format:\n");
|
||||
printf(" format: %d (%s)\n", data->format.info.raw.format,
|
||||
spa_debug_type_find_name(spa_type_video_format,
|
||||
data->format.info.raw.format));
|
||||
printf(" size: %dx%d\n", data->format.info.raw.size.width,
|
||||
data->format.info.raw.size.height);
|
||||
printf(" framerate: %d/%d\n", data->format.info.raw.framerate.num,
|
||||
data->format.info.raw.framerate.denom);
|
||||
|
||||
/** prepare to render video of this size */
|
||||
}
|
||||
\endcode
|
||||
|
||||
In this example we dump the video size and parameters but in a real playback
|
||||
or capture application you might want to set up the screen or encoder to
|
||||
deal with the format.
|
||||
|
||||
After negotiation, the process function is called for each new frame. Check out
|
||||
\ref page_tutorial4 for another example.
|
||||
|
||||
\snippet tutorial5.c on_process
|
||||
|
||||
In a real playback application, one would do something with the data, like
|
||||
copy it to the screen or encode it into a file.
|
||||
|
||||
\ref page_tutorial4 | \ref page_tutorial "Index" | \ref page_tutorial6
|
||||
|
||||
*/
|
||||
69
doc/dox/tutorial/tutorial6.dox
Normal file
69
doc/dox/tutorial/tutorial6.dox
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/** \page page_tutorial6 Tutorial - Part 6: Binding Objects
|
||||
|
||||
\ref page_tutorial5 | \ref page_tutorial "Index"
|
||||
|
||||
In this tutorial we show how to bind to an object so that we can
|
||||
receive events and call methods on the object.
|
||||
|
||||
Let take a look at the following application to start.
|
||||
|
||||
\snippet tutorial6.c code
|
||||
|
||||
To compile the simple test application, copy it into a tutorial6.c file and
|
||||
use:
|
||||
|
||||
gcc -Wall tutorial6.c -o tutorial6 $(pkg-config --cflags --libs libpipewire-0.3)
|
||||
|
||||
Most of this is the same as \ref page_tutorial2 where we simply
|
||||
enumerated all objects on the server. Instead of just printing the object
|
||||
id and some other properties, in this example we also bind to the object.
|
||||
|
||||
We use the `pw_registry_bind()` method on our registry object like this:
|
||||
|
||||
\snippet tutorial6.c registry_event_global
|
||||
|
||||
We bind to the first client object that we see. This gives us a pointer
|
||||
to a `struct pw_proxy` that we can also cast to a `struct pw_client`.
|
||||
|
||||
On the proxy we can call methods and listen for events. PipeWire will
|
||||
automatically serialize the method calls and events between client and
|
||||
server for us.
|
||||
|
||||
We can now listen for events by adding a listener. We're going to
|
||||
listen to the info event on the client object that is emitted right
|
||||
after we bind to it or when it changes. This is not very different
|
||||
from the registry listener we added before:
|
||||
|
||||
\snippet tutorial6.c client_info
|
||||
|
||||
\code{.c}
|
||||
static void registry_event_global(void *_data, uint32_t id,
|
||||
uint32_t permissions, const char *type,
|
||||
uint32_t version, const struct spa_dict *props)
|
||||
{
|
||||
/* ... */
|
||||
pw_client_add_listener(data->client,
|
||||
&data->client_listener,
|
||||
&client_events, data);
|
||||
/* ... */
|
||||
}
|
||||
\endcode
|
||||
|
||||
We're also quitting the mainloop after we get the info to nicely stop
|
||||
our tutorial application.
|
||||
|
||||
When we stop the application, don't forget to destroy all proxies that
|
||||
you created. Otherwise, they will be leaked:
|
||||
|
||||
\code{.c}
|
||||
/* ... */
|
||||
pw_proxy_destroy((struct pw_proxy *)data.client);
|
||||
/* ... */
|
||||
|
||||
return 0;
|
||||
}
|
||||
\endcode
|
||||
|
||||
\ref page_tutorial5 | \ref page_tutorial "Index"
|
||||
|
||||
*/
|
||||
Loading…
Add table
Add a link
Reference in a new issue