1// Copyright (C) 2024 The Qt Company Ltd.
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 "qffmpegencodinginitializer_p.h"
5#include "qffmpegrecordingengineutils_p.h"
6#include "qffmpegrecordingengine_p.h"
7#include "qffmpegaudioinput_p.h"
8#include "qvideoframe.h"
9
10#include "private/qplatformvideoframeinput_p.h"
11#include "private/qplatformaudiobufferinput_p.h"
12#include "private/qplatformaudiobufferinput_p.h"
13
14QT_BEGIN_NAMESPACE
15
16namespace QFFmpeg {
17
18EncodingInitializer::EncodingInitializer(RecordingEngine &engine) : m_recordingEngine(engine) { }
19
20EncodingInitializer::~EncodingInitializer()
21{
22 for (QObject *source : m_pendingSources)
23 setEncoderInterface(source, nullptr);
24}
25
26bool EncodingInitializer::start(const std::vector<QAudioBufferSource *> &audioSources,
27 const std::vector<QPlatformVideoSource *> &videoSources)
28{
29 for (auto source : audioSources) {
30 if (auto audioInput = qobject_cast<QFFmpegAudioInput *>(object: source))
31 m_recordingEngine.addAudioInput(input: audioInput);
32 else if (auto audioBufferInput = qobject_cast<QPlatformAudioBufferInput *>(object: source))
33 addAudioBufferInput(input: audioBufferInput);
34 else
35 Q_ASSERT(!"Undefined source type");
36 }
37
38 for (auto source : videoSources)
39 addVideoSource(source);
40
41 return tryStartRecordingEngine();
42}
43
44void EncodingInitializer::addAudioBufferInput(QPlatformAudioBufferInput *input)
45{
46 Q_ASSERT(input);
47
48 if (input->audioFormat().isValid())
49 m_recordingEngine.addAudioBufferInput(input, firstBuffer: {});
50 else
51 addPendingAudioBufferInput(input);
52}
53
54void EncodingInitializer::addPendingAudioBufferInput(QPlatformAudioBufferInput *input)
55{
56 addPendingSource(source: input);
57
58 connect(sender: input, signal: &QPlatformAudioBufferInput::destroyed, context: this, slot: [this, input]() {
59 erasePendingSource(source: input, QStringLiteral("Audio source deleted"), destroyed: true);
60 });
61
62 connect(sender: input, signal: &QPlatformAudioBufferInput::newAudioBuffer, context: this,
63 slot: [this, input](const QAudioBuffer &buffer) {
64 if (buffer.isValid())
65 erasePendingSource(
66 source: input, functionOrError: [&]() { m_recordingEngine.addAudioBufferInput(input, firstBuffer: buffer); });
67 else
68 erasePendingSource(source: input,
69 QStringLiteral("Audio source has sent the end frame"));
70 });
71}
72
73void EncodingInitializer::addVideoSource(QPlatformVideoSource *source)
74{
75 Q_ASSERT(source);
76 Q_ASSERT(source->isActive());
77
78 if (source->frameFormat().isValid())
79 m_recordingEngine.addVideoSource(source, firstFrame: {});
80 else if (source->hasError())
81 emitStreamInitializationError(QStringLiteral("Video source error: ")
82 + source->errorString());
83 else
84 addPendingVideoSource(source);
85}
86
87void EncodingInitializer::addPendingVideoSource(QPlatformVideoSource *source)
88{
89 addPendingSource(source);
90
91 connect(sender: source, signal: &QPlatformVideoSource::errorChanged, context: this, slot: [this, source]() {
92 if (source->hasError())
93 erasePendingSource(source,
94 QStringLiteral("Videio source error: ") + source->errorString());
95 });
96
97 connect(sender: source, signal: &QPlatformVideoSource::destroyed, context: this, slot: [this, source]() {
98 erasePendingSource(source, QStringLiteral("Source deleted"), destroyed: true);
99 });
100
101 connect(sender: source, signal: &QPlatformVideoSource::activeChanged, context: this, slot: [this, source]() {
102 if (!source->isActive())
103 erasePendingSource(source, QStringLiteral("Video source deactivated"));
104 });
105
106 connect(sender: source, signal: &QPlatformVideoSource::newVideoFrame, context: this,
107 slot: [this, source](const QVideoFrame &frame) {
108 if (frame.isValid())
109 erasePendingSource(source,
110 functionOrError: [&]() { m_recordingEngine.addVideoSource(source, firstFrame: frame); });
111 else
112 erasePendingSource(source,
113 QStringLiteral("Video source has sent the end frame"));
114 });
115}
116
117bool EncodingInitializer::tryStartRecordingEngine()
118{
119 if (m_pendingSources.empty())
120 return m_recordingEngine.startEncoders();
121
122 // return true as no errors found, even though they can occur later on,
123 // upon the following encoders initializations.
124 return true;
125}
126
127void EncodingInitializer::emitStreamInitializationError(QString error)
128{
129 emit m_recordingEngine.streamInitializationError(
130 code: QMediaRecorder::ResourceError,
131 QStringLiteral("Video steam initialization error. ") + error);
132}
133
134void EncodingInitializer::addPendingSource(QObject *source)
135{
136 Q_ASSERT(m_pendingSources.count(source) == 0);
137
138 setEncoderInterface(source, this);
139 m_pendingSources.emplace(args&: source);
140}
141
142template <typename F>
143void EncodingInitializer::erasePendingSource(QObject *source, F &&functionOrError, bool destroyed)
144{
145 const auto erasedCount = m_pendingSources.erase(x: source);
146 if (erasedCount == 0)
147 return; // got a queued event, just ignore it.
148
149 if (!destroyed) {
150 setEncoderInterface(source, nullptr);
151 disconnect(sender: source, signal: nullptr, receiver: this, member: nullptr);
152 }
153
154 if constexpr (std::is_invocable_v<F>)
155 functionOrError();
156 else
157 emitStreamInitializationError(error: functionOrError);
158
159 tryStartRecordingEngine();
160}
161
162bool EncodingInitializer::canPushFrame() const
163{
164 return true;
165}
166
167} // namespace QFFmpeg
168
169QT_END_NAMESPACE
170

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencodinginitializer.cpp