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
qffmpegvideoframeencoder.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 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
8#include <qloggingcategory.h>
9
10extern "C" {
11#include "libavutil/display.h"
12}
13
15
16static Q_LOGGING_CATEGORY(qLcVideoFrameEncoder, "qt.multimedia.ffmpeg.videoencoder");
17
18namespace QFFmpeg {
19
20std::unique_ptr<VideoFrameEncoder>
22 const QSize &sourceSize,
23 QtVideo::Rotation sourceRotation,
24 qreal sourceFrameRate,
25 AVPixelFormat sourceFormat,
26 AVPixelFormat sourceSWFormat,
27 AVFormatContext *formatContext)
28{
29 Q_ASSERT(isSwPixelFormat(sourceSWFormat));
31
32 std::unique_ptr<VideoFrameEncoder> result(new VideoFrameEncoder);
33
34 result->m_settings = encoderSettings;
35 result->m_sourceSize = sourceSize;
36 result->m_sourceFormat = sourceFormat;
37 result->m_sourceRotation = sourceRotation;
38
39 // Temporary: check isSwPixelFormat because of android issue (QTBUG-116836)
40 result->m_sourceSWFormat = isSwPixelFormat(sourceFormat) ? sourceFormat : sourceSWFormat;
41
42 if (!result->m_settings.videoResolution().isValid())
43 result->m_settings.setVideoResolution(sourceSize);
44
45 if (result->m_settings.videoFrameRate() <= 0.)
46 result->m_settings.setVideoFrameRate(sourceFrameRate);
47
48 if (!result->initCodec() || !result->initTargetFormats()
49 || !result->initCodecContext(formatContext)) {
50 return nullptr;
51 }
52
53 // TODO: make VideoFrameEncoder::private and do openning here
54 // if (!open()) {
55 // m_error = QMediaRecorder::FormatError;
56 // m_errorStr = QLatin1StringView("Cannot open codec");
57 // return;
58 // }
59
60 result->updateConversions();
61
62 return result;
63}
64
65bool VideoFrameEncoder::initCodec()
66{
67 const auto qVideoCodec = m_settings.videoCodec();
68 const auto codecID = QFFmpegMediaFormatInfo::codecIdForVideoCodec(qVideoCodec);
69 const auto resolution = m_settings.videoResolution();
70
71 std::tie(m_codec, m_accel) = findHwEncoder(codecID, resolution);
72
73 if (!m_codec)
74 m_codec = findSwEncoder(codecID, m_sourceSWFormat);
75
76 if (!m_codec) {
77 qWarning() << "Could not find encoder for codecId" << codecID;
78 return false;
79 }
80
81 qCDebug(qLcVideoFrameEncoder) << "found encoder" << m_codec->name << "for id" << m_codec->id;
82
83#ifdef Q_OS_WINDOWS
84 // TODO: investigate, there might be more encoders not supporting odd resolution
85 if (strcmp(m_codec->name, "h264_mf") == 0) {
86 auto makeEven = [](int size) { return size & ~1; };
87 const QSize fixedResolution(makeEven(resolution.width()), makeEven(resolution.height()));
88 if (fixedResolution != resolution) {
89 qCDebug(qLcVideoFrameEncoder) << "Fix odd video resolution for codec" << m_codec->name
90 << ":" << resolution << "->" << fixedResolution;
91 m_settings.setVideoResolution(fixedResolution);
92 }
93 }
94#endif
95
96 auto fixedResolution = adjustVideoResolution(m_codec, m_settings.videoResolution());
97 if (resolution != fixedResolution) {
98 qCDebug(qLcVideoFrameEncoder) << "Fix odd video resolution for codec" << m_codec->name
99 << ":" << resolution << "->" << fixedResolution;
100
101 m_settings.setVideoResolution(fixedResolution);
102 }
103
104 if (m_codec->supported_framerates && qLcVideoFrameEncoder().isEnabled(QtDebugMsg))
105 for (auto rate = m_codec->supported_framerates; rate->num && rate->den; ++rate)
106 qCDebug(qLcVideoFrameEncoder) << "supported frame rate:" << *rate;
107
108 m_codecFrameRate = adjustFrameRate(m_codec->supported_framerates, m_settings.videoFrameRate());
109 qCDebug(qLcVideoFrameEncoder) << "Adjusted frame rate:" << m_codecFrameRate;
110
111 return true;
112}
113
114bool VideoFrameEncoder::initTargetFormats()
115{
116 m_targetFormat = findTargetFormat(m_sourceFormat, m_sourceSWFormat, m_codec, m_accel.get());
117
118 if (m_targetFormat == AV_PIX_FMT_NONE) {
119 qWarning() << "Could not find target format for codecId" << m_codec->id;
120 return false;
121 }
122
123 if (isHwPixelFormat(m_targetFormat)) {
124 Q_ASSERT(m_accel);
125
126 m_targetSWFormat = findTargetSWFormat(m_sourceSWFormat, m_codec, *m_accel);
127
128 if (m_targetSWFormat == AV_PIX_FMT_NONE) {
129 qWarning() << "Cannot find software target format. sourceSWFormat:" << m_sourceSWFormat
130 << "targetFormat:" << m_targetFormat;
131 return false;
132 }
133
134 m_accel->createFramesContext(m_targetSWFormat, m_settings.videoResolution());
135 if (!m_accel->hwFramesContextAsBuffer())
136 return false;
137 } else {
138 m_targetSWFormat = m_targetFormat;
139 }
140
141 return true;
142}
143
145
146bool QFFmpeg::VideoFrameEncoder::initCodecContext(AVFormatContext *formatContext)
147{
148 m_stream = avformat_new_stream(formatContext, nullptr);
149 m_stream->id = formatContext->nb_streams - 1;
150 //qCDebug(qLcVideoFrameEncoder) << "Video stream: index" << d->stream->id;
151 m_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
152 m_stream->codecpar->codec_id = m_codec->id;
153
154 // Apples HEVC decoders don't like the hev1 tag ffmpeg uses by default, use hvc1 as the more commonly accepted tag
155 if (m_codec->id == AV_CODEC_ID_HEVC)
156 m_stream->codecpar->codec_tag = MKTAG('h', 'v', 'c', '1');
157
158 const auto resolution = m_settings.videoResolution();
159
160 // ### Fix hardcoded values
161 m_stream->codecpar->format = m_targetFormat;
162 m_stream->codecpar->width = resolution.width();
163 m_stream->codecpar->height = resolution.height();
164 m_stream->codecpar->sample_aspect_ratio = AVRational{ 1, 1 };
165
166 if (m_sourceRotation != QtVideo::Rotation::None) {
167 constexpr auto displayMatrixSize = sizeof(int32_t) * 9;
168 AVPacketSideData sideData = { reinterpret_cast<uint8_t *>(av_malloc(displayMatrixSize)),
169 displayMatrixSize, AV_PKT_DATA_DISPLAYMATRIX };
170 av_display_rotation_set(reinterpret_cast<int32_t *>(sideData.data),
171 static_cast<double>(m_sourceRotation));
172 addStreamSideData(m_stream, sideData);
173 }
174
175 Q_ASSERT(m_codec);
176
177 m_stream->time_base = adjustFrameTimeBase(m_codec->supported_framerates, m_codecFrameRate);
178 m_codecContext.reset(avcodec_alloc_context3(m_codec));
179 if (!m_codecContext) {
180 qWarning() << "Could not allocate codec context";
181 return false;
182 }
183
184 avcodec_parameters_to_context(m_codecContext.get(), m_stream->codecpar);
185 m_codecContext->time_base = m_stream->time_base;
186 qCDebug(qLcVideoFrameEncoder) << "codecContext time base" << m_codecContext->time_base.num
187 << m_codecContext->time_base.den;
188
189 m_codecContext->framerate = m_codecFrameRate;
190 m_codecContext->pix_fmt = m_targetFormat;
191 m_codecContext->width = resolution.width();
192 m_codecContext->height = resolution.height();
193
194 if (m_accel) {
195 auto deviceContext = m_accel->hwDeviceContextAsBuffer();
196 Q_ASSERT(deviceContext);
197 m_codecContext->hw_device_ctx = av_buffer_ref(deviceContext);
198
199 if (auto framesContext = m_accel->hwFramesContextAsBuffer())
200 m_codecContext->hw_frames_ctx = av_buffer_ref(framesContext);
201 }
202
203 return true;
204}
205
207{
208 if (!m_codecContext)
209 return false;
210
212 applyVideoEncoderOptions(m_settings, m_codec->name, m_codecContext.get(), opts);
213 applyExperimentalCodecOptions(m_codec, opts);
214
215 int res = avcodec_open2(m_codecContext.get(), m_codec, opts);
216 if (res < 0) {
217 m_codecContext.reset();
218 qWarning() << "Couldn't open codec for writing" << err2str(res);
219 return false;
220 }
221 qCDebug(qLcVideoFrameEncoder) << "video codec opened" << res << "time base"
222 << m_codecContext->time_base;
223 return true;
224}
225
227{
228 qint64 div = 1'000'000 * m_stream->time_base.num;
229 return div != 0 ? (us * m_stream->time_base.den + div / 2) / div : 0;
230}
231
232const AVRational &VideoFrameEncoder::getTimeBase() const
233{
234 return m_stream->time_base;
235}
236
238{
239 if (!m_codecContext) {
240 qWarning() << "codec context is not initialized!";
241 return AVERROR(EINVAL);
242 }
243
244 if (!frame)
245 return avcodec_send_frame(m_codecContext.get(), frame.get());
246
247 if (frame->format != m_sourceFormat) {
248 qWarning() << "Frame format has changed:" << m_sourceFormat << "->" << frame->format;
249 return AVERROR(EINVAL);
250 }
251
253 if (frameSize != m_sourceSize) {
254 qCDebug(qLcVideoFrameEncoder) << "Update conversions on the fly. Source size"
255 << m_sourceSize << "->" << frameSize;
256 m_sourceSize = frameSize;
257 updateConversions();
258 }
259
260 int64_t pts = 0;
261 AVRational timeBase = {};
262 getAVFrameTime(*frame, pts, timeBase);
263
264 if (m_downloadFromHW) {
265 auto f = makeAVFrame();
266
267 int err = av_hwframe_transfer_data(f.get(), frame.get(), 0);
268 if (err < 0) {
269 qCDebug(qLcVideoFrameEncoder) << "Error transferring frame data to surface." << err2str(err);
270 return err;
271 }
272
273 frame = std::move(f);
274 }
275
276 if (m_converter) {
277 auto f = makeAVFrame();
278
279 f->format = m_targetSWFormat;
280 f->width = m_settings.videoResolution().width();
281 f->height = m_settings.videoResolution().height();
282
283 av_frame_get_buffer(f.get(), 0);
284 const auto scaledHeight = sws_scale(m_converter.get(), frame->data, frame->linesize, 0,
285 frame->height, f->data, f->linesize);
286
287 if (scaledHeight != f->height)
288 qCWarning(qLcVideoFrameEncoder) << "Scaled height" << scaledHeight << "!=" << f->height;
289
290 frame = std::move(f);
291 }
292
293 if (m_uploadToHW) {
294 auto *hwFramesContext = m_accel->hwFramesContextAsBuffer();
296 auto f = makeAVFrame();
297
298 if (!f)
299 return AVERROR(ENOMEM);
300 int err = av_hwframe_get_buffer(hwFramesContext, f.get(), 0);
301 if (err < 0) {
302 qCDebug(qLcVideoFrameEncoder) << "Error getting HW buffer" << err2str(err);
303 return err;
304 } else {
305 qCDebug(qLcVideoFrameEncoder) << "got HW buffer";
306 }
307 if (!f->hw_frames_ctx) {
308 qCDebug(qLcVideoFrameEncoder) << "no hw frames context";
309 return AVERROR(ENOMEM);
310 }
311 err = av_hwframe_transfer_data(f.get(), frame.get(), 0);
312 if (err < 0) {
313 qCDebug(qLcVideoFrameEncoder) << "Error transferring frame data to surface." << err2str(err);
314 return err;
315 }
316 frame = std::move(f);
317 }
318
319 qCDebug(qLcVideoFrameEncoder) << "sending frame" << pts << "*" << timeBase;
320
321 setAVFrameTime(*frame, pts, timeBase);
322 return avcodec_send_frame(m_codecContext.get(), frame.get());
323}
324
326{
327 if (!m_codecContext)
328 return nullptr;
329
330 auto getPacket = [&]() {
331 AVPacketUPtr packet(av_packet_alloc());
332 const int ret = avcodec_receive_packet(m_codecContext.get(), packet.get());
333 if (ret < 0) {
334 if (ret != AVERROR(EOF) && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
335 qCDebug(qLcVideoFrameEncoder) << "Error receiving packet" << ret << err2str(ret);
336 return AVPacketUPtr{};
337 }
338 auto ts = timeStampMs(packet->pts, m_stream->time_base);
339
340 qCDebug(qLcVideoFrameEncoder)
341 << "got a packet" << packet->pts << packet->dts << (ts ? *ts : 0);
342
343 packet->stream_index = m_stream->id;
344 return packet;
345 };
346
347 auto fixPacketDts = [&](AVPacket &packet) {
348 // Workaround for some ffmpeg codecs bugs (e.g. nvenc)
349 // Ideally, packet->pts < packet->dts is not expected
350
351 if (packet.dts == AV_NOPTS_VALUE)
352 return true;
353
354 packet.dts -= m_packetDtsOffset;
355
356 if (packet.pts != AV_NOPTS_VALUE && packet.pts < packet.dts) {
357 m_packetDtsOffset += packet.dts - packet.pts;
358 packet.dts = packet.pts;
359
360 if (m_prevPacketDts != AV_NOPTS_VALUE && packet.dts < m_prevPacketDts) {
361 qCWarning(qLcVideoFrameEncoder)
362 << "Skip packet; failed to fix dts:" << packet.dts << m_prevPacketDts;
363 return false;
364 }
365 }
366
367 m_prevPacketDts = packet.dts;
368
369 return true;
370 };
371
372 while (auto packet = getPacket()) {
373 if (fixPacketDts(*packet))
374 return packet;
375 }
376
377 return nullptr;
378}
379
380void VideoFrameEncoder::updateConversions()
381{
382 const bool needToScale = m_sourceSize != m_settings.videoResolution();
383 const bool zeroCopy = m_sourceFormat == m_targetFormat && !needToScale;
384
385 m_converter.reset();
386
387 if (zeroCopy) {
388 m_downloadFromHW = false;
389 m_uploadToHW = false;
390
391 qCDebug(qLcVideoFrameEncoder) << "zero copy encoding, format" << m_targetFormat;
392 // no need to initialize any converters
393 return;
394 }
395
396 m_downloadFromHW = m_sourceFormat != m_sourceSWFormat;
397 m_uploadToHW = m_targetFormat != m_targetSWFormat;
398
399 if (m_sourceSWFormat != m_targetSWFormat || needToScale) {
400 const auto targetSize = m_settings.videoResolution();
401 qCDebug(qLcVideoFrameEncoder)
402 << "video source and encoder use different formats:" << m_sourceSWFormat
403 << m_targetSWFormat << "or sizes:" << m_sourceSize << targetSize;
404
405 m_converter.reset(sws_getContext(m_sourceSize.width(), m_sourceSize.height(),
406 m_sourceSWFormat, targetSize.width(), targetSize.height(),
407 m_targetSWFormat, SWS_FAST_BILINEAR, nullptr, nullptr,
408 nullptr));
409 }
410
411 qCDebug(qLcVideoFrameEncoder) << "VideoFrameEncoder conversions initialized:"
412 << "sourceFormat:" << m_sourceFormat
413 << (isHwPixelFormat(m_sourceFormat) ? "(hw)" : "(sw)")
414 << "targetFormat:" << m_targetFormat
415 << (isHwPixelFormat(m_targetFormat) ? "(hw)" : "(sw)")
416 << "sourceSWFormat:" << m_sourceSWFormat
417 << "targetSWFormat:" << m_targetSWFormat
418 << "converter:" << m_converter.get();
419}
420
421} // namespace QFFmpeg
422
static AVCodecID codecIdForVideoCodec(QMediaFormat::VideoCodec codec)
const AVRational & getTimeBase() const
static std::unique_ptr< VideoFrameEncoder > create(const QMediaEncoderSettings &encoderSettings, const QSize &sourceSize, QtVideo::Rotation sourceRotation, qreal sourceFrameRate, AVPixelFormat sourceFormat, AVPixelFormat sourceSWFormat, AVFormatContext *formatContext)
QMediaFormat::VideoCodec videoCodec() const
void setVideoResolution(const QSize &size)
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:133
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:130
int width
the width of the widget excluding any window frame
Definition qwidget.h:114
int height
the height of the widget excluding any window frame
Definition qwidget.h:115
AVFrameUPtr makeAVFrame()
Definition qffmpeg_p.h:136
void getAVFrameTime(const AVFrame &frame, int64_t &pts, AVRational &timeBase)
Definition qffmpeg_p.h:81
AVPixelFormat findTargetFormat(AVPixelFormat sourceFormat, AVPixelFormat sourceSWFormat, const AVCodec *codec, const HWAccel *accel)
void applyVideoEncoderOptions(const QMediaEncoderSettings &settings, const QByteArray &codecName, AVCodecContext *codec, AVDictionary **opts)
bool isHwPixelFormat(AVPixelFormat format)
Definition qffmpeg.cpp:456
QSize adjustVideoResolution(const AVCodec *codec, QSize requestedResolution)
std::pair< const AVCodec *, std::unique_ptr< HWAccel > > findHwEncoder(AVCodecID codecID, const QSize &resolution)
AVPacketSideData * addStreamSideData(AVStream *stream, AVPacketSideData sideData)
Definition qffmpeg.cpp:512
QString err2str(int errnum)
Definition qffmpeg_p.h:64
void setAVFrameTime(AVFrame &frame, int64_t pts, const AVRational &timeBase)
Definition qffmpeg_p.h:71
bool isSwPixelFormat(AVPixelFormat format)
Definition qffmpeg_p.h:220
std::unique_ptr< AVFrame, AVDeleter< decltype(&av_frame_free), &av_frame_free > > AVFrameUPtr
Definition qffmpeg_p.h:134
const AVCodec * findSwEncoder(AVCodecID codecID, AVPixelFormat sourceSWFormat)
void applyExperimentalCodecOptions(const AVCodec *codec, AVDictionary **opts)
Definition qffmpeg.cpp:467
std::optional< qint64 > timeStampMs(qint64 ts, AVRational base)
Definition qffmpeg_p.h:49
AVRational adjustFrameRate(const AVRational *supportedRates, qreal requestedRate)
adjustFrameRate get a rational frame rate be requested qreal rate. If the codec supports fixed frame ...
AVPixelFormat findTargetSWFormat(AVPixelFormat sourceSWFormat, const AVCodec *codec, const HWAccel &accel)
std::unique_ptr< AVPacket, AVDeleter< decltype(&av_packet_free), &av_packet_free > > AVPacketUPtr
Definition qffmpeg_p.h:141
AVRational adjustFrameTimeBase(const AVRational *supportedRates, AVRational frameRate)
adjustFrameTimeBase gets adjusted timebase by a list of supported frame rates and an already adjusted...
Combined button and popup list for selecting options.
bool isEnabled()
AVBufferRef * hwFramesContext
std::unique_ptr< QFFmpeg::HWAccel > m_accel
@ QtDebugMsg
Definition qlogging.h:30
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
return ret
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLfloat GLfloat f
GLuint res
GLuint GLenum * rate
GLuint64EXT * result
[6]
static constexpr QSize frameSize(const T &frame)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
QFrame frame
[0]