summaryrefslogtreecommitdiff
path: root/noncore/multimedia/camera2/videocaptureview.cpp
Side-by-side diff
Diffstat (limited to 'noncore/multimedia/camera2/videocaptureview.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r--noncore/multimedia/camera2/videocaptureview.cpp599
1 files changed, 599 insertions, 0 deletions
diff --git a/noncore/multimedia/camera2/videocaptureview.cpp b/noncore/multimedia/camera2/videocaptureview.cpp
new file mode 100644
index 0000000..410634a
--- a/dev/null
+++ b/noncore/multimedia/camera2/videocaptureview.cpp
@@ -0,0 +1,599 @@
+/**********************************************************************
+** Copyright (C) 2000-2006 Trolltech AS. All rights reserved.
+**
+** This file is part of the Qtopia Environment.
+**
+** This program is free software; you can redistribute it and/or modify it
+** under the terms of the GNU General Public License as published by the
+** Free Software Foundation; either version 2 of the License, or (at your
+** option) any later version.
+**
+** A copy of the GNU GPL license version 2 is included in this package as
+** LICENSE.GPL.
+**
+** This program is distributed in the hope that it will be useful, but
+** WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+** See the GNU General Public License for more details.
+**
+** In addition, as a special exception Trolltech gives permission to link
+** the code of this program with Qtopia applications copyrighted, developed
+** and distributed by Trolltech under the terms of the Qtopia Personal Use
+** License Agreement. You must comply with the GNU General Public License
+** in all respects for all of the code used other than the applications
+** licensed under the Qtopia Personal Use License Agreement. If you modify
+** this file, you may extend this exception to your version of the file,
+** but you are not obligated to do so. If you do not wish to do so, delete
+** this exception statement from your version.
+**
+** See http://www.trolltech.com/gpl/ for GPL licensing information.
+**
+** Contact info@trolltech.com if any conditions of this licensing are
+** not clear to you.
+**
+**********************************************************************/
+
+#include "videocaptureview.h"
+#include <qimage.h>
+#include <qpainter.h>
+#ifdef Q_WS_QWS
+#include <qgfx_qws.h>
+#include <qdirectpainter_qws.h>
+#endif
+
+#ifdef __linux__
+#define HAVE_VIDEO4LINUX 1
+#endif
+
+#ifdef HAVE_VIDEO4LINUX
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <linux/videodev.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+
+#endif /* HAVE_VIDEO4LINUX */
+
+class VideoCapture {
+ public:
+ VideoCapture();
+ ~VideoCapture();
+
+ bool hasCamera() const;
+ void getCameraImage(QImage & img, bool copy = FALSE);
+
+ QValueList < QSize > photoSizes() const;
+ QValueList < QSize > videoSizes() const;
+
+ QSize recommendedPhotoSize() const;
+ QSize recommendedVideoSize() const;
+ QSize recommendedPreviewSize() const;
+
+ QSize captureSize() const;
+ void setCaptureSize(QSize size);
+
+ uint refocusDelay() const;
+ int minimumFramePeriod() const;
+
+ private:
+#ifdef HAVE_VIDEO4LINUX
+ int fd;
+ int width, height;
+ struct video_capability caps;
+ struct video_mbuf mbuf;
+ unsigned char *frames;
+ int currentFrame;
+
+ void setupCamera(QSize size);
+ void shutdown();
+#endif
+};
+
+#ifdef HAVE_VIDEO4LINUX
+
+#define VIDEO_DEVICE "/dev/video"
+
+bool VideoCapture::hasCamera() const
+{
+ return (fd != -1);
+}
+
+QSize VideoCapture::captureSize() const
+{
+ return QSize(width, height);
+}
+
+uint VideoCapture::refocusDelay() const
+{
+ return 250;
+}
+
+int VideoCapture::minimumFramePeriod() const
+{
+ return 40; // milliseconds
+}
+
+VideoCapture::VideoCapture()
+{
+ setupCamera(QSize(0, 0));
+}
+
+VideoCapture::~VideoCapture()
+{
+ shutdown();
+}
+
+void VideoCapture::setupCamera(QSize size)
+{
+ qWarning(" VideoCapture::setupCamera");
+ // Clear important variables.
+ frames = 0;
+ currentFrame = 0;
+ width = 640;
+ height = 480;
+ caps.minwidth = width;
+ caps.minheight = height;
+ caps.maxwidth = width;
+ caps.maxheight = height;
+
+ // Open the video device.
+ fd = open(VIDEO_DEVICE, O_RDWR);
+ if (fd == -1) {
+ qWarning("%s: %s", VIDEO_DEVICE, strerror(errno));
+ return;
+ }
+
+ // Get the device's current capabilities.
+ memset(&caps, 0, sizeof(caps));
+ if (ioctl(fd, VIDIOCGCAP, &caps) < 0) {
+ qWarning("%s: could not retrieve the video capabilities", VIDEO_DEVICE);
+ close(fd);
+ fd = -1;
+ return;
+ }
+
+ // Change the channel to the first-connected camera, skipping TV inputs.
+ // If there are multiple cameras, this may need to be modified.
+ int chan;
+ struct video_channel chanInfo;
+ qWarning("available video capture inputs:");
+ for (chan = 0; chan < caps.channels; ++chan) {
+ chanInfo.channel = chan;
+ if (ioctl(fd, VIDIOCGCHAN, &chanInfo) >= 0) {
+ if (chanInfo.type == VIDEO_TYPE_CAMERA)
+ qWarning(" %s (camera)", chanInfo.name);
+ else if (chanInfo.type == VIDEO_TYPE_TV)
+ qWarning(" %s (tv)", chanInfo.name);
+ else
+ qWarning(" %s (unknown)", chanInfo.name);
+ }
+ }
+ for (chan = 0; chan < caps.channels; ++chan) {
+ chanInfo.channel = chan;
+ if (ioctl(fd, VIDIOCGCHAN, &chanInfo) >= 0) {
+ if (chanInfo.type == VIDEO_TYPE_CAMERA) {
+ qWarning("selecting camera on input %s", chanInfo.name);
+ if (ioctl(fd, VIDIOCSCHAN, &chan) < 0) {
+ qWarning("%s: could not set the channel", VIDEO_DEVICE);
+ }
+ break;
+ }
+ }
+ }
+
+ // Set the desired picture mode to RGB32.
+ struct video_picture pict;
+ memset(&pict, 0, sizeof(pict));
+ ioctl(fd, VIDIOCGPICT, &pict);
+ pict.palette = VIDEO_PALETTE_RGB32;
+ if (ioctl(fd, VIDIOCSPICT, &pict) < 0) {
+ qWarning("%s: could not set the picture mode", VIDEO_DEVICE);
+ close(fd);
+ fd = -1;
+ return;
+ }
+
+ // Determine the capture size to use. Zero indicates "preview mode".
+ if (size.width() == 0) {
+ size = QSize(caps.minwidth, caps.minheight);
+ }
+
+ // Get the current capture window.
+ struct video_window wind;
+ memset(&wind, 0, sizeof(wind));
+ ioctl(fd, VIDIOCGWIN, &wind);
+
+ // Adjust the capture size to match the camera's aspect ratio.
+ if (caps.maxwidth > 0 && caps.maxheight > 0) {
+ if (size.width() > size.height()) {
+ size = QSize(size.height() * caps.maxwidth / caps.maxheight, size.height());
+ }
+ else {
+ size = QSize(size.width(), size.width() * caps.maxheight / caps.maxwidth);
+ }
+ }
+
+ // Set the new capture window.
+ wind.x = 0;
+ wind.y = 0;
+ wind.width = size.width();
+ wind.height = size.height();
+ if (ioctl(fd, VIDIOCSWIN, &wind) < 0) {
+ qWarning("%s: could not set the capture window", VIDEO_DEVICE);
+ }
+
+ // Re-read the capture window, to see what it was adjusted to.
+ ioctl(fd, VIDIOCGWIN, &wind);
+ width = wind.width;
+ height = wind.height;
+
+ // Enable mmap-based access to the camera.
+ memset(&mbuf, 0, sizeof(mbuf));
+ if (ioctl(fd, VIDIOCGMBUF, &mbuf) < 0) {
+ qWarning("%s: mmap-based camera access is not available", VIDEO_DEVICE);
+ close(fd);
+ fd = -1;
+ return;
+ }
+
+ // Mmap the designated memory region.
+ frames = (unsigned char *) mmap(0, mbuf.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (!frames || frames == (unsigned char *) (long) (-1)) {
+ qWarning("%s: could not mmap the device", VIDEO_DEVICE);
+ close(fd);
+ fd = -1;
+ return;
+ }
+
+ // Start capturing of the first frame.
+ struct video_mmap capture;
+ currentFrame = 0;
+ capture.frame = currentFrame;
+ capture.width = width;
+ capture.height = height;
+ capture.format = VIDEO_PALETTE_RGB32;
+ ioctl(fd, VIDIOCMCAPTURE, &capture);
+}
+
+void VideoCapture::shutdown()
+{
+ if (frames != 0) {
+ munmap(frames, mbuf.size);
+ frames = 0;
+ }
+ if (fd != -1) {
+ int flag = 0;
+ ioctl(fd, VIDIOCSYNC, 0);
+ ioctl(fd, VIDIOCCAPTURE, &flag);
+ close(fd);
+ fd = -1;
+ }
+}
+
+void VideoCapture::getCameraImage(QImage & img, bool copy)
+{
+ if (fd == -1) {
+ if (img.isNull()) {
+ img.create(width, height, 32);
+ }
+ return;
+ }
+
+ // Start capturing the next frame (we alternate between 0 and 1).
+ int frame = currentFrame;
+ struct video_mmap capture;
+ if (mbuf.frames > 1) {
+ currentFrame = !currentFrame;
+ capture.frame = currentFrame;
+ capture.width = width;
+ capture.height = height;
+ capture.format = VIDEO_PALETTE_RGB32;
+ ioctl(fd, VIDIOCMCAPTURE, &capture);
+ }
+
+ // Wait for the current frame to complete.
+ ioctl(fd, VIDIOCSYNC, &frame);
+
+ // Create an image that refers directly to the kernel's
+ // frame buffer, to avoid having to copy the data.
+ if (!copy) {
+ img = QImage(frames + mbuf.offsets[frame], width, height, 32, 0, 0, QImage::IgnoreEndian);
+ }
+ else {
+ img.create(width, height, 32);
+ memcpy(img.bits(), frames + mbuf.offsets[frame], width * height * 4);
+ }
+
+ // Queue up another frame if the device only supports one at a time.
+ if (mbuf.frames <= 1) {
+ capture.frame = currentFrame;
+ capture.width = width;
+ capture.height = height;
+ capture.format = VIDEO_PALETTE_RGB32;
+ ioctl(fd, VIDIOCMCAPTURE, &capture);
+ }
+}
+
+QValueList < QSize > VideoCapture::photoSizes() const
+{
+ QValueList < QSize > list;
+ list.append(QSize(caps.maxwidth, caps.maxheight));
+ if (caps.maxwidth != caps.minwidth || caps.maxheight != caps.minheight)
+ list.append(QSize(caps.minwidth, caps.minheight));
+ return list;
+}
+
+QValueList < QSize > VideoCapture::videoSizes() const
+{
+ // We use the same sizes for both.
+ return photoSizes();
+}
+
+QSize VideoCapture::recommendedPhotoSize() const
+{
+ return QSize(caps.maxwidth, caps.maxheight);
+}
+
+QSize VideoCapture::recommendedVideoSize() const
+{
+ return QSize(caps.minwidth, caps.minheight);
+}
+
+QSize VideoCapture::recommendedPreviewSize() const
+{
+ return QSize(caps.minwidth, caps.minheight);
+}
+
+void VideoCapture::setCaptureSize(QSize size)
+{
+ if (size.width() != width || size.height() != height) {
+ shutdown();
+ setupCamera(size);
+ }
+}
+
+#else /* !HAVE_VIDEO4LINUX */
+
+// Dummy implementation for systems without video.
+
+VideoCapture::VideoCapture()
+{
+}
+
+VideoCapture::~VideoCapture()
+{
+}
+
+bool VideoCapture::hasCamera() const
+{
+ return TRUE;
+}
+
+QSize VideoCapture::captureSize() const
+{
+ return QSize(640, 480);
+}
+
+uint VideoCapture::refocusDelay() const
+{
+ return 0;
+}
+
+int VideoCapture::minimumFramePeriod() const
+{
+ return 100;
+}
+
+static unsigned int nextrand()
+{
+#define A 16807
+#define M 2147483647
+#define Q 127773
+#define R 2836
+ static unsigned int rnd = 1;
+ unsigned long hi = rnd / Q;
+ unsigned long lo = rnd % Q;
+ unsigned long test = A * lo - R * hi;
+ if (test > 0)
+ rnd = test;
+ else
+ rnd = test + M;
+ return rnd;
+}
+
+void VideoCapture::getCameraImage(QImage & img, bool)
+{
+ // Just generate something dynamic (rectangles)
+ static QImage cimg;
+ int x, y, w, h;
+ if (cimg.isNull()) {
+ x = y = 0;
+ w = 640;
+ h = 480;
+ cimg.create(w, h, 32);
+ }
+ else {
+ w = nextrand() % (cimg.width() - 10) + 10;
+ h = nextrand() % (cimg.height() - 10) + 10;
+ x = nextrand() % (cimg.width() - w);
+ y = nextrand() % (cimg.height() - h);
+ }
+ QRgb c = qRgb(nextrand() % 255, nextrand() % 255, nextrand() % 255);
+ for (int j = 0; j < h; j++) {
+ QRgb *l = (QRgb *) cimg.scanLine(y + j) + x;
+ for (int i = 0; i < w; i++)
+ l[i] = c;
+ }
+ img = cimg;
+}
+
+QValueList < QSize > VideoCapture::photoSizes() constconst
+{
+ QValueList < QSize > list;
+ list.append(QSize(640, 480));
+ list.append(QSize(320, 240));
+ return list;
+}
+
+QValueList < QSize > VideoCapture::videoSizes() constconst
+{
+ QValueList < QSize > list;
+ list.append(QSize(640, 480));
+ list.append(QSize(320, 240));
+ return list;
+}
+
+QSize VideoCapture::recommendedPhotoSize() const
+{
+ return QSize(640, 480);
+}
+
+QSize VideoCapture::recommendedVideoSize() const
+{
+ return QSize(320, 240);
+}
+
+QSize VideoCapture::recommendedPreviewSize() const
+{
+ return QSize(320, 240);
+}
+
+void VideoCapture::setCaptureSize(QSize size)
+{
+}
+
+#endif /* !HAVE_VIDEO4LINUX */
+
+VideoCaptureView::VideoCaptureView(QWidget * parent, const char *name, WFlags fl):QWidget(parent,
+ name, fl)
+{
+ capture = new VideoCapture();
+ QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ setSizePolicy(sp);
+ tid_update = 0;
+ setLive();
+}
+
+VideoCaptureView::~VideoCaptureView()
+{
+ delete capture;
+}
+
+void VideoCaptureView::setLive(int period)
+{
+ if (tid_update)
+ killTimer(tid_update);
+ if (period == 0)
+ tid_update = startTimer(capture->minimumFramePeriod());
+ else if (period > 0)
+ tid_update = startTimer(period);
+ else
+ tid_update = 0;
+}
+
+void VideoCaptureView::setStill(const QImage & i)
+{
+ setLive(-1);
+ img = i;
+ repaint(TRUE);
+}
+
+QValueList < QSize > VideoCaptureView::photoSizes() const
+{
+ return capture->photoSizes();
+}
+
+QValueList < QSize > VideoCaptureView::videoSizes() const
+{
+ return capture->videoSizes();
+}
+
+QSize VideoCaptureView::recommendedPhotoSize() const
+{
+ return capture->recommendedPhotoSize();
+}
+
+QSize VideoCaptureView::recommendedVideoSize() const
+{
+ return capture->recommendedVideoSize();
+}
+
+QSize VideoCaptureView::recommendedPreviewSize() const
+{
+ return capture->recommendedPreviewSize();
+}
+
+QSize VideoCaptureView::captureSize() const
+{
+ return capture->captureSize();
+}
+
+void VideoCaptureView::setCaptureSize(QSize size)
+{
+ capture->setCaptureSize(size);
+}
+
+uint VideoCaptureView::refocusDelay() const
+{
+ return capture->refocusDelay();
+}
+
+bool VideoCaptureView::available() const
+{
+ return capture->hasCamera();
+}
+
+void VideoCaptureView::paintEvent(QPaintEvent *)
+{
+ if (tid_update && !capture->hasCamera()) {
+ QPainter p(this);
+ p.drawText(rect(), AlignCenter, tr("No Camera"));
+ return;
+ }
+
+ if (tid_update)
+ capture->getCameraImage(img);
+ int w = img.width();
+ int h = img.height();
+
+ if (!w || !h)
+ return;
+
+ if (width() * w > height() * h) {
+ w = w * height() / h;
+ h = height();
+ }
+ else {
+ h = h * width() / w;
+ w = width();
+ }
+
+ if (qt_screen->transformOrientation() == 0) {
+ // Stretch and draw the image.
+ QDirectPainter p(this);
+ QGfx *gfx = p.internalGfx();
+ if (gfx) {
+ gfx->setSource(&img);
+ gfx->setAlphaType(QGfx::IgnoreAlpha);
+ gfx->stretchBlt((width() - w) / 2, (height() - h) / 2, w, h, img.width(), img.height());
+ }
+ }
+ else {
+ // This code is nowhere near efficient enough (hence the above).
+ // TODO - handle rotations during direct painting.
+ QImage scimg = img.smoothScale(w, h);
+ QPainter p(this);
+ p.drawImage((width() - w) / 2, (height() - h) / 2, scimg);
+ }
+}
+
+void VideoCaptureView::timerEvent(QTimerEvent *)
+{
+ repaint(FALSE);
+}
+