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_free_context(&m_avCodecContext);
80std::pair<int, int> Encoder::encodeFrame(
int maximumFrames)
82 auto frame = av_frame_alloc();
84 qFatal(
"Failed to allocate memory");
91 if (
auto result = av_buffersink_get_frame(m_outputFilter, frame); result < 0) {
92 if (result != AVERROR_EOF && result != AVERROR(EAGAIN)) {
93 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed receiving filtered frame:" << av_err2str(result);
100 if (queued + 1 < maximumFrames) {
103 std::lock_guard guard(m_avCodecMutex);
104 ret = avcodec_send_frame(m_avCodecContext, frame);
107 if (ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) {
108 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Error sending a frame for encoding:" << av_err2str(ret);
114 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Encode queue is full, discarding filtered frame" << frame->pts;
116 av_frame_unref(frame);
119 av_frame_free(&frame);
121 return std::make_pair(filtered, queued);
124int Encoder::receivePacket()
126 auto packet = av_packet_alloc();
128 qFatal(
"Failed to allocate memory");
136 std::lock_guard guard(m_avCodecMutex);
137 ret = avcodec_receive_packet(m_avCodecContext, packet);
140 if (ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) {
141 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Error encoding a frame: " << av_err2str(ret);
143 av_packet_unref(packet);
149 m_produce->processPacket(packet);
150 av_packet_unref(packet);
153 av_packet_free(&packet);
158void Encoder::finish()
160 std::lock_guard guard(m_avCodecMutex);
161 avcodec_send_frame(m_avCodecContext,
nullptr);
164AVCodecContext *Encoder::avCodecContext()
const
166 return m_avCodecContext;
169void Encoder::setQuality(std::optional<quint8> quality)
172 if (m_avCodecContext) {
173 m_avCodecContext->global_quality = percentageToAbsoluteQuality(quality);
177bool Encoder::supportsHardwareEncoding()
179 return !VaapiUtils::instance()->devicePath().isEmpty();
182void Encoder::setEncodingPreference(PipeWireBaseEncodedStream::EncodingPreference preference)
184 m_encodingPreference = preference;
187AVDictionary *Encoder::buildEncodingOptions()
189 AVDictionary *options = NULL;
193 switch (m_encodingPreference) {
194 case PipeWireBaseEncodedStream::EncodingPreference::NoPreference:
195 av_dict_set(&options,
"preset",
"veryfast", 0);
197 case PipeWireBaseEncodedStream::EncodingPreference::Quality:
198 av_dict_set(&options,
"preset",
"medium", 0);
200 case PipeWireBaseEncodedStream::EncodingPreference::Speed:
201 av_dict_set(&options,
"preset",
"ultrafast", 0);
202 av_dict_set(&options,
"tune",
"zerolatency", 0);
204 case PipeWireBaseEncodedStream::EncodingPreference::Size:
205 av_dict_set(&options,
"preset",
"slow", 0);
208 av_dict_set(&options,
"preset",
"veryfast", 0);
215void Encoder::maybeLogOptions(AVDictionary *options)
217 if (PIPEWIRERECORD_LOGGING().isInfoEnabled()) {
219 av_dict_get_string(options, &buffer,
'=',
',');
220 qCInfo(PIPEWIRERECORD_LOGGING) <<
"Using encoding options:" << buffer;
225SoftwareEncoder::SoftwareEncoder(PipeWireProduce *produce)
230bool SoftwareEncoder::filterFrame(
const PipeWireFrame &frame)
232 auto size = m_produce->m_stream->size();
237 if (!m_dmaBufHandler.downloadFrame(image, frame)) {
238 m_produce->m_stream->renegotiateModifierFailed(frame.format, frame.dmabuf->modifier);
241 }
else if (frame.dataFrame) {
242 image = frame.dataFrame->toImage();
247 AVFrame *avFrame = av_frame_alloc();
249 qFatal(
"Failed to allocate memory");
251 avFrame->format = convertQImageFormatToAVPixelFormat(image.
format());
252 avFrame->width = size.width();
253 avFrame->height = size.height();
255 avFrame->quality = percentageToFrameQuality(m_quality.value());
258 av_frame_get_buffer(avFrame, 32);
260 const std::uint8_t *buffers[] = {image.
constBits(),
nullptr};
261 const int strides[] = {
static_cast<int>(image.
bytesPerLine()), 0, 0, 0};
263 av_image_copy(avFrame->data, avFrame->linesize, buffers, strides,
static_cast<AVPixelFormat
>(avFrame->format), size.width(), size.height());
265 if (frame.presentationTimestamp) {
266 avFrame->pts = m_produce->framePts(frame.presentationTimestamp);
269 if (
auto result = av_buffersrc_add_frame(m_inputFilter, avFrame); result < 0) {
270 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed to submit frame for filtering";
276bool SoftwareEncoder::createFilterGraph(
const QSize &size)
278 m_avFilterGraph = avfilter_graph_alloc();
279 if (!m_avFilterGraph) {
280 qFatal(
"Failed to allocate memory");
283 int ret = avfilter_graph_create_filter(&m_inputFilter,
284 avfilter_get_by_name(
"buffer"),
286 "width=1:height=1:pix_fmt=rgba:time_base=1/1",
290 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed to create the buffer filter";
294 auto parameters = av_buffersrc_parameters_alloc();
296 qFatal(
"Failed to allocate memory");
299 parameters->format = AV_PIX_FMT_RGBA;
300 parameters->width = size.
width();
301 parameters->height = size.
height();
302 parameters->time_base = {1, 1000};
304 av_buffersrc_parameters_set(m_inputFilter, parameters);
306 parameters =
nullptr;
308 ret = avfilter_graph_create_filter(&m_outputFilter, avfilter_get_by_name(
"buffersink"),
"out",
nullptr,
nullptr, m_avFilterGraph);
310 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Could not create buffer output filter";
314 auto inputs = avfilter_inout_alloc();
316 qFatal(
"Failed to allocate memory");
318 inputs->name = av_strdup(
"in");
319 inputs->filter_ctx = m_inputFilter;
321 inputs->next =
nullptr;
323 auto outputs = avfilter_inout_alloc();
325 qFatal(
"Failed to allocate memory");
327 outputs->name = av_strdup(
"out");
328 outputs->filter_ctx = m_outputFilter;
329 outputs->pad_idx = 0;
330 outputs->next =
nullptr;
332 ret = avfilter_graph_parse(m_avFilterGraph, m_filterGraphToParse.toUtf8().data(), outputs, inputs, NULL);
334 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed creating filter graph";
338 ret = avfilter_graph_config(m_avFilterGraph,
nullptr);
340 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed configuring filter graph";
347HardwareEncoder::HardwareEncoder(PipeWireProduce *produce)
352HardwareEncoder::~HardwareEncoder()
354 if (m_drmFramesContext) {
355 av_free(m_drmFramesContext);
359 av_free(m_drmContext);
363bool HardwareEncoder::filterFrame(
const PipeWireFrame &frame)
369 auto attribs = frame.dmabuf.value();
371 auto drmFrame = av_frame_alloc();
373 qFatal(
"Failed to allocate memory");
375 drmFrame->format = AV_PIX_FMT_DRM_PRIME;
376 drmFrame->width = attribs.width;
377 drmFrame->height = attribs.height;
379 drmFrame->quality = percentageToFrameQuality(m_quality.value());
382 AVDRMFrameDescriptor *frameDesc = (AVDRMFrameDescriptor *)av_mallocz(
sizeof(AVDRMFrameDescriptor));
383 frameDesc->nb_layers = 1;
384 frameDesc->layers[0].nb_planes = attribs.planes.count();
385 frameDesc->layers[0].format = attribs.format;
386 for (
int i = 0; i < attribs.planes.count(); ++i) {
387 const auto &plane = attribs.planes[i];
388 frameDesc->layers[0].planes[i].object_index = 0;
389 frameDesc->layers[0].planes[i].offset = plane.offset;
390 frameDesc->layers[0].planes[i].pitch = plane.stride;
392 frameDesc->nb_objects = 1;
393 frameDesc->objects[0].fd = attribs.planes[0].fd;
394 frameDesc->objects[0].format_modifier = attribs.modifier;
395 frameDesc->objects[0].size = attribs.width * attribs.height * 4;
397 drmFrame->data[0] =
reinterpret_cast<uint8_t *
>(frameDesc);
398 drmFrame->buf[0] = av_buffer_create(
reinterpret_cast<uint8_t *
>(frameDesc),
sizeof(*frameDesc), av_buffer_default_free,
nullptr, 0);
399 if (frame.presentationTimestamp) {
400 drmFrame->pts = m_produce->framePts(frame.presentationTimestamp);
403 if (
auto result = av_buffersrc_add_frame(m_inputFilter, drmFrame); result < 0) {
404 qCDebug(PIPEWIRERECORD_LOGGING) <<
"Failed sending frame for encoding" << av_err2str(result);
405 av_frame_unref(drmFrame);
409 av_frame_free(&drmFrame);
415 auto utils = VaapiUtils::instance();
416 if (utils->devicePath().isEmpty()) {
417 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Hardware encoding is not supported on this device.";
421 auto minSize = utils->minimumSize();
422 if (size.
width() < minSize.width() || size.
height() < minSize.height()) {
423 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Requested size" << size <<
"less than minimum supported hardware size" << minSize;
427 auto maxSize = utils->maximumSize();
428 if (size.
width() > maxSize.width() || size.
height() > maxSize.height()) {
429 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Requested size" << size <<
"exceeds maximum supported hardware size" << maxSize;
433 return utils->devicePath();
436bool HardwareEncoder::createDrmContext(
const QSize &size)
438 auto path = checkVaapi(size);
443 int err = av_hwdevice_ctx_create(&m_drmContext, AV_HWDEVICE_TYPE_DRM,
path.
data(), NULL, AV_HWFRAME_MAP_READ);
445 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed to create DRM device. Error" << av_err2str(err);
449 m_drmFramesContext = av_hwframe_ctx_alloc(m_drmContext);
450 if (!m_drmFramesContext) {
451 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed to create DRM frames context";
455 auto framesContext =
reinterpret_cast<AVHWFramesContext *
>(m_drmFramesContext->data);
456 framesContext->format = AV_PIX_FMT_DRM_PRIME;
457 framesContext->sw_format = AV_PIX_FMT_0BGR;
458 framesContext->width = size.
width();
459 framesContext->height = size.
height();
461 if (
auto result = av_hwframe_ctx_init(m_drmFramesContext); result < 0) {
462 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Failed initializing DRM frames context" << av_err2str(result);
463 av_buffer_unref(&m_drmFramesContext);
470#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)