From 853a46120e334f6340df95db1feb9e8b58c03c68 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 9 Jul 2024 14:14:42 +0200 Subject: [PATCH] v4l2: Improve format and control enumeration Use dynamic pod builder so that we can also build complex formats. Make sure we zero the format before we parse it or else we end up with potentially uninitialized values. When ENUM_FRAMESIZES or VIDIOC_ENUM_FRAMEINTERVALS return EINVAL for the first index, make a dummy result and continue with that. This will trigger an intersect withe filter so that we end up with something valid instead of nothing. Handle 0 framerates without crashing. See #4063 --- spa/plugins/v4l2/v4l2-source.c | 60 ++++++++++++--------- spa/plugins/v4l2/v4l2-utils.c | 99 ++++++++++++++++++++++------------ 2 files changed, 101 insertions(+), 58 deletions(-) diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c index ac956c695..20ae2fc95 100644 --- a/spa/plugins/v4l2/v4l2-source.c +++ b/spa/plugins/v4l2/v4l2-source.c @@ -243,7 +243,8 @@ static int impl_node_enum_params(void *object, int seq, { struct impl *this = object; struct spa_pod *param; - struct spa_pod_builder b = { 0 }; + spa_auto(spa_pod_dynamic_builder) b = { 0 }; + struct spa_pod_builder_state state; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; @@ -252,12 +253,15 @@ static int impl_node_enum_params(void *object, int seq, spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + spa_pod_builder_get_state(&b.b, &state); + result.id = id; result.next = start; next: result.index = result.next++; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_reset(&b.b, &state); switch (id) { case SPA_PARAM_PropInfo: @@ -266,21 +270,21 @@ static int impl_node_enum_params(void *object, int seq, switch (result.index) { case 0: - param = spa_pod_builder_add_object(&b, + param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_device), SPA_PROP_INFO_description, SPA_POD_String("The V4L2 device"), SPA_PROP_INFO_type, SPA_POD_String(p->device)); break; case 1: - param = spa_pod_builder_add_object(&b, + param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceName), SPA_PROP_INFO_description, SPA_POD_String("The V4L2 device name"), SPA_PROP_INFO_type, SPA_POD_String(p->device_name)); break; case 2: - param = spa_pod_builder_add_object(&b, + param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceFd), SPA_PROP_INFO_description, SPA_POD_String("The V4L2 fd"), @@ -305,8 +309,8 @@ static int impl_node_enum_params(void *object, int seq, switch (result.index) { case 0: - spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_Props, id); - spa_pod_builder_add(&b, + spa_pod_builder_push_object(&b.b, &f, SPA_TYPE_OBJECT_Props, id); + spa_pod_builder_add(&b.b, SPA_PROP_device, SPA_POD_String(p->device), SPA_PROP_deviceName, SPA_POD_String(p->device_name), SPA_PROP_deviceFd, SPA_POD_Int(p->device_fd), @@ -314,20 +318,20 @@ static int impl_node_enum_params(void *object, int seq, for (i = 0; i < port->n_controls; i++) { struct control *c = &port->controls[i]; - spa_pod_builder_prop(&b, c->id, 0); + spa_pod_builder_prop(&b.b, c->id, 0); switch (c->type) { case SPA_TYPE_Int: - spa_pod_builder_int(&b, c->value); + spa_pod_builder_int(&b.b, c->value); break; case SPA_TYPE_Bool: - spa_pod_builder_bool(&b, c->value); + spa_pod_builder_bool(&b.b, c->value); break; default: - spa_pod_builder_int(&b, c->value); + spa_pod_builder_int(&b.b, c->value); break; } } - param = spa_pod_builder_pop(&b, &f); + param = spa_pod_builder_pop(&b.b, &f); break; default: return 0; @@ -338,14 +342,14 @@ static int impl_node_enum_params(void *object, int seq, return spa_v4l2_enum_format(this, seq, start, num, filter); case SPA_PARAM_Format: if((res = port_get_format(GET_OUT_PORT(this, 0), - result.index, filter, ¶m, &b)) <= 0) + result.index, filter, ¶m, &b.b)) <= 0) return res; break; default: return -ENOENT; } - if (spa_pod_filter(&b, &result.param, param, filter) < 0) + if (spa_pod_filter(&b.b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); @@ -535,7 +539,8 @@ static int impl_node_port_enum_params(void *object, int seq, struct impl *this = object; struct port *port; struct spa_pod *param; - struct spa_pod_builder b = { 0 }; + spa_auto(spa_pod_dynamic_builder) b = { 0 }; + struct spa_pod_builder_state state; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; @@ -545,6 +550,9 @@ static int impl_node_port_enum_params(void *object, int seq, spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + spa_pod_builder_get_state(&b.b, &state); + port = GET_PORT(this, direction, port_id); result.id = id; @@ -552,7 +560,7 @@ static int impl_node_port_enum_params(void *object, int seq, next: result.index = result.next++; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_reset(&b.b, &state); switch (id) { case SPA_PARAM_PropInfo: @@ -562,7 +570,7 @@ static int impl_node_port_enum_params(void *object, int seq, return spa_v4l2_enum_format(this, seq, start, num, filter); case SPA_PARAM_Format: - if((res = port_get_format(port, result.index, filter, ¶m, &b)) <= 0) + if((res = port_get_format(port, result.index, filter, ¶m, &b.b)) <= 0) return res; break; case SPA_PARAM_Buffers: @@ -571,7 +579,7 @@ static int impl_node_port_enum_params(void *object, int seq, if (result.index > 0) return 0; - param = spa_pod_builder_add_object(&b, + param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(4, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), @@ -582,13 +590,13 @@ static int impl_node_port_enum_params(void *object, int seq, case SPA_PARAM_Meta: switch (result.index) { case 0: - param = spa_pod_builder_add_object(&b, + param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; case 1: - param = spa_pod_builder_add_object(&b, + param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_videotransform))); @@ -600,19 +608,19 @@ static int impl_node_port_enum_params(void *object, int seq, case SPA_PARAM_IO: switch (result.index) { case 0: - param = spa_pod_builder_add_object(&b, + param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: - param = spa_pod_builder_add_object(&b, + param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 2: - param = spa_pod_builder_add_object(&b, + param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Control), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_sequence))); @@ -624,7 +632,7 @@ static int impl_node_port_enum_params(void *object, int seq, case SPA_PARAM_Latency: switch (result.index) { case 0: case 1: - param = spa_latency_build(&b, id, &this->latency[result.index]); + param = spa_latency_build(&b.b, id, &this->latency[result.index]); break; default: return 0; @@ -634,7 +642,7 @@ static int impl_node_port_enum_params(void *object, int seq, return -ENOENT; } - if (spa_pod_filter(&b, &result.param, param, filter) < 0) + if (spa_pod_filter(&b.b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); @@ -652,6 +660,8 @@ static int port_set_format(struct impl *this, struct port *port, struct spa_video_info info; int res; + spa_zero(info); + if (port->have_format) { spa_v4l2_stream_off(this); spa_v4l2_clear_buffers(this); diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index be2752617..cb48e9f69 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -507,7 +507,8 @@ spa_v4l2_enum_format(struct impl *this, int seq, uint32_t filter_media_type, filter_media_subtype; struct spa_v4l2_device *dev = &port->dev; uint8_t buffer[1024]; - struct spa_pod_builder b = { 0 }; + spa_auto(spa_pod_dynamic_builder) b = { 0 }; + struct spa_pod_builder_state state; struct spa_pod_frame f[2]; struct spa_result_node_params result; uint32_t count = 0; @@ -515,6 +516,9 @@ spa_v4l2_enum_format(struct impl *this, int seq, if ((res = spa_v4l2_open(dev, this->props.device)) < 0) return res; + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + spa_pod_builder_get_state(&b.b, &state); + result.id = SPA_PARAM_EnumFormat; result.next = start; @@ -632,14 +636,31 @@ do_enum_fmt: } do_frmsize: if ((res = xioctl(dev->fd, VIDIOC_ENUM_FRAMESIZES, &port->frmsize)) < 0) { - if (errno == EINVAL || errno == ENOTTY) + if (errno == ENOTTY) goto next_fmtdesc; + if (errno == EINVAL) { + if (port->frmsize.index == 0) { + port->frmsize.type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + port->frmsize.stepwise.min_width = 16; + port->frmsize.stepwise.min_height = 16; + port->frmsize.stepwise.max_width = 16384; + port->frmsize.stepwise.max_height = 16384; + port->frmsize.stepwise.step_width = 16; + port->frmsize.stepwise.step_height = 16; + port->fmtdesc.index++; + port->next_fmtdesc = true; + goto do_frmsize_filter; + } + else + goto next_fmtdesc; + } res = -errno; spa_log_error(this->log, "'%s' VIDIOC_ENUM_FRAMESIZES: %m", this->props.device); goto exit; } +do_frmsize_filter: if (filter) { static const struct spa_rectangle step = {1, 1}; @@ -697,34 +718,35 @@ do_enum_fmt: } } - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(&b, + spa_pod_builder_reset(&b.b, &state); + spa_pod_builder_push_object(&b.b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(&b.b, SPA_FORMAT_mediaType, SPA_POD_Id(info->media_type), SPA_FORMAT_mediaSubtype, SPA_POD_Id(info->media_subtype), 0); if (info->media_subtype == SPA_MEDIA_SUBTYPE_raw) { - spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_format, 0); - spa_pod_builder_id(&b, info->format); + spa_pod_builder_prop(&b.b, SPA_FORMAT_VIDEO_format, 0); + spa_pod_builder_id(&b.b, info->format); } - spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_size, 0); + + spa_pod_builder_prop(&b.b, SPA_FORMAT_VIDEO_size, 0); if (port->frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { - spa_pod_builder_rectangle(&b, + spa_pod_builder_rectangle(&b.b, port->frmsize.discrete.width, port->frmsize.discrete.height); } else if (port->frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS || port->frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) { - spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_None, 0); - choice = (struct spa_pod_choice*)spa_pod_builder_frame(&b, &f[1]); + spa_pod_builder_push_choice(&b.b, &f[1], SPA_CHOICE_None, 0); + choice = (struct spa_pod_choice*)spa_pod_builder_frame(&b.b, &f[1]); - spa_pod_builder_rectangle(&b, + spa_pod_builder_rectangle(&b.b, port->frmsize.stepwise.min_width, port->frmsize.stepwise.min_height); - spa_pod_builder_rectangle(&b, + spa_pod_builder_rectangle(&b.b, port->frmsize.stepwise.min_width, port->frmsize.stepwise.min_height); - spa_pod_builder_rectangle(&b, + spa_pod_builder_rectangle(&b.b, port->frmsize.stepwise.max_width, port->frmsize.stepwise.max_height); @@ -732,35 +754,43 @@ do_enum_fmt: choice->body.type = SPA_CHOICE_Range; } else { choice->body.type = SPA_CHOICE_Step; - spa_pod_builder_rectangle(&b, + spa_pod_builder_rectangle(&b.b, port->frmsize.stepwise.max_width, port->frmsize.stepwise.max_height); } - spa_pod_builder_pop(&b, &f[1]); + spa_pod_builder_pop(&b.b, &f[1]); } - spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_framerate, 0); + spa_pod_builder_prop(&b.b, SPA_FORMAT_VIDEO_framerate, 0); n_fractions = 0; - spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_None, 0); - choice = (struct spa_pod_choice*)spa_pod_builder_frame(&b, &f[1]); + spa_pod_builder_push_choice(&b.b, &f[1], SPA_CHOICE_None, 0); + choice = (struct spa_pod_choice*)spa_pod_builder_frame(&b.b, &f[1]); port->frmival.index = 0; while (true) { if ((res = xioctl(dev->fd, VIDIOC_ENUM_FRAMEINTERVALS, &port->frmival)) < 0) { res = -errno; + port->frmsize.index++; + port->next_frmsize = true; if (errno == EINVAL || errno == ENOTTY) { - port->frmsize.index++; - port->next_frmsize = true; - if (port->frmival.index == 0) - goto next_frmsize; - break; + if (port->frmival.index == 0) { + port->frmival.type = V4L2_FRMIVAL_TYPE_CONTINUOUS; + port->frmival.stepwise.min.denominator = 1; + port->frmival.stepwise.min.numerator = 1; + port->frmival.stepwise.max.denominator = 120; + port->frmival.stepwise.max.numerator = 1; + goto do_frminterval_filter; + } + else + break; } spa_log_error(this->log, "'%s' VIDIOC_ENUM_FRAMEINTERVALS: %m", this->props.device); goto exit; } +do_frminterval_filter: if (filter) { static const struct spa_fraction step = {1, 1}; @@ -812,10 +842,10 @@ do_enum_fmt: if (port->frmival.type == V4L2_FRMIVAL_TYPE_DISCRETE) { choice->body.type = SPA_CHOICE_Enum; if (n_fractions == 0) - spa_pod_builder_fraction(&b, + spa_pod_builder_fraction(&b.b, port->frmival.discrete.denominator, port->frmival.discrete.numerator); - spa_pod_builder_fraction(&b, + spa_pod_builder_fraction(&b.b, port->frmival.discrete.denominator, port->frmival.discrete.numerator); port->frmival.index++; @@ -823,11 +853,11 @@ do_enum_fmt: } else if (port->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS || port->frmival.type == V4L2_FRMIVAL_TYPE_STEPWISE) { if (n_fractions == 0) - spa_pod_builder_fraction(&b, 25, 1); - spa_pod_builder_fraction(&b, + spa_pod_builder_fraction(&b.b, 25, 1); + spa_pod_builder_fraction(&b.b, port->frmival.stepwise.min.denominator, port->frmival.stepwise.min.numerator); - spa_pod_builder_fraction(&b, + spa_pod_builder_fraction(&b.b, port->frmival.stepwise.max.denominator, port->frmival.stepwise.max.numerator); @@ -836,7 +866,7 @@ do_enum_fmt: n_fractions += 2; } else { choice->body.type = SPA_CHOICE_Step; - spa_pod_builder_fraction(&b, + spa_pod_builder_fraction(&b.b, port->frmival.stepwise.step.denominator, port->frmival.stepwise.step.numerator); n_fractions += 3; @@ -851,9 +881,9 @@ do_enum_fmt: goto next_frmsize; if (n_fractions == 1) choice->body.type = SPA_CHOICE_None; + spa_pod_builder_pop(&b.b, &f[1]); - spa_pod_builder_pop(&b, &f[1]); - result.param = spa_pod_builder_pop(&b, &f[0]); + result.param = spa_pod_builder_pop(&b.b, &f[0]); spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); @@ -966,7 +996,7 @@ static int spa_v4l2_set_format(struct impl *this, struct spa_video_info *format, streamparm.parm.capture.timeperframe.numerator = framerate->denom; streamparm.parm.capture.timeperframe.denominator = framerate->num; - spa_log_debug(this->log, "set %.4s %dx%d %d/%d", (char *)&fmt.fmt.pix.pixelformat, + spa_log_info(this->log, "set %.4s %dx%d %d/%d", (char *)&fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height, streamparm.parm.capture.timeperframe.denominator, streamparm.parm.capture.timeperframe.numerator); @@ -1005,6 +1035,9 @@ static int spa_v4l2_set_format(struct impl *this, struct spa_video_info *format, if (flags & SPA_NODE_PARAM_FLAG_TEST_ONLY) return match ? 0 : 1; + if (streamparm.parm.capture.timeperframe.denominator == 0) + streamparm.parm.capture.timeperframe.denominator = 1; + spa_log_info(this->log, "'%s' got %.4s %dx%d %d/%d", dev->path, (char *)&fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height,