From ddd8b7037bf34c35800f2a8cec0af45fcbd72a68 Mon Sep 17 00:00:00 2001 From: Mohammed Sameer Date: Sun, 26 Aug 2012 09:49:17 +0300 Subject: [PATCH] Initial implementation --- lib/lib.pro | 28 +++ lib/main.cpp | 143 +++++++++++++++ lib/main.h | 37 ++++ lib/qtcamconfig.cpp | 166 ++++++++++++++++++ lib/qtcamconfig.h | 56 ++++++ lib/qtcamdevice.cpp | 230 +++++++++++++++++++++++++ lib/qtcamdevice.h | 53 ++++++ lib/qtcamdevice_p.h | 143 +++++++++++++++ lib/qtcamera.cpp | 77 +++++++++ lib/qtcamera.h | 36 ++++ lib/qtcamgraphicsviewfinder.cpp | 102 +++++++++++ lib/qtcamgraphicsviewfinder.h | 35 ++++ lib/qtcamgstreamermessagehandler.cpp | 26 +++ lib/qtcamgstreamermessagehandler.h | 29 ++++ lib/qtcamgstreamermessagelistener.cpp | 218 +++++++++++++++++++++++ lib/qtcamgstreamermessagelistener.h | 36 ++++ lib/qtcamimagemode.cpp | 104 +++++++++++ lib/qtcamimagemode.h | 40 +++++ lib/qtcamimagesettings.cpp | 80 +++++++++ lib/qtcamimagesettings.h | 35 ++++ lib/qtcammetadata.cpp | 158 +++++++++++++++++ lib/qtcammetadata.h | 49 ++++++ lib/qtcammode.cpp | 203 ++++++++++++++++++++++ lib/qtcammode.h | 45 +++++ lib/qtcammode_p.h | 75 ++++++++ lib/qtcampreviewhelper.h | 20 +++ lib/qtcamscanner.cpp | 106 ++++++++++++ lib/qtcamscanner.h | 27 +++ lib/qtcamvideomode.cpp | 108 ++++++++++++ lib/qtcamvideomode.h | 39 +++++ lib/qtcamvideosettings.cpp | 71 ++++++++ lib/qtcamvideosettings.h | 34 ++++ lib/qtcamviewfinder.cpp | 9 + lib/qtcamviewfinder.h | 19 ++ lib/qtcamviewfinderrenderer.cpp | 39 +++++ lib/qtcamviewfinderrenderer.h | 38 ++++ lib/qtcamviewfinderrenderergeneric.cpp | 143 +++++++++++++++ lib/qtcamviewfinderrenderergeneric.h | 37 ++++ 38 files changed, 2894 insertions(+) create mode 100644 lib/lib.pro create mode 100644 lib/main.cpp create mode 100644 lib/main.h create mode 100644 lib/qtcamconfig.cpp create mode 100644 lib/qtcamconfig.h create mode 100644 lib/qtcamdevice.cpp create mode 100644 lib/qtcamdevice.h create mode 100644 lib/qtcamdevice_p.h create mode 100644 lib/qtcamera.cpp create mode 100644 lib/qtcamera.h create mode 100644 lib/qtcamgraphicsviewfinder.cpp create mode 100644 lib/qtcamgraphicsviewfinder.h create mode 100644 lib/qtcamgstreamermessagehandler.cpp create mode 100644 lib/qtcamgstreamermessagehandler.h create mode 100644 lib/qtcamgstreamermessagelistener.cpp create mode 100644 lib/qtcamgstreamermessagelistener.h create mode 100644 lib/qtcamimagemode.cpp create mode 100644 lib/qtcamimagemode.h create mode 100644 lib/qtcamimagesettings.cpp create mode 100644 lib/qtcamimagesettings.h create mode 100644 lib/qtcammetadata.cpp create mode 100644 lib/qtcammetadata.h create mode 100644 lib/qtcammode.cpp create mode 100644 lib/qtcammode.h create mode 100644 lib/qtcammode_p.h create mode 100644 lib/qtcampreviewhelper.h create mode 100644 lib/qtcamscanner.cpp create mode 100644 lib/qtcamscanner.h create mode 100644 lib/qtcamvideomode.cpp create mode 100644 lib/qtcamvideomode.h create mode 100644 lib/qtcamvideosettings.cpp create mode 100644 lib/qtcamvideosettings.h create mode 100644 lib/qtcamviewfinder.cpp create mode 100644 lib/qtcamviewfinder.h create mode 100644 lib/qtcamviewfinderrenderer.cpp create mode 100644 lib/qtcamviewfinderrenderer.h create mode 100644 lib/qtcamviewfinderrenderergeneric.cpp create mode 100644 lib/qtcamviewfinderrenderergeneric.h diff --git a/lib/lib.pro b/lib/lib.pro new file mode 100644 index 0000000..76a862c --- /dev/null +++ b/lib/lib.pro @@ -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 index 0000000..69c63c7 --- /dev/null +++ b/lib/main.cpp @@ -0,0 +1,143 @@ +#include +#include +#include "qtcamera.h" +#include "qtcamdevice.h" +#include +#include +#include +#include +#include +#include +#include "main.h" +#include "qtcamgraphicsviewfinder.h" +#include +#include +#include +#include +#include +#include "qtcamimagemode.h" +#include "qtcamvideomode.h" +#include +#include "qtcammetadata.h" +#include + +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 > 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 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 index 0000000..d8c7084 --- /dev/null +++ b/lib/main.h @@ -0,0 +1,37 @@ +// -*- c++ -*- + +#ifndef MAIN_H +#define MAIN_H + +#include + +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 index 0000000..1cb9624 --- /dev/null +++ b/lib/qtcamconfig.cpp @@ -0,0 +1,166 @@ +#include "qtcamconfig.h" +#include +#include +#include + +#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 parts = conf->value(key).toString().trimmed().split("x"); + return QSize(parts[0].toInt(), parts[1].toInt()); + } + + QPair readFrameRate() { + QList parts = conf->value("fps").toString().trimmed().split("/"); + return qMakePair(parts[0].toInt(), parts[1].toInt()); + } + + QSettings *conf; + + QList imageSettings; + QList 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 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 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 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 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 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 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 index 0000000..eb04bc9 --- /dev/null +++ b/lib/qtcamconfig.h @@ -0,0 +1,56 @@ +// -*- c++ -*- + +#ifndef QT_CAM_CONFIG_H +#define QT_CAM_CONFIG_H + +#include +#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 imageSettings(); + + QtCamVideoSettings defaultVideoSettings(); + QList 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 index 0000000..17620bc --- /dev/null +++ b/lib/qtcamdevice.cpp @@ -0,0 +1,230 @@ +#include "qtcamdevice.h" +#include "qtcamviewfinder.h" +#include "qtcamconfig.h" +#include "qtcamdevice_p.h" +#include +#include +#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 index 0000000..59d7421 --- /dev/null +++ b/lib/qtcamdevice.h @@ -0,0 +1,53 @@ +// -*- c++ -*- + +#ifndef QT_CAM_DEVICE_H +#define QT_CAM_DEVICE_H + +#include +#include + +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 index 0000000..e31fdfb --- /dev/null +++ b/lib/qtcamdevice_p.h @@ -0,0 +1,143 @@ +// -*- c++ -*- + +#ifndef QT_CAM_DEVICE_P_H +#define QT_CAM_DEVICE_P_H + +#include +#include +#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 index 0000000..3f712bd --- /dev/null +++ b/lib/qtcamera.cpp @@ -0,0 +1,77 @@ +#include "qtcamera.h" +#include "qtcamscanner.h" +#include "qtcamconfig.h" +#include "qtcamdevice.h" +#include + +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 > QtCamera::devices() const { + return d_ptr->scanner->devices(); +} + +QtCamDevice *QtCamera::device(const QVariant& id, QObject *parent) { + QList > devs = devices(); + + // G++ barfs with foreach and class templates. + typedef QPair 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 index 0000000..e4d96a0 --- /dev/null +++ b/lib/qtcamera.h @@ -0,0 +1,36 @@ +// -*- c++ -*- + +#ifndef QT_CAMERA_H +#define QT_CAMERA_H + +#include +#include +#include + +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 > 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 index 0000000..76ccad1 --- /dev/null +++ b/lib/qtcamgraphicsviewfinder.cpp @@ -0,0 +1,102 @@ +#include "qtcamgraphicsviewfinder.h" +#include "qtcamconfig.h" +#include "qtcamdevice.h" +#include +#include "qtcamviewfinderrenderer.h" +#include +#include + +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 index 0000000..b8bd811 --- /dev/null +++ b/lib/qtcamgraphicsviewfinder.h @@ -0,0 +1,35 @@ +// -*- c++ -*- + +#ifndef QT_CAM_GRAPHICS_VIEWFINDER_H +#define QT_CAM_GRAPHICS_VIEWFINDER_H + +#include "qtcamviewfinder.h" +#include + +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 index 0000000..3683717 --- /dev/null +++ b/lib/qtcamgstreamermessagehandler.cpp @@ -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 index 0000000..4277d1a --- /dev/null +++ b/lib/qtcamgstreamermessagehandler.h @@ -0,0 +1,29 @@ +// -*- c++ -*- + +#ifndef QT_CAM_GSTREAMER_MESSAGE_HANDLER_H +#define QT_CAM_GSTREAMER_MESSAGE_HANDLER_H + +#include +#include + +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 index 0000000..94600e1 --- /dev/null +++ b/lib/qtcamgstreamermessagelistener.cpp @@ -0,0 +1,218 @@ +#include "qtcamgstreamermessagelistener.h" +#include "qtcamgstreamermessagehandler.h" +#include +#include +#include +#include "qtcamdevice_p.h" + +class QtCamGStreamerMessageListenerPrivate { +public: + QMultiMap handlers; + QMultiMap syncHandlers; + + int handleMessage(GstMessage *message, QMultiMap& map) { + const GstStructure *s = gst_message_get_structure(message); + if (!s) { + return 0; + } + + QList 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& map) { + if (!map.contains(handler->messageName(), handler)) { + map.insert(handler->messageName(), handler); + handler->setParent(q_ptr); + } + } + + void removeHandler(QtCamGStreamerMessageHandler *handler, + QMultiMap& 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(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(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 index 0000000..5c747f8 --- /dev/null +++ b/lib/qtcamgstreamermessagelistener.h @@ -0,0 +1,36 @@ +// -*- c++ -*- + +#ifndef QT_CAM_GSTREAMER_MESSAGE_LISTENER_H +#define QT_CAM_GSTREAMER_MESSAGE_LISTENER_H + +#include +#include + +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 index 0000000..4fddcab --- /dev/null +++ b/lib/qtcamimagemode.cpp @@ -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 index 0000000..bf64e4a --- /dev/null +++ b/lib/qtcamimagemode.h @@ -0,0 +1,40 @@ +// -*- c++ -*- + +#ifndef QT_CAM_IMAGE_MODE_H +#define QT_CAM_IMAGE_MODE_H + +#include "qtcammode.h" +#include + +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 index 0000000..6bcde00 --- /dev/null +++ b/lib/qtcamimagesettings.cpp @@ -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 QtCamImageSettings::frameRate() const { + return qMakePair(d_ptr->numerator, d_ptr->denominator); +} diff --git a/lib/qtcamimagesettings.h b/lib/qtcamimagesettings.h new file mode 100644 index 0000000..940347a --- /dev/null +++ b/lib/qtcamimagesettings.h @@ -0,0 +1,35 @@ +// -*- c++ -*- + +#ifndef QT_CAM_IMAGE_SETTINGS_H +#define QT_CAM_IMAGE_SETTINGS_H + +#include +#include +#include + +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 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 index 0000000..6116be7 --- /dev/null +++ b/lib/qtcammetadata.cpp @@ -0,0 +1,158 @@ +#include "qtcammetadata.h" +#include +#include "qtcamdevice.h" +#include "qtcamdevice_p.h" +#include +#include +#include +#include + +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 index 0000000..a051fc6 --- /dev/null +++ b/lib/qtcammetadata.h @@ -0,0 +1,49 @@ +// -*- c++ -*- + +#ifndef QT_CAM_META_DATA_H +#define QT_CAM_META_DATA_H + +#include + +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 index 0000000..e21a6b1 --- /dev/null +++ b/lib/qtcammode.cpp @@ -0,0 +1,203 @@ +#include "qtcammode.h" +#include "qtcammode_p.h" +#include "qtcamdevice_p.h" +#include "qtcamdevice.h" +#include +#include "qtcamgstreamermessagehandler.h" +#include "qtcamgstreamermessagelistener.h" +#include +#include + +#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 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 index 0000000..7351a14 --- /dev/null +++ b/lib/qtcammode.h @@ -0,0 +1,45 @@ +// -*- c++ -*- + +#ifndef QT_CAM_MODE_H +#define QT_CAM_MODE_H + +#include +#include + +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 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 index 0000000..9a96ab1 --- /dev/null +++ b/lib/qtcammode_p.h @@ -0,0 +1,75 @@ +// -*- c++ -*- + +#ifndef QT_CAM_MODE_P_H +#define QT_CAM_MODE_P_H + +#include +#include "qtcamdevice_p.h" +#include +#include + +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 index 0000000..528f77f --- /dev/null +++ b/lib/qtcampreviewhelper.h @@ -0,0 +1,20 @@ +// -*- c++ -*- + +#ifndef QT_CAM_PREVIEW_HELPER_H +#define QT_CAM_PREVIEW_HELPER_H + +#include + + +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 index 0000000..ace6f75 --- /dev/null +++ b/lib/qtcamscanner.cpp @@ -0,0 +1,106 @@ +#include "qtcamscanner.h" +#include "qtcamconfig.h" +#include +#include +#include +#include +#include +#include +#include +#include + +class QtCamScannerPrivate { +public: + void scanEnum(); + void scanV4l2(); + + QtCamConfig *conf; + QList > 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(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(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((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 > QtCamScanner::devices() const { + return d_ptr->devices; +} diff --git a/lib/qtcamscanner.h b/lib/qtcamscanner.h new file mode 100644 index 0000000..211154f --- /dev/null +++ b/lib/qtcamscanner.h @@ -0,0 +1,27 @@ +// -*- c++ -*- + +#ifndef QT_CAM_SCANNER_H +#define QT_CAM_SCANNER_H + +#include +#include +#include + +class QtCamConfig; +class QtCamScannerPrivate; + +class QtCamScanner : public QObject { + Q_OBJECT + +public: + QtCamScanner(QtCamConfig *conf, QObject *parent = 0); + ~QtCamScanner(); + + void refresh(); + QList > 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 index 0000000..0e089bd --- /dev/null +++ b/lib/qtcamvideomode.cpp @@ -0,0 +1,108 @@ +#include "qtcamvideomode.h" +#include "qtcammode_p.h" +#include +#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 index 0000000..c6fb34b --- /dev/null +++ b/lib/qtcamvideomode.h @@ -0,0 +1,39 @@ +// -*- c++ -*- + +#ifndef QT_CAM_VIDEO_MODE_H +#define QT_CAM_VIDEO_MODE_H + +#include "qtcammode.h" +#include + +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 index 0000000..7e3f478 --- /dev/null +++ b/lib/qtcamvideosettings.cpp @@ -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 QtCamVideoSettings::frameRate() const { + return qMakePair(d_ptr->numerator, d_ptr->denominator); +} diff --git a/lib/qtcamvideosettings.h b/lib/qtcamvideosettings.h new file mode 100644 index 0000000..a25ab83 --- /dev/null +++ b/lib/qtcamvideosettings.h @@ -0,0 +1,34 @@ +// -*- c++ -*- + +#ifndef QT_CAM_VIDEO_SETTINGS_H +#define QT_CAM_VIDEO_SETTINGS_H + +#include +#include +#include + +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 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 index 0000000..eda49f4 --- /dev/null +++ b/lib/qtcamviewfinder.cpp @@ -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 index 0000000..cefa84b --- /dev/null +++ b/lib/qtcamviewfinder.h @@ -0,0 +1,19 @@ +// -*- c++ -*- + +#ifndef QT_CAM_VIEWFINDER_H +#define QT_CAM_VIEWFINDER_H + +#include + +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 index 0000000..7ab63e6 --- /dev/null +++ b/lib/qtcamviewfinderrenderer.cpp @@ -0,0 +1,39 @@ +#include "qtcamviewfinderrenderer.h" +#include "qtcamconfig.h" +#include +#include + +static QMap _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(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 index 0000000..4e46af0 --- /dev/null +++ b/lib/qtcamviewfinderrenderer.h @@ -0,0 +1,38 @@ +// -*- c++ -*- + +#ifndef QT_CAM_VIEWFINDER_RENDERER_H +#define QT_CAM_VIEWFINDER_RENDERER_H + +#include +#include + +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 index 0000000..8789226 --- /dev/null +++ b/lib/qtcamviewfinderrenderergeneric.cpp @@ -0,0 +1,143 @@ +#include "qtcamviewfinderrenderergeneric.h" +#include +#include +#include + +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 index 0000000..36b8381 --- /dev/null +++ b/lib/qtcamviewfinderrenderergeneric.h @@ -0,0 +1,37 @@ +// -*- c++ -*- + +#ifndef QT_CAM_VIEWFINDER_RENDERER_GENERIC_H +#define QT_CAM_VIEWFINDER_RENDERER_GENERIC_H + +#include "qtcamviewfinderrenderer.h" +#include +#include + +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 */ -- 2.25.1