diff options
author | Wim Taymans <wtaymans@redhat.com> | 2020-06-09 09:06:07 +0200 |
---|---|---|
committer | Wim Taymans <wtaymans@redhat.com> | 2020-06-09 09:06:07 +0200 |
commit | 8f251fc7d410c1c8aa415227f98c2fda13b35db1 (patch) | |
tree | d472ec63c8da942c736da14ac03517b4be6286da /doc | |
parent | a44bea0b6a770d4cc2d51b6958c535f74e6dc351 (diff) |
docs: add some docs about SPA POD
Diffstat (limited to 'doc')
-rw-r--r-- | doc/spa/pod.md | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/doc/spa/pod.md b/doc/spa/pod.md new file mode 100644 index 00000000..c8e280dc --- /dev/null +++ b/doc/spa/pod.md @@ -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. + + |