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
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
10
11#include <QtCore/qloggingcategory.h>
12#include <QtCore/qoperatingsystemversion.h>
13#include <QtCore/private/qexpected_p.h>
14
15extern "C" {
16#include "libavutil/display.h"
17#include "libavutil/pixdesc.h"
18}
19
20QT_BEGIN_NAMESPACE
21
22namespace ranges = QtMultimediaPrivate::ranges;
23
24Q_STATIC_LOGGING_CATEGORY(qLcVideoFrameEncoder, "qt.multimedia.ffmpeg.videoencoder");
25
26namespace QFFmpeg {
27
28namespace {
29
30AVCodecID avCodecID(const QMediaEncoderSettings &settings)
31{
32 const QMediaFormat::VideoCodec qVideoCodec = settings.videoCodec();
33 return QFFmpegMediaFormatInfo::codecIdForVideoCodec(qVideoCodec);
34}
35
36[[maybe_unused]] bool is420(AVPixelFormat fmt)
37{
38 const auto desc = av_pix_fmt_desc_get(fmt);
39 if (!desc)
40 return false;
41
42 return desc->log2_chroma_w == 1 && desc->log2_chroma_h == 1;
43}
44
45constexpr bool isAndroid =
46 QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Android;
47
48// LATER: move to HWAccel or Codec?
49std::optional<AVHWDeviceType> getHwDeviceType(const Codec &codec)
50{
51 std::optional<AVPixelFormat> pixelFormat = findAVPixelFormat(codec, isHwPixelFormat);
52 if (!pixelFormat)
53 return std::nullopt;
54
55 const AVCodecHWConfig *cfg = codec.hwConfigForPixelFormat(*pixelFormat);
56 if (!cfg)
57 return std::nullopt;
58
59 bool supportsHwDeviceContext = (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) != 0;
60 bool supportsHwFramesContext = (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) != 0;
61 if (!supportsHwDeviceContext && !supportsHwFramesContext)
62 return std::nullopt;
63
64 const QSpan deviceTypes = HWAccel::encodingDeviceTypes();
65 auto found = ranges::find_if(deviceTypes, [&](AVHWDeviceType deviceType) {
66 return pixelFormatForHwDevice(deviceType) == pixelFormat;
67 });
68 if (found != deviceTypes.end())
69 return *found;
70 else
71 return std::nullopt;
72}
73
74} // namespace
75
76VideoFrameEncoderUPtr VideoFrameEncoder::create(const QMediaEncoderSettings &encoderSettings,
77 const SourceParams &sourceParams,
78 AVFormatContext *formatContext)
79{
80 Q_ASSERT(isSwPixelFormat(sourceParams.swFormat));
81 Q_ASSERT(isHwPixelFormat(sourceParams.format) || sourceParams.swFormat == sourceParams.format);
82
83 AVStream *stream = createStream(sourceParams, formatContext);
84 if (!stream)
85 return nullptr;
86
87 const AVCodecID codecId = avCodecID(encoderSettings);
88
89 auto createWithFallback = [&](const Codec &codec, HWAccelUPtr hwAccel) {
90 const AVHWDeviceType deviceType = hwAccel ? hwAccel->deviceType() : AV_HWDEVICE_TYPE_NONE;
91 auto result = create(stream, codec, std::move(hwAccel), sourceParams, encoderSettings);
92
93 if constexpr (isAndroid) {
94 // On Android some encoders fail to open encoders with 4:2:0 formats unless it's NV12.
95 // Let's fallback to another format.
96 if (!result.encoder) {
97 if (is420(result.targetFormat) && result.targetFormat != AV_PIX_FMT_NV12) {
98 AVPixelFormatSet prohibitedTargetFormats;
99 prohibitedTargetFormats.insert(result.targetFormat);
100 hwAccel = HWAccel::create(deviceType);
101 result = create(stream, codec, std::move(hwAccel), sourceParams,
102 encoderSettings, prohibitedTargetFormats);
103 }
104 }
105 }
106 return result;
107 };
108
109 // first we try to open a hardware encoder
110 CreationResult creationResult = [&] {
111 const std::vector encoders = findAndScoreEncoders(codecId, [](const Codec &codec) {
112 std::optional<AVHWDeviceType> deviceType = getHwDeviceType(codec);
113 if (!deviceType)
114 return NotSuitableAVScore;
115
116 const QSpan deviceTypes = HWAccel::encodingDeviceTypes();
117 auto found = ranges::find(deviceTypes, *deviceType);
118 Q_ASSERT(found != deviceTypes.end());
119
120 return DefaultAVScore - static_cast<AVScore>(found - deviceTypes.begin());
121 });
122
123 for (const auto &[codec, score] : encoders) {
124 HWAccelUPtr hwAccel = HWAccel::create(*getHwDeviceType(codec));
125 if (!hwAccel)
126 continue;
127 if (!hwAccel->matchesSizeContraints(encoderSettings.videoResolution()))
128 continue;
129 CreationResult result = createWithFallback(codec, std::move(hwAccel));
130 if (result.encoder)
131 return result;
132 }
133 return CreationResult{};
134 }();
135
136 if (!creationResult.encoder) {
137 // otherwise fall back to software
138 creationResult = [&] {
139 const std::vector encoders = findAndScoreEncoders(codecId, [&](const Codec &codec) {
140 return findSWFormatScores(codec, sourceParams.swFormat);
141 });
142
143 for (const auto &[codec, score] : encoders) {
144 CreationResult result = createWithFallback(codec, nullptr);
145 if (result.encoder)
146 return result;
147 }
148 return CreationResult{};
149 }();
150 }
151
152 VideoFrameEncoderUPtr encoder = std::move(creationResult.encoder);
153
154 if (encoder)
155 qCDebug(qLcVideoFrameEncoder)
156 << "found" << (encoder->m_accel ? "hw" : "sw") << "encoder"
157 << encoder->m_codec.name() << "for id" << encoder->m_codec.id();
158 else
159 qCWarning(qLcVideoFrameEncoder) << "No valid video codecs found";
160
161 return encoder;
162}
163
164VideoFrameEncoder::VideoFrameEncoder(AVStream *stream, const Codec &codec, HWAccelUPtr hwAccel,
165 const SourceParams &sourceParams,
166 const QMediaEncoderSettings &encoderSettings)
167 : m_settings(encoderSettings),
168 m_stream(stream),
169 m_codec(codec),
170 m_accel(std::move(hwAccel)),
171 m_sourceSize(sourceParams.size),
172 m_sourceFormat(sourceParams.format),
173 m_sourceSWFormat(sourceParams.swFormat),
174 m_sourceFrameRate(sourceParams.frameRate)
175{
176}
177
178AVStream *VideoFrameEncoder::createStream(const SourceParams &sourceParams,
179 AVFormatContext *formatContext)
180{
181 AVStream *stream = avformat_new_stream(formatContext, nullptr);
182
183 if (!stream)
184 return stream;
185
186 stream->id = formatContext->nb_streams - 1;
187 stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
188
189 stream->codecpar->color_trc = sourceParams.colorTransfer;
190 stream->codecpar->color_space = sourceParams.colorSpace;
191 stream->codecpar->color_range = sourceParams.colorRange;
192
193 if (sourceParams.transform.rotation != QtVideo::Rotation::None || sourceParams.transform.mirroredHorizontallyAfterRotation) {
194 constexpr auto displayMatrixSize = sizeof(int32_t) * 9;
195 AVPacketSideData sideData = { reinterpret_cast<uint8_t *>(av_malloc(displayMatrixSize)),
196 displayMatrixSize, AV_PKT_DATA_DISPLAYMATRIX };
197 int32_t *matrix = reinterpret_cast<int32_t *>(sideData.data);
198 av_display_rotation_set(matrix, static_cast<double>(sourceParams.transform.rotation));
199 if (sourceParams.transform.mirroredHorizontallyAfterRotation)
200 av_display_matrix_flip(matrix, sourceParams.transform.mirroredHorizontallyAfterRotation, false);
201
202 addStreamSideData(stream, sideData);
203 }
204
205 return stream;
206}
207
208VideoFrameEncoder::CreationResult
209VideoFrameEncoder::create(AVStream *stream, const Codec &codec, HWAccelUPtr hwAccel,
210 const SourceParams &sourceParams,
211 const QMediaEncoderSettings &encoderSettings,
212 const AVPixelFormatSet &prohibitedTargetFormats)
213{
214 VideoFrameEncoderUPtr frameEncoder(new VideoFrameEncoder(stream, codec, std::move(hwAccel),
215 sourceParams, encoderSettings));
216 frameEncoder->initTargetSize();
217
218 frameEncoder->initCodecFrameRate();
219
220 if (!frameEncoder->initTargetFormats(prohibitedTargetFormats))
221 return {};
222
223 frameEncoder->initStream();
224
225 const AVPixelFormat targetFormat = frameEncoder->m_targetFormat;
226
227 if (!frameEncoder->initCodecContext())
228 return { nullptr, targetFormat };
229
230 if (!frameEncoder->open())
231 return { nullptr, targetFormat };
232
233 frameEncoder->updateConversions();
234
235 return { std::move(frameEncoder), targetFormat };
236}
237
238void VideoFrameEncoder::initTargetSize()
239{
240 m_targetSize = adjustVideoResolution(m_codec, m_settings.videoResolution());
241
242#ifdef Q_OS_WINDOWS
243 // TODO: investigate, there might be more encoders not supporting odd resolution
244 if (m_codec.name() == u"h264_mf") {
245 auto makeEven = [](int size) { return size & ~1; };
246 const QSize fixedSize(makeEven(m_targetSize.width()), makeEven(m_targetSize.height()));
247 if (fixedSize != m_targetSize) {
248 qCDebug(qLcVideoFrameEncoder) << "Fix odd video resolution for codec" << m_codec.name()
249 << ":" << m_targetSize << "->" << fixedSize;
250 m_targetSize = fixedSize;
251 }
252 }
253#endif
254}
255
256void VideoFrameEncoder::initCodecFrameRate()
257{
258 const auto frameRates = m_codec.frameRates();
259 if (qLcVideoFrameEncoder().isEnabled(QtDebugMsg))
260 for (AVRational rate : frameRates)
261 qCDebug(qLcVideoFrameEncoder) << "supported frame rate:" << rate;
262
263 m_codecFrameRate = adjustFrameRate(frameRates, m_settings.videoFrameRate(), m_sourceFrameRate);
264 qCDebug(qLcVideoFrameEncoder) << "Adjusted frame rate:" << m_codecFrameRate;
265}
266
267bool VideoFrameEncoder::initTargetFormats(const AVPixelFormatSet &prohibitedTargetFormats)
268{
269 const std::optional format =
270 findTargetFormat(m_sourceSWFormat, m_codec, m_accel.get(), prohibitedTargetFormats);
271
272 if (!format) {
273 qWarning() << "Could not find target format for codecId" << m_codec.id();
274 return false;
275 }
276
277 m_targetFormat = *format;
278
279 if (isHwPixelFormat(m_targetFormat)) {
280 Q_ASSERT(m_accel);
281
282 // don't pass prohibitedTargetFormats here as m_targetSWFormat is the format,
283 // from which we load a hardware texture, and the format doesn't impact on encoding.
284 const auto swFormat = findTargetSWFormat(m_sourceSWFormat, m_codec, *m_accel);
285 if (!swFormat) {
286 qWarning() << "Cannot find software target format. sourceSWFormat:" << m_sourceSWFormat
287 << "targetFormat:" << m_targetFormat;
288 return false;
289 }
290
291 m_targetSWFormat = *swFormat;
292
293 m_accel->createFramesContext(m_targetSWFormat, m_targetSize);
294 if (!m_accel->hwFramesContextAsBuffer())
295 return false;
296 } else {
297 m_targetSWFormat = m_targetFormat;
298 }
299
300 return true;
301}
302
304
305void VideoFrameEncoder::initStream()
306{
307 m_stream->codecpar->codec_id = m_codec.id();
308
309 // Apples HEVC decoders don't like the hev1 tag ffmpeg uses by default, use hvc1 as the more
310 // commonly accepted tag
311 if (m_codec.id() == AV_CODEC_ID_HEVC)
312 m_stream->codecpar->codec_tag = MKTAG('h', 'v', 'c', '1');
313 else
314 m_stream->codecpar->codec_tag = 0;
315
316 // ### Fix hardcoded values
317 m_stream->codecpar->format = m_targetFormat;
318 m_stream->codecpar->width = m_targetSize.width();
319 m_stream->codecpar->height = m_targetSize.height();
320 m_stream->codecpar->sample_aspect_ratio = AVRational{ 1, 1 };
321#if QT_CODEC_PARAMETERS_HAVE_FRAMERATE
322 // Some codecs (e.g. Windows Media Foundation encoders) require a concrete,
323 // non-zero frame rate
324 m_stream->codecpar->framerate = effectiveCodecFrameRate();
325#endif
326
327 const auto frameRates = m_codec.frameRates();
328 const bool isFixedRate = m_codecFrameRate.den > 0 && m_codecFrameRate.num > 0;
329 m_stream->time_base = adjustFrameTimeBase(frameRates, effectiveCodecFrameRate(), isFixedRate);
330}
331
332bool VideoFrameEncoder::initCodecContext()
333{
334 namespace views = QtMultimediaPrivate::views;
335
336 Q_ASSERT(m_stream->codecpar->codec_id);
337
338 m_codecContext.reset(avcodec_alloc_context3(m_codec.get()));
339 if (!m_codecContext) {
340 qWarning() << "Could not allocate codec context";
341 return false;
342 }
343
344 // copies format, size, color params, framerate
345 const int status = avcodec_parameters_to_context(m_codecContext.get(), m_stream->codecpar);
346 if (status < 0) {
347 qCWarning(qLcVideoFrameEncoder)
348 << "Cannot set codec parameters; result:" << AVError(status);
349 return false;
350 }
351
352#if !QT_CODEC_PARAMETERS_HAVE_FRAMERATE
353 m_codecContext->framerate = effectiveCodecFrameRate();
354#endif
355 m_codecContext->time_base = m_stream->time_base;
356 qCDebug(qLcVideoFrameEncoder) << "codecContext time base" << m_codecContext->time_base.num
357 << m_codecContext->time_base.den;
358
359 if (m_accel) {
360 const AVHWDeviceType deviceType = m_accel->deviceType();
361 auto hwConfigs = m_codec.hwConfigs();
362 auto matchingConfigs = hwConfigs | views::filter([&](const AVCodecHWConfig *cfg) {
363 return cfg->device_type == deviceType;
364 });
365
366 bool supportsDeviceCtx = false;
367 bool supportsFramesCtx = false;
368 for (const AVCodecHWConfig *cfg : matchingConfigs) {
369 if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
370 supportsDeviceCtx = true;
371 if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX)
372 supportsFramesCtx = true;
373 }
374
375 if (supportsDeviceCtx) {
376 auto deviceContext = m_accel->hwDeviceContextAsBuffer();
377 Q_ASSERT(deviceContext);
378 m_codecContext->hw_device_ctx = av_buffer_ref(deviceContext);
379 }
380
381 if (supportsFramesCtx)
382 if (auto framesContext = m_accel->hwFramesContextAsBuffer())
383 m_codecContext->hw_frames_ctx = av_buffer_ref(framesContext);
384 }
385
386 return true;
387}
388
389bool VideoFrameEncoder::open()
390{
391 Q_ASSERT(m_codecContext);
392
393 AVDictionaryHolder opts;
394 applyVideoEncoderOptions(m_settings, m_codec.name(), m_codecContext.get(), opts);
395 applyExperimentalCodecOptions(m_codec, opts);
396
397 qCDebug(qLcVideoFrameEncoder) << "Opening encoder" << m_codec.name() << "with" << opts;
398
399 int res = avcodec_open2(m_codecContext.get(), m_codec.get(), opts);
400 if (res < 0) {
401 qCWarning(qLcVideoFrameEncoder)
402 << "Couldn't open video encoder" << m_codec.name() << "; result:" << AVError(res);
403 return false;
404 }
405 qCDebug(qLcVideoFrameEncoder) << "video codec opened" << res << "time base"
406 << m_codecContext->time_base;
407
408 res = avcodec_parameters_from_context(m_stream->codecpar, m_codecContext.get());
409 if (res < 0) {
410 qCWarning(qLcVideoFrameEncoder) << "Cannot get parameters from video codec context"
411 << m_codec.name() << "; result:" << AVError(res);
412 return false;
413 }
414
415 return true;
416}
417
419{
420 return m_codecFrameRate.den ? qreal(m_codecFrameRate.num) / m_codecFrameRate.den : 0.;
421}
422
424{
425 qint64 div = 1'000'000 * m_stream->time_base.num;
426 return div != 0 ? (us * m_stream->time_base.den + div / 2) / div : 0;
427}
428
430{
431 return m_stream->time_base;
432}
433
434namespace {
435struct FrameConverter
436{
437 FrameConverter(AVFrameUPtr inputFrame) : m_inputFrame{ std::move(inputFrame) } { }
438
439 int downloadFromHw()
440 {
441 AVFrameUPtr cpuFrame = makeAVFrame();
442
443 int err = av_hwframe_transfer_data(cpuFrame.get(), currentFrame(), 0);
444 if (err < 0) {
445 qCDebug(qLcVideoFrameEncoder)
446 << "Error transferring frame data to surface." << AVError(err);
447 return err;
448 }
449
450 setFrame(std::move(cpuFrame));
451 return 0;
452 }
453
454 void convert(SwsContext *scaleContext, AVPixelFormat format, const QSize &size)
455 {
456 AVFrameUPtr scaledFrame = makeAVFrame();
457
458 scaledFrame->format = format;
459 scaledFrame->width = size.width();
460 scaledFrame->height = size.height();
461
462 const int status = av_frame_get_buffer(scaledFrame.get(), 0);
463 if (status < 0) {
464 qCWarning(qLcVideoFrameEncoder)
465 << "Failed to allocate scaled frame buffer:" << AVError(status);
466 return;
467 }
468
469 const AVFrame *srcFrame = currentFrame();
470
471 const auto scaledHeight =
472 sws_scale(scaleContext, srcFrame->data, srcFrame->linesize, 0, srcFrame->height,
473 scaledFrame->data, scaledFrame->linesize);
474
475 if (scaledHeight != scaledFrame->height)
476 qCWarning(qLcVideoFrameEncoder)
477 << "Scaled height" << scaledHeight << "!=" << scaledFrame->height;
478
479 setFrame(std::move(scaledFrame));
480 }
481
482 int uploadToHw(HWAccel *accel)
483 {
484 auto *hwFramesContext = accel->hwFramesContextAsBuffer();
485 Q_ASSERT(hwFramesContext);
486 AVFrameUPtr hwFrame = makeAVFrame();
487 if (!hwFrame)
488 return AVERROR(ENOMEM);
489
490 int err = av_hwframe_get_buffer(hwFramesContext, hwFrame.get(), 0);
491 if (err < 0) {
492 qCDebug(qLcVideoFrameEncoder) << "Error getting HW buffer" << AVError(err);
493 return err;
494 } else {
495 qCDebug(qLcVideoFrameEncoder) << "got HW buffer";
496 }
497 if (!hwFrame->hw_frames_ctx) {
498 qCDebug(qLcVideoFrameEncoder) << "no hw frames context";
499 return AVERROR(ENOMEM);
500 }
501 err = av_hwframe_transfer_data(hwFrame.get(), currentFrame(), 0);
502 if (err < 0) {
503 qCDebug(qLcVideoFrameEncoder)
504 << "Error transferring frame data to surface." << AVError(err);
505 return err;
506 }
507
508 setFrame(std::move(hwFrame));
509
510 return 0;
511 }
512
513 q23::expected<AVFrameUPtr, int> takeResultFrame()
514 {
515 // Ensure that object is reset to empty state
516 AVFrameUPtr converted = std::move(m_convertedFrame);
517 AVFrameUPtr input = std::move(m_inputFrame);
518
519 if (!converted)
520 return input;
521
522 // Copy metadata except size and format from input frame
523 const int status = av_frame_copy_props(converted.get(), input.get());
524 if (status != 0)
525 return q23::unexpected{ status };
526
527 return converted;
528 }
529
530private:
531 void setFrame(AVFrameUPtr frame) { m_convertedFrame = std::move(frame); }
532
533 AVFrame *currentFrame() const
534 {
535 if (m_convertedFrame)
536 return m_convertedFrame.get();
537 return m_inputFrame.get();
538 }
539
540 AVFrameUPtr m_inputFrame;
541 AVFrameUPtr m_convertedFrame;
542};
543} // namespace
544
545int VideoFrameEncoder::sendFrame(AVFrameUPtr inputFrame)
546{
547 if (!m_codecContext) {
548 qWarning() << "codec context is not initialized!";
549 return AVERROR(EINVAL);
550 }
551
552 if (!inputFrame)
553 return avcodec_send_frame(m_codecContext.get(), nullptr); // Flush
554
555 if (!updateSourceFormatAndSize(inputFrame.get()))
556 return AVERROR(EINVAL);
557
558 // some codecs require quality to be set on each frame and ignore global_quality
559 inputFrame->quality = m_codecContext->global_quality;
560
561 FrameConverter converter{ std::move(inputFrame) };
562
563 if (m_downloadFromHW) {
564 const int status = converter.downloadFromHw();
565 if (status != 0)
566 return status;
567 }
568
569 if (m_scaleContext)
570 converter.convert(m_scaleContext.get(), m_targetSWFormat, m_targetSize);
571
572 if (m_uploadToHW) {
573 const int status = converter.uploadToHw(m_accel.get());
574 if (status != 0)
575 return status;
576 }
577
578 const q23::expected<AVFrameUPtr, int> resultFrame = converter.takeResultFrame();
579 if (!resultFrame)
580 return resultFrame.error();
581
582 AVRational timeBase{};
583 int64_t pts{};
584 getAVFrameTime(*resultFrame.value(), pts, timeBase);
585 qCDebug(qLcVideoFrameEncoder) << "sending frame" << pts << "*" << timeBase;
586
587 return avcodec_send_frame(m_codecContext.get(), resultFrame.value().get());
588}
589
590qint64 VideoFrameEncoder::estimateDuration(const AVPacket &packet, bool isFirstPacket)
591{
592 qint64 duration = 0; // In stream units, multiply by time_base to get seconds
593
594 if (isFirstPacket) {
595 // First packet - Estimate duration from frame rate. Duration must
596 // be set for single-frame videos, otherwise they won't open in
597 // media player. The codec frame rate is 0 for variable-rate sources,
598 // so fall back to a default rate to avoid dividing by zero.
599 AVRational frameRate = m_codecContext->framerate;
600 if (frameRate.num <= 0)
601 frameRate = AVRational{ DefaultVideoFrameRate, 1 };
602
603 const AVRational frameDuration = av_inv_q(frameRate);
604 duration = av_rescale_q(1, frameDuration, m_stream->time_base);
605 } else {
606 // Duration is calculated from actual packet times. TODO: Handle discontinuities
607 duration = packet.pts - m_lastPacketTime;
608 }
609
610 return duration;
611}
612
614{
615 if (!m_codecContext)
616 return nullptr;
617
618 auto getPacket = [&]() {
619 AVPacketUPtr packet(av_packet_alloc());
620 const int ret = avcodec_receive_packet(m_codecContext.get(), packet.get());
621 if (ret < 0) {
622 if (ret != AVERROR(EOF) && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
623 qCDebug(qLcVideoFrameEncoder) << "Error receiving packet" << ret << AVError(ret);
624 return AVPacketUPtr{};
625 }
626 auto ts = timeStampMs(packet->pts, m_stream->time_base);
627
628 qCDebug(qLcVideoFrameEncoder)
629 << "got a packet" << packet->pts << packet->dts << (ts ? *ts : 0);
630
631 packet->stream_index = m_stream->id;
632
633 if (packet->duration == 0) {
634 const bool firstFrame = m_lastPacketTime == AV_NOPTS_VALUE;
635 packet->duration = estimateDuration(*packet, firstFrame);
636 }
637
638 m_lastPacketTime = packet->pts;
639
640 return packet;
641 };
642
643 auto fixPacketDts = [&](AVPacket &packet) {
644 // Workaround for some ffmpeg codecs bugs (e.g. nvenc)
645 // Ideally, packet->pts < packet->dts is not expected
646
647 if (packet.dts == AV_NOPTS_VALUE)
648 return true;
649
650 // Some encoders (e.g. libx265) produce garbage negative DTS with single-frame encodes.
651 // Clamp to PTS (or 0) to prevent corrupt output that makes playback hang.
652 if (packet.dts < 0)
653 packet.dts = packet.pts != AV_NOPTS_VALUE ? packet.pts : 0;
654
655 packet.dts -= m_packetDtsOffset;
656
657 if (packet.pts != AV_NOPTS_VALUE && packet.pts < packet.dts) {
658 m_packetDtsOffset += packet.dts - packet.pts;
659 packet.dts = packet.pts;
660
661 if (m_prevPacketDts != AV_NOPTS_VALUE && packet.dts < m_prevPacketDts) {
662 qCWarning(qLcVideoFrameEncoder)
663 << "Skip packet; failed to fix dts:" << packet.dts << m_prevPacketDts;
664 return false;
665 }
666 }
667
668 m_prevPacketDts = packet.dts;
669
670 return true;
671 };
672
673 while (auto packet = getPacket()) {
674 if (fixPacketDts(*packet))
675 return packet;
676 }
677
678 return nullptr;
679}
680
681bool VideoFrameEncoder::updateSourceFormatAndSize(const AVFrame *frame)
682{
683 Q_ASSERT(frame);
684
685 const QSize frameSize(frame->width, frame->height);
686 const AVPixelFormat frameFormat = static_cast<AVPixelFormat>(frame->format);
687
688 if (frameSize == m_sourceSize && frameFormat == m_sourceFormat)
689 return true;
690
691 auto applySourceFormatAndSize = [&](AVPixelFormat swFormat) {
692 m_sourceSize = frameSize;
693 m_sourceFormat = frameFormat;
694 m_sourceSWFormat = swFormat;
695 updateConversions();
696 return true;
697 };
698
699 if (frameFormat == m_sourceFormat)
700 return applySourceFormatAndSize(m_sourceSWFormat);
701
702 if (frameFormat == AV_PIX_FMT_NONE) {
703 qWarning() << "Got a frame with invalid pixel format";
704 return false;
705 }
706
707 if (isSwPixelFormat(frameFormat))
708 return applySourceFormatAndSize(frameFormat);
709
710 auto framesCtx = reinterpret_cast<const AVHWFramesContext *>(frame->hw_frames_ctx->data);
711 if (!framesCtx || framesCtx->sw_format == AV_PIX_FMT_NONE) {
712 qWarning() << "Cannot update conversions as hw frame has invalid framesCtx" << framesCtx;
713 return false;
714 }
715
716 return applySourceFormatAndSize(framesCtx->sw_format);
717}
718
719void VideoFrameEncoder::updateConversions()
720{
721 const bool needToScale = m_sourceSize != m_targetSize;
722 const bool zeroCopy = m_sourceFormat == m_targetFormat && !needToScale;
723
724 m_scaleContext.reset();
725
726 if (zeroCopy) {
727 m_downloadFromHW = false;
728 m_uploadToHW = false;
729
730 qCDebug(qLcVideoFrameEncoder) << "zero copy encoding, format" << m_targetFormat;
731 // no need to initialize any converters
732 return;
733 }
734
735 m_downloadFromHW = m_sourceFormat != m_sourceSWFormat;
736 m_uploadToHW = m_targetFormat != m_targetSWFormat;
737
738 if (m_sourceSWFormat != m_targetSWFormat || needToScale) {
739 qCDebug(qLcVideoFrameEncoder)
740 << "video source and encoder use different formats:" << m_sourceSWFormat
741 << m_targetSWFormat << "or sizes:" << m_sourceSize << m_targetSize;
742
743 const SwsFlags conversionType = getScaleConversionType(m_sourceSize, m_targetSize);
744
745 m_scaleContext = createSwsContext(m_sourceSize, m_sourceSWFormat, m_targetSize,
746 m_targetSWFormat, conversionType);
747 }
748
749 qCDebug(qLcVideoFrameEncoder) << "VideoFrameEncoder conversions initialized:"
750 << "sourceFormat:" << m_sourceFormat
751 << (isHwPixelFormat(m_sourceFormat) ? "(hw)" : "(sw)")
752 << "targetFormat:" << m_targetFormat
753 << (isHwPixelFormat(m_targetFormat) ? "(hw)" : "(sw)")
754 << "sourceSWFormat:" << m_sourceSWFormat
755 << "targetSWFormat:" << m_targetSWFormat
756 << "scaleContext:" << m_scaleContext.get();
757}
758
759} // namespace QFFmpeg
760
761QT_END_NAMESPACE
const AVRational & getTimeBase() const
int sendFrame(AVFrameUPtr inputFrame)
QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) QMacScreenCaptureStreamDelegate
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)