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:
Peter Hutterer 2021-05-25 13:08:04 +10:00 committed by Wim Taymans
parent fce28ac59b
commit 9ed9980fa2
9 changed files with 145 additions and 153 deletions

View file

@ -48,13 +48,13 @@ extra_docs = [
'pipewire-design.md',
'pipewire-architecture.md',
'pipewire-objects-design.md',
'tutorial-index.md',
'tutorial1.md',
'tutorial2.md',
'tutorial3.md',
'tutorial4.md',
'tutorial5.md',
'tutorial6.md',
'tutorial.dox',
'tutorial1.dox',
'tutorial2.dox',
'tutorial3.dox',
'tutorial4.dox',
'tutorial5.dox',
'tutorial6.dox',
'spa-index.md',
'spa-design.md',
'spa-pod.md',

View file

@ -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
View 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
*/

View file

@ -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.
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.
```c
\code{.c}
#include <pipewire/pipewire.h>
int main(int argc, char *argv[])
@ -23,7 +24,7 @@ int main(int argc, char *argv[])
pw_get_library_version());
return 0;
}
```
\endcode
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
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:
```
# ./test1
Compiled with libpipewire 0.3.5
Linked with libpipewire 0.3.5
#
```
# ./test1
Compiled 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
*/

View file

@ -1,14 +1,13 @@
# Tutorial - Part 2: Enumerating objects
[[previous]](tutorial1.md) [[index]](tutorial-index.md) [[next]](tutorial3.md)
/** \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.
```c
\code{.c}
#include <pipewire/pipewire.h>
static void registry_event_global(void *data, uint32_t id,
@ -58,14 +57,12 @@ int main(int argc, char *argv[])
return 0;
}
```
\endcode
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)
```
gcc -Wall tutorial2.c -o tutorial2 $(pkg-config --cflags --libs libpipewire-0.3)
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
modules and setup logging and other tasks.
```c
\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
@ -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
us to connect to a PipeWire daemon:
```c
\code{.c}
struct pw_main_loop *loop;
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),
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
@ -115,12 +112,12 @@ the code easier to read.
With the context we can now connect to the PipeWire daemon:
```c
\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
@ -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.
```c
\code{.c}
struct pw_registry *registry;
struct spa_hook registry_listener;
@ -141,7 +138,7 @@ we're going to handle them on the registry object.
spa_zero(registry_listener);
pw_registry_add_listener(registry, &registry_listener,
&registry_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
@ -152,7 +149,7 @@ events we want to listen to.
This is how we define the event handler and the function to
handle the events:
```c
\code{.c}
static const struct pw_registry_events registry_events = {
PW_VERSION_REGISTRY_EVENTS,
.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);
}
```
\endcode
Now that everything is set up we can start the mainloop and let
the communication between client and server continue:
```c
\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.
[[previous]](tutorial1.md) [[index]](tutorial-index.md) [[next]](tutorial3.md)
\ref page_tutorial1 | \ref page_tutorial "Index" | \ref page_tutorial3
*/

View file

@ -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
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.
Let's take the following small method first:
```c
\code{.c}
static int roundtrip(struct pw_core *core, struct pw_main_loop *loop)
{
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);
return 0;
}
```
\endcode
Let's take a look at what this method does.
```c
\code{.c}
struct spa_hook core_listener;
spa_zero(core_listener);
pw_core_add_listener(core, &core_listener,
&core_events, NULL);
```
\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:
```c
\code{.c}
int pending, done = 0;
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,
.done = core_event_done,
};
```
\endcode
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
@ -75,9 +75,9 @@ variable to 1 and call `pw_main_loop_quit()`.
Next we do:
```c
\code{.c}
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.
@ -96,20 +96,20 @@ method in the sequence number.
We then run the mainloop to send the messages to the server and
receive the events:
```c
\code{.c}
while (!done) {
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:
```c
\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
@ -118,7 +118,7 @@ completed and thus that we also received all events for the globals
on the server.
```c
\code{.c}
#include <pipewire/pipewire.h>
static int roundtrip(struct pw_core *core, struct pw_main_loop *loop)
@ -196,22 +196,20 @@ int main(int argc, char *argv[])
return 0;
}
```
\endcode
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)
```
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:
```c
\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
@ -219,18 +217,20 @@ an error to destroy an object more than once.
We can disconnect from the server with:
```c
\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:
```c
\code{.c}
pw_context_destroy(context);
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
*/

View file

@ -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.
Let's take a look at the code before we break it down:
```c
\code{.c}
#include <math.h>
#include <spa/param/audio/format-utils.h>
@ -110,19 +110,17 @@ int main(int argc, char *argv[])
return 0;
}
```
\endcode
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're going to store our objects in a structure so that we can pass them
around in callbacks later.
```c
\code{.c}
struct data {
struct pw_main_loop *loop;
struct pw_stream *stream;
@ -136,13 +134,13 @@ int main(int argc, char *argv[])
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.
```c
\code{.c}
data.stream = pw_stream_new_simple(
pw_main_loop_get_loop(data.loop),
"audio-src",
@ -153,7 +151,7 @@ and a callback + data.
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
@ -176,18 +174,18 @@ later.
This is the event structure that we use to listen for events:
```c
\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:
```c
\code{.c}
const struct spa_pod *params[1];
uint8_t buffer[1024];
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,
.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`
@ -213,7 +211,7 @@ make these POD objects.
Now we're ready to connect the stream and run the main loop:
```c
\code{.c}
pw_stream_connect(data.stream,
PW_DIRECTION_OUTPUT,
PW_ID_ANY,
@ -223,7 +221,7 @@ Now we're ready to connect the stream and run the main loop:
params, 1);
pw_main_loop_run(data.loop);
```
\endcode
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:
@ -252,7 +250,7 @@ The main program flow of the process function is:
* adjust buffer with number of written bytes, offset, stride,
* `pw_stream_queue_buffer()` to queue the buffer for playback.
```c
\code{.c}
static void on_process(void *userdata)
{
struct data *data = userdata;
@ -289,7 +287,7 @@ static void on_process(void *userdata)
pw_stream_queue_buffer(data->stream, b);
}
```
\endcode
Check out the docs for [buffers](spa-buffer.md) for more information
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.
[[previous]](tutorial3.md) [[index]](tutorial-index.md) [[next]](tutorial5.md)
\ref page_tutorial3 | \ref page_tutorial "Index" | \ref page_tutorial5
*/

View file

@ -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
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 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:
```c
\code{.c}
#include <spa/param/video/format-utils.h>
#include <spa/debug/types.h>
#include <spa/param/video/type-info.h>
@ -140,21 +140,18 @@ int main(int argc, char *argv[])
return 0;
}
```
\endcode
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
[tutorial 4](tutorial4.md).
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.
```c
\code{.c}
data.stream = pw_stream_new_simple(
pw_main_loop_get_loop(data.loop),
"video-capture",
@ -165,24 +162,24 @@ Video Capture stream.
NULL),
&stream_events,
&data);
```
\endcode
In addition to the `process` event, we are also going to listen to a new event,
`param_changed`:
```c
\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:
```c
\code{.c}
const struct spa_pod *params[1];
uint8_t buffer[1024];
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(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`
@ -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
of preference:
```c
\code{.c}
SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7,
SPA_VIDEO_FORMAT_RGB, /* default */
SPA_VIDEO_FORMAT_RGB, /* alternative 1 */
@ -229,17 +226,17 @@ of preference:
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:
```c
\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.
@ -251,7 +248,7 @@ POD objects.
Now we're ready to connect the stream and run the main loop:
```c
\code{.c}
pw_stream_connect(data.stream,
PW_DIRECTION_INPUT,
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);
pw_main_loop_run(data.loop);
```
\endcode
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
@ -283,14 +280,14 @@ connected.
Let's take a look at how we can parse the format in the `param_changed`
event:
```c
\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
@ -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
parse them:
```c
\code{.c}
if (spa_format_parse(param,
&data->format.media_type,
&data->format.media_subtype) < 0)
@ -310,13 +307,13 @@ parse them:
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.
```c
\code{.c}
if (spa_format_video_raw_parse(param, &data->format.info.raw) < 0)
return;
@ -331,16 +328,16 @@ to deal with.
/** 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
[tutorial 4](tutorial4.md) for another example.
\ref page_tutorial4 for another example.
```c
\code{.c}
static void on_process(void *userdata)
{
struct data *data = userdata;
@ -361,9 +358,11 @@ static void on_process(void *userdata)
pw_stream_queue_buffer(data->stream, b);
}
```
\endcode
In a real playback application, one would do something with the data, like
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
*/

View file

@ -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
receive events and call methods on the object.
Let take a look at the following application to start.
```c
\code{.c}
#include <pipewire/pipewire.h>
struct data {
@ -94,22 +94,20 @@ int main(int argc, char *argv[])
return 0;
}
```
\endcode
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)
```
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
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:
```c
\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)
@ -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
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
from the registry listener we added before:
```c
\code{.c}
static void client_info(void *object, const struct pw_client_info *info)
{
struct data *data = object;
@ -167,7 +165,7 @@ static void registry_event_global(void *_data, uint32_t id,
&client_events, data);
/* ... */
}
```
\endcode
We're also quitting the mainloop after we get the info to nicely stop
our tutorial application.
@ -175,13 +173,15 @@ our tutorial application.
When we stop the application, don't forget to destroy all proxies that
you created. Otherwise, they will be leaked:
```c
\code{.c}
/* ... */
pw_proxy_destroy((struct pw_proxy *)data.client);
/* ... */
return 0;
}
```
\endcode
[[previous]](tutorial5.md) [[index]](tutorial-index.md)
\ref page_tutorial5 | \ref page_tutorial "Index"
*/