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