Initial implementation
authorMohammed Sameer <msameer@foolab.org>
Sun, 26 Aug 2012 06:49:17 +0000 (09:49 +0300)
committerMohammed Sameer <msameer@foolab.org>
Thu, 6 Sep 2012 16:10:09 +0000 (19:10 +0300)
38 files changed:
lib/lib.pro [new file with mode: 0644]
lib/main.cpp [new file with mode: 0644]
lib/main.h [new file with mode: 0644]
lib/qtcamconfig.cpp [new file with mode: 0644]
lib/qtcamconfig.h [new file with mode: 0644]
lib/qtcamdevice.cpp [new file with mode: 0644]
lib/qtcamdevice.h [new file with mode: 0644]
lib/qtcamdevice_p.h [new file with mode: 0644]
lib/qtcamera.cpp [new file with mode: 0644]
lib/qtcamera.h [new file with mode: 0644]
lib/qtcamgraphicsviewfinder.cpp [new file with mode: 0644]
lib/qtcamgraphicsviewfinder.h [new file with mode: 0644]
lib/qtcamgstreamermessagehandler.cpp [new file with mode: 0644]
lib/qtcamgstreamermessagehandler.h [new file with mode: 0644]
lib/qtcamgstreamermessagelistener.cpp [new file with mode: 0644]
lib/qtcamgstreamermessagelistener.h [new file with mode: 0644]
lib/qtcamimagemode.cpp [new file with mode: 0644]
lib/qtcamimagemode.h [new file with mode: 0644]
lib/qtcamimagesettings.cpp [new file with mode: 0644]
lib/qtcamimagesettings.h [new file with mode: 0644]
lib/qtcammetadata.cpp [new file with mode: 0644]
lib/qtcammetadata.h [new file with mode: 0644]
lib/qtcammode.cpp [new file with mode: 0644]
lib/qtcammode.h [new file with mode: 0644]
lib/qtcammode_p.h [new file with mode: 0644]
lib/qtcampreviewhelper.h [new file with mode: 0644]
lib/qtcamscanner.cpp [new file with mode: 0644]
lib/qtcamscanner.h [new file with mode: 0644]
lib/qtcamvideomode.cpp [new file with mode: 0644]
lib/qtcamvideomode.h [new file with mode: 0644]
lib/qtcamvideosettings.cpp [new file with mode: 0644]
lib/qtcamvideosettings.h [new file with mode: 0644]
lib/qtcamviewfinder.cpp [new file with mode: 0644]
lib/qtcamviewfinder.h [new file with mode: 0644]
lib/qtcamviewfinderrenderer.cpp [new file with mode: 0644]
lib/qtcamviewfinderrenderer.h [new file with mode: 0644]
lib/qtcamviewfinderrenderergeneric.cpp [new file with mode: 0644]
lib/qtcamviewfinderrenderergeneric.h [new file with mode: 0644]

diff --git a/lib/lib.pro b/lib/lib.pro
new file mode 100644 (file)
index 0000000..76a862c
--- /dev/null
@@ -0,0 +1,28 @@
+TEMPLATE = app
+TARGET = 
+DEPENDPATH += .
+INCLUDEPATH += .
+
+QT += opengl
+
+CONFIG += link_pkgconfig debug
+
+PKGCONFIG = gstreamer-0.10 gstreamer-interfaces-0.10 gstreamer-video-0.10 gstreamer-tag-0.10 \
+            gstreamer-pbutils-0.10
+
+HEADERS += qtcamconfig.h qtcamera.h qtcamscanner.h qtcamdevice.h qtcamviewfinder.h \
+           qtcammode.h qtcamgstreamermessagehandler.h qtcamgstreamermessagelistener.h \
+           qtcamgraphicsviewfinder.h qtcamviewfinderrenderer.h \
+           qtcamviewfinderrenderergeneric.h qtcamimagesettings.h qtcamvideosettings.h \
+           qtcamimagemode.h qtcamvideomode.h qtcammetadata.h
+
+SOURCES += qtcamconfig.cpp qtcamera.cpp qtcamscanner.cpp qtcamdevice.cpp qtcamviewfinder.cpp \
+           qtcammode.cpp qtcamgstreamermessagehandler.cpp qtcamgstreamermessagelistener.cpp \
+           qtcamgraphicsviewfinder.cpp qtcamviewfinderrenderer.cpp \
+           qtcamviewfinderrenderergeneric.cpp qtcamimagesettings.cpp qtcamvideosettings.cpp \
+           qtcamimagemode.cpp qtcamvideomode.cpp qtcammetadata.cpp
+
+HEADERS += qtcammode_p.h qtcamdevice_p.h
+
+SOURCES += main.cpp
+HEADERS += main.h
diff --git a/lib/main.cpp b/lib/main.cpp
new file mode 100644 (file)
index 0000000..69c63c7
--- /dev/null
@@ -0,0 +1,143 @@
+#include <QApplication>
+#include <QDebug>
+#include "qtcamera.h"
+#include "qtcamdevice.h"
+#include <QWidget>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QGraphicsView>
+#include <QGraphicsScene>
+#include <QGLWidget>
+#include "main.h"
+#include "qtcamgraphicsviewfinder.h"
+#include <QPushButton>
+#include <QToolBar>
+#include <QAction>
+#include <QToolButton>
+#include <QActionGroup>
+#include "qtcamimagemode.h"
+#include "qtcamvideomode.h"
+#include <QPushButton>
+#include "qtcammetadata.h"
+#include <QDateTime>
+
+Camera::Camera(QWidget *parent) :
+  QWidget(parent),
+  m_cam(new QtCamera("camera.ini")),
+  m_dev(0), m_vf(0) {
+
+  setMinimumSize(700, 500);
+
+  QGraphicsView *v = new QGraphicsView;
+  v->setViewport(new QGLWidget);
+  v->setMinimumSize(640, 480);
+  m_scene = new QGraphicsScene;
+  v->setScene(m_scene);
+
+  //  m_vf = new QtCamGraphicsViewfinder(m_cam->config());
+  //  m_vf->resize(640, 480);
+  //  s->addItem(m_vf);
+
+  QVBoxLayout *layout = new QVBoxLayout(this);
+  layout->setContentsMargins(0, 0, 0, 0);
+  layout->addWidget(v);
+
+  QToolBar *tools = new QToolBar(this);
+  layout->addWidget(tools);
+
+  QToolButton *devs = new QToolButton(this);
+  devs->setArrowType(Qt::DownArrow);
+  devs->setPopupMode(QToolButton::InstantPopup);
+  tools->addWidget(devs);
+
+  QActionGroup *group = new QActionGroup(devs);
+  QObject::connect(group, SIGNAL(triggered(QAction *)), this, SLOT(setDevice(QAction *)));
+
+  QList<QPair<QString, QVariant> > devices = m_cam->devices();
+  for (int x = 0; x < devices.size(); x++) {
+    QAction *a = group->addAction(devices[x].first);
+    a->setData(devices[x].second);
+    a->setCheckable(true);
+    devs->addAction(a);
+  }
+
+  tools->addSeparator();
+  tools->addAction(tr("Image"), this, SLOT(switchToImage()));
+  tools->addAction(tr("Video"), this, SLOT(switchToVideo()));
+
+  QList<QAction *> actions = group->actions();
+  if (!actions.isEmpty()) {
+    actions[0]->setChecked(true);
+    setDevice(actions[0]);
+  }
+
+  m_button = new QPushButton(tr("Capture"));
+  tools->addWidget(m_button);
+  QObject::connect(m_button, SIGNAL(clicked()), this, SLOT(buttonClicked()));
+
+  m_meta = new QtCamMetaData(this);
+  m_meta->setDevice(m_dev);
+}
+
+Camera::~Camera() {
+  if (m_dev) {
+    m_dev->stop();
+  }
+}
+
+void Camera::setDevice(QAction *action) {
+  QVariant id = action->data();
+
+  if (m_dev) {
+    m_dev->stop();
+    delete m_dev;
+    delete m_vf;
+  }
+
+  m_vf = new QtCamGraphicsViewfinder(m_cam->config());
+  m_vf->resize(640, 480);
+  m_scene->addItem(m_vf);
+  m_vf->show();
+
+  m_dev = m_cam->device(id);
+  m_dev->setViewfinder(m_vf);
+  m_dev->start();
+}
+
+void Camera::switchToImage() {
+  m_dev->imageMode()->activate();
+}
+
+void Camera::switchToVideo() {
+  m_dev->videoMode()->activate();
+}
+
+void Camera::buttonClicked() {
+  if (m_dev->activeMode() == m_dev->imageMode()) {
+    m_meta->reset();
+    m_meta->setArtist("Mohammed");
+    m_meta->setDateTime(QDateTime::currentDateTime());
+    m_dev->imageMode()->capture("/tmp/foo.jpg");
+  }
+  else if (m_dev->activeMode() == m_dev->videoMode()) {
+    if (m_dev->videoMode()->isRecording()) {
+      m_dev->videoMode()->stopRecording();
+      m_button->setText(tr("Capture"));
+    }
+    else {
+    m_meta->reset();
+    m_meta->setArtist("Mohammed");
+    m_meta->setDateTime(QDateTime::currentDateTime());
+      m_dev->videoMode()->startRecording("/tmp/foo.ogg");
+      m_button->setText(tr("Stop"));
+    }
+  }
+}
+
+int main(int argc, char *argv[]) {
+  QApplication app(argc, argv);
+  Camera c;
+  c.show();
+
+  return app.exec();
+}
diff --git a/lib/main.h b/lib/main.h
new file mode 100644 (file)
index 0000000..d8c7084
--- /dev/null
@@ -0,0 +1,37 @@
+// -*- c++ -*-
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#include <QWidget>
+
+class QtCamera;
+class QtCamDevice;
+class QtCamGraphicsViewfinder;
+class QPushButton;
+class QtCamMetaData;
+class QGraphicsScene;
+
+class Camera : public QWidget {
+  Q_OBJECT
+
+public:
+  Camera(QWidget *parent = 0);
+  ~Camera();
+
+private slots:
+  void setDevice(QAction *action);
+  void switchToImage();
+  void switchToVideo();
+  void buttonClicked();
+
+private:
+  QGraphicsScene *m_scene;
+  QtCamera *m_cam;
+  QtCamDevice *m_dev;
+  QtCamGraphicsViewfinder *m_vf;
+  QPushButton *m_button;
+  QtCamMetaData *m_meta;
+};
+
+#endif /* MAIN_H */
diff --git a/lib/qtcamconfig.cpp b/lib/qtcamconfig.cpp
new file mode 100644 (file)
index 0000000..1cb9624
--- /dev/null
@@ -0,0 +1,166 @@
+#include "qtcamconfig.h"
+#include <QSettings>
+#include <QStringList>
+#include <QDebug>
+
+#define CONFIGURATION_FILE                    "/etc/qtcamera.ini"
+
+class QtCamConfigPrivate {
+public:
+  QString element(const QString& name) const {
+    return conf->value(QString("%1/element").arg(name)).toString();
+  }
+
+  QSize readResolution(const QString key) {
+    QList<QString> parts = conf->value(key).toString().trimmed().split("x");
+    return QSize(parts[0].toInt(), parts[1].toInt());
+  }
+
+  QPair<int, int> readFrameRate() {
+    QList<QString> parts = conf->value("fps").toString().trimmed().split("/");
+    return qMakePair<int, int>(parts[0].toInt(), parts[1].toInt());
+  }
+
+  QSettings *conf;
+
+  QList<QtCamImageSettings> imageSettings;
+  QList<QtCamVideoSettings> videoSettings;
+};
+
+QtCamConfig::QtCamConfig(QObject *parent) :
+  QObject(parent), d_ptr(new QtCamConfigPrivate) {
+
+  d_ptr->conf = new QSettings(CONFIGURATION_FILE, QSettings::IniFormat, this);
+}
+
+QtCamConfig::QtCamConfig(const QString& configPath, QObject *parent) :
+  QObject(parent), d_ptr(new QtCamConfigPrivate) {
+
+  d_ptr->conf = new QSettings(configPath, QSettings::IniFormat, this);
+
+  defaultImageSettings();
+}
+
+QtCamConfig::~QtCamConfig() {
+  delete d_ptr;
+}
+
+QString QtCamConfig::deviceScannerType() const {
+  return d_ptr->conf->value("devices/scanner").toString();
+}
+
+QString QtCamConfig::deviceScannerProperty() const {
+  return d_ptr->conf->value("devices/property").toString();
+}
+
+QString QtCamConfig::videoSource() const {
+  return d_ptr->element("video-source");
+}
+
+QString QtCamConfig::viewfinderSink() const {
+  return d_ptr->element("viewfinder-sink");
+}
+
+QString QtCamConfig::viewfinderRenderer() const {
+  return d_ptr->conf->value("viewfinder-sink/renderer").toString();
+}
+
+QString QtCamConfig::audioSource() const {
+  return d_ptr->element("audio-source");
+}
+
+QString QtCamConfig::wrapperVideoSource() const {
+  return d_ptr->element("wrapper-video-source");
+}
+
+QString QtCamConfig::wrapperVideoSourceProperty() const {
+  return d_ptr->conf->value("wrapper-video-source/property").toString();
+}
+
+QtCamImageSettings QtCamConfig::defaultImageSettings() {
+
+  QList<QtCamImageSettings> settings = imageSettings();
+
+  QString def = d_ptr->conf->value("image/default").toString();
+  foreach (const QtCamImageSettings& s, settings) {
+    if (s.id() == def) {
+      return s;
+    }
+  }
+
+  // This will crash if no presets have been shipped but you deserve paying
+  // the price of shipping a broken configuration file.
+  return settings[0];
+}
+
+QList<QtCamImageSettings> QtCamConfig::imageSettings() {
+  if (d_ptr->imageSettings.isEmpty()) {
+    QStringList presets = d_ptr->conf->value("image/presets").toStringList();
+    foreach (const QString& preset, presets) {
+      d_ptr->conf->beginGroup(preset);
+      QPair<int, int> fps = d_ptr->readFrameRate();
+      d_ptr->imageSettings <<
+       QtCamImageSettings(preset, d_ptr->conf->value("name").toString(),
+                          d_ptr->readResolution("capture"),
+                          d_ptr->readResolution("preview"),
+                          d_ptr->readResolution("viewfinder"),
+                          fps.first, fps.second);
+      d_ptr->conf->endGroup();
+    }
+  }
+
+  return d_ptr->imageSettings;
+}
+
+QtCamVideoSettings QtCamConfig::defaultVideoSettings() {
+  QList<QtCamVideoSettings> settings = videoSettings();
+
+  QString def = d_ptr->conf->value("video/default").toString();
+  foreach (const QtCamVideoSettings& s, settings) {
+    if (s.id() == def) {
+      return s;
+    }
+  }
+
+  // This will crash if no presets have been shipped but you deserve paying
+  // the price of shipping a broken configuration file.
+  return settings[0];
+}
+
+QList<QtCamVideoSettings> QtCamConfig::videoSettings() {
+  if (d_ptr->videoSettings.isEmpty()) {
+    QStringList presets = d_ptr->conf->value("video/presets").toStringList();
+    foreach (const QString& preset, presets) {
+      d_ptr->conf->beginGroup(preset);
+      QPair<int, int> fps = d_ptr->readFrameRate();
+      d_ptr->videoSettings <<
+       QtCamVideoSettings(preset, d_ptr->conf->value("name").toString(),
+                          d_ptr->readResolution("capture"),
+                          d_ptr->readResolution("preview"),
+                          fps.first, fps.second);
+      d_ptr->conf->endGroup();
+    }
+  }
+
+  return d_ptr->videoSettings;
+}
+
+QString QtCamConfig::imageEncodingProfileName() const {
+  return d_ptr->conf->value("image/profile-name").toString();
+}
+
+QString QtCamConfig::imageEncodingProfilePath() const {
+  return d_ptr->conf->value("image/profile-path").toString();
+}
+
+QString QtCamConfig::videoEncodingProfileName() const {
+  return d_ptr->conf->value("video/profile-name").toString();
+}
+
+QString QtCamConfig::videoEncodingProfilePath() const {
+  return d_ptr->conf->value("video/profile-path").toString();
+}
+
+QString QtCamConfig::audioCaptureCaps() const {
+  return d_ptr->conf->value("audio-capture-caps/caps").toString();
+}
diff --git a/lib/qtcamconfig.h b/lib/qtcamconfig.h
new file mode 100644 (file)
index 0000000..eb04bc9
--- /dev/null
@@ -0,0 +1,56 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_CONFIG_H
+#define QT_CAM_CONFIG_H
+
+#include <QObject>
+#include "qtcamimagesettings.h"
+#include "qtcamvideosettings.h"
+
+#define SCANNER_TYPE_V4L2                     "v4l2"
+#define SCANNER_TYPE_ENUM                     "enum"
+
+//#define RENDERER_TYPE_GL_SINK                 "glsink"
+//#define RENDERER_TYPE_X_OVERLAY               "xoverlay"
+//#define RENDERER_TYPE_MEEGO                   "meego"
+#define RENDERER_TYPE_GENERIC                 "generic"
+class QtCamConfigPrivate;
+
+class QtCamConfig : public QObject {
+  Q_OBJECT
+
+public:
+  QtCamConfig(QObject *parent = 0);
+  QtCamConfig(const QString& configPath, QObject *parent = 0);
+
+  virtual ~QtCamConfig();
+
+  QString deviceScannerType() const;
+  QString deviceScannerProperty() const;
+
+  QString videoSource() const;
+  QString viewfinderSink() const;
+  QString viewfinderRenderer() const;
+  QString audioSource() const;
+  QString wrapperVideoSource() const;
+  QString wrapperVideoSourceProperty() const;
+
+  QtCamImageSettings defaultImageSettings();
+  QList<QtCamImageSettings> imageSettings();
+
+  QtCamVideoSettings defaultVideoSettings();
+  QList<QtCamVideoSettings> videoSettings();
+
+  QString imageEncodingProfileName() const;
+  QString imageEncodingProfilePath() const;
+
+  QString videoEncodingProfileName() const;
+  QString videoEncodingProfilePath() const;
+
+  QString audioCaptureCaps() const;
+
+private:
+  QtCamConfigPrivate *d_ptr;
+};
+
+#endif /* QT_CAM_CONFIG_H */
diff --git a/lib/qtcamdevice.cpp b/lib/qtcamdevice.cpp
new file mode 100644 (file)
index 0000000..17620bc
--- /dev/null
@@ -0,0 +1,230 @@
+#include "qtcamdevice.h"
+#include "qtcamviewfinder.h"
+#include "qtcamconfig.h"
+#include "qtcamdevice_p.h"
+#include <QDebug>
+#include <gst/gst.h>
+#include "qtcamgstreamermessagelistener.h"
+#include "qtcammode.h"
+#include "qtcamimagemode.h"
+#include "qtcamvideomode.h"
+
+QtCamDevice::QtCamDevice(QtCamConfig *config, const QString& name,
+                        const QVariant& id, QObject *parent) :
+  QObject(parent), d_ptr(new QtCamDevicePrivate) {
+
+  d_ptr->q_ptr = this;
+  d_ptr->name = name;
+  d_ptr->id = id;
+  d_ptr->conf = config;
+
+  d_ptr->cameraBin = gst_element_factory_make("camerabin2", "QtCameraCameraBin");
+  if (!d_ptr->cameraBin) {
+    qCritical() << "Failed to create camerabin";
+    return;
+  }
+
+  d_ptr->createAndAddElement(d_ptr->conf->audioSource(), "audio-source", "QtCameraAudioSrc");
+  d_ptr->createAndAddVideoSource();
+
+  int flags =
+    0x00000001 /* no-audio-conversion - Do not use audio conversion elements */
+    | 0x00000002 /* no-video-conversion - Do not use video conversion elements */
+    | 0x00000004 /* no-viewfinder-conversion - Do not use viewfinder conversion elements */
+    | 0x00000008; /* no-image-conversion - Do not use image conversion elements */
+
+  g_object_set(d_ptr->cameraBin, "flags", flags, NULL);
+
+  d_ptr->setAudioCaptureCaps();
+
+  // TODO: audio bitrate
+  // TODO: video bitrate
+  // TODO: filters
+  // TODO: capabilities
+  // TODO: custom properties for jifmux, mp4mux, audio encoder, video encoder, sink & video source
+  // color tune, scene modes
+  d_ptr->listener = new QtCamGStreamerMessageListener(gst_element_get_bus(d_ptr->cameraBin),
+                                                     d_ptr, this);
+
+  QObject::connect(d_ptr->listener, SIGNAL(error(const QString&, int, const QString&)),
+                  this, SLOT(_d_error(const QString&, int, const QString&)));
+  QObject::connect(d_ptr->listener, SIGNAL(started()), this, SIGNAL(started()));
+  QObject::connect(d_ptr->listener, SIGNAL(stopped()), this, SIGNAL(stopped()));
+
+  d_ptr->image = new QtCamImageMode(d_ptr, this);
+  d_ptr->video = new QtCamVideoMode(d_ptr, this);
+}
+
+QtCamDevice::~QtCamDevice() {
+  stop();
+
+  d_ptr->image->deactivate();
+  d_ptr->video->deactivate();
+
+  delete d_ptr->image; d_ptr->image = 0;
+  delete d_ptr->video; d_ptr->video = 0;
+  delete d_ptr; d_ptr = 0;
+}
+
+bool QtCamDevice::setViewfinder(QtCamViewfinder *viewfinder) {
+  if (isRunning()) {
+    qWarning() << "QtCamDevice: pipeline must be stopped before setting a viewfinder";
+    return false;
+  }
+
+  if (d_ptr->viewfinder == viewfinder) {
+    return true;
+  }
+
+  if (!viewfinder) {
+    qWarning() << "QtCamDevice: viewfinder cannot be unset.";
+    return false;
+  }
+
+  if (d_ptr->viewfinder) {
+    qWarning() << "QtCamDevice: viewfinder cannot be replaced.";
+    return false;
+  }
+
+  if (!viewfinder->setDevice(this)) {
+    return false;
+  }
+
+  d_ptr->viewfinder = viewfinder;
+
+  return true;
+}
+
+bool QtCamDevice::start() {
+  if (d_ptr->error) {
+    qWarning() << "Pipeline must be stopped first because of an error.";
+    return false;
+  }
+
+  if (!d_ptr->cameraBin) {
+    qWarning() << "Missing camerabin";
+    return false;
+  }
+
+  if (!d_ptr->viewfinder) {
+    qWarning() << "Viewfinder not set";
+    return false;
+  }
+
+  if (isRunning()) {
+    return true;
+  }
+
+  if (!d_ptr->active) {
+    d_ptr->image->activate();
+  }
+  else {
+    d_ptr->active->applySettings();
+  }
+
+  // Set sink.
+  if (!d_ptr->setViewfinderSink()) {
+    return false;
+  }
+
+  GstStateChangeReturn err = gst_element_set_state(d_ptr->cameraBin, GST_STATE_PLAYING);
+  if (err == GST_STATE_CHANGE_FAILURE) {
+    qWarning() << "Failed to start camera pipeline";
+    return false;
+  }
+
+  return true;
+}
+
+bool QtCamDevice::stop() {
+  if (!d_ptr->cameraBin) {
+    return true;
+  }
+
+  if (d_ptr->error) {
+    gst_element_set_state(d_ptr->cameraBin, GST_STATE_NULL);
+    d_ptr->error = false;
+    return true;
+  }
+
+  GstState state;
+  gst_element_get_state(d_ptr->cameraBin, &state, 0, GST_CLOCK_TIME_NONE);
+
+  if (state == GST_STATE_NULL) {
+    // Nothing to do.
+    return true;
+  }
+
+  if (!isIdle()) {
+    return false;
+  }
+
+  // First we go to ready:
+  GstStateChangeReturn st = gst_element_set_state(d_ptr->cameraBin, GST_STATE_READY);
+  if (st != GST_STATE_CHANGE_FAILURE) {
+    // Flush the bus:
+    d_ptr->listener->flushMessages();
+  }
+
+  // Now to NULL
+  gst_element_set_state(d_ptr->cameraBin, GST_STATE_NULL);
+
+  return true;
+}
+
+bool QtCamDevice::isRunning() {
+  if (!d_ptr->cameraBin) {
+    return false;
+  }
+
+  GstState state;
+  GstStateChangeReturn err = gst_element_get_state(d_ptr->cameraBin,
+                                                  &state, 0, GST_CLOCK_TIME_NONE);
+
+  if (err == GST_STATE_CHANGE_FAILURE || state != GST_STATE_PLAYING) {
+    return false;
+  }
+
+  return true;
+}
+
+bool QtCamDevice::isIdle() {
+  if (!d_ptr->cameraBin) {
+    return true;
+  }
+
+  gboolean idle = FALSE;
+  g_object_get(d_ptr->cameraBin, "idle", &idle, NULL);
+
+  return idle == TRUE;
+}
+
+QtCamImageMode *QtCamDevice::imageMode() const {
+  return d_ptr->image;
+}
+
+QtCamVideoMode *QtCamDevice::videoMode() const {
+  return d_ptr->video;
+}
+
+QtCamMode *QtCamDevice::activeMode() const {
+  return d_ptr->active;
+}
+
+QString QtCamDevice::name() const {
+  return d_ptr->name;
+}
+
+QVariant QtCamDevice::id() const {
+  return d_ptr->id;
+}
+
+QtCamConfig *QtCamDevice::config() const {
+  return d_ptr->conf;
+}
+
+QtCamGStreamerMessageListener *QtCamDevice::listener() const {
+  return d_ptr->listener;
+}
+
+#include "moc_qtcamdevice.cpp"
diff --git a/lib/qtcamdevice.h b/lib/qtcamdevice.h
new file mode 100644 (file)
index 0000000..59d7421
--- /dev/null
@@ -0,0 +1,53 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_DEVICE_H
+#define QT_CAM_DEVICE_H
+
+#include <QObject>
+#include <QVariant>
+
+class QtCamDevicePrivate;
+class QtCamConfig;
+class QtCamViewfinder;
+class QtCamVideoMode;
+class QtCamImageMode;
+class QtCamMode;
+class QtCamGStreamerMessageListener;
+class QtCamMetaData;
+
+class QtCamDevice : public QObject {
+  Q_OBJECT
+
+public:
+  QtCamDevice(QtCamConfig *config, const QString& name, const QVariant& id, QObject *parent = 0);
+  ~QtCamDevice();
+
+  bool setViewfinder(QtCamViewfinder *viewfinder);
+  bool start();
+  bool stop();
+  bool isRunning();
+  bool isIdle();
+
+  QtCamImageMode *imageMode() const;
+  QtCamVideoMode *videoMode() const;
+
+  QtCamMode *activeMode() const;
+
+  QString name() const;
+  QVariant id() const;
+
+  QtCamConfig *config() const;
+  QtCamGStreamerMessageListener *listener() const;
+
+signals:
+  void error(const QString& message, int code, const QString& debug);
+  void started();
+  void stopped();
+
+private:
+  Q_PRIVATE_SLOT(d_ptr, void _d_error(const QString&, int, const QString&))
+  friend class QtCamMetaData;
+  QtCamDevicePrivate *d_ptr;
+};
+
+#endif /* QT_CAM_DEVICE_H */
diff --git a/lib/qtcamdevice_p.h b/lib/qtcamdevice_p.h
new file mode 100644 (file)
index 0000000..e31fdfb
--- /dev/null
@@ -0,0 +1,143 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_DEVICE_P_H
+#define QT_CAM_DEVICE_P_H
+
+#include <QDebug>
+#include <gst/gst.h>
+#include "qtcamconfig.h"
+#include "qtcamviewfinder.h"
+#include "qtcamdevice.h"
+
+class QtCamGStreamerMessageListener;
+class QtCamMode;
+class QtCamImageMode;
+class QtCamVideoMode;
+
+class QtCamDevicePrivate {
+public:
+  QtCamDevicePrivate() :
+    cameraBin(0),
+    videoSource(0),
+    wrapperVideoSource(0),
+    image(0),
+    video(0),
+    active(0),
+    viewfinder(0),
+    conf(0),
+    error(false) {
+
+  }
+
+  GstElement *createAndAddElement(const QString& elementName, const char *prop, const char *name) {
+    GstElement *elem = gst_element_factory_make(elementName.toAscii(), name);
+    if (!elem) {
+      qWarning() << "Failed to create" << elementName;
+      return 0;
+    }
+
+    g_object_set(cameraBin, prop, elem, NULL);
+
+    return elem;
+  }
+
+  void createAndAddVideoSource() {
+    GstElement *src, *wrapper;
+    QString wrapperSrc = conf->wrapperVideoSource();
+    QString prop = conf->wrapperVideoSourceProperty();
+
+    if (!prop.isEmpty() && !wrapperSrc.isEmpty()) {
+      wrapper = gst_element_factory_make(wrapperSrc.toAscii(), "QCameraWrapperVideoSrc");
+      if (!wrapper) {
+       qCritical() << "Failed to create wrapper source" << wrapperSrc;
+       return;
+      }
+    }
+
+    src = gst_element_factory_make(conf->videoSource().toAscii(),
+                                              "QtCameraVideoSrc");
+    if (!src) {
+      qCritical() << "Failed to create video source";
+      if (wrapper) {
+       gst_object_unref(wrapper);
+      }
+      return;
+    }
+
+    if (wrapper) {
+      g_object_set(wrapper, prop.toAscii(), src, NULL);
+      g_object_set(cameraBin, "camera-source", wrapper, NULL);
+    }
+
+    videoSource = src;
+    wrapperVideoSource = wrapper;
+
+    if (conf->deviceScannerType() == SCANNER_TYPE_ENUM) {
+      int dev = id.toInt();
+      g_object_set(src, conf->deviceScannerProperty().toAscii().data(), dev, NULL);
+    }
+    else {
+      QString dev = id.toString();
+      g_object_set(src, conf->deviceScannerProperty().toAscii().data(),
+                  dev.toAscii().data(), NULL);
+    }
+  }
+
+  bool setViewfinderSink() {
+    GstElement *sink;
+    g_object_get(cameraBin, "viewfinder-sink", &sink, NULL);
+
+    if (sink) {
+      gst_object_unref(sink);
+      return true;
+    }
+
+    sink = viewfinder->sinkElement();
+    if (!sink) {
+      qCritical() << "Failed to create GStreamer sink element";
+      return false;
+    }
+
+    g_object_set(cameraBin, "viewfinder-sink", sink, NULL);
+
+    return true;
+  }
+
+  void _d_error(const QString& message, int code, const QString& debug) {
+    error = true;
+
+    QMetaObject::invokeMethod(q_ptr, "error", Q_ARG(QString, message),
+                             Q_ARG(int, code), Q_ARG(QString, debug));
+  }
+
+  void setAudioCaptureCaps() {
+    QString captureCaps = conf->audioCaptureCaps();
+    if (!captureCaps.isEmpty()) {
+      GstCaps *caps = gst_caps_from_string(captureCaps.toAscii().data());
+      if (caps) {
+       g_object_set(cameraBin, "audio-capture-caps", caps, NULL);
+       gst_caps_unref(caps);
+      }
+    }
+  }
+
+  QString name;
+  QVariant id;
+
+  QtCamDevice *q_ptr;
+
+  GstElement *cameraBin;
+  GstElement *videoSource;
+  GstElement *wrapperVideoSource;
+
+  QtCamImageMode *image;
+  QtCamVideoMode *video;
+  QtCamMode *active;
+
+  QtCamViewfinder *viewfinder;
+  QtCamConfig *conf;
+  QtCamGStreamerMessageListener *listener;
+  bool error;
+};
+
+#endif /* QT_CAM_DEVICE_P_H */
diff --git a/lib/qtcamera.cpp b/lib/qtcamera.cpp
new file mode 100644 (file)
index 0000000..3f712bd
--- /dev/null
@@ -0,0 +1,77 @@
+#include "qtcamera.h"
+#include "qtcamscanner.h"
+#include "qtcamconfig.h"
+#include "qtcamdevice.h"
+#include <gst/gst.h>
+
+class QtCameraPrivate {
+public:
+  QtCamConfig *conf;
+  QtCamScanner *scanner;
+};
+
+QtCamera::QtCamera(QObject *parent) :
+  QObject(parent), d_ptr(new QtCameraPrivate) {
+
+  gst_init(0, 0);
+
+  d_ptr->conf = new QtCamConfig(this);
+  d_ptr->scanner = new QtCamScanner(d_ptr->conf, this);
+
+  refreshDevices();
+}
+
+QtCamera::QtCamera(const QString& configPath, QObject *parent) :
+  QObject(parent), d_ptr(new QtCameraPrivate) {
+
+  gst_init(0, 0);
+
+  d_ptr->conf = new QtCamConfig(configPath, this);
+  d_ptr->scanner = new QtCamScanner(d_ptr->conf, this);
+
+  refreshDevices();
+}
+
+QtCamera::QtCamera(QtCamConfig *config, QObject *parent) :
+  QObject(parent), d_ptr(new QtCameraPrivate) {
+
+  gst_init(0, 0);
+
+  d_ptr->conf = config;
+  d_ptr->scanner = new QtCamScanner(d_ptr->conf, this);
+
+  refreshDevices();
+}
+
+QtCamera::~QtCamera() {
+  delete d_ptr; d_ptr = 0;
+
+  gst_deinit();
+}
+
+
+void QtCamera::refreshDevices() {
+  d_ptr->scanner->refresh();
+}
+
+QList<QPair<QString, QVariant> > QtCamera::devices() const {
+  return d_ptr->scanner->devices();
+}
+
+QtCamDevice *QtCamera::device(const QVariant& id, QObject *parent) {
+  QList<QPair<QString, QVariant> > devs = devices();
+
+  // G++ barfs with foreach and class templates.
+  typedef QPair<QString, QVariant> Dev;
+  foreach (const Dev& dev, devs) {
+    if (dev.second == id) {
+      return new QtCamDevice(d_ptr->conf, dev.first, dev.second, parent ? parent : this);
+    }
+  }
+
+  return 0;
+}
+
+QtCamConfig *QtCamera::config() const {
+  return d_ptr->conf;
+}
diff --git a/lib/qtcamera.h b/lib/qtcamera.h
new file mode 100644 (file)
index 0000000..e4d96a0
--- /dev/null
@@ -0,0 +1,36 @@
+// -*- c++ -*-
+
+#ifndef QT_CAMERA_H
+#define QT_CAMERA_H
+
+#include <QObject>
+#include <QPair>
+#include <QVariant>
+
+class QtCamConfig;
+class QtCameraPrivate;
+class QtCamDevice;
+
+class QtCamera : public QObject {
+  Q_OBJECT
+
+public:
+  QtCamera(QObject *parent = 0);
+  QtCamera(const QString& configPath, QObject *parent = 0);
+  QtCamera(QtCamConfig *config, QObject *parent = 0);
+
+  ~QtCamera();
+
+  void refreshDevices();
+
+  QList<QPair<QString, QVariant> > devices() const;
+
+  QtCamDevice *device(const QVariant& id, QObject *parent = 0);
+
+  QtCamConfig *config() const;
+
+private:
+  QtCameraPrivate *d_ptr;
+};
+
+#endif /* QT_CAMERA_H */
diff --git a/lib/qtcamgraphicsviewfinder.cpp b/lib/qtcamgraphicsviewfinder.cpp
new file mode 100644 (file)
index 0000000..76ccad1
--- /dev/null
@@ -0,0 +1,102 @@
+#include "qtcamgraphicsviewfinder.h"
+#include "qtcamconfig.h"
+#include "qtcamdevice.h"
+#include <QDebug>
+#include "qtcamviewfinderrenderer.h"
+#include <QPainter>
+#include <QGraphicsSceneResizeEvent>
+
+class QtCamGraphicsViewfinderPrivate {
+public:
+  void ensureBackend() {
+    if (!renderer) {
+      renderer = QtCamViewfinderRenderer::create(conf, q_ptr);
+      renderer->resize(q_ptr->size());
+      QObject::connect(renderer, SIGNAL(updateRequested()), q_ptr, SLOT(updateRequested()));
+    }
+  }
+
+  void resetBackend() {
+    if (renderer) {
+      renderer->reset();
+    }
+  }
+
+  QtCamViewfinderRenderer *renderer;
+  QtCamConfig *conf;
+  QtCamDevice *dev;
+  QtCamGraphicsViewfinder *q_ptr;
+};
+
+QtCamGraphicsViewfinder::QtCamGraphicsViewfinder(QtCamConfig *config, QGraphicsItem *parent) :
+  QGraphicsWidget(parent), d_ptr(new QtCamGraphicsViewfinderPrivate) {
+  d_ptr->conf = config;
+  d_ptr->dev = 0;
+  d_ptr->renderer = 0;
+  d_ptr->q_ptr = this;
+}
+
+QtCamGraphicsViewfinder::~QtCamGraphicsViewfinder() {
+  delete d_ptr; d_ptr = 0;
+}
+
+GstElement *QtCamGraphicsViewfinder::sinkElement() {
+  d_ptr->ensureBackend();
+
+  return d_ptr->renderer->sinkElement();
+}
+
+bool QtCamGraphicsViewfinder::setDevice(QtCamDevice *device) {
+  if (device && d_ptr->dev == device) {
+    return true;
+  }
+
+  if (d_ptr->dev) {
+    qWarning() << "QtCamGraphicsViewfinder: viewfinder cannot be replaced.";
+    return false;
+  }
+
+  if (!device) {
+    qWarning() << "QtCamGraphicsViewfinder: viewfinder cannot be unset.";
+    return false;
+  }
+
+  // This is to break the loop.
+  d_ptr->dev = device;
+  if (!device->setViewfinder(this)) {
+    d_ptr->dev = 0;
+    return false;
+  }
+
+  d_ptr->resetBackend();
+
+  return true;
+}
+
+void QtCamGraphicsViewfinder::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+                                   QWidget *widget) {
+  Q_UNUSED(widget);
+  Q_UNUSED(option);
+
+  painter->fillRect(boundingRect(), Qt::black);
+
+  if (!d_ptr->renderer) {
+    return;
+  }
+
+  d_ptr->renderer->paint(painter);
+}
+
+void QtCamGraphicsViewfinder::resizeEvent(QGraphicsSceneResizeEvent *event) {
+  QGraphicsWidget::resizeEvent(event);
+
+  if (!d_ptr->renderer) {
+    return;
+  }
+
+  d_ptr->renderer->resize(event->newSize());
+}
+
+void QtCamGraphicsViewfinder::updateRequested() {
+  update();
+}
diff --git a/lib/qtcamgraphicsviewfinder.h b/lib/qtcamgraphicsviewfinder.h
new file mode 100644 (file)
index 0000000..b8bd811
--- /dev/null
@@ -0,0 +1,35 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_GRAPHICS_VIEWFINDER_H
+#define QT_CAM_GRAPHICS_VIEWFINDER_H
+
+#include "qtcamviewfinder.h"
+#include <QGraphicsWidget>
+
+class QtCamGraphicsViewfinderPrivate;
+class QtCamConfig;
+
+class QtCamGraphicsViewfinder : public QGraphicsWidget, public QtCamViewfinder {
+  Q_OBJECT
+
+public:
+  QtCamGraphicsViewfinder(QtCamConfig *config, QGraphicsItem *parent = 0);
+  virtual ~QtCamGraphicsViewfinder();
+
+  virtual GstElement *sinkElement();
+  virtual bool setDevice(QtCamDevice *device);
+
+  virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
+                    QWidget *widget = 0);
+
+protected:
+  void resizeEvent(QGraphicsSceneResizeEvent *event);
+
+private slots:
+  void updateRequested();
+
+private:
+  QtCamGraphicsViewfinderPrivate *d_ptr;
+};
+
+#endif /* QT_CAM_GRAPHICS_VIEWFINDER_H */
diff --git a/lib/qtcamgstreamermessagehandler.cpp b/lib/qtcamgstreamermessagehandler.cpp
new file mode 100644 (file)
index 0000000..3683717
--- /dev/null
@@ -0,0 +1,26 @@
+#include "qtcamgstreamermessagehandler.h"
+
+class QtCamGStreamerMessageHandlerPrivate {
+public:
+  QString name;
+};
+
+QtCamGStreamerMessageHandler::QtCamGStreamerMessageHandler(const QString& messageName,
+                                                          QObject *parent) :
+  QObject(parent), d_ptr(new QtCamGStreamerMessageHandlerPrivate) {
+
+  d_ptr->name = messageName;
+}
+
+QtCamGStreamerMessageHandler::~QtCamGStreamerMessageHandler() {
+  delete d_ptr; d_ptr = 0;
+}
+
+QString QtCamGStreamerMessageHandler::messageName() const {
+  return d_ptr->name;
+}
+
+void QtCamGStreamerMessageHandler::handleMessage(GstMessage *message) {
+  emit messageSent(message);
+}
+
diff --git a/lib/qtcamgstreamermessagehandler.h b/lib/qtcamgstreamermessagehandler.h
new file mode 100644 (file)
index 0000000..4277d1a
--- /dev/null
@@ -0,0 +1,29 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_GSTREAMER_MESSAGE_HANDLER_H
+#define QT_CAM_GSTREAMER_MESSAGE_HANDLER_H
+
+#include <QObject>
+#include <gst/gst.h>
+
+class QtCamGStreamerMessageHandlerPrivate;
+
+class QtCamGStreamerMessageHandler : public QObject {
+  Q_OBJECT
+
+public:
+  QtCamGStreamerMessageHandler(const QString& messageName, QObject *parent = 0);
+  virtual ~QtCamGStreamerMessageHandler();
+
+  QString messageName() const;
+
+  virtual void handleMessage(GstMessage *message);
+
+signals:
+  void messageSent(GstMessage *message);
+
+private:
+  QtCamGStreamerMessageHandlerPrivate *d_ptr;
+};
+
+#endif /* QT_CAM_GSTREAMER_MESSAGE_HANDLER_H */
diff --git a/lib/qtcamgstreamermessagelistener.cpp b/lib/qtcamgstreamermessagelistener.cpp
new file mode 100644 (file)
index 0000000..94600e1
--- /dev/null
@@ -0,0 +1,218 @@
+#include "qtcamgstreamermessagelistener.h"
+#include "qtcamgstreamermessagehandler.h"
+#include <QMultiMap>
+#include <QMutex>
+#include <QDebug>
+#include "qtcamdevice_p.h"
+
+class QtCamGStreamerMessageListenerPrivate {
+public:
+  QMultiMap<QString, QtCamGStreamerMessageHandler *> handlers;
+  QMultiMap<QString, QtCamGStreamerMessageHandler *> syncHandlers;
+
+  int handleMessage(GstMessage *message, QMultiMap<QString, QtCamGStreamerMessageHandler *>& map) {
+    const GstStructure *s = gst_message_get_structure(message);
+    if (!s) {
+      return 0;
+    }
+
+    QList<QtCamGStreamerMessageHandler *> list =
+      map.values(gst_structure_get_name(s));
+
+    foreach (QtCamGStreamerMessageHandler *handler, list) {
+      handler->handleMessage(message);
+    }
+
+    return list.size();
+  }
+
+  void handleMessage(GstMessage *message) {
+
+    switch (GST_MESSAGE_TYPE(message)) {
+    case GST_MESSAGE_ELEMENT:
+      handleMessage(message, handlers);
+      break;
+
+    case GST_MESSAGE_ERROR: {
+      GError *err = NULL;
+      gchar *debug;
+
+      gst_message_parse_error(message, &err, &debug);
+
+      QMetaObject::invokeMethod(q_ptr, "error", Q_ARG(QString, err->message),
+                               Q_ARG(int, err->code), Q_ARG(QString, debug));
+
+      qDebug() << "Error" << err->message << ":" << debug;
+
+      g_error_free(err);
+      g_free(debug);
+    }
+      break;
+
+    case GST_MESSAGE_WARNING: {
+      GError *err = NULL;
+      gchar *debug;
+
+      gst_message_parse_warning(message, &err, &debug);
+
+      qDebug() << "Warning" << err->message << ":" << debug;
+
+      g_error_free(err);
+      g_free(debug);
+    }
+      break;
+
+    case GST_MESSAGE_INFO: {
+      GError *err = NULL;
+      gchar *debug;
+
+      gst_message_parse_info(message, &err, &debug);
+
+      qDebug() << "Info" << err->message << ":" << debug;
+
+      g_error_free(err);
+      g_free(debug);
+    }
+      break;
+
+    case GST_MESSAGE_STATE_CHANGED: {
+      if (GST_ELEMENT(GST_MESSAGE_SRC(message)) != dev->cameraBin) {
+       break;
+      }
+
+      GstState oldState, newState, pending;
+      gst_message_parse_state_changed(message, &oldState, &newState, &pending);
+      if (oldState == GST_STATE_PAUSED && newState == GST_STATE_PLAYING) {
+       QMetaObject::invokeMethod(q_ptr, "started");
+      }
+      else if (oldState == GST_STATE_PLAYING && newState == GST_STATE_PAUSED) {
+       QMetaObject::invokeMethod(q_ptr, "stopped");
+      }
+    }
+      break;
+
+    default:
+      // TODO: other types
+      break;
+    }
+  }
+
+  bool handleSyncMessage(GstMessage *message) {
+    QMutexLocker locker(&syncMutex);
+
+    if (handleMessage(message, syncHandlers) != 0) {
+      return true;
+    }
+
+    return false;
+  }
+
+  void addHandler(QtCamGStreamerMessageHandler *handler,
+                 QMultiMap<QString, QtCamGStreamerMessageHandler *>& map) {
+    if (!map.contains(handler->messageName(), handler)) {
+      map.insert(handler->messageName(), handler);
+      handler->setParent(q_ptr);
+    }
+  }
+
+  void removeHandler(QtCamGStreamerMessageHandler *handler,
+                    QMultiMap<QString, QtCamGStreamerMessageHandler *>& map) {
+    map.remove(handler->messageName(), handler);
+    handler->setParent(0);
+  }
+
+  QMutex syncMutex;
+
+  GstBus *bus;
+
+  QtCamDevicePrivate *dev;
+
+  guint watchId;
+
+  QtCamGStreamerMessageListener *q_ptr;
+};
+
+gboolean async_handler(GstBus *bus, GstMessage *message, gpointer data)
+{
+  Q_UNUSED(bus);
+
+  QtCamGStreamerMessageListenerPrivate *d_ptr =
+    static_cast<QtCamGStreamerMessageListenerPrivate *>(data);
+
+  d_ptr->handleMessage(message);
+
+  // Call us again
+  return TRUE;
+}
+
+GstBusSyncReply sync_handler(GstBus *bus, GstMessage *message, gpointer data) {
+  Q_UNUSED(bus);
+
+  QtCamGStreamerMessageListenerPrivate *d_ptr =
+    static_cast<QtCamGStreamerMessageListenerPrivate *>(data);
+
+  if (d_ptr->handleSyncMessage(message)) {
+    gst_message_unref(message);
+    return GST_BUS_DROP;
+  }
+
+  return GST_BUS_PASS;
+}
+
+QtCamGStreamerMessageListener::QtCamGStreamerMessageListener(GstBus *bus,
+                                                            QtCamDevicePrivate *d,
+                                                            QObject *parent) :
+  QObject(parent), d_ptr(new QtCamGStreamerMessageListenerPrivate) {
+
+  d_ptr->dev = d;
+  d_ptr->bus = bus;
+  d_ptr->q_ptr = this;
+
+  d_ptr->watchId = gst_bus_add_watch(d_ptr->bus, async_handler, d_ptr);
+
+  gst_bus_set_sync_handler(d_ptr->bus, sync_handler, d_ptr);
+}
+
+QtCamGStreamerMessageListener::~QtCamGStreamerMessageListener() {
+  g_source_remove(d_ptr->watchId);
+  gst_bus_set_sync_handler(d_ptr->bus, NULL, NULL);
+
+  qDeleteAll(d_ptr->handlers);
+
+  d_ptr->syncMutex.lock();
+  qDeleteAll(d_ptr->syncHandlers);
+  d_ptr->syncMutex.unlock();
+
+  gst_object_unref(d_ptr->bus);
+
+  delete d_ptr; d_ptr = 0;
+}
+
+void QtCamGStreamerMessageListener::addHandler(QtCamGStreamerMessageHandler *handler) {
+  d_ptr->addHandler(handler, d_ptr->handlers);
+}
+
+void QtCamGStreamerMessageListener::removeHandler(QtCamGStreamerMessageHandler *handler) {
+  d_ptr->removeHandler(handler, d_ptr->handlers);
+}
+
+void QtCamGStreamerMessageListener::addSyncHandler(QtCamGStreamerMessageHandler *handler) {
+  QMutexLocker locker(&d_ptr->syncMutex);
+
+  d_ptr->addHandler(handler, d_ptr->syncHandlers);
+}
+
+void QtCamGStreamerMessageListener::removeSyncHandler(QtCamGStreamerMessageHandler *handler) {
+  QMutexLocker locker(&d_ptr->syncMutex);
+
+  d_ptr->removeHandler(handler, d_ptr->syncHandlers);
+}
+
+void QtCamGStreamerMessageListener::flushMessages() {
+  GstMessage *message = 0;
+
+  while ((message = gst_bus_pop(d_ptr->bus))) {
+    d_ptr->handleMessage(message);
+    gst_message_unref(message);
+  }
+}
diff --git a/lib/qtcamgstreamermessagelistener.h b/lib/qtcamgstreamermessagelistener.h
new file mode 100644 (file)
index 0000000..5c747f8
--- /dev/null
@@ -0,0 +1,36 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_GSTREAMER_MESSAGE_LISTENER_H
+#define QT_CAM_GSTREAMER_MESSAGE_LISTENER_H
+
+#include <QObject>
+#include <gst/gst.h>
+
+class QtCamGStreamerMessageListenerPrivate;
+class QtCamGStreamerMessageHandler;
+class QtCamDevicePrivate;
+
+class QtCamGStreamerMessageListener : public QObject {
+  Q_OBJECT
+
+public:
+  QtCamGStreamerMessageListener(GstBus *bus, QtCamDevicePrivate *d, QObject *parent = 0);
+  ~QtCamGStreamerMessageListener();
+
+  void addHandler(QtCamGStreamerMessageHandler *handler);
+  void removeHandler(QtCamGStreamerMessageHandler *handler);
+  void addSyncHandler(QtCamGStreamerMessageHandler *handler);
+  void removeSyncHandler(QtCamGStreamerMessageHandler *handler);
+
+  void flushMessages();
+
+signals:
+  void error(const QString& message, int code, const QString& debug);
+  void started();
+  void stopped();
+
+private:
+  QtCamGStreamerMessageListenerPrivate *d_ptr;
+};
+
+#endif /* QT_CAM_GSTREAMER_MESSAGE_LISTENER_H */
diff --git a/lib/qtcamimagemode.cpp b/lib/qtcamimagemode.cpp
new file mode 100644 (file)
index 0000000..4fddcab
--- /dev/null
@@ -0,0 +1,104 @@
+#include "qtcamimagemode.h"
+#include "qtcammode_p.h"
+#include "qtcamdevice_p.h"
+#include "qtcamimagesettings.h"
+#include "qtcamdevice.h"
+
+class QtCamImageModePrivate : public QtCamModePrivate {
+public:
+  QtCamImageModePrivate(QtCamDevicePrivate *dev) :
+  QtCamModePrivate(dev),
+  settings(dev->conf->defaultImageSettings()) {
+
+  }
+
+  ~QtCamImageModePrivate() {
+  }
+
+  bool wrapperIsReady() {
+    if (!dev->wrapperVideoSource) {
+      return false;
+    }
+
+    gboolean ready = FALSE;
+    g_object_get(dev->wrapperVideoSource, "ready-for-capture", &ready, NULL);
+
+    return ready == TRUE;
+  }
+
+  QtCamImageSettings settings;
+};
+
+QtCamImageMode::QtCamImageMode(QtCamDevicePrivate *d, QObject *parent) :
+  QtCamMode(new QtCamImageModePrivate(d), "mode-image", "image-done", parent) {
+
+  d_ptr = (QtCamImageModePrivate *)QtCamMode::d_ptr;
+
+  QString name = d_ptr->dev->conf->imageEncodingProfileName();
+  QString path = d_ptr->dev->conf->imageEncodingProfilePath();
+
+  if (!name.isEmpty() && !path.isEmpty()) {
+    GstEncodingProfile *profile = d_ptr->loadProfile(path, name);
+    if (profile) {
+      setProfile(profile);
+    }
+  }
+}
+
+QtCamImageMode::~QtCamImageMode() {
+
+}
+
+bool QtCamImageMode::canCapture() {
+  return QtCamMode::canCapture() && d_ptr->wrapperIsReady();
+}
+
+void QtCamImageMode::applySettings() {
+  setCaps("image-capture-caps", d_ptr->settings.captureResolution(),
+         d_ptr->settings.frameRate());
+  setCaps("viewfinder-caps", d_ptr->settings.viewfinderResolution(),
+         d_ptr->settings.frameRate());
+  setPreviewSize(d_ptr->settings.previewResolution());
+}
+
+void QtCamImageMode::start() {
+  // Nothing
+}
+
+void QtCamImageMode::stop() {
+  // Nothing
+}
+
+bool QtCamImageMode::capture(const QString& fileName) {
+  if (!canCapture()) {
+    return false;
+  }
+
+  setFileName(fileName);
+
+  g_object_set(d_ptr->dev->cameraBin, "location", fileName.toUtf8().data(), NULL);
+  g_signal_emit_by_name(d_ptr->dev->cameraBin, "start-capture", NULL);
+
+  return true;
+}
+
+bool QtCamImageMode::setSettings(const QtCamImageSettings& settings) {
+  d_ptr->settings = settings;
+
+  if (!d_ptr->dev->q_ptr->isIdle()) {
+    return false;
+  }
+
+  applySettings();
+
+  return true;
+}
+
+void QtCamImageMode::setProfile(GstEncodingProfile *profile) {
+  if (!d_ptr->dev->cameraBin) {
+    gst_encoding_profile_unref(profile);
+    return;
+  }
+
+  g_object_set(d_ptr->dev->cameraBin, "image-profile", profile, NULL);
+}
diff --git a/lib/qtcamimagemode.h b/lib/qtcamimagemode.h
new file mode 100644 (file)
index 0000000..bf64e4a
--- /dev/null
@@ -0,0 +1,40 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_IMAGE_MODE_H
+#define QT_CAM_IMAGE_MODE_H
+
+#include "qtcammode.h"
+#include <gst/pbutils/encoding-profile.h>
+
+class QtCamDevicePrivate;
+class QtCamImageModePrivate;
+class QtCamImageSettings;
+
+class QtCamImageMode : public QtCamMode {
+  Q_OBJECT
+
+public:
+  QtCamImageMode(QtCamDevicePrivate *d, QObject *parent = 0);
+  ~QtCamImageMode();
+
+  virtual bool canCapture();
+  virtual void applySettings();
+
+  bool capture(const QString& fileName);
+
+  bool setSettings(const QtCamImageSettings& settings);
+
+  void setProfile(GstEncodingProfile *profile);
+
+signals:
+  void imageSaved(const QString& fileName);
+
+protected:
+  virtual void start();
+  virtual void stop();
+
+private:
+  QtCamImageModePrivate *d_ptr;
+};
+
+#endif /* QT_CAM_IMAGE_MODE_H */
diff --git a/lib/qtcamimagesettings.cpp b/lib/qtcamimagesettings.cpp
new file mode 100644 (file)
index 0000000..6bcde00
--- /dev/null
@@ -0,0 +1,80 @@
+#include "qtcamimagesettings.h"
+
+class QtCamImageSettingsPrivate {
+public:
+  QString id;
+  QString name;
+  QSize capture;
+  QSize preview;
+  QSize viewfinder;
+  int numerator;
+  int denominator;
+};
+
+QtCamImageSettings::QtCamImageSettings(const QString& id, const QString& name,
+                                      const QSize& capture, const QSize& preview,
+                                      const QSize& viewfinder,
+                                      int numerator, int denominator) :
+  d_ptr(new QtCamImageSettingsPrivate) {
+
+  d_ptr->id = id;
+  d_ptr->name = name;
+  d_ptr->capture = capture;
+  d_ptr->preview = preview;
+  d_ptr->viewfinder = viewfinder;
+  d_ptr->numerator = numerator;
+  d_ptr->denominator = denominator;
+}
+
+QtCamImageSettings::QtCamImageSettings(const QtCamImageSettings& other) :
+  d_ptr(new QtCamImageSettingsPrivate) {
+
+  d_ptr->id = other.d_ptr->id;
+  d_ptr->name = other.d_ptr->name;
+  d_ptr->capture = other.d_ptr->capture;
+  d_ptr->preview = other.d_ptr->preview;
+  d_ptr->viewfinder = other.d_ptr->viewfinder;
+  d_ptr->numerator = other.d_ptr->numerator;
+  d_ptr->denominator = other.d_ptr->denominator;
+}
+
+QtCamImageSettings::~QtCamImageSettings() {
+  delete d_ptr;
+}
+
+QtCamImageSettings& QtCamImageSettings::operator=(const QtCamImageSettings&
+                                                               other) {
+  d_ptr->id = other.d_ptr->id;
+  d_ptr->name = other.d_ptr->name;
+  d_ptr->capture = other.d_ptr->capture;
+  d_ptr->preview = other.d_ptr->preview;
+  d_ptr->viewfinder = other.d_ptr->viewfinder;
+  d_ptr->numerator = other.d_ptr->numerator;
+  d_ptr->denominator = other.d_ptr->denominator;
+
+  return *this;
+}
+
+QString QtCamImageSettings::id() const {
+  return d_ptr->id;
+}
+
+QString QtCamImageSettings::name() const {
+  return d_ptr->name;
+}
+
+QSize QtCamImageSettings::captureResolution() const {
+  return d_ptr->capture;
+}
+
+QSize QtCamImageSettings::viewfinderResolution() const {
+  return d_ptr->viewfinder;
+}
+
+QSize QtCamImageSettings::previewResolution() const {
+  return d_ptr->preview;
+}
+
+QPair<int, int> QtCamImageSettings::frameRate() const {
+  return qMakePair<int, int>(d_ptr->numerator, d_ptr->denominator);
+}
diff --git a/lib/qtcamimagesettings.h b/lib/qtcamimagesettings.h
new file mode 100644 (file)
index 0000000..940347a
--- /dev/null
@@ -0,0 +1,35 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_IMAGE_SETTINGS_H
+#define QT_CAM_IMAGE_SETTINGS_H
+
+#include <QSize>
+#include <QPair>
+#include <QString>
+
+class QtCamImageSettingsPrivate;
+
+class QtCamImageSettings {
+public:
+  QtCamImageSettings(const QString& id, const QString& name, const QSize& capture,
+                    const QSize& preview, const QSize& viewfinder,
+                    int numerator, int denominator);
+
+  QtCamImageSettings(const QtCamImageSettings& other);
+
+  ~QtCamImageSettings();
+
+  QtCamImageSettings& operator=(const QtCamImageSettings& other);
+
+  QString id() const;
+  QString name() const;
+  QSize captureResolution() const;
+  QSize viewfinderResolution() const;
+  QSize previewResolution() const;
+  QPair<int, int> frameRate() const;
+
+private:
+  QtCamImageSettingsPrivate *d_ptr;
+};
+
+#endif /* QT_CAM_IMAGE_SETTINGS_H */
diff --git a/lib/qtcammetadata.cpp b/lib/qtcammetadata.cpp
new file mode 100644 (file)
index 0000000..6116be7
--- /dev/null
@@ -0,0 +1,158 @@
+#include "qtcammetadata.h"
+#include <gst/gsttaglist.h>
+#include "qtcamdevice.h"
+#include "qtcamdevice_p.h"
+#include <QDebug>
+#include <QDate>
+#include <QTime>
+#include <QDateTime>
+
+const char *orientations[] = {
+  "rotate-0",
+  "rotate-90",
+  "rotate-180",
+  "rotate-270"
+};
+
+class QtCamMetaDataPrivate {
+public:
+  void addTag(const char *tag, const QString& value) {
+    if (!setter) {
+      return;
+    }
+
+    gst_tag_setter_add_tags(setter, GST_TAG_MERGE_REPLACE, tag, value.toUtf8().data(), NULL);
+  }
+
+  void addTag(const char *tag, double value) {
+    if (!setter) {
+      return;
+    }
+
+    gst_tag_setter_add_tags(setter, GST_TAG_MERGE_REPLACE, tag, value, NULL);
+  }
+
+  void addTag(const char *tag, GstDateTime *value) {
+    if (!setter) {
+      return;
+    }
+
+    gst_tag_setter_add_tags(setter, GST_TAG_MERGE_REPLACE, tag, value, NULL);
+  }
+
+  GstTagSetter *setter;
+};
+
+QtCamMetaData::QtCamMetaData(QObject *parent) :
+  QObject(parent), d_ptr(new QtCamMetaDataPrivate) {
+  d_ptr->setter = 0;
+}
+
+QtCamMetaData::~QtCamMetaData() {
+  setDevice(0);
+  delete d_ptr; d_ptr = 0;
+}
+
+void QtCamMetaData::setDevice(QtCamDevice *device) {
+  if (d_ptr->setter) {
+    gst_object_unref(d_ptr->setter);
+    d_ptr->setter = 0;
+  }
+
+  if (!device || !device->d_ptr->cameraBin) {
+    return;
+  }
+
+  if (!GST_IS_TAG_SETTER(device->d_ptr->cameraBin)) {
+    return;
+  }
+
+  d_ptr->setter = GST_TAG_SETTER(gst_object_ref(device->d_ptr->cameraBin));
+}
+
+void QtCamMetaData::setManufacturer(const QString& manufacturer) {
+  d_ptr->addTag(GST_TAG_DEVICE_MANUFACTURER, manufacturer);
+}
+
+void QtCamMetaData::setModel(const QString& model) {
+  d_ptr->addTag(GST_TAG_DEVICE_MODEL, model);
+}
+
+void QtCamMetaData::setCountry(const QString& country) {
+  d_ptr->addTag(GST_TAG_GEO_LOCATION_COUNTRY, country);
+}
+
+void QtCamMetaData::setCity(const QString& city) {
+  d_ptr->addTag(GST_TAG_GEO_LOCATION_CITY, city);
+}
+
+void QtCamMetaData::setSuburb(const QString& suburb) {
+  d_ptr->addTag(GST_TAG_GEO_LOCATION_SUBLOCATION, suburb);
+}
+
+void QtCamMetaData::setLongitude(double longitude) {
+  d_ptr->addTag(GST_TAG_GEO_LOCATION_LONGITUDE, longitude);
+}
+
+void QtCamMetaData::setLatitude(double latitude) {
+  d_ptr->addTag(GST_TAG_GEO_LOCATION_LATITUDE, latitude);
+}
+
+void QtCamMetaData::setElevation(double elevation) {
+  d_ptr->addTag(GST_TAG_GEO_LOCATION_ELEVATION, elevation);
+}
+
+void QtCamMetaData::setOrientation(Orientation orientation) {
+  int len = sizeof(orientations) / sizeof(orientations[0]);
+
+  if (orientation <= 0 || orientation >= len) {
+    orientation = Landscape;
+  }
+
+  d_ptr->addTag(GST_TAG_IMAGE_ORIENTATION, orientations[orientation]);
+}
+
+void QtCamMetaData::setArtist(const QString& artist) {
+  d_ptr->addTag(GST_TAG_ARTIST, artist);
+}
+
+void QtCamMetaData::setDateTime(const QDateTime& dateTime) {
+  QDate d = dateTime.date();
+  QTime t = dateTime.time();
+
+  int day = d.day();
+  int month = d.month();
+  int year = d.year();
+  int hour = t.hour();
+  int minute = t.minute();
+
+  // GstDateTime seconds expects microseconds to be there too :|
+  gdouble seconds = t.second();
+  seconds += t.msec()/(1000.0);
+
+  // Current UTC time. Created through string so that the link between
+  // current and utc is lost and secsTo returns non-zero values.
+  QDateTime utcTime = QDateTime::fromString(dateTime.toUTC().toString());
+  gfloat tzoffset = (utcTime.secsTo(dateTime)/3600.0);
+
+  GstDateTime *dt = gst_date_time_new(tzoffset, year, month, day,
+                                     hour, minute, seconds);
+
+  d_ptr->addTag(GST_TAG_DATE_TIME, dt);
+
+  gst_date_time_unref(dt);
+}
+
+void QtCamMetaData::setCaptureDirection(double direction) {
+  d_ptr->addTag(GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION, direction);
+}
+
+void QtCamMetaData::setHorizontalError(double error) {
+  d_ptr->addTag(GST_TAG_GEO_LOCATION_HORIZONTAL_ERROR, error);
+}
+
+void QtCamMetaData::reset() {
+  if (d_ptr->setter) {
+    gst_tag_setter_reset_tags(d_ptr->setter);
+  }
+}
diff --git a/lib/qtcammetadata.h b/lib/qtcammetadata.h
new file mode 100644 (file)
index 0000000..a051fc6
--- /dev/null
@@ -0,0 +1,49 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_META_DATA_H
+#define QT_CAM_META_DATA_H
+
+#include <QObject>
+
+class QtCamMetaDataPrivate;
+class QDateTime;
+class QtCamDevice;
+
+class QtCamMetaData : public QObject {
+  Q_OBJECT
+
+public:
+
+  typedef enum {
+    Landscape = 0,
+    Portrait,
+    InvertedLandscape,
+    InvertedPortrait
+  } Orientation;
+
+  QtCamMetaData(QObject *parent = 0);
+  ~QtCamMetaData();
+
+  void setDevice(QtCamDevice *device);
+
+  void setManufacturer(const QString& manufacturer);
+  void setModel(const QString& model);
+  void setCountry(const QString& country);
+  void setCity(const QString& city);
+  void setSuburb(const QString& suburb);
+  void setLongitude(double longitude);
+  void setLatitude(double latitude);
+  void setElevation(double elevetion);
+  void setOrientation(Orientation orientation);
+  void setArtist(const QString& artist);
+  void setDateTime(const QDateTime& dateTime);
+  void setCaptureDirection(double direction);
+  void setHorizontalError(double error);
+
+  void reset();
+
+private:
+  QtCamMetaDataPrivate *d_ptr;
+};
+
+#endif /* QT_CAM_META_DATA_H */
diff --git a/lib/qtcammode.cpp b/lib/qtcammode.cpp
new file mode 100644 (file)
index 0000000..e21a6b1
--- /dev/null
@@ -0,0 +1,203 @@
+#include "qtcammode.h"
+#include "qtcammode_p.h"
+#include "qtcamdevice_p.h"
+#include "qtcamdevice.h"
+#include <QDebug>
+#include "qtcamgstreamermessagehandler.h"
+#include "qtcamgstreamermessagelistener.h"
+#include <gst/video/video.h>
+#include <QImage>
+
+#define PREVIEW_CAPS "video/x-raw-rgb, width = (int) %1, height = (int) %2, bpp = (int) 32, depth = (int) 24, red_mask = (int) 65280, green_mask = (int) 16711680, blue_mask = (int) -16777216"
+
+#define CAPS "video/x-raw-yuv, width = (int) %1, height = (int) %2, framerate = (fraction) %3/%4"
+
+class PreviewImageHandler : public QtCamGStreamerMessageHandler {
+public:
+  PreviewImageHandler(QtCamMode *m, QObject *parent = 0) :
+    QtCamGStreamerMessageHandler("preview-image", parent) {
+    mode = m;
+  }
+
+  virtual void handleMessage(GstMessage *message) {
+    const GstStructure *s = gst_message_get_structure(message);
+    if (!s) {
+      return;
+    }
+
+    const char *file = gst_structure_get_string(s, "location");
+    if (!file) {
+      return;
+    }
+
+    const GValue *val = gst_structure_get_value(s, "buffer");
+    if (!val) {
+      return;
+    }
+
+    GstBuffer *buffer = gst_value_get_buffer(val);
+    if (!buffer) {
+      return;
+    }
+
+    int width, height;
+    GstVideoFormat fmt;
+    if (!gst_video_format_parse_caps(buffer->caps, &fmt, &width, &height)) {
+      return;
+    }
+
+    if (fmt !=  GST_VIDEO_FORMAT_BGRx || width <= 0 || height <= 0) {
+      return;
+    }
+
+    QImage image(buffer->data, width, height, QImage::Format_RGB32);
+
+    // We need to copy because GStreamer will free the buffer after we return
+    // and since QImage doesn't copythe data by default we will end up with garbage.
+    // TODO: consider a QImage subclass that takes a GstBuffer reference
+    QImage cp = image.copy();
+
+    QString fileName = QString::fromUtf8(file);
+
+    QMetaObject::invokeMethod(mode, "previewAvailable",
+                             Q_ARG(QImage, cp), Q_ARG(QString, fileName));
+  }
+
+  QtCamMode *mode;
+};
+
+class DoneHandler : public QtCamGStreamerMessageHandler {
+public:
+  DoneHandler(QtCamMode *m, const char *done, QObject *parent = 0) :
+    QtCamGStreamerMessageHandler(done, parent) {
+    mode = m;
+  }
+
+  virtual void handleMessage(GstMessage *message) {
+    const GstStructure *s = gst_message_get_structure(message);
+    if (gst_structure_has_field(s, "filename")) {
+      const char *str = gst_structure_get_string(s, "filename");
+      if (str) {
+       fileName = QString::fromUtf8(str);
+      }
+    }
+
+    QMetaObject::invokeMethod(mode, "saved", Q_ARG(QString, fileName));
+  }
+
+  QString fileName;
+  QtCamMode *mode;
+};
+
+QtCamMode::QtCamMode(QtCamModePrivate *d, const char *mode, const char *done, QObject *parent) :
+  QObject(parent), d_ptr(d) {
+
+  d_ptr->id = d_ptr->modeId(mode);
+  d_ptr->previewImageHandler = new PreviewImageHandler(this, this);
+  d_ptr->doneHandler = new DoneHandler(this, done, this);
+}
+
+QtCamMode::~QtCamMode() {
+  delete d_ptr; d_ptr = 0;
+}
+
+void QtCamMode::activate() {
+  if (!d_ptr->dev->cameraBin) {
+    return;
+  }
+
+  if (d_ptr->dev->active == this) {
+    return;
+  }
+
+  if (d_ptr->dev->active) {
+    d_ptr->dev->active->deactivate();
+  }
+
+  d_ptr->dev->active = this;
+
+  // TODO: check that we can actually do it. Perhaps the pipeline is busy.
+
+  g_object_set(d_ptr->dev->cameraBin, "mode", d_ptr->id, NULL);
+
+  d_ptr->dev->listener->addHandler(d_ptr->previewImageHandler);
+  d_ptr->dev->listener->addHandler(d_ptr->doneHandler);
+
+  start();
+
+  applySettings();
+}
+
+void QtCamMode::deactivate() {
+  if (d_ptr->dev->active != this) {
+    return;
+  }
+
+  d_ptr->dev->listener->removeHandler(d_ptr->previewImageHandler);
+  d_ptr->dev->listener->removeHandler(d_ptr->doneHandler);
+
+  d_ptr->previewImageHandler->setParent(this);
+  d_ptr->doneHandler->setParent(this);
+
+  stop();
+
+  d_ptr->dev->active = 0;
+}
+
+bool QtCamMode::canCapture() {
+  return d_ptr->dev->cameraBin && isActive() && d_ptr->dev->q_ptr->isRunning();
+}
+
+bool QtCamMode::isActive() {
+  return d_ptr->dev->active == this;
+}
+
+void QtCamMode::setCaps(const char *property, const QSize& resolution,
+                       const QPair<int, int> frameRate) {
+
+  if (!d_ptr->dev->cameraBin) {
+    return;
+  }
+
+  // TODO: allow proceeding without specifying a frame rate (maybe we can calculate it ?)
+  if (frameRate.first <= 0 || frameRate.second <= 0) {
+    return;
+  }
+
+  if (resolution.width() <= 0 || resolution.width() <= 0) {
+    return;
+  }
+
+  QString capsString = QString(CAPS)
+    .arg(resolution.width()).arg(resolution.height())
+    .arg(frameRate.first).arg(frameRate.second);
+
+  GstCaps *caps = gst_caps_from_string(capsString.toAscii());
+
+  g_object_set(d_ptr->dev->cameraBin, property, caps, NULL);
+
+  gst_caps_unref(caps);
+}
+
+void QtCamMode::setPreviewSize(const QSize& size) {
+  if (!d_ptr->dev->cameraBin) {
+    return;
+  }
+
+  if (size.width() <= 0 && size.height() <= 0) {
+    g_object_set(d_ptr->dev->cameraBin, "preview-caps", NULL, "post-previews", FALSE, NULL);
+  }
+  else {
+    QString preview = QString(PREVIEW_CAPS).arg(size.width()).arg(size.height());
+
+    GstCaps *caps = gst_caps_from_string(preview.toAscii());
+
+    g_object_set(d_ptr->dev->cameraBin, "preview-caps", caps, "post-previews", TRUE, NULL);
+
+    gst_caps_unref(caps);
+  }
+}
+
+void QtCamMode::setFileName(const QString& fileName) {
+  d_ptr->doneHandler->fileName = fileName;
+}
diff --git a/lib/qtcammode.h b/lib/qtcammode.h
new file mode 100644 (file)
index 0000000..7351a14
--- /dev/null
@@ -0,0 +1,45 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_MODE_H
+#define QT_CAM_MODE_H
+
+#include <QObject>
+#include <QPair>
+
+class QtCamModePrivate;
+class QtCamDevicePrivate;
+class QSize;
+class QImage;
+
+class QtCamMode : public QObject {
+  Q_OBJECT
+
+public:
+  QtCamMode(QtCamModePrivate *d, const char *mode, const char *done, QObject *parent = 0);
+  virtual ~QtCamMode();
+
+  void activate();
+  void deactivate();
+
+  virtual bool canCapture();
+  bool isActive();
+
+  virtual void applySettings() = 0;
+
+signals:
+  void previewAvailable(const QImage& image, const QString& fileName);
+  void saved(const QString& fileName);
+
+protected:
+  virtual void start() = 0;
+  virtual void stop() = 0;
+
+  void setCaps(const char *property, const QSize& resolution, const QPair<int, int> frameRate);
+  void setPreviewSize(const QSize& size);
+
+  void setFileName(const QString& fileName);
+
+  QtCamModePrivate *d_ptr;
+};
+
+#endif /* QT_CAM_MODE_H */
diff --git a/lib/qtcammode_p.h b/lib/qtcammode_p.h
new file mode 100644 (file)
index 0000000..9a96ab1
--- /dev/null
@@ -0,0 +1,75 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_MODE_P_H
+#define QT_CAM_MODE_P_H
+
+#include <QSize>
+#include "qtcamdevice_p.h"
+#include <gst/pbutils/encoding-profile.h>
+#include <gst/pbutils/encoding-target.h>
+
+class QtCamDevicePrivate;
+class PreviewImageHandler;
+class DoneHandler;
+
+class QtCamModePrivate {
+public:
+  QtCamModePrivate(QtCamDevicePrivate *d) : id(-1), dev(d) {}
+  virtual ~QtCamModePrivate() {}
+
+  int modeId(const char *mode) {
+    if (!dev->cameraBin) {
+      return -1;
+    }
+
+    GParamSpec *pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(dev->cameraBin),
+                                                    "mode");
+    if (!pspec) {
+      return -1;
+    }
+
+    if (!G_IS_PARAM_SPEC_ENUM(pspec)) {
+      return -1;
+    }
+
+    GParamSpecEnum *e = (GParamSpecEnum *)pspec;
+    GEnumClass *klass = e->enum_class;
+
+    for (unsigned x = 0; x < klass->n_values; x++) {
+      if (QLatin1String(mode) == QLatin1String(klass->values[x].value_nick)) {
+       return klass->values[x].value;
+      }
+    }
+
+    return -1;
+  }
+
+  GstEncodingProfile *loadProfile(const QString& path, const QString& name) {
+    GError *error = NULL;
+
+    GstEncodingTarget *target = gst_encoding_target_load_from_file(path.toUtf8().data(), &error);
+    if (!target) {
+      qCritical() << "Failed to load encoding target from" << path << error->message;
+      g_error_free(error);
+      return 0;
+    }
+
+    GstEncodingProfile *profile = gst_encoding_target_get_profile(target, name.toUtf8().data());
+    if (!profile) {
+      qCritical() << "Failed to load encoding profile from" << path;
+      gst_encoding_target_unref(target);
+      return 0;
+    }
+
+    gst_encoding_target_unref(target);
+
+    return profile;
+  }
+
+  int id;
+  QtCamDevicePrivate *dev;
+  PreviewImageHandler *previewImageHandler;
+  DoneHandler *doneHandler;
+};
+
+#endif /* QT_CAM_MODE_P_H */
diff --git a/lib/qtcampreviewhelper.h b/lib/qtcampreviewhelper.h
new file mode 100644 (file)
index 0000000..528f77f
--- /dev/null
@@ -0,0 +1,20 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_PREVIEW_HELPER_H
+#define QT_CAM_PREVIEW_HELPER_H
+
+#include <QObject>
+
+
+class QtCamPreviewHelper : public QObject {
+  Q_OBJECT
+
+public:
+  QtCamPreviewHelper(QObject *parent = 0);
+  ~QtCamPreviewHelper();
+
+  void enable();
+  void disable();
+};
+
+#endif /* QT_CAM_PREVIEW_HELPER_H */
diff --git a/lib/qtcamscanner.cpp b/lib/qtcamscanner.cpp
new file mode 100644 (file)
index 0000000..ace6f75
--- /dev/null
@@ -0,0 +1,106 @@
+#include "qtcamscanner.h"
+#include "qtcamconfig.h"
+#include <QDir>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <linux/videodev2.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <gst/gst.h>
+
+class QtCamScannerPrivate {
+public:
+  void scanEnum();
+  void scanV4l2();
+
+  QtCamConfig *conf;
+  QList<QPair<QString, QVariant> > devices;
+};
+
+void QtCamScannerPrivate::scanEnum() {
+  // Too bad there's no way to get the values of an enum without creating the element :(
+  GstElement *elem = gst_element_factory_make(conf->videoSource().toAscii(), NULL);
+  if (!elem) {
+    return;
+  }
+
+  GParamSpec *spec = g_object_class_find_property(G_OBJECT_GET_CLASS(elem),
+                                                 conf->deviceScannerProperty().toAscii());
+  if (!spec) {
+    gst_object_unref(elem);
+    return;
+  }
+
+  if (!G_IS_PARAM_SPEC_ENUM(spec)) {
+    gst_object_unref(elem);
+    return;
+  }
+
+  GParamSpecEnum *e = G_PARAM_SPEC_ENUM(spec);
+  // First add the default:
+  devices << qMakePair<QString, QVariant>(e->enum_class->values[e->default_value].value_name,
+                                         QByteArray::number(e->default_value));
+
+  for (int x = e->enum_class->minimum; x <= e->enum_class->maximum; x++) {
+    if (x != e->default_value) {
+      devices << qMakePair<QString, QVariant>(e->enum_class->values[x].value_name,
+                                             QByteArray::number(x));
+    }
+  }
+
+  gst_object_unref(elem);
+}
+
+void QtCamScannerPrivate::scanV4l2() {
+  QDir d("/dev/", "video?", QDir::Name | QDir::IgnoreCase, QDir::System);
+
+  QStringList entries = d.entryList();
+
+  foreach (const QString& dv, entries) {
+    QString dev = d.absoluteFilePath(dv);
+    struct v4l2_capability cap;
+    memset(&cap, 0x0, sizeof(cap));
+
+    int fd = open(dev.toLocal8Bit().data(), O_RDONLY);
+    if (fd == -1) {
+      continue;
+    }
+
+    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) != 0) {
+      close(fd);
+      continue;
+    }
+
+    close(fd);
+
+    if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
+      devices << qMakePair<QString, QVariant>((char *)cap.card, dev.toLocal8Bit());
+    }
+  }
+}
+
+QtCamScanner::QtCamScanner(QtCamConfig *conf, QObject *parent) :
+  QObject(parent), d_ptr(new QtCamScannerPrivate) {
+
+  d_ptr->conf = conf;
+}
+
+QtCamScanner::~QtCamScanner() {
+  delete d_ptr; d_ptr = 0;
+}
+
+void QtCamScanner::refresh() {
+  d_ptr->devices.clear();
+
+  if (d_ptr->conf->deviceScannerType() == SCANNER_TYPE_ENUM) {
+    d_ptr->scanEnum();
+  }
+  else {
+    d_ptr->scanV4l2();
+  }
+}
+
+QList<QPair<QString, QVariant> > QtCamScanner::devices() const {
+  return d_ptr->devices;
+}
diff --git a/lib/qtcamscanner.h b/lib/qtcamscanner.h
new file mode 100644 (file)
index 0000000..211154f
--- /dev/null
@@ -0,0 +1,27 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_SCANNER_H
+#define QT_CAM_SCANNER_H
+
+#include <QObject>
+#include <QPair>
+#include <QVariant>
+
+class QtCamConfig;
+class QtCamScannerPrivate;
+
+class QtCamScanner : public QObject {
+  Q_OBJECT
+
+public:
+  QtCamScanner(QtCamConfig *conf, QObject *parent = 0);
+  ~QtCamScanner();
+
+  void refresh();
+  QList<QPair<QString, QVariant> > devices() const;
+
+private:
+  QtCamScannerPrivate *d_ptr;
+};
+
+#endif /* QT_CAM_SCANNER_H */
diff --git a/lib/qtcamvideomode.cpp b/lib/qtcamvideomode.cpp
new file mode 100644 (file)
index 0000000..0e089bd
--- /dev/null
@@ -0,0 +1,108 @@
+#include "qtcamvideomode.h"
+#include "qtcammode_p.h"
+#include <QDebug>
+#include "qtcamdevice_p.h"
+#include "qtcamdevice.h"
+#include "qtcamvideosettings.h"
+
+class QtCamVideoModePrivate : public QtCamModePrivate {
+public:
+  QtCamVideoModePrivate(QtCamDevicePrivate *dev) :
+  QtCamModePrivate(dev),
+  settings(dev->conf->defaultVideoSettings()) {
+
+  }
+
+  ~QtCamVideoModePrivate() {}
+
+  QtCamVideoSettings settings;
+};
+
+QtCamVideoMode::QtCamVideoMode(QtCamDevicePrivate *d, QObject *parent) :
+  QtCamMode(new QtCamVideoModePrivate(d), "mode-video", "video-done", parent) {
+
+  d_ptr = (QtCamVideoModePrivate *)QtCamMode::d_ptr;
+
+  QString name = d_ptr->dev->conf->videoEncodingProfileName();
+  QString path = d_ptr->dev->conf->videoEncodingProfilePath();
+
+  if (!name.isEmpty() && !path.isEmpty()) {
+    GstEncodingProfile *profile = d_ptr->loadProfile(path, name);
+    if (profile) {
+      setProfile(profile);
+    }
+  }
+}
+
+QtCamVideoMode::~QtCamVideoMode() {
+
+}
+
+bool QtCamVideoMode::canCapture() {
+  return d_ptr->dev->q_ptr->isIdle();
+}
+
+void QtCamVideoMode::applySettings() {
+  setCaps("video-capture-caps", d_ptr->settings.captureResolution(),
+         d_ptr->settings.frameRate());
+  setCaps("viewfinder-caps", d_ptr->settings.captureResolution(),
+         d_ptr->settings.frameRate());
+  setPreviewSize(d_ptr->settings.previewResolution());
+}
+
+void QtCamVideoMode::start() {
+  // Nothing
+}
+
+void QtCamVideoMode::stop() {
+  if (isRecording()) {
+    stopRecording();
+  }
+}
+
+bool QtCamVideoMode::isRecording() {
+  return !d_ptr->dev->q_ptr->isIdle();
+}
+
+bool QtCamVideoMode::startRecording(const QString& fileName) {
+  if (!canCapture() || isRecording()) {
+    return false;
+  }
+
+  setFileName(fileName);
+
+  g_object_set(d_ptr->dev->cameraBin, "location", fileName.toUtf8().data(), NULL);
+  g_signal_emit_by_name(d_ptr->dev->cameraBin, "start-capture", NULL);
+
+  return true;
+}
+
+bool QtCamVideoMode::stopRecording() {
+  if (isRecording()) {
+    g_signal_emit_by_name(d_ptr->dev->cameraBin, "stop-capture", NULL);
+  }
+
+  return true;
+}
+
+bool QtCamVideoMode::setSettings(const QtCamVideoSettings& settings) {
+  d_ptr->settings = settings;
+
+  if (isRecording()) {
+    return false;
+  }
+
+  applySettings();
+
+  return true;
+}
+
+
+void QtCamVideoMode::setProfile(GstEncodingProfile *profile) {
+  if (!d_ptr->dev->cameraBin) {
+    gst_encoding_profile_unref(profile);
+    return;
+  }
+
+  g_object_set(d_ptr->dev->cameraBin, "video-profile", profile, NULL);
+}
diff --git a/lib/qtcamvideomode.h b/lib/qtcamvideomode.h
new file mode 100644 (file)
index 0000000..c6fb34b
--- /dev/null
@@ -0,0 +1,39 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_VIDEO_MODE_H
+#define QT_CAM_VIDEO_MODE_H
+
+#include "qtcammode.h"
+#include <gst/pbutils/encoding-profile.h>
+
+class QtCamDevicePrivate;
+class QtCamVideoModePrivate;
+class QtCamVideoSettings;
+
+class QtCamVideoMode : public QtCamMode {
+  Q_OBJECT
+
+public:
+  QtCamVideoMode(QtCamDevicePrivate *d, QObject *parent = 0);
+  ~QtCamVideoMode();
+
+  virtual bool canCapture();
+  virtual void applySettings();
+
+  bool isRecording();
+  bool startRecording(const QString& fileName);
+  bool stopRecording();
+
+  bool setSettings(const QtCamVideoSettings& settings);
+
+  void setProfile(GstEncodingProfile *profile);
+
+protected:
+  virtual void start();
+  virtual void stop();
+
+private:
+  QtCamVideoModePrivate *d_ptr;
+};
+
+#endif /* QT_CAM_VIDEO_MODE_H */
diff --git a/lib/qtcamvideosettings.cpp b/lib/qtcamvideosettings.cpp
new file mode 100644 (file)
index 0000000..7e3f478
--- /dev/null
@@ -0,0 +1,71 @@
+#include "qtcamvideosettings.h"
+
+class QtCamVideoSettingsPrivate {
+public:
+  QString id;
+  QString name;
+  QSize capture;
+  QSize preview;
+  int numerator;
+  int denominator;
+};
+
+QtCamVideoSettings::QtCamVideoSettings(const QString& id, const QString& name,
+                                      const QSize& capture, const QSize& preview,
+                                      int numerator, int denominator) :
+  d_ptr(new QtCamVideoSettingsPrivate) {
+
+  d_ptr->id = id;
+  d_ptr->name = name;
+  d_ptr->capture = capture;
+  d_ptr->preview = preview;
+  d_ptr->numerator = numerator;
+  d_ptr->denominator = denominator;
+}
+
+QtCamVideoSettings::QtCamVideoSettings(const QtCamVideoSettings& other) :
+  d_ptr(new QtCamVideoSettingsPrivate) {
+
+  d_ptr->id = other.d_ptr->id;
+  d_ptr->name = other.d_ptr->name;
+  d_ptr->capture = other.d_ptr->capture;
+  d_ptr->preview = other.d_ptr->preview;
+  d_ptr->numerator = other.d_ptr->numerator;
+  d_ptr->denominator = other.d_ptr->denominator;
+}
+
+QtCamVideoSettings::~QtCamVideoSettings() {
+  delete d_ptr;
+}
+
+QtCamVideoSettings& QtCamVideoSettings::operator=(const QtCamVideoSettings&
+                                                               other) {
+  d_ptr->id = other.d_ptr->id;
+  d_ptr->name = other.d_ptr->name;
+  d_ptr->capture = other.d_ptr->capture;
+  d_ptr->preview = other.d_ptr->preview;
+  d_ptr->numerator = other.d_ptr->numerator;
+  d_ptr->denominator = other.d_ptr->denominator;
+
+  return *this;
+}
+
+QString QtCamVideoSettings::id() const {
+  return d_ptr->id;
+}
+
+QString QtCamVideoSettings::name() const {
+  return d_ptr->name;
+}
+
+QSize QtCamVideoSettings::captureResolution() const {
+  return d_ptr->capture;
+}
+
+QSize QtCamVideoSettings::previewResolution() const {
+  return d_ptr->preview;
+}
+
+QPair<int, int> QtCamVideoSettings::frameRate() const {
+  return qMakePair<int, int>(d_ptr->numerator, d_ptr->denominator);
+}
diff --git a/lib/qtcamvideosettings.h b/lib/qtcamvideosettings.h
new file mode 100644 (file)
index 0000000..a25ab83
--- /dev/null
@@ -0,0 +1,34 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_VIDEO_SETTINGS_H
+#define QT_CAM_VIDEO_SETTINGS_H
+
+#include <QSize>
+#include <QPair>
+#include <QString>
+
+class QtCamVideoSettingsPrivate;
+
+class QtCamVideoSettings {
+public:
+  QtCamVideoSettings(const QString& id, const QString& name,
+                    const QSize& capture, const QSize& preview,
+                    int numerator, int denominator);
+
+  QtCamVideoSettings(const QtCamVideoSettings& other);
+
+  ~QtCamVideoSettings();
+
+  QtCamVideoSettings& operator=(const QtCamVideoSettings& other);
+
+  QString id() const;
+  QString name() const;
+  QSize captureResolution() const;
+  QSize previewResolution() const;
+  QPair<int, int> frameRate() const;
+
+private:
+  QtCamVideoSettingsPrivate *d_ptr;
+};
+
+#endif /* QT_CAM_VIDEO_SETTINGS_H */
diff --git a/lib/qtcamviewfinder.cpp b/lib/qtcamviewfinder.cpp
new file mode 100644 (file)
index 0000000..eda49f4
--- /dev/null
@@ -0,0 +1,9 @@
+#include "qtcamviewfinder.h"
+
+QtCamViewfinder::QtCamViewfinder() {
+
+}
+
+QtCamViewfinder::~QtCamViewfinder() {
+
+}
diff --git a/lib/qtcamviewfinder.h b/lib/qtcamviewfinder.h
new file mode 100644 (file)
index 0000000..cefa84b
--- /dev/null
@@ -0,0 +1,19 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_VIEWFINDER_H
+#define QT_CAM_VIEWFINDER_H
+
+#include <gst/gst.h>
+
+class QtCamDevice;
+
+class QtCamViewfinder {
+public:
+  QtCamViewfinder();
+  virtual ~QtCamViewfinder();
+
+  virtual GstElement *sinkElement() = 0;
+  virtual bool setDevice(QtCamDevice *device) = 0;
+};
+
+#endif /* QT_CAM_VIEWFINDER_H */
diff --git a/lib/qtcamviewfinderrenderer.cpp b/lib/qtcamviewfinderrenderer.cpp
new file mode 100644 (file)
index 0000000..7ab63e6
--- /dev/null
@@ -0,0 +1,39 @@
+#include "qtcamviewfinderrenderer.h"
+#include "qtcamconfig.h"
+#include <QMap>
+#include <QDebug>
+
+static QMap<QString, QMetaObject> _renderers;
+
+QtCamViewfinderRenderer::QtCamViewfinderRenderer(QtCamConfig *config, QObject *parent) :
+  QObject(parent) {
+
+  Q_UNUSED(config);
+}
+
+QtCamViewfinderRenderer::~QtCamViewfinderRenderer() {
+
+}
+
+QtCamViewfinderRenderer *QtCamViewfinderRenderer::create(QtCamConfig *config, QObject *parent) {
+  QString key = config->viewfinderRenderer();
+  if (!_renderers.contains(key)) {
+    qCritical() << "Unknown renderer" << key;
+    return 0;
+  }
+
+  QObject *obj = _renderers[key].newInstance(Q_ARG(QtCamConfig *, config),
+                                            Q_ARG(QObject *, parent));
+
+  if (!obj) {
+    qCritical() << "Failed to create renderer" << key;
+    return 0;
+  }
+
+  return dynamic_cast<QtCamViewfinderRenderer *>(obj);
+}
+
+int QtCamViewfinderRenderer::registerRenderer(const QString& key, const QMetaObject& meta) {
+  _renderers[key] = meta;
+  return _renderers.size() - 1;
+}
diff --git a/lib/qtcamviewfinderrenderer.h b/lib/qtcamviewfinderrenderer.h
new file mode 100644 (file)
index 0000000..4e46af0
--- /dev/null
@@ -0,0 +1,38 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_VIEWFINDER_RENDERER_H
+#define QT_CAM_VIEWFINDER_RENDERER_H
+
+#include <QObject>
+#include <gst/gst.h>
+
+class QtCamConfig;
+class QMetaObject;
+class QPainter;
+class QSizeF;
+
+class QtCamViewfinderRenderer : public QObject {
+  Q_OBJECT
+
+public:
+  static QtCamViewfinderRenderer *create(QtCamConfig *config, QObject *parent = 0);
+  static int registerRenderer(const QString& key, const QMetaObject& meta);
+
+  virtual ~QtCamViewfinderRenderer();
+
+  virtual void paint(QPainter *painter) = 0;
+  virtual void resize(const QSizeF& size) = 0;
+  virtual void reset() = 0;
+  virtual GstElement *sinkElement() = 0;
+
+protected:
+  QtCamViewfinderRenderer(QtCamConfig *config, QObject *parent = 0);
+
+signals:
+  void updateRequested();
+};
+
+#define QT_CAM_VIEWFINDER_RENDERER(key, klass) \
+static int klass_seq = QtCamViewfinderRenderer::registerRenderer(key, klass::staticMetaObject)
+
+#endif /* QT_CAM_VIEWFINDER_RENDERER_H */
diff --git a/lib/qtcamviewfinderrenderergeneric.cpp b/lib/qtcamviewfinderrenderergeneric.cpp
new file mode 100644 (file)
index 0000000..8789226
--- /dev/null
@@ -0,0 +1,143 @@
+#include "qtcamviewfinderrenderergeneric.h"
+#include <QDebug>
+#include <gst/video/video.h>
+#include <QPainter>
+
+QT_CAM_VIEWFINDER_RENDERER("generic", QtCamViewfinderRendererGeneric);
+
+#define CAPS "video/x-raw-rgb, bpp = (int) 32, depth = (int) 24, red_mask = (int) 65280, green_mask = (int) 16711680, blue_mask = (int) -16777216"
+
+// TODO: this needs to be debugged or rewritten. There are race conditions.
+QtCamViewfinderRendererGeneric::QtCamViewfinderRendererGeneric(QtCamConfig *config,
+                                                              QObject *parent) :
+  QtCamViewfinderRenderer(config, parent), m_elem(0), m_sink(0) {
+
+}
+
+QtCamViewfinderRendererGeneric::~QtCamViewfinderRendererGeneric() {
+  m_mutex.lock();
+
+  if (m_elem) {
+    g_object_remove_toggle_ref(G_OBJECT(m_elem), (GToggleNotify)sink_notify, this);
+    m_elem = 0;
+
+    g_signal_handlers_disconnect_by_data(m_sink, this);
+  }
+
+  m_mutex.unlock();
+}
+
+void QtCamViewfinderRendererGeneric::paint(QPainter *painter) {
+  // TODO: scale and keep aspect ratio.
+  m_mutex.lock();
+
+  if (!m_image.isNull()) {
+    painter->drawImage(QRectF(QPointF(0, 0), m_size), m_image);
+  }
+
+  m_mutex.unlock();
+}
+
+void QtCamViewfinderRendererGeneric::resize(const QSizeF& size) {
+  m_size = size;
+}
+
+void QtCamViewfinderRendererGeneric::reset() {
+  m_image = QImage();
+}
+
+GstElement *QtCamViewfinderRendererGeneric::sinkElement() {
+  if (!m_elem) {
+    m_elem = gst_bin_new("QtCamViewfinderRendererGenericBin");
+    if (!m_elem) {
+      qCritical() << "Failed to create sink bin";
+      return 0;
+    }
+
+    GstElement *sink = gst_element_factory_make("fakesink", "QtCamViewfinderRendererGenericSink");
+    if (!sink) {
+      qCritical() << "Failed to create fakesink";
+      gst_object_unref(m_elem);
+      m_elem = 0;
+      return 0;
+    }
+
+    g_object_set(G_OBJECT(sink), "signal-handoffs", TRUE, "sync", TRUE, NULL);
+    g_signal_connect(sink, "handoff", G_CALLBACK(on_gst_buffer), this);
+
+    GstElement *csp = gst_element_factory_make("ffmpegcolorspace",
+                                              "QtCamViewfinderRendererGenericCsp");
+    if (!csp) {
+      qCritical() << "Failed to create ffmpegcolorspace";
+      gst_object_unref(sink);
+      gst_object_unref(m_elem);
+      m_elem = 0;
+      return 0;
+    }
+
+    GstElement *filter = gst_element_factory_make("capsfilter",
+                                                 "QtCamViewfinderRendererGenericCapsFilter");
+    if (!filter) {
+      qCritical() << "Failed to create capsfilter";
+      gst_object_unref(sink);
+      gst_object_unref(csp);
+      gst_object_unref(m_elem);
+      m_elem = 0;
+      return 0;
+    }
+
+    GstCaps *caps = gst_caps_from_string(CAPS);
+    g_object_set(filter, "caps", caps, NULL);
+    gst_caps_unref(caps);
+
+    gst_bin_add_many(GST_BIN(m_elem), csp, filter, sink, NULL);
+    gst_element_link_many(csp, filter, sink, NULL);
+
+    GstPad *pad = gst_element_get_static_pad(csp, "sink");
+    gst_element_add_pad(m_elem, gst_ghost_pad_new("sink", pad));
+    gst_object_unref(pad);
+
+    g_object_add_toggle_ref(G_OBJECT(m_elem), (GToggleNotify)sink_notify, this);
+
+    m_sink = sink;
+  }
+
+  return m_elem;
+}
+
+void QtCamViewfinderRendererGeneric::on_gst_buffer(GstElement *element,
+                                                  GstBuffer *buf, GstPad *pad,
+                                                  QtCamViewfinderRendererGeneric *q) {
+
+  q->m_mutex.lock();
+
+  if (!q->m_elem) {
+    q->m_mutex.unlock();
+    return;
+  }
+
+  int width, height;
+
+  if (!gst_video_get_size(pad, &width, &height)) {
+    return;
+  }
+
+  QImage image(buf->data, width, height, QImage::Format_RGB32);
+
+  q->m_image = image.copy();
+
+  QMetaObject::invokeMethod(q, "updateRequested", Qt::QueuedConnection);
+
+  q->m_mutex.unlock();
+}
+
+void QtCamViewfinderRendererGeneric::sink_notify(QtCamViewfinderRendererGeneric *q,
+                                                GObject *object,
+                                                gboolean is_last_ref) {
+
+  if (is_last_ref) {
+    g_object_remove_toggle_ref(G_OBJECT(q->m_elem), (GToggleNotify)sink_notify, q);
+    q->m_elem = 0;
+    q->m_sink = 0;
+  }
+}
diff --git a/lib/qtcamviewfinderrenderergeneric.h b/lib/qtcamviewfinderrenderergeneric.h
new file mode 100644 (file)
index 0000000..36b8381
--- /dev/null
@@ -0,0 +1,37 @@
+// -*- c++ -*-
+
+#ifndef QT_CAM_VIEWFINDER_RENDERER_GENERIC_H
+#define QT_CAM_VIEWFINDER_RENDERER_GENERIC_H
+
+#include "qtcamviewfinderrenderer.h"
+#include <QImage>
+#include <QMutex>
+
+class QtCamViewfinderRendererGeneric : public QtCamViewfinderRenderer {
+  Q_OBJECT
+
+public:
+  Q_INVOKABLE QtCamViewfinderRendererGeneric(QtCamConfig *config, QObject *parent = 0);
+
+  ~QtCamViewfinderRendererGeneric();
+
+  virtual void paint(QPainter *painter);
+  virtual void resize(const QSizeF& size);
+  virtual void reset();
+  virtual GstElement *sinkElement();
+
+private:
+  static void on_gst_buffer(GstElement *element, GstBuffer *buf, GstPad *pad,
+                           QtCamViewfinderRendererGeneric *q);
+
+  static void sink_notify(QtCamViewfinderRendererGeneric *q, GObject *object,
+                         gboolean is_last_ref);
+
+  GstElement *m_elem;
+  GstElement *m_sink;
+  QImage m_image;
+  QMutex m_mutex;
+  QSizeF m_size;
+};
+
+#endif /* QT_CAM_VIEWFINDER_RENDERER_GENERIC_H */