Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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 <QtMultimedia/private/qavfcameradebug_p.h>
7#include "avfcamera_p.h"
11#include <QtMultimedia/private/qavfcamerautility_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
29QT_USE_NAMESPACE
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
43 AVFCameraSession *m_session;
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);
94 QMetaObject::invokeMethod(m_session, "processRuntimeError", Qt::AutoConnection);
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
111AVFCameraSession::AVFCameraSession(AVFCameraService *service, QObject *parent)
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 delete m_videoOutput;
138
139 [m_observer release];
140 [m_captureSession release];
141}
142
143void AVFCameraSession::setActiveCamera(const QCameraDevice &info)
144{
145 if (m_activeCameraDevice != info) {
146 m_activeCameraDevice = info;
147
148 if (checkCameraPermission())
150 }
151}
152
153void AVFCameraSession::setCameraFormat(const QCameraFormat &format)
154{
155 if (m_cameraFormat == format)
156 return;
157
158 updateCameraFormat(format);
159}
160
162{
163 return m_cameraFormat;
164}
165
166void AVFCameraSession::updateCameraFormat(const QCameraFormat &format)
167{
168 m_cameraFormat = format;
169
170 AVCaptureDevice *captureDevice = videoCaptureDevice();
171 if (!captureDevice)
172 return;
173
174 AVCaptureDeviceFormat *newFormat = qt_convert_to_capture_device_format(captureDevice, format);
175 if (newFormat)
176 qt_set_active_format(captureDevice, newFormat, false);
177}
178
179void AVFCameraSession::setVideoOutput(AVFCameraRenderer *output)
180{
181 if (m_videoOutput == output)
182 return;
183
184 delete m_videoOutput;
185 m_videoOutput = output;
186 if (output)
188}
189
190void AVFCameraSession::addAudioCapture()
191{
192 if (!m_audioOutput) {
193 m_audioOutput = [[AVCaptureAudioDataOutput alloc] init];
194 if (m_audioOutput && [m_captureSession canAddOutput:m_audioOutput]) {
195 [m_captureSession addOutput:m_audioOutput];
196 } else {
197 qWarning() << Q_FUNC_INFO << "failed to add audio output";
198 }
199 }
200}
201
203{
204 if (m_videoInput)
205 return m_videoInput.device;
206
207 return nullptr;
208}
209
211{
212 if (m_audioInput)
213 return m_audioInput.device;
214
215 return nullptr;
216}
217
219{
220 m_inputVolume = volume;
221
222 if (m_inputMuted)
223 volume = 0.0;
224
225#ifdef Q_OS_MACOS
226 AVCaptureConnection *audioInputConnection = [m_audioOutput connectionWithMediaType:AVMediaTypeAudio];
227 NSArray<AVCaptureAudioChannel *> *audioChannels = audioInputConnection.audioChannels;
228 if (audioChannels) {
229 for (AVCaptureAudioChannel *channel in audioChannels) {
230 channel.volume = volume;
231 }
232 }
233#endif
234}
235
237{
238 m_inputMuted = muted;
239 setAudioInputVolume(m_inputVolume);
240}
241
243{
244 if (m_audioPreviewDelegate)
245 [m_audioPreviewDelegate setVolume:volume];
246}
247
249{
250 if (m_audioPreviewDelegate)
251 [m_audioPreviewDelegate setMuted:muted];
252}
253
255{
256 return m_active;
257}
258
259void AVFCameraSession::setActive(bool active)
260{
261 if (m_active == active)
262 return;
263
264 m_active = active;
265
266 qCDebug(qLcCamera) << Q_FUNC_INFO << m_active << " -> " << active;
267
268 if (active) {
269 if (!m_activeCameraDevice.isNull()) {
270 Q_EMIT readyToConfigureConnections();
271 m_defaultCodec = 0;
272 defaultCodec();
273 }
274
275 applyImageEncoderSettings();
276
277 // According to the doc, the capture device must be locked before
278 // startRunning to prevent the format we set to be overridden by the
279 // session preset.
280 [videoCaptureDevice() lockForConfiguration:nil];
281 [m_captureSession startRunning];
282 [videoCaptureDevice() unlockForConfiguration];
283 } else {
284 [m_captureSession stopRunning];
285 }
286}
287
289{
290 qWarning() << tr("Runtime camera error");
291 m_active = false;
292 Q_EMIT error(QCamera::CameraError, tr("Runtime camera error"));
293}
294
296{
297 qCDebug(qLcCamera) << Q_FUNC_INFO;
298 if (!m_active) {
299 m_active = true;
300 Q_EMIT activeChanged(m_active);
301 }
302}
303
305{
306 qCDebug(qLcCamera) << Q_FUNC_INFO;
307 if (m_active) {
308 m_active = false;
309 Q_EMIT activeChanged(m_active);
310 }
311}
312
313AVCaptureDevice *AVFCameraSession::createVideoCaptureDevice()
314{
315 AVCaptureDevice *device = nullptr;
316
317 QByteArray deviceId = m_activeCameraDevice.id();
318 if (!deviceId.isEmpty()) {
319 device = [AVCaptureDevice deviceWithUniqueID:
320 [NSString stringWithUTF8String:
321 deviceId.constData()]];
322 }
323
324 return device;
325}
326
327AVCaptureDevice *AVFCameraSession::createAudioCaptureDevice()
328{
329 AVCaptureDevice *device = nullptr;
330
331 QByteArray deviceId = m_service->audioInput() ? m_service->audioInput()->device.id()
332 : QByteArray();
333 if (!deviceId.isEmpty())
334 device = [AVCaptureDevice deviceWithUniqueID: [NSString stringWithUTF8String:deviceId.constData()]];
335
336 return device;
337}
338
339void AVFCameraSession::attachVideoInputDevice()
340{
341 if (!checkCameraPermission())
342 return;
343
344 if (m_videoInput) {
345 [m_captureSession removeInput:m_videoInput];
346 [m_videoInput release];
347 m_videoInput = nullptr;
348 }
349
350 AVCaptureDevice *videoDevice = createVideoCaptureDevice();
351 if (!videoDevice)
352 return;
353
354 m_videoInput = [AVCaptureDeviceInput
355 deviceInputWithDevice:videoDevice
356 error:nil];
357 if (m_videoInput && [m_captureSession canAddInput:m_videoInput]) {
358 [m_videoInput retain];
359 [m_captureSession addInput:m_videoInput];
360 } else {
361 qWarning() << "Failed to create video device input";
362 }
363}
364
365void AVFCameraSession::attachAudioInputDevice()
366{
367 if (m_audioInput) {
368 [m_captureSession removeInput:m_audioInput];
369 [m_audioInput release];
370 m_audioInput = nullptr;
371 }
372
373 AVCaptureDevice *audioDevice = createAudioCaptureDevice();
374 if (!audioDevice)
375 return;
376
377 m_audioInput = [AVCaptureDeviceInput
378 deviceInputWithDevice:audioDevice
379 error:nil];
380
381 if (m_audioInput && [m_captureSession canAddInput:m_audioInput]) {
382 [m_audioInput retain];
383 [m_captureSession addInput:m_audioInput];
384 } else {
385 qWarning() << "Failed to create audio device input";
386 }
387}
388
389bool AVFCameraSession::applyImageEncoderSettings()
390{
391 if (AVFImageCapture *control = m_service->avfImageCaptureControl())
392 return control->applySettings();
393
394 return false;
395}
396
398{
399 if (!m_defaultCodec) {
400 if (AVCaptureDevice *device = videoCaptureDevice()) {
401 AVCaptureDeviceFormat *format = device.activeFormat;
402 if (!format || !format.formatDescription)
403 return m_defaultCodec;
404 m_defaultCodec = CMVideoFormatDescriptionGetCodecType(format.formatDescription);
405 }
406 }
407 return m_defaultCodec;
408}
409
410void AVFCameraSession::setVideoSink(QVideoSink *sink)
411{
412 auto *videoSink = sink ? static_cast<AVFVideoSink *>(sink->platformVideoSink()) : nullptr;
413
414 if (m_videoSink == videoSink)
415 return;
416
417 m_videoSink = videoSink;
418
419 updateVideoOutput();
420}
421
423{
424 auto recorder = m_service->recorderControl();
425 if (recorder && recorder->state() == QMediaRecorder::RecordingState)
426 recorder->toggleRecord(false);
427
428 [m_captureSession beginConfiguration];
429
430 attachVideoInputDevice();
431 if (!m_activeCameraDevice.isNull() && !m_videoOutput) {
432 setVideoOutput(new AVFCameraRenderer(this));
433 connect(m_videoOutput, &AVFCameraRenderer::newViewfinderFrame,
434 this, &AVFCameraSession::newViewfinderFrame);
435 updateVideoOutput();
436 }
437 if (m_videoOutput)
438 m_videoOutput->deviceOrientationChanged();
439
440 [m_captureSession commitConfiguration];
441
442 if (recorder && recorder->state() == QMediaRecorder::RecordingState)
443 recorder->toggleRecord(true);
444 Q_EMIT readyToConfigureConnections();
445}
446
448{
449 if (!checkMicrophonePermission())
450 return;
451
452 auto recorder = m_service->recorderControl();
453 if (recorder && recorder->state() == QMediaRecorder::RecordingState)
454 recorder->toggleRecord(false);
455
456 [m_captureSession beginConfiguration];
457 if (m_audioOutput) {
458 AVCaptureConnection *lastConnection = [m_audioOutput connectionWithMediaType:AVMediaTypeAudio];
459 [m_captureSession removeConnection:lastConnection];
460 }
461 attachAudioInputDevice();
462 if (m_audioInput)
463 addAudioCapture();
464 [m_captureSession commitConfiguration];
465
466 if (recorder && recorder->state() == QMediaRecorder::RecordingState)
467 recorder->toggleRecord(true);
468}
469
471{
472 QByteArray deviceId = m_service->audioOutput()
473 ? m_service->audioOutput()->device.id()
474 : QByteArray();
475
476 [m_audioPreviewDelegate release];
477 m_audioPreviewDelegate = nil;
478 if (!deviceId.isEmpty()) {
479 m_audioPreviewDelegate = [[AVFAudioPreviewDelegate alloc] init];
480 [m_audioPreviewDelegate setupWithCaptureSession:this
481 audioOutputDevice:[NSString stringWithUTF8String:
482 deviceId.constData()]];
483 }
484}
485
486void AVFCameraSession::updateVideoOutput()
487{
488 if (m_videoOutput)
489 m_videoOutput->setVideoSink(m_videoSink);
490}
491
492bool AVFCameraSession::checkCameraPermission()
493{
494 const QCameraPermission permission;
495 const bool granted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
496 if (!granted)
497 qWarning() << "Access to camera not granted";
498
499 return granted;
500}
501
502bool AVFCameraSession::checkMicrophonePermission()
503{
504 const QMicrophonePermission permission;
505 const bool granted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
506 if (!granted)
507 qWarning() << "Access to microphone not granted";
508
509 return granted;
510}
511
512#include "moc_avfcamerasession_p.cpp"
void configureAVCaptureSession(AVFCameraSession *cameraSession)
AVFMediaEncoder * recorderControl() const
AVFImageCapture * avfImageCaptureControl() const
void setCameraFormat(const QCameraFormat &format)
AVCaptureDevice * videoCaptureDevice() const
void setAudioOutputMuted(bool muted)
QCameraFormat cameraFormat() const
~AVFCameraSession() override
void setActiveCamera(const QCameraDevice &info)
void setAudioInputVolume(float volume)
AVCaptureDevice * audioCaptureDevice() const
void setVideoSink(QVideoSink *sink)
FourCharCode defaultCodec()
void setAudioOutputVolume(float volume)
void setAudioInputMuted(bool muted)
void toggleRecord(bool enable)
void setVideoSink(AVFVideoSink *sink)