summaryrefslogtreecommitdiff
path: root/doc/tutorial4.md
blob: e5ff7a47b5eb2b260baf2fff05bc65ddb1293287 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
[[previous]](tutorial3.md) [[index]](tutorial-index.md) [[next]](tutorial5.md)

# Playing a tone (Tutorial 4)

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
#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;
}
```

Save as tutorial4.c and compile with:

```
gcc -Wall tutorial4.c -o tutorial4 -lm $(pkg-config --cflags --libs libpipewire-0.3)
```

We start with the usual boilerplate, `pw_init()` and a `pw_main_loop_new()`.
We're going to store our objects in a structure so that we can pass them
around in callbacks later.

```c
struct data {
	struct pw_main_loop *loop;
	struct pw_stream *stream;
	double accumulator;
};

int main(int argc, char *argv[])
{
	struct data data = { 0, };

	pw_init(&argc, &argv);

	data.loop = pw_main_loop_new(NULL);
```

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
	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);
```

We using `pw_stream_new_simple()` but there is also a `pw_stream_new()` that
takes an exising `struct pw_core` as the first argument and that requires you
to add the event handle manually, for more control. The `pw_stream_new_simple()`
is, as the name implies, easier to use because it creates  a `struct pw_context`
and `struct pw_core` automatically.

In the properties we need to give as much information about the stream as we
can so that the session manager can make good decisions about how and where
to route this stream. There are 3 important properties to configure:

* `PW_KEY_MEDIA_TYPE`      The media type, like Audio, Video, Midi
* `pw_KEY_MEDIA_CATEGORY`  The category, like Playback, Capture, Duplex, Monitor
* `PW_KEY_MEDIA_ROLE`      The media role, like Movie, Music, Camera, Screen,
					Communication, Game, Notification, DSP,
					Production, Accessibility, Test

The properties are owned by the stream and freed when the stream is destroyed
later.

This is the event structure that we use to listen for events:

```c
static const struct pw_stream_events stream_events = {
	PW_VERSION_STREAM_EVENTS,
	.process = on_process,
};
```

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
	const struct spa_pod *params[1];
	uint8_t buffer[1024];
	struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));

#define DEFAULT_RATE		44100
#define DEFAULT_CHANNELS	2

	params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
			&SPA_AUDIO_INFO_RAW_INIT(
				.format = SPA_AUDIO_FORMAT_S16,
				.channels = DEFAULT_CHANNELS,
				.rate = DEFAULT_RATE ));
```

This is using a `struct spa_pod_builder` to make a `struct spa_pod *` object
in the buffer array on the stack. The parameter is of type `SPA_PARAM_EnumFormat`
which means that it enumerates the possible formats for this stream. We have
only one, a Signed 16 bit stereo format at 44.1KHz.

We use `spa_format_audio_raw_build()` which is a helper function to make the param
with the builder. See [SPA POD](spa/pod.md) for more information about how to
make these POD objects.

Now we're ready to connect the stream and run the main loop:

```c
	pw_stream_connect(data.stream,
			  PW_DIRECTION_OUTPUT,
			  PW_ID_ANY,
			  PW_STREAM_FLAG_AUTOCONNECT |
			  PW_STREAM_FLAG_MAP_BUFFERS |
			  PW_STREAM_FLAG_RT_PROCESS,
			  params, 1);

	pw_main_loop_run(data.loop);
```

To connect we speficy that we have an `PW_DIRECTION_OUTPUT` stream. `PW_ID_ANY`
means that we are ok with conneting to any consumer. Next we set some flags:

* `PW_STREAM_FLAG_AUTOCONNECT`  automatically connect this stream. This instructs
                                the session manager to link us to some consumer.
* `PW_STREAM_FLAG_MAP_BUFFERS`	mmap the buffers for us so we can access the
                                memory. If you don't set these flags you have
				either work with the fd or mmap yourself.
* `PW_STREAM_FLAG_RT_PROCESS` 	Run the process function in the realtime thread.
		                Only use this is the process function only 
				uses functions that are realtime safe, this means
				no allocation or file access or locking.

And last we pass the extra parameters for our stream. Here we only have the
allowed formats (`SPA_PARAM_EnumFormat`).

Running the mainloop will then start processing and will result in our
`process` callback to be called. Let have a look at that function now.

The main program flow of the process function is:

* `pw_stream_dequeue_buffer()` to obtain a buffer to write into.
* Get pointers in buffer memory to write to
* write data into buffer
* adjust buffer with number of written bytes, offset, stride,
* `pw_stream_queue_buffer()` to queue the buffer for playback.

```c
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);
}
```

Check out the docs for [buffers](spa/buffer.md) for more information
about how to work with buffers.

Try to change the number of channels, samplerate or format; the stream
will automatically convert to the format on the server.


[[previous]](tutorial3.md) [[index]](tutorial-index.md) [[next]](tutorial5.md)