mirror of
https://gitlab.freedesktop.org/pipewire/pipewire
synced 2024-10-14 20:02:38 +00:00
doc: change the tutorials to doxygen sources
While doxygen can handle markdown pages, support for it is very limited: markdown pages can only be included as a whole page, they get an automatic title (custom titles are possible but aren't standard markdown) and it's not possible to use \subpage without messing with the markdown again. Any markdown page will thus end up as separate item in the doxygen output, not really suitable for generating a good page hiearchy. Let's switch the tutorial to use doxygen directly instead of markdown, short of using code/endcode instead of markdown's ``` there isn't that much difference anyway but it allows us to structure things nicer in the online docs.
This commit is contained in:
parent
fce28ac59b
commit
9ed9980fa2
|
@ -48,13 +48,13 @@ extra_docs = [
|
||||||
'pipewire-design.md',
|
'pipewire-design.md',
|
||||||
'pipewire-architecture.md',
|
'pipewire-architecture.md',
|
||||||
'pipewire-objects-design.md',
|
'pipewire-objects-design.md',
|
||||||
'tutorial-index.md',
|
'tutorial.dox',
|
||||||
'tutorial1.md',
|
'tutorial1.dox',
|
||||||
'tutorial2.md',
|
'tutorial2.dox',
|
||||||
'tutorial3.md',
|
'tutorial3.dox',
|
||||||
'tutorial4.md',
|
'tutorial4.dox',
|
||||||
'tutorial5.md',
|
'tutorial5.dox',
|
||||||
'tutorial6.md',
|
'tutorial6.dox',
|
||||||
'spa-index.md',
|
'spa-index.md',
|
||||||
'spa-design.md',
|
'spa-design.md',
|
||||||
'spa-pod.md',
|
'spa-pod.md',
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
# Tutorial - The PipeWire API
|
|
||||||
|
|
||||||
Welcome to the PipeWire tutorial. The goal is to learn
|
|
||||||
PipeWire API step-by-step with simple short examples.
|
|
||||||
|
|
||||||
1) Getting started ([tutorial 1](tutorial1.md)).
|
|
||||||
|
|
||||||
2) Enumerating objects ([tutorial 2](tutorial2.md)).
|
|
||||||
|
|
||||||
3) Forcing a roundtrip ([tutorial 3](tutorial3.md)).
|
|
||||||
|
|
||||||
4) Playing a tone with `pw_stream` ([tutorial 4](tutorial4.md)).
|
|
||||||
|
|
||||||
5) Capture video frames with `pw_stream` ([tutorial 5](tutorial5.md)).
|
|
||||||
|
|
||||||
6) Bind to an object ([tutorial 6](tutorial6.md)).
|
|
13
doc/tutorial.dox
Normal file
13
doc/tutorial.dox
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/** \page page_tutorial Tutorial - The PipeWire API
|
||||||
|
|
||||||
|
Welcome to the PipeWire 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
|
||||||
|
|
||||||
|
*/
|
|
@ -1,6 +1,7 @@
|
||||||
# Tutorial - Part 1: Getting started
|
/** \page page_tutorial1 Tutorial - Part 1: Getting started
|
||||||
|
|
||||||
[[index]](tutorial-index.md) [[next]](tutorial2.md)
|
|
||||||
|
\ref page_tutorial "Index" | \ref page_tutorial2
|
||||||
|
|
||||||
In this tutorial we show the basics of a simple PipeWire application.
|
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
|
Use this tutorial to get started and help you set up your development
|
||||||
|
@ -10,7 +11,7 @@ environment.
|
||||||
|
|
||||||
Let get started with the simplest application.
|
Let get started with the simplest application.
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
|
@ -23,7 +24,7 @@ int main(int argc, char *argv[])
|
||||||
pw_get_library_version());
|
pw_get_library_version());
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
Before you can use any PipeWire functions, you need to call `pw_init()`.
|
Before you can use any PipeWire functions, you need to call `pw_init()`.
|
||||||
|
|
||||||
|
@ -32,17 +33,15 @@ Before you can use any PipeWire functions, you need to call `pw_init()`.
|
||||||
To compile the simple test application, copy it into a test1.c file and
|
To compile the simple test application, copy it into a test1.c file and
|
||||||
use:
|
use:
|
||||||
|
|
||||||
```
|
gcc -Wall test1.c -o test1 $(pkg-config --cflags --libs libpipewire-0.3)
|
||||||
gcc -Wall test1.c -o test1 $(pkg-config --cflags --libs libpipewire-0.3)
|
|
||||||
```
|
|
||||||
|
|
||||||
then run it with:
|
then run it with:
|
||||||
|
|
||||||
```
|
# ./test1
|
||||||
# ./test1
|
Compiled with libpipewire 0.3.5
|
||||||
Compiled with libpipewire 0.3.5
|
Linked with libpipewire 0.3.5
|
||||||
Linked with libpipewire 0.3.5
|
#
|
||||||
#
|
|
||||||
```
|
|
||||||
|
|
||||||
[[index]](tutorial-index.md) [[next]](tutorial2.md)
|
\ref page_tutorial "Index" | \ref page_tutorial2
|
||||||
|
|
||||||
|
*/
|
|
@ -1,14 +1,13 @@
|
||||||
# Tutorial - Part 2: Enumerating objects
|
/** \page page_tutorial2 Tutorial - Part 2: Enumerating objects
|
||||||
|
|
||||||
[[previous]](tutorial1.md) [[index]](tutorial-index.md) [[next]](tutorial3.md)
|
|
||||||
|
|
||||||
|
\ref page_tutorial1 | \ref page_tutorial "Index" | \ref page_tutorial3
|
||||||
|
|
||||||
In this tutorial we show how to connect to a PipeWire daemon and
|
In this tutorial we show how to connect to a PipeWire daemon and
|
||||||
enumerate the objects that it has.
|
enumerate the objects that it has.
|
||||||
|
|
||||||
Let take a look at the following application to start.
|
Let take a look at the following application to start.
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
|
|
||||||
static void registry_event_global(void *data, uint32_t id,
|
static void registry_event_global(void *data, uint32_t id,
|
||||||
|
@ -58,14 +57,12 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
To compile the simple test application, copy it into a tutorial2.c file and
|
To compile the simple test application, copy it into a tutorial2.c file and
|
||||||
use:
|
use:
|
||||||
|
|
||||||
```
|
gcc -Wall tutorial2.c -o tutorial2 $(pkg-config --cflags --libs libpipewire-0.3)
|
||||||
gcc -Wall tutorial2.c -o tutorial2 $(pkg-config --cflags --libs libpipewire-0.3)
|
|
||||||
```
|
|
||||||
|
|
||||||
Let's break this down:
|
Let's break this down:
|
||||||
|
|
||||||
|
@ -73,11 +70,11 @@ 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
|
saw in the previous tutorial. This will load and configure the right
|
||||||
modules and setup logging and other tasks.
|
modules and setup logging and other tasks.
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
...
|
...
|
||||||
pw_init(&argc, &argv);
|
pw_init(&argc, &argv);
|
||||||
...
|
...
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
Next we need to create one of the `struct pw_loop` wrappers. PipeWire
|
Next we need to create one of the `struct pw_loop` wrappers. PipeWire
|
||||||
ships with 2 types of mainloop implementations. We will use the
|
ships with 2 types of mainloop implementations. We will use the
|
||||||
|
@ -93,7 +90,7 @@ 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
|
object will manage the resources for us and will make it possible for
|
||||||
us to connect to a PipeWire daemon:
|
us to connect to a PipeWire daemon:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
struct pw_main_loop *loop;
|
struct pw_main_loop *loop;
|
||||||
struct pw_context *context;
|
struct pw_context *context;
|
||||||
|
|
||||||
|
@ -101,7 +98,7 @@ us to connect to a PipeWire daemon:
|
||||||
context = pw_context_new(pw_main_loop_get_loop(loop),
|
context = pw_context_new(pw_main_loop_get_loop(loop),
|
||||||
NULL /* properties */,
|
NULL /* properties */,
|
||||||
0 /* user_data size */);
|
0 /* user_data size */);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
It is possible to give extra properties when making the mainloop or
|
It is possible to give extra properties when making the mainloop or
|
||||||
context to tweak its features and functionality. It is also possible
|
context to tweak its features and functionality. It is also possible
|
||||||
|
@ -115,12 +112,12 @@ the code easier to read.
|
||||||
|
|
||||||
With the context we can now connect to the PipeWire daemon:
|
With the context we can now connect to the PipeWire daemon:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
struct pw_core *core;
|
struct pw_core *core;
|
||||||
core = pw_context_connect(context,
|
core = pw_context_connect(context,
|
||||||
NULL /* properties */,
|
NULL /* properties */,
|
||||||
0 /* user_data size */);
|
0 /* user_data size */);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
This creates a socket between the client and the server and makes
|
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
|
a proxy object (with ID 0) for the core. Don't forget to check the
|
||||||
|
@ -131,7 +128,7 @@ For now we're not going to handle events on this core proxy but
|
||||||
we're going to handle them on the registry object.
|
we're going to handle them on the registry object.
|
||||||
|
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
struct pw_registry *registry;
|
struct pw_registry *registry;
|
||||||
struct spa_hook registry_listener;
|
struct spa_hook registry_listener;
|
||||||
|
|
||||||
|
@ -141,7 +138,7 @@ we're going to handle them on the registry object.
|
||||||
spa_zero(registry_listener);
|
spa_zero(registry_listener);
|
||||||
pw_registry_add_listener(registry, ®istry_listener,
|
pw_registry_add_listener(registry, ®istry_listener,
|
||||||
®istry_events, NULL);
|
®istry_events, NULL);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
From the core we get the registry proxy object and when we use
|
From the core we get the registry proxy object and when we use
|
||||||
`pw_registry_add_listener()` to listen for events. We need a
|
`pw_registry_add_listener()` to listen for events. We need a
|
||||||
|
@ -152,7 +149,7 @@ events we want to listen to.
|
||||||
This is how we define the event handler and the function to
|
This is how we define the event handler and the function to
|
||||||
handle the events:
|
handle the events:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
static const struct pw_registry_events registry_events = {
|
static const struct pw_registry_events registry_events = {
|
||||||
PW_VERSION_REGISTRY_EVENTS,
|
PW_VERSION_REGISTRY_EVENTS,
|
||||||
.global = registry_event_global,
|
.global = registry_event_global,
|
||||||
|
@ -164,18 +161,18 @@ static void registry_event_global(void *data, uint32_t id,
|
||||||
{
|
{
|
||||||
printf("object: id:%u type:%s/%d\n", id, type, version);
|
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
|
Now that everything is set up we can start the mainloop and let
|
||||||
the communication between client and server continue:
|
the communication between client and server continue:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
pw_main_loop_run(loop);
|
pw_main_loop_run(loop);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
Since we don't call `pw_main_loop_quit()` anywhere, this loop will
|
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
|
continue forever. In the next tutorial we'll see how we can nicely
|
||||||
exit our application after we received all server objects.
|
exit our application after we received all server objects.
|
||||||
|
|
||||||
|
\ref page_tutorial1 | \ref page_tutorial "Index" | \ref page_tutorial3
|
||||||
[[previous]](tutorial1.md) [[index]](tutorial-index.md) [[next]](tutorial3.md)
|
*/
|
|
@ -1,16 +1,16 @@
|
||||||
# Tutorial - Part 3: Forcing a roundtrip
|
/** \page page_tutorial3 Tutorial - Part 3: Forcing a roundtrip
|
||||||
|
|
||||||
[[previous]](tutorial2.md) [[index]](tutorial-index.md) [[next]](tutorial4.md)
|
\ref page_tutorial2 | \ref page_tutorial "Index" | \ref page_tutorial4
|
||||||
|
|
||||||
In this tutorial we show how to force a roundtrip to the server
|
In this tutorial we show how to force a roundtrip to the server
|
||||||
to make sure an action completed.
|
to make sure an action completed.
|
||||||
|
|
||||||
We'll change our example from [Tutorial 2](tutorial2.md) slightly
|
We'll change our example from \ref page_tutorial2 "Tutorial2" slightly
|
||||||
and add the extra code to implement the roundtrip.
|
and add the extra code to implement the roundtrip.
|
||||||
|
|
||||||
Let's take the following small method first:
|
Let's take the following small method first:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
static int roundtrip(struct pw_core *core, struct pw_main_loop *loop)
|
static int roundtrip(struct pw_core *core, struct pw_main_loop *loop)
|
||||||
{
|
{
|
||||||
struct spa_hook core_listener;
|
struct spa_hook core_listener;
|
||||||
|
@ -39,22 +39,22 @@ static int roundtrip(struct pw_core *core, struct pw_main_loop *loop)
|
||||||
spa_hook_remove(&core_listener);
|
spa_hook_remove(&core_listener);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
Let's take a look at what this method does.
|
Let's take a look at what this method does.
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
struct spa_hook core_listener;
|
struct spa_hook core_listener;
|
||||||
spa_zero(core_listener);
|
spa_zero(core_listener);
|
||||||
pw_core_add_listener(core, &core_listener,
|
pw_core_add_listener(core, &core_listener,
|
||||||
&core_events, NULL);
|
&core_events, NULL);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
First of all we add a listener for the events of the core
|
First of all we add a listener for the events of the core
|
||||||
object. We are only interested in the `done` event in this
|
object. We are only interested in the `done` event in this
|
||||||
tutorial. This is the event handler:
|
tutorial. This is the event handler:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
int pending, done = 0;
|
int pending, done = 0;
|
||||||
|
|
||||||
void core_event_done(void *object, uint32_t id, int seq) {
|
void core_event_done(void *object, uint32_t id, int seq) {
|
||||||
|
@ -67,7 +67,7 @@ tutorial. This is the event handler:
|
||||||
PW_VERSION_CORE_EVENTS,
|
PW_VERSION_CORE_EVENTS,
|
||||||
.done = core_event_done,
|
.done = core_event_done,
|
||||||
};
|
};
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
When the done event is received for an object with id `PW_ID_CORE`
|
When the done event is received for an object with id `PW_ID_CORE`
|
||||||
and a certain sequence number `seq`, this function will set the done
|
and a certain sequence number `seq`, this function will set the done
|
||||||
|
@ -75,9 +75,9 @@ variable to 1 and call `pw_main_loop_quit()`.
|
||||||
|
|
||||||
Next we do:
|
Next we do:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
pending = pw_core_sync(core, PW_ID_CORE, 0);
|
pending = pw_core_sync(core, PW_ID_CORE, 0);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
This triggers the `sync` method on the core object with id
|
This triggers the `sync` method on the core object with id
|
||||||
`PW_ID_CORE` and sequence number 0.
|
`PW_ID_CORE` and sequence number 0.
|
||||||
|
@ -96,20 +96,20 @@ method in the sequence number.
|
||||||
We then run the mainloop to send the messages to the server and
|
We then run the mainloop to send the messages to the server and
|
||||||
receive the events:
|
receive the events:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
while (!done) {
|
while (!done) {
|
||||||
pw_main_loop_run(loop);
|
pw_main_loop_run(loop);
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
When we get the done event, we can compare it to the sync method
|
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
|
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
|
more pending methods on the server. We can quit the mainloop and
|
||||||
remove the listener:
|
remove the listener:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
spa_hook_remove(&core_listener);
|
spa_hook_remove(&core_listener);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
If we add this roundtrip method to our code and call it instead of the
|
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
|
`pw_main_loop_run()` we will exit the program after all previous methods
|
||||||
|
@ -118,7 +118,7 @@ completed and thus that we also received all events for the globals
|
||||||
on the server.
|
on the server.
|
||||||
|
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
|
|
||||||
static int roundtrip(struct pw_core *core, struct pw_main_loop *loop)
|
static int roundtrip(struct pw_core *core, struct pw_main_loop *loop)
|
||||||
|
@ -196,22 +196,20 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
To compile the simple test application, copy it into a tutorial3.c file and
|
To compile the simple test application, copy it into a tutorial3.c file and
|
||||||
use:
|
use:
|
||||||
|
|
||||||
```
|
gcc -Wall tutorial3.c -o tutorial3 $(pkg-config --cflags --libs libpipewire-0.3)
|
||||||
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
|
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
|
the objects we created. Let's destroy each of them in reverse order that we
|
||||||
created them:
|
created them:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
pw_proxy_destroy((struct pw_proxy*)registry);
|
pw_proxy_destroy((struct pw_proxy*)registry);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
The registry is a proxy and can be destroyed with the generic proxy destroy
|
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
|
method. After destroying the object, you should not use it anymore. It is
|
||||||
|
@ -219,18 +217,20 @@ an error to destroy an object more than once.
|
||||||
|
|
||||||
We can disconnect from the server with:
|
We can disconnect from the server with:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
pw_core_disconnect(core);
|
pw_core_disconnect(core);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
This will also destroy the core proxy object and will remove the proxies
|
This will also destroy the core proxy object and will remove the proxies
|
||||||
that might have been created on this connection.
|
that might have been created on this connection.
|
||||||
|
|
||||||
We can finally destroy our context and mainloop to conclude this tutorial:
|
We can finally destroy our context and mainloop to conclude this tutorial:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
pw_context_destroy(context);
|
pw_context_destroy(context);
|
||||||
pw_main_loop_destroy(loop);
|
pw_main_loop_destroy(loop);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
[[previous]](tutorial2.md) [[index]](tutorial-index.md) [[next]](tutorial4.md)
|
\ref page_tutorial2 | \ref page_tutorial "Index" | \ref page_tutorial4
|
||||||
|
|
||||||
|
*/
|
|
@ -1,12 +1,12 @@
|
||||||
# Tutorial - Part 4: Playing a tone
|
/** \page page_tutorial4 Tutorial - Part 4: Playing a tone
|
||||||
|
|
||||||
[[previous]](tutorial3.md) [[index]](tutorial-index.md) [[next]](tutorial5.md)
|
\ref page_tutorial3 | \ref page_tutorial "Index" | \ref page_tutorial5
|
||||||
|
|
||||||
In this tutorial we show how to use a stream to play a tone.
|
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:
|
Let's take a look at the code before we break it down:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include <spa/param/audio/format-utils.h>
|
#include <spa/param/audio/format-utils.h>
|
||||||
|
@ -110,19 +110,17 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
Save as tutorial4.c and compile with:
|
Save as tutorial4.c and compile with:
|
||||||
|
|
||||||
```
|
gcc -Wall tutorial4.c -o tutorial4 -lm $(pkg-config --cflags --libs libpipewire-0.3)
|
||||||
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 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
|
We're going to store our objects in a structure so that we can pass them
|
||||||
around in callbacks later.
|
around in callbacks later.
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
struct data {
|
struct data {
|
||||||
struct pw_main_loop *loop;
|
struct pw_main_loop *loop;
|
||||||
struct pw_stream *stream;
|
struct pw_stream *stream;
|
||||||
|
@ -136,13 +134,13 @@ int main(int argc, char *argv[])
|
||||||
pw_init(&argc, &argv);
|
pw_init(&argc, &argv);
|
||||||
|
|
||||||
data.loop = pw_main_loop_new(NULL);
|
data.loop = pw_main_loop_new(NULL);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
Next we create a stream object. It takes the mainloop as first argument and
|
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
|
a stream name as the second. Next we provide some properties for the stream
|
||||||
and a callback + data.
|
and a callback + data.
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
data.stream = pw_stream_new_simple(
|
data.stream = pw_stream_new_simple(
|
||||||
pw_main_loop_get_loop(data.loop),
|
pw_main_loop_get_loop(data.loop),
|
||||||
"audio-src",
|
"audio-src",
|
||||||
|
@ -153,7 +151,7 @@ and a callback + data.
|
||||||
NULL),
|
NULL),
|
||||||
&stream_events,
|
&stream_events,
|
||||||
&data);
|
&data);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
We are using `pw_stream_new_simple()` but there is also a `pw_stream_new()` that
|
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
|
takes an existing `struct pw_core` as the first argument and that requires you
|
||||||
|
@ -176,18 +174,18 @@ later.
|
||||||
|
|
||||||
This is the event structure that we use to listen for events:
|
This is the event structure that we use to listen for events:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
static const struct pw_stream_events stream_events = {
|
static const struct pw_stream_events stream_events = {
|
||||||
PW_VERSION_STREAM_EVENTS,
|
PW_VERSION_STREAM_EVENTS,
|
||||||
.process = on_process,
|
.process = on_process,
|
||||||
};
|
};
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
We are for the moment only interested now in the `process` event. This event
|
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 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:
|
is implemented but first we need to setup the format of the stream:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
const struct spa_pod *params[1];
|
const struct spa_pod *params[1];
|
||||||
uint8_t buffer[1024];
|
uint8_t buffer[1024];
|
||||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||||
|
@ -200,7 +198,7 @@ is implemented but first we need to setup the format of the stream:
|
||||||
.format = SPA_AUDIO_FORMAT_S16,
|
.format = SPA_AUDIO_FORMAT_S16,
|
||||||
.channels = DEFAULT_CHANNELS,
|
.channels = DEFAULT_CHANNELS,
|
||||||
.rate = DEFAULT_RATE ));
|
.rate = DEFAULT_RATE ));
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
This is using a `struct spa_pod_builder` to make a `struct spa_pod *` object
|
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`
|
in the buffer array on the stack. The parameter is of type `SPA_PARAM_EnumFormat`
|
||||||
|
@ -213,7 +211,7 @@ make these POD objects.
|
||||||
|
|
||||||
Now we're ready to connect the stream and run the main loop:
|
Now we're ready to connect the stream and run the main loop:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
pw_stream_connect(data.stream,
|
pw_stream_connect(data.stream,
|
||||||
PW_DIRECTION_OUTPUT,
|
PW_DIRECTION_OUTPUT,
|
||||||
PW_ID_ANY,
|
PW_ID_ANY,
|
||||||
|
@ -223,7 +221,7 @@ Now we're ready to connect the stream and run the main loop:
|
||||||
params, 1);
|
params, 1);
|
||||||
|
|
||||||
pw_main_loop_run(data.loop);
|
pw_main_loop_run(data.loop);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
To connect we specify that we have a `PW_DIRECTION_OUTPUT` stream. `PW_ID_ANY`
|
To connect we specify that we have a `PW_DIRECTION_OUTPUT` stream. `PW_ID_ANY`
|
||||||
means that we are ok with connecting to any consumer. Next we set some flags:
|
means that we are ok with connecting to any consumer. Next we set some flags:
|
||||||
|
@ -252,7 +250,7 @@ The main program flow of the process function is:
|
||||||
* adjust buffer with number of written bytes, offset, stride,
|
* adjust buffer with number of written bytes, offset, stride,
|
||||||
* `pw_stream_queue_buffer()` to queue the buffer for playback.
|
* `pw_stream_queue_buffer()` to queue the buffer for playback.
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
static void on_process(void *userdata)
|
static void on_process(void *userdata)
|
||||||
{
|
{
|
||||||
struct data *data = userdata;
|
struct data *data = userdata;
|
||||||
|
@ -289,7 +287,7 @@ static void on_process(void *userdata)
|
||||||
|
|
||||||
pw_stream_queue_buffer(data->stream, b);
|
pw_stream_queue_buffer(data->stream, b);
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
Check out the docs for [buffers](spa-buffer.md) for more information
|
Check out the docs for [buffers](spa-buffer.md) for more information
|
||||||
about how to work with buffers.
|
about how to work with buffers.
|
||||||
|
@ -298,4 +296,6 @@ Try to change the number of channels, samplerate or format; the stream
|
||||||
will automatically convert to the format on the server.
|
will automatically convert to the format on the server.
|
||||||
|
|
||||||
|
|
||||||
[[previous]](tutorial3.md) [[index]](tutorial-index.md) [[next]](tutorial5.md)
|
\ref page_tutorial3 | \ref page_tutorial "Index" | \ref page_tutorial5
|
||||||
|
|
||||||
|
*/
|
|
@ -1,17 +1,17 @@
|
||||||
# Tutorial - Part 5: Capturing video frames
|
/** \page page_tutorial5 Tutorial - Part 5: Capturing video frames
|
||||||
|
|
||||||
[[previous]](tutorial4.md) [[index]](tutorial-index.md) [[next]](tutorial6.md)
|
\ref page_tutorial4 | \ref page_tutorial "Index" | \ref page_tutorial6
|
||||||
|
|
||||||
In this tutorial we show how to use a stream to capture a
|
In this tutorial we show how to use a stream to capture a
|
||||||
stream of video frames.
|
stream of video frames.
|
||||||
|
|
||||||
Even though we are now working with a different media type and
|
Even though we are now working with a different media type and
|
||||||
we are capturing instead of playback, you will see that this
|
we are capturing instead of playback, you will see that this
|
||||||
example is very similar to the previous [tutorial 4](tutorial4.md).
|
example is very similar to \ref page_tutorial4.
|
||||||
|
|
||||||
Let's take a look at the code before we break it down:
|
Let's take a look at the code before we break it down:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
#include <spa/param/video/format-utils.h>
|
#include <spa/param/video/format-utils.h>
|
||||||
#include <spa/debug/types.h>
|
#include <spa/debug/types.h>
|
||||||
#include <spa/param/video/type-info.h>
|
#include <spa/param/video/type-info.h>
|
||||||
|
@ -140,21 +140,18 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
Save as tutorial5.c and compile with:
|
Save as tutorial5.c and compile with:
|
||||||
|
|
||||||
```
|
gcc -Wall tutorial5.c -o tutorial5 -lm $(pkg-config --cflags --libs libpipewire-0.3)
|
||||||
gcc -Wall tutorial5.c -o tutorial5 -lm $(pkg-config --cflags --libs libpipewire-0.3)
|
|
||||||
```
|
|
||||||
|
|
||||||
Most of the application is structured like the previous
|
Most of the application is structured like \ref page_tutorial4.
|
||||||
[tutorial 4](tutorial4.md).
|
|
||||||
|
|
||||||
We create a stream object with different properties to make it a Camera
|
We create a stream object with different properties to make it a Camera
|
||||||
Video Capture stream.
|
Video Capture stream.
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
data.stream = pw_stream_new_simple(
|
data.stream = pw_stream_new_simple(
|
||||||
pw_main_loop_get_loop(data.loop),
|
pw_main_loop_get_loop(data.loop),
|
||||||
"video-capture",
|
"video-capture",
|
||||||
|
@ -165,24 +162,24 @@ Video Capture stream.
|
||||||
NULL),
|
NULL),
|
||||||
&stream_events,
|
&stream_events,
|
||||||
&data);
|
&data);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
In addition to the `process` event, we are also going to listen to a new event,
|
In addition to the `process` event, we are also going to listen to a new event,
|
||||||
`param_changed`:
|
`param_changed`:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
static const struct pw_stream_events stream_events = {
|
static const struct pw_stream_events stream_events = {
|
||||||
PW_VERSION_STREAM_EVENTS,
|
PW_VERSION_STREAM_EVENTS,
|
||||||
.param_changed = on_param_changed,
|
.param_changed = on_param_changed,
|
||||||
.process = on_process,
|
.process = on_process,
|
||||||
};
|
};
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
Because we capture a stream of a wide range of different
|
Because we capture a stream of a wide range of different
|
||||||
video formats and resolutions, we have to describe our accepted formats in
|
video formats and resolutions, we have to describe our accepted formats in
|
||||||
a different way:
|
a different way:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
const struct spa_pod *params[1];
|
const struct spa_pod *params[1];
|
||||||
uint8_t buffer[1024];
|
uint8_t buffer[1024];
|
||||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||||
|
@ -207,7 +204,7 @@ a different way:
|
||||||
&SPA_FRACTION(25, 1),
|
&SPA_FRACTION(25, 1),
|
||||||
&SPA_FRACTION(0, 1),
|
&SPA_FRACTION(0, 1),
|
||||||
&SPA_FRACTION(1000, 1)));
|
&SPA_FRACTION(1000, 1)));
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
This is using a `struct spa_pod_builder` to make a `struct spa_pod *` object
|
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`
|
in the buffer array on the stack. The parameter is of type `SPA_PARAM_EnumFormat`
|
||||||
|
@ -220,7 +217,7 @@ We have an enumeration of formats, we need to first give the amount of enumerati
|
||||||
that follow, then the default (preferred) value, followed by alternatives in order
|
that follow, then the default (preferred) value, followed by alternatives in order
|
||||||
of preference:
|
of preference:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7,
|
SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7,
|
||||||
SPA_VIDEO_FORMAT_RGB, /* default */
|
SPA_VIDEO_FORMAT_RGB, /* default */
|
||||||
SPA_VIDEO_FORMAT_RGB, /* alternative 1 */
|
SPA_VIDEO_FORMAT_RGB, /* alternative 1 */
|
||||||
|
@ -229,17 +226,17 @@ of preference:
|
||||||
SPA_VIDEO_FORMAT_BGRx,
|
SPA_VIDEO_FORMAT_BGRx,
|
||||||
SPA_VIDEO_FORMAT_YUY2,
|
SPA_VIDEO_FORMAT_YUY2,
|
||||||
SPA_VIDEO_FORMAT_I420),
|
SPA_VIDEO_FORMAT_I420),
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
We also have a `RANGE` of values for the size. We need to give a default (preferred)
|
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:
|
size and then a min and max value:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
|
SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
|
||||||
&SPA_RECTANGLE(320, 240), /* default */
|
&SPA_RECTANGLE(320, 240), /* default */
|
||||||
&SPA_RECTANGLE(1, 1), /* min */
|
&SPA_RECTANGLE(1, 1), /* min */
|
||||||
&SPA_RECTANGLE(4096, 4096)), /* max */
|
&SPA_RECTANGLE(4096, 4096)), /* max */
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
We have something similar for the framerate.
|
We have something similar for the framerate.
|
||||||
|
|
||||||
|
@ -251,7 +248,7 @@ POD objects.
|
||||||
|
|
||||||
Now we're ready to connect the stream and run the main loop:
|
Now we're ready to connect the stream and run the main loop:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
pw_stream_connect(data.stream,
|
pw_stream_connect(data.stream,
|
||||||
PW_DIRECTION_INPUT,
|
PW_DIRECTION_INPUT,
|
||||||
argc > 1 ? (uint32_t)atoi(argv[1]) : PW_ID_ANY,
|
argc > 1 ? (uint32_t)atoi(argv[1]) : PW_ID_ANY,
|
||||||
|
@ -260,7 +257,7 @@ Now we're ready to connect the stream and run the main loop:
|
||||||
params, 1);
|
params, 1);
|
||||||
|
|
||||||
pw_main_loop_run(data.loop);
|
pw_main_loop_run(data.loop);
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
To connect we specify that we have a `PW_DIRECTION_INPUT` stream. `PW_ID_ANY`
|
To connect we specify that we have a `PW_DIRECTION_INPUT` stream. `PW_ID_ANY`
|
||||||
means that we are ok with connecting to any producer. We also allow the user
|
means that we are ok with connecting to any producer. We also allow the user
|
||||||
|
@ -283,14 +280,14 @@ connected.
|
||||||
Let's take a look at how we can parse the format in the `param_changed`
|
Let's take a look at how we can parse the format in the `param_changed`
|
||||||
event:
|
event:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
static void on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param)
|
static void on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param)
|
||||||
{
|
{
|
||||||
struct data *data = userdata;
|
struct data *data = userdata;
|
||||||
|
|
||||||
if (param == NULL || id != SPA_PARAM_Format)
|
if (param == NULL || id != SPA_PARAM_Format)
|
||||||
return;
|
return;
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
First check if there is a param. A NULL param means that it is cleared. The id
|
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
|
of the param tells you what param it is. We are only interested in Format
|
||||||
|
@ -301,7 +298,7 @@ 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
|
EnumFormat contains different media types or subtypes, this is how you can
|
||||||
parse them:
|
parse them:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
if (spa_format_parse(param,
|
if (spa_format_parse(param,
|
||||||
&data->format.media_type,
|
&data->format.media_type,
|
||||||
&data->format.media_subtype) < 0)
|
&data->format.media_subtype) < 0)
|
||||||
|
@ -310,13 +307,13 @@ parse them:
|
||||||
if (data->format.media_type != SPA_MEDIA_TYPE_video ||
|
if (data->format.media_type != SPA_MEDIA_TYPE_video ||
|
||||||
data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
|
data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
|
||||||
return;
|
return;
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
For the `video/raw` media type/subtype there is a utility function to
|
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
|
parse out the values into a `struct spa_video_info`. This makes it easier
|
||||||
to deal with.
|
to deal with.
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
if (spa_format_video_raw_parse(param, &data->format.info.raw) < 0)
|
if (spa_format_video_raw_parse(param, &data->format.info.raw) < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -331,16 +328,16 @@ to deal with.
|
||||||
|
|
||||||
/** prepare to render video of this size */
|
/** prepare to render video of this size */
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
In this example we dump the video size and parameters but in a real playback
|
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
|
or capture application you might want to set up the screen or encoder to
|
||||||
deal with the format.
|
deal with the format.
|
||||||
|
|
||||||
After negotiation, the process function is called for each new frame. Check out
|
After negotiation, the process function is called for each new frame. Check out
|
||||||
[tutorial 4](tutorial4.md) for another example.
|
\ref page_tutorial4 for another example.
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
static void on_process(void *userdata)
|
static void on_process(void *userdata)
|
||||||
{
|
{
|
||||||
struct data *data = userdata;
|
struct data *data = userdata;
|
||||||
|
@ -361,9 +358,11 @@ static void on_process(void *userdata)
|
||||||
|
|
||||||
pw_stream_queue_buffer(data->stream, b);
|
pw_stream_queue_buffer(data->stream, b);
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
In a real playback application, one would do something with the data, like
|
In a real playback application, one would do something with the data, like
|
||||||
copy it to the screen or encode it into a file.
|
copy it to the screen or encode it into a file.
|
||||||
|
|
||||||
[[previous]](tutorial4.md) [[index]](tutorial-index.md) [[next]](tutorial6.md)
|
\ref page_tutorial4 | \ref page_tutorial "Index" | \ref page_tutorial6
|
||||||
|
|
||||||
|
*/
|
|
@ -1,13 +1,13 @@
|
||||||
# Tutorial - Part 6: Binding objects
|
/** \page page_tutorial6 Tutorial - Part 6: Binding objects
|
||||||
|
|
||||||
[[previous]](tutorial5.md) [[index]](tutorial-index.md)
|
\ref page_tutorial5 | \ref page_tutorial "Index"
|
||||||
|
|
||||||
In this tutorial we show how to bind to an object so that we can
|
In this tutorial we show how to bind to an object so that we can
|
||||||
receive events and call methods on the object.
|
receive events and call methods on the object.
|
||||||
|
|
||||||
Let take a look at the following application to start.
|
Let take a look at the following application to start.
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
|
|
||||||
struct data {
|
struct data {
|
||||||
|
@ -94,22 +94,20 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
To compile the simple test application, copy it into a tutorial6.c file and
|
To compile the simple test application, copy it into a tutorial6.c file and
|
||||||
use:
|
use:
|
||||||
|
|
||||||
```
|
gcc -Wall tutorial6.c -o tutorial6 $(pkg-config --cflags --libs libpipewire-0.3)
|
||||||
gcc -Wall tutorial6.c -o tutorial6 $(pkg-config --cflags --libs libpipewire-0.3)
|
|
||||||
```
|
|
||||||
|
|
||||||
Most of this is the same as [tutorial 2](tutorial2.md) where we simply
|
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
|
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.
|
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:
|
We use the `pw_registry_bind()` method on our registry object like this:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
static void registry_event_global(void *_data, uint32_t id,
|
static void registry_event_global(void *_data, uint32_t id,
|
||||||
uint32_t permissions, const char *type,
|
uint32_t permissions, const char *type,
|
||||||
uint32_t version, const struct spa_dict *props)
|
uint32_t version, const struct spa_dict *props)
|
||||||
|
@ -124,7 +122,7 @@ static void registry_event_global(void *_data, uint32_t id,
|
||||||
/* ... */
|
/* ... */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
We bind to the first client object that we see. This gives us a pointer
|
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`.
|
to a `struct pw_proxy` that we can also cast to a `struct pw_client`.
|
||||||
|
@ -138,7 +136,7 @@ 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
|
after we bind to it or when it changes. This is not very different
|
||||||
from the registry listener we added before:
|
from the registry listener we added before:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
static void client_info(void *object, const struct pw_client_info *info)
|
static void client_info(void *object, const struct pw_client_info *info)
|
||||||
{
|
{
|
||||||
struct data *data = object;
|
struct data *data = object;
|
||||||
|
@ -167,7 +165,7 @@ static void registry_event_global(void *_data, uint32_t id,
|
||||||
&client_events, data);
|
&client_events, data);
|
||||||
/* ... */
|
/* ... */
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
We're also quitting the mainloop after we get the info to nicely stop
|
We're also quitting the mainloop after we get the info to nicely stop
|
||||||
our tutorial application.
|
our tutorial application.
|
||||||
|
@ -175,13 +173,15 @@ our tutorial application.
|
||||||
When we stop the application, don't forget to destroy all proxies that
|
When we stop the application, don't forget to destroy all proxies that
|
||||||
you created. Otherwise, they will be leaked:
|
you created. Otherwise, they will be leaked:
|
||||||
|
|
||||||
```c
|
\code{.c}
|
||||||
/* ... */
|
/* ... */
|
||||||
pw_proxy_destroy((struct pw_proxy *)data.client);
|
pw_proxy_destroy((struct pw_proxy *)data.client);
|
||||||
/* ... */
|
/* ... */
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
\endcode
|
||||||
|
|
||||||
[[previous]](tutorial5.md) [[index]](tutorial-index.md)
|
\ref page_tutorial5 | \ref page_tutorial "Index"
|
||||||
|
|
||||||
|
*/
|
Loading…
Reference in a new issue