PipeWire: SPA POD

PipeWire 1.6.5

Loading...
Searching...
No Matches
SPA POD

POD (plain old data) is a sort of data container.

It is comparable to DBus Variant or LV2 Atom.

A POD can express nested structures of objects (with properties), vectors, arrays, sequences and various primitives types. All information in the POD is laid out sequentially in memory and can be written directly to storage or exchanged between processes or threads without additional marshalling.

Each POD is made of a 32 bits size followed by a 32 bits type field, followed by the POD contents. This makes it possible to skip over unknown POD types. The POD start is always aligned to 8 bytes.

POD's can be efficiently constructed and parsed in real-time threads without requiring memory allocations.

POD's use the SPA type system for the basic types and containers. See the SPA types for more info.

# Types

POD's can contain a number of basic SPA types:

- SPA_TYPE_None: No value or a NULL pointer.
- SPA_TYPE_Bool: A boolean value.
- SPA_TYPE_Id: An enumerated value.
- SPA_TYPE_Int, SPA_TYPE_Long, SPA_TYPE_Float, SPA_TYPE_Double: various numeral types, 32 and 64 bits.
- SPA_TYPE_String: A string.
- SPA_TYPE_Bytes: A byte array.
- SPA_TYPE_Rectangle: A rectangle with width and height.
- SPA_TYPE_Fraction: A fraction with numerator and denominator.
- SPA_TYPE_Bitmap: An array of bits. Deprecated and unused.

POD's can be grouped together in these container types:

- SPA_TYPE_Array: An array of equal sized objects.
- SPA_TYPE_Struct: A collection of types and objects.
- SPA_TYPE_Object: An object with properties.
- SPA_TYPE_Sequence: A timed sequence of POD's.

POD's can also contain some extra types:

- SPA_TYPE_Pointer: A typed pointer in memory.
- SPA_TYPE_Fd: A file descriptor.
- SPA_TYPE_Choice: A choice of values.
- SPA_TYPE_Pod: A generic type for the POD itself.

# Constructing A POD

A POD is usually constructed with a struct spa_pod_builder. The builder needs to be initialized with a memory region to write into. It is also possible to dynamically grow the memory as needed.

The most common way to construct a POD is on the stack. This does not require any memory allocations. The size of the POD can be estimated pretty easily and if the buffer is not large enough, an appropriate error will be generated.

The code fragment below initializes a POD builder to write into the stack allocated buffer.

uint8_t buffer[4096];
struct spa_pod_builder b;
spa_pod_builder_init(&b, buffer, sizeof(buffer));
spa_pod_builder_init
SPA_API_POD_BUILDER void spa_pod_builder_init(struct spa_pod_builder *builder, void *data, uint32_t size)
Definition builder.h:102
spa_pod_builder
Definition builder.h:63

Next we need to write some object into the builder. Let's write a simple struct with an Int and Float in it. Structs are comparable to JSON arrays.

struct spa_pod_frame f;
spa_pod_builder_push_struct(&b, &f);
spa_pod_builder_push_struct
SPA_API_POD_BUILDER int spa_pod_builder_push_struct(struct spa_pod_builder *builder, struct spa_pod_frame *frame)
Definition builder.h:462
spa_pod_frame
Definition body.h:38

First we open the struct container, the struct spa_pod_frame keeps track of the container context. Next we add some values to the container like this:

spa_pod_builder_int(&b, 5);
spa_pod_builder_float(&b, 3.1415f);
spa_pod_builder_int
SPA_API_POD_BUILDER int spa_pod_builder_int(struct spa_pod_builder *builder, int32_t val)
Definition builder.h:296
spa_pod_builder_float
SPA_API_POD_BUILDER int spa_pod_builder_float(struct spa_pod_builder *builder, float val)
Definition builder.h:314

Then we close the container by popping the frame again:

struct spa_pod *pod;
pod = spa_pod_builder_pop(&b, &f);
spa_pod_builder_pop
SPA_API_POD_BUILDER void * spa_pod_builder_pop(struct spa_pod_builder *builder, struct spa_pod_frame *frame)
Definition builder.h:213
spa_pod
Definition pod.h:57

spa_pod_builder_pop() returns a reference to the object we completed on the stack.

## Using varargs Builder

We can also use the following construct to make POD objects:

spa_pod_builder_push_struct(&b, &f);
spa_pod_builder_add(&b,
SPA_POD_Int(5),
SPA_POD_Float(3.1415f));
pod = spa_pod_builder_pop(&b, &f);
spa_pod_builder_add
SPA_API_POD_BUILDER int spa_pod_builder_add(struct spa_pod_builder *builder,...)
Definition builder.h:713
SPA_POD_Float
#define SPA_POD_Float(val)
Definition vararg.h:84
SPA_POD_Int
#define SPA_POD_Int(val)
Definition vararg.h:58

Or even shorter:

pod = spa_pod_builder_add_struct(&b,
SPA_POD_Int(5),
SPA_POD_Float(3.1415f));
spa_pod_builder_add_struct
#define spa_pod_builder_add_struct(b,...)
Definition builder.h:734

It's not possible to use the varargs builder to make a sequence or array, use the normal builder methods for that.

## Making Objects

POD objects are containers for properties and are comparable to JSON objects.

Start by pushing an object:

spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
SPA_PARAM_Props
@ SPA_PARAM_Props
properties as SPA_TYPE_OBJECT_Props
Definition param.h:32
spa_pod_builder_push_object
SPA_API_POD_BUILDER int spa_pod_builder_push_object(struct spa_pod_builder *builder, struct spa_pod_frame *frame, uint32_t type, uint32_t id)
Definition builder.h:475
SPA_TYPE_OBJECT_Props
@ SPA_TYPE_OBJECT_Props
Definition type.h:86

An object requires an object type (SPA_TYPE_OBJECT_Props) and a context ID (SPA_PARAM_Props). The object type defines the properties that can be added to the object and their meaning. The SPA type system allows you to make this connection (See the type system).

Next we can push some properties in the object:

spa_pod_builder_prop(&b, SPA_PROP_device, 0);
spa_pod_builder_string(&b, "hw:0");
spa_pod_builder_prop(&b, SPA_PROP_frequency, 0);
spa_pod_builder_float(&b, 440.0);
SPA_PROP_device
@ SPA_PROP_device
Definition props.h:47
SPA_PROP_frequency
@ SPA_PROP_frequency
Definition props.h:69
spa_pod_builder_string
SPA_API_POD_BUILDER int spa_pod_builder_string(struct spa_pod_builder *builder, const char *str)
Definition builder.h:351
spa_pod_builder_prop
SPA_API_POD_BUILDER int spa_pod_builder_prop(struct spa_pod_builder *builder, uint32_t key, uint32_t flags)
Definition builder.h:490

As can be seen, we always need to push a prop (with key and flags) and then the associated value. For performance reasons it is a good idea to always push (and parse) the object keys in ascending order.

Don't forget to pop the result when the object is finished:

pod = spa_pod_builder_pop(&b, &f);

There is a shortcut for making objects:

pod = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, SPA_PARAM_Props,
SPA_PROP_device, SPA_POD_String("hw:0"),
SPA_PROP_frequency, SPA_POD_Float(440.0f));
SPA_POD_String
#define SPA_POD_String(val)
Definition vararg.h:102
spa_pod_builder_add_object
#define spa_pod_builder_add_object(b, type, id,...)
Definition builder.h:725

## Choice Values

It is possible to express ranges or enumerations of possible values for properties (and to some extend structs). This is achieved with choice values.

Choice values are really just a choice type and an array of choice values (of the same type). Depending on the choice type, the array values are interpreted in different ways:

- SPA_CHOICE_None: No choice, first value is current.
- SPA_CHOICE_Range: Range: default, min, max.
- SPA_CHOICE_Step: Range with step: default, min, max, step.
- SPA_CHOICE_Enum: Enum: default, alternative,...
- SPA_CHOICE_Flags: Bitmask of flags.

Let's illustrate this with a props object that specifies a range of possible values for the frequency:

struct spa_pod_frame f2;
spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
spa_pod_builder_prop(&b, SPA_PROP_frequency, 0);
spa_pod_builder_push_choice(&b, &f2, SPA_CHOICE_Range, 0);
spa_pod_builder_float(&b, 440.0); // default
spa_pod_builder_float(&b, 110.0); // min
spa_pod_builder_float(&b, 880.0); // min
pod = spa_pod_builder_pop(&b, &f2);
pod = spa_pod_builder_pop(&b, &f);
spa_pod_builder_push_choice
SPA_API_POD_BUILDER int spa_pod_builder_push_choice(struct spa_pod_builder *builder, struct spa_pod_frame *frame, uint32_t type, uint32_t flags)
Definition builder.h:446
SPA_CHOICE_Range
@ SPA_CHOICE_Range
range: default, min, max
Definition pod.h:162
spa_pod_frame::pod
struct spa_pod pod
Definition body.h:39

As you can see, first push the choice as a range, then the values. A range choice expects at least three values, the default value, minimum and maximum values. There is a shortcut for this as well using varargs:

pod = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, SPA_PARAM_Props,
SPA_PROP_frequency, SPA_POD_CHOICE_RANGE_Float(440.0f, 110.0f, 880.0f));
SPA_POD_CHOICE_RANGE_Float
#define SPA_POD_CHOICE_RANGE_Float(def, min, max)
Definition vararg.h:88

## Choice Examples

This is a description of a possible SPA_TYPE_OBJECT_Format as used when enumerating allowed formats (SPA_PARAM_EnumFormat) in SPA objects:

pod = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
// specify the media type and subtype
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
// audio/raw properties
SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(4, // 4 values follow
SPA_AUDIO_FORMAT_S16, // default
SPA_AUDIO_FORMAT_S16, // alternative1
SPA_AUDIO_FORMAT_S32, // alternative2
SPA_AUDIO_FORMAT_F32 // alternative3
),
SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(
44100, // default
8000, // min
192000 // max
),
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2));
SPA_MEDIA_TYPE_audio
@ SPA_MEDIA_TYPE_audio
Definition format.h:27
SPA_PARAM_EnumFormat
@ SPA_PARAM_EnumFormat
available formats as SPA_TYPE_OBJECT_Format
Definition param.h:33
SPA_FORMAT_mediaType
@ SPA_FORMAT_mediaType
media type (Id enum spa_media_type)
Definition format.h:99
SPA_FORMAT_AUDIO_rate
@ SPA_FORMAT_AUDIO_rate
sample rate (Int)
Definition format.h:106
SPA_FORMAT_mediaSubtype
@ SPA_FORMAT_mediaSubtype
media subtype (Id enum spa_media_subtype)
Definition format.h:100
SPA_FORMAT_AUDIO_format
@ SPA_FORMAT_AUDIO_format
audio format, (Id enum spa_audio_format)
Definition format.h:104
SPA_FORMAT_AUDIO_channels
@ SPA_FORMAT_AUDIO_channels
number of audio channels (Int)
Definition format.h:107
SPA_MEDIA_SUBTYPE_raw
@ SPA_MEDIA_SUBTYPE_raw
Definition format.h:38
SPA_AUDIO_FORMAT_S32
@ SPA_AUDIO_FORMAT_S32
Definition raw.h:100
SPA_AUDIO_FORMAT_S16
@ SPA_AUDIO_FORMAT_S16
Definition raw.h:96
SPA_AUDIO_FORMAT_F32
@ SPA_AUDIO_FORMAT_F32
Definition raw.h:108
SPA_POD_CHOICE_ENUM_Id
#define SPA_POD_CHOICE_ENUM_Id(n_vals,...)
Definition vararg.h:55
SPA_POD_CHOICE_RANGE_Int
#define SPA_POD_CHOICE_RANGE_Int(def, min, max)
Definition vararg.h:62
SPA_POD_Id
#define SPA_POD_Id(val)
Definition vararg.h:53
SPA_TYPE_OBJECT_Format
@ SPA_TYPE_OBJECT_Format
Definition type.h:87

## Fixate

We can remove all choice values from the object with the spa_pod_object_fixate() method. This modifies the pod in-place and sets all choice properties to SPA_CHOICE_None, forcing the default value as the only available value in the choice.

Running fixate on our previous example would result in an object equivalent to:

pod = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
// specify the media type and subtype
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
// audio/raw properties
SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16),
SPA_FORMAT_AUDIO_rate, SPA_POD_Int(44100),
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2));

# Parsing A POD

Parsing a POD usually consists of:

- Validating if raw bytes + size can contain a valid POD.
- Inspecting the type of a POD.
- Looping over the items in an object or struct.
- Getting data out of POD's.

## Validating Bytes

Use spa_pod_from_data() to check if maxsize of bytes in data contain a POD at the size bytes starting at offset. This function checks that the POD size will fit and not overflow.

struct spa_pod *pod;
pod = spa_pod_from_data(data, maxsize, offset, size);
spa_pod_from_data
SPA_API_POD_ITER void * spa_pod_from_data(void *data, size_t maxsize, off_t offset, size_t size)
Definition iter.h:129
spa_pod::size
uint32_t size
Definition pod.h:58

## Checking The Type Of POD

Use one of spa_pod_is_bool(), spa_pod_is_int(), etc to check for the type of the pod. For simple (non-container) types, spa_pod_get_bool(), spa_pod_get_int() etc can be used to extract the value of the pod.

spa_pod_is_object_type() can be used to check if the POD contains an object of the expected type.

## Struct Fields

To iterate over the fields of a struct use:

struct spa_pod *pod, *obj;
SPA_POD_STRUCT_FOREACH(obj, pod) {
printf("field type:%d\n", pod->type);
}
SPA_POD_STRUCT_FOREACH
#define SPA_POD_STRUCT_FOREACH(obj, iter)
Definition iter.h:110
spa_pod::type
uint32_t type
Definition pod.h:59

For parsing structs it is usually much easier to use the parser below.

## Object Properties

To iterate over the properties in an object you can do:

struct spa_pod_prop *prop;
struct spa_pod_object *obj = (struct spa_pod_object*)pod;
SPA_POD_OBJECT_FOREACH(pod, prop) {
printf("prop key:%d\n", prop->key);
}
SPA_POD_OBJECT_FOREACH
#define SPA_POD_OBJECT_FOREACH(obj, iter)
Definition iter.h:118
spa_pod_object
Definition pod.h:202
spa_pod_object::pod
struct spa_pod pod
Definition pod.h:203
spa_pod_prop
Definition pod.h:227
spa_pod_prop::key
uint32_t key
key of property, list of valid keys depends on the object type
Definition pod.h:228

There is a function to retrieve the property for a certain key in the object. If the properties of the object are in ascending order, you can start searching from the previous key.

struct spa_pod_prop *prop;
prop = spa_pod_find_prop(obj, NULL, SPA_FORMAT_AUDIO_format);
// .. use first prop
prop = spa_pod_find_prop(obj, prop, SPA_FORMAT_AUDIO_rate);
// .. use next prop
spa_pod_find_prop
SPA_API_POD_ITER const struct spa_pod_prop * spa_pod_find_prop(const struct spa_pod *pod, const struct spa_pod_prop *start, uint32_t key)
Definition iter.h:274

## Parser

Similar to the builder, there is a parser object as well.

If the fields in a struct are known, it is much easier to use the parser. Similarly, if the object type (and thus its keys) are known, the parser is easier.

First initialize a struct spa_pod_parser:

struct spa_pod_parser p;
spa_pod_parser_pod(&p, obj);
spa_pod_parser_pod
SPA_API_POD_PARSER void spa_pod_parser_pod(struct spa_pod_parser *parser, const struct spa_pod *pod)
Definition parser.h:60
spa_pod_parser
Definition parser.h:44

You can then enter containers such as objects or structs with a push operation:

struct spa_pod_frame f;
spa_pod_parser_push_struct(&p, &f);
spa_pod_parser_push_struct
SPA_API_POD_PARSER int spa_pod_parser_push_struct(struct spa_pod_parser *parser, struct spa_pod_frame *frame)
Definition parser.h:400

You need to store the context in a struct spa_pod_frame to be able to exit the container again later.

You can then parse each field. The parser takes care of moving to the next field.

uint32_t id, val;
spa_pod_parser_get_id(&p, &id);
spa_pod_parser_get_int(&p, &val);
...
id
uint32_t id
Definition core.h:432
spa_pod_parser_get_int
SPA_API_POD_PARSER int spa_pod_parser_get_int(struct spa_pod_parser *parser, int32_t *value)
Definition parser.h:236
spa_pod_parser_get_id
SPA_API_POD_PARSER int spa_pod_parser_get_id(struct spa_pod_parser *parser, uint32_t *value)
Definition parser.h:224

And finally exit the container again:

spa_pod_parser_pop(&p, &f);
spa_pod_parser_pop
SPA_API_POD_PARSER int spa_pod_parser_pop(struct spa_pod_parser *parser, struct spa_pod_frame *frame)
Definition parser.h:204

## Parser With Variable Arguments

In most cases, parsing objects is easier with the variable argument functions. The parse function look like the mirror image of the builder functions.

To parse a struct:

spa_pod_parser_get_struct(&p,
SPA_POD_Id(&id),
SPA_POD_Int(&val));
spa_pod_parser_get_struct
#define spa_pod_parser_get_struct(p,...)
Definition parser.h:917

To parse properties in an object:

uint32_t id, type, subtype, format, rate, channels;
spa_pod_parser_get_object(&p,
SPA_TYPE_OBJECT_Format, &id,
SPA_FORMAT_mediaType, SPA_POD_Id(&type),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(&subtype),
SPA_FORMAT_AUDIO_format, SPA_POD_Id(&format),
SPA_FORMAT_AUDIO_rate, SPA_POD_Int(&rate),
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(&channels));
spa_pod_parser_get_object
#define spa_pod_parser_get_object(p, type, id,...)
Definition parser.h:906

When parsing objects it is possible to have optional fields. You can make a field optional be parsing it with the SPA_POD_OPT_ prefix for the type.

In the next example, the rate and channels fields are optional and when they are not present, the variables will not be changed.

uint32_t id, type, subtype, format, rate = 0, channels = 0;
spa_pod_parser_get_object(&p,
SPA_TYPE_OBJECT_Format, &id,
SPA_FORMAT_mediaType, SPA_POD_Id(&type),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(&subtype),
SPA_FORMAT_AUDIO_format, SPA_POD_Id(&format),
SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&rate),
SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&channels));
SPA_POD_OPT_Int
#define SPA_POD_OPT_Int(val)
Definition parser.h:866

It is not possible to parse a sequence or array with the parser. Use the iterator for this.

## Choice Values

The parser will handle choice values as long as they are of type none. It will then parse the single value from the choice. When dealing with other choice values, it's possible to parse the property values into a struct spa_pod and then inspect the choice manually, if needed.

Here is an example of parsing the format values as a POD:

uint32_t id, type, subtype;
struct spa_pod *format;
spa_pod_parser_get_object(&p,
SPA_TYPE_OBJECT_Format, &id,
SPA_FORMAT_mediaType, SPA_POD_Id(&type),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(&subtype),
SPA_FORMAT_AUDIO_format, SPA_POD_Pod(&format));
SPA_POD_Pod
#define SPA_POD_Pod(val)
Definition vararg.h:136

spa_pod_get_values() is a useful function. It returns a struct spa_pod* with and array of values. For invalid PODs it returns the POD and no values. For normal PODs it returns the POD and one value. For choice values it returns the choice type and an array of values. If the choice doesn't fit even a single value, the array will have no values.

struct spa_pod *value;
uint32_t n_vals, choice;
value = spa_pod_get_values(pod, &n_vals, &choice);
switch (choice) {
case SPA_CHOICE_None:
// one single value
break;
case SPA_CHOICE_Range:
// array of values of type of pod, cast to right type
// to iterate.
uint32_t *v = SPA_POD_BODY(values);
if (n_vals < 3)
break;
printf("default value: %u\n", v[0]);
printf("min value: %u\n", v[1]);
printf("max value: %u\n", v[2]);
break;
// ...
default:
break;
}
spa_pod_get_values
SPA_API_POD_ITER struct spa_pod * spa_pod_get_values(const struct spa_pod *pod, uint32_t *n_vals, uint32_t *choice)
Definition iter.h:229
SPA_POD_BODY
#define SPA_POD_BODY(pod)
Definition pod.h:44
SPA_CHOICE_None
@ SPA_CHOICE_None
no choice, first value is current
Definition pod.h:161

# Filter

Given two POD objects of the same type (object, struct, ..) one can run a filter and generate a new POD that only contains values that are compatible with both input POD's.

This is, for example, used to find a compatible format between two ports.

As an example we can run a filter on two simple POD's:

pod = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(4, // 4 values follow
SPA_AUDIO_FORMAT_S16, // default
SPA_AUDIO_FORMAT_S16, // alternative1
SPA_AUDIO_FORMAT_S32, // alternative2
SPA_AUDIO_FORMAT_F32 // alternative3
));
filter = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(3, // 3 values follow
SPA_AUDIO_FORMAT_S16, // default
SPA_AUDIO_FORMAT_S16, // alternative1
SPA_AUDIO_FORMAT_F64 // alternative2
));
struct spa_pod *result;
if (spa_pod_filter(&b, &result, pod, filter) < 0)
goto exit_error;
SPA_AUDIO_FORMAT_F64
@ SPA_AUDIO_FORMAT_F64
Definition raw.h:109
spa_pod_filter
SPA_API_POD_FILTER int spa_pod_filter(struct spa_pod_builder *b, struct spa_pod **result, const struct spa_pod *pod, const struct spa_pod *filter)
Definition filter.h:370

Filter will contain a POD equivalent to:

result = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_AUDIO_format, SPA_AUDIO_FORMAT_S16);

# POD Layout

A POD always starts with a size/type pair of uint32_t in native endianness, followed by size in bytes of the payload data and padding. See SPA POD for more details.

The payload is always padded to 8 bytes so that a complete pod is always a multiple of 8 bytes.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| payload ... |
. | ... padding .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The total size of the POD is thus ROUND_UP_8(8 + size).

# POD Types

Here follows the layout of the POD types.

## None (1)

Type 1 is the None type or the null pointer. It has a size of 0 and thus no payload.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Bool (2)

Type 2 is the Bool type. I contains a true or false value. The value is stored in a int32, a value of 0 is false, any other value is true.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| value (int32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Id (3)

An id is stored as a uint32. The id refers to an index in a table where more information about the value can be found. This is typically a type table containing some well known ids.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 3 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| id (uint32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Int (4)

A 32 bit signed integer.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| value (int32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Long (5)

A 64 bit signed integer.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 8 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 5 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| value (int64) |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Float (6)

A 32 bit float value.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 6 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| value (float32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Double (7)

A 64 bit float value.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 8 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 7 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| value (float64) |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## String (8)

A string. This does not have to be valid UTF8 but it is 0 terminated. The size field is set to the length of the string, including the 0 byte.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 8 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| chars .... |
. .
| ... 0 | padding.. |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Bytes (9)

A byte array. The size field is set to the number of bytes.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 9 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| bytes .... |
. .
| | padding.. |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Rectangle (10)

A Rectangle.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 8 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 10 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| width (uint32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| height (uint32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Fraction (11)

A Fraction.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 8 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 11 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| num (uint32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| denom (uint32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Bitmap (12)

A bitmap. Stored as bits in uint8. size is the number of bytes with bits.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 12 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| bits (uint8) ... |
. .
| | padding.. |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Array (13)

An array is an array of (basic) types. In principle the array can contain any type as long as each item in the array has the same child_size and child_type.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 13 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| child_size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| child_type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| child1 (child_size bytes) ... |
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| childN (child_size bytes) ... |
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

We describe Array types with a shortcut like:

Array[Int](<val1>,<val2>,...)

## Struct (14)

Multiple PODs can be combined into a struct:

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 14 (Struct) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| size1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| type1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| payload 1... |
. | ... padding .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sizeN |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| typeN |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| payloadN ... |
. | ... padding .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

We describe Struct types with a shortcut like:

Struct(
<pod1 type> : <pod1 description>,
<pod2 type> : <pod2 description>,
...)

The type of a struct is 14 and the size the total sum in bytes of all PODs (with padding) inside the struct.

## Object (15)

An object contains a set of of properties.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 15 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| object_type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| object_id |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| property1 |
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| propertyN |
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

object_type is one of the well defined object types. object_id is extra information about the context of the object.

Each property is as follows:

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| key (uint32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| flags (uint32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| POD value ... |
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Object are written with a shortcut as:

Object[type,id](
key1: <pod1>,
key2: <pod1>,
...)

## Sequence (16)

A sequence is a series of times events. It is usually used for transporting MIDI and control updates.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 16 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unit |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| pad |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| control1 |
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| controlN |
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The unit field and pad is currently set to 0.

Each control look like:

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| offset (uint32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| type (uint32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| POD value ... |
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

- offset: the offset relative to the current graph clock time.
- type: the type of control, see enum spa_control_type

## Pointer (17)

A generic pointer to some memory region. Pointer types are usually not serialized.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 16 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 17 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| padding (must be 0) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| native pointer value ... |
. | .. padding .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Fd (18)

A file descriptor stored as int64. When serializing, the file descriptor is modified to contain the index of the fd in the message.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 8 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 18 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| fd (int64) ... |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

## Choice (19)

A choice contains an array of possible values.

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 19 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| child_size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| child_type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| child1 (child_size bytes) ... |
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| childN (child_size bytes) ... |
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

- type: one of possible values, see enum spa_choice_type
  - None (0) : only child1 is an valid option
  - Range (1) : child1 is a default value, options are between child2 and child3 in the value array.
  - Step (2) : child1 is a default value, options are between child2 and child3, in steps of child4 in the value array.
  - Enum (3) : child1 is a default value, options are any value from the value array, preferred values come first.
  - Flags (4) : only child1 is a flag value. When filtering, the flags are AND-ed together.
- flags: must be 0

## Pod (20)

The value id the POD itself.