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
qffmpegvideoencoder.cpp
Go to the documentation of this file.
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
9#include "private/qvideoframe_p.h"
10#include "private/qmultimediautils_p.h"
11#include <QtCore/qloggingcategory.h>
12
14
15using namespace Qt::StringLiterals;
16
17namespace QFFmpeg {
18
19Q_STATIC_LOGGING_CATEGORY(qLcFFmpegVideoEncoder, "qt.multimedia.ffmpeg.videoencoder");
20
21VideoEncoder::VideoEncoder(RecordingEngine &recordingEngine, const QMediaEncoderSettings &settings,
22 const QVideoFrameFormat &format, std::optional<AVPixelFormat> hwFormat)
23 : EncoderThread(recordingEngine), m_settings(settings)
24{
25 setObjectName(QLatin1String("VideoEncoder"));
26
27 const AVPixelFormat swFormat = QFFmpegVideoBuffer::toAVPixelFormat(format.pixelFormat());
28 if (format.streamFrameRate() > 0.)
29 m_fixedSourceFrameRate = format.streamFrameRate();
30
31 m_sourceParams.size = format.frameSize();
32 m_sourceParams.format = hwFormat && *hwFormat != AV_PIX_FMT_NONE ? *hwFormat : swFormat;
33 // Temporary: check isSwPixelFormat because of android issue (QTBUG-116836)
34 // TODO: assign swFormat.
35 m_sourceParams.swFormat =
36 isSwPixelFormat(m_sourceParams.format) ? m_sourceParams.format : swFormat;
37 m_sourceParams.transform = qNormalizedSurfaceTransformation(format);
38 m_sourceParams.frameRate = m_fixedSourceFrameRate.value_or(0.f);
39 m_sourceParams.colorTransfer = QFFmpeg::toAvColorTransfer(format.colorTransfer());
40 m_sourceParams.colorSpace = QFFmpeg::toAvColorSpace(format.colorSpace());
41 m_sourceParams.colorRange = QFFmpeg::toAvColorRange(format.colorRange());
42
43 if (!m_settings.videoResolution().isValid())
44 m_settings.setVideoResolution(m_sourceParams.size);
45
46 // Use a provisional target before negotiation with codec. This is for first
47 // frame, before init() is called
48 const qreal provisionalTargetRate = m_settings.videoFrameRate() > 0.
49 ? m_settings.videoFrameRate()
50 : m_sourceParams.frameRate;
51 m_frameRateAdapter.setRates(m_fixedSourceFrameRate, provisionalTargetRate);
52}
53
54VideoEncoder::~VideoEncoder() = default;
55
56void VideoEncoder::addFrame(const QVideoFrame &frame)
57{
58 if (!frame.isValid()) {
59 setEndOfSourceStream();
60 dataReady();
61 return;
62 }
63
64 {
65 auto guard = lockLoopData();
66 resetEndOfSourceStream();
67
68 if (m_paused)
69 return; // Frames are dropped while paused
70
71 if (!tryPushToInputQueue(frame)) {
72 if (!isInitialized())
73 qCWarning(qLcFFmpegVideoEncoder) << "Too many frames received before encoder "
74 "initialization. Buffer full, dropping "
75 "frame.";
76 else
77 qCDebug(qLcFFmpegVideoEncoder) << "RecordingEngine frame queue full. Frame lost. "
78 "This might mean we're trying "
79 "to encode at too high of a frame rate.";
80 return;
81 }
82 }
83
84 dataReady();
85}
86
87FrameInfo VideoEncoder::dequeueAdaptedFrame()
88{
89 auto guard = lockLoopData();
90 saturateEncoderQueue();
91 return dequeueIfPossible(m_encoderQueue);
92}
93
94void VideoEncoder::retrievePackets()
95{
96 Q_ASSERT(m_frameEncoder);
97 while (auto packet = m_frameEncoder->retrievePacket())
98 m_recordingEngine.getMuxer()->addPacket(std::move(packet));
99}
100
101bool VideoEncoder::tryPushToInputQueue(const QVideoFrame &frame)
102{
103 if (m_inputQueue.size() >= m_maxQueueSize)
104 return false;
105
106 m_inputQueue.push({ frame, std::exchange(m_shouldAdjustTimeBaseForNextFrame, false) });
107 return true;
108}
109
110void VideoEncoder::saturateEncoderQueue()
111{
112 // Caller must hold lockLoopData() guard
113 while (m_encoderQueue.empty() && !m_inputQueue.empty()) {
114 const InputFrame input = std::move(m_inputQueue.front());
115 m_inputQueue.pop();
116
117 if (input.restartTimeBase) {
118 flushAdapter();
119 m_frameRateAdapter.reset();
120 }
121
122 std::vector<FrameInfo> adapted =
123 m_frameRateAdapter.adapt(input.frame, input.restartTimeBase);
124 for (FrameInfo &adaptedFrame : adapted)
125 m_encoderQueue.push(std::move(adaptedFrame));
126 }
127
128 if (m_inputQueue.empty() && isEndOfSourceStream() && m_frameRateAdapter.hasPendingFlush())
129 flushAdapter();
130}
131
132void VideoEncoder::flushAdapter()
133{
134 std::optional<FrameInfo> flushed = m_frameRateAdapter.flush();
135 if (flushed)
136 m_encoderQueue.push(std::move(*flushed));
137}
138
140{
141 if (paused)
142 m_shouldAdjustTimeBaseForNextFrame = true;
143}
144
146{
147 m_frameEncoder = VideoFrameEncoder::create(m_settings, m_sourceParams,
148 m_recordingEngine.avFormatContext());
149
150 qCDebug(qLcFFmpegVideoEncoder) << "VideoEncoder::init started video device thread.";
151 if (!m_frameEncoder) {
152 markResolved(false);
153 emit m_recordingEngine.sessionError(QMediaRecorder::ResourceError,
154 u"Could not initialize encoder"_s);
155 return false;
156 }
157
158 {
159 auto guard = lockLoopData();
160 m_frameRateAdapter.setRates(m_fixedSourceFrameRate, m_frameEncoder->codecFrameRate());
161 saturateEncoderQueue();
162 }
163
164 return EncoderThread::init();
165}
166
168{
169 Q_ASSERT(m_frameEncoder);
170
171 while (hasData())
173
174 while (m_frameEncoder->sendFrame(nullptr) == AVERROR(EAGAIN))
175 retrievePackets();
176 retrievePackets();
177}
178
180{
181 return !m_encoderQueue.empty() || !m_inputQueue.empty()
182 || (isEndOfSourceStream() && m_frameRateAdapter.hasPendingFlush());
183}
184
186{
187 QVideoFrame f;
189};
190
191static void freeQVideoFrame(void *opaque, uint8_t *)
192{
193 delete reinterpret_cast<QVideoFrameHolder *>(opaque);
194}
195
197{
198 Q_ASSERT(m_frameEncoder);
199
200 retrievePackets();
201
202 FrameInfo frameInfo = dequeueAdaptedFrame();
203 QVideoFrame &frame = frameInfo.frame;
204 if (!frame.isValid())
205 return; // Nothing to encode
206
207 // qCDebug(qLcFFmpegEncoder) << "new video buffer" << frame.startTime();
208
209 AVFrameUPtr avFrame;
210
211 auto *videoBuffer = dynamic_cast<QFFmpegVideoBuffer *>(QVideoFramePrivate::hwBuffer(frame));
212 if (videoBuffer) {
213 // ffmpeg video buffer, let's use the native AVFrame stored in there
214 auto *hwFrame = videoBuffer->getHWFrame();
215 if (hwFrame && hwFrame->format == m_frameEncoder->sourceFormat())
216 avFrame.reset(av_frame_clone(hwFrame));
217 }
218
219 if (!avFrame) {
220 frame.map(QVideoFrame::ReadOnly);
221 auto size = frame.size();
222 avFrame = makeAVFrame();
223 avFrame->format = m_frameEncoder->sourceFormat();
224 avFrame->width = size.width();
225 avFrame->height = size.height();
226
227 for (int i = 0; i < 4; ++i) {
228 avFrame->data[i] = const_cast<uint8_t *>(frame.bits(i));
229 avFrame->linesize[i] = frame.bytesPerLine(i);
230 }
231
232 // TODO: investigate if we need to set color params to AVFrame.
233 // Setting only codec carameters might be sufficient.
234 // What happens if frame color params are set and not equal codec prms?
235 //
236 // QVideoFrameFormat format = frame.surfaceFormat();
237 // avFrame->color_trc = QFFmpeg::toAvColorTransfer(format.colorTransfer());
238 // avFrame->colorspace = QFFmpeg::toAvColorSpace(format.colorSpace());
239 // avFrame->color_range = QFFmpeg::toAvColorRange(format.colorRange());
240
241 QImage img;
242 if (frame.pixelFormat() == QVideoFrameFormat::Format_Jpeg) {
243 // the QImage is cached inside the video frame, so we can take the pointer to the image
244 // data here
245 img = frame.toImage();
246 avFrame->data[0] = (uint8_t *)img.bits();
247 avFrame->linesize[0] = img.bytesPerLine();
248 }
249
250 Q_ASSERT(avFrame->data[0]);
251 // ensure the video frame and it's data is alive as long as it's being used in the encoder
252 avFrame->opaque_ref = av_buffer_create(nullptr, 0, freeQVideoFrame,
253 new QVideoFrameHolder{ frame, img }, 0);
254 }
255
256 auto [startTime, endTime] = frameTimeStamps(frame);
257
258 if (frameInfo.overriddenPts) {
259 startTime = *frameInfo.overriddenPts;
260 endTime = startTime + m_frameRateAdapter.frameDuration();
261 }
262
263 if (frameInfo.adjustTimeBase) {
264 m_baseTime += startTime - m_lastFrameTime;
265 qCDebug(qLcFFmpegVideoEncoder)
266 << ">>>> adjusting base time to" << m_baseTime << startTime << m_lastFrameTime;
267 }
268
269 const qint64 time = startTime - m_baseTime;
270 m_lastFrameTime = endTime;
271
272 setAVFrameTime(*avFrame, m_frameEncoder->getPts(time), m_frameEncoder->getTimeBase());
273
274 m_recordingEngine.newTimeStamp(time / 1000);
275
276 qCDebug(qLcFFmpegVideoEncoder)
277 << ">>> sending frame" << avFrame->pts << time << m_lastFrameTime;
278 int ret = m_frameEncoder->sendFrame(std::move(avFrame));
279 if (ret < 0) {
280 qCDebug(qLcFFmpegVideoEncoder) << "error sending frame" << ret << AVError(ret);
281 emit m_recordingEngine.sessionError(QMediaRecorder::ResourceError, err2str(ret));
282 }
283}
284
286{
287 return isFinished() ? false : m_inputQueue.size() < m_maxQueueSize;
288}
289
290std::pair<qint64, qint64> VideoEncoder::frameTimeStamps(const QVideoFrame &frame) const
291{
292 qint64 startTime = frame.startTime();
293 qint64 endTime = frame.endTime();
294
295 if (startTime == -1) {
296 startTime = m_lastFrameTime;
297 endTime = -1;
298 }
299
300 if (endTime == -1) {
301 // No endTime: advance by an assumed duration. If the frame rate
302 // adapter is enabled, these get overridden later
303 qreal frameRate = frame.streamFrameRate();
304 if (frameRate <= 0.)
305 frameRate = DefaultVideoFrameRate;
306
307 Q_ASSERT(frameRate > 0.f);
308 endTime = startTime + static_cast<qint64>(std::round(VideoFrameTimeBase / frameRate));
309 }
310
311 return { startTime, endTime };
312}
313
314} // namespace QFFmpeg
315
316QT_END_NAMESPACE
bool init() override
Called on this thread when thread starts.
bool checkIfCanPushFrame() const override
~VideoEncoder() override
void processOne() override
Process one work item.
void cleanup() override
Called on this thread before thread exits.
void handlePausedChanged(bool paused) override
bool hasData() const override
Must return true when data is available for processing.
Q_STATIC_LOGGING_CATEGORY(qLCAndroidCaCertificates, "qt.multimedia.ffmpeg.android.caCertificates")
static void freeQVideoFrame(void *opaque, uint8_t *)
constexpr int DefaultVideoFrameRate
Combined button and popup list for selecting options.