11#include <QtCore/qloggingcategory.h>
12#include <QtCore/private/qexpected_p.h>
15#include "libavutil/display.h"
16#include "libavutil/pixdesc.h"
21namespace ranges = QtMultimediaPrivate::ranges;
29AVCodecID avCodecID(
const QMediaEncoderSettings &settings)
31 const QMediaFormat::VideoCodec qVideoCodec = settings.videoCodec();
32 return QFFmpegMediaFormatInfo::codecIdForVideoCodec(qVideoCodec);
36std::optional<AVHWDeviceType> getHwDeviceType(
const Codec &codec)
38 std::optional<AVPixelFormat> pixelFormat = findAVPixelFormat(codec, isHwPixelFormat);
42 const AVCodecHWConfig *cfg = codec.hwConfigForPixelFormat(*pixelFormat);
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)
51 const QSpan deviceTypes = HWAccel::encodingDeviceTypes();
52 auto found = ranges::find_if(deviceTypes, [&](AVHWDeviceType deviceType) {
53 return pixelFormatForHwDevice(deviceType) == pixelFormat;
55 if (found != deviceTypes.end())
61bool muxerNeedsGlobalHeader(
const AVFormatContext *formatContext)
63 return (formatContext->oformat->flags & AVFMT_GLOBALHEADER) != 0;
68VideoFrameEncoderUPtr
VideoFrameEncoder::create(
const QMediaEncoderSettings &encoderSettings,
70 AVFormatContext *formatContext)
72 Q_ASSERT(isSwPixelFormat(sourceParams.swFormat));
73 Q_ASSERT(isHwPixelFormat(sourceParams.format) || sourceParams.swFormat == sourceParams.format);
75 AVStream *stream = createStream(sourceParams, formatContext);
79 const AVCodecID codecId = avCodecID(encoderSettings);
80 const bool needsGlobalHeader = muxerNeedsGlobalHeader(formatContext);
83 VideoFrameEncoderUPtr encoder = [&]() -> VideoFrameEncoderUPtr {
84 const std::vector encoders = findAndScoreEncoders(codecId, [](
const Codec &codec) {
85 std::optional<AVHWDeviceType> deviceType = getHwDeviceType(codec);
87 return NotSuitableAVScore;
89 const QSpan deviceTypes = HWAccel::encodingDeviceTypes();
90 auto found = ranges::find(deviceTypes, *deviceType);
91 Q_ASSERT(found != deviceTypes.end());
93 return DefaultAVScore -
static_cast<AVScore>(found - deviceTypes.begin());
96 for (
const auto &[codec, score] : encoders) {
97 HWAccelUPtr hwAccel = HWAccel::create(*getHwDeviceType(codec));
100 if (!hwAccel->matchesSizeContraints(encoderSettings.videoResolution()))
102 auto encoder = create(stream, codec, std::move(hwAccel), sourceParams, encoderSettings,
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);
118 for (
const auto &[codec, score] : encoders) {
119 auto encoder = create(stream, codec,
nullptr, sourceParams, encoderSettings,
129 qCDebug(qLcVideoFrameEncoder)
130 <<
"found" << (encoder->m_accel ?
"hw" :
"sw") <<
"encoder"
131 << encoder->m_codec.name() <<
"for id" << encoder->m_codec.id();
133 qCWarning(qLcVideoFrameEncoder) <<
"No valid video codecs found";
139 HWAccel *accel, QSize targetSize)
141 const std::optional format = findTargetFormat(sourceSwFormat, codec, accel, targetSize);
143 qWarning() <<
"Could not find target format for codecId" << codec.id();
145 return format.value_or(AV_PIX_FMT_NONE);
150 QSize targetSize = adjustVideoResolution(codec, settings.videoResolution());
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;
168VideoFrameEncoder::VideoFrameEncoder(AVStream *stream,
const Codec &codec, HWAccelUPtr hwAccel,
170 const QMediaEncoderSettings &encoderSettings,
171 bool needsGlobalHeader)
172 : m_settings(encoderSettings),
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)
184 initCodecFrameRate();
189 AVFormatContext *formatContext)
191 AVStream *stream = avformat_new_stream(formatContext,
nullptr);
196 stream->id = formatContext->nb_streams - 1;
197 stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
199 stream->codecpar->color_trc = sourceParams.colorTransfer;
200 stream->codecpar->color_space = sourceParams.colorSpace;
201 stream->codecpar->color_range = sourceParams.colorRange;
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);
212 addStreamSideData(stream, sideData);
218VideoFrameEncoderUPtr
VideoFrameEncoder::create(AVStream *stream,
const Codec &codec,
221 const QMediaEncoderSettings &encoderSettings,
222 bool needsGlobalHeader)
225 sourceParams, encoderSettings,
228 if (frameEncoder->targetFormat() == AV_PIX_FMT_NONE)
231 auto targetSwFormats = frameEncoder->enumerateTargetSwFormats();
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();
239 qCDebug(qLcVideoFrameEncoder) <<
"Failed to init target sw format" << candidate.format
240 <<
"for codec" << frameEncoder->m_codec.name();
248 const auto frameRates = m_codec.frameRates();
249 if (qLcVideoFrameEncoder().isEnabled(QtDebugMsg))
250 for (AVRational rate : frameRates)
251 qCDebug(qLcVideoFrameEncoder) <<
"supported frame rate:" << rate;
253 m_codecFrameRate = adjustFrameRate(frameRates, m_settings.videoFrameRate(), m_sourceFrameRate);
254 qCDebug(qLcVideoFrameEncoder) <<
"Adjusted frame rate:" << m_codecFrameRate;
257std::vector<ScoredPixelFormat>
VideoFrameEncoder::enumerateTargetSwFormats()
const
259 return QFFmpeg::findAndScoreTargetSWFormats(m_sourceSWFormat, m_codec, m_accel.get(),
265 qCDebug(qLcVideoFrameEncoder) <<
"Trying to init target sw format" << m_targetFormat
268 m_targetSWFormat = targetSwFormat;
270 m_accel->destroyFramesContext();
271 m_accel->createFramesContext(m_targetSWFormat, m_targetSize);
273 m_targetFormat = targetSwFormat;
274 m_stream->codecpar->format = targetSwFormat;
276 auto cleanup = QScopeGuard([
this] {
278 m_accel->destroyFramesContext();
281 if (m_accel && !m_accel->hwFramesContextAsBuffer()) {
282 qCDebug(qLcVideoFrameEncoder)
283 <<
"Failed to create frames context for targetSwFormat" << targetSwFormat;
287 if (!initCodecContext())
302 m_stream->codecpar->codec_id = m_codec.id();
306 if (m_codec.id() == AV_CODEC_ID_HEVC)
307 m_stream->codecpar->codec_tag = MKTAG(
'h',
'v',
'c',
'1');
309 m_stream->codecpar->codec_tag = 0;
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
319 m_stream->codecpar->framerate = effectiveCodecFrameRate();
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);
329 namespace views = QtMultimediaPrivate::views;
331 Q_ASSERT(m_stream->codecpar->codec_id);
333 m_codecContext.reset(avcodec_alloc_context3(m_codec.get()));
334 if (!m_codecContext) {
335 qWarning() <<
"Could not allocate codec context";
340 const int status = avcodec_parameters_to_context(m_codecContext.get(), m_stream->codecpar);
342 qCWarning(qLcVideoFrameEncoder)
343 <<
"Cannot set codec parameters; result:" << AVError(status);
347#if !QT_CODEC_PARAMETERS_HAVE_FRAMERATE
348 m_codecContext->framerate = effectiveCodecFrameRate();
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;
354 if (m_needsGlobalHeader)
355 m_codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
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;
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;
373 if (supportsDeviceCtx) {
374 auto deviceContext = m_accel->hwDeviceContextAsBuffer();
375 Q_ASSERT(deviceContext);
376 m_codecContext->hw_device_ctx = av_buffer_ref(deviceContext);
379 if (supportsFramesCtx)
380 if (
auto framesContext = m_accel->hwFramesContextAsBuffer())
381 m_codecContext->hw_frames_ctx = av_buffer_ref(framesContext);
389 Q_ASSERT(m_codecContext);
391 AVDictionaryHolder opts;
392 applyVideoEncoderOptions(m_settings, m_codec.name(), m_codecContext.get(), opts);
393 applyExperimentalCodecOptions(m_codec, opts);
395 qCDebug(qLcVideoFrameEncoder) <<
"Opening encoder" << m_codec.name() <<
"with" << opts;
397 int res = avcodec_open2(m_codecContext.get(), m_codec.get(), opts);
399 qCWarning(qLcVideoFrameEncoder)
400 <<
"Couldn't open video encoder" << m_codec.name() <<
"; result:" << AVError(res);
403 qCDebug(qLcVideoFrameEncoder) <<
"video codec opened" << res <<
"time base"
404 << m_codecContext->time_base;
406 res = avcodec_parameters_from_context(m_stream->codecpar, m_codecContext.get());
408 qCWarning(qLcVideoFrameEncoder) <<
"Cannot get parameters from video codec context"
409 << m_codec.name() <<
"; result:" << AVError(res);
418 return m_codecFrameRate.den ? qreal(m_codecFrameRate.num) / m_codecFrameRate.den : 0.;
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;
429 return m_stream->time_base;
435 FrameConverter(AVFrameUPtr inputFrame) : m_inputFrame{ std::move(inputFrame) } { }
439 AVFrameUPtr cpuFrame = makeAVFrame();
441 int err = av_hwframe_transfer_data(cpuFrame.get(), currentFrame(), 0);
443 qCDebug(qLcVideoFrameEncoder)
444 <<
"Error transferring frame data to surface." << AVError(err);
448 setFrame(std::move(cpuFrame));
452 void convert(SwsContext *scaleContext, AVPixelFormat format,
const QSize &size)
454 AVFrameUPtr scaledFrame = makeAVFrame();
456 scaledFrame->format = format;
457 scaledFrame->width = size.width();
458 scaledFrame->height = size.height();
460 const int status = av_frame_get_buffer(scaledFrame.get(), 0);
462 qCWarning(qLcVideoFrameEncoder)
463 <<
"Failed to allocate scaled frame buffer:" << AVError(status);
467 const AVFrame *srcFrame = currentFrame();
469 const auto scaledHeight =
470 sws_scale(scaleContext, srcFrame->data, srcFrame->linesize, 0, srcFrame->height,
471 scaledFrame->data, scaledFrame->linesize);
473 if (scaledHeight != scaledFrame->height)
474 qCWarning(qLcVideoFrameEncoder)
475 <<
"Scaled height" << scaledHeight <<
"!=" << scaledFrame->height;
477 setFrame(std::move(scaledFrame));
480 int uploadToHw(HWAccel *accel)
482 auto *hwFramesContext = accel->hwFramesContextAsBuffer();
483 Q_ASSERT(hwFramesContext);
484 AVFrameUPtr hwFrame = makeAVFrame();
486 return AVERROR(ENOMEM);
488 int err = av_hwframe_get_buffer(hwFramesContext, hwFrame.get(), 0);
490 qCDebug(qLcVideoFrameEncoder) <<
"Error getting HW buffer" << AVError(err);
493 qCDebug(qLcVideoFrameEncoder) <<
"got HW buffer";
495 if (!hwFrame->hw_frames_ctx) {
496 qCDebug(qLcVideoFrameEncoder) <<
"no hw frames context";
497 return AVERROR(ENOMEM);
499 err = av_hwframe_transfer_data(hwFrame.get(), currentFrame(), 0);
501 qCDebug(qLcVideoFrameEncoder)
502 <<
"Error transferring frame data to surface." << AVError(err);
506 setFrame(std::move(hwFrame));
511 q23::expected<AVFrameUPtr,
int> takeResultFrame()
514 AVFrameUPtr converted =
std::move(m_convertedFrame);
515 AVFrameUPtr input =
std::move(m_inputFrame);
521 const int status = av_frame_copy_props(converted.get(), input.get());
523 return q23::unexpected{ status };
529 void setFrame(AVFrameUPtr frame) { m_convertedFrame = std::move(frame); }
531 AVFrame *currentFrame()
const
533 if (m_convertedFrame)
534 return m_convertedFrame.get();
535 return m_inputFrame.get();
538 AVFrameUPtr m_inputFrame;
539 AVFrameUPtr m_convertedFrame;
545 if (!m_codecContext) {
546 qWarning() <<
"codec context is not initialized!";
547 return AVERROR(EINVAL);
551 return avcodec_send_frame(m_codecContext.get(),
nullptr);
553 if (!updateSourceFormatAndSize(inputFrame.get()))
554 return AVERROR(EINVAL);
557 inputFrame->quality = m_codecContext->global_quality;
559 FrameConverter converter{
std::move(inputFrame) };
561 if (m_downloadFromHW) {
562 const int status = converter.downloadFromHw();
568 converter.convert(m_scaleContext.get(), m_targetSWFormat, m_targetSize);
571 const int status = converter.uploadToHw(m_accel.get());
576 const q23::expected<AVFrameUPtr,
int> resultFrame = converter.takeResultFrame();
578 return resultFrame.error();
580 AVRational timeBase{};
582 getAVFrameTime(*resultFrame.value(), pts, timeBase);
583 qCDebug(qLcVideoFrameEncoder) <<
"sending frame" << pts <<
"*" << timeBase;
585 return avcodec_send_frame(m_codecContext.get(), resultFrame.value().get());
588qint64
VideoFrameEncoder::estimateDuration(
const AVPacket &packet,
bool isFirstPacket)
597 AVRational frameRate = m_codecContext->framerate;
598 if (frameRate.num <= 0)
599 frameRate = AVRational{ DefaultVideoFrameRate, 1 };
601 const AVRational frameDuration = av_inv_q(frameRate);
602 duration = av_rescale_q(1, frameDuration, m_stream->time_base);
605 duration = packet.pts - m_lastPacketTime;
616 auto getPacket = [&]() {
617 AVPacketUPtr packet(av_packet_alloc());
618 const int ret = avcodec_receive_packet(m_codecContext.get(), packet.get());
620 if (ret != AVERROR(EOF) && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
621 qCDebug(qLcVideoFrameEncoder) <<
"Error receiving packet" << ret << AVError(ret);
622 return AVPacketUPtr{};
624 auto ts = timeStampMs(packet->pts, m_stream->time_base);
626 qCDebug(qLcVideoFrameEncoder)
627 <<
"got a packet" << packet->pts << packet->dts << (ts ? *ts : 0);
629 packet->stream_index = m_stream->id;
631 if (packet->duration == 0) {
632 const bool firstFrame = m_lastPacketTime == AV_NOPTS_VALUE;
633 packet->duration = estimateDuration(*packet, firstFrame);
636 m_lastPacketTime = packet->pts;
641 auto fixPacketDts = [&](AVPacket &packet) {
645 if (packet.dts == AV_NOPTS_VALUE)
648 packet.dts -= m_packetDtsOffset;
650 if (packet.pts != AV_NOPTS_VALUE && packet.pts < packet.dts) {
651 m_packetDtsOffset += packet.dts - packet.pts;
652 packet.dts = packet.pts;
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;
661 m_prevPacketDts = packet.dts;
666 while (
auto packet = getPacket()) {
667 if (fixPacketDts(*packet))
678 const QSize frameSize(frame->width, frame->height);
679 const AVPixelFormat frameFormat =
static_cast<AVPixelFormat>(frame->format);
681 if (frameSize == m_sourceSize && frameFormat == m_sourceFormat)
684 auto applySourceFormatAndSize = [&](AVPixelFormat swFormat) {
685 m_sourceSize = frameSize;
686 m_sourceFormat = frameFormat;
687 m_sourceSWFormat = swFormat;
692 if (frameFormat == m_sourceFormat)
693 return applySourceFormatAndSize(m_sourceSWFormat);
695 if (frameFormat == AV_PIX_FMT_NONE) {
696 qWarning() <<
"Got a frame with invalid pixel format";
700 if (isSwPixelFormat(frameFormat))
701 return applySourceFormatAndSize(frameFormat);
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;
709 return applySourceFormatAndSize(framesCtx->sw_format);
714 const bool needToScale = m_sourceSize != m_targetSize;
715 const bool zeroCopy = m_sourceFormat == m_targetFormat && !needToScale;
717 m_scaleContext.reset();
720 m_downloadFromHW =
false;
721 m_uploadToHW =
false;
723 qCDebug(qLcVideoFrameEncoder) <<
"zero copy encoding, format" << m_targetFormat;
728 m_downloadFromHW = m_sourceFormat != m_sourceSWFormat;
729 m_uploadToHW = m_targetFormat != m_targetSWFormat;
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;
736 const SwsFlags conversionType = getScaleConversionType(m_sourceSize, m_targetSize);
738 m_scaleContext = createSwsContext(m_sourceSize, m_sourceSWFormat, m_targetSize,
739 m_targetSWFormat, conversionType);
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();
const AVRational & getTimeBase() const
qint64 getPts(qint64 ms) const
int sendFrame(AVFrameUPtr inputFrame)
AVPacketUPtr retrievePacket()
qreal codecFrameRate() const
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)