Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
avfcamerasession.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "avfcameradebug_p.h"
7#include "avfcamera_p.h"
9#include "avfimagecapture_p.h"
10#include "avfmediaencoder_p.h"
11#include "avfcamerautility_p.h"
12#include <avfvideosink_p.h>
13
14#include <CoreFoundation/CoreFoundation.h>
15#include <Foundation/Foundation.h>
16
17#include <QtCore/qcoreapplication.h>
18#include <QtCore/qdatetime.h>
19#include <QtCore/qurl.h>
20#include <QtCore/qelapsedtimer.h>
21#include <QtCore/qpermissions.h>
22#include <QtCore/qpointer.h>
23
24#include <private/qplatformaudioinput_p.h>
25#include <private/qplatformaudiooutput_p.h>
26
27#include <QtCore/qdebug.h>
28
30
31@interface AVFCameraSessionObserver : NSObject
32
33- (AVFCameraSessionObserver *) initWithCameraSession:(AVFCameraSession*)session;
34- (void) processRuntimeError:(NSNotification *)notification;
35- (void) processSessionStarted:(NSNotification *)notification;
36- (void) processSessionStopped:(NSNotification *)notification;
37
38@end
39
40@implementation AVFCameraSessionObserver
41{
42@private
44 AVCaptureSession *m_captureSession;
45}
46
47- (AVFCameraSessionObserver *) initWithCameraSession:(AVFCameraSession*)session
48{
49 if (!(self = [super init]))
50 return nil;
51
52 self->m_session = session;
53 self->m_captureSession = session->captureSession();
54
55 [m_captureSession retain];
56 [[NSNotificationCenter defaultCenter] addObserver:self
57 selector:@selector(processRuntimeError:)
58 name:AVCaptureSessionRuntimeErrorNotification
59 object:m_captureSession];
60
61 [[NSNotificationCenter defaultCenter] addObserver:self
62 selector:@selector(processSessionStarted:)
63 name:AVCaptureSessionDidStartRunningNotification
64 object:m_captureSession];
65
66 [[NSNotificationCenter defaultCenter] addObserver:self
67 selector:@selector(processSessionStopped:)
68 name:AVCaptureSessionDidStopRunningNotification
69 object:m_captureSession];
70
71 return self;
72}
73
74- (void) dealloc
75{
76 [[NSNotificationCenter defaultCenter] removeObserver:self
77 name:AVCaptureSessionRuntimeErrorNotification
78 object:m_captureSession];
79
80 [[NSNotificationCenter defaultCenter] removeObserver:self
81 name:AVCaptureSessionDidStartRunningNotification
82 object:m_captureSession];
83
84 [[NSNotificationCenter defaultCenter] removeObserver:self
85 name:AVCaptureSessionDidStopRunningNotification
86 object:m_captureSession];
87 [m_captureSession release];
88 [super dealloc];
89}
90
91- (void) processRuntimeError:(NSNotification *)notification
92{
93 Q_UNUSED(notification);
95}
96
97- (void) processSessionStarted:(NSNotification *)notification
98{
99 Q_UNUSED(notification);
100 QMetaObject::invokeMethod(m_session, "processSessionStarted", Qt::AutoConnection);
101}
102
103- (void) processSessionStopped:(NSNotification *)notification
104{
105 Q_UNUSED(notification);
106 QMetaObject::invokeMethod(m_session, "processSessionStopped", Qt::AutoConnection);
107}
108
109@end
110
112 : QObject(parent)
113 , m_service(service)
114 , m_defaultCodec(0)
115{
116 m_captureSession = [[AVCaptureSession alloc] init];
117 m_observer = [[AVFCameraSessionObserver alloc] initWithCameraSession:this];
118}
119
121{
122 if (m_videoInput) {
123 [m_captureSession removeInput:m_videoInput];
124 [m_videoInput release];
125 }
126
127 if (m_audioInput) {
128 [m_captureSession removeInput:m_audioInput];
129 [m_audioInput release];
130 }
131
132 if (m_audioOutput) {
133 [m_captureSession removeOutput:m_audioOutput];
134 [m_audioOutput release];
135 }
136
137 if (m_videoOutput)
138 delete m_videoOutput;
139
140 [m_observer release];
141 [m_captureSession release];
142}
143
145{
146 if (m_activeCameraDevice != info) {
147 m_activeCameraDevice = info;
148
149 if (checkCameraPermission())
151 }
152}
153
155{
156 if (m_cameraFormat == format)
157 return;
158
159 updateCameraFormat(format);
160}
161
163{
164 return m_cameraFormat;
165}
166
167void AVFCameraSession::updateCameraFormat(const QCameraFormat &format)
168{
169 m_cameraFormat = format;
170
171 AVCaptureDevice *captureDevice = videoCaptureDevice();
172 if (!captureDevice)
173 return;
174
175 AVCaptureDeviceFormat *newFormat = qt_convert_to_capture_device_format(captureDevice, format);
176 if (newFormat)
177 qt_set_active_format(captureDevice, newFormat, false);
178}
179
180void AVFCameraSession::setVideoOutput(AVFCameraRenderer *output)
181{
182 if (m_videoOutput == output)
183 return;
184
185 delete m_videoOutput;
186 m_videoOutput = output;
187 if (output)
188 output->configureAVCaptureSession(this);
189}
190
191void AVFCameraSession::addAudioCapture()
192{
193 if (!m_audioOutput) {
194 m_audioOutput = [[AVCaptureAudioDataOutput alloc] init];
195 if (m_audioOutput && [m_captureSession canAddOutput:m_audioOutput]) {
196 [m_captureSession addOutput:m_audioOutput];
197 } else {
198 qWarning() << Q_FUNC_INFO << "failed to add audio output";
199 }
200 }
201}
202
204{
205 if (m_videoInput)
206 return m_videoInput.device;
207
208 return nullptr;
209}
210
212{
213 if (m_audioInput)
214 return m_audioInput.device;
215
216 return nullptr;
217}
218
220{
221 m_inputVolume = volume;
222
223 if (m_inputMuted)
224 volume = 0.0;
225
226#ifdef Q_OS_MACOS
227 AVCaptureConnection *audioInputConnection = [m_audioOutput connectionWithMediaType:AVMediaTypeAudio];
228 NSArray<AVCaptureAudioChannel *> *audioChannels = audioInputConnection.audioChannels;
229 if (audioChannels) {
230 for (AVCaptureAudioChannel *channel in audioChannels) {
231 channel.volume = volume;
232 }
233 }
234#endif
235}
236
238{
239 m_inputMuted = muted;
240 setAudioInputVolume(m_inputVolume);
241}
242
244{
245 if (m_audioPreviewDelegate)
247}
248
250{
251 if (m_audioPreviewDelegate)
253}
254
256{
257 return m_active;
258}
259
261{
262 if (m_active == active)
263 return;
264
265 m_active = active;
266
267 qCDebug(qLcCamera) << Q_FUNC_INFO << m_active << " -> " << active;
268
269 if (active) {
270 if (!m_activeCameraDevice.isNull()) {
272 m_defaultCodec = 0;
273 defaultCodec();
274 }
275
276 applyImageEncoderSettings();
277
278 // According to the doc, the capture device must be locked before
279 // startRunning to prevent the format we set to be overridden by the
280 // session preset.
281 [videoCaptureDevice() lockForConfiguration:nil];
282 [m_captureSession startRunning];
283 [videoCaptureDevice() unlockForConfiguration];
284 } else {
285 [m_captureSession stopRunning];
286 }
287}
288
290{
291 qWarning() << tr("Runtime camera error");
292 m_active = false;
293 Q_EMIT error(QCamera::CameraError, tr("Runtime camera error"));
294}
295
297{
298 qCDebug(qLcCamera) << Q_FUNC_INFO;
299 if (!m_active) {
300 m_active = true;
302 }
303}
304
306{
307 qCDebug(qLcCamera) << Q_FUNC_INFO;
308 if (m_active) {
309 m_active = false;
311 }
312}
313
314AVCaptureDevice *AVFCameraSession::createVideoCaptureDevice()
315{
316 AVCaptureDevice *device = nullptr;
317
318 QByteArray deviceId = m_activeCameraDevice.id();
319 if (!deviceId.isEmpty()) {
320 device = [AVCaptureDevice deviceWithUniqueID:
321 [NSString stringWithUTF8String:
322 deviceId.constData()]];
323 }
324
325 return device;
326}
327
328AVCaptureDevice *AVFCameraSession::createAudioCaptureDevice()
329{
330 AVCaptureDevice *device = nullptr;
331
332 QByteArray deviceId = m_service->audioInput() ? m_service->audioInput()->device.id()
333 : QByteArray();
334 if (!deviceId.isEmpty())
335 device = [AVCaptureDevice deviceWithUniqueID: [NSString stringWithUTF8String:deviceId.constData()]];
336
337 return device;
338}
339
340void AVFCameraSession::attachVideoInputDevice()
341{
342 if (!checkCameraPermission())
343 return;
344
345 if (m_videoInput) {
346 [m_captureSession removeInput:m_videoInput];
347 [m_videoInput release];
348 m_videoInput = nullptr;
349 }
350
351 AVCaptureDevice *videoDevice = createVideoCaptureDevice();
352 if (!videoDevice)
353 return;
354
355 m_videoInput = [AVCaptureDeviceInput
356 deviceInputWithDevice:videoDevice
357 error:nil];
358 if (m_videoInput && [m_captureSession canAddInput:m_videoInput]) {
359 [m_videoInput retain];
360 [m_captureSession addInput:m_videoInput];
361 } else {
362 qWarning() << "Failed to create video device input";
363 }
364}
365
366void AVFCameraSession::attachAudioInputDevice()
367{
368 if (m_audioInput) {
369 [m_captureSession removeInput:m_audioInput];
370 [m_audioInput release];
371 m_audioInput = nullptr;
372 }
373
374 AVCaptureDevice *audioDevice = createAudioCaptureDevice();
375 if (!audioDevice)
376 return;
377
378 m_audioInput = [AVCaptureDeviceInput
379 deviceInputWithDevice:audioDevice
380 error:nil];
381
382 if (m_audioInput && [m_captureSession canAddInput:m_audioInput]) {
383 [m_audioInput retain];
384 [m_captureSession addInput:m_audioInput];
385 } else {
386 qWarning() << "Failed to create audio device input";
387 }
388}
389
390bool AVFCameraSession::applyImageEncoderSettings()
391{
392 if (AVFImageCapture *control = m_service->avfImageCaptureControl())
393 return control->applySettings();
394
395 return false;
396}
397
399{
400 if (!m_defaultCodec) {
401 if (AVCaptureDevice *device = videoCaptureDevice()) {
402 AVCaptureDeviceFormat *format = device.activeFormat;
403 if (!format || !format.formatDescription)
404 return m_defaultCodec;
405 m_defaultCodec = CMVideoFormatDescriptionGetCodecType(format.formatDescription);
406 }
407 }
408 return m_defaultCodec;
409}
410
412{
413 auto *videoSink = sink ? static_cast<AVFVideoSink *>(sink->platformVideoSink()) : nullptr;
414
415 if (m_videoSink == videoSink)
416 return;
417
418 m_videoSink = videoSink;
419
420 updateVideoOutput();
421}
422
424{
425 auto recorder = m_service->recorderControl();
427 recorder->toggleRecord(false);
428
429 [m_captureSession beginConfiguration];
430
431 attachVideoInputDevice();
432 if (!m_activeCameraDevice.isNull() && !m_videoOutput) {
433 setVideoOutput(new AVFCameraRenderer(this));
436 updateVideoOutput();
437 }
438 if (m_videoOutput)
439 m_videoOutput->deviceOrientationChanged();
440
441 [m_captureSession commitConfiguration];
442
444 recorder->toggleRecord(true);
446}
447
449{
450 if (!checkMicrophonePermission())
451 return;
452
453 auto recorder = m_service->recorderControl();
455 recorder->toggleRecord(false);
456
457 [m_captureSession beginConfiguration];
458 if (m_audioOutput) {
459 AVCaptureConnection *lastConnection = [m_audioOutput connectionWithMediaType:AVMediaTypeAudio];
460 [m_captureSession removeConnection:lastConnection];
461 }
462 attachAudioInputDevice();
463 if (m_audioInput)
464 addAudioCapture();
465 [m_captureSession commitConfiguration];
466
468 recorder->toggleRecord(true);
469}
470
472{
473 QByteArray deviceId = m_service->audioOutput()
474 ? m_service->audioOutput()->device.id()
475 : QByteArray();
476
477 [m_audioPreviewDelegate release];
478 m_audioPreviewDelegate = nil;
479 if (!deviceId.isEmpty()) {
480 m_audioPreviewDelegate = [[AVFAudioPreviewDelegate alloc] init];
482 audioOutputDevice:[NSString stringWithUTF8String:
483 deviceId.constData()]];
484 }
485}
486
487void AVFCameraSession::updateVideoOutput()
488{
489 if (m_videoOutput)
490 m_videoOutput->setVideoSink(m_videoSink);
491}
492
493bool AVFCameraSession::checkCameraPermission()
494{
495 const QCameraPermission permission;
496 const bool granted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
497 if (!granted)
498 qWarning() << "Access to camera not granted";
499
500 return granted;
501}
502
503bool AVFCameraSession::checkMicrophonePermission()
504{
505 const QMicrophonePermission permission;
506 const bool granted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
507 if (!granted)
508 qWarning() << "Access to microphone not granted";
509
510 return granted;
511}
512
513#include "moc_avfcamerasession_p.cpp"
AVFCameraSession * m_session
AVCaptureSession * m_captureSession
bool qt_set_active_format(AVCaptureDevice *captureDevice, AVCaptureDeviceFormat *format, bool preserveFps)
AVCaptureDeviceFormat * qt_convert_to_capture_device_format(AVCaptureDevice *captureDevice, const QCameraFormat &cameraFormat, const std::function< bool(uint32_t)> &cvFormatValidator)
bool m_active
IOBluetoothL2CAPChannel * channel
IOBluetoothDevice * device
void setupWithCaptureSession:audioOutputDevice:(AVFCameraSession *session,[audioOutputDevice] NSString *deviceId)
void newViewfinderFrame(const QVideoFrame &frame)
void deviceOrientationChanged(int angle=-1)
AVFMediaEncoder * recorderControl() const
QPlatformAudioOutput * audioOutput()
AVFImageCapture * avfImageCaptureControl() const
QPlatformAudioInput * audioInput()
void setActive(bool active)
AVCaptureSession * captureSession() const
void setCameraFormat(const QCameraFormat &format)
AVCaptureDevice * videoCaptureDevice() const
void setAudioOutputMuted(bool muted)
QCameraFormat cameraFormat() const
void readyToConfigureConnections()
void setActiveCamera(const QCameraDevice &info)
void setAudioInputVolume(float volume)
AVFCameraSession(AVFCameraService *service, QObject *parent=nullptr)
AVCaptureDevice * audioCaptureDevice() const
void newViewfinderFrame(const QVideoFrame &frame)
void setVideoSink(QVideoSink *sink)
FourCharCode defaultCodec()
void activeChanged(bool)
void setAudioOutputVolume(float volume)
void setAudioInputMuted(bool muted)
void setVideoSink(AVFVideoSink *sink)
QByteArray id
\qmlproperty string QtMultimedia::audioDevice::id
\inmodule QtCore
Definition qbytearray.h:57
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
The QCameraDevice class provides general information about camera devices.
bool isNull() const
Returns true if this QCameraDevice is null or invalid.
QByteArray id
\qmlproperty string QtMultimedia::cameraDevice::id
The QCameraFormat class describes a video format supported by a camera device. \inmodule QtMultimedia...
Access the camera for taking pictures or videos.
@ CameraError
Definition qcamera.h:63
Access the microphone for monitoring or recording sound.
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
The QVideoSink class represents a generic sink for video data.
Definition qvideosink.h:22
QMediaRecorder * recorder
Definition camera.cpp:20
@ AutoConnection
#define Q_FUNC_INFO
#define qApp
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
DBusConnection const char DBusError * error
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
#define qWarning
Definition qlogging.h:166
#define qCDebug(category,...)
GLint GLsizei GLsizei GLenum format
GLuint in
GLsizei GLenum GLboolean sink
#define tr(X)
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define Q_EMIT
#define Q_UNUSED(x)
QT_BEGIN_NAMESPACE typedef uchar * output
QHostInfo info
[0]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...