mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-29 19:50:40 +00:00
output-selector: Add pad-negotiation-mode property
Adds getcaps/setcaps to output-selector and adds a property to select which type of negotiation should be done. The available modes are: * none: no negotiation (current behavior), getcaps return ANY and setcaps aren't set on any of the peers * all: use all pads (default), getcaps returns the intersection of peer pads and setcaps is set on all peers * active: getcaps and setcaps are proxied to the active pad https://bugzilla.gnome.org/show_bug.cgi?id=638381
This commit is contained in:
parent
c8ffd4e395
commit
757dc90faa
3 changed files with 276 additions and 10 deletions
|
@ -49,13 +49,43 @@ GST_STATIC_PAD_TEMPLATE ("src%d",
|
||||||
GST_PAD_REQUEST,
|
GST_PAD_REQUEST,
|
||||||
GST_STATIC_CAPS_ANY);
|
GST_STATIC_CAPS_ANY);
|
||||||
|
|
||||||
|
enum GstOutputSelectorPadNegotiationMode
|
||||||
|
{
|
||||||
|
GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_NONE,
|
||||||
|
GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL,
|
||||||
|
GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ACTIVE
|
||||||
|
};
|
||||||
|
#define GST_TYPE_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE (gst_output_selector_pad_negotiation_mode_get_type())
|
||||||
|
static GType
|
||||||
|
gst_output_selector_pad_negotiation_mode_get_type (void)
|
||||||
|
{
|
||||||
|
static GType pad_negotiation_mode_type = 0;
|
||||||
|
static GEnumValue pad_negotiation_modes[] = {
|
||||||
|
{GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_NONE, "None", "none"},
|
||||||
|
{GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL, "All", "all"},
|
||||||
|
{GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ACTIVE, "Active", "active"},
|
||||||
|
{0, NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!pad_negotiation_mode_type) {
|
||||||
|
pad_negotiation_mode_type =
|
||||||
|
g_enum_register_static ("GstOutputSelectorPadNegotiationMode",
|
||||||
|
pad_negotiation_modes);
|
||||||
|
}
|
||||||
|
return pad_negotiation_mode_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
PROP_0,
|
PROP_0,
|
||||||
PROP_ACTIVE_PAD,
|
PROP_ACTIVE_PAD,
|
||||||
PROP_RESEND_LATEST
|
PROP_RESEND_LATEST,
|
||||||
|
PROP_PAD_NEGOTIATION_MODE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define DEFAULT_PAD_NEGOTIATION_MODE GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL
|
||||||
|
|
||||||
#define _do_init(bla) \
|
#define _do_init(bla) \
|
||||||
GST_DEBUG_CATEGORY_INIT (output_selector_debug, \
|
GST_DEBUG_CATEGORY_INIT (output_selector_debug, \
|
||||||
"output-selector", 0, "Output stream selector");
|
"output-selector", 0, "Output stream selector");
|
||||||
|
@ -79,6 +109,8 @@ static GstStateChangeReturn gst_output_selector_change_state (GstElement *
|
||||||
element, GstStateChange transition);
|
element, GstStateChange transition);
|
||||||
static gboolean gst_output_selector_handle_sink_event (GstPad * pad,
|
static gboolean gst_output_selector_handle_sink_event (GstPad * pad,
|
||||||
GstEvent * event);
|
GstEvent * event);
|
||||||
|
static void gst_output_selector_switch_pad_negotiation_mode (GstOutputSelector *
|
||||||
|
sel, gint mode);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gst_output_selector_base_init (gpointer g_class)
|
gst_output_selector_base_init (gpointer g_class)
|
||||||
|
@ -113,6 +145,12 @@ gst_output_selector_class_init (GstOutputSelectorClass * klass)
|
||||||
g_param_spec_boolean ("resend-latest", "Resend latest buffer",
|
g_param_spec_boolean ("resend-latest", "Resend latest buffer",
|
||||||
"Resend latest buffer after a switch to a new pad", FALSE,
|
"Resend latest buffer after a switch to a new pad", FALSE,
|
||||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
g_object_class_install_property (gobject_class, PROP_PAD_NEGOTIATION_MODE,
|
||||||
|
g_param_spec_enum ("pad-negotiation-mode", "Pad negotiation mode",
|
||||||
|
"The mode to be used for pad negotiation",
|
||||||
|
GST_TYPE_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE,
|
||||||
|
DEFAULT_PAD_NEGOTIATION_MODE,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
gstelement_class->request_new_pad =
|
gstelement_class->request_new_pad =
|
||||||
GST_DEBUG_FUNCPTR (gst_output_selector_request_new_pad);
|
GST_DEBUG_FUNCPTR (gst_output_selector_request_new_pad);
|
||||||
|
@ -135,12 +173,6 @@ gst_output_selector_init (GstOutputSelector * sel,
|
||||||
GST_DEBUG_FUNCPTR (gst_output_selector_handle_sink_event));
|
GST_DEBUG_FUNCPTR (gst_output_selector_handle_sink_event));
|
||||||
gst_pad_set_bufferalloc_function (sel->sinkpad,
|
gst_pad_set_bufferalloc_function (sel->sinkpad,
|
||||||
GST_DEBUG_FUNCPTR (gst_output_selector_buffer_alloc));
|
GST_DEBUG_FUNCPTR (gst_output_selector_buffer_alloc));
|
||||||
/*
|
|
||||||
gst_pad_set_setcaps_function (sel->sinkpad,
|
|
||||||
GST_DEBUG_FUNCPTR (gst_pad_proxy_setcaps));
|
|
||||||
gst_pad_set_getcaps_function (sel->sinkpad,
|
|
||||||
GST_DEBUG_FUNCPTR (gst_pad_proxy_getcaps));
|
|
||||||
*/
|
|
||||||
|
|
||||||
gst_element_add_pad (GST_ELEMENT (sel), sel->sinkpad);
|
gst_element_add_pad (GST_ELEMENT (sel), sel->sinkpad);
|
||||||
|
|
||||||
|
@ -152,6 +184,8 @@ gst_output_selector_init (GstOutputSelector * sel,
|
||||||
|
|
||||||
sel->resend_latest = FALSE;
|
sel->resend_latest = FALSE;
|
||||||
sel->latest_buffer = NULL;
|
sel->latest_buffer = NULL;
|
||||||
|
gst_output_selector_switch_pad_negotiation_mode (sel,
|
||||||
|
DEFAULT_PAD_NEGOTIATION_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -218,6 +252,11 @@ gst_output_selector_set_property (GObject * object, guint prop_id,
|
||||||
sel->resend_latest = g_value_get_boolean (value);
|
sel->resend_latest = g_value_get_boolean (value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case PROP_PAD_NEGOTIATION_MODE:{
|
||||||
|
gst_output_selector_switch_pad_negotiation_mode (sel,
|
||||||
|
g_value_get_enum (value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -243,12 +282,75 @@ gst_output_selector_get_property (GObject * object, guint prop_id,
|
||||||
GST_OBJECT_UNLOCK (object);
|
GST_OBJECT_UNLOCK (object);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case PROP_PAD_NEGOTIATION_MODE:
|
||||||
|
g_value_set_enum (value, sel->pad_negotiation_mode);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static GstCaps *
|
||||||
|
gst_output_selector_sink_getcaps (GstPad * pad)
|
||||||
|
{
|
||||||
|
GstOutputSelector *sel = GST_OUTPUT_SELECTOR (GST_PAD_PARENT (pad));
|
||||||
|
GstPad *active;
|
||||||
|
GstCaps *caps;
|
||||||
|
|
||||||
|
GST_OBJECT_LOCK (sel);
|
||||||
|
if (sel->pending_srcpad)
|
||||||
|
active = gst_object_ref (sel->pending_srcpad);
|
||||||
|
else
|
||||||
|
active = gst_object_ref (sel->active_srcpad);
|
||||||
|
GST_OBJECT_UNLOCK (sel);
|
||||||
|
|
||||||
|
caps = gst_pad_peer_get_caps_reffed (active);
|
||||||
|
gst_object_unref (active);
|
||||||
|
if (caps == NULL) {
|
||||||
|
caps = gst_caps_new_any ();
|
||||||
|
}
|
||||||
|
return caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gst_output_selector_sink_setcaps (GstPad * pad, GstCaps * caps)
|
||||||
|
{
|
||||||
|
GstOutputSelector *sel = GST_OUTPUT_SELECTOR (GST_PAD_PARENT (pad));
|
||||||
|
GstPad *active;
|
||||||
|
gboolean ret;
|
||||||
|
|
||||||
|
GST_OBJECT_LOCK (sel);
|
||||||
|
if (sel->pending_srcpad)
|
||||||
|
active = gst_object_ref (sel->pending_srcpad);
|
||||||
|
else
|
||||||
|
active = gst_object_ref (sel->active_srcpad);
|
||||||
|
GST_OBJECT_UNLOCK (sel);
|
||||||
|
|
||||||
|
ret = gst_pad_set_caps (active, caps);
|
||||||
|
gst_object_unref (active);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_output_selector_switch_pad_negotiation_mode (GstOutputSelector * sel,
|
||||||
|
gint mode)
|
||||||
|
{
|
||||||
|
sel->pad_negotiation_mode = mode;
|
||||||
|
if (mode == GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL) {
|
||||||
|
gst_pad_set_getcaps_function (sel->sinkpad, gst_pad_proxy_getcaps);
|
||||||
|
gst_pad_set_setcaps_function (sel->sinkpad, gst_pad_proxy_setcaps);
|
||||||
|
} else if (mode == GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_NONE) {
|
||||||
|
gst_pad_set_getcaps_function (sel->sinkpad, NULL);
|
||||||
|
gst_pad_set_setcaps_function (sel->sinkpad, NULL);
|
||||||
|
} else { /* active */
|
||||||
|
gst_pad_set_getcaps_function (sel->sinkpad,
|
||||||
|
gst_output_selector_sink_getcaps);
|
||||||
|
gst_pad_set_setcaps_function (sel->sinkpad,
|
||||||
|
gst_output_selector_sink_setcaps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static GstFlowReturn
|
static GstFlowReturn
|
||||||
gst_output_selector_buffer_alloc (GstPad * pad, guint64 offset, guint size,
|
gst_output_selector_buffer_alloc (GstPad * pad, guint64 offset, guint size,
|
||||||
GstCaps * caps, GstBuffer ** buf)
|
GstCaps * caps, GstBuffer ** buf)
|
||||||
|
|
|
@ -47,6 +47,8 @@ struct _GstOutputSelector {
|
||||||
GstPad *pending_srcpad;
|
GstPad *pending_srcpad;
|
||||||
guint nb_srcpads;
|
guint nb_srcpads;
|
||||||
|
|
||||||
|
gint pad_negotiation_mode;
|
||||||
|
|
||||||
GstSegment segment;
|
GstSegment segment;
|
||||||
|
|
||||||
/* resend latest buffer after switch */
|
/* resend latest buffer after switch */
|
||||||
|
|
|
@ -61,13 +61,16 @@ probe_cb (GstPad * pad, GstMiniObject * obj, gpointer user_data)
|
||||||
|
|
||||||
/* Create and link output pad: selector:src%d ! output_pad */
|
/* Create and link output pad: selector:src%d ! output_pad */
|
||||||
static GstPad *
|
static GstPad *
|
||||||
setup_output_pad (GstElement * element)
|
setup_output_pad (GstElement * element, GstStaticPadTemplate * tmpl)
|
||||||
{
|
{
|
||||||
GstPad *srcpad = NULL, *output_pad = NULL;
|
GstPad *srcpad = NULL, *output_pad = NULL;
|
||||||
gulong probe_id = 0;
|
gulong probe_id = 0;
|
||||||
|
|
||||||
|
if (tmpl == NULL)
|
||||||
|
tmpl = &sinktemplate;
|
||||||
|
|
||||||
/* create output_pad */
|
/* create output_pad */
|
||||||
output_pad = gst_pad_new_from_static_template (&sinktemplate, "sink");
|
output_pad = gst_pad_new_from_static_template (tmpl, "sink");
|
||||||
fail_if (output_pad == NULL, "Could not create a output_pad");
|
fail_if (output_pad == NULL, "Could not create a output_pad");
|
||||||
|
|
||||||
/* add probe */
|
/* add probe */
|
||||||
|
@ -244,7 +247,7 @@ run_output_selector_buffer_count (gint num_output_pads,
|
||||||
input_pads = g_list_append (input_pads, input_pad);
|
input_pads = g_list_append (input_pads, input_pad);
|
||||||
gst_pad_set_active (input_pad, TRUE);
|
gst_pad_set_active (input_pad, TRUE);
|
||||||
for (i = 0; i < num_output_pads; i++) {
|
for (i = 0; i < num_output_pads; i++) {
|
||||||
output_pads = g_list_append (output_pads, setup_output_pad (sel));
|
output_pads = g_list_append (output_pads, setup_output_pad (sel, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* run the test */
|
/* run the test */
|
||||||
|
@ -369,6 +372,157 @@ GST_START_TEST (test_input_selector_buffer_count);
|
||||||
|
|
||||||
GST_END_TEST;
|
GST_END_TEST;
|
||||||
|
|
||||||
|
GstElement *sel;
|
||||||
|
GstPad *input_pad;
|
||||||
|
GList *output_pads = NULL; /* list of sinkpads linked to output-selector */
|
||||||
|
#define OUTPUT_SELECTOR_NUM_PADS 2
|
||||||
|
|
||||||
|
static GstStaticPadTemplate sinktmpl_nego_a = GST_STATIC_PAD_TEMPLATE ("sink",
|
||||||
|
GST_PAD_SINK,
|
||||||
|
GST_PAD_ALWAYS,
|
||||||
|
GST_STATIC_CAPS ("format/abc; format/xyz"));
|
||||||
|
static GstStaticPadTemplate sinktmpl_nego_b = GST_STATIC_PAD_TEMPLATE ("sink",
|
||||||
|
GST_PAD_SINK,
|
||||||
|
GST_PAD_ALWAYS,
|
||||||
|
GST_STATIC_CAPS ("format/abc"));
|
||||||
|
|
||||||
|
static void
|
||||||
|
setup_output_selector (void)
|
||||||
|
{
|
||||||
|
sel = gst_check_setup_element ("output-selector");
|
||||||
|
input_pad = gst_check_setup_src_pad (sel, &srctemplate, NULL);
|
||||||
|
gst_pad_set_active (input_pad, TRUE);
|
||||||
|
|
||||||
|
output_pads = g_list_append (output_pads, setup_output_pad (sel,
|
||||||
|
&sinktmpl_nego_a));
|
||||||
|
output_pads = g_list_append (output_pads, setup_output_pad (sel,
|
||||||
|
&sinktmpl_nego_b));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
teardown_output_selector (void)
|
||||||
|
{
|
||||||
|
gst_pad_set_active (input_pad, FALSE);
|
||||||
|
gst_object_unref (input_pad);
|
||||||
|
gst_check_teardown_src_pad (sel);
|
||||||
|
g_list_foreach (output_pads, (GFunc) cleanup_pad, sel);
|
||||||
|
g_list_free (output_pads);
|
||||||
|
gst_check_teardown_element (sel);
|
||||||
|
output_pads = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_START_TEST (test_output_selector_getcaps_none);
|
||||||
|
{
|
||||||
|
GList *walker;
|
||||||
|
|
||||||
|
/* set pad negotiation mode to none */
|
||||||
|
g_object_set (sel, "pad-negotiation-mode", 0, NULL);
|
||||||
|
|
||||||
|
fail_unless (gst_element_set_state (sel,
|
||||||
|
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
|
||||||
|
"could not set to playing");
|
||||||
|
|
||||||
|
for (walker = output_pads; walker; walker = g_list_next (walker)) {
|
||||||
|
GstCaps *caps;
|
||||||
|
GstPad *pad;
|
||||||
|
|
||||||
|
pad = gst_pad_get_peer ((GstPad *) walker->data);
|
||||||
|
|
||||||
|
g_object_set (sel, "active-pad", pad, NULL);
|
||||||
|
|
||||||
|
caps = gst_pad_peer_get_caps (input_pad);
|
||||||
|
|
||||||
|
/* in 'none' mode, the getcaps returns the template, which is ANY */
|
||||||
|
g_assert (gst_caps_is_any (caps));
|
||||||
|
gst_caps_unref (caps);
|
||||||
|
}
|
||||||
|
|
||||||
|
fail_unless (gst_element_set_state (sel,
|
||||||
|
GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_END_TEST;
|
||||||
|
|
||||||
|
|
||||||
|
GST_START_TEST (test_output_selector_getcaps_all);
|
||||||
|
{
|
||||||
|
GList *walker;
|
||||||
|
GstCaps *expected;
|
||||||
|
|
||||||
|
/* set pad negotiation mode to 'all' */
|
||||||
|
g_object_set (sel, "pad-negotiation-mode", 1, NULL);
|
||||||
|
|
||||||
|
fail_unless (gst_element_set_state (sel,
|
||||||
|
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
|
||||||
|
"could not set to playing");
|
||||||
|
|
||||||
|
/* in 'all' mode, the intersection of the srcpad caps should be returned on
|
||||||
|
* the sinkpad's getcaps */
|
||||||
|
expected = gst_caps_new_simple ("format/abc", NULL);
|
||||||
|
|
||||||
|
for (walker = output_pads; walker; walker = g_list_next (walker)) {
|
||||||
|
GstCaps *caps;
|
||||||
|
GstPad *pad;
|
||||||
|
|
||||||
|
pad = gst_pad_get_peer ((GstPad *) walker->data);
|
||||||
|
|
||||||
|
g_object_set (sel, "active-pad", pad, NULL);
|
||||||
|
|
||||||
|
caps = gst_pad_peer_get_caps (input_pad);
|
||||||
|
|
||||||
|
g_assert (gst_caps_is_equal (caps, expected));
|
||||||
|
gst_caps_unref (caps);
|
||||||
|
}
|
||||||
|
gst_caps_unref (expected);
|
||||||
|
|
||||||
|
fail_unless (gst_element_set_state (sel,
|
||||||
|
GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_END_TEST;
|
||||||
|
|
||||||
|
|
||||||
|
GST_START_TEST (test_output_selector_getcaps_active);
|
||||||
|
{
|
||||||
|
GList *walker;
|
||||||
|
GstCaps *expected;
|
||||||
|
|
||||||
|
/* set pad negotiation mode to 'active' */
|
||||||
|
g_object_set (sel, "pad-negotiation-mode", 2, NULL);
|
||||||
|
|
||||||
|
fail_unless (gst_element_set_state (sel,
|
||||||
|
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
|
||||||
|
"could not set to playing");
|
||||||
|
|
||||||
|
for (walker = output_pads; walker; walker = g_list_next (walker)) {
|
||||||
|
GstCaps *caps;
|
||||||
|
GstPad *pad;
|
||||||
|
|
||||||
|
pad = gst_pad_get_peer ((GstPad *) walker->data);
|
||||||
|
|
||||||
|
g_object_set (sel, "active-pad", pad, NULL);
|
||||||
|
|
||||||
|
/* in 'active' mode, the active srcpad peer's caps should be returned on
|
||||||
|
* the sinkpad's getcaps */
|
||||||
|
|
||||||
|
expected = gst_pad_template_get_caps (gst_pad_get_pad_template ((GstPad *)
|
||||||
|
walker->data));
|
||||||
|
caps = gst_pad_peer_get_caps (input_pad);
|
||||||
|
|
||||||
|
g_assert (gst_caps_is_equal (caps, expected));
|
||||||
|
gst_caps_unref (caps);
|
||||||
|
}
|
||||||
|
|
||||||
|
fail_unless (gst_element_set_state (sel,
|
||||||
|
GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_END_TEST;
|
||||||
|
|
||||||
|
|
||||||
static Suite *
|
static Suite *
|
||||||
selector_suite (void)
|
selector_suite (void)
|
||||||
{
|
{
|
||||||
|
@ -379,6 +533,14 @@ selector_suite (void)
|
||||||
tcase_add_test (tc_chain, test_output_selector_buffer_count);
|
tcase_add_test (tc_chain, test_output_selector_buffer_count);
|
||||||
tcase_add_test (tc_chain, test_input_selector_buffer_count);
|
tcase_add_test (tc_chain, test_input_selector_buffer_count);
|
||||||
|
|
||||||
|
tc_chain = tcase_create ("output-selector-negotiation");
|
||||||
|
tcase_add_checked_fixture (tc_chain, setup_output_selector,
|
||||||
|
teardown_output_selector);
|
||||||
|
suite_add_tcase (s, tc_chain);
|
||||||
|
tcase_add_test (tc_chain, test_output_selector_getcaps_none);
|
||||||
|
tcase_add_test (tc_chain, test_output_selector_getcaps_all);
|
||||||
|
tcase_add_test (tc_chain, test_output_selector_getcaps_active);
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue