From 64ad7454373a1d8b2ec9145c16c5792afcae16cd Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 7 Sep 2016 13:43:14 +0200 Subject: [PATCH] v4l2: implement enum_formats with filter Use the filter to query the device and return a filtered result. --- pinos/gst/gstpinosformat.c | 2 + pinos/server/link.c | 1 + spa/include/spa/defs.h | 1 + spa/include/spa/props.h | 4 +- spa/lib/video-raw.c | 2 +- spa/plugins/v4l2/v4l2-utils.c | 336 +++++++++++++++++++++++++++++----- 6 files changed, 302 insertions(+), 44 deletions(-) diff --git a/pinos/gst/gstpinosformat.c b/pinos/gst/gstpinosformat.c index 6c9cb696f..e3f3080b4 100644 --- a/pinos/gst/gstpinosformat.c +++ b/pinos/gst/gstpinosformat.c @@ -449,6 +449,8 @@ gst_caps_from_format (SpaFormat *format) } else if (format->media_subtype == SPA_MEDIA_SUBTYPE_MJPG) { res = gst_caps_new_simple ("image/jpeg", + "width", G_TYPE_INT, f.info.mjpg.size.width, + "height", G_TYPE_INT, f.info.mjpg.size.height, "framerate", GST_TYPE_FRACTION, f.info.mjpg.framerate.num, f.info.mjpg.framerate.denom, NULL); } diff --git a/pinos/server/link.c b/pinos/server/link.c index 26378a01a..11a3078ae 100644 --- a/pinos/server/link.c +++ b/pinos/server/link.c @@ -230,6 +230,7 @@ again: g_warning ("error output enum formats: %d", res); goto error; } + spa_debug_format (format); spa_format_fixate (format); } else if (in_state == SPA_NODE_STATE_CONFIGURE) { /* only input needs format */ diff --git a/spa/include/spa/defs.h b/spa/include/spa/defs.h index 607a6bfdd..d99601d05 100644 --- a/spa/include/spa/defs.h +++ b/spa/include/spa/defs.h @@ -76,6 +76,7 @@ typedef void (*SpaNotify) (void *data); #define SPA_PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p))) #define SPA_UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u))) +#define SPA_IDX_INVALID ((unsigned int)-1) #define SPA_ID_INVALID ((uint32_t)0xffffffff) diff --git a/spa/include/spa/props.h b/spa/include/spa/props.h index 4aec84102..74a075fec 100644 --- a/spa/include/spa/props.h +++ b/spa/include/spa/props.h @@ -184,7 +184,7 @@ spa_props_index_for_id (const SpaProps *props, uint32_t id) if (props->prop_info[i].id == id) return i; } - return -1; + return SPA_IDX_INVALID; } static inline unsigned int @@ -196,7 +196,7 @@ spa_props_index_for_name (const SpaProps *props, const char *name) if (strcmp (props->prop_info[i].name, name) == 0) return i; } - return -1; + return SPA_IDX_INVALID; } diff --git a/spa/lib/video-raw.c b/spa/lib/video-raw.c index 30b8fbe69..604fadbb2 100644 --- a/spa/lib/video-raw.c +++ b/spa/lib/video-raw.c @@ -110,7 +110,7 @@ static const uint32_t format_values[] = { }; static const SpaPropRangeInfo format_range[] = { - { "ENCODED,", "ENCODED", sizeof (uint32_t), &format_values[1] }, + { "ENCODED", "ENCODED", sizeof (uint32_t), &format_values[1] }, { "I420", "I420", sizeof (uint32_t), &format_values[2] }, { "YV12", "YV12", sizeof (uint32_t), &format_values[3] }, { "YUY2", "YUY2", sizeof (uint32_t), &format_values[4] }, diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index 7e8534296..45b4684fc 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -297,6 +297,129 @@ find_format_info_by_media_type (SpaMediaType type, return NULL; } +static SpaVideoFormat +enum_filter_format (const SpaFormat *filter, unsigned int index) +{ + SpaVideoFormat video_format = SPA_VIDEO_FORMAT_UNKNOWN; + + if ((filter->media_type == SPA_MEDIA_TYPE_VIDEO || filter->media_type == SPA_MEDIA_TYPE_IMAGE)) { + if (filter->media_subtype == SPA_MEDIA_SUBTYPE_RAW) { + SpaPropValue val; + SpaResult res; + unsigned int idx; + const SpaPropInfo *pi; + + idx = spa_props_index_for_id (&filter->props, SPA_PROP_ID_VIDEO_FORMAT); + if (idx == SPA_IDX_INVALID) + return SPA_VIDEO_FORMAT_UNKNOWN; + + pi = &filter->props.prop_info[idx]; + if (pi->type != SPA_PROP_TYPE_UINT32) + return SPA_VIDEO_FORMAT_UNKNOWN; + + res = spa_props_get_prop (&filter->props, idx, &val); + if (res >= 0) { + if (index == 0) + video_format = *((SpaVideoFormat *)val.value); + } else if (res == SPA_RESULT_PROPERTY_UNSET) { + + if (index < pi->n_range_values) + video_format = *((SpaVideoFormat *)pi->range_values[index].value); + } + } else { + if (index == 0) + video_format = SPA_VIDEO_FORMAT_ENCODED; + } + } + return video_format; +} + +static bool +filter_framesize (struct v4l2_frmsizeenum *frmsize, + const SpaRectangle *min, + const SpaRectangle *max, + const SpaRectangle *step) +{ + if (frmsize->type == V4L2_FRMSIZE_TYPE_DISCRETE) { + if (frmsize->discrete.width < min->width || + frmsize->discrete.height < min->height || + frmsize->discrete.width > max->width || + frmsize->discrete.height > max->height) { + return false; + } + } else if (frmsize->type == V4L2_FRMSIZE_TYPE_CONTINUOUS || + frmsize->type == V4L2_FRMSIZE_TYPE_STEPWISE) { + /* FIXME, use LCM */ + frmsize->stepwise.step_width *= step->width; + frmsize->stepwise.step_height *= step->height; + + if (frmsize->stepwise.max_width < min->width || + frmsize->stepwise.max_height < min->height || + frmsize->stepwise.min_width > max->width || + frmsize->stepwise.min_height > max->height) + return false; + + frmsize->stepwise.min_width = SPA_MAX (frmsize->stepwise.min_width, min->width); + frmsize->stepwise.min_height = SPA_MAX (frmsize->stepwise.min_height, min->height); + frmsize->stepwise.max_width = SPA_MIN (frmsize->stepwise.max_width, max->width); + frmsize->stepwise.max_height = SPA_MIN (frmsize->stepwise.max_height, max->height); + } else + return false; + + return true; +} + +static int +compare_fraction (struct v4l2_fract *f1, const SpaFraction *f2) +{ + uint64_t n1, n2; + + /* fractions are reduced when set, so we can quickly see if they're equal */ + if (f1->denominator == f2->num && f1->numerator == f2->denom) + return 0; + + /* extend to 64 bits */ + n1 = ((int64_t) f1->denominator) * f2->denom; + n2 = ((int64_t) f1->numerator) * f2->num; + if (n1 < n2) + return -1; + return 1; +} + +static bool +filter_framerate (struct v4l2_frmivalenum *frmival, + const SpaFraction *min, + const SpaFraction *max, + const SpaFraction *step) +{ + if (frmival->type == V4L2_FRMIVAL_TYPE_DISCRETE) { + if (compare_fraction (&frmival->discrete, min) < 0 || + compare_fraction (&frmival->discrete, max) > 0) + return false; + } else if (frmival->type == V4L2_FRMIVAL_TYPE_CONTINUOUS || + frmival->type == V4L2_FRMIVAL_TYPE_STEPWISE) { + /* FIXME, use LCM */ + frmival->stepwise.step.denominator *= step->num; + frmival->stepwise.step.numerator *= step->denom; + + if (compare_fraction (&frmival->stepwise.max, min) < 0 || + compare_fraction (&frmival->stepwise.min, max) > 0) + return false; + + if (compare_fraction (&frmival->stepwise.min, min) < 0) { + frmival->stepwise.min.denominator = min->num; + frmival->stepwise.min.numerator = min->denom; + } + if (compare_fraction (&frmival->stepwise.max, max) > 0) { + frmival->stepwise.max.denominator = max->num; + frmival->stepwise.max.numerator = max->denom; + } + } else + return false; + + return true; +} + #define FOURCC_ARGS(f) (f)&0x7f,((f)>>8)&0x7f,((f)>>16)&0x7f,((f)>>24)&0x7f static SpaResult @@ -306,7 +429,6 @@ spa_v4l2_enum_format (SpaV4l2Source *this, SpaFormat **format, const SpaFormat * int res, i, pi; V4l2Format *fmt; const FormatInfo *info; - SpaVideoFormat video_format; if (spa_v4l2_open (this) < 0) return SPA_RESULT_ERROR; @@ -324,29 +446,18 @@ spa_v4l2_enum_format (SpaV4l2Source *this, SpaFormat **format, const SpaFormat * *cookie = state; } + if (false) { +next_fmtdesc: + state->fmtdesc.index++; + state->next_fmtdesc = true; + } -again: - if (state->next_fmtdesc) { + while (state->next_fmtdesc) { if (filter) { - SpaPropValue val; + SpaVideoFormat video_format; - if (state->fmtdesc.index == 1) - return SPA_RESULT_ENUM_END; - - video_format = SPA_VIDEO_FORMAT_UNKNOWN; - if ((filter->media_type == SPA_MEDIA_TYPE_VIDEO)) { - if (filter->media_subtype == SPA_MEDIA_SUBTYPE_RAW) { - if (spa_props_get_prop (&filter->props, - spa_props_index_for_id (&filter->props, SPA_PROP_ID_VIDEO_FORMAT), - &val) >= 0) { - video_format = *((SpaVideoFormat *)val.value); - } - } else { - video_format = SPA_VIDEO_FORMAT_ENCODED; - } - } else if ((filter->media_type == SPA_MEDIA_TYPE_IMAGE)) { - video_format = SPA_VIDEO_FORMAT_ENCODED; - } else + video_format = enum_filter_format (filter, state->fmtdesc.index); + if (video_format == SPA_VIDEO_FORMAT_UNKNOWN) return SPA_RESULT_ENUM_END; info = find_format_info_by_media_type (filter->media_type, @@ -354,7 +465,7 @@ again: video_format, 0); if (info == NULL) - return SPA_RESULT_ENUM_END; + goto next_fmtdesc; state->fmtdesc.pixelformat = info->fourcc; } else { @@ -370,29 +481,104 @@ again: state->next_frmsize = true; } - if (!(info = fourcc_to_format_info (state->fmtdesc.pixelformat))) { - state->fmtdesc.index++; - state->next_fmtdesc = true; - goto again; - } + if (!(info = fourcc_to_format_info (state->fmtdesc.pixelformat))) + goto next_fmtdesc; - if (state->next_frmsize) { - if ((res = xioctl (state->fd, VIDIOC_ENUM_FRAMESIZES, &state->frmsize)) < 0) { - if (errno == EINVAL) { - state->fmtdesc.index++; - state->next_fmtdesc = true; - goto again; +next_frmsize: + while (state->next_frmsize) { + if (filter) { + const SpaPropInfo *pi; + unsigned int idx; + SpaPropValue val; + SpaResult res; + + /* check if we have a fixed frame size */ + idx = spa_props_index_for_id (&filter->props, SPA_PROP_ID_VIDEO_SIZE); + if (idx == SPA_IDX_INVALID) + goto do_frmsize; + + pi = &filter->props.prop_info[idx]; + if (pi->type != SPA_PROP_TYPE_RECTANGLE) + return SPA_RESULT_ENUM_END; + + res = spa_props_get_prop (&filter->props, idx, &val); + if (res >= 0) { + const SpaRectangle *size = val.value; + + if (state->frmsize.index > 0) + goto next_fmtdesc; + + state->frmsize.type = V4L2_FRMSIZE_TYPE_DISCRETE; + state->frmsize.discrete.width = size->width; + state->frmsize.discrete.height = size->height; + goto have_size; } + } +do_frmsize: + if ((res = xioctl (state->fd, VIDIOC_ENUM_FRAMESIZES, &state->frmsize)) < 0) { + if (errno == EINVAL) + goto next_fmtdesc; + perror ("VIDIOC_ENUM_FRAMESIZES"); return SPA_RESULT_ENUM_END; } - state->next_frmsize = false; + if (filter) { + const SpaPropInfo *pi; + unsigned int idx; + const SpaRectangle step = { 1, 1 }; + /* check if we have a fixed frame size */ + idx = spa_props_index_for_id (&filter->props, SPA_PROP_ID_VIDEO_SIZE); + if (idx == SPA_IDX_INVALID) + goto have_size; + + /* checked above */ + pi = &filter->props.prop_info[idx]; + + if (pi->range_type == SPA_PROP_RANGE_TYPE_MIN_MAX) { + if (filter_framesize (&state->frmsize, pi->range_values[0].value, + pi->range_values[1].value, + &step)) + goto have_size; + } else if (pi->range_type == SPA_PROP_RANGE_TYPE_STEP) { + if (filter_framesize (&state->frmsize, pi->range_values[0].value, + pi->range_values[1].value, + pi->range_values[2].value)) + goto have_size; + } else if (pi->range_type == SPA_PROP_RANGE_TYPE_ENUM) { + unsigned int i; + for (i = 0; i < pi->n_range_values; i++) { + if (filter_framesize (&state->frmsize, pi->range_values[i].value, + pi->range_values[i].value, + &step)) + goto have_size; + } + } + /* nothing matches the filter, get next frame size */ + state->frmsize.index++; + continue; + } + +have_size: if (state->frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + /* we have a fixed size, use this to get the frame intervals */ state->frmival.index = 0; state->frmival.pixel_format = state->frmsize.pixel_format; state->frmival.width = state->frmsize.discrete.width; state->frmival.height = state->frmsize.discrete.height; + state->next_frmsize = false; + } + else if (state->frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS || + state->frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) { + /* we have a non fixed size, fix to something sensible to get the + * framerate */ + state->frmival.index = 0; + state->frmival.pixel_format = state->frmsize.pixel_format; + state->frmival.width = state->frmsize.stepwise.min_width; + state->frmival.height = state->frmsize.stepwise.min_height; + state->next_frmsize = false; + } else { + state->frmsize.index++; } } @@ -424,7 +610,6 @@ again: spa_prop_info_fill_video (&fmt->infos[pi], SPA_PROP_ID_VIDEO_FRAMERATE, offsetof (V4l2Format, framerate)); - fmt->infos[pi].range_type = SPA_PROP_RANGE_TYPE_ENUM; fmt->infos[pi].range_values = fmt->ranges; fmt->infos[pi].n_range_values = 0; i = state->frmival.index = 0; @@ -434,20 +619,89 @@ again: if (errno == EINVAL) { state->frmsize.index++; state->next_frmsize = true; + if (i == 0) + goto next_frmsize; break; } perror ("VIDIOC_ENUM_FRAMEINTERVALS"); return SPA_RESULT_ENUM_END; } + if (filter) { + SpaPropValue val; + const SpaPropInfo *pi; + unsigned int idx; + SpaResult res; + const SpaFraction step = { 1, 1 }; + /* check against filter */ + idx = spa_props_index_for_id (&filter->props, SPA_PROP_ID_VIDEO_FRAMERATE); + if (idx == SPA_IDX_INVALID) + return SPA_RESULT_ENUM_END; + + pi = &filter->props.prop_info[idx]; + if (pi->type != SPA_PROP_TYPE_FRACTION) + return SPA_RESULT_ENUM_END; + + res = spa_props_get_prop (&filter->props, idx, &val); + if (res == 0) { + if (filter_framerate (&state->frmival, val.value, + val.value, + &step)) + goto have_framerate; + } else if (pi->range_type == SPA_PROP_RANGE_TYPE_MIN_MAX) { + if (filter_framerate (&state->frmival, pi->range_values[0].value, + pi->range_values[1].value, + &step)) + goto have_framerate; + } else if (pi->range_type == SPA_PROP_RANGE_TYPE_STEP) { + if (filter_framerate (&state->frmival, pi->range_values[0].value, + pi->range_values[1].value, + pi->range_values[2].value)) + goto have_framerate; + } else if (pi->range_type == SPA_PROP_RANGE_TYPE_ENUM) { + unsigned int i; + for (i = 0; i < pi->n_range_values; i++) { + if (filter_framerate (&state->frmival, pi->range_values[i].value, + pi->range_values[i].value, + &step)) + goto have_framerate; + } + } + state->frmival.index++; + continue; + } + +have_framerate: fmt->ranges[i].name = NULL; fmt->ranges[i].description = NULL; - fmt->ranges[i].size = sizeof (SpaFraction); - fmt->framerates[i].num = state->frmival.discrete.denominator; - fmt->framerates[i].denom = state->frmival.discrete.numerator; - fmt->ranges[i].value = &fmt->framerates[i]; - - i = ++state->frmival.index; + if (state->frmival.type == V4L2_FRMIVAL_TYPE_DISCRETE) { + fmt->infos[pi].range_type = SPA_PROP_RANGE_TYPE_ENUM; + fmt->ranges[i].size = sizeof (SpaFraction); + fmt->framerates[i].num = state->frmival.discrete.denominator; + fmt->framerates[i].denom = state->frmival.discrete.numerator; + fmt->ranges[i].value = &fmt->framerates[i]; + i++; + state->frmival.index++; + } else if (state->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS || + state->frmival.type == V4L2_FRMIVAL_TYPE_STEPWISE) { + fmt->framerates[0].num = state->frmival.stepwise.min.denominator; + fmt->framerates[0].denom = state->frmival.stepwise.min.numerator; + fmt->ranges[0].value = &fmt->framerates[0]; + fmt->framerates[1].num = state->frmival.stepwise.max.denominator; + fmt->framerates[1].denom = state->frmival.stepwise.max.numerator; + fmt->ranges[1].value = &fmt->framerates[1]; + if (state->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS) { + fmt->infos[pi].range_type = SPA_PROP_RANGE_TYPE_MIN_MAX; + i = 2; + } else { + fmt->infos[pi].range_type = SPA_PROP_RANGE_TYPE_STEP; + fmt->framerates[2].num = state->frmival.stepwise.step.denominator; + fmt->framerates[2].denom = state->frmival.stepwise.step.numerator; + fmt->ranges[2].value = &fmt->framerates[2]; + i = 3; + } + break; + } } fmt->infos[pi].n_range_values = i; fmt->framerate = fmt->framerates[0];