mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
docs: add some docs about SPA POD
This commit is contained in:
parent
a44bea0b6a
commit
8f251fc7d4
1 changed files with 521 additions and 0 deletions
521
doc/spa/pod.md
Normal file
521
doc/spa/pod.md
Normal file
|
|
@ -0,0 +1,521 @@
|
|||
# POD
|
||||
|
||||
POD (plan old data) is a sort of data container. It is comparable to
|
||||
DBus Variant of 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 layed out sequenctially in memory and can be written directly do
|
||||
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 type.
|
||||
|
||||
PODs can be efficiently constructed and parsed in real-time threads without
|
||||
requiring memory allocations.
|
||||
|
||||
PODs use the SPA type system for the basic types and containers. See
|
||||
the SPA types for more info.
|
||||
|
||||
## Types
|
||||
|
||||
PODs 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 numerial 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
|
||||
|
||||
PODs 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 PODs
|
||||
|
||||
PODs 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 it 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));
|
||||
```
|
||||
|
||||
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);
|
||||
```
|
||||
|
||||
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);
|
||||
```
|
||||
|
||||
The we close the container by popping the frame again:
|
||||
|
||||
```
|
||||
struct spa_pod *pod;
|
||||
pod = spa_pod_builder_pop(&b, &f);
|
||||
```
|
||||
|
||||
`spa_pod_builder_pop()` returns a reference to the object we completed
|
||||
on the stack.
|
||||
|
||||
## Using varags 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);
|
||||
```
|
||||
|
||||
Or even shorter:
|
||||
|
||||
```
|
||||
pod = spa_pod_builder_add_struct(&b,
|
||||
SPA_POD_Int(5),
|
||||
SPA_POD_Float(3.1415f));
|
||||
```
|
||||
|
||||
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);
|
||||
```
|
||||
|
||||
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);
|
||||
```
|
||||
|
||||
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));
|
||||
```
|
||||
|
||||
## 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);
|
||||
```
|
||||
|
||||
As you can see, first push the choice as a Range, then the values. A Range
|
||||
choice expects at least 3 values, the default value, mininum and maximum
|
||||
values. There is a shotcut 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));
|
||||
```
|
||||
|
||||
## 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(
|
||||
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));
|
||||
```
|
||||
|
||||
## 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 PODs.
|
||||
|
||||
## 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);
|
||||
```
|
||||
|
||||
## 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);
|
||||
}
|
||||
```
|
||||
|
||||
For parsing Structs it is usually much easier to use the parser
|
||||
below.
|
||||
|
||||
## Object Properties
|
||||
|
||||
To iterate over the properies 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);
|
||||
}
|
||||
```
|
||||
|
||||
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 */
|
||||
```
|
||||
|
||||
## Parser
|
||||
|
||||
Similar to the builder, there is a parser object as well.
|
||||
|
||||
It the fields in a struct are known, it is much easier to use the
|
||||
parser. Similarly, if the object type (and thus it's) 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);
|
||||
```
|
||||
|
||||
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);
|
||||
```
|
||||
|
||||
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);
|
||||
...
|
||||
```
|
||||
|
||||
And finally exit the container again:
|
||||
|
||||
```
|
||||
spa_pod_parser_pop(&p, &f);
|
||||
```
|
||||
|
||||
## 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));
|
||||
```
|
||||
|
||||
To parse properties in an object:
|
||||
|
||||
```
|
||||
uint32_t type, subtype, format, rate, channels;
|
||||
spa_pod_parser_get_object(&p,
|
||||
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
|
||||
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));
|
||||
```
|
||||
|
||||
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 type, subtype, format, rate = 0, channels = 0;
|
||||
spa_pod_parser_get_object(&p,
|
||||
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
|
||||
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));
|
||||
```
|
||||
|
||||
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 type, subtype;
|
||||
struct spa_pod *format;
|
||||
spa_pod_parser_get_object(&p,
|
||||
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
|
||||
SPA_FORMAT_mediaType, SPA_POD_Id(&type),
|
||||
SPA_FORMAT_mediaSubtype, SPA_POD_Id(&subtype),
|
||||
SPA_FORMAT_AUDIO_format, SPA_POD_Pod(&format));
|
||||
```
|
||||
|
||||
`spa_pod_get_values()` is a useful function. It returns a
|
||||
`struct spa_pod*` with and array of values. For normal PODs
|
||||
and Choice None values, it simply returns the POD and 1 value.
|
||||
For other Choice values it returns the Choice type and an array
|
||||
of 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;
|
||||
}
|
||||
```
|
||||
|
||||
# Filter
|
||||
|
||||
Given 2 pod objects of the same type (Object, Struct, ..) one can
|
||||
run a filter and generate a new pod that only contains values that
|
||||
are compatibe with both input pods.
|
||||
|
||||
This is, for example, used to find a compatible format between to ports.
|
||||
|
||||
As an example we can run a filter on two simple PODs:
|
||||
|
||||
```
|
||||
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(
|
||||
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(
|
||||
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;
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
Each POD has a 32 bits size field, followed by a 32 bits type field. The size
|
||||
field specifies the size following the type field.
|
||||
|
||||
Each POD is aligned to an 8 byte boundary.
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue