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