diff --git a/pjmedia/include/pjmedia/conference.h b/pjmedia/include/pjmedia/conference.h index f12c52efa9..d0b71c80fb 100644 --- a/pjmedia/include/pjmedia/conference.h +++ b/pjmedia/include/pjmedia/conference.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -233,6 +233,12 @@ typedef struct pjmedia_conf_op_info */ pjmedia_conf_op_param op_param; + /** + * The application data specified in pjmedia_conf_param::user_data + * when the conference bridge was created. + */ + void *user_data; + } pjmedia_conf_op_info; /** @@ -373,6 +379,16 @@ typedef struct pjmedia_conf_param * level using the pjsua2::MediaConfig::confThreadPrio parameter. */ int worker_thread_prio; + + /** + * Application data, which will be passed back in + * pjmedia_conf_op_info::user_data of the callback set with + * #pjmedia_conf_set_op_cb(). + * + * The default value is NULL. + */ + void *user_data; + } pjmedia_conf_param; @@ -512,6 +528,9 @@ PJ_DECL(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ); * * The callback will most likely be called from media threads, * thus application must not perform long/blocking processing in this callback. + * + * The application data specified in pjmedia_conf_param::user_data is + * passed to the callback in pjmedia_conf_op_info::user_data. * * @param conf The conference bridge. * @param cb Callback to be called. Set this to NULL to unregister diff --git a/pjmedia/src/pjmedia/conf_thread.c b/pjmedia/src/pjmedia/conf_thread.c index d540a282e7..05e9a954c9 100644 --- a/pjmedia/src/pjmedia/conf_thread.c +++ b/pjmedia/src/pjmedia/conf_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -379,6 +379,7 @@ struct pjmedia_conf op_entry *op_queue; /**< Queue of operations. */ op_entry *op_queue_free;/**< Queue of free entries. */ pjmedia_conf_op_cb cb; /**< OP callback. */ + void *user_data; /**< OP callback user data. */ pj_atomic_slist *unused_slots; /**< Unused port's slots. */ @@ -579,6 +580,8 @@ static void handle_op_queue(pjmedia_conf *conf) pjmedia_conf_op_info info = { 0 }; pj_log_push_indent(); + info.conf = conf; + info.user_data = conf->user_data; info.op_type = type; info.status = status; info.op_param = param; @@ -1096,6 +1099,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_create2(pj_pool_t *pool_, conf->options = param->options; conf->max_ports = param->max_slots; + conf->user_data = param->user_data; conf->sampling_rate = param->sampling_rate; conf->channel_count = param->channel_count; conf->samples_per_frame = param->samples_per_frame; @@ -1339,6 +1343,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) pj_log_push_indent(); op_info.conf = conf; + op_info.user_data = conf->user_data; op_info.op_type = PJMEDIA_CONF_OP_REMOVE_PORT; op_info.status = status; op_info.op_param = oprm; @@ -2418,6 +2423,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf, if (conf->cb) { pj_log_push_indent(); op_info.conf = conf; + op_info.user_data = conf->user_data; op_info.status = PJ_ECANCELLED; (*conf->cb)(&op_info); pj_log_pop_indent(); @@ -2503,6 +2509,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf, pj_log_push_indent(); op_info.conf = conf; + op_info.user_data = conf->user_data; op_info.op_type = PJMEDIA_CONF_OP_REMOVE_PORT; op_info.status = status; op_info.op_param = prm; diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index 6cf7fd690c..12a47a5696 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -267,6 +267,7 @@ struct pjmedia_conf op_entry *op_queue; /**< Queue of operations. */ op_entry *op_queue_free;/**< Queue of free entries. */ pjmedia_conf_op_cb cb; /**< OP callback. */ + void *user_data; /**< OP callback user data. */ }; @@ -396,6 +397,7 @@ static void handle_op_queue(pjmedia_conf *conf) pj_log_push_indent(); info.conf = conf; + info.user_data = conf->user_data; info.op_type = type; info.status = status; info.op_param = param; @@ -793,11 +795,18 @@ PJ_DEF(pj_status_t) pjmedia_conf_create2(pj_pool_t *pool, const pjmedia_conf_param *param, pjmedia_conf **p_conf) { - PJ_ASSERT_RETURN(param, PJ_EINVAL); - return pjmedia_conf_create(pool, - param->max_slots, param->sampling_rate, - param->channel_count, param->samples_per_frame, - param->bits_per_sample, param->options, p_conf); + pj_status_t status; + + PJ_ASSERT_RETURN(param && p_conf, PJ_EINVAL); + status = pjmedia_conf_create(pool, param->max_slots, + param->sampling_rate, param->channel_count, + param->samples_per_frame, + param->bits_per_sample, param->options, + p_conf); + if (status == PJ_SUCCESS) + (*p_conf)->user_data = param->user_data; + + return status; } /* @@ -965,6 +974,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) pj_log_push_indent(); op_info.conf = conf; + op_info.user_data = conf->user_data; op_info.op_type = PJMEDIA_CONF_OP_REMOVE_PORT; op_info.status = status; op_info.op_param = oprm; @@ -1919,6 +1929,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf, if (conf->cb) { pj_log_push_indent(); op_info.conf = conf; + op_info.user_data = conf->user_data; op_info.status = PJ_ECANCELLED; (*conf->cb)(&op_info); pj_log_pop_indent(); @@ -2006,6 +2017,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf, pj_log_push_indent(); op_info.conf = conf; + op_info.user_data = conf->user_data; op_info.op_type = PJMEDIA_CONF_OP_REMOVE_PORT; op_info.status = status; op_info.op_param = prm; diff --git a/pjmedia/src/test/conf_test.c b/pjmedia/src/test/conf_test.c index 6a5b929935..f87e8596be 100644 --- a/pjmedia/src/test/conf_test.c +++ b/pjmedia/src/test/conf_test.c @@ -651,6 +651,141 @@ static int worker_prio_test(void) } +#if PJMEDIA_CONF_BACKEND != PJMEDIA_CONF_SWITCH_BOARD_BACKEND +/* The operation callbacks delivered by the bridge, and how many of them + * carried the bridge and the user data it was created with. + */ +typedef struct op_record +{ + pjmedia_conf *conf; + unsigned count; + unsigned ok_cnt; + pjmedia_conf_op_type types[8]; + pj_status_t statuses[8]; +} op_record; + +static op_record g_ops; +static int g_op_user_data; + +static void record_op(const pjmedia_conf_op_info *info) +{ + if (g_ops.count < PJ_ARRAY_SIZE(g_ops.types)) { + g_ops.types[g_ops.count] = info->op_type; + g_ops.statuses[g_ops.count] = info->status; + } + if (info->conf == g_ops.conf && info->user_data == &g_op_user_data) + ++g_ops.ok_cnt; + ++g_ops.count; +} + +static void reset_ops(pjmedia_conf *conf) +{ + pj_bzero(&g_ops, sizeof(g_ops)); + g_ops.conf = conf; +} + +/* Every operation callback carries pjmedia_conf_param::user_data: the + * queued operations, the cancelled operations and the synchronous removal + * of a port whose add-op is still queued, and the removals on destroy. + */ +static int op_user_data_test(void) +{ + pj_pool_t *pool = NULL; + pjmedia_conf *conf = NULL; + pjmedia_conf_param param; + pjmedia_port *master, *p1 = NULL, *p2 = NULL; + unsigned slot1 = 0, slot2 = 0; + int rc = 0; + pj_status_t status; + + PJ_LOG(3, (THIS_FILE, " conf op callback user data")); + + pool = pj_pool_create(mem, "conf_op_ud", 4000, 4000, NULL); + if (!pool) return -500; + + pjmedia_conf_param_default(¶m); + param.max_slots = 8; + param.sampling_rate = CLOCK_RATE; + param.channel_count = CHANNELS; + param.samples_per_frame = SPF; + param.bits_per_sample = BPS; + param.options = PJMEDIA_CONF_NO_DEVICE; + param.user_data = &g_op_user_data; + status = pjmedia_conf_create2(pool, ¶m, &conf); + if (status != PJ_SUCCESS) { rc = -501; goto on_return; } + status = pjmedia_conf_set_op_cb(conf, &record_op); + if (status != PJ_SUCCESS) { rc = -502; goto on_return; } + master = pjmedia_conf_get_master_port(conf); + + status = pjmedia_null_port_create(pool, CLOCK_RATE, CHANNELS, SPF, BPS, + &p1); + if (status != PJ_SUCCESS) { rc = -503; goto on_return; } + status = pjmedia_null_port_create(pool, CLOCK_RATE, CHANNELS, SPF, BPS, + &p2); + if (status != PJ_SUCCESS) { rc = -504; goto on_return; } + + status = pjmedia_conf_add_port(conf, pool, p1, NULL, &slot1); + if (status != PJ_SUCCESS) { rc = -505; goto on_return; } + status = pjmedia_conf_add_port(conf, pool, p2, NULL, &slot2); + if (status != PJ_SUCCESS) { rc = -506; goto on_return; } + status = pjmedia_conf_connect_port(conf, slot1, slot2, 0); + if (status != PJ_SUCCESS) { rc = -507; goto on_return; } + + /* No clock tick yet, so removing slot1 cancels its queued add-op and + * connect-op, and removes it synchronously. + */ + reset_ops(conf); + status = pjmedia_conf_remove_port(conf, slot1); + if (status != PJ_SUCCESS) { rc = -510; goto on_return; } + if (g_ops.count != 3 || + g_ops.types[0] != PJMEDIA_CONF_OP_ADD_PORT || + g_ops.statuses[0] != PJ_ECANCELLED || + g_ops.types[1] != PJMEDIA_CONF_OP_CONNECT_PORTS || + g_ops.statuses[1] != PJ_ECANCELLED || + g_ops.types[2] != PJMEDIA_CONF_OP_REMOVE_PORT || + g_ops.statuses[2] != PJ_SUCCESS) + { + PJ_LOG(1, (THIS_FILE, " sync removal: unexpected %u callbacks", + g_ops.count)); + rc = -511; goto on_return; + } + if (g_ops.ok_cnt != g_ops.count) { rc = -512; goto on_return; } + + /* The add-op of slot2 is completed by the clock */ + reset_ops(conf); + status = pump(master, 1); + if (status != PJ_SUCCESS) { rc = -520; goto on_return; } + if (g_ops.count != 1 || + g_ops.types[0] != PJMEDIA_CONF_OP_ADD_PORT || + g_ops.statuses[0] != PJ_SUCCESS) + { + PJ_LOG(1, (THIS_FILE, " queued op: unexpected %u callbacks", + g_ops.count)); + rc = -521; goto on_return; + } + if (g_ops.ok_cnt != g_ops.count) { rc = -522; goto on_return; } + + /* Destroying the bridge removes the remaining ports */ + reset_ops(conf); + pjmedia_conf_destroy(conf); + conf = NULL; + if (g_ops.count == 0) { rc = -525; goto on_return; } + if (g_ops.ok_cnt != g_ops.count) { rc = -526; goto on_return; } + +on_return: + if (rc != 0 && g_ops.ok_cnt != g_ops.count) { + PJ_LOG(1, (THIS_FILE, " only %u of %u callbacks carried the bridge " + "and its user data", g_ops.ok_cnt, g_ops.count)); + } + if (conf) + pjmedia_conf_destroy(conf); + if (pool) + pj_pool_release(pool); + return rc; +} +#endif + + int conf_test(void) { int rc; @@ -663,6 +798,14 @@ int conf_test(void) return rc; } +#if PJMEDIA_CONF_BACKEND != PJMEDIA_CONF_SWITCH_BOARD_BACKEND + rc = op_user_data_test(); + if (rc != 0) { + PJ_LOG(1,(THIS_FILE, " conf op user data test failed (rc=%d)", rc)); + return rc; + } +#endif + rc = detach_replace_test(); if (rc == 1) { /* Skipped (backend unsupported) - treat as pass. */