Loading...
Searching...
No Matches
Tutorial - Part 4: Playing A Tone

Tutorial - Part 3: Forcing A Roundtrip | Index | Tutorial - Part 5: Capturing Video Frames

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:

#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;
if (b->requested)
n_frames = SPA_MIN(b->requested, n_frames);
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;
/* sin() gives a value between -1.0 and 1.0, we first apply
* the volume and then scale with 32767.0 to get a 16 bits value
* between [-32767 32767].
* Another common method to convert a double to
* 16 bits is to multiple by 32768.0 and then clamp to
* [-32768 32767] to get the full 16 bits range. */
val = sin(data->accumulator) * DEFAULT_VOLUME * 32767.0;
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,
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;
}
format-utils.h
PW_ID_ANY
#define PW_ID_ANY
Definition core.h:66
PW_KEY_MEDIA_TYPE
#define PW_KEY_MEDIA_TYPE
Media.
Definition keys.h:438
PW_KEY_MEDIA_ROLE
#define PW_KEY_MEDIA_ROLE
Role: Movie, Music, Camera, Screen, Communication, Game, Notification, DSP, Production,...
Definition keys.h:444
PW_KEY_MEDIA_CATEGORY
#define PW_KEY_MEDIA_CATEGORY
Media Category: Playback, Capture, Duplex, Monitor, Manager.
Definition keys.h:441
pw_log_warn
#define pw_log_warn(...)
Definition log.h:132
pw_main_loop_destroy
void pw_main_loop_destroy(struct pw_main_loop *loop)
Destroy a loop.
Definition main-loop.c:71
pw_main_loop_run
int pw_main_loop_run(struct pw_main_loop *loop)
Run a main loop.
Definition main-loop.c:120
pw_main_loop_new
struct pw_main_loop * pw_main_loop_new(const struct spa_dict *props)
Create a new main loop.
Definition main-loop.c:61
pw_main_loop_get_loop
struct pw_loop * pw_main_loop_get_loop(struct pw_main_loop *loop)
Get the loop implementation.
Definition main-loop.c:94
pw_init
void pw_init(int *argc, char **argv[])
Initialize PipeWire.
Definition pipewire.c:556
PW_DIRECTION_OUTPUT
#define PW_DIRECTION_OUTPUT
Definition port.h:50
pw_properties_new
struct pw_properties * pw_properties_new(const char *key,...)
Make a new properties object.
Definition properties.c:85
pw_stream_connect
int pw_stream_connect(struct pw_stream *stream, enum pw_direction direction, uint32_t target_id, enum pw_stream_flags flags, const struct spa_pod **params, uint32_t n_params)
Connect a stream for input or output on port_path.
Definition stream.c:1904
pw_stream_dequeue_buffer
struct pw_buffer * pw_stream_dequeue_buffer(struct pw_stream *stream)
Get a buffer that can be filled for playback streams or consumed for capture streams.
Definition stream.c:2425
pw_stream_queue_buffer
int pw_stream_queue_buffer(struct pw_stream *stream, struct pw_buffer *buffer)
Submit a buffer for playback or recycle a buffer for capture.
Definition stream.c:2453
pw_stream_new_simple
struct pw_stream * pw_stream_new_simple(struct pw_loop *loop, const char *name, struct pw_properties *props, const struct pw_stream_events *events, void *data)
Definition stream.c:1603
PW_VERSION_STREAM_EVENTS
#define PW_VERSION_STREAM_EVENTS
Definition stream.h:345
pw_stream_destroy
void pw_stream_destroy(struct pw_stream *stream)
Destroy a stream.
Definition stream.c:1697
PW_STREAM_FLAG_MAP_BUFFERS
@ PW_STREAM_FLAG_MAP_BUFFERS
mmap the buffers except DmaBuf that is not explicitly marked as mappable.
Definition stream.h:393
PW_STREAM_FLAG_AUTOCONNECT
@ PW_STREAM_FLAG_AUTOCONNECT
try to automatically connect this stream
Definition stream.h:388
PW_STREAM_FLAG_RT_PROCESS
@ PW_STREAM_FLAG_RT_PROCESS
call process from the realtime thread.
Definition stream.h:396
spa_format_audio_raw_build
static struct spa_pod * spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, struct spa_audio_info_raw *info)
Definition raw-utils.h:47
SPA_AUDIO_INFO_RAW_INIT
#define SPA_AUDIO_INFO_RAW_INIT(...)
Definition raw.h:293
SPA_PARAM_EnumFormat
@ SPA_PARAM_EnumFormat
available formats as SPA_TYPE_OBJECT_Format
Definition param.h:33
SPA_AUDIO_FORMAT_S16
@ SPA_AUDIO_FORMAT_S16
Definition raw.h:94
SPA_POD_BUILDER_INIT
#define SPA_POD_BUILDER_INIT(buffer, size)
Definition builder.h:62
SPA_MIN
#define SPA_MIN(a, b)
Definition defs.h:151
pipewire.h
pipewire/pipewire.h
pw_buffer
a buffer structure obtained from pw_stream_dequeue_buffer().
Definition stream.h:196
pw_buffer::requested
uint64_t requested
For playback streams, this field contains the suggested amount of data to provide.
Definition stream.h:208
pw_buffer::buffer
struct spa_buffer * buffer
the spa buffer
Definition stream.h:197
pw_main_loop
A main loop object.
pw_stream_events
Events for a stream.
Definition stream.h:343
pw_stream_events::process
void(* process)(void *data)
when a buffer can be queued (for playback streams) or dequeued (for capture streams).
Definition stream.h:370
pw_stream
spa_buffer
A Buffer.
Definition buffer.h:94
spa_buffer::datas
struct spa_data * datas
array of data members
Definition buffer.h:98
spa_chunk::stride
int32_t stride
stride of valid data
Definition buffer.h:49
spa_chunk::size
uint32_t size
size of valid data.
Definition buffer.h:47
spa_chunk::offset
uint32_t offset
offset of valid data.
Definition buffer.h:44
spa_data::chunk
struct spa_chunk * chunk
valid chunk of memory
Definition buffer.h:90
spa_data::data
void * data
optional data pointer
Definition buffer.h:89
spa_data::maxsize
uint32_t maxsize
max size of data
Definition buffer.h:88
spa_pod_builder
Definition builder.h:53
spa_pod_builder::data
void * data
Definition builder.h:54
spa_pod
Definition pod.h:43
spa_pod::type
uint32_t type
Definition pod.h:45

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.

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.

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 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 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 three 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:

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:

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 for more information about how to make these POD objects.

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

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 specify that we have a `PW_DIRECTION_OUTPUT` stream. The third argument is always `PW_ID_ANY`. 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 if the process function only uses functions that are realtime safe, this means no allocation or file access or any 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's 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.

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;
if (b->requested)
n_frames = SPA_MIN(b->requested, n_frames);
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;
/* sin() gives a value between -1.0 and 1.0, we first apply
* the volume and then scale with 32767.0 to get a 16 bits value
* between [-32767 32767].
* Another common method to convert a double to
* 16 bits is to multiple by 32768.0 and then clamp to
* [-32768 32767] to get the full 16 bits range. */
val = sin(data->accumulator) * DEFAULT_VOLUME * 32767.0;
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 SPA Buffers 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.

Tutorial - Part 3: Forcing A Roundtrip | Index | Tutorial - Part 5: Capturing Video Frames