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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | namespace QFFmpeg { |
17 | |
18 | EncodingInitializer::EncodingInitializer(RecordingEngine &engine) : m_recordingEngine(engine) { } |
19 | |
20 | EncodingInitializer::~EncodingInitializer() |
21 | { |
22 | for (QObject *source : m_pendingSources) |
23 | setEncoderInterface(source, nullptr); |
24 | } |
25 | |
26 | void EncodingInitializer::start(const std::vector<QPlatformAudioBufferInputBase *> &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 | tryStartRecordingEngine(); |
42 | } |
43 | |
44 | void 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 | |
54 | void 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 | |
73 | void 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 | |
87 | void 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 | |
117 | void EncodingInitializer::tryStartRecordingEngine() |
118 | { |
119 | if (m_pendingSources.empty()) |
120 | m_recordingEngine.handleFormatsInitialization(); |
121 | } |
122 | |
123 | void EncodingInitializer::emitStreamInitializationError(QString error) |
124 | { |
125 | emit m_recordingEngine.streamInitializationError( |
126 | code: QMediaRecorder::ResourceError, |
127 | QStringLiteral("Video steam initialization error. " ) + error); |
128 | } |
129 | |
130 | void EncodingInitializer::addPendingSource(QObject *source) |
131 | { |
132 | Q_ASSERT(m_pendingSources.count(source) == 0); |
133 | |
134 | setEncoderInterface(source, this); |
135 | m_pendingSources.emplace(args&: source); |
136 | } |
137 | |
138 | template <typename F> |
139 | void EncodingInitializer::erasePendingSource(QObject *source, F &&functionOrError, bool destroyed) |
140 | { |
141 | const auto erasedCount = m_pendingSources.erase(x: source); |
142 | if (erasedCount == 0) |
143 | return; // got a queued event, just ignore it. |
144 | |
145 | if (!destroyed) { |
146 | setEncoderInterface(source, nullptr); |
147 | disconnect(sender: source, signal: nullptr, receiver: this, member: nullptr); |
148 | } |
149 | |
150 | if constexpr (std::is_invocable_v<F>) |
151 | functionOrError(); |
152 | else |
153 | emitStreamInitializationError(error: functionOrError); |
154 | |
155 | tryStartRecordingEngine(); |
156 | } |
157 | |
158 | bool EncodingInitializer::canPushFrame() const |
159 | { |
160 | return true; |
161 | } |
162 | |
163 | } // namespace QFFmpeg |
164 | |
165 | QT_END_NAMESPACE |
166 | |