9#include "private/qplatformmediarecorder_p.h"
10#include "private/qmultimediautils_p.h"
19VideoFrameEncoder::Data::~Data()
22 sws_freeContext(converter);
27 AVPixelFormat sourceFormat, AVPixelFormat sourceSWFormat)
30 d->settings = encoderSettings;
32 d->sourceSize = sourceSize;
34 if (!
d->settings.videoResolution().isValid())
35 d->settings.setVideoResolution(
d->sourceSize);
37 d->sourceFormat = sourceFormat;
38 d->sourceSWFormat = sourceSWFormat;
40 Q_ASSERT(isSwPixelFormat(sourceSWFormat));
42 const auto qVideoCodec = encoderSettings.
videoCodec();
45 std::tie(
d->codec,
d->accel) = findHwEncoder(codecID, sourceSize);
48 d->codec = findSwEncoder(codecID, sourceFormat, sourceSWFormat);
51 qWarning() <<
"Could not find encoder for codecId" << codecID;
56 qCDebug(qLcVideoFrameEncoder) <<
"found encoder" <<
d->codec->name <<
"for id" <<
d->codec->id;
58 d->targetFormat = findTargetFormat(sourceFormat, sourceSWFormat,
d->codec,
d->accel.get());
60 if (
d->targetFormat == AV_PIX_FMT_NONE) {
61 qWarning() <<
"Could not find target format for codecId" << codecID;
66 const bool needToScale =
d->sourceSize !=
d->settings.videoResolution();
67 const bool zeroCopy =
d->sourceFormat ==
d->targetFormat && !needToScale;
70 qCDebug(qLcVideoFrameEncoder) <<
"zero copy encoding, format" <<
d->targetFormat;
75 if (isHwPixelFormat(
d->sourceFormat))
76 d->downloadFromHW =
true;
78 d->sourceSWFormat =
d->sourceFormat;
80 if (isHwPixelFormat(
d->targetFormat)) {
84 d->targetSWFormat = findTargetSWFormat(
d->sourceSWFormat, *
d->accel);
86 if (
d->targetSWFormat == AV_PIX_FMT_NONE) {
87 qWarning() <<
"Cannot find software target format. sourceSWFormat:" <<
d->sourceSWFormat
88 <<
"targetFormat:" <<
d->targetFormat;
94 <<
"using sw format" <<
d->targetSWFormat <<
"as transfer format.";
97 d->accel->createFramesContext(
d->targetSWFormat,
d->settings.videoResolution());
99 d->targetSWFormat =
d->targetFormat;
102 if (
d->sourceSWFormat !=
d->targetSWFormat || needToScale) {
103 const auto targetSize =
d->settings.videoResolution();
105 <<
"camera and encoder use different formats:" <<
d->sourceSWFormat
106 <<
d->targetSWFormat <<
"or sizes:" <<
d->sourceSize << targetSize;
109 sws_getContext(
d->sourceSize.width(),
d->sourceSize.height(),
d->sourceSWFormat,
110 targetSize.width(), targetSize.height(),
d->targetSWFormat,
111 SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
114 qCDebug(qLcVideoFrameEncoder) <<
"VideoFrameEncoder conversions initialized:"
115 <<
"sourceFormat:" <<
d->sourceFormat
116 << (isHwPixelFormat(
d->sourceFormat) ?
"(hw)" :
"(sw)")
117 <<
"targetFormat:" <<
d->targetFormat
118 << (isHwPixelFormat(
d->targetFormat) ?
"(hw)" :
"(sw)")
119 <<
"sourceSWFormat:" <<
d->sourceSWFormat
120 <<
"targetSWFormat:" <<
d->targetSWFormat;
123VideoFrameEncoder::~VideoFrameEncoder()
132 d->stream = avformat_new_stream(formatContext,
nullptr);
133 d->stream->id = formatContext->nb_streams - 1;
135 d->stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
136 d->stream->codecpar->codec_id = d->codec->id;
139 if (d->codec->id == AV_CODEC_ID_HEVC)
140 d->stream->codecpar->codec_tag = MKTAG(
'h',
'v',
'c',
'1');
143 d->stream->codecpar->format = d->targetFormat;
144 d->stream->codecpar->width = d->settings.videoResolution().width();
145 d->stream->codecpar->height = d->settings.videoResolution().height();
146 d->stream->codecpar->sample_aspect_ratio = AVRational{1, 1};
147 float requestedRate = d->frameRate;
148 constexpr int TimeScaleFactor = 1000;
149 d->stream->time_base = AVRational{ 1,
static_cast<int>(requestedRate * TimeScaleFactor) };
152 if (d->codec->supported_framerates) {
154 auto *best = d->codec->supported_framerates;
155 qCDebug(qLcVideoFrameEncoder) <<
"Finding fixed rate:";
156 for (
auto *
f = d->codec->supported_framerates;
f->num != 0;
f++) {
160 float d =
qAbs(*maybeRate - requestedRate);
161 qCDebug(qLcVideoFrameEncoder) <<
" " <<
f->num <<
f->den <<
d;
167 qCDebug(qLcVideoFrameEncoder) <<
"Fixed frame rate required. Requested:" << requestedRate <<
"Using:" << best->num <<
"/" << best->den;
168 d->stream->time_base = *best;
169 requestedRate =
toFloat(*best).value_or(0.f);
173 d->codecContext.
reset(avcodec_alloc_context3(d->codec));
174 if (!d->codecContext) {
175 qWarning() <<
"Could not allocate codec context";
180 avcodec_parameters_to_context(d->codecContext.
get(), d->stream->codecpar);
181 d->codecContext->time_base = d->stream->time_base;
182 qCDebug(qLcVideoFrameEncoder) <<
"requesting time base" << d->codecContext->time_base.num << d->codecContext->time_base.den;
184 d->codecContext->framerate = {
num, den };
186 auto deviceContext = d->accel->hwDeviceContextAsBuffer();
188 d->codecContext->hw_device_ctx = av_buffer_ref(deviceContext);
189 auto framesContext = d->accel->hwFramesContextAsBuffer();
191 d->codecContext->hw_frames_ctx = av_buffer_ref(framesContext);
198 qWarning() <<
"Cannot open null VideoFrameEncoder";
201 AVDictionaryHolder opts;
203 int res = avcodec_open2(d->codecContext.
get(), d->codec, opts);
205 d->codecContext.
reset();
209 qCDebug(qLcVideoFrameEncoder) <<
"video codec opened" <<
res <<
"time base" << d->codecContext->time_base.num << d->codecContext->time_base.den;
210 d->stream->time_base = d->stream->time_base;
217 qint64 div = 1'000'000 * d->stream->time_base.num;
218 return div != 0 ? (us * d->stream->time_base.den + div / 2) / div : 0;
223 return d->stream->time_base;
228 if (!d->codecContext) {
229 qWarning() <<
"codec context is not initialized!";
230 return AVERROR(EINVAL);
234 return avcodec_send_frame(d->codecContext.
get(),
frame.get());
237 AVRational timeBase = {};
240 if (d->downloadFromHW) {
243 int err = av_hwframe_transfer_data(
f.get(),
frame.get(), 0);
245 qCDebug(qLcVideoFrameEncoder) <<
"Error transferring frame data to surface." <<
err2str(err);
255 f->format = d->targetSWFormat;
256 f->width = d->settings.videoResolution().width();
257 f->height = d->settings.videoResolution().height();
259 av_frame_get_buffer(
f.get(), 0);
260 const auto scaledHeight = sws_scale(d->converter,
frame->data,
frame->linesize, 0,
263 if (scaledHeight !=
f->height)
264 qCWarning(qLcVideoFrameEncoder) <<
"Scaled height" << scaledHeight <<
"!=" <<
f->height;
275 return AVERROR(ENOMEM);
278 qCDebug(qLcVideoFrameEncoder) <<
"Error getting HW buffer" <<
err2str(err);
281 qCDebug(qLcVideoFrameEncoder) <<
"got HW buffer";
283 if (!
f->hw_frames_ctx) {
284 qCDebug(qLcVideoFrameEncoder) <<
"no hw frames context";
285 return AVERROR(ENOMEM);
287 err = av_hwframe_transfer_data(
f.get(),
frame.get(), 0);
289 qCDebug(qLcVideoFrameEncoder) <<
"Error transferring frame data to surface." <<
err2str(err);
295 qCDebug(qLcVideoFrameEncoder) <<
"sending frame" << pts <<
"*" << timeBase.num <<
"/"
299 return avcodec_send_frame(d->codecContext.
get(),
frame.get());
304 if (!d || !d->codecContext)
306 AVPacket *packet = av_packet_alloc();
307 int ret = avcodec_receive_packet(d->codecContext.
get(), packet);
309 av_packet_free(&packet);
310 if (
ret != AVERROR(EOF) &&
ret != AVERROR(EAGAIN) &&
ret != AVERROR_EOF)
314 auto ts =
timeStampMs(packet->pts, d->stream->time_base);
316 qCDebug(qLcVideoFrameEncoder) <<
"got a packet" << packet->pts << packet->dts << (ts ? *ts : 0);
318 if (packet->dts != AV_NOPTS_VALUE && packet->pts < packet->dts) {
320 packet->dts = AV_NOPTS_VALUE;
323 packet->stream_index = d->stream->id;
void reset(T *ptr=nullptr) noexcept
void initWithFormatContext(AVFormatContext *formatContext)
AVPacket * retrievePacket()
const AVRational & getTimeBase() const
qint64 getPts(qint64 ms) const
int sendFrame(AVFrameUPtr frame)
AVFrameUPtr makeAVFrame()
void getAVFrameTime(const AVFrame &frame, int64_t &pts, AVRational &timeBase)
void applyVideoEncoderOptions(const QMediaEncoderSettings &settings, const QByteArray &codecName, AVCodecContext *codec, AVDictionary **opts)
QString err2str(int errnum)
void setAVFrameTime(AVFrame &frame, int64_t pts, const AVRational &timeBase)
std::optional< qint64 > timeStampMs(qint64 ts, AVRational base)
std::optional< float > toFloat(AVRational r)
std::unique_ptr< AVFrame, AVDeleter< decltype(&av_frame_free), &av_frame_free > > AVFrameUPtr
Combined button and popup list for selecting options.
AVBufferRef * hwFramesContext
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr T qAbs(const T &t)