Revision control

Copy as Markdown

// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <QtFFmpegMediaPluginImpl/private/qavfcapturephotooutputdelegate_p.h>
#include <QtCore/private/qexpected_p.h>
#include <QtCore/qmutex.h>
#include <QtFFmpegMediaPluginImpl/private/qavfcamerarotationtracker_p.h>
#include <QtFFmpegMediaPluginImpl/private/qcvimagevideobuffer_p.h>
#include <QtMultimedia/private/qavfcameradebug_p.h>
#include <QtMultimedia/private/qavfcamerautility_p.h>
#include <QtMultimedia/private/qavfhelpers_p.h>
#include <QtMultimedia/private/qmultimediautils_p.h>
#include <QtMultimedia/private/qvideoframe_p.h>
QT_USE_NAMESPACE
namespace {
struct ImageCaptureErrorPair {
QImageCapture::Error type;
QString message;
};
} // Anonymous namespace
[[nodiscard]] static q23::expected<QVideoFrame, ImageCaptureErrorPair> processAvCaptureOutput(
AVCapturePhoto *,
QFFmpeg::QAVFCapturePhotoOutputDelegate *,
NSError *);
@implementation QT_MANGLE_NAMESPACE(QAVFCapturePhotoOutputDelegate) {
@private
QFFmpeg::QAVFStillPhotoNotifier m_notifier;
@public
QFFmpeg::AvfCameraRotationTracker m_qAvfCameraRotationTracker;
}
- (instancetype) init:(AVCaptureDevice *)captureDevice
{
if (!(self = [super init]))
return nil;
Q_ASSERT(captureDevice);
m_qAvfCameraRotationTracker = QFFmpeg::AvfCameraRotationTracker(captureDevice);
return self;
}
- (QFFmpeg::QAVFStillPhotoNotifier &)notifier
{
return self->m_notifier;
}
// The AVCapturePhotoOutput gives no guarantee on which thread may call this function,
// so this function needs to be completely thread safe.
- (void) captureOutput:(AVCapturePhotoOutput *)output
didFinishProcessingPhoto:(AVCapturePhoto *)photo
error:(NSError *)nsError
{
q23::expected<QVideoFrame, ImageCaptureErrorPair> processResult = processAvCaptureOutput(
photo,
self,
nsError);
if (processResult) {
Q_EMIT self->m_notifier.succeeded(std::move(*processResult));
} else {
Q_EMIT self->m_notifier.failed(
processResult.error().type,
std::move(processResult.error().message));
}
}
@end
static q23::expected<QVideoFrame, ImageCaptureErrorPair> processAvCaptureOutput(
AVCapturePhoto *photo,
QFFmpeg::QAVFCapturePhotoOutputDelegate *captureDelegate,
NSError *nsError)
{
Q_ASSERT(photo);
Q_ASSERT(captureDelegate);
using namespace Qt::Literals::StringLiterals;
if (nsError) {
qCWarning(qLcCamera)
<< "Error while finalizing AVCapturePhotoOutput capture:"
<< QString::fromNSString(nsError.localizedDescription);
return q23::unexpected{ ImageCaptureErrorPair {
QImageCapture::Error::ResourceError,
u"Internal error while finalizing still photo capture"_s } };
}
if (!photo.pixelBuffer) {
qCWarning(qLcCamera)
<< "When finalizing AVCapturePhotoOutput capture, pixelBuffer was null";
return q23::unexpected{ ImageCaptureErrorPair {
QImageCapture::Error::ResourceError,
u"Internal error while finalizing still photo capture"_s } };
}
QVideoFrameFormat format = QAVFHelpers::videoFormatForImageBuffer(photo.pixelBuffer);
if (!format.isValid()) {
qCWarning(qLcCamera)
<< "Unable to determine QVideoFrameFormat based on "
"AVCapturePhotoOutput result. Likely an issue with the"
"with the configuration of the still photo capture";
return q23::unexpected{ ImageCaptureErrorPair {
QImageCapture::Error::ResourceError,
u"Internal error while finalizing still photo capture"_s } };
}
// Apply rotation data to the format
const int cameraRotationDegrees = captureDelegate->m_qAvfCameraRotationTracker
.rotationDegrees();
format.setRotation(qVideoRotationFromDegrees(cameraRotationDegrees));
auto sharedPixelBuffer = QAVFHelpers::QSharedCVPixelBuffer(
photo.pixelBuffer,
QAVFHelpers::QSharedCVPixelBuffer::RefMode::NeedsRef);
auto videoFrame = QVideoFramePrivate::createFrame(
std::make_unique<QFFmpeg::CVImageVideoBuffer>(std::move(sharedPixelBuffer)),
std::move(format));
if (!videoFrame.isValid()) {
qCWarning(qLcCamera) << "Unable to create QVideoFrame from AVCapturePhotoOutput result";
return q23::unexpected{ ImageCaptureErrorPair {
QImageCapture::Error::ResourceError,
u"Internal error while finalizing still photo capture"_s } };
}
// If we are the front-facing camera, we need to provide the flip horizontally flag to the
// frame (not the format).
AVCaptureDevice *avCaptureDevice = captureDelegate->m_qAvfCameraRotationTracker
.avCaptureDevice();
Q_ASSERT(avCaptureDevice);
videoFrame.setMirrored(avCaptureDevice.position == AVCaptureDevicePositionFront);
return videoFrame;
}