14#include <libavcodec/avcodec.h>
15#include <libavfilter/avfilter.h>
16#include <libavfilter/buffersink.h>
17#include <libavfilter/buffersrc.h>
18#include <libavutil/avutil.h>
19#include <libavutil/hwcontext.h>
20#include <libavutil/hwcontext_drm.h>
21#include <libavutil/imgutils.h>
24#include <libdrm/drm_fourcc.h>
26#include "vaapiutils_p.h"
28#include "logging_record.h"
32char str[AV_ERROR_MAX_STRING_SIZE];
33char *av_err2str(
int errnum)
35 return av_make_error_string(str, AV_ERROR_MAX_STRING_SIZE, errnum);
38static AVPixelFormat convertQImageFormatToAVPixelFormat(
QImage::Format format)
43 return AV_PIX_FMT_RGB24;
45 return AV_PIX_FMT_BGR24;
48 return AV_PIX_FMT_RGBA;
51 return AV_PIX_FMT_RGB32;
53 qDebug() <<
"Unexpected pixel format" << format;
54 return AV_PIX_FMT_RGB32;
58static int percentageToFrameQuality(quint8 quality)
60 return std::max(1,
int(FF_LAMBDA_MAX - (quality / 100.0) * FF_LAMBDA_MAX));
63Encoder::Encoder(PipeWireProduce *produce)
71 if (m_avFilterGraph) {
72 avfilter_graph_free(&m_avFilterGraph);
75 if (m_avCodecContext) {
76 avcodec_close(m_avCodecContext);
77 av_free(m_avCodecContext);
81std::pair<int, int> Encoder::encodeFrame(
int maximumFrames)
83 auto frame = av_frame_alloc();
85 qFatal(
"Failed to allocate memory");
92 if (
auto result = av_buffersink_get_frame(m_outputFilter, frame); result < 0) {
93 if (result != AVERROR_EOF && result != AVERROR(EAGAIN)) {
94 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed receiving filtered frame:" << av_err2str(result);
101 if (queued + 1 < maximumFrames) {
104 std::lock_guard guard(m_avCodecMutex);
105 ret = avcodec_send_frame(m_avCodecContext, frame);
108 if (ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) {
109 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Error sending a frame for encoding:" << av_err2str(ret);
115 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Encode queue is full, discarding filtered frame" << frame->pts;
117 av_frame_unref(frame);
120 av_frame_free(&frame);
122 return std::make_pair(filtered, queued);
125int Encoder::receivePacket()
127 auto packet = av_packet_alloc();
129 qFatal(
"Failed to allocate memory");
137 std::lock_guard guard(m_avCodecMutex);
138 ret = avcodec_receive_packet(m_avCodecContext, packet);
141 if (ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) {
142 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Error encoding a frame: " << av_err2str(ret);
144 av_packet_unref(packet);
150 m_produce->processPacket(packet);
151 av_packet_unref(packet);
154 av_packet_free(&packet);
159void Encoder::finish()
161 std::lock_guard guard(m_avCodecMutex);
162 avcodec_send_frame(m_avCodecContext,
nullptr);
165AVCodecContext *Encoder::avCodecContext()
const
167 return m_avCodecContext;
170void Encoder::setQuality(std::optional<quint8> quality)
173 if (m_avCodecContext) {
174 m_avCodecContext->global_quality = percentageToAbsoluteQuality(quality);
178bool Encoder::supportsHardwareEncoding()
180 return !VaapiUtils::instance()->devicePath().isEmpty();
183void Encoder::setEncodingPreference(PipeWireBaseEncodedStream::EncodingPreference preference)
185 m_encodingPreference = preference;
188void Encoder::applyEncodingPreference(AVDictionary *options)
190 switch (m_encodingPreference) {
191 case PipeWireBaseEncodedStream::EncodingPreference::NoPreference:
192 av_dict_set(&options,
"preset",
"veryfast", 0);
194 case PipeWireBaseEncodedStream::EncodingPreference::Quality:
195 av_dict_set(&options,
"preset",
"medium", 0);
197 case PipeWireBaseEncodedStream::EncodingPreference::Speed:
198 av_dict_set(&options,
"preset",
"ultrafast", 0);
199 av_dict_set(&options,
"tune",
"zerolatency", 0);
201 case PipeWireBaseEncodedStream::EncodingPreference::Size:
202 av_dict_set(&options,
"preset",
"slow", 0);
205 av_dict_set(&options,
"preset",
"veryfast", 0);
210SoftwareEncoder::SoftwareEncoder(PipeWireProduce *produce)
215bool SoftwareEncoder::filterFrame(
const PipeWireFrame &frame)
217 auto size = m_produce->m_stream->size();
222 if (!m_dmaBufHandler.downloadFrame(image, frame)) {
223 m_produce->m_stream->renegotiateModifierFailed(frame.format, frame.dmabuf->modifier);
226 }
else if (frame.dataFrame) {
227 image = frame.dataFrame->toImage();
232 AVFrame *avFrame = av_frame_alloc();
234 qFatal(
"Failed to allocate memory");
236 avFrame->format = convertQImageFormatToAVPixelFormat(image.
format());
237 avFrame->width = size.width();
238 avFrame->height = size.height();
240 avFrame->quality = percentageToFrameQuality(m_quality.value());
243 av_frame_get_buffer(avFrame, 32);
245 const std::uint8_t *buffers[] = {image.
constBits(),
nullptr};
246 const int strides[] = {
static_cast<int>(image.
bytesPerLine()), 0, 0, 0};
248 av_image_copy(avFrame->data, avFrame->linesize, buffers, strides,
static_cast<AVPixelFormat
>(avFrame->format), size.width(), size.height());
250 if (frame.presentationTimestamp) {
251 avFrame->pts = m_produce->framePts(frame.presentationTimestamp);
254 if (
auto result = av_buffersrc_add_frame(m_inputFilter, avFrame); result < 0) {
255 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed to submit frame for filtering";
261bool SoftwareEncoder::createFilterGraph(
const QSize &size)
263 m_avFilterGraph = avfilter_graph_alloc();
264 if (!m_avFilterGraph) {
265 qFatal(
"Failed to allocate memory");
268 int ret = avfilter_graph_create_filter(&m_inputFilter,
269 avfilter_get_by_name(
"buffer"),
271 "width=1:height=1:pix_fmt=rgba:time_base=1/1",
275 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed to create the buffer filter";
279 auto parameters = av_buffersrc_parameters_alloc();
281 qFatal(
"Failed to allocate memory");
284 parameters->format = AV_PIX_FMT_RGBA;
285 parameters->width = size.
width();
286 parameters->height = size.
height();
287 parameters->time_base = {1, 1000};
289 av_buffersrc_parameters_set(m_inputFilter, parameters);
291 parameters =
nullptr;
293 ret = avfilter_graph_create_filter(&m_outputFilter, avfilter_get_by_name(
"buffersink"),
"out",
nullptr,
nullptr, m_avFilterGraph);
295 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Could not create buffer output filter";
299 auto inputs = avfilter_inout_alloc();
301 qFatal(
"Failed to allocate memory");
303 inputs->name = av_strdup(
"in");
304 inputs->filter_ctx = m_inputFilter;
306 inputs->next =
nullptr;
308 auto outputs = avfilter_inout_alloc();
310 qFatal(
"Failed to allocate memory");
312 outputs->name = av_strdup(
"out");
313 outputs->filter_ctx = m_outputFilter;
314 outputs->pad_idx = 0;
315 outputs->next =
nullptr;
317 ret = avfilter_graph_parse(m_avFilterGraph, m_filterGraphToParse.toUtf8().data(), outputs, inputs, NULL);
319 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed creating filter graph";
323 ret = avfilter_graph_config(m_avFilterGraph,
nullptr);
325 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed configuring filter graph";
332HardwareEncoder::HardwareEncoder(PipeWireProduce *produce)
337HardwareEncoder::~HardwareEncoder()
339 if (m_drmFramesContext) {
340 av_free(m_drmFramesContext);
344 av_free(m_drmContext);
348bool HardwareEncoder::filterFrame(
const PipeWireFrame &frame)
354 auto attribs = frame.dmabuf.value();
356 auto drmFrame = av_frame_alloc();
358 qFatal(
"Failed to allocate memory");
360 drmFrame->format = AV_PIX_FMT_DRM_PRIME;
361 drmFrame->width = attribs.width;
362 drmFrame->height = attribs.height;
364 drmFrame->quality = percentageToFrameQuality(m_quality.value());
367 auto frameDesc =
new AVDRMFrameDescriptor;
368 frameDesc->nb_layers = 1;
369 frameDesc->layers[0].nb_planes = attribs.planes.count();
370 frameDesc->layers[0].format = attribs.format;
371 for (
int i = 0; i < attribs.planes.count(); ++i) {
372 const auto &plane = attribs.planes[i];
373 frameDesc->layers[0].planes[i].object_index = 0;
374 frameDesc->layers[0].planes[i].offset = plane.offset;
375 frameDesc->layers[0].planes[i].pitch = plane.stride;
377 frameDesc->nb_objects = 1;
378 frameDesc->objects[0].fd = attribs.planes[0].fd;
379 frameDesc->objects[0].format_modifier = attribs.modifier;
380 frameDesc->objects[0].size = attribs.width * attribs.height * 4;
382 drmFrame->data[0] =
reinterpret_cast<uint8_t *
>(frameDesc);
383 drmFrame->buf[0] = av_buffer_create(
reinterpret_cast<uint8_t *
>(frameDesc),
sizeof(*frameDesc), av_buffer_default_free,
nullptr, 0);
384 if (frame.presentationTimestamp) {
385 drmFrame->pts = m_produce->framePts(frame.presentationTimestamp);
388 if (
auto result = av_buffersrc_add_frame(m_inputFilter, drmFrame); result < 0) {
389 qCDebug(PIPEWIRERECORD_LOGGING) <<
"Failed sending frame for encoding" << av_err2str(result);
390 av_frame_unref(drmFrame);
394 av_frame_free(&drmFrame);
400 auto utils = VaapiUtils::instance();
401 if (utils->devicePath().isEmpty()) {
402 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Hardware encoding is not supported on this device.";
406 auto minSize = utils->minimumSize();
407 if (size.
width() < minSize.width() || size.
height() < minSize.height()) {
408 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Requested size" << size <<
"less than minimum supported hardware size" << minSize;
412 auto maxSize = utils->maximumSize();
413 if (size.
width() > maxSize.width() || size.
height() > maxSize.height()) {
414 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Requested size" << size <<
"exceeds maximum supported hardware size" << maxSize;
418 return utils->devicePath();
421bool HardwareEncoder::createDrmContext(
const QSize &size)
423 auto path = checkVaapi(size);
428 int err = av_hwdevice_ctx_create(&m_drmContext, AV_HWDEVICE_TYPE_DRM,
path.
data(), NULL, AV_HWFRAME_MAP_READ);
430 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed to create DRM device. Error" << av_err2str(err);
434 m_drmFramesContext = av_hwframe_ctx_alloc(m_drmContext);
435 if (!m_drmFramesContext) {
436 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed to create DRM frames context";
440 auto framesContext =
reinterpret_cast<AVHWFramesContext *
>(m_drmFramesContext->data);
441 framesContext->format = AV_PIX_FMT_DRM_PRIME;
442 framesContext->sw_format = AV_PIX_FMT_0BGR;
443 framesContext->width = size.
width();
444 framesContext->height = size.
height();
446 if (
auto result = av_hwframe_ctx_init(m_drmFramesContext); result < 0) {
447 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed initializing DRM frames context" << av_err2str(result);
448 av_buffer_unref(&m_drmFramesContext);
455#include "moc_encoder_p.cpp"
QString path(const QString &relativePath)
qsizetype bytesPerLine() const const
const uchar * constBits() const const
bool isEmpty() const const
QFuture< typename qValueType< Iterator >::value_type > filtered(Iterator begin, Iterator end, KeepFunctor &&filterFunction)