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
26void 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
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
117void EncodingInitializer::tryStartRecordingEngine()
118{
119 if (m_pendingSources.empty())
120 m_recordingEngine.handleFormatsInitialization();
121}
122
123void EncodingInitializer::emitStreamInitializationError(QString error)
124{
125 emit m_recordingEngine.streamInitializationError(
126 code: QMediaRecorder::ResourceError,
127 QStringLiteral("Video steam initialization error. ") + error);
128}
129
130void 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
138template <typename F>
139void 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
158bool EncodingInitializer::canPushFrame() const
159{
160 return true;
161}
162
163} // namespace QFFmpeg
164
165QT_END_NAMESPACE
166

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