Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
4#include "qffmpegmuxer_p.h"
8#include <QtCore/qloggingcategory.h>
9
11
12namespace QFFmpeg {
13
14static Q_LOGGING_CATEGORY(qLcFFmpegVideoEncoder, "qt.multimedia.ffmpeg.videoencoder");
15
17 const QVideoFrameFormat &format, std::optional<AVPixelFormat> hwFormat)
18 : EncoderThread(recordingEngine)
19{
20 setObjectName(QLatin1String("VideoEncoder"));
21
22 AVPixelFormat swFormat = QFFmpegVideoBuffer::toAVPixelFormat(format.pixelFormat());
23 AVPixelFormat ffmpegPixelFormat =
24 hwFormat && *hwFormat != AV_PIX_FMT_NONE ? *hwFormat : swFormat;
25 auto frameRate = format.streamFrameRate();
26 if (frameRate <= 0.) {
27 qWarning() << "Invalid frameRate" << frameRate << "; Using the default instead";
28
29 // set some default frame rate since ffmpeg has UB if it's 0.
30 frameRate = 30.;
31 }
32
33 m_frameEncoder = VideoFrameEncoder::create(settings,
34 format.frameSize(),
35 format.rotation(),
37 ffmpegPixelFormat,
38 swFormat,
39 recordingEngine.avFormatContext());
40}
41
43
45{
46 return m_frameEncoder != nullptr;
47}
48
50{
51 {
52 auto guard = lockLoopData();
53
54 if (m_paused)
55 return;
56
57 // Drop frames if encoder can not keep up with the video source data rate;
58 // canPushFrame might be used instead
59 const bool queueFull = m_videoFrameQueue.size() >= m_maxQueueSize;
60
61 if (queueFull) {
62 qCDebug(qLcFFmpegVideoEncoder) << "RecordingEngine frame queue full. Frame lost.";
63 return;
64 }
65
66 m_videoFrameQueue.push(frame);
67 }
68
69 dataReady();
70}
71
72QVideoFrame VideoEncoder::takeFrame()
73{
74 auto guard = lockLoopData();
75 return dequeueIfPossible(m_videoFrameQueue);
76}
77
78void VideoEncoder::retrievePackets()
79{
80 if (!m_frameEncoder)
81 return;
82 while (auto packet = m_frameEncoder->retrievePacket())
83 m_recordingEngine.getMuxer()->addPacket(std::move(packet));
84}
85
87{
89
90 qCDebug(qLcFFmpegVideoEncoder) << "VideoEncoder::init started video device thread.";
91 bool ok = m_frameEncoder->open();
92 if (!ok)
94 "Could not initialize encoder");
95}
96
98{
99 while (!m_videoFrameQueue.empty())
100 processOne();
101 if (m_frameEncoder) {
102 while (m_frameEncoder->sendFrame(nullptr) == AVERROR(EAGAIN))
103 retrievePackets();
104 retrievePackets();
105 }
106}
107
109{
110 return !m_videoFrameQueue.empty();
111}
112
118
119static void freeQVideoFrame(void *opaque, uint8_t *)
120{
121 delete reinterpret_cast<QVideoFrameHolder *>(opaque);
122}
123
125{
126 retrievePackets();
127
128 auto frame = takeFrame();
129 if (!frame.isValid())
130 return;
131
132 if (!isValid())
133 return;
134
135 // qCDebug(qLcFFmpegEncoder) << "new video buffer" << frame.startTime();
136
137 AVFrameUPtr avFrame;
138
139 auto *videoBuffer = dynamic_cast<QFFmpegVideoBuffer *>(frame.videoBuffer());
140 if (videoBuffer) {
141 // ffmpeg video buffer, let's use the native AVFrame stored in there
142 auto *hwFrame = videoBuffer->getHWFrame();
143 if (hwFrame && hwFrame->format == m_frameEncoder->sourceFormat())
144 avFrame.reset(av_frame_clone(hwFrame));
145 }
146
147 if (!avFrame) {
149 auto size = frame.size();
150 avFrame = makeAVFrame();
151 avFrame->format = m_frameEncoder->sourceFormat();
152 avFrame->width = size.width();
153 avFrame->height = size.height();
154
155 for (int i = 0; i < 4; ++i) {
156 avFrame->data[i] = const_cast<uint8_t *>(frame.bits(i));
157 avFrame->linesize[i] = frame.bytesPerLine(i);
158 }
159
160 QImage img;
161 if (frame.pixelFormat() == QVideoFrameFormat::Format_Jpeg) {
162 // the QImage is cached inside the video frame, so we can take the pointer to the image
163 // data here
164 img = frame.toImage();
165 avFrame->data[0] = (uint8_t *)img.bits();
166 avFrame->linesize[0] = img.bytesPerLine();
167 }
168
169 Q_ASSERT(avFrame->data[0]);
170 // ensure the video frame and it's data is alive as long as it's being used in the encoder
171 avFrame->opaque_ref = av_buffer_create(nullptr, 0, freeQVideoFrame,
172 new QVideoFrameHolder{ frame, img }, 0);
173 }
174
175 if (m_baseTime.loadAcquire() == std::numeric_limits<qint64>::min()) {
176 m_baseTime.storeRelease(frame.startTime() - m_lastFrameTime);
177 qCDebug(qLcFFmpegVideoEncoder) << ">>>> adjusting base time to" << m_baseTime.loadAcquire()
178 << frame.startTime() << m_lastFrameTime;
179 }
180
181 qint64 time = frame.startTime() - m_baseTime.loadAcquire();
182 m_lastFrameTime = frame.endTime() - m_baseTime.loadAcquire();
183
184 setAVFrameTime(*avFrame, m_frameEncoder->getPts(time), m_frameEncoder->getTimeBase());
185
187
188 qCDebug(qLcFFmpegVideoEncoder)
189 << ">>> sending frame" << avFrame->pts << time << m_lastFrameTime;
190 int ret = m_frameEncoder->sendFrame(std::move(avFrame));
191 if (ret < 0) {
192 qCDebug(qLcFFmpegVideoEncoder) << "error sending frame" << ret << err2str(ret);
194 }
195}
196
198{
199 if (isRunning())
200 return m_videoFrameQueue.size() < m_maxQueueSize;
201 if (!isFinished())
202 return m_videoFrameQueue.empty();
203
204 return false;
205}
206
207} // namespace QFFmpeg
208
T loadAcquire() const noexcept
void storeRelease(T newValue) noexcept
static AVPixelFormat toAVPixelFormat(QVideoFrameFormat::PixelFormat pixelFormat)
AVFrame * getHWFrame() const
void dataReady()
Wake thread from sleep and process data until hasData() returns false.
RecordingEngine & m_recordingEngine
void addPacket(AVPacketUPtr packet)
void sessionError(QMediaRecorder::Error code, const QString &description)
bool checkIfCanPushFrame() const override
~VideoEncoder() override
void init() override
Called on this thread when thread starts.
void processOne() override
Process one work item.
void cleanup() override
Called on this thread before thread exits.
void addFrame(const QVideoFrame &frame)
bool hasData() const override
Must return true when data is available for processing.
VideoEncoder(RecordingEngine &recordingEngine, const QMediaEncoderSettings &settings, const QVideoFrameFormat &format, std::optional< AVPixelFormat > hwFormat)
static std::unique_ptr< VideoFrameEncoder > create(const QMediaEncoderSettings &encoderSettings, const QSize &sourceSize, QtVideo::Rotation sourceRotation, qreal sourceFrameRate, AVPixelFormat sourceFormat, AVPixelFormat sourceSWFormat, AVFormatContext *formatContext)
\inmodule QtGui
Definition qimage.h:37
Q_WEAK_OVERLOAD void setObjectName(const QString &name)
Sets the object's name to name.
Definition qobject.h:127
bool isRunning() const
Definition qthread.cpp:1064
bool isFinished() const
Definition qthread.cpp:1059
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:27
QSize size
the size of the widget excluding any window frame
Definition qwidget.h:113
AVFrameUPtr makeAVFrame()
Definition qffmpeg_p.h:136
QString err2str(int errnum)
Definition qffmpeg_p.h:64
void setAVFrameTime(AVFrame &frame, int64_t pts, const AVRational &timeBase)
Definition qffmpeg_p.h:71
static void freeQVideoFrame(void *opaque, uint8_t *)
std::unique_ptr< AVFrame, AVDeleter< decltype(&av_frame_free), &av_frame_free > > AVFrameUPtr
Definition qffmpeg_p.h:134
T dequeueIfPossible(std::queue< T > &queue)
Combined button and popup list for selecting options.
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
return ret
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLsizei GLsizei GLenum format
GLint void * img
Definition qopenglext.h:233
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define emit
long long qint64
Definition qtypes.h:60
QSettings settings("MySoft", "Star Runner")
[0]
QFrame frame
[0]