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