--- /dev/null
+/*
+ Copyright (C) 2010 by Juan Carlos Torres <jucato@kdemail.net>
+
+ 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) version 3 or any later version
+ accepted by the membership of KDE e.V. (or its successor appro-
+ ved by the membership of KDE e.V.), which shall act as a proxy
+ defined in Section 14 of version 3 of the license.
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see http://www.gnu.org/licenses/.
+*/
+
+
+#include "camera.h"
+
+#include <QDBusConnection>
+#include <QDBusMetaType>
+#include <QDBusInterface>
+#include <QDebug>
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <linux/videodev2.h>
+
+/**
+ * This section sets up the necessary types and methods
+ * to create a D-Bus connection using Qt.
+ */
+
+#define HAL_CAM_SHUTTER_UDI "/org/freedesktop/Hal/devices/platform_cam_shutter"
+#define HAL_CAM_SHUTTER_STATE "button.state.value"
+
+Q_DECLARE_METATYPE(Property)
+Q_DECLARE_METATYPE(QList<Property>)
+
+const QDBusArgument & operator<<(QDBusArgument &arg, const Property &change)
+{
+ arg.beginStructure();
+ arg << change.name << change.added << change.removed;
+ arg.endStructure();
+ return arg;
+}
+const QDBusArgument & operator>>(const QDBusArgument &arg, Property &change)
+{
+ arg.beginStructure();
+ arg >> change.name >> change.added >> change.removed;
+ arg.endStructure();
+ return arg;
+}
+
+
+/**
+ * Camera class definition
+ */
+
+int Camera::m_fd = 0;
+char Camera::m_deviceName[15] = "";
+
+Camera::Camera(QObject* parent) : QObject(parent)
+{
+ m_buffers = NULL;
+ m_numBuffers = 0;
+}
+
+Camera::~Camera()
+{
+ if (m_fd != -1)
+ {
+ close();
+ }
+}
+
+/**
+ * Open and acquire a file descriptor for a file connected to
+ * the character device representing the camera, usually /dev/video0
+ */
+int Camera::open(char* device)
+{
+ struct stat st;
+
+ if (device == NULL)
+ {
+ qDebug() << "Device name not specified";
+ return Camera::GenericError;
+ }
+
+ memcpy(m_deviceName, device, sizeof(m_deviceName));
+
+ if (stat(m_deviceName, &st) == -1)
+ {
+ qDebug() << "Cannot identify device:" << m_deviceName;
+ return Camera::GenericError;
+ }
+
+ if (!S_ISCHR(st.st_mode))
+ {
+ qDebug() << "No such device: " << m_deviceName;
+ return Camera::GenericError;
+ }
+
+ m_fd = ::open(m_deviceName, O_RDWR | O_NONBLOCK, 0);
+
+ if (m_fd == -1)
+ {
+ qDebug() << "Cannot open device: " << m_deviceName;
+ return Camera::GenericError;
+ }
+
+ return m_fd;
+}
+
+int Camera::close()
+{
+ if (m_fd != -1)
+ {
+ if (::close(m_fd) == -1)
+ {
+ qDebug() << "Cannot close device: " << m_deviceName;
+ return Camera::GenericError;
+ }
+ }
+
+ m_fd = -1;
+
+ return Camera::NoError;
+}
+
+/**
+ * Initializes the camera by setting up the
+ * cropping rectangle, data format and memory buffers
+ *
+ * Based on @ref http://v4l2spec.bytesex.org/spec/capture-example.html
+ */
+int Camera::init()
+{
+ struct v4l2_cropcap cropcap;
+ struct v4l2_crop crop;
+ struct v4l2_format fmt;
+ struct v4l2_requestbuffers req;
+
+ // Set cropping rectangle
+ cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (ioctl(m_fd, VIDIOC_CROPCAP, &cropcap) != -1)
+ {
+ crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ crop.c = cropcap.defrect; // reset to default
+
+ if (ioctl(m_fd, VIDIOC_S_CROP, &crop) == -1)
+ {
+ qDebug() << "Unable to set cropping rectangle";
+ return Camera::GenericError;
+ }
+ }
+ else
+ {
+ qDebug() << "Unable to get crop capabilities";
+ return Camera::GenericError;
+ }
+
+ // Set data format
+ fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ fmt.fmt.pix.width = 640;
+ fmt.fmt.pix.height = 480;
+ fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
+ fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
+
+ if (ioctl(m_fd, VIDIOC_S_FMT, &fmt) == -1)
+ {
+ qDebug() << "Unable to set data format";
+ return Camera::GenericError;
+ }
+
+ // Set up memory mapped buffers
+ req.count = 4;
+ req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ req.memory = V4L2_MEMORY_MMAP;
+
+ if (ioctl(m_fd, VIDIOC_REQBUFS, &req) == -1)
+ {
+ qDebug() << "No memory mapping support";
+ return Camera::GenericError;
+ }
+
+ if (req.count < 2)
+ {
+ qDebug() << "Insufficient buffer memory on device: " << m_deviceName;
+ return Camera::GenericError;
+ }
+
+ m_buffers = static_cast<buffer*>(calloc(req.count, sizeof(*m_buffers)));
+
+ if (!m_buffers)
+ {
+ qDebug() << "Out of memory";
+ return Camera::GenericError;
+ }
+
+ for (m_numBuffers = 0; m_numBuffers < req.count; ++m_numBuffers)
+ {
+ struct v4l2_buffer buf;
+
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = m_numBuffers;
+
+ if (ioctl(m_fd, VIDIOC_QUERYBUF, &buf) == -1)
+ {
+ qDebug() << "Unable to query the status of buffer number: " << m_numBuffers;
+ return Camera::GenericError;
+ }
+
+ m_buffers[m_numBuffers].length = buf.length;
+ m_buffers[m_numBuffers].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
+ MAP_SHARED, m_fd, buf.m.offset);
+
+ if (m_buffers[m_numBuffers].start == MAP_FAILED)
+ {
+ qDebug() << "Unable to map memmory";
+ return Camera::GenericError;
+ }
+ }
+
+ return Camera::NoError;
+}
+
+int Camera::deinit()
+{
+ quint32 i;
+
+ // Unmap memory mapped buffers
+ if (m_numBuffers > 0)
+ {
+ for (i = 0; i < m_numBuffers; ++i)
+ {
+ if (munmap(m_buffers[i].start, m_buffers[i].length) == -1)
+ {
+ qDebug() << "Unable to unmap memory";
+ return Camera::GenericError;
+ }
+ }
+
+ m_numBuffers = 0;
+ }
+
+ // Free allocated memory
+ if (m_buffers)
+ {
+ free (m_buffers);
+ }
+
+ m_buffers = NULL;
+
+ return Camera::NoError;
+}
+
+int Camera::stream(bool stream)
+{
+ enum v4l2_buf_type type;
+
+ type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ // Start or stop streaming
+ if (stream)
+ {
+ if (ioctl(m_fd, VIDIOC_STREAMON, &type) == -1)
+ {
+ qDebug() << "Camera::stream: Unable to start streaming";
+ return Camera::GenericError;
+ }
+ }
+ else
+ {
+ if (ioctl(m_fd, VIDIOC_STREAMOFF, &type) == -1)
+ {
+ qDebug() << "Camera::stream: Unable to stop streaming";
+ return Camera::GenericError;
+ }
+ }
+
+ return Camera::NoError;
+}
+
+/**
+ * Strobes the camera flash ONCE only
+ */
+int Camera::strobe()
+{
+ struct v4l2_control ctrl;
+
+ ctrl.id = V4L2_CID_FLASH_STROBE;
+
+ if (ioctl(m_fd, VIDIOC_S_CTRL, &ctrl) == -1)
+ {
+ qDebug() << "Cannot set flash";
+ return Camera::GenericError;
+ }
+
+ return Camera::NoError;
+}
+
+/**
+ * Sets the LED intensity for strobe (flash) mode only.
+ *
+ * Formulas:
+ * code = (intensity - 35)/15
+ * intensity = (code * 15) + 35
+ *
+ * Range: 215mA to 500mA
+ *
+ * @param intensity Intensity in mA
+ */
+int Camera::setFlashIntensity(int intensity)
+{
+ struct v4l2_control ctrl;
+ int intensityCode = (intensity - 35)/15;
+
+ // Set intensity to safe limits
+ if (intensityCode > 31)
+ {
+ intensityCode = 31;
+ }
+ else if (intensityCode < 0)
+ {
+ intensityCode = 0;
+ }
+
+ // Set flash intensity
+ ctrl.id = V4L2_CID_FLASH_INTENSITY;
+ ctrl.value = intensityCode;
+ if (ioctl(m_fd, VIDIOC_S_CTRL, &ctrl) == -1)
+ {
+ qDebug() << "Cannot set intensity";
+ return Camera::GenericError;
+ }
+
+ if (intensity > 0)
+ {
+ stream(true);
+ }
+ else
+ {
+ stream(false);
+ }
+
+ return Camera::NoError;
+}
+
+int Camera::flashIntensity() const
+{
+ struct v4l2_control ctrl;
+
+ ctrl.id = V4L2_CID_FLASH_INTENSITY;
+
+ if (ioctl(m_fd, VIDIOC_G_CTRL, &ctrl) == -1) {
+ qDebug() << "Cannot get intensity";
+ return Camera::GenericError;
+ }
+
+ return 35 + (ctrl.value * 15);
+}
+
+/**
+ * Sets the flash timeout. Works only in strobe (flash) mode.
+ * Formulas:
+ * 1 sec = 1,000 ms = 1,000,000 us (microseconds)
+ *
+ * Range: 54,600us to 820,000us (54.6ms to 820ms)
+ *
+ * @param timeout Timeout in microseconds
+ */
+int Camera::setTimeout(quint32 timeout)
+{
+ struct v4l2_control ctrl;
+
+ // Set timeout to safe limits
+ if (timeout < 54600)
+ {
+ timeout = 54600;
+ }
+ else if (timeout > 820000)
+ {
+ timeout = 820000;
+ }
+
+ // Set flash timeout
+ ctrl.id = V4L2_CID_FLASH_TIMEOUT;
+ ctrl.value = timeout;
+
+ if (ioctl(m_fd, VIDIOC_S_CTRL, &ctrl) == -1)
+ {
+ qDebug() << "Cannot set timeout";
+ return Camera::GenericError;
+ }
+
+ return Camera::NoError;
+}
+
+quint32 Camera::timeout() const
+{
+ struct v4l2_control ctrl;
+
+ ctrl.id = V4L2_CID_FLASH_TIMEOUT;
+
+ if (ioctl(m_fd, VIDIOC_G_CTRL, &ctrl) == -1) {
+ qDebug() << "Cannot get timeout";
+ return Camera::GenericError;
+ }
+
+ return ctrl.value;
+}
+
+/**
+ * Sets up a D-Bus connection to listen for camera shutter
+ * state changes and calls the slot @ref shutterPropertyModified()
+ */
+void Camera::registerDbusWatcher()
+{
+ qDBusRegisterMetaType< Property >();
+ qDBusRegisterMetaType< QList<Property> >();
+
+ QDBusConnection::systemBus().connect(
+ QString(),
+ HAL_CAM_SHUTTER_UDI,
+ "org.freedesktop.Hal.Device",
+ "PropertyModified",
+ this,
+ SLOT(shutterPropertyModified(int, QList<Property>)));
+}
+
+/**
+ * Checks for the status of the camera shutter using D-Bus and HAL
+ */
+bool Camera::isShutterOpen() const
+{
+ QDBusInterface propertyInterface("org.freedesktop.Hal",
+ HAL_CAM_SHUTTER_UDI,
+ "org.freedesktop.Hal.Device",
+ QDBusConnection::systemBus());
+
+ bool isOpen = propertyInterface.call("GetProperty",
+ HAL_CAM_SHUTTER_STATE).arguments().at(0).toBool();
+
+ return !isOpen;
+}
+
+/**
+ * Called when camera shutter state changes. Checks for the
+ * actual state using @ref isShutterOpen() and emits the
+ * @ref shutterStateChanged() signal
+ */
+void Camera::shutterPropertyModified(int /*numUpdates*/, QList< Property > /*updates*/)
+{
+ bool isOpen = isShutterOpen();
+
+ emit shutterStateChanged(isOpen);
+}