On Fri, Jan 25, 2019 at 09:09:29AM -0200, Martin Pieuchot wrote:
> On 25/01/19(Fri) 11:16, Raphael Graf wrote:
> > On Wed, Jan 23, 2019 at 12:54:51PM -0200, Martin Pieuchot wrote:
> > > > Index: video.c
> > > > ===================================================================
> > > > RCS file: /cvs/xenocara/app/video/video.c,v
> > > > retrieving revision 1.26
> > > > diff -u -p -u -p -r1.26 video.c
> > > > --- video.c 4 Jan 2019 17:45:00 -0000 1.26
> > > > +++ video.c 16 Jan 2019 11:30:21 -0000
> > > > @@ -38,6 +38,8 @@
> > > > #include <X11/extensions/Xvlib.h>
> > > > #include <X11/Xatom.h>
> > > >
> > > > +#define V_V4L2_PIX_FMT_YUY2 v4l2_fourcc('Y', 'U', 'Y', '2')
> > >
> > > Why do we need this define and not the others? Aren't we missing an
> > > include?
> >
> > Actually, I don't know why this define is missing in videoio.h.
> > This YUY2 format (id:0x32595559) is reported by Xv.
> > The uvideo(4) driver reports YUYV which has a define in videoio.h.
> > (YUY2 and YUYV are equivalent)
>
> I see. I believe you're being too clever her. XvListImageFormats(3)
> returns you a list with an Id in FourCC format and you're using V4L2
> defines to match these IDs.
>
> I'd suggest you define only one encoding "yuy2" or "yuyv" and make the
> matching function, find_enc_by_id() a bit more clever. That would
> explicitly document that Xv and V4L2 use different names for the same
> format. On top of that you'll get rid of your conversion code :)
>
The new diff below solves this yuy2/yuyv problem by defining them both under
the same name 'yuy2'.
The only change to the manpage is now the addition of yv12 to the list of
valid encodings.
The trickiest part is the 'choose_enc' function where encodings are chosen
based on device capabilities.
The following conversions are now possible:
yuy2 -> uyvy
yuy2 -> yv12
uyvy -> yuy2
uyvy -> yv12
As my webcam only provides yuy2, I have used input-files for testing:
$ video -i test.yuy2
$ video -i test.uyvy -e uyvy
$ video -i test.yv12 -e yv12
These examples work for me with both drivers (modesetting and intel).
The conversion to yv12 has a small performance impact, of course. Do you think
the performance is acceptable?
> > In the new diff, I have changed the variable names enc_in/enc_out to
> > enc/enc_xv
> > as the second 'enc' is only used for Xv output.
> > I also fixed the style(9) issues and added a CONV_NONE define.
>
> I find enc_in/enc_out more explicit, but let's keep it like that for
> now. This also makes the diff smaller :)
>
> > Note that I'm not sure if the algorithm used in the yuy2_to_yv12 function is
> > correct in respect to color theory..
> > (It is a conversion from 16 to 12 bits/pixel)
>
> I'm sure you can find some good documentation and/or example about that
> on the Web or in the existing freesoftware ecosystem.
>
Yes, there are libraries like 'libyuv' doing it in the same way.
(they also have optimized versions (SSE etc.))
Any comments, questions or feedback?
Index: video.1
===================================================================
RCS file: /cvs/xenocara/app/video/video.1,v
retrieving revision 1.13
diff -u -p -u -p -r1.13 video.1
--- video.1 4 Jun 2016 07:44:32 -0000 1.13
+++ video.1 7 Feb 2019 12:08:37 -0000
@@ -84,9 +84,10 @@ The default is 0, the first adaptor repo
.It Fl e Ar encoding
Lowercase FOURCC name of video encoding to use.
Valid arguments are
-.Ql uyvy
+.Ql uyvy ,
+.Ql yuy2
and
-.Ql yuy2 .
+.Ql yv12 .
The default is
.Ql yuy2
unless
Index: video.c
===================================================================
RCS file: /cvs/xenocara/app/video/video.c,v
retrieving revision 1.27
diff -u -p -u -p -r1.27 video.c
--- video.c 22 Jan 2019 20:02:40 -0000 1.27
+++ video.c 7 Feb 2019 12:08:37 -0000
@@ -38,6 +38,9 @@
#include <X11/extensions/Xvlib.h>
#include <X11/Xatom.h>
+/* Synonym for YUYV */
+#define V_V4L2_PIX_FMT_YUY2 v4l2_fourcc('Y', 'U', 'Y', '2')
+
/* Xv(3) adaptor properties */
struct xv_adap {
char name[128];
@@ -133,20 +136,22 @@ struct dev {
/* video encodingss */
struct encodings {
char *name;
+ int id;
int bpp;
- int xv_id;
- int dev_id;
-#define SW_DEV 0x1
-#define SW_XV 0x2
-#define SW_MASK (SW_DEV | SW_XV)
+#define SUP_DEV 0x1
+#define SUP_XV 0x2
int flags;
} encs[] = {
+#define ENC_YUYV 1
+ { "yuy2", V4L2_PIX_FMT_YUYV, 16, 0 },
#define ENC_YUY2 0
- { "yuy2", 16, -1, -1, 0 },
-#define ENC_UYVY 1
- { "uyvy", 16, -1, -1, 0 },
-#define ENC_LAST 2
- { NULL, 0, 0, 0, 0 }
+ { "yuy2", V_V4L2_PIX_FMT_YUY2, 16, 0 },
+#define ENC_UYVY 2
+ { "uyvy", V4L2_PIX_FMT_UYVY, 16, 0 },
+#define ENC_YV12 3
+ { "yv12", V4L2_PIX_FMT_YVU420, 12, 0 },
+#define ENC_LAST 4
+ { NULL, 0, 0, 0 }
};
struct video {
@@ -162,9 +167,12 @@ struct video {
char iofile[FILENAME_MAX];
int iofile_fd;
char *sz_str;
-#define CONV_SWAP 0x1
+#define CONV_NONE 0x1
+#define CONV_SWAP 0x2
+#define CONV_YV12 0x4
int conv_type;
int enc;
+ int enc_xv;
int full_screen;
int net_wm;
int width;
@@ -216,6 +224,7 @@ int stream(struct video *);
void got_frame(int);
void got_shutdown(int);
int find_enc(char *);
+int find_enc_by_id(int);
void usage(void);
static volatile sig_atomic_t play, shutdown, hold, wout;
@@ -242,6 +251,17 @@ find_enc(char *name)
}
int
+find_enc_by_id(int id)
+{
+ int i;
+
+ for (i = 0; i < ENC_LAST; i++)
+ if (encs[i].id == id)
+ break;
+ return i;
+}
+
+int
xv_get_info(struct video *vid)
{
struct xdsp *x = &vid->xdsp;
@@ -250,8 +270,7 @@ xv_get_info(struct video *vid)
XvEncodingInfo *xv_encs;
struct xv_adap *adap;
unsigned int nenc, p;
- int num_xvformats, nadaps, i, j, ret;
- char fmtName[5];
+ int num_xvformats, nadaps, i, j, ret, enc;
if ((x->dpy = XOpenDisplay(NULL)) == NULL) {
warnx("cannot open display %s", XDisplayName(NULL));
@@ -312,16 +331,9 @@ xv_get_info(struct video *vid)
&num_xvformats);
adap->nfmts = 0;
for (j = 0; j < num_xvformats; j++) {
- snprintf(fmtName, sizeof(fmtName), "%c%c%c%c",
- xvformats[j].id & 0xff,
- (xvformats[j].id >> 8) & 0xff,
- (xvformats[j].id >> 16) & 0xff,
- (xvformats[j].id >> 24) & 0xff);
- if (!strcmp(fmtName, "YUY2")) {
- encs[ENC_YUY2].xv_id = xvformats[j].id;
- adap->fmts[adap->nfmts++] = xvformats[j].id;
- } else if (!strcmp(fmtName, "UYVY")) {
- encs[ENC_UYVY].xv_id = xvformats[j].id;
+ enc = find_enc_by_id(xvformats[j].id);
+ if (enc < ENC_LAST) {
+ encs[enc].flags |= SUP_XV;
adap->fmts[adap->nfmts++] = xvformats[j].id;
}
if (adap->nfmts >= MAX_FMTS)
@@ -369,20 +381,20 @@ xv_sel_adap(struct video *vid)
x->cur_adap = i;
adap = &x->adaps[i];
for (i = 0; i < adap->nfmts; i++) {
- if (adap->fmts[i] == encs[vid->enc].xv_id)
+ if (adap->fmts[i] == encs[vid->enc_xv].id)
break;
}
if (i >= adap->nfmts) {
warnx("Xv adaptor '%d' doesn't support %s",
x->adaps[x->cur_adap].adap_index,
- encs[vid->enc].name);
+ encs[vid->enc_xv].name);
return 0;
}
}
for (i = 0; i < x->nadaps && x->cur_adap == -1; i++) {
adap = &x->adaps[i];
for (j = 0; j < adap->nfmts; j++) {
- if (adap->fmts[j] == encs[vid->enc].xv_id) {
+ if (adap->fmts[j] == encs[vid->enc_xv].id) {
x->cur_adap = i;
break;
}
@@ -445,7 +457,7 @@ xv_dump_info(struct video *vid)
fprintf(stderr, " encodings: ");
for (i = 0, j = 0; i < ENC_LAST; i++) {
- if (encs[i].xv_id != -1 && !(encs[i].flags & SW_XV)) {
+ if (encs[i].id != -1 && (encs[i].flags & SUP_XV)) {
if (j)
fprintf(stderr, ", ");
fprintf(stderr, "%s", encs[i].name);
@@ -502,7 +514,7 @@ xv_init(struct video *vid)
resize_window(vid, 0);
- x->xv_image = XvCreateImage(x->dpy, x->port, encs[vid->enc].xv_id,
+ x->xv_image = XvCreateImage(x->dpy, x->port, encs[vid->enc_xv].id,
vid->frame_buffer, vid->width, vid->height);
return 1;
@@ -780,36 +792,19 @@ dev_get_encs(struct video *vid)
fmtdesc.index = 0;
fmtdesc.type = d->buf_type;
while (ioctl(d->fd, VIDIOC_ENUM_FMT, &fmtdesc) >= 0) {
- if (fmtdesc.pixelformat == V4L2_PIX_FMT_YUYV) {
- i = find_enc("yuy2");
- if (i < ENC_LAST)
- encs[i].dev_id = fmtdesc.pixelformat;
- }
- if (fmtdesc.pixelformat == V4L2_PIX_FMT_UYVY) {
- i = find_enc("uyvy");
- if (i < ENC_LAST)
- encs[i].dev_id = fmtdesc.pixelformat;
- }
+ i = find_enc_by_id(fmtdesc.pixelformat);
+ if (i < ENC_LAST)
+ encs[i].flags |= SUP_DEV;
fmtdesc.index++;
}
for (i = 0; encs[i].name; i++) {
- if (encs[i].dev_id != -1)
+ if (encs[i].flags & SUP_DEV)
break;
}
if (i >= ENC_LAST) {
warnx("%s has no usable YUV encodings", d->path);
return 0;
}
- if (encs[ENC_YUY2].dev_id == -1 &&
- encs[ENC_UYVY].dev_id != -1) {
- encs[ENC_YUY2].dev_id = encs[ENC_UYVY].dev_id;
- encs[ENC_YUY2].flags |= SW_DEV;
- }
- if (encs[ENC_UYVY].dev_id == -1 &&
- encs[ENC_YUY2].dev_id != -1) {
- encs[ENC_UYVY].dev_id = encs[ENC_YUY2].dev_id;
- encs[ENC_UYVY].flags |= SW_DEV;
- }
return 1;
}
@@ -824,7 +819,7 @@ dev_get_sizes(struct video *vid)
nsizes = 0;
fsize.index = 0;
- fsize.pixel_format = encs[vid->enc].dev_id;
+ fsize.pixel_format = encs[vid->enc].id;
while (ioctl(d->fd, VIDIOC_ENUM_FRAMESIZES, &fsize) == 0) {
switch (fsize.type) {
case V4L2_FRMSIZE_TYPE_DISCRETE:
@@ -915,7 +910,7 @@ dev_get_rates(struct video *vid)
for (i = 0; i < d->nsizes; i++) {
bzero(&ival, sizeof(ival));
- ival.pixel_format = encs[vid->enc].dev_id;
+ ival.pixel_format = encs[vid->enc].id;
ival.width = d->sizes[i].w;
ival.height = d->sizes[i].h;
ival.index = 0;
@@ -1072,7 +1067,7 @@ dev_dump_info(struct video *vid)
fprintf(stderr, " encodings: ");
for (i = 0, j = 0; i < ENC_LAST; i++) {
- if (encs[i].dev_id != -1 && !(encs[i].flags & SW_DEV)) {
+ if (encs[i].flags & SUP_DEV) {
if (j)
fprintf(stderr, ", ");
fprintf(stderr, "%s", encs[i].name);
@@ -1136,7 +1131,7 @@ dev_init(struct video *vid)
fmt.type = d->buf_type;
fmt.fmt.pix.width = vid->width;
fmt.fmt.pix.height = vid->height;
- fmt.fmt.pix.pixelformat = encs[vid->enc].dev_id;
+ fmt.fmt.pix.pixelformat = encs[vid->enc].id;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
if (ioctl(d->fd, VIDIOC_S_FMT, &fmt) < 0) {
warn("VIDIOC_S_FMT");
@@ -1289,58 +1284,60 @@ int
choose_enc(struct video *vid)
{
int i;
+ int enc, enc_xv;
if (vid->enc < 0) {
- for (i = 0; vid->enc < 0 && i < ENC_LAST; i++) {
- if ((vid->mode & M_IN_DEV) && (vid->mode & M_OUT_XV)) {
- if (encs[i].dev_id != -1 &&
- encs[i].xv_id != -1 &&
- (encs[i].flags & SW_MASK) == 0)
- vid->enc = i;
- } else if (vid->mode & M_IN_DEV) {
- if (encs[i].dev_id != -1 &&
- (encs[i].flags & SW_MASK) == 0)
+ if (vid->mode & M_IN_DEV) {
+ for (i = 0; vid->enc < 0 && i < ENC_LAST; i++)
+ if (encs[i].flags & SUP_DEV)
vid->enc = i;
- } else if (vid->mode & M_OUT_XV) {
- if (encs[i].xv_id != -1 &&
- (encs[i].flags & SW_MASK) == 0)
- vid->enc = i;
- }
+ } else {
+ vid->enc = ENC_YUYV;
}
- for (i = 0; vid->enc < 0 && i < ENC_LAST; i++) {
- if ((vid->mode & M_IN_DEV) && (vid->mode & M_OUT_XV)) {
- if (encs[i].dev_id != -1 &&
- encs[i].xv_id != -1)
- vid->enc = i;
- } else if (vid->mode & M_IN_DEV) {
- if (encs[i].dev_id != -1)
- vid->enc = i;
- } else if (vid->mode & M_OUT_XV) {
- if (encs[i].xv_id != -1)
- vid->enc = i;
- }
+ if ((vid->mode & M_IN_DEV) && (vid->mode & M_OUT_XV)) {
+ for (i = 0; vid->enc < 0 && i < ENC_LAST; i++)
+ if (encs[i].flags == (SUP_DEV | SUP_XV))
+ vid->enc = vid->enc_xv = i;
}
}
if (vid->enc < 0) {
warnx("could not find a usable encoding");
return 0;
}
- if ((vid->mode & M_IN_DEV) && encs[vid->enc].dev_id == -1) {
+ if ((vid->mode & M_IN_DEV) && !(encs[vid->enc].flags & SUP_DEV)) {
warnx("device %s can't supply %s", vid->dev.path,
encs[vid->enc].name);
return 0;
}
- if ((vid->mode & M_OUT_XV) && encs[vid->enc].xv_id == -1) {
+ if (vid->enc_xv < 0 && (vid->mode & M_OUT_XV)) {
+ if (encs[vid->enc].flags & SUP_XV)
+ vid->enc_xv = vid->enc;
+ for (i = 0; vid->enc_xv < 0 && i < ENC_LAST; i++)
+ if (encs[i].flags & SUP_XV)
+ vid->enc_xv = i;
+ }
+
+ if (vid->mode & M_OUT_XV && vid->enc != vid->enc_xv) {
+ /* check if conversion is possible */
+ enc = (vid->enc == ENC_YUYV) ? ENC_YUY2 : vid->enc;
+ enc_xv = (vid->enc_xv == ENC_YUYV) ? ENC_YUY2 : vid->enc_xv;
+ if (enc == enc_xv)
+ vid->conv_type = CONV_NONE;
+ else {
+ if (enc == ENC_UYVY || enc_xv == ENC_UYVY)
+ vid->conv_type |= CONV_SWAP;
+ if (enc_xv == ENC_YV12)
+ vid->conv_type |= CONV_YV12;
+ }
+ if (!vid->conv_type)
+ vid->enc_xv = vid->enc;
+ }
+ if ((vid->mode & M_OUT_XV) && !(encs[vid->enc_xv].flags & SUP_XV)) {
warnx("Xv adaptor %d can't display %s",
vid->xdsp.adaps[vid->xdsp.cur_adap].adap_index,
- encs[vid->enc].name);
+ encs[vid->enc_xv].name);
return 0;
}
- if (((vid->mode & M_IN_DEV) &&
- (encs[vid->enc].flags & SW_MASK) == SW_DEV) ||
- ((vid->mode & M_OUT_XV) &&
- (encs[vid->enc].flags & SW_MASK) == SW_XV))
- vid->conv_type = CONV_SWAP;
return 1;
}
@@ -1490,13 +1487,9 @@ setup(struct video *vid)
return 0;
}
- if (vid->conv_type) {
- if (vid->conv_type == CONV_SWAP) {
- vid->conv_bufsz = vid->bpf;
- } else {
- warnx("invalid conversion type");
- return 0;
- }
+ if (vid->conv_type > CONV_NONE) {
+ vid->conv_bufsz =
+ (vid->width * vid->height * encs[vid->enc_xv].bpp) / NBBY;
if ((vid->conv_buffer = calloc(1, vid->conv_bufsz)) == NULL) {
warn("conv_buffer");
return 0;
@@ -1633,6 +1626,37 @@ got_shutdown(int s)
}
int
+yuy2_to_yv12(uint8_t *src, uint8_t *dst, int width, int height, int swap)
+{
+ int row, col;
+ uint8_t *s = src, *p;
+ uint8_t *dy = dst;
+ uint8_t *du = dy + width * height;
+ uint8_t *dv = du + width * height / 4;
+
+ if ((width | height) & 1)
+ errx(1, "frame size %dx%d is not supported", width, height);
+
+ for (row = 0; row < height; row++) {
+ for (col = 0; col < width; col += 2) {
+ p = (swap) ? s + 1 : s;
+ *(dy++) = *p;
+ *(dy++) = *(p + 2);
+ if (!(row & 0x01)) {
+ p = (swap) ? s - 1 : s;
+ *(du++) =
+ (*(p + 3) + *(p + 3 + width * 2) + 1) / 2;
+ *(dv++) =
+ (*(p + 1) + *(p + 1 + width * 2) + 1) / 2;
+ }
+ s += 4;
+ }
+ }
+
+ return 0;
+}
+
+int
stream(struct video *vid)
{
struct xdsp *x = &vid->xdsp;
@@ -1719,10 +1743,12 @@ stream(struct video *vid)
play = 0;
src = vid->frame_buffer;
- if (vid->conv_type == CONV_SWAP) {
+ if (vid->conv_type & CONV_YV12)
+ yuy2_to_yv12(src, vid->conv_buffer,
+ vid->width, vid->height,
+ (vid->conv_type & CONV_SWAP));
+ else if (vid->conv_type & CONV_SWAP)
swab(src, vid->conv_buffer, vid->bpf);
- src = vid->conv_buffer;
- }
if ((vid->mode & M_OUT_FILE) && wout) {
done = 0;
@@ -1743,6 +1769,8 @@ stream(struct video *vid)
}
}
if (vid->mode & M_OUT_XV) {
+ src = (vid->conv_type > CONV_NONE) ?
+ vid->conv_buffer : vid->frame_buffer;
x->xv_image->data = src;
if (x->resized) {
x->resized = 0;
@@ -1863,6 +1891,7 @@ main(int argc, char *argv[])
vid.dev.fd = vid.iofile_fd = -1;
vid.mode = M_IN_DEV | M_OUT_XV;
vid.enc = -1;
+ vid.enc_xv = -1;
vid.mmap_on = 1; /* mmap method is default */
wout = 1;