doc: add tutorials as Doxygen examples

This commit is contained in:
Pauli Virtanen 2021-10-10 00:09:32 +03:00
parent 98a0e54d5f
commit 336caa9db3
14 changed files with 528 additions and 594 deletions

View file

@ -23,7 +23,8 @@ FILTER_PATTERNS = "*.c=@c_input_filter@" "*.h=@h_input_filter@"
FILE_PATTERNS = "*.h" "*.c" FILE_PATTERNS = "*.h" "*.c"
RECURSIVE = YES RECURSIVE = YES
EXAMPLE_PATH = "@top_srcdir@/src/examples" \ EXAMPLE_PATH = "@top_srcdir@/src/examples" \
"@top_srcdir@/spa/examples" "@top_srcdir@/spa/examples" \
"@top_srcdir@/doc"
EXAMPLE_PATTERNS = "*.c" EXAMPLE_PATTERNS = "*.c"
REFERENCED_BY_RELATION = NO REFERENCED_BY_RELATION = NO

View file

@ -86,6 +86,12 @@ cssfiles = [
# Example files (in order from simple to esoteric) # Example files (in order from simple to esoteric)
example_files = [ example_files = [
'tutorial1.c',
'tutorial2.c',
'tutorial3.c',
'tutorial4.c',
'tutorial5.c',
'tutorial6.c',
'audio-src.c', 'audio-src.c',
'audio-dsp-src.c', 'audio-dsp-src.c',
'audio-dsp-filter.c', 'audio-dsp-filter.c',

19
doc/tutorial1.c Normal file
View file

@ -0,0 +1,19 @@
/*
[title]
\ref page_tutorial1
[title]
*/
/* [code] */
#include <pipewire/pipewire.h>
int main(int argc, char *argv[])
{
pw_init(&argc, &argv);
fprintf(stdout, "Compiled with libpipewire %s\n"
"Linked with libpipewire %s\n",
pw_get_headers_version(),
pw_get_library_version());
return 0;
}
/* [code] */

View file

@ -11,20 +11,7 @@ environment.
Let get started with the simplest application. Let get started with the simplest application.
\code{.c} \snippet tutorial1.c code
#include <pipewire/pipewire.h>
int main(int argc, char *argv[])
{
pw_init(&argc, &argv);
fprintf(stdout, "Compiled with libpipewire %s\n"
"Linked with libpipewire %s\n",
pw_get_headers_version(),
pw_get_library_version());
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()`.

56
doc/tutorial2.c Normal file
View file

@ -0,0 +1,56 @@
/*
[title]
\ref page_tutorial2
[title]
*/
/* [code] */
#include <pipewire/pipewire.h>
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, &registry_listener,
&registry_events, NULL);
pw_main_loop_run(loop);
pw_proxy_destroy((struct pw_proxy*)registry);
pw_core_disconnect(core);
pw_context_destroy(context);
pw_main_loop_destroy(loop);
return 0;
}
/* [code] */

View file

@ -7,57 +7,7 @@ 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.
\code{.c} \snippet tutorial2.c code
#include <pipewire/pipewire.h>
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, &registry_listener,
&registry_events, NULL);
pw_main_loop_run(loop);
pw_proxy_destroy((struct pw_proxy*)registry);
pw_core_disconnect(core);
pw_context_destroy(context);
pw_main_loop_destroy(loop);
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:

86
doc/tutorial3.c Normal file
View file

@ -0,0 +1,86 @@
/*
[title]
\ref page_tutorial3
[title]
*/
/* [code] */
#include <pipewire/pipewire.h>
/* [roundtrip] */
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;
}
/* [roundtrip] */
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, &registry_listener,
&registry_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;
}
/* [code] */

View file

@ -10,36 +10,7 @@ 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:
\code{.c} \snippet tutorial3.c roundtrip
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;
}
\endcode
Let's take a look at what this method does. Let's take a look at what this method does.
@ -117,86 +88,7 @@ are finished. This means that the `pw_core_get_registry()` call
completed and thus that we also received all events for the globals completed and thus that we also received all events for the globals
on the server. on the server.
\snippet tutorial3.c code
\code{.c}
#include <pipewire/pipewire.h>
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, &registry_listener,
&registry_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;
}
\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:

112
doc/tutorial4.c Normal file
View file

@ -0,0 +1,112 @@
/*
[title]
\ref page_tutorial4
[title]
*/
/* [code] */
#include <math.h>
#include <spa/param/audio/format-utils.h>
#include <pipewire/pipewire.h>
#define M_PI_M2 ( M_PI + M_PI )
#define DEFAULT_RATE 44100
#define DEFAULT_CHANNELS 2
#define DEFAULT_VOLUME 0.7
struct data {
struct pw_main_loop *loop;
struct pw_stream *stream;
double accumulator;
};
/* [on_process] */
static void on_process(void *userdata)
{
struct data *data = userdata;
struct pw_buffer *b;
struct spa_buffer *buf;
int i, c, n_frames, stride;
int16_t *dst, val;
if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) {
pw_log_warn("out of buffers: %m");
return;
}
buf = b->buffer;
if ((dst = buf->datas[0].data) == NULL)
return;
stride = sizeof(int16_t) * DEFAULT_CHANNELS;
n_frames = buf->datas[0].maxsize / stride;
for (i = 0; i < n_frames; i++) {
data->accumulator += M_PI_M2 * 440 / DEFAULT_RATE;
if (data->accumulator >= M_PI_M2)
data->accumulator -= M_PI_M2;
val = sin(data->accumulator) * DEFAULT_VOLUME * 16767.f;
for (c = 0; c < DEFAULT_CHANNELS; c++)
*dst++ = val;
}
buf->datas[0].chunk->offset = 0;
buf->datas[0].chunk->stride = stride;
buf->datas[0].chunk->size = n_frames * stride;
pw_stream_queue_buffer(data->stream, b);
}
/* [on_process] */
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.process = on_process,
};
int main(int argc, char *argv[])
{
struct data data = { 0, };
const struct spa_pod *params[1];
uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
pw_init(&argc, &argv);
data.loop = pw_main_loop_new(NULL);
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);
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 ));
pw_stream_connect(data.stream,
PW_DIRECTION_OUTPUT,
argc > 1 ? (uint32_t)atoi(argv[1]) : 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);
pw_stream_destroy(data.stream);
pw_main_loop_destroy(data.loop);
return 0;
}
/* [code] */

View file

@ -6,111 +6,7 @@ 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:
\code{.c} \snippet tutorial4.c code
#include <math.h>
#include <spa/param/audio/format-utils.h>
#include <pipewire/pipewire.h>
#define M_PI_M2 ( M_PI + M_PI )
#define DEFAULT_RATE 44100
#define DEFAULT_CHANNELS 2
#define DEFAULT_VOLUME 0.7
struct data {
struct pw_main_loop *loop;
struct pw_stream *stream;
double accumulator;
};
static void on_process(void *userdata)
{
struct data *data = userdata;
struct pw_buffer *b;
struct spa_buffer *buf;
int i, c, n_frames, stride;
int16_t *dst, val;
if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) {
pw_log_warn("out of buffers: %m");
return;
}
buf = b->buffer;
if ((dst = buf->datas[0].data) == NULL)
return;
stride = sizeof(int16_t) * DEFAULT_CHANNELS;
n_frames = buf->datas[0].maxsize / stride;
for (i = 0; i < n_frames; i++) {
data->accumulator += M_PI_M2 * 440 / DEFAULT_RATE;
if (data->accumulator >= M_PI_M2)
data->accumulator -= M_PI_M2;
val = sin(data->accumulator) * DEFAULT_VOLUME * 16767.f;
for (c = 0; c < DEFAULT_CHANNELS; c++)
*dst++ = val;
}
buf->datas[0].chunk->offset = 0;
buf->datas[0].chunk->stride = stride;
buf->datas[0].chunk->size = n_frames * stride;
pw_stream_queue_buffer(data->stream, b);
}
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.process = on_process,
};
int main(int argc, char *argv[])
{
struct data data = { 0, };
const struct spa_pod *params[1];
uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
pw_init(&argc, &argv);
data.loop = pw_main_loop_new(NULL);
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);
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 ));
pw_stream_connect(data.stream,
PW_DIRECTION_OUTPUT,
argc > 1 ? (uint32_t)atoi(argv[1]) : 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);
pw_stream_destroy(data.stream);
pw_main_loop_destroy(data.loop);
return 0;
}
\endcode
Save as tutorial4.c and compile with: Save as tutorial4.c and compile with:
@ -250,44 +146,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.
\code{.c} \snippet tutorial4.c on_process
static void on_process(void *userdata)
{
struct data *data = userdata;
struct pw_buffer *b;
struct spa_buffer *buf;
int i, c, n_frames, stride;
int16_t *dst, val;
if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) {
pw_log_warn("out of buffers: %m");
return;
}
buf = b->buffer;
if ((dst = buf->datas[0].data) == NULL)
return;
stride = sizeof(int16_t) * DEFAULT_CHANNELS;
n_frames = buf->datas[0].maxsize / stride;
for (i = 0; i < n_frames; i++) {
data->accumulator += M_PI_M2 * 440 / DEFAULT_RATE;
if (data->accumulator >= M_PI_M2)
data->accumulator -= M_PI_M2;
val = sin(data->accumulator) * DEFAULT_VOLUME * 16767.f;
for (c = 0; c < DEFAULT_CHANNELS; c++)
*dst++ = val;
}
buf->datas[0].chunk->offset = 0;
buf->datas[0].chunk->stride = stride;
buf->datas[0].chunk->size = n_frames * stride;
pw_stream_queue_buffer(data->stream, b);
}
\endcode
Check out the docs for \ref page_spa_buffer for more information Check out the docs for \ref page_spa_buffer for more information
about how to work with buffers. about how to work with buffers.

137
doc/tutorial5.c Normal file
View file

@ -0,0 +1,137 @@
/*
[title]
\ref page_tutorial5
[title]
*/
/* [code] */
#include <spa/param/video/format-utils.h>
#include <spa/debug/types.h>
#include <spa/param/video/type-info.h>
#include <pipewire/pipewire.h>
struct data {
struct pw_main_loop *loop;
struct pw_stream *stream;
struct spa_video_info format;
};
/* [on_process] */
static void on_process(void *userdata)
{
struct data *data = userdata;
struct pw_buffer *b;
struct spa_buffer *buf;
if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) {
pw_log_warn("out of buffers: %m");
return;
}
buf = b->buffer;
if (buf->datas[0].data == NULL)
return;
/** copy frame data to screen */
printf("got a frame of size %d\n", buf->datas[0].chunk->size);
pw_stream_queue_buffer(data->stream, b);
}
/* [on_process] */
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;
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;
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 */
}
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.param_changed = on_param_changed,
.process = on_process,
};
int main(int argc, char *argv[])
{
struct data data = { 0, };
const struct spa_pod *params[1];
uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
pw_init(&argc, &argv);
data.loop = pw_main_loop_new(NULL);
data.stream = pw_stream_new_simple(
pw_main_loop_get_loop(data.loop),
"video-capture",
pw_properties_new(
PW_KEY_MEDIA_TYPE, "Video",
PW_KEY_MEDIA_CATEGORY, "Capture",
PW_KEY_MEDIA_ROLE, "Camera",
NULL),
&stream_events,
&data);
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)));
pw_stream_connect(data.stream,
PW_DIRECTION_INPUT,
argc > 1 ? (uint32_t)atoi(argv[1]) : PW_ID_ANY,
PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_MAP_BUFFERS,
params, 1);
pw_main_loop_run(data.loop);
pw_stream_destroy(data.stream);
pw_main_loop_destroy(data.loop);
return 0;
}
/* [code] */

View file

@ -11,136 +11,7 @@ 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:
\code{.c} \snippet tutorial5.c code
#include <spa/param/video/format-utils.h>
#include <spa/debug/types.h>
#include <spa/param/video/type-info.h>
#include <pipewire/pipewire.h>
struct data {
struct pw_main_loop *loop;
struct pw_stream *stream;
struct spa_video_info format;
};
static void on_process(void *userdata)
{
struct data *data = userdata;
struct pw_buffer *b;
struct spa_buffer *buf;
if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) {
pw_log_warn("out of buffers: %m");
return;
}
buf = b->buffer;
if (buf->datas[0].data == NULL)
return;
/** copy frame data to screen */
printf("got a frame of size %d\n", buf->datas[0].chunk->size);
pw_stream_queue_buffer(data->stream, b);
}
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;
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;
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 */
}
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.param_changed = on_param_changed,
.process = on_process,
};
int main(int argc, char *argv[])
{
struct data data = { 0, };
const struct spa_pod *params[1];
uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
pw_init(&argc, &argv);
data.loop = pw_main_loop_new(NULL);
data.stream = pw_stream_new_simple(
pw_main_loop_get_loop(data.loop),
"video-capture",
pw_properties_new(
PW_KEY_MEDIA_TYPE, "Video",
PW_KEY_MEDIA_CATEGORY, "Capture",
PW_KEY_MEDIA_ROLE, "Camera",
NULL),
&stream_events,
&data);
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)));
pw_stream_connect(data.stream,
PW_DIRECTION_INPUT,
argc > 1 ? (uint32_t)atoi(argv[1]) : PW_ID_ANY,
PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_MAP_BUFFERS,
params, 1);
pw_main_loop_run(data.loop);
pw_stream_destroy(data.stream);
pw_main_loop_destroy(data.loop);
return 0;
}
\endcode
Save as tutorial5.c and compile with: Save as tutorial5.c and compile with:
@ -337,28 +208,7 @@ 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
\ref page_tutorial4 for another example. \ref page_tutorial4 for another example.
\code{.c} \snippet tutorial5.c on_process
static void on_process(void *userdata)
{
struct data *data = userdata;
struct pw_buffer *b;
struct spa_buffer *buf;
if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) {
pw_log_warn("out of buffers: %m");
return;
}
buf = b->buffer;
if (buf->datas[0].data == NULL)
return;
/** copy frame data to screen */
printf("got a frame of size %d\n", buf->datas[0].chunk->size);
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.

97
doc/tutorial6.c Normal file
View file

@ -0,0 +1,97 @@
/*
[title]
\ref page_tutorial6
[title]
*/
/* [code] */
#include <pipewire/pipewire.h>
struct data {
struct pw_main_loop *loop;
struct pw_context *context;
struct pw_core *core;
struct pw_registry *registry;
struct spa_hook registry_listener;
struct pw_client *client;
struct spa_hook client_listener;
};
/* [client_info] */
static void client_info(void *object, const struct pw_client_info *info)
{
struct data *data = object;
const struct spa_dict_item *item;
printf("client: id:%u\n", info->id);
printf("\tprops:\n");
spa_dict_for_each(item, info->props)
printf("\t\t%s: \"%s\"\n", item->key, item->value);
pw_main_loop_quit(data->loop);
}
static const struct pw_client_events client_events = {
PW_VERSION_CLIENT_EVENTS,
.info = client_info,
};
/* [client_info] */
/* [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)
{
struct data *data = _data;
if (data->client != NULL)
return;
if (strcmp(type, PW_TYPE_INTERFACE_Client) == 0) {
data->client = pw_registry_bind(data->registry,
id, type, PW_VERSION_CLIENT, 0);
pw_client_add_listener(data->client,
&data->client_listener,
&client_events, data);
}
}
/* [registry_event_global] */
static const struct pw_registry_events registry_events = {
PW_VERSION_REGISTRY_EVENTS,
.global = registry_event_global,
};
int main(int argc, char *argv[])
{
struct data data;
spa_zero(data);
pw_init(&argc, &argv);
data.loop = pw_main_loop_new(NULL /* properties */ );
data.context = pw_context_new(pw_main_loop_get_loop(data.loop),
NULL /* properties */ ,
0 /* user_data size */ );
data.core = pw_context_connect(data.context, NULL /* properties */ ,
0 /* user_data size */ );
data.registry = pw_core_get_registry(data.core, PW_VERSION_REGISTRY,
0 /* user_data size */ );
pw_registry_add_listener(data.registry, &data.registry_listener,
&registry_events, &data);
pw_main_loop_run(data.loop);
pw_proxy_destroy((struct pw_proxy *)data.client);
pw_proxy_destroy((struct pw_proxy *)data.registry);
pw_core_disconnect(data.core);
pw_context_destroy(data.context);
pw_main_loop_destroy(data.loop);
return 0;
}
/* [code] */

View file

@ -7,94 +7,7 @@ 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.
\code{.c} \snippet tutorial6.c code
#include <pipewire/pipewire.h>
struct data {
struct pw_main_loop *loop;
struct pw_context *context;
struct pw_core *core;
struct pw_registry *registry;
struct spa_hook registry_listener;
struct pw_client *client;
struct spa_hook client_listener;
};
static void client_info(void *object, const struct pw_client_info *info)
{
struct data *data = object;
const struct spa_dict_item *item;
printf("client: id:%u\n", info->id);
printf("\tprops:\n");
spa_dict_for_each(item, info->props)
printf("\t\t%s: \"%s\"\n", item->key, item->value);
pw_main_loop_quit(data->loop);
}
static const struct pw_client_events client_events = {
PW_VERSION_CLIENT_EVENTS,
.info = client_info,
};
static void registry_event_global(void *_data, uint32_t id,
uint32_t permissions, const char *type,
uint32_t version, const struct spa_dict *props)
{
struct data *data = _data;
if (data->client != NULL)
return;
if (strcmp(type, PW_TYPE_INTERFACE_Client) == 0) {
data->client = pw_registry_bind(data->registry,
id, type, PW_VERSION_CLIENT, 0);
pw_client_add_listener(data->client,
&data->client_listener,
&client_events, data);
}
}
static const struct pw_registry_events registry_events = {
PW_VERSION_REGISTRY_EVENTS,
.global = registry_event_global,
};
int main(int argc, char *argv[])
{
struct data data;
spa_zero(data);
pw_init(&argc, &argv);
data.loop = pw_main_loop_new(NULL /* properties */ );
data.context = pw_context_new(pw_main_loop_get_loop(data.loop),
NULL /* properties */ ,
0 /* user_data size */ );
data.core = pw_context_connect(data.context, NULL /* properties */ ,
0 /* user_data size */ );
data.registry = pw_core_get_registry(data.core, PW_VERSION_REGISTRY,
0 /* user_data size */ );
pw_registry_add_listener(data.registry, &data.registry_listener,
&registry_events, &data);
pw_main_loop_run(data.loop);
pw_proxy_destroy((struct pw_proxy *)data.client);
pw_proxy_destroy((struct pw_proxy *)data.registry);
pw_core_disconnect(data.core);
pw_context_destroy(data.context);
pw_main_loop_destroy(data.loop);
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:
@ -107,22 +20,7 @@ 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:
\code{.c} \snippet tutorial6.c 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)
{
struct data *data = _data;
if (data->client != NULL)
return;
if (strcmp(type, PW_TYPE_INTERFACE_Client) == 0) {
data->client = pw_registry_bind(data->registry,
id, type, PW_VERSION_CLIENT, 0);
/* ... */
}
}
\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`.
@ -136,25 +34,9 @@ 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:
\snippet tutorial6.c client_info
\code{.c} \code{.c}
static void client_info(void *object, const struct pw_client_info *info)
{
struct data *data = object;
const struct spa_dict_item *item;
printf("client: id:%u\n", info->id);
printf("\tprops:\n");
spa_dict_for_each(item, info->props)
printf("\t\t%s: \"%s\"\n", item->key, item->value);
pw_main_loop_quit(data->loop);
}
static const struct pw_client_events client_events = {
PW_VERSION_CLIENT_EVENTS,
.info = client_info,
};
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)