Revision control

Copy as Markdown

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qffmpegvideoframeencoder_p.h"
#include "qffmpegmediaformatinfo_p.h"
#include "qffmpegencoderoptions_p.h"
#include "qffmpegvideoencoderutils_p.h"
#include "qffmpegcodecstorage_p.h"
#include "qffmpegrecordingengineutils_p.h"
#include <QtCore/qloggingcategory.h>
#include <QtCore/private/qexpected_p.h>
extern "C" {
#include "libavutil/display.h"
#include "libavutil/pixdesc.h"
}
QT_BEGIN_NAMESPACE
namespace ranges = QtMultimediaPrivate::ranges;
Q_STATIC_LOGGING_CATEGORY(qLcVideoFrameEncoder, "qt.multimedia.ffmpeg.videoencoder");
namespace QFFmpeg {
namespace {
AVCodecID avCodecID(const QMediaEncoderSettings &settings)
{
const QMediaFormat::VideoCodec qVideoCodec = settings.videoCodec();
return QFFmpegMediaFormatInfo::codecIdForVideoCodec(qVideoCodec);
}
// LATER: move to HWAccel or Codec?
std::optional<AVHWDeviceType> getHwDeviceType(const Codec &codec)
{
std::optional<AVPixelFormat> pixelFormat = findAVPixelFormat(codec, isHwPixelFormat);
if (!pixelFormat)
return std::nullopt;
const AVCodecHWConfig *cfg = codec.hwConfigForPixelFormat(*pixelFormat);
if (!cfg)
return std::nullopt;
bool supportsHwDeviceContext = (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) != 0;
bool supportsHwFramesContext = (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) != 0;
if (!supportsHwDeviceContext && !supportsHwFramesContext)
return std::nullopt;
const QSpan deviceTypes = HWAccel::encodingDeviceTypes();
auto found = ranges::find_if(deviceTypes, [&](AVHWDeviceType deviceType) {
return pixelFormatForHwDevice(deviceType) == pixelFormat;
});
if (found != deviceTypes.end())
return *found;
else
return std::nullopt;
}
bool muxerNeedsGlobalHeader(const AVFormatContext *formatContext)
{
return (formatContext->oformat->flags & AVFMT_GLOBALHEADER) != 0;
}
} // namespace
VideoFrameEncoderUPtr VideoFrameEncoder::create(const QMediaEncoderSettings &encoderSettings,
const SourceParams &sourceParams,
AVFormatContext *formatContext)
{
Q_ASSERT(isSwPixelFormat(sourceParams.swFormat));
Q_ASSERT(isHwPixelFormat(sourceParams.format) || sourceParams.swFormat == sourceParams.format);
AVStream *stream = createStream(sourceParams, formatContext);
if (!stream)
return nullptr;
const AVCodecID codecId = avCodecID(encoderSettings);
const bool needsGlobalHeader = muxerNeedsGlobalHeader(formatContext);
// first we try to open a hardware encoder
VideoFrameEncoderUPtr encoder = [&]() -> VideoFrameEncoderUPtr {
const std::vector encoders = findAndScoreEncoders(codecId, [](const Codec &codec) {
std::optional<AVHWDeviceType> deviceType = getHwDeviceType(codec);
if (!deviceType)
return NotSuitableAVScore;
const QSpan deviceTypes = HWAccel::encodingDeviceTypes();
auto found = ranges::find(deviceTypes, *deviceType);
Q_ASSERT(found != deviceTypes.end());
return DefaultAVScore - static_cast<AVScore>(found - deviceTypes.begin());
});
for (const auto &[codec, score] : encoders) {
HWAccelUPtr hwAccel = HWAccel::create(*getHwDeviceType(codec));
if (!hwAccel)
continue;
if (!hwAccel->matchesSizeContraints(encoderSettings.videoResolution()))
continue;
auto encoder = create(stream, codec, std::move(hwAccel), sourceParams, encoderSettings,
needsGlobalHeader);
if (encoder)
return encoder;
}
return nullptr;
}();
if (!encoder) {
// otherwise fall back to software
encoder = [&]() -> VideoFrameEncoderUPtr {
const std::vector encoders = findAndScoreEncoders(codecId, [&](const Codec &codec) {
const QSize resolution = adjustVideoResolution(codec, encoderSettings.videoResolution());
return findSWFormatScores(codec, sourceParams.swFormat, resolution);
});
for (const auto &[codec, score] : encoders) {
auto encoder = create(stream, codec, nullptr, sourceParams, encoderSettings,
needsGlobalHeader);
if (encoder)
return encoder;
}
return nullptr;
}();
}
if (encoder)
qCDebug(qLcVideoFrameEncoder)
<< "found" << (encoder->m_accel ? "hw" : "sw") << "encoder"
<< encoder->m_codec.name() << "for id" << encoder->m_codec.id();
else
qCWarning(qLcVideoFrameEncoder) << "No valid video codecs found";
return encoder;
}
static AVPixelFormat inferHardwareFormat(AVPixelFormat sourceSwFormat, const Codec &codec,
HWAccel *accel, QSize targetSize)
{
const std::optional format = findTargetFormat(sourceSwFormat, codec, accel, targetSize);
if (!format)
qWarning() << "Could not find target format for codecId" << codec.id();
return format.value_or(AV_PIX_FMT_NONE);
}
static QSize computeTargetSize(const Codec &codec, const QMediaEncoderSettings &settings)
{
QSize targetSize = adjustVideoResolution(codec, settings.videoResolution());
#ifdef Q_OS_WINDOWS
// TODO: investigate, there might be more encoders not supporting odd resolution
if (codec.name() == u"h264_mf") {
auto makeEven = [](int size) { return size & ~1; };
const QSize fixedSize(makeEven(targetSize.width()), makeEven(targetSize.height()));
if (fixedSize != targetSize) {
qCDebug(qLcVideoFrameEncoder) << "Fix odd video resolution for codec" << codec.name()
<< ":" << targetSize << "->" << fixedSize;
targetSize = fixedSize;
}
}
#endif
return targetSize;
}
VideoFrameEncoder::VideoFrameEncoder(AVStream *stream, const Codec &codec, HWAccelUPtr hwAccel,
const SourceParams &sourceParams,
const QMediaEncoderSettings &encoderSettings,
bool needsGlobalHeader)
: m_settings(encoderSettings),
m_stream(stream),
m_codec(codec),
m_accel(std::move(hwAccel)),
m_needsGlobalHeader(needsGlobalHeader),
m_sourceSize(sourceParams.size),
m_targetSize(computeTargetSize(codec, encoderSettings)),
m_sourceFormat(sourceParams.format),
m_sourceSWFormat(sourceParams.swFormat),
m_targetFormat(inferHardwareFormat(sourceParams.swFormat, codec, m_accel.get(), m_targetSize)),
m_sourceFrameRate(sourceParams.frameRate)
{
initCodecFrameRate();
initStream();
}
AVStream *VideoFrameEncoder::createStream(const SourceParams &sourceParams,
AVFormatContext *formatContext)
{
AVStream *stream = avformat_new_stream(formatContext, nullptr);
if (!stream)
return stream;
stream->id = formatContext->nb_streams - 1;
stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
stream->codecpar->color_trc = sourceParams.colorTransfer;
stream->codecpar->color_space = sourceParams.colorSpace;
stream->codecpar->color_range = sourceParams.colorRange;
if (sourceParams.transform.rotation != QtVideo::Rotation::None || sourceParams.transform.mirroredHorizontallyAfterRotation) {
constexpr auto displayMatrixSize = sizeof(int32_t) * 9;
AVPacketSideData sideData = { reinterpret_cast<uint8_t *>(av_malloc(displayMatrixSize)),
displayMatrixSize, AV_PKT_DATA_DISPLAYMATRIX };
int32_t *matrix = reinterpret_cast<int32_t *>(sideData.data);
av_display_rotation_set(matrix, static_cast<double>(sourceParams.transform.rotation));
if (sourceParams.transform.mirroredHorizontallyAfterRotation)
av_display_matrix_flip(matrix, sourceParams.transform.mirroredHorizontallyAfterRotation, false);
addStreamSideData(stream, sideData);
}
return stream;
}
VideoFrameEncoderUPtr VideoFrameEncoder::create(AVStream *stream, const Codec &codec,
HWAccelUPtr hwAccel,
const SourceParams &sourceParams,
const QMediaEncoderSettings &encoderSettings,
bool needsGlobalHeader)
{
VideoFrameEncoderUPtr frameEncoder(new VideoFrameEncoder(stream, codec, std::move(hwAccel),
sourceParams, encoderSettings,
needsGlobalHeader));
if (frameEncoder->targetFormat() == AV_PIX_FMT_NONE)
return {};
auto targetSwFormats = frameEncoder->enumerateTargetSwFormats();
for (ScoredPixelFormat candidate : targetSwFormats) {
if (frameEncoder->tryInitTargetSwFormat(candidate.format)) {
qCDebug(qLcVideoFrameEncoder) << "Selected target sw format" << candidate.format
<< "for codec" << frameEncoder->m_codec.name();
return frameEncoder;
}
qCDebug(qLcVideoFrameEncoder) << "Failed to init target sw format" << candidate.format
<< "for codec" << frameEncoder->m_codec.name();
}
return nullptr;
}
void VideoFrameEncoder::initCodecFrameRate()
{
const auto frameRates = m_codec.frameRates();
if (qLcVideoFrameEncoder().isEnabled(QtDebugMsg))
for (AVRational rate : frameRates)
qCDebug(qLcVideoFrameEncoder) << "supported frame rate:" << rate;
m_codecFrameRate = adjustFrameRate(frameRates, m_settings.videoFrameRate(), m_sourceFrameRate);
qCDebug(qLcVideoFrameEncoder) << "Adjusted frame rate:" << m_codecFrameRate;
}
std::vector<ScoredPixelFormat> VideoFrameEncoder::enumerateTargetSwFormats() const
{
return QFFmpeg::findAndScoreTargetSWFormats(m_sourceSWFormat, m_codec, m_accel.get(),
m_targetSize);
}
bool VideoFrameEncoder::tryInitTargetSwFormat(AVPixelFormat targetSwFormat)
{
qCDebug(qLcVideoFrameEncoder) << "Trying to init target sw format" << m_targetFormat
<< targetSwFormat;
m_targetSWFormat = targetSwFormat;
if (m_accel) {
m_accel->destroyFramesContext();
m_accel->createFramesContext(m_targetSWFormat, m_targetSize);
} else {
m_targetFormat = targetSwFormat;
m_stream->codecpar->format = targetSwFormat;
}
auto cleanup = QScopeGuard([this] {
if (m_accel)
m_accel->destroyFramesContext();
});
if (m_accel && !m_accel->hwFramesContextAsBuffer()) {
qCDebug(qLcVideoFrameEncoder)
<< "Failed to create frames context for targetSwFormat" << targetSwFormat;
return false;
}
if (!initCodecContext())
return false;
if (!open())
return false;
cleanup.dismiss();
updateConversions();
return true;
}
VideoFrameEncoder::~VideoFrameEncoder() = default;
void VideoFrameEncoder::initStream()
{
m_stream->codecpar->codec_id = m_codec.id();
// Apples HEVC decoders don't like the hev1 tag ffmpeg uses by default, use hvc1 as the more
// commonly accepted tag
if (m_codec.id() == AV_CODEC_ID_HEVC)
m_stream->codecpar->codec_tag = MKTAG('h', 'v', 'c', '1');
else
m_stream->codecpar->codec_tag = 0;
// ### Fix hardcoded values
m_stream->codecpar->format = m_targetFormat;
m_stream->codecpar->width = m_targetSize.width();
m_stream->codecpar->height = m_targetSize.height();
m_stream->codecpar->sample_aspect_ratio = AVRational{ 1, 1 };
#if QT_CODEC_PARAMETERS_HAVE_FRAMERATE
// Some codecs (e.g. Windows Media Foundation encoders) require a concrete,
// non-zero frame rate
m_stream->codecpar->framerate = effectiveCodecFrameRate();
#endif
const auto frameRates = m_codec.frameRates();
const bool isFixedRate = m_codecFrameRate.den > 0 && m_codecFrameRate.num > 0;
m_stream->time_base = adjustFrameTimeBase(frameRates, effectiveCodecFrameRate(), isFixedRate);
}
bool VideoFrameEncoder::initCodecContext()
{
namespace views = QtMultimediaPrivate::views;
Q_ASSERT(m_stream->codecpar->codec_id);
m_codecContext.reset(avcodec_alloc_context3(m_codec.get()));
if (!m_codecContext) {
qWarning() << "Could not allocate codec context";
return false;
}
// copies format, size, color params, framerate
const int status = avcodec_parameters_to_context(m_codecContext.get(), m_stream->codecpar);
if (status < 0) {
qCWarning(qLcVideoFrameEncoder)
<< "Cannot set codec parameters; result:" << AVError(status);
return false;
}
#if !QT_CODEC_PARAMETERS_HAVE_FRAMERATE
m_codecContext->framerate = effectiveCodecFrameRate();
#endif
m_codecContext->time_base = m_stream->time_base;
qCDebug(qLcVideoFrameEncoder) << "codecContext time base" << m_codecContext->time_base.num
<< m_codecContext->time_base.den;
if (m_needsGlobalHeader)
m_codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
if (m_accel) {
const AVHWDeviceType deviceType = m_accel->deviceType();
auto hwConfigs = m_codec.hwConfigs();
auto matchingConfigs = hwConfigs | views::filter([&](const AVCodecHWConfig *cfg) {
return cfg->device_type == deviceType;
});
bool supportsDeviceCtx = false;
bool supportsFramesCtx = false;
for (const AVCodecHWConfig *cfg : matchingConfigs) {
if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
supportsDeviceCtx = true;
if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX)
supportsFramesCtx = true;
}
if (supportsDeviceCtx) {
auto deviceContext = m_accel->hwDeviceContextAsBuffer();
Q_ASSERT(deviceContext);
m_codecContext->hw_device_ctx = av_buffer_ref(deviceContext);
}
if (supportsFramesCtx)
if (auto framesContext = m_accel->hwFramesContextAsBuffer())
m_codecContext->hw_frames_ctx = av_buffer_ref(framesContext);
}
return true;
}
bool VideoFrameEncoder::open()
{
Q_ASSERT(m_codecContext);
AVDictionaryHolder opts;
applyVideoEncoderOptions(m_settings, m_codec.name(), m_codecContext.get(), opts);
applyExperimentalCodecOptions(m_codec, opts);
qCDebug(qLcVideoFrameEncoder) << "Opening encoder" << m_codec.name() << "with" << opts;
int res = avcodec_open2(m_codecContext.get(), m_codec.get(), opts);
if (res < 0) {
qCWarning(qLcVideoFrameEncoder)
<< "Couldn't open video encoder" << m_codec.name() << "; result:" << AVError(res);
return false;
}
qCDebug(qLcVideoFrameEncoder) << "video codec opened" << res << "time base"
<< m_codecContext->time_base;
res = avcodec_parameters_from_context(m_stream->codecpar, m_codecContext.get());
if (res < 0) {
qCWarning(qLcVideoFrameEncoder) << "Cannot get parameters from video codec context"
<< m_codec.name() << "; result:" << AVError(res);
return false;
}
return true;
}
qreal VideoFrameEncoder::codecFrameRate() const
{
return m_codecFrameRate.den ? qreal(m_codecFrameRate.num) / m_codecFrameRate.den : 0.;
}
qint64 VideoFrameEncoder::getPts(qint64 us) const
{
qint64 div = 1'000'000 * m_stream->time_base.num;
return div != 0 ? (us * m_stream->time_base.den + div / 2) / div : 0;
}
const AVRational &VideoFrameEncoder::getTimeBase() const
{
return m_stream->time_base;
}
namespace {
struct FrameConverter
{
FrameConverter(AVFrameUPtr inputFrame) : m_inputFrame{ std::move(inputFrame) } { }
int downloadFromHw()
{
AVFrameUPtr cpuFrame = makeAVFrame();
int err = av_hwframe_transfer_data(cpuFrame.get(), currentFrame(), 0);
if (err < 0) {
qCDebug(qLcVideoFrameEncoder)
<< "Error transferring frame data to surface." << AVError(err);
return err;
}
setFrame(std::move(cpuFrame));
return 0;
}
void convert(SwsContext *scaleContext, AVPixelFormat format, const QSize &size)
{
AVFrameUPtr scaledFrame = makeAVFrame();
scaledFrame->format = format;
scaledFrame->width = size.width();
scaledFrame->height = size.height();
const int status = av_frame_get_buffer(scaledFrame.get(), 0);
if (status < 0) {
qCWarning(qLcVideoFrameEncoder)
<< "Failed to allocate scaled frame buffer:" << AVError(status);
return;
}
const AVFrame *srcFrame = currentFrame();
const auto scaledHeight =
sws_scale(scaleContext, srcFrame->data, srcFrame->linesize, 0, srcFrame->height,
scaledFrame->data, scaledFrame->linesize);
if (scaledHeight != scaledFrame->height)
qCWarning(qLcVideoFrameEncoder)
<< "Scaled height" << scaledHeight << "!=" << scaledFrame->height;
setFrame(std::move(scaledFrame));
}
int uploadToHw(HWAccel *accel)
{
auto *hwFramesContext = accel->hwFramesContextAsBuffer();
Q_ASSERT(hwFramesContext);
AVFrameUPtr hwFrame = makeAVFrame();
if (!hwFrame)
return AVERROR(ENOMEM);
int err = av_hwframe_get_buffer(hwFramesContext, hwFrame.get(), 0);
if (err < 0) {
qCDebug(qLcVideoFrameEncoder) << "Error getting HW buffer" << AVError(err);
return err;
} else {
qCDebug(qLcVideoFrameEncoder) << "got HW buffer";
}
if (!hwFrame->hw_frames_ctx) {
qCDebug(qLcVideoFrameEncoder) << "no hw frames context";
return AVERROR(ENOMEM);
}
err = av_hwframe_transfer_data(hwFrame.get(), currentFrame(), 0);
if (err < 0) {
qCDebug(qLcVideoFrameEncoder)
<< "Error transferring frame data to surface." << AVError(err);
return err;
}
setFrame(std::move(hwFrame));
return 0;
}
q23::expected<AVFrameUPtr, int> takeResultFrame()
{
// Ensure that object is reset to empty state
AVFrameUPtr converted = std::move(m_convertedFrame);
AVFrameUPtr input = std::move(m_inputFrame);
if (!converted)
return input;
// Copy metadata except size and format from input frame
const int status = av_frame_copy_props(converted.get(), input.get());
if (status != 0)
return q23::unexpected{ status };
return converted;
}
private:
void setFrame(AVFrameUPtr frame) { m_convertedFrame = std::move(frame); }
AVFrame *currentFrame() const
{
if (m_convertedFrame)
return m_convertedFrame.get();
return m_inputFrame.get();
}
AVFrameUPtr m_inputFrame;
AVFrameUPtr m_convertedFrame;
};
} // namespace
int VideoFrameEncoder::sendFrame(AVFrameUPtr inputFrame)
{
if (!m_codecContext) {
qWarning() << "codec context is not initialized!";
return AVERROR(EINVAL);
}
if (!inputFrame)
return avcodec_send_frame(m_codecContext.get(), nullptr); // Flush
if (!updateSourceFormatAndSize(inputFrame.get()))
return AVERROR(EINVAL);
// some codecs require quality to be set on each frame and ignore global_quality
inputFrame->quality = m_codecContext->global_quality;
FrameConverter converter{ std::move(inputFrame) };
if (m_downloadFromHW) {
const int status = converter.downloadFromHw();
if (status != 0)
return status;
}
if (m_scaleContext)
converter.convert(m_scaleContext.get(), m_targetSWFormat, m_targetSize);
if (m_uploadToHW) {
const int status = converter.uploadToHw(m_accel.get());
if (status != 0)
return status;
}
const q23::expected<AVFrameUPtr, int> resultFrame = converter.takeResultFrame();
if (!resultFrame)
return resultFrame.error();
AVRational timeBase{};
int64_t pts{};
getAVFrameTime(*resultFrame.value(), pts, timeBase);
qCDebug(qLcVideoFrameEncoder) << "sending frame" << pts << "*" << timeBase;
return avcodec_send_frame(m_codecContext.get(), resultFrame.value().get());
}
qint64 VideoFrameEncoder::estimateDuration(const AVPacket &packet, bool isFirstPacket)
{
qint64 duration = 0; // In stream units, multiply by time_base to get seconds
if (isFirstPacket) {
// First packet - Estimate duration from frame rate. Duration must
// be set for single-frame videos, otherwise they won't open in
// media player. The codec frame rate is 0 for variable-rate sources,
// so fall back to a default rate to avoid dividing by zero.
AVRational frameRate = m_codecContext->framerate;
if (frameRate.num <= 0)
frameRate = AVRational{ DefaultVideoFrameRate, 1 };
const AVRational frameDuration = av_inv_q(frameRate);
duration = av_rescale_q(1, frameDuration, m_stream->time_base);
} else {
// Duration is calculated from actual packet times. TODO: Handle discontinuities
duration = packet.pts - m_lastPacketTime;
}
return duration;
}
AVPacketUPtr VideoFrameEncoder::retrievePacket()
{
if (!m_codecContext)
return nullptr;
auto getPacket = [&]() {
AVPacketUPtr packet(av_packet_alloc());
const int ret = avcodec_receive_packet(m_codecContext.get(), packet.get());
if (ret < 0) {
if (ret != AVERROR(EOF) && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
qCDebug(qLcVideoFrameEncoder) << "Error receiving packet" << ret << AVError(ret);
return AVPacketUPtr{};
}
auto ts = timeStampMs(packet->pts, m_stream->time_base);
qCDebug(qLcVideoFrameEncoder)
<< "got a packet" << packet->pts << packet->dts << (ts ? *ts : 0);
packet->stream_index = m_stream->id;
if (packet->duration == 0) {
const bool firstFrame = m_lastPacketTime == AV_NOPTS_VALUE;
packet->duration = estimateDuration(*packet, firstFrame);
}
m_lastPacketTime = packet->pts;
return packet;
};
auto fixPacketDts = [&](AVPacket &packet) {
// Workaround for some ffmpeg codecs bugs (e.g. nvenc)
// Ideally, packet->pts < packet->dts is not expected
if (packet.dts == AV_NOPTS_VALUE)
return true;
packet.dts -= m_packetDtsOffset;
if (packet.pts != AV_NOPTS_VALUE && packet.pts < packet.dts) {
m_packetDtsOffset += packet.dts - packet.pts;
packet.dts = packet.pts;
if (m_prevPacketDts != AV_NOPTS_VALUE && packet.dts < m_prevPacketDts) {
qCWarning(qLcVideoFrameEncoder)
<< "Skip packet; failed to fix dts:" << packet.dts << m_prevPacketDts;
return false;
}
}
m_prevPacketDts = packet.dts;
return true;
};
while (auto packet = getPacket()) {
if (fixPacketDts(*packet))
return packet;
}
return nullptr;
}
bool VideoFrameEncoder::updateSourceFormatAndSize(const AVFrame *frame)
{
Q_ASSERT(frame);
const QSize size = frameSize(frame);
const AVPixelFormat frameFormat = static_cast<AVPixelFormat>(frame->format);
if (size == m_sourceSize && frameFormat == m_sourceFormat)
return true;
auto applySourceFormatAndSize = [&](AVPixelFormat swFormat) {
m_sourceSize = size;
m_sourceFormat = frameFormat;
m_sourceSWFormat = swFormat;
updateConversions();
return true;
};
if (frameFormat == m_sourceFormat)
return applySourceFormatAndSize(m_sourceSWFormat);
if (frameFormat == AV_PIX_FMT_NONE) {
qWarning() << "Got a frame with invalid pixel format";
return false;
}
if (isSwPixelFormat(frameFormat))
return applySourceFormatAndSize(frameFormat);
auto framesCtx = reinterpret_cast<const AVHWFramesContext *>(frame->hw_frames_ctx->data);
if (!framesCtx || framesCtx->sw_format == AV_PIX_FMT_NONE) {
qWarning() << "Cannot update conversions as hw frame has invalid framesCtx" << framesCtx;
return false;
}
return applySourceFormatAndSize(framesCtx->sw_format);
}
void VideoFrameEncoder::updateConversions()
{
const bool needToScale = m_sourceSize != m_targetSize;
const bool zeroCopy = m_sourceFormat == m_targetFormat && !needToScale;
m_scaleContext.reset();
if (zeroCopy) {
m_downloadFromHW = false;
m_uploadToHW = false;
qCDebug(qLcVideoFrameEncoder) << "zero copy encoding, format" << m_targetFormat;
// no need to initialize any converters
return;
}
m_downloadFromHW = m_sourceFormat != m_sourceSWFormat;
m_uploadToHW = m_targetFormat != m_targetSWFormat;
if (m_sourceSWFormat != m_targetSWFormat || needToScale) {
qCDebug(qLcVideoFrameEncoder)
<< "video source and encoder use different formats:" << m_sourceSWFormat
<< m_targetSWFormat << "or sizes:" << m_sourceSize << m_targetSize;
const SwsFlags conversionType = getScaleConversionType(m_sourceSize, m_targetSize);
m_scaleContext = createSwsContext(m_sourceSize, m_sourceSWFormat, m_targetSize,
m_targetSWFormat, conversionType);
}
qCDebug(qLcVideoFrameEncoder) << "VideoFrameEncoder conversions initialized:"
<< "sourceFormat:" << m_sourceFormat
<< (isHwPixelFormat(m_sourceFormat) ? "(hw)" : "(sw)")
<< "targetFormat:" << m_targetFormat
<< (isHwPixelFormat(m_targetFormat) ? "(hw)" : "(sw)")
<< "sourceSWFormat:" << m_sourceSWFormat
<< "targetSWFormat:" << m_targetSWFormat
<< "scaleContext:" << m_scaleContext.get();
}
} // namespace QFFmpeg
QT_END_NAMESPACE