AVFoundation: fix QCameraInfo::availableCameras() on OS X.
authorYoann Lopes <yoann.lopes@theqtcompany.com>
Thu, 9 Apr 2015 13:14:36 +0000 (15:14 +0200)
committerTimur Pocheptsov <Timur.Pocheptsov@digia.com>
Fri, 10 Apr 2015 09:12:41 +0000 (09:12 +0000)
Cameras can be dynamically added or removed on OS X. Make sure
the cache is updated often enough so QCameraInfo::availableCameras()
return an up to date list.

Task-number: QTBUG-39708
Change-Id: Id806d52278e1a29163fcc6707da7f86c0f3e7c0d
Reviewed-by: Timur Pocheptsov <Timur.Pocheptsov@digia.com>
src/plugins/avfoundation/camera/avfcameraserviceplugin.mm
src/plugins/avfoundation/camera/avfcamerasession.h
src/plugins/avfoundation/camera/avfcamerasession.mm
src/plugins/avfoundation/camera/avfvideodevicecontrol.mm

index 414a847..99966f0 100644 (file)
@@ -71,18 +71,26 @@ void AVFServicePlugin::release(QMediaService *service)
 
 QByteArray AVFServicePlugin::defaultDevice(const QByteArray &service) const
 {
-    if (service == Q_MEDIASERVICE_CAMERA)
-        return AVFCameraSession::defaultCameraDevice();
+    if (service == Q_MEDIASERVICE_CAMERA) {
+        int i = AVFCameraSession::defaultCameraIndex();
+        if (i != -1)
+            return AVFCameraSession::availableCameraDevices().at(i).deviceId;
+    }
 
     return QByteArray();
 }
 
 QList<QByteArray> AVFServicePlugin::devices(const QByteArray &service) const
 {
-    if (service == Q_MEDIASERVICE_CAMERA)
-        return AVFCameraSession::availableCameraDevices();
+    QList<QByteArray> devs;
+
+    if (service == Q_MEDIASERVICE_CAMERA) {
+        const QList<AVFCameraInfo> &cameras = AVFCameraSession::availableCameraDevices();
+        Q_FOREACH (const AVFCameraInfo &info, cameras)
+            devs.append(info.deviceId);
+    }
 
-    return QList<QByteArray>();
+    return devs;
 }
 
 QString AVFServicePlugin::deviceDescription(const QByteArray &service, const QByteArray &device)
index 75ca3f4..8ce7461 100644 (file)
@@ -52,6 +52,7 @@ struct AVFCameraInfo
     AVFCameraInfo() : position(QCamera::UnspecifiedPosition), orientation(0)
     { }
 
+    QByteArray deviceId;
     QString description;
     QCamera::Position position;
     int orientation;
@@ -64,8 +65,8 @@ public:
     AVFCameraSession(AVFCameraService *service, QObject *parent = 0);
     ~AVFCameraSession();
 
-    static const QByteArray &defaultCameraDevice();
-    static const QList<QByteArray> &availableCameraDevices();
+    static int defaultCameraIndex();
+    static const QList<AVFCameraInfo> &availableCameraDevices();
     static AVFCameraInfo cameraDeviceInfo(const QByteArray &device);
 
     void setVideoOutput(AVFVideoRendererControl *output);
@@ -93,9 +94,8 @@ private:
     static void updateCameraDevices();
     void attachInputDevices();
 
-    static QByteArray m_defaultCameraDevice;
-    static QList<QByteArray> m_cameraDevices;
-    static QMap<QByteArray, AVFCameraInfo> m_cameraInfo;
+    static int m_defaultCameraIndex;
+    static QList<AVFCameraInfo> m_cameraDevices;
 
     AVFCameraService *m_service;
     AVFVideoRendererControl *m_videoOutput;
index a72ef50..4d4b2f6 100644 (file)
 
 #include <QtCore/qdatetime.h>
 #include <QtCore/qurl.h>
+#include <QtCore/qelapsedtimer.h>
 
 #include <QtCore/qdebug.h>
 
 QT_USE_NAMESPACE
 
-QByteArray AVFCameraSession::m_defaultCameraDevice;
-QList<QByteArray> AVFCameraSession::m_cameraDevices;
-QMap<QByteArray, AVFCameraInfo> AVFCameraSession::m_cameraInfo;
+int AVFCameraSession::m_defaultCameraIndex;
+QList<AVFCameraInfo> AVFCameraSession::m_cameraDevices;
 
 @interface AVFCameraSessionObserver : NSObject
 {
@@ -172,45 +172,55 @@ AVFCameraSession::~AVFCameraSession()
     [m_captureSession release];
 }
 
-const QByteArray &AVFCameraSession::defaultCameraDevice()
+int AVFCameraSession::defaultCameraIndex()
 {
-    if (m_cameraDevices.isEmpty())
-        updateCameraDevices();
-
-    return m_defaultCameraDevice;
+    updateCameraDevices();
+    return m_defaultCameraIndex;
 }
 
-const QList<QByteArray> &AVFCameraSession::availableCameraDevices()
+const QList<AVFCameraInfo> &AVFCameraSession::availableCameraDevices()
 {
-    if (m_cameraDevices.isEmpty())
-        updateCameraDevices();
-
+    updateCameraDevices();
     return m_cameraDevices;
 }
 
 AVFCameraInfo AVFCameraSession::cameraDeviceInfo(const QByteArray &device)
 {
-    if (m_cameraDevices.isEmpty())
-        updateCameraDevices();
+    updateCameraDevices();
 
-    return m_cameraInfo.value(device);
+    Q_FOREACH (const AVFCameraInfo &info, m_cameraDevices) {
+        if (info.deviceId == device)
+            return info;
+    }
+
+    return AVFCameraInfo();
 }
 
 void AVFCameraSession::updateCameraDevices()
 {
-    m_defaultCameraDevice.clear();
+#ifdef Q_OS_IOS
+    // Cameras can't change dynamically on iOS. Update only once.
+    if (!m_cameraDevices.isEmpty())
+        return;
+#else
+    // On OS X, cameras can be added or removed. Update the list every time, but not more than
+    // once every 500 ms
+    static QElapsedTimer timer;
+    if (timer.isValid() && timer.elapsed() < 500) // ms
+        return;
+#endif
+
+    m_defaultCameraIndex = -1;
     m_cameraDevices.clear();
-    m_cameraInfo.clear();
 
     AVCaptureDevice *defaultDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
-    if (defaultDevice)
-        m_defaultCameraDevice = QByteArray([[defaultDevice uniqueID] UTF8String]);
-
     NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
     for (AVCaptureDevice *device in videoDevices) {
-        QByteArray deviceId([[device uniqueID] UTF8String]);
+        if (defaultDevice && [defaultDevice.uniqueID isEqualToString:device.uniqueID])
+            m_defaultCameraIndex = m_cameraDevices.count();
 
         AVFCameraInfo info;
+        info.deviceId = QByteArray([[device uniqueID] UTF8String]);
         info.description = QString::fromNSString([device localizedName]);
 
         // There is no API to get the camera sensor orientation, however, cameras are always
@@ -235,9 +245,12 @@ void AVFCameraSession::updateCameraDevices()
             break;
         }
 
-        m_cameraDevices << deviceId;
-        m_cameraInfo.insert(deviceId, info);
+        m_cameraDevices.append(info);
     }
+
+#ifndef Q_OS_IOS
+    timer.restart();
+#endif
 }
 
 void AVFCameraSession::setVideoOutput(AVFVideoRendererControl *output)
index 03736c3..1730437 100644 (file)
@@ -65,25 +65,25 @@ int AVFVideoDeviceControl::deviceCount() const
 
 QString AVFVideoDeviceControl::deviceName(int index) const
 {
-    const QList<QByteArray> &devices = AVFCameraSession::availableCameraDevices();
+    const QList<AVFCameraInfo> &devices = AVFCameraSession::availableCameraDevices();
     if (index < 0 || index >= devices.count())
         return QString();
 
-    return QString::fromUtf8(devices.at(index));
+    return QString::fromUtf8(devices.at(index).deviceId);
 }
 
 QString AVFVideoDeviceControl::deviceDescription(int index) const
 {
-    const QList<QByteArray> &devices = AVFCameraSession::availableCameraDevices();
+    const QList<AVFCameraInfo> &devices = AVFCameraSession::availableCameraDevices();
     if (index < 0 || index >= devices.count())
         return QString();
 
-    return AVFCameraSession::cameraDeviceInfo(devices.at(index)).description;
+    return devices.at(index).description;
 }
 
 int AVFVideoDeviceControl::defaultDevice() const
 {
-    return AVFCameraSession::availableCameraDevices().indexOf(AVFCameraSession::defaultCameraDevice());
+    return AVFCameraSession::defaultCameraIndex();
 }
 
 int AVFVideoDeviceControl::selectedDevice() const