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

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoframeencoder.cpp