mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-06-12 03:01:58 -04:00
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:
parent
25203c5293
commit
7262d0f34c
3 changed files with 129 additions and 64 deletions
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue