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
5
6#include <avfvideosink_p.h>
7#include <camera/avfcamera_p.h>
8#include <camera/avfcameraservice_p.h>
9#include <camera/avfcamerarenderer_p.h>
10#include <camera/avfimagecapture_p.h>
11#include <camera/avfmediaencoder_p.h>
12
13#include <QtMultimedia/private/qavfcameradebug_p.h>
14#include <QtMultimedia/private/qavfcamerautility_p.h>
15#include <QtMultimedia/private/qplatformaudioinput_p.h>
16#include <QtMultimedia/private/qplatformaudiooutput_p.h>
17#include <QtCore/qcoreapplication.h>
18#include <QtCore/qdatetime.h>
19#include <QtCore/qdebug.h>
20#include <QtCore/qelapsedtimer.h>
21#include <QtCore/qpermissions.h>
22#include <QtCore/qpointer.h>
23#include <QtCore/qurl.h>
24
25#import <CoreFoundation/CoreFoundation.h>
26#import <Foundation/Foundation.h>
27
28QT_USE_NAMESPACE
29
30@interface AVFCameraSessionObserver : NSObject
31
32- (AVFCameraSessionObserver *) initWithCameraSession:(AVFCameraSession*)session;
33- (void) processRuntimeError:(NSNotification *)notification;
34- (void) processSessionStarted:(NSNotification *)notification;
35- (void) processSessionStopped:(NSNotification *)notification;
36
37@end
38
39@implementation AVFCameraSessionObserver
40{
41@private
42 AVFCameraSession *m_session;
43 AVCaptureSession *m_captureSession;
44}
45
46- (AVFCameraSessionObserver *) initWithCameraSession:(AVFCameraSession*)session
47{
48 if (!(self = [super init]))
49 return nil;
50
51 self->m_session = session;
52 self->m_captureSession = session->captureSession();
53
54 [m_captureSession retain];
55 [[NSNotificationCenter defaultCenter] addObserver:self
56 selector:@selector(processRuntimeError:)
57 name:AVCaptureSessionRuntimeErrorNotification
58 object:m_captureSession];
59
60 [[NSNotificationCenter defaultCenter] addObserver:self
61 selector:@selector(processSessionStarted:)
62 name:AVCaptureSessionDidStartRunningNotification
63 object:m_captureSession];
64
65 [[NSNotificationCenter defaultCenter] addObserver:self
66 selector:@selector(processSessionStopped:)
67 name:AVCaptureSessionDidStopRunningNotification
68 object:m_captureSession];
69
70 return self;
71}
72
73- (void) dealloc
74{
75 [[NSNotificationCenter defaultCenter] removeObserver:self
76 name:AVCaptureSessionRuntimeErrorNotification
77 object:m_captureSession];
78
79 [[NSNotificationCenter defaultCenter] removeObserver:self
80 name:AVCaptureSessionDidStartRunningNotification
81 object:m_captureSession];
82
83 [[NSNotificationCenter defaultCenter] removeObserver:self
84 name:AVCaptureSessionDidStopRunningNotification
85 object:m_captureSession];
86 [m_captureSession release];
87 [super dealloc];
88}
89
90- (void) processRuntimeError:(NSNotification *)notification
91{
92 Q_UNUSED(notification);
93 QMetaObject::invokeMethod(m_session, "processRuntimeError", Qt::AutoConnection);
94}
95
96- (void) processSessionStarted:(NSNotification *)notification
97{
98 Q_UNUSED(notification);
99 QMetaObject::invokeMethod(m_session, "processSessionStarted", Qt::AutoConnection);
100}
101
102- (void) processSessionStopped:(NSNotification *)notification
103{
104 Q_UNUSED(notification);
105 QMetaObject::invokeMethod(m_session, "processSessionStopped", Qt::AutoConnection);
106}
107
108@end
109
110AVFCameraSession::AVFCameraSession(AVFCameraService *service, QObject *parent)
111 : QObject(parent)
112 , m_service(service)
113 , m_defaultCodec(0)
114{
115 m_captureSession = [[AVCaptureSession alloc] init];
116 m_observer = [[AVFCameraSessionObserver alloc] initWithCameraSession:this];
117}
118
120{
121 if (m_videoInput) {
122 [m_captureSession removeInput:m_videoInput];
123 [m_videoInput release];
124 }
125
126 if (m_audioInput) {
127 [m_captureSession removeInput:m_audioInput];
128 [m_audioInput release];
129 }
130
131 if (m_audioOutput) {
132 [m_captureSession removeOutput:m_audioOutput];
133 [m_audioOutput release];
134 }
135
136 delete m_videoOutput;
137
138 [m_observer release];
139 [m_captureSession release];
140}
141
142void AVFCameraSession::setActiveCamera(const QCameraDevice &info)
143{
144 if (m_activeCameraDevice != info) {
145 m_activeCameraDevice = info;
146
147 if (checkCameraPermission())
149 }
150}
151
152void AVFCameraSession::setCameraFormat(const QCameraFormat &format)
153{
154 if (m_cameraFormat == format)
155 return;
156
157 updateCameraFormat(format);
158}
159
161{
162 return m_cameraFormat;
163}
164
165void AVFCameraSession::updateCameraFormat(const QCameraFormat &format)
166{
167 m_cameraFormat = format;
168
169 AVCaptureDevice *captureDevice = videoCaptureDevice();
170 if (!captureDevice)
171 return;
172
173 AVCaptureDeviceFormat *newFormat = qt_convert_to_capture_device_format(captureDevice, format);
174 if (newFormat)
175 qt_set_active_format(captureDevice, newFormat, false);
176}
177
178void AVFCameraSession::setVideoOutput(AVFCameraRenderer *output)
179{
180 if (m_videoOutput == output)
181 return;
182
183 delete m_videoOutput;
184 m_videoOutput = output;
185 if (output)
187}
188
189void AVFCameraSession::addAudioCapture()
190{
191 if (!m_audioOutput) {
192 m_audioOutput = [[AVCaptureAudioDataOutput alloc] init];
193 if (m_audioOutput && [m_captureSession canAddOutput:m_audioOutput]) {
194 [m_captureSession addOutput:m_audioOutput];
195 } else {
196 qWarning() << Q_FUNC_INFO << "failed to add audio output";
197 }
198 }
199}
200
202{
203 if (m_videoInput)
204 return m_videoInput.device;
205
206 return nullptr;
207}
208
210{
211 if (m_audioInput)
212 return m_audioInput.device;
213
214 return nullptr;
215}
216
218{
219 m_inputVolume = volume;
220
221 if (m_inputMuted)
222 volume = 0.0;
223
224#ifdef Q_OS_MACOS
225 AVCaptureConnection *audioInputConnection = [m_audioOutput connectionWithMediaType:AVMediaTypeAudio];
226 NSArray<AVCaptureAudioChannel *> *audioChannels = audioInputConnection.audioChannels;
227 if (audioChannels) {
228 for (AVCaptureAudioChannel *channel in audioChannels) {
229 channel.volume = volume;
230 }
231 }
232#endif
233}
234
236{
237 m_inputMuted = muted;
238 setAudioInputVolume(m_inputVolume);
239}
240
242{
243 if (m_audioPreviewDelegate)
244 [m_audioPreviewDelegate setVolume:volume];
245}
246
248{
249 if (m_audioPreviewDelegate)
250 [m_audioPreviewDelegate setMuted:muted];
251}
252
254{
255 return m_active;
256}
257
258void AVFCameraSession::setActive(bool active)
259{
260 if (m_active == active)
261 return;
262
263 m_active = active;
264
265 qCDebug(qLcCamera) << Q_FUNC_INFO << m_active << " -> " << active;
266
267 if (active) {
268 if (!m_activeCameraDevice.isNull()) {
269 Q_EMIT readyToConfigureConnections();
270 m_defaultCodec = 0;
271 defaultCodec();
272 }
273
274 applyImageEncoderSettings();
275
276 // According to the doc, the capture device must be locked before
277 // startRunning to prevent the format we set to be overridden by the
278 // session preset.
279 [videoCaptureDevice() lockForConfiguration:nil];
280 [m_captureSession startRunning];
281 [videoCaptureDevice() unlockForConfiguration];
282 } else {
283 [m_captureSession stopRunning];
284 }
285}
286
288{
289 qWarning() << tr("Runtime camera error");
290 m_active = false;
291 Q_EMIT error(QCamera::CameraError, tr("Runtime camera error"));
292}
293
295{
296 qCDebug(qLcCamera) << Q_FUNC_INFO;
297 if (!m_active) {
298 m_active = true;
299 Q_EMIT activeChanged(m_active);
300 }
301}
302
304{
305 qCDebug(qLcCamera) << Q_FUNC_INFO;
306 if (m_active) {
307 m_active = false;
308 Q_EMIT activeChanged(m_active);
309 }
310}
311
312AVCaptureDevice *AVFCameraSession::createVideoCaptureDevice()
313{
314 AVCaptureDevice *device = nullptr;
315
316 QByteArray deviceId = m_activeCameraDevice.id();
317 if (!deviceId.isEmpty()) {
318 device = [AVCaptureDevice deviceWithUniqueID:
319 [NSString stringWithUTF8String:
320 deviceId.constData()]];
321 }
322
323 return device;
324}
325
326AVCaptureDevice *AVFCameraSession::createAudioCaptureDevice()
327{
328 AVCaptureDevice *device = nullptr;
329
330 QByteArray deviceId = m_service->audioInput() ? m_service->audioInput()->device.id()
331 : QByteArray();
332 if (!deviceId.isEmpty())
333 device = [AVCaptureDevice deviceWithUniqueID: [NSString stringWithUTF8String:deviceId.constData()]];
334
335 return device;
336}
337
338void AVFCameraSession::attachVideoInputDevice()
339{
340 if (!checkCameraPermission())
341 return;
342
343 if (m_videoInput) {
344 [m_captureSession removeInput:m_videoInput];
345 [m_videoInput release];
346 m_videoInput = nullptr;
347 }
348
349 AVCaptureDevice *videoDevice = createVideoCaptureDevice();
350 if (!videoDevice)
351 return;
352
353 m_videoInput = [AVCaptureDeviceInput
354 deviceInputWithDevice:videoDevice
355 error:nil];
356 if (m_videoInput && [m_captureSession canAddInput:m_videoInput]) {
357 [m_videoInput retain];
358 [m_captureSession addInput:m_videoInput];
359 } else {
360 qWarning() << "Failed to create video device input";
361 }
362}
363
364void AVFCameraSession::attachAudioInputDevice()
365{
366 if (m_audioInput) {
367 [m_captureSession removeInput:m_audioInput];
368 [m_audioInput release];
369 m_audioInput = nullptr;
370 }
371
372 AVCaptureDevice *audioDevice = createAudioCaptureDevice();
373 if (!audioDevice)
374 return;
375
376 m_audioInput = [AVCaptureDeviceInput
377 deviceInputWithDevice:audioDevice
378 error:nil];
379
380 if (m_audioInput && [m_captureSession canAddInput:m_audioInput]) {
381 [m_audioInput retain];
382 [m_captureSession addInput:m_audioInput];
383 } else {
384 qWarning() << "Failed to create audio device input";
385 }
386}
387
388bool AVFCameraSession::applyImageEncoderSettings()
389{
390 if (AVFImageCapture *control = m_service->avfImageCaptureControl())
391 return control->applySettings();
392
393 return false;
394}
395
397{
398 if (!m_defaultCodec) {
399 if (AVCaptureDevice *device = videoCaptureDevice()) {
400 AVCaptureDeviceFormat *format = device.activeFormat;
401 if (!format || !format.formatDescription)
402 return m_defaultCodec;
403 m_defaultCodec = CMVideoFormatDescriptionGetCodecType(format.formatDescription);
404 }
405 }
406 return m_defaultCodec;
407}
408
409void AVFCameraSession::setVideoSink(QVideoSink *sink)
410{
411 auto *videoSink = sink ? static_cast<AVFVideoSink *>(sink->platformVideoSink()) : nullptr;
412
413 if (m_videoSink == videoSink)
414 return;
415
416 m_videoSink = videoSink;
417
418 updateVideoOutput();
419}
420
422{
423 auto recorder = m_service->recorderControl();
424 if (recorder && recorder->state() == QMediaRecorder::RecordingState)
425 recorder->toggleRecord(false);
426
427 [m_captureSession beginConfiguration];
428
429 attachVideoInputDevice();
430 if (!m_activeCameraDevice.isNull() && !m_videoOutput) {
431 setVideoOutput(new AVFCameraRenderer(this));
432 connect(m_videoOutput, &AVFCameraRenderer::newViewfinderFrame,
433 this, &AVFCameraSession::newViewfinderFrame);
434 updateVideoOutput();
435 }
436 if (m_videoOutput)
437 m_videoOutput->deviceOrientationChanged();
438
439 [m_captureSession commitConfiguration];
440
441 if (recorder && recorder->state() == QMediaRecorder::RecordingState)
442 recorder->toggleRecord(true);
443 Q_EMIT readyToConfigureConnections();
444}
445
447{
448 if (!checkMicrophonePermission())
449 return;
450
451 auto recorder = m_service->recorderControl();
452 if (recorder && recorder->state() == QMediaRecorder::RecordingState)
453 recorder->toggleRecord(false);
454
455 [m_captureSession beginConfiguration];
456 if (m_audioOutput) {
457 AVCaptureConnection *lastConnection = [m_audioOutput connectionWithMediaType:AVMediaTypeAudio];
458 [m_captureSession removeConnection:lastConnection];
459 }
460 attachAudioInputDevice();
461 if (m_audioInput)
462 addAudioCapture();
463 [m_captureSession commitConfiguration];
464
465 if (recorder && recorder->state() == QMediaRecorder::RecordingState)
466 recorder->toggleRecord(true);
467}
468
470{
471 QByteArray deviceId = m_service->audioOutput()
472 ? m_service->audioOutput()->device.id()
473 : QByteArray();
474
475 [m_audioPreviewDelegate release];
476 m_audioPreviewDelegate = nil;
477 if (!deviceId.isEmpty()) {
478 m_audioPreviewDelegate = [[AVFAudioPreviewDelegate alloc] init];
479 [m_audioPreviewDelegate setupWithCaptureSession:this
480 audioOutputDevice:[NSString stringWithUTF8String:
481 deviceId.constData()]];
482 }
483}
484
485void AVFCameraSession::updateVideoOutput()
486{
487 if (m_videoOutput)
488 m_videoOutput->setVideoSink(m_videoSink);
489}
490
491bool AVFCameraSession::checkCameraPermission()
492{
493 const QCameraPermission permission;
494 const bool granted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
495 if (!granted)
496 qWarning() << "Access to camera not granted";
497
498 return granted;
499}
500
501bool AVFCameraSession::checkMicrophonePermission()
502{
503 const QMicrophonePermission permission;
504 const bool granted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
505 if (!granted)
506 qWarning() << "Access to microphone not granted";
507
508 return granted;
509}
510
511#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)