Initial commit. corresponds to 1.0-1 release
[flashstrobe] / src / camera.cpp
diff --git a/src/camera.cpp b/src/camera.cpp
new file mode 100644 (file)
index 0000000..ec2652a
--- /dev/null
@@ -0,0 +1,468 @@
+/*
+  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);
+}