From nobody Wed Feb 11 05:28:55 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=bytedance.com ARC-Seal: i=1; a=rsa-sha256; t=1640615450; cv=none; d=zohomail.com; s=zohoarc; b=n6wS2u8N/tF4lNLgYRc1vlYdqDZZVb2gOgZoohYtKIl/Gwy8xis3iWHrWa/EKCHoBv2yHebxoNAAxmFFFgT1lO69pUiYgQqu3ukw1b1ZME9ln7XSuFXU+n6jOgaYPF31tmLeOrst1DsV49LMLGd9kNl3ltBxCP9nZxyOfJnfQO0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1640615450; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=6aSGfDjayQ1PrEwkX49lBg7Z2rZJio7adzqNVFRKOIQ=; b=b0Hx70A9ao5wWmeDOCvHiycCGaOfIIL7uPzm1i10F1Tev6a0Hy4/bzQFcC6ahHCVzbt7BA0Yv0noNn6oWdTnwh7vn7s1MdYE+AH0OXDzQvp8wtlAHAIAIc4QX30mqTvOGF6frV1wbB1L/ai6KUeKegiVtwzAr4ORFGhgQijmPDU= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1640615450305299.4550865023922; Mon, 27 Dec 2021 06:30:50 -0800 (PST) Received: from localhost ([::1]:40588 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1n1r1V-0004Vs-Ak for importer@patchew.org; Mon, 27 Dec 2021 09:30:49 -0500 Received: from eggs.gnu.org ([209.51.188.92]:54848) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1n1qzN-0001iz-WB for qemu-devel@nongnu.org; Mon, 27 Dec 2021 09:28:38 -0500 Received: from [2607:f8b0:4864:20::535] (port=44740 helo=mail-pg1-x535.google.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1n1qzL-000324-3K for qemu-devel@nongnu.org; Mon, 27 Dec 2021 09:28:37 -0500 Received: by mail-pg1-x535.google.com with SMTP id m15so13633874pgu.11 for ; Mon, 27 Dec 2021 06:28:34 -0800 (PST) Received: from libai.bytedance.net ([153.254.110.96]) by smtp.gmail.com with ESMTPSA id p2sm15072916pgn.73.2021.12.27.06.28.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 27 Dec 2021 06:28:33 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bytedance-com.20210112.gappssmtp.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=6aSGfDjayQ1PrEwkX49lBg7Z2rZJio7adzqNVFRKOIQ=; b=RMo/3WHGF7BlkNjif91Ij/qUq/2rI+C3SZiL03y0Ees8/f4WBRqvroK7kg47xrQ9ax 05BD0up95T/MK/wCVu9iA3fyQ5kFllNyCio2QNieHow/YSlMUuG4i6TcFLaKZcUYDKXf g6acM/JXDfBRfjzNuZoeoxptataP/4S5iOyCoz/E39dl9iTC9TtZQf8sRCzkZjpGANXo lAUepq8KPQAKCoNh3/RUcniByE57+5W9vs0EmsTClaOFV6/nVsKf8lUofQFiL9VrDf5I fvTlT8hGRNJS3zBtw9N8aD6rS9CxdkfdXoWgQwxVbJqDvkrqBZNUErPIfzgmUPjPQWvl LmRA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=6aSGfDjayQ1PrEwkX49lBg7Z2rZJio7adzqNVFRKOIQ=; b=wxIoRxg5nX2O9sCAexLIjkV9s7s9w831l9pAnev1en83iQ8KHbLT6//3StmATBwrQN eJoYEJUs6gVfdKUosVqrZaXQMmUHoZzoJbZLh7NrRqLWKrS0aK8C+nnHVDLq9h7RvYbP zrwaIM+ih+2H1OCkieKoWA0jtJgRZwIIkDGbPmx7s2Or78ixldR93+vCWO11pSXoBFud Pnw7u/c3gz8fEKqUDTrIWd62j0+EtYg9vCpOvAwXwkTddqNyb2xdtdYHC050g1Zg9Kiy 2qHgE+tGz22F4AsijriBNNK3m/usXin6kjimMvWBcN9rc6Y4czg0FBK9J60dnYOLlGO3 SGgw== X-Gm-Message-State: AOAM533ddvwEblOKAm/dYmS078VzIDS5aTb5rtSqfeQMSehb5c2OcOna xvvsZyBxZBk1CY5jkur/eEb2sw== X-Google-Smtp-Source: ABdhPJz4/viepMH3eu4xBtjWw1ZxaPQmSXMF/X+bSfTn+e+Ck111PTbEnqe0VvKsYCUXjm0eTHxmdg== X-Received: by 2002:a05:6a00:ac7:b0:4bb:d3b8:3faf with SMTP id c7-20020a056a000ac700b004bbd3b83fafmr7823716pfl.48.1640615313774; Mon, 27 Dec 2021 06:28:33 -0800 (PST) From: zhenwei pi To: peter.maydell@linaro.org, richard.henderson@linaro.org, kraxel@redhat.com, eblake@redhat.com, pbonzini@redhat.com Subject: [PATCH 2/5] camera: v4l2: Introduce v4l2 camera driver Date: Mon, 27 Dec 2021 22:27:31 +0800 Message-Id: <20211227142734.691900-3-pizhenwei@bytedance.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20211227142734.691900-1-pizhenwei@bytedance.com> References: <20211227142734.691900-1-pizhenwei@bytedance.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Host-Lookup-Failed: Reverse DNS lookup failed for 2607:f8b0:4864:20::535 (failed) Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::535; envelope-from=pizhenwei@bytedance.com; helo=mail-pg1-x535.google.com X-Spam_score_int: -10 X-Spam_score: -1.1 X-Spam_bar: - X-Spam_report: (-1.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RDNS_NONE=0.793, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: qemu-devel@nongnu.org, zhenwei pi Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: pass (identity @bytedance-com.20210112.gappssmtp.com) X-ZM-MESSAGEID: 1640615587247000001 Content-Type: text/plain; charset="utf-8" On a Linux platform, user process could accesses /dev/videoX to capture video frames. We can run QEMU like this: qemu-system-x86_64 ... -cameradev v4l2,path=3D/dev/video0,id=3Dcamera0 The basic logic of v4l2 driver: stream on -> qbuf -> dqbuf(drive by POLLIN event) -> qbuf -> dqbuf ... -> stream off Signed-off-by: zhenwei pi --- camera/meson.build | 4 + camera/trace-events | 4 + camera/v4l2.c | 637 ++++++++++++++++++++++++++++++++++++++++++++ qapi/camera.json | 21 +- qemu-options.hx | 3 + 5 files changed, 667 insertions(+), 2 deletions(-) create mode 100644 camera/v4l2.c diff --git a/camera/meson.build b/camera/meson.build index d50ee5ebf7..6e7aeb5ebd 100644 --- a/camera/meson.build +++ b/camera/meson.build @@ -2,6 +2,10 @@ camera_ss.add([files( 'camera.c', )]) =20 +camera_ss.add(when: 'CONFIG_LINUX', if_true: files( + 'v4l2.c', +)) + camera_modules =3D {} foreach m : [ ['builtin', cairo, files('builtin.c')], diff --git a/camera/trace-events b/camera/trace-events index 2f4d93e924..4527303d58 100644 --- a/camera/trace-events +++ b/camera/trace-events @@ -22,3 +22,7 @@ qemu_camera_enum_control_ret(const char *dev, int ret) "%= s: ret %d" =20 # builtin.c camera_builtin_timer(const char *dev) "%s: new image" + +# v4l2.c +camera_v4l2_qbuf(const char *dev, uint32_t index) "%s: qbuf index %u" +camera_v4l2_dqbuf(const char *dev, uint32_t index) "%s: qbuf index %u" diff --git a/camera/v4l2.c b/camera/v4l2.c new file mode 100644 index 0000000000..6425e0e1e9 --- /dev/null +++ b/camera/v4l2.c @@ -0,0 +1,637 @@ +/* + * V4L2 camera backend implemention + * + * Copyright 2021 Bytedance, Inc. + * + * Authors: + * zhenwei pi + * + * This work is licensed under the terms of the GNU GPL, version 2 or late= r. + * See the COPYING file in the top-level directory. + */ +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "qom/object.h" +#include "qapi/error.h" +#include "qapi/visitor.h" +#include "qapi/qapi-visit-camera.h" +#include "camera/camera.h" +#include "camera-int.h" +#include "trace.h" +#include +#include + +#define TYPE_CAMERA_V4L2 TYPE_CAMERADEV"-v4l2" + +#define CAMERA_V4L2_BUFFER_MAX 16 +#define CAMERA_V4L2_BUFFER_DEF 2 + +typedef struct CameraV4l2Buffer { + unsigned char *addr; + uint32_t length; +} CameraV4l2Buffer; + +typedef struct CameraV4l2 { + QEMUCamera parent; + + int devfd; + size_t sizeimage; + uint8_t nbuffers; + CameraV4l2Buffer buffers[CAMERA_V4L2_BUFFER_MAX]; +} CameraV4l2; + +DECLARE_INSTANCE_CHECKER(CameraV4l2, CAMERA_V4L2_DEV, TYPE_CAMERA_V4L2) + +typedef struct CameraV4l2Ctrl { + QEMUCameraControlType q; + uint32_t v; +} CameraV4l2Ctrl; + +static CameraV4l2Ctrl camera_v4l2_ctrl_table[] =3D { + { .q =3D QEMUCameraBrightness, + .v =3D V4L2_CID_BRIGHTNESS }, + { .q =3D QEMUCameraContrast, + .v =3D V4L2_CID_CONTRAST }, + { .q =3D QEMUCameraGain, + .v =3D V4L2_CID_GAIN }, + { .q =3D QEMUCameraGamma, + .v =3D V4L2_CID_GAMMA }, + { .q =3D QEMUCameraHue, + .v =3D V4L2_CID_HUE }, + { .q =3D QEMUCameraHueAuto, + .v =3D V4L2_CID_HUE_AUTO }, + { .q =3D QEMUCameraSaturation, + .v =3D V4L2_CID_SATURATION }, + { .q =3D QEMUCameraSharpness, + .v =3D V4L2_CID_SHARPNESS }, + { .q =3D QEMUCameraWhiteBalanceTemperature, + .v =3D V4L2_CID_WHITE_BALANCE_TEMPERATURE }, +}; + +static QEMUCameraControlType camera_v4l2_control_to_qemu(uint32_t id) +{ + CameraV4l2Ctrl *ctrl; + int i; + + for (i =3D 0; i < ARRAY_SIZE(camera_v4l2_ctrl_table); i++) { + ctrl =3D &camera_v4l2_ctrl_table[i]; + if (ctrl->v =3D=3D id) { + return ctrl->q; + } + } + + return QEMUCameraControlMax; +} + +static uint32_t camera_qemu_control_to_v4l2(QEMUCameraControlType type) +{ + CameraV4l2Ctrl *ctrl; + int i; + + for (i =3D 0; i < ARRAY_SIZE(camera_v4l2_ctrl_table); i++) { + ctrl =3D &camera_v4l2_ctrl_table[i]; + if (ctrl->q =3D=3D type) { + return ctrl->v; + } + } + + return 0; +} + +static int camera_v4l2_enum_pixel_format(QEMUCamera *camera, uint32_t *pix= fmts, + int npixfmt, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + struct v4l2_fmtdesc v4l2_fmt; + int index, total =3D 0; + + for (index =3D 0; total < npixfmt; index++) { + v4l2_fmt.index =3D index; + v4l2_fmt.type =3D V4L2_CAP_VIDEO_CAPTURE; + if (ioctl(v4l2->devfd, VIDIOC_ENUM_FMT, &v4l2_fmt) < 0) { + if (errno =3D=3D EINVAL) { + break; /* the last one */ + } + + error_setg(errp, "%s: enum fmt on device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + if (!qemu_camera_pixel_supported(v4l2_fmt.pixelformat)) { + continue; + } + + pixfmts[total++] =3D v4l2_fmt.pixelformat; + } + + return total; +} + +static int camera_v4l2_enum_frame_size(QEMUCamera *camera, uint32_t pixfmt, + QEMUCameraFrameSize *frmszs, int nf= rmsz, + Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + QEMUCameraFrameSize *frmsz; + struct v4l2_frmsizeenum v4l2_frmsz; + int index, total =3D 0; + + if (!qemu_camera_pixel_supported(pixfmt)) { + return -EINVAL; + } + + for (index =3D 0; total < nfrmsz; index++) { + v4l2_frmsz.index =3D index; + v4l2_frmsz.pixel_format =3D pixfmt; + if (ioctl(v4l2->devfd, VIDIOC_ENUM_FRAMESIZES, &v4l2_frmsz) < 0) { + if (errno =3D=3D EINVAL) { + break; /* the last one */ + } + + error_setg(errp, "%s: enum frame size device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + if (v4l2_frmsz.type !=3D V4L2_FRMSIZE_TYPE_DISCRETE) { + continue; /* TODO stepwise support in future*/ + } + + frmsz =3D frmszs + total++; + frmsz->pixel_format =3D v4l2_frmsz.pixel_format; + frmsz->type =3D QEMU_CAMERA_FRMSIZE_TYPE_DISCRETE; + frmsz->d.width =3D v4l2_frmsz.discrete.width; + frmsz->d.height =3D v4l2_frmsz.discrete.height; + } + + return total; +} + +static int camera_v4l2_enum_frame_interval(QEMUCamera *camera, + const QEMUCameraFormat *format, + QEMUCameraFrameInterval *frmiva= ls, + int nfrmival, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + QEMUCameraFrameInterval *frmival; + struct v4l2_frmivalenum v4l2_frmival; + int index, total =3D 0; + + for (index =3D 0; total < nfrmival; index++) { + v4l2_frmival.index =3D index; + v4l2_frmival.pixel_format =3D format->pixel_format; + v4l2_frmival.width =3D format->width; + v4l2_frmival.height =3D format->height; + if (ioctl(v4l2->devfd, VIDIOC_ENUM_FRAMEINTERVALS, &v4l2_frmival) = < 0) { + if (errno =3D=3D EINVAL) { + break; /* the last one */ + } + + error_setg(errp, "%s: enum frame intervals on device %s failed= , %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + if (v4l2_frmival.type !=3D V4L2_FRMIVAL_TYPE_DISCRETE) { + continue; /* TODO stepwise support in future*/ + } + + frmival =3D frmivals + total++; + frmival->pixel_format =3D v4l2_frmival.pixel_format; + frmival->type =3D QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE; + frmival->width =3D v4l2_frmival.width; + frmival->height =3D v4l2_frmival.height; + frmival->d.numerator =3D v4l2_frmival.discrete.numerator; + frmival->d.denominator =3D v4l2_frmival.discrete.denominator; + } + + return total; +} + +static int camera_v4l2_get_format(QEMUCamera *camera, QEMUCameraFormat *fo= rmat, + Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + struct v4l2_format v4l2_fmt =3D {.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE= }; + struct v4l2_pix_format *v4l2_pix =3D &v4l2_fmt.fmt.pix; + + if (ioctl(v4l2->devfd, VIDIOC_G_FMT, &v4l2_fmt) < 0) { + error_setg(errp, "%s: get fmt on device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + if (format) { + format->pixel_format =3D v4l2_pix->pixelformat; + format->width =3D v4l2_pix->width; + format->height =3D v4l2_pix->height; + } + + v4l2->sizeimage =3D v4l2_pix->sizeimage; + + return 0; +} + +static int camera_v4l2_set_format(QEMUCamera *camera, + const QEMUCameraFormat *format, Error **= errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + struct v4l2_format v4l2_fmt =3D {0}; + struct v4l2_pix_format *v4l2_pix; + + v4l2_fmt.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_pix =3D &v4l2_fmt.fmt.pix; + v4l2_pix->pixelformat =3D format->pixel_format; + v4l2_pix->width =3D format->width; + v4l2_pix->height =3D format->height; + if (ioctl(v4l2->devfd, VIDIOC_S_FMT, &v4l2_fmt) < 0) { + error_setg(errp, "%s: set fmt on device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + v4l2->sizeimage =3D v4l2_pix->sizeimage; + + return 0; +} + +static int camera_v4l2_set_frame_interval(QEMUCamera *camera, + const QEMUCameraFrameInterval *frmival, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + QEMUCameraFormat fmt; + struct v4l2_streamparm streamparm; + struct v4l2_captureparm *capture; + int ret; + + if (frmival->type !=3D QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE) { + error_setg(errp, "%s: only support discrete mode", TYPE_CAMERA_V4L= 2); + return -ENOTSUP; + } + + fmt.pixel_format =3D frmival->pixel_format; + fmt.width =3D frmival->width; + fmt.height =3D frmival->height; + ret =3D camera_v4l2_set_format(camera, &fmt, errp); + if (ret) { + return ret; + } + + streamparm.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + capture =3D &streamparm.parm.capture; + capture->timeperframe.numerator =3D frmival->d.numerator; + capture->timeperframe.denominator =3D frmival->d.denominator; + if (ioctl(v4l2->devfd, VIDIOC_S_PARM, &streamparm) < 0) { + error_setg(errp, "%s: set stream parm on device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + return 0; +} + +static int camera_v4l2_enum_control(QEMUCamera *camera, + QEMUCameraControl *controls, int ncontrols, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + QEMUCameraControl *control; + struct v4l2_queryctrl v4l2_ctrl =3D {0}; + QEMUCameraControlType type; + int index, total =3D 0; + + for (index =3D 0; total < ncontrols; index++) { + v4l2_ctrl.id |=3D V4L2_CTRL_FLAG_NEXT_CTRL; + if (ioctl(v4l2->devfd, VIDIOC_QUERYCTRL, &v4l2_ctrl) < 0) { + if (errno =3D=3D EINVAL) { + break; /* the last one */ + } + + error_setg(errp, "%s: enum control on device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + if (v4l2_ctrl.flags & V4L2_CTRL_FLAG_INACTIVE) { + continue; + } + + type =3D camera_v4l2_control_to_qemu(v4l2_ctrl.id); + if (type =3D=3D QEMUCameraControlMax) { + continue; + } + + control =3D controls + total++; + control->type =3D type; + control->def =3D v4l2_ctrl.default_value; + control->min =3D v4l2_ctrl.minimum; + control->max =3D v4l2_ctrl.maximum; + control->step =3D v4l2_ctrl.step; + } + + return total; +} + +static int camera_v4l2_set_control(QEMUCamera *camera, + const QEMUCameraControl *control, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + struct v4l2_control v4l2_ctrl; + uint32_t cid; + + cid =3D camera_qemu_control_to_v4l2(control->type); + if (!cid) { + error_setg(errp, "%s: unsupported control type %d", + TYPE_CAMERA_V4L2, control->type); + return -EINVAL; + } + + v4l2_ctrl.id =3D cid; + v4l2_ctrl.value =3D control->cur; + if (ioctl(v4l2->devfd, VIDIOC_S_CTRL, &v4l2_ctrl) < 0) { + error_setg(errp, "%s: set ctrl on device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + return 0; +} + +static int camera_v4l2_qbuf(QEMUCamera *camera, int index) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + struct v4l2_buffer buffer =3D {0}; + + trace_camera_v4l2_qbuf(qemu_camera_id(camera), index); + + buffer.index =3D index; + buffer.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.field =3D V4L2_FIELD_ANY; + buffer.memory =3D V4L2_MEMORY_MMAP; + + return ioctl(v4l2->devfd, VIDIOC_QBUF, &buffer); +} + +static int camera_v4l2_dqbuf(QEMUCamera *camera) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + struct v4l2_buffer buffer; + + buffer.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory =3D V4L2_MEMORY_MMAP; + + if (ioctl(v4l2->devfd, VIDIOC_DQBUF, &buffer) < 0) { + return -errno; + } + + trace_camera_v4l2_dqbuf(qemu_camera_id(camera), buffer.index); + + return buffer.index; +} + +static void camera_v4l2_free_buffers(QEMUCamera *camera) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + struct v4l2_requestbuffers v4l2_reqbufs =3D {0}; + CameraV4l2Buffer *buffer; + int index; + + /* 1, try to dequeue all buffers */ + for (index =3D 0; index < v4l2->nbuffers; index++) { + camera_v4l2_dqbuf(camera); + } + + /* 2, try to unmap all buffers */ + for (index =3D 0; index < v4l2->nbuffers; index++) { + buffer =3D &v4l2->buffers[index]; + if (buffer->addr) { + munmap(buffer->addr, buffer->length); + buffer->addr =3D NULL; + buffer->length =3D 0; + } + } + + /* 3, free all the v4l2 reqbufs */ + v4l2_reqbufs.count =3D 0; + v4l2_reqbufs.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_reqbufs.memory =3D V4L2_MEMORY_MMAP; + ioctl(v4l2->devfd, VIDIOC_REQBUFS, &v4l2_reqbufs); +} + +static int camera_v4l2_request_buffers(QEMUCamera *camera, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + struct v4l2_requestbuffers v4l2_reqbufs =3D {0}; + struct v4l2_buffer v4l2_buf; + CameraV4l2Buffer *buffer; + void *addr; + int index; + + v4l2_reqbufs.count =3D v4l2->nbuffers; + v4l2_reqbufs.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_reqbufs.memory =3D V4L2_MEMORY_MMAP; + if (ioctl(v4l2->devfd, VIDIOC_REQBUFS, &v4l2_reqbufs) < 0) { + return -errno; + } + + for (index =3D 0; index < v4l2->nbuffers; index++) { + v4l2_buf.index =3D index; + v4l2_buf.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_buf.memory =3D V4L2_MEMORY_MMAP; + v4l2_buf.length =3D 0; + if (ioctl(v4l2->devfd, VIDIOC_QUERYBUF, &v4l2_buf) < 0) { + goto error; + } + + if (v4l2_buf.type !=3D V4L2_BUF_TYPE_VIDEO_CAPTURE) { + continue; /* TODO V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE support */ + } + + addr =3D mmap(NULL, v4l2_buf.length, PROT_READ | PROT_WRITE, MAP_S= HARED, + v4l2->devfd, v4l2_buf.m.offset); + if (addr =3D=3D MAP_FAILED) { + goto error; + } + + if (camera_v4l2_qbuf(camera, index) < 0) { + goto error; + } + + buffer =3D &v4l2->buffers[index]; + buffer->addr =3D addr; + buffer->length =3D v4l2_buf.length; + } + + return 0; + +error: + camera_v4l2_free_buffers(camera); + + return -errno; +} + +static void camera_v4l2_open(QEMUCamera *camera, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + struct v4l2_capability v4l2_cap =3D {0}; + + if (v4l2opts->has_buffers) { + if (!v4l2opts->buffers || v4l2opts->buffers > CAMERA_V4L2_BUFFER_M= AX) { + error_setg(errp, "%s: zero buffers or too large(max %d)", + TYPE_CAMERA_V4L2, CAMERA_V4L2_BUFFER_MAX); + return; + } + + v4l2->nbuffers =3D v4l2opts->buffers; + } else { + v4l2->nbuffers =3D CAMERA_V4L2_BUFFER_DEF; + } + + if (!v4l2opts->has_path) { + error_setg(errp, "%s: missing device path", TYPE_CAMERA_V4L2); + return; + } + + v4l2->devfd =3D open(v4l2opts->path, O_RDWR | O_NONBLOCK); + if (v4l2->devfd =3D=3D -1) { + error_setg(errp, "%s: open device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return; + } + + if (ioctl(v4l2->devfd, VIDIOC_QUERYCAP, &v4l2_cap) < 0) { + error_setg(errp, "%s: query device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + goto error; + } + + if (!(v4l2_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) || + !(v4l2_cap.device_caps & V4L2_CAP_VIDEO_CAPTURE)) { + error_setg(errp, "%s: %s is not a video capture device", + TYPE_CAMERA_V4L2, v4l2opts->path); + goto error; + } + + if (camera_v4l2_get_format(camera, NULL, errp) < 0) { + goto error; + } + + return; + +error: + if (v4l2->devfd > 0) { + close(v4l2->devfd); + } +} + +static void camera_v4l2_read_handler(void *opaque) +{ + QEMUCamera *camera =3D (QEMUCamera *)opaque; + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Buffer *buffer; + int index; + + index =3D camera_v4l2_dqbuf(camera); + if (index < 0) { + return; + } + + buffer =3D &v4l2->buffers[index]; + qemu_camera_new_image(camera, buffer->addr, buffer->length); + + camera_v4l2_qbuf(camera, index); +} + +static void camera_v4l2_stream_on(QEMUCamera *camera, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + int type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + + qemu_camera_alloc_image(camera, v4l2->sizeimage, errp); + + if (camera_v4l2_request_buffers(camera, errp)) { + return; + } + + if (ioctl(v4l2->devfd, VIDIOC_STREAMON, &type) < 0) { + error_setg(errp, "%s: stream on failed on %s", + TYPE_CAMERA_V4L2, v4l2opts->path); + camera_v4l2_free_buffers(camera); + return; + } + + qemu_set_fd_handler(v4l2->devfd, camera_v4l2_read_handler, NULL, camer= a); +} + +static void camera_v4l2_stream_off(QEMUCamera *camera, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + int type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + + qemu_set_fd_handler(v4l2->devfd, NULL, NULL, camera); + + if (ioctl(v4l2->devfd, VIDIOC_STREAMOFF, &type) < 0) { + error_setg(errp, "%s: stream off failed on %s", + TYPE_CAMERA_V4L2, v4l2opts->path); + } + + camera_v4l2_free_buffers(camera); + + qemu_camera_free_image(camera); +} + +static void camera_v4l2_init(Object *obj) +{ +} + +static void camera_v4l2_finalize(Object *obj) +{ + QEMUCamera *camera =3D CAMERADEV(obj); + Error *local_err =3D NULL; + + camera_v4l2_stream_off(camera, &local_err); +} + +static void camera_v4l2_class_init(ObjectClass *oc, void *data) +{ + QEMUCameraClass *klass =3D CAMERADEV_CLASS(oc); + + klass->open =3D camera_v4l2_open; + klass->stream_on =3D camera_v4l2_stream_on; + klass->stream_off =3D camera_v4l2_stream_off; + klass->enum_pixel_format =3D camera_v4l2_enum_pixel_format; + klass->enum_frame_size =3D camera_v4l2_enum_frame_size; + klass->enum_frame_interval =3D camera_v4l2_enum_frame_interval; + klass->set_frame_interval =3D camera_v4l2_set_frame_interval; + klass->enum_control =3D camera_v4l2_enum_control; + klass->set_control =3D camera_v4l2_set_control; +} + +static const TypeInfo camera_v4l2_type_info =3D { + .name =3D TYPE_CAMERA_V4L2, + .parent =3D TYPE_CAMERADEV, + .instance_size =3D sizeof(CameraV4l2), + .instance_init =3D camera_v4l2_init, + .instance_finalize =3D camera_v4l2_finalize, + .class_init =3D camera_v4l2_class_init, +}; + +static void register_types(void) +{ + type_register_static(&camera_v4l2_type_info); +} + +type_init(register_types); diff --git a/qapi/camera.json b/qapi/camera.json index 2c8314ba4a..763f7f0c57 100644 --- a/qapi/camera.json +++ b/qapi/camera.json @@ -9,6 +9,22 @@ # =3D Camera ## =20 +## +# @CameraV4l2Options: +# +# Options of the v4l2 camera. +# +# @path: video capture device path +# +# @buffers: buffer count of v4l2 driver +# +# Since: 6.3 +## +{ 'struct': 'CameraV4l2Options', + 'data': { + '*path': 'str', + '*buffers': 'uint32' } } + ## # @ColorType: # @@ -62,7 +78,7 @@ # Since: 6.3 ## { 'enum': 'CameradevDriver', - 'data': [ 'builtin' ] } + 'data': [ 'builtin', 'v4l2' ] } =20 ## # @Cameradev: @@ -81,4 +97,5 @@ 'driver': 'CameradevDriver'}, 'discriminator': 'driver', 'data': { - 'builtin': 'CameraBuiltinOptions' } } + 'builtin': 'CameraBuiltinOptions', + 'v4l2': 'CameraV4l2Options' } } diff --git a/qemu-options.hx b/qemu-options.hx index fa439134dc..60975d6c3d 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -3535,6 +3535,9 @@ DEF("cameradev", HAS_ARG, QEMU_OPTION_cameradev, "-cameradev help\n" #ifdef CONFIG_CAIRO "-cameradev builtin,id=3Did[,debug=3Dtrue|false][,fps=3DFPS][,width=3D= WIDTH][,height=3DHEIGHT][,mjpeg=3Dtrue|false][,yuv=3Dtrue|false][,rgb565=3D= true|false][,bgcolor=3Dblue|gree|red|rainbow|digital-rain]\n" +#endif +#ifdef CONFIG_LINUX + "-cameradev v4l2,id=3Did[,path=3DPATH][,buffers=3DCOUNT]\n" #endif , QEMU_ARCH_ALL ) --=20 2.25.1