4#include <QtMultimedia/private/qavfvideodevices_p.h>
5#include <QtMultimedia/private/qcameradevice_p.h>
6#include <QtMultimedia/private/qavfhelpers_p.h>
7#include <QtMultimedia/private/qavfcamerautility_p.h>
9#include <QtCore/qcoreapplication.h>
10#include <QtCore/qloggingcategory.h>
11#include <QtCore/private/qexpected_p.h>
12#include <QtCore/qset.h>
13#include <QtCore/qspan.h>
14#include <QtCore/qthread.h>
22using namespace Qt::Literals::StringLiterals;
27[[nodiscard]] QCameraDevice::Position qAvfToQCameraDevicePosition(AVCaptureDevicePosition input)
30 case AVCaptureDevicePositionFront:
31 return QCameraDevice::Position::FrontFace;
32 case AVCaptureDevicePositionBack:
33 return QCameraDevice::Position::BackFace;
35 return QCameraDevice::Position::UnspecifiedPosition;
40[[nodiscard]] q23::expected<AVFScopedPointer<AVCaptureDeviceDiscoverySession>, QString>
41createAvCaptureDeviceDiscoverySession()
48 NSArray *discoveryDevices = @[
50 AVCaptureDeviceTypeBuiltInTripleCamera,
51 AVCaptureDeviceTypeBuiltInDualCamera,
53 AVCaptureDeviceTypeBuiltInDualWideCamera,
56 AVCaptureDeviceTypeBuiltInWideAngleCamera,
59 AVCaptureDeviceTypeBuiltInTelephotoCamera,
61 AVCaptureDeviceTypeBuiltInUltraWideCamera,
65 if (@available(macOS 14, *)) {
66 discoveryDevices = [discoveryDevices arrayByAddingObjectsFromArray: @[
67 AVCaptureDeviceTypeExternal,
68 AVCaptureDeviceTypeContinuityCamera
73 QT_WARNING_DISABLE_DEPRECATED
74 discoveryDevices = [discoveryDevices arrayByAddingObjectsFromArray: @[
75 AVCaptureDeviceTypeExternalUnknown
84 AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
85 discoverySessionWithDeviceTypes:discoveryDevices
86 mediaType:AVMediaTypeVideo
87 position:AVCaptureDevicePositionUnspecified];
88 return AVFScopedPointer{ [discoverySession retain] };
90 @
catch (NSException *e) {
91 return q23::unexpected{
92 u"Exception caught when trying to create AVCaptureDeviceDiscoverySession: "_s
93 + QString::fromNSString(e.reason) };
100template <
typename FormatChecker>
101[[nodiscard]] QList<QCameraDevice>
102qGenerateQCameraDevices(NSArray<AVCaptureDevice *> *videoDevices,
103 const FormatChecker &isCvPixelFormatSupported)
105 QList<QCameraDevice> cameras;
107 for (AVCaptureDevice *device in videoDevices) {
108 if ([device isSuspended])
111 auto info = std::make_unique<QCameraDevicePrivate>();
112 if ([videoDevices[0].uniqueID isEqualToString:device.uniqueID])
113 info->isDefault =
true;
114 info->id = QByteArray([[device uniqueID] UTF8String]);
115 info->description = QString::fromNSString([device localizedName]);
116 info->position = qAvfToQCameraDevicePosition([device position]);
118 qCDebug(qLcAvfVideoDevices) <<
"Handling camera info" << info->description
119 << (info->isDefault ?
"(default)" :
"");
121 QSet<QSize> photoResolutions;
122 QList<QCameraFormat> videoFormats;
124 for (AVCaptureDeviceFormat *format in device.formats) {
125 if (![format.mediaType isEqualToString:AVMediaTypeVideo])
128 const CMVideoDimensions dimensions =
129 CMVideoFormatDescriptionGetDimensions(format.formatDescription);
130 QSize resolution(dimensions.width, dimensions.height);
131 photoResolutions.insert(resolution);
133 float maxFrameRate = 0;
134 float minFrameRate = 1.e6;
136 const CvPixelFormat cvPixelFormat =
137 CMVideoFormatDescriptionGetCodecType(format.formatDescription);
141 if (!isCvPixelFormatSupported(cvPixelFormat))
144 const QVideoFrameFormat::PixelFormat pixelFormat =
145 QAVFHelpers::fromCVPixelFormat(cvPixelFormat);
146 const QVideoFrameFormat::ColorRange colorRange =
147 QAVFHelpers::colorRangeForCVPixelFormat(cvPixelFormat);
150 if (pixelFormat == QVideoFrameFormat::Format_Invalid) {
151 qCDebug(qLcAvfVideoDevices) <<
"ignore camera CV format" << cvPixelFormat
152 <<
"as no matching video format found";
156 for (
const AVFrameRateRange *frameRateRange in format.videoSupportedFrameRateRanges) {
157 if (frameRateRange.minFrameRate < minFrameRate)
158 minFrameRate = frameRateRange.minFrameRate;
159 if (frameRateRange.maxFrameRate > maxFrameRate)
160 maxFrameRate = frameRateRange.maxFrameRate;
169 const QSize hrRes(qt_device_format_high_resolution(format));
170 if (!hrRes.isNull() && hrRes.isValid())
171 photoResolutions.insert(hrRes);
174 qCDebug(qLcAvfVideoDevices) <<
"Add camera format. pixelFormat:" << pixelFormat
175 <<
"colorRange:" << colorRange <<
"cvPixelFormat" << cvPixelFormat
176 <<
"resolution:" << resolution <<
"frameRate: [" << minFrameRate
177 << maxFrameRate <<
"]";
179 auto *f =
new QCameraFormatPrivate{ QSharedData(), pixelFormat, resolution,
180 minFrameRate, maxFrameRate, colorRange };
181 videoFormats << f->create();
183 if (videoFormats.isEmpty()) {
185 qCWarning(qLcAvfVideoDevices())
186 <<
"Skip camera" << info->description <<
"without supported formats";
189 info->videoFormats = videoFormats;
190 info->photoResolutions = photoResolutions.values();
192 cameras.append(info.release()->create());
201QAVFVideoDevices::QAVFVideoDevices(
202 QPlatformMediaIntegration *integration,
203 std::function<
bool(uint32_t)> &&isCvPixelFormatSupportedDelegate)
204 : QPlatformVideoDevices(integration),
205 m_isCvPixelFormatSupportedDelegate(std::move(isCvPixelFormatSupportedDelegate))
207 Q_ASSERT(QCoreApplication::instance());
208 moveToThread(QCoreApplication::instance()->thread());
211 QMacAutoReleasePool autoReleasePool;
213 auto discoverySessionResult = createAvCaptureDeviceDiscoverySession();
214 if (!discoverySessionResult) {
215 qCWarning(qLcAvfVideoDevices) << discoverySessionResult.error();
216 qWarning() <<
"Failed to establish camera device discovery session. "
217 "QMediaDevices::videoInputs() will not work.";
221 m_avDiscoverySession = std::move(*discoverySessionResult);
222 m_avDiscoverySessionObserver = QMacKeyValueObserver(
223 m_avDiscoverySession,
226 onAvCaptureDevicesChanged();
230 QMetaObject::invokeMethod(
this, [
this]{
231 rebuildObserveredAvCaptureDevices();
235QAVFVideoDevices::~QAVFVideoDevices() =
default;
238QList<QCameraDevice> QAVFVideoDevices::findVideoInputs()
const
240 if (!m_avDiscoverySession)
245 QMacAutoReleasePool autoReleasePool;
247 NSArray<AVCaptureDevice *> *deviceList = m_avDiscoverySession.data().devices;
248 Q_ASSERT(deviceList);
250 return qGenerateQCameraDevices(deviceList, [
this](uint32_t cvPixelFormat) {
251 return isCvPixelFormatSupported(cvPixelFormat);
255bool QAVFVideoDevices::isCvPixelFormatSupported(uint32_t cvPixelFormat)
const
257 return !m_isCvPixelFormatSupportedDelegate || m_isCvPixelFormatSupportedDelegate(cvPixelFormat);
261void QAVFVideoDevices::rebuildObserveredAvCaptureDevices()
263 Q_ASSERT(QCoreApplication::instance()->thread()->isCurrentThread());
265 m_observedAvCaptureDevices.clear();
267 if (!m_avDiscoverySession)
270 NSArray<AVCaptureDevice *> *deviceList = m_avDiscoverySession.data().devices;
271 Q_ASSERT(deviceList);
273 m_observedAvCaptureDevices.reserve(deviceList.count);
275 for (AVCaptureDevice *captureDevice in deviceList) {
276 AVFScopedPointer retainedDevice{ [captureDevice retain] };
279 QMacKeyValueObserver observer(
283 onAvCaptureDevicesChanged();
286 m_observedAvCaptureDevices.push_back({ std::move(retainedDevice), std::move(observer) });
290void QAVFVideoDevices::onAvCaptureDevicesChanged()
294 QMetaObject::invokeMethod(
this, [
this] {
295 rebuildObserveredAvCaptureDevices();
296 onVideoInputsChanged();
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)