Ring Daemon 16.0.0
Loading...
Searching...
No Matches
accel.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include <algorithm>
19
20#ifdef HAVE_CONFIG_H
21#include "config.h"
22#endif
23
24#include "media_buffer.h"
25#include "string_utils.h"
26#include "fileutils.h"
27#include "logger.h"
28#include "accel.h"
29
30namespace jami {
31namespace video {
32
34{
35 std::string name;
39 std::vector<AVCodecID> supportedCodecs;
40 std::list<std::pair<std::string, DeviceState>> possible_devices;
42};
43
44
83
84static std::list<HardwareAPI> apiListEnc = {
85 {"nvenc",
91 true},
92 {"vaapi",
97 {{"default", DeviceState::NOT_TESTED}, {"/dev/dri/renderD128", DeviceState::NOT_TESTED},
98 {"/dev/dri/renderD129", DeviceState::NOT_TESTED},
100 false},
101 {"videotoolbox",
106 {{"default", DeviceState::NOT_TESTED}},
107 false},
108 // Disable temporarily QSVENC
109 // {"qsv",
110 // AV_HWDEVICE_TYPE_QSV,
111 // AV_PIX_FMT_QSV,
112 // AV_PIX_FMT_NV12,
113 // {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8},
114 // {{"default", DeviceState::NOT_TESTED}},
115 // false},
116};
117
119 const std::string& name,
120 AVHWDeviceType hwType,
121 AVPixelFormat format,
122 AVPixelFormat swFormat,
123 CodecType type,
124 bool dynBitrate)
125 : id_(id)
126 , name_(name)
127 , hwType_(hwType)
128 , format_(format)
129 , swFormat_(swFormat)
130 , type_(type)
131 , dynBitrate_(dynBitrate)
132{}
133
135{
136 if (deviceCtx_)
137 av_buffer_unref(&deviceCtx_);
138 if (framesCtx_)
139 av_buffer_unref(&framesCtx_);
140}
141
142static AVPixelFormat
144{
145 auto accel = static_cast<HardwareAccel*>(codecCtx->opaque);
146
147 for (int i = 0; formats[i] != AV_PIX_FMT_NONE; ++i) {
148 if (accel && formats[i] == accel->getFormat()) {
149 // found hardware format for codec with api
150 JAMI_DBG() << "Found compatible hardware format for "
151 << avcodec_get_name(static_cast<AVCodecID>(accel->getCodecId()))
152 << " decoder with " << accel->getName();
153 // hardware tends to under-report supported levels
154 codecCtx->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
155 return formats[i];
156 }
157 }
158 return AV_PIX_FMT_NONE;
159}
160
161int
162HardwareAccel::init_device(const char* name, const char* device, int flags)
163{
164 const AVHWDeviceContext* dev = nullptr;
165
166 // Create device ctx
167 int err;
168 err = av_hwdevice_ctx_create(&deviceCtx_, hwType_, device, NULL, flags);
169 if (err < 0) {
170 JAMI_DBG("Failed to create %s device: %d.\n", name, err);
171 return 1;
172 }
173
174 // Verify that the device create correspond to api
175 dev = (AVHWDeviceContext*) deviceCtx_->data;
176 if (dev->type != hwType_) {
177 JAMI_DBG("Device created as type %d has type %d.", hwType_, dev->type);
178 av_buffer_unref(&deviceCtx_);
179 return -1;
180 }
181 JAMI_DBG("Device type %s successfully created.", name);
182
183 return 0;
184}
185
186int
187HardwareAccel::init_device_type(std::string& dev)
188{
190 const char* name;
191 int err;
192
193 name = av_hwdevice_get_type_name(hwType_);
194 if (!name) {
195 JAMI_DBG("No name available for device type %d.", hwType_);
196 return -1;
197 }
198
200 if (check != hwType_) {
201 JAMI_DBG("Type %d maps to name %s maps to type %d.", hwType_, name, check);
202 return -1;
203 }
204
205 JAMI_WARN("-- Starting %s init for %s with default device.",
206 (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
207 name);
208 if (possible_devices_->front().second != DeviceState::NOT_USABLE) {
209 if (name_ == "qsv")
210 err = init_device(name, "auto", 0);
211 else
212 err = init_device(name, nullptr, 0);
213 if (err == 0) {
214 JAMI_DBG("-- Init passed for %s with default device.", name);
215 possible_devices_->front().second = DeviceState::USABLE;
216 dev = "default";
217 return 0;
218 } else {
219 possible_devices_->front().second = DeviceState::NOT_USABLE;
220 JAMI_DBG("-- Init failed for %s with default device.", name);
221 }
222 }
223
224 for (auto& device : *possible_devices_) {
225 if (device.second == DeviceState::NOT_USABLE)
226 continue;
227 JAMI_WARN("-- Init %s for %s with device %s.",
228 (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
229 name,
230 device.first.c_str());
231 err = init_device(name, device.first.c_str(), 0);
232 if (err == 0) {
233 JAMI_DBG("-- Init passed for %s with device %s.", name, device.first.c_str());
234 device.second = DeviceState::USABLE;
235 dev = device.first;
236 return 0;
237 } else {
238 device.second = DeviceState::NOT_USABLE;
239 JAMI_DBG("-- Init failed for %s with device %s.", name, device.first.c_str());
240 }
241 }
242 return -1;
243}
244
245std::string
247{
248 if (type_ == CODEC_DECODER) {
249 return avcodec_get_name(id_);
250 } else if (type_ == CODEC_ENCODER) {
251 return fmt::format("{}_{}", avcodec_get_name(id_), name_);
252 }
253 return {};
254}
255
256std::unique_ptr<VideoFrame>
258{
259 int ret = 0;
260 if (type_ == CODEC_DECODER) {
261 auto input = frame.pointer();
262 if (input->format != format_) {
263 JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(format_)
264 << ", got "
265 << av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format));
266 return nullptr;
267 }
268
269 return transferToMainMemory(frame, swFormat_);
270 } else if (type_ == CODEC_ENCODER) {
271 auto input = frame.pointer();
272 if (input->format != swFormat_) {
273 JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(swFormat_)
274 << ", got "
275 << av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format));
276 return nullptr;
277 }
278
279 auto framePtr = std::make_unique<VideoFrame>();
280 auto hwFrame = framePtr->pointer();
281
282 if ((ret = av_hwframe_get_buffer(framesCtx_, hwFrame, 0)) < 0) {
283 JAMI_ERR() << "Failed to allocate hardware buffer: "
284 << libav_utils::getError(ret).c_str();
285 return nullptr;
286 }
287
288 if (!hwFrame->hw_frames_ctx) {
289 JAMI_ERR() << "Failed to allocate hardware buffer: Unable to allocate memory";
290 return nullptr;
291 }
292
293 if ((ret = av_hwframe_transfer_data(hwFrame, input, 0)) < 0) {
294 JAMI_ERR() << "Failed to push frame to GPU: " << libav_utils::getError(ret).c_str();
295 return nullptr;
296 }
297
298 hwFrame->pts = input->pts; // transfer does not copy timestamp
299 return framePtr;
300 } else {
301 JAMI_ERR() << "Invalid hardware accelerator";
302 return nullptr;
303 }
304}
305
306void
308{
309 if (type_ == CODEC_DECODER) {
310 codecCtx->hw_device_ctx = av_buffer_ref(deviceCtx_);
311 codecCtx->get_format = getFormatCb;
312 } else if (type_ == CODEC_ENCODER) {
313 if (framesCtx_)
314 // encoder doesn't need a device context, only a frame context
315 codecCtx->hw_frames_ctx = av_buffer_ref(framesCtx_);
316 }
317}
318
319bool
320HardwareAccel::initFrame()
321{
322 int ret = 0;
323 if (!deviceCtx_) {
324 JAMI_ERR() << "Unable to initialize hardware frames without a valid hardware device";
325 return false;
326 }
327
328 framesCtx_ = av_hwframe_ctx_alloc(deviceCtx_);
329 if (!framesCtx_)
330 return false;
331
332 auto ctx = reinterpret_cast<AVHWFramesContext*>(framesCtx_->data);
333 ctx->format = format_;
334 ctx->sw_format = swFormat_;
335 ctx->width = width_;
336 ctx->height = height_;
337 ctx->initial_pool_size = 20; // TODO try other values
338
339 if ((ret = av_hwframe_ctx_init(framesCtx_)) < 0) {
340 JAMI_ERR("Failed to initialize hardware frame context: %s (%d)",
342 ret);
343 av_buffer_unref(&framesCtx_);
344 }
345
346 return ret >= 0;
347}
348
349bool
351{
352 if (framesCtx) {
353 // Force sw_format to match swFormat_. Frame is never transferred to main
354 // memory when hardware is linked, so the sw_format doesn't matter.
355 auto hw = reinterpret_cast<AVHWFramesContext*>(framesCtx->data);
356 hw->sw_format = swFormat_;
357
358 if (framesCtx_)
359 av_buffer_unref(&framesCtx_);
360 framesCtx_ = av_buffer_ref(framesCtx);
361 if ((linked_ = (framesCtx_ != nullptr))) {
362 JAMI_DBG() << "Hardware transcoding pipeline successfully set up for"
363 << " encoder '" << getCodecName() << "'";
364 }
365 return linked_;
366 } else {
367 return false;
368 }
369}
370
371std::unique_ptr<VideoFrame>
373{
374 auto input = frame.pointer();
375 if (not input)
376 throw std::runtime_error("Unable to transfer null frame");
377
378 auto desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format));
379 if (!desc) {
380 throw std::runtime_error("Unable to transfer frame with invalid format");
381 }
382
383 auto out = std::make_unique<VideoFrame>();
384 if (not(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
385 out->copyFrom(frame);
386 return out;
387 }
388
389 auto output = out->pointer();
390 output->format = desiredFormat;
391
392 int ret = av_hwframe_transfer_data(output, input, 0);
393 if (ret < 0) {
394 throw std::runtime_error("Unable to transfer the frame from GPU");
395 }
396
397 output->pts = input->pts;
402 return out;
403}
404
405int
407{
408 const auto& codecName = getCodecName();
409 std::string device;
410 auto ret = init_device_type(device);
411 if (ret == 0) {
412 bool link = false;
413 if (linkable && framesCtx)
415 // we don't need frame context for videotoolbox
416 if (hwType_ == AV_HWDEVICE_TYPE_VIDEOTOOLBOX || link || initFrame()) {
417 return 0;
418 }
419 }
420 return -1;
421}
422
423std::list<HardwareAccel>
425{
426 std::list<HardwareAccel> l;
427 const auto& list = (type == CODEC_ENCODER) ? &apiListEnc : &apiListDec;
428 for (auto& api : *list) {
429 const auto& it = std::find(api.supportedCodecs.begin(), api.supportedCodecs.end(), id);
430 if (it != api.supportedCodecs.end()) {
433 if (hwtype == api.hwType) {
434 auto accel = HardwareAccel(id,
435 api.name,
436 api.hwType,
437 api.format,
438 api.swFormat,
439 type,
440 api.dynBitrate);
441 accel.height_ = height;
442 accel.width_ = width;
443 accel.possible_devices_ = &api.possible_devices;
444 l.emplace_back(std::move(accel));
445 }
446 }
447 }
448 }
449 return l;
450}
451
452} // namespace video
453} // namespace jami
Provides an abstraction layer to the hardware acceleration APIs in FFmpeg.
Definition accel.h:45
bool linkHardware(AVBufferRef *framesCtx)
Links this HardwareAccel's frames context with the passed in context.
Definition accel.cpp:350
static std::list< HardwareAccel > getCompatibleAccel(AVCodecID id, int width, int height, CodecType type)
Definition accel.cpp:424
static std::unique_ptr< VideoFrame > transferToMainMemory(const VideoFrame &frame, AVPixelFormat desiredFormat)
Transfers hardware frame to main memory.
Definition accel.cpp:372
std::string getCodecName() const
Gets the name of the codec.
Definition accel.cpp:246
std::unique_ptr< VideoFrame > transfer(const VideoFrame &frame)
Transfers a frame to/from the GPU memory.
Definition accel.cpp:257
const std::string & getName() const
Name of the hardware layer/API being used.
Definition accel.h:88
HardwareAccel(AVCodecID id, const std::string &name, AVHWDeviceType hwType, AVPixelFormat format, AVPixelFormat swFormat, CodecType type, bool dynBitrate)
Constructs a HardwareAccel object.
Definition accel.cpp:118
void setDetails(AVCodecContext *codecCtx)
Set some extra details in the codec context.
Definition accel.cpp:307
~HardwareAccel()
Dereferences hardware contexts.
Definition accel.cpp:134
int initAPI(bool linkable, AVBufferRef *framesCtx)
Definition accel.cpp:406
AVFrameSideData * av_frame_new_side_data_from_buf(AVFrame *frame, enum AVFrameSideDataType type, AVBufferRef *buf)
void av_buffer_unref(AVBufferRef **buf)
#define JAMI_ERR(...)
Definition logger.h:218
#define JAMI_DBG(...)
Definition logger.h:216
#define JAMI_WARN(...)
Definition logger.h:217
Definition Address.h:25
std::string getError(int err)
static AVPixelFormat getFormatCb(AVCodecContext *codecCtx, const AVPixelFormat *formats)
Definition accel.cpp:143
static std::list< HardwareAPI > apiListEnc
Definition accel.cpp:84
static std::list< HardwareAPI > apiListDec
Definition accel.cpp:45
void emitSignal(Args... args)
Definition ring_signal.h:64
@ CODEC_ENCODER
Definition media_codec.h:40
@ CODEC_DECODER
Definition media_codec.h:41
std::list< std::pair< std::string, DeviceState > > possible_devices
Definition accel.cpp:40
std::vector< AVCodecID > supportedCodecs
Definition accel.cpp:39
AVHWDeviceType hwType
Definition accel.cpp:36
AVPixelFormat format
Definition accel.cpp:37
AVPixelFormat swFormat
Definition accel.cpp:38