milan-avb: ensure the pw streams match the milan AVB stream format and derives correct strides,

also fix crash in entity parser when not available
This commit is contained in:
hackerman-kl 2026-06-09 09:44:57 +00:00 committed by Alexandre Malki
parent 25203c5293
commit 7262d0f34c
3 changed files with 129 additions and 64 deletions

View file

@ -339,7 +339,8 @@ static void init_descriptor_milan_v12(struct server *server)
/**************************************************************************************/ /**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.1 - ENTITY Descriptor */ /* IEEE 1722.1-2021, Sec. 7.2.1 - ENTITY Descriptor */
/* Milan v1.2, Sec. 5.3.3.1 */ /* Milan v1.2, Sec. 5.3.3.1 */
struct avb_entity_config entity_conf = conf_load_entity(server->impl->props); struct avb_entity_config entity_conf;
conf_load_entity(server->impl->props, &entity_conf);
struct avb_aem_desc_entity entity = { struct avb_aem_desc_entity entity = {
.entity_id = htobe64(server->entity_id), .entity_id = htobe64(server->entity_id),

View file

@ -21,79 +21,96 @@ struct avb_entity_config {
uint32_t controller_capabilities; uint32_t controller_capabilities;
}; };
static inline struct avb_entity_config conf_load_entity (struct pw_properties *props) { /* Default entity configuration, taken from the compiled-in Milan entity model. */
// Grab entity field and turn into pw_properties struct #define AVB_ENTITY_CONFIG_DEFAULT ((struct avb_entity_config) { \
struct avb_entity_config entity_conf; .entity_name = DSC_ENTITY_MODEL_ENTITY_NAME, \
const char *str; .group_name = DSC_ENTITY_MODEL_GROUP_NAME, \
pw_log_info("Acquiring entity properties from entity dict in avb.properties"); .serial_number = DSC_ENTITY_MODEL_SERIAL_NUMBER, \
str = pw_properties_get(props, "entity"); .firmware_version = DSC_ENTITY_MODEL_FIRMWARE_VERSION, \
struct pw_properties *entity_props; .vendor_name = DSC_ENTITY_MODEL_VENDOR_NAME_STRING, \
entity_props = pw_properties_new(NULL,NULL); .model_name = DSC_ENTITY_MODEL_MODEL_NAME_STRING, \
pw_properties_update_string(entity_props, str, strlen(str)); .talker_capabilities = DSC_ENTITY_MODEL_TALKER_CAPABILITIES, \
.listener_capabilities = DSC_ENTITY_MODEL_LISTENER_CAPABILITIES, \
.entity_capabilities = DSC_ENTITY_MODEL_ENTITY_CAPABILITIES, \
.controller_capabilities = DSC_ENTITY_MODEL_CONTROLLER_CAPABILITIES,\
})
// Assign properties to aem_entity_config struct /* Override a 64-byte, zero-padded AVB string field from a property. The default
// First handle strings * already in dst is kept when the key is missing or the value is not valid
//TODO: with strings, check utf8 format and set as a filled 64 byte char array * UTF-8 (IEEE 1722.1, Sec. 7.4.17.1). */
// check zero padding and utf8 format static inline void conf_load_string(char dst[64], const struct pw_properties *props,
pw_log_info("Assigning entity properties"); const char *key)
const char *name = pw_properties_get(entity_props, "entity_name"); {
const char *entity_name; size_t len;
const char *val = pw_properties_get(props, key);
if (val == NULL)
return;
int use_default = name ? 1 : 0; len = strnlen(val, 64);
size_t len = 0; if (validate_utf8((const unsigned char *)val, len) != 0) {
pw_log_warn("entity.%s is not valid UTF-8, keeping default", key);
return;
}
if (name) { memcpy(dst, val, len);
len = strnlen(name, 64); memset(dst + len, 0, 64 - len);
if (validate_utf8((uint8_t*)name, len)) }
entity_name = name;
else
use_default = 1;
}
if (use_default) {
len = strnlen(name, 64);
entity_name = (char *)DSC_ENTITY_MODEL_ENTITY_NAME;
}
memcpy(entity_conf.entity_name, entity_name, 64);
memset(entity_conf.entity_name + len, 0, 64 - len);
const char *serial = pw_properties_get(entity_props, "serial_number"); /* Override a uint16_t field when the key is present and parses as an integer. */
const char *serial_number = serial ? serial : DSC_ENTITY_MODEL_SERIAL_NUMBER; static inline void conf_load_u16(uint16_t *dst, const struct pw_properties *props,
strncpy(entity_conf.serial_number, serial_number, sizeof(entity_conf.serial_number)); const char *key)
{
uint32_t val;
if (pw_properties_fetch_uint32(props, key, &val) == 0) {
*dst = (uint16_t)val;
}
}
const char *firmware = pw_properties_get(entity_props, "firmware_version"); /* Override a uint32_t field when the key is present and parses as an integer. */
const char *firmware_version = firmware ? firmware : DSC_ENTITY_MODEL_FIRMWARE_VERSION; static inline void conf_load_u32(uint32_t *dst, const struct pw_properties *props,
strncpy(entity_conf.firmware_version, firmware_version, sizeof(entity_conf.firmware_version)); const char *key)
{
uint32_t val;
if (pw_properties_fetch_uint32(props, key, &val) == 0) {
*dst = val;
}
}
const char *group = pw_properties_get(entity_props, "group_name"); /* Fill entity_conf from the "entity" dict in avb.properties, starting from the
const char *group_name = group ? group: DSC_ENTITY_MODEL_GROUP_NAME; * compiled-in defaults. Any field absent or invalid keeps its default value. */
strncpy(entity_conf.group_name, group_name, sizeof(entity_conf.group_name)); static inline void conf_load_entity(struct pw_properties *props,
struct avb_entity_config *entity_conf)
{
struct pw_properties *entity_props;
/* Start from the defaults; everything below only overrides. */
*entity_conf = AVB_ENTITY_CONFIG_DEFAULT;
// Handle integers pw_log_info("Acquiring entity properties from entity dict in avb.properties");
uint32_t vendor_name; const char *str = pw_properties_get(props, "entity");
int vn_found = pw_properties_fetch_uint32(entity_props, "vendor_name", &vendor_name); if (str == NULL)
entity_conf.vendor_name = !vn_found ? (uint16_t)vendor_name : DSC_ENTITY_MODEL_VENDOR_NAME_STRING; return;
uint32_t model_name; /* The "entity" property is itself a serialized dict; parse it so the
int mn_found = pw_properties_fetch_uint32(entity_props, "model_name", &model_name); * individual entity fields can be read back by key. */
entity_conf.model_name = !mn_found ? (uint16_t)model_name : DSC_ENTITY_MODEL_MODEL_NAME_STRING; entity_props = pw_properties_new(NULL, NULL);
if (entity_props == NULL)
return;
pw_properties_update_string(entity_props, str, strlen(str));
uint32_t talker_capabilities; pw_log_info("Assigning entity properties");
int tc_found = pw_properties_fetch_uint32(entity_props, "talker_capabilities", &talker_capabilities); conf_load_string(entity_conf->entity_name, entity_props, "entity_name");
entity_conf.talker_capabilities = !tc_found ? (uint16_t)talker_capabilities : DSC_ENTITY_MODEL_TALKER_CAPABILITIES; conf_load_string(entity_conf->group_name, entity_props, "group_name");
conf_load_string(entity_conf->serial_number, entity_props, "serial_number");
conf_load_string(entity_conf->firmware_version, entity_props, "firmware_version");
uint32_t listener_capabilities; conf_load_u16(&entity_conf->vendor_name, entity_props, "vendor_name");
int lc_found = pw_properties_fetch_uint32(entity_props, "listener_capabilities", &listener_capabilities); conf_load_u16(&entity_conf->model_name, entity_props, "model_name");
entity_conf.listener_capabilities = !lc_found ? (uint16_t)listener_capabilities : DSC_ENTITY_MODEL_LISTENER_CAPABILITIES; conf_load_u16(&entity_conf->talker_capabilities, entity_props, "talker_capabilities");
conf_load_u16(&entity_conf->listener_capabilities, entity_props, "listener_capabilities");
conf_load_u32(&entity_conf->entity_capabilities, entity_props, "entity_capabilities");
conf_load_u32(&entity_conf->controller_capabilities, entity_props, "controller_capabilities");
uint32_t entity_capabilities; pw_properties_free(entity_props);
int ec_found = pw_properties_fetch_uint32(entity_props, "entity_capabilities", &entity_capabilities);
entity_conf.entity_capabilities = !ec_found ? entity_capabilities : DSC_ENTITY_MODEL_ENTITY_CAPABILITIES;
uint32_t controller_capabilities;
int cc_found = pw_properties_fetch_uint32(entity_props, "controller_capabilities", &controller_capabilities);
entity_conf.controller_capabilities = !cc_found ? controller_capabilities : DSC_ENTITY_MODEL_CONTROLLER_CAPABILITIES;
return entity_conf;
} }
#endif /* ENTITY_PARSER_H */ #endif /* ENTITY_PARSER_H */

View file

@ -265,7 +265,9 @@ static void on_flush_tick(void *data, uint64_t expirations)
} }
} }
/* Talker egress pacing runs on the RT data loop (impl->data_loop); a source cannot be added/removed off-thread, so the flush timer is created and destroyed ON the RT thread via pw_loop_invoke. */ /* Talker egress pacing runs on the RT data loop (impl->data_loop)
a source cannot be added/removed off-thread, so the flush timer is created
and destroyed ON the RT thread via pw_loop_invoke. */
static int do_add_flush_timer(struct spa_loop *loop, bool async, uint32_t seq, static int do_add_flush_timer(struct spa_loop *loop, bool async, uint32_t seq,
const void *data, size_t size, void *user_data) const void *data, size_t size, void *user_data)
{ {
@ -868,6 +870,39 @@ static const struct pw_stream_events sink_stream_events = {
.process = on_sink_stream_process .process = on_sink_stream_process
}; };
/* milan-avb: derive the runtime audio format (rate/channels/stride/frames_per_pdu/pdu_period) */
static void stream_apply_current_format(struct stream *stream)
{
uint16_t desc_type = (stream->direction == SPA_DIRECTION_INPUT)
? AVB_AEM_DESC_STREAM_INPUT : AVB_AEM_DESC_STREAM_OUTPUT;
struct descriptor *desc = server_find_descriptor(stream->server, desc_type,
stream->index);
struct avb_aem_desc_stream *body = desc ? descriptor_body(desc) : NULL;
struct avb_aem_stream_format_info fi = { 0 };
stream->format = body ? body->current_format : stream->format;
if (stream->format)
avb_aem_stream_format_decode(stream->format, &fi);
stream->info.info.raw.format = SPA_AUDIO_FORMAT_S32_BE;
stream->info.info.raw.flags = SPA_AUDIO_FLAG_UNPOSITIONED;
stream->info.info.raw.rate = fi.is_audio && fi.rate ? fi.rate : 48000;
stream->info.info.raw.channels = fi.is_audio && fi.channels ? fi.channels : 8;
stream->stride = stream->info.info.raw.channels * 4;
/* AAF Base Format NS samples/PDU: 6/12/24 at 48/96/192 kHz (Milan Base Audio Formats v1.2). */
stream->frames_per_pdu = stream->info.info.raw.rate > 96000 ? 24 :
stream->info.info.raw.rate > 48000 ? 12 : 6;
stream->pdu_period = SPA_NSEC_PER_SEC * stream->frames_per_pdu /
stream->info.info.raw.rate;
if (stream->server->avb_mode == AVB_MODE_MILAN_V12) {
setup_pdu_milan_v12(stream);
} else {
setup_pdu_legacy(stream);
}
}
struct stream *server_create_stream(struct server *server, struct stream *stream, struct stream *server_create_stream(struct server *server, struct stream *stream,
enum spa_direction direction, uint16_t index) enum spa_direction direction, uint16_t index)
{ {
@ -1474,6 +1509,18 @@ int stream_activate(struct stream *stream, uint16_t index, uint64_t now)
struct stream_common *common; struct stream_common *common;
common = SPA_CONTAINER_OF(stream, struct stream_common, stream); common = SPA_CONTAINER_OF(stream, struct stream_common, stream);
/* milan-avb: re-verify the stream format at start. Milan sets the format via SET_STREAM_FORMAT */
stream_apply_current_format(stream);
if (!stream->is_crf && stream->stream != NULL) {
uint8_t fbuf[1024];
struct spa_pod_builder fb;
const struct spa_pod *fparams[1];
spa_pod_builder_init(&fb, fbuf, sizeof(fbuf));
fparams[0] = spa_format_audio_raw_build(&fb, SPA_PARAM_EnumFormat,
&stream->info.info.raw);
pw_stream_update_params(stream->stream, fparams, 1);
}
/* milan-avb: SR-class priority + VLAN id come from the MSRP Domain (the authoritative network-declared values), not a hardcoded default; read before setup_socket() since the listener uses stream->vlan_id to select its VLAN sub-iface. */ /* milan-avb: SR-class priority + VLAN id come from the MSRP Domain (the authoritative network-declared values), not a hardcoded default; read before setup_socket() since the listener uses stream->vlan_id to select its VLAN sub-iface. */
{ {
struct descriptor *avbif = server_find_descriptor(server, struct descriptor *avbif = server_find_descriptor(server,