From 9b245da5c07aea4d64caebcd140a66853b823b0e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 11 Jun 2020 16:25:47 +0200 Subject: [PATCH] docs: add tutorial 3 --- doc/tutorial-index.md | 2 + doc/tutorial2.md | 4 +- doc/tutorial3.md | 210 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 doc/tutorial3.md diff --git a/doc/tutorial-index.md b/doc/tutorial-index.md index d2feec379..f35d7537f 100644 --- a/doc/tutorial-index.md +++ b/doc/tutorial-index.md @@ -6,3 +6,5 @@ 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)). diff --git a/doc/tutorial2.md b/doc/tutorial2.md index cc20dfd1c..9ec01a602 100644 --- a/doc/tutorial2.md +++ b/doc/tutorial2.md @@ -59,11 +59,11 @@ int main(int argc, char *argv[]) } ``` -To compile the simple test application, copy it into a test2.c file and +To compile the simple test application, copy it into a tutorial2.c file and use: ``` -gcc -Wall test2.c -o test2 $(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: diff --git a/doc/tutorial3.md b/doc/tutorial3.md new file mode 100644 index 000000000..6263cd621 --- /dev/null +++ b/doc/tutorial3.md @@ -0,0 +1,210 @@ +[previous](tutorial2.md) [index](tutorial-index.md) [next](tutorial4.md) + +# Forcing a roundtrip (Tutorial 3) + +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 +and add the extra code to implement the roundtrip. + +Let's take the following small method first: + +```c +static int roundtrip(struct pw_core *core, struct pw_main_loop *loop) +{ + struct spa_hook core_listener; + int pending, done = 0; + + void core_event_done(void *object, uint32_t id, int seq) { + if (id == PW_ID_CORE && seq == pending) { + done = 1; + pw_main_loop_quit(loop); + } + } + const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .done = core_event_done, + }; + + spa_zero(core_listener); + pw_core_add_listener(core, &core_listener, + &core_events, NULL); + + pending = pw_core_sync(core, PW_ID_CORE, 0); + + while (!done) { + pw_main_loop_run(loop); + } + spa_hook_remove(&core_listener); + return 0; +} +``` + +Let's take a look at what this method does. + +```c + struct spa_hook core_listener; + spa_zero(core_listener); + pw_core_add_listener(core, &core_listener, + &core_events, NULL); +``` + +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 + int pending, done = 0; + + void core_event_done(void *object, uint32_t id, int seq) { + if (id == PW_ID_CORE && seq == pending) { + done = 1; + pw_main_loop_quit(loop); + } + } + const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .done = core_event_done, + }; +``` + +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 +variable to 1 and call `pw_main_loop_quit()`. + +Next we do: + +```c + pending = pw_core_sync(core, PW_ID_CORE, 0); +``` + +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 returns value will reflect this. PipeWire +uses the return values of the underlying SPA (Simple Plugin API) +helper objects (See also [error codes](spa/design.md#error-codes). + +Because all messages on the PipeWire server are handled sequencially, +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: + +```c + while (!done) { + pw_main_loop_run(loop); + } +``` + +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 + spa_hook_remove(&core_listener); + return 0; +} +``` + +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. + + +```c +#include + +static int roundtrip(struct pw_core *core, struct pw_main_loop *loop) +{ + struct spa_hook core_listener; + int pending, done = 0; + void core_event_done(void *object, uint32_t id, int seq) { + if (id == PW_ID_CORE && seq == pending) { + done = 1; + pw_main_loop_quit(loop); + } + } + const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .done = core_event_done, + }; + + spa_zero(core_listener); + pw_core_add_listener(core, &core_listener, + &core_events, NULL); + + pending = pw_core_sync(core, PW_ID_CORE, 0); + + while (!done) { + pw_main_loop_run(loop); + } + spa_hook_remove(&core_listener); + return 0; +} + +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); +} + +static const struct pw_registry_events registry_events = { + PW_VERSION_REGISTRY_EVENTS, + .global = registry_event_global, +}; + +int main(int argc, char *argv[]) +{ + struct pw_main_loop *loop; + struct pw_context *context; + struct pw_core *core; + struct pw_registry *registry; + struct spa_hook registry_listener; + + pw_init(&argc, &argv); + + loop = pw_main_loop_new(NULL /* properties */); + context = pw_context_new(pw_main_loop_get_loop(loop), + NULL /* properties */, + 0 /* user_data size */); + + core = pw_context_connect(context, + NULL /* properties */, + 0 /* user_data size */); + + 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); + + roundtrip(core, loop); + + pw_proxy_destroy((struct pw_proxy*)registry); + pw_core_disconnect(core); + pw_context_destroy(context); + pw_main_loop_destroy(loop); + + return 0; +} +``` + +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) +``` + +[previous](tutorial2.md) [index](tutorial-index.md) [next](tutorial4.md)