Ring Daemon
Loading...
Searching...
No Matches
accel.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2026 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 "logger.h"
26#include "accel.h"
27
28namespace jami {
29namespace video {
30
32{
33 std::string name;
37 std::vector<AVCodecID> supportedCodecs;
38 std::list<std::pair<std::string, DeviceState>> possible_devices;
40};
41
82
83static std::list<HardwareAPI> apiListEnc = {
84 {"nvenc",
90 true},
91 {"vaapi",
96 {{"default", DeviceState::NOT_TESTED},
97 {"/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())) << " decoder with "
152 << 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 const auto* input = frame.pointer();
262 if (input->format != format_) {
263 JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(format_) << ", got "
264 << av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format));
265 return nullptr;
266 }
267
268 return transferToMainMemory(frame, swFormat_);
269 } else if (type_ == CODEC_ENCODER) {
270 const auto* input = frame.pointer();
271 if (input->format != swFormat_) {
272 JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(swFormat_) << ", got "
273 << av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format));
274 return nullptr;
275 }
276
277 auto framePtr = std::make_unique<VideoFrame>();
278 auto* hwFrame = framePtr->pointer();
279
280 if ((ret = av_hwframe_get_buffer(framesCtx_, hwFrame, 0)) < 0) {
281 JAMI_ERR() << "Failed to allocate hardware buffer: " << libav_utils::getError(ret).c_str();
282 return nullptr;
283 }
284
285 if (!hwFrame->hw_frames_ctx) {
286 JAMI_ERR() << "Failed to allocate hardware buffer: Unable to allocate memory";
287 return nullptr;
288 }
289
290 if ((ret = av_hwframe_transfer_data(hwFrame, input, 0)) < 0) {
291 JAMI_ERR() << "Failed to push frame to GPU: " << libav_utils::getError(ret).c_str();
292 return nullptr;
293 }
294
295 hwFrame->pts = input->pts; // transfer does not copy timestamp
296 return framePtr;
297 } else {
298 JAMI_ERR() << "Invalid hardware accelerator";
299 return nullptr;
300 }
301}
302
303void
305{
306 if (type_ == CODEC_DECODER) {
307 codecCtx->hw_device_ctx = av_buffer_ref(deviceCtx_);
308 codecCtx->get_format = getFormatCb;
309 } else if (type_ == CODEC_ENCODER) {
310 if (framesCtx_)
311 // encoder doesn't need a device context, only a frame context
312 codecCtx->hw_frames_ctx = av_buffer_ref(framesCtx_);
313 }
314}
315
316bool
317HardwareAccel::initFrame()
318{
319 int ret = 0;
320 if (!deviceCtx_) {
321 JAMI_ERR() << "Unable to initialize hardware frames without a valid hardware device";
322 return false;
323 }
324
325 framesCtx_ = av_hwframe_ctx_alloc(deviceCtx_);
326 if (!framesCtx_)
327 return false;
328
329 auto* ctx = reinterpret_cast<AVHWFramesContext*>(framesCtx_->data);
330 ctx->format = format_;
331 ctx->sw_format = swFormat_;
332 ctx->width = width_;
333 ctx->height = height_;
334 ctx->initial_pool_size = 20; // TODO try other values
335
336 if ((ret = av_hwframe_ctx_init(framesCtx_)) < 0) {
337 JAMI_ERR("Failed to initialize hardware frame context: %s (%d)", libav_utils::getError(ret).c_str(), ret);
338 av_buffer_unref(&framesCtx_);
339 }
340
341 return ret >= 0;
342}
343
344bool
346{
347 if (framesCtx) {
348 // Force sw_format to match swFormat_. Frame is never transferred to main
349 // memory when hardware is linked, so the sw_format doesn't matter.
350 auto* hw = reinterpret_cast<AVHWFramesContext*>(framesCtx->data);
351 hw->sw_format = swFormat_;
352
353 if (framesCtx_)
354 av_buffer_unref(&framesCtx_);
355 framesCtx_ = av_buffer_ref(framesCtx);
356 if ((linked_ = (framesCtx_ != nullptr))) {
357 JAMI_DBG() << "Hardware transcoding pipeline successfully set up for" << " encoder '" << getCodecName()
358 << "'";
359 }
360 return linked_;
361 } else {
362 return false;
363 }
364}
365
366std::unique_ptr<VideoFrame>
368{
369 const auto* input = frame.pointer();
370 if (not input)
371 throw std::runtime_error("Unable to transfer null frame");
372
373 const auto* desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format));
374 if (!desc) {
375 throw std::runtime_error("Unable to transfer frame with invalid format");
376 }
377
378 auto out = std::make_unique<VideoFrame>();
379 if (not(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
380 out->copyFrom(frame);
381 return out;
382 }
383
384 auto* output = out->pointer();
385 output->format = desiredFormat;
386
387 int ret = av_hwframe_transfer_data(output, input, 0);
388 if (ret < 0) {
389 throw std::runtime_error("Unable to transfer the frame from GPU");
390 }
391
392 output->pts = input->pts;
395 return out;
396}
397
398int
400{
401 const auto& codecName = getCodecName();
402 std::string device;
403 auto ret = init_device_type(device);
404 if (ret == 0) {
405 bool link = false;
406 if (linkable && framesCtx)
408 // we don't need frame context for videotoolbox
409 if (hwType_ == AV_HWDEVICE_TYPE_VIDEOTOOLBOX || link || initFrame()) {
410 return 0;
411 }
412 }
413 return -1;
414}
415
416std::list<HardwareAccel>
418{
419 std::list<HardwareAccel> l;
420 const auto& list = (type == CODEC_ENCODER) ? &apiListEnc : &apiListDec;
421 for (auto& api : *list) {
422 const auto& it = std::find(api.supportedCodecs.begin(), api.supportedCodecs.end(), id);
423 if (it != api.supportedCodecs.end()) {
426 if (hwtype == api.hwType) {
427 auto accel = HardwareAccel(id, api.name, api.hwType, api.format, api.swFormat, type, api.dynBitrate);
428 accel.height_ = height;
429 accel.width_ = width;
430 accel.possible_devices_ = &api.possible_devices;
431 l.emplace_back(std::move(accel));
432 }
433 }
434 }
435 }
436 return l;
437}
438
439} // namespace video
440} // namespace jami
Provides an abstraction layer to the hardware acceleration APIs in FFmpeg.
Definition accel.h:40
bool linkHardware(AVBufferRef *framesCtx)
Links this HardwareAccel's frames context with the passed in context.
Definition accel.cpp:345
static std::list< HardwareAccel > getCompatibleAccel(AVCodecID id, int width, int height, CodecType type)
Definition accel.cpp:417
static std::unique_ptr< VideoFrame > transferToMainMemory(const VideoFrame &frame, AVPixelFormat desiredFormat)
Transfers hardware frame to main memory.
Definition accel.cpp:367
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:82
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:304
~HardwareAccel()
Dereferences hardware contexts.
Definition accel.cpp:134
int initAPI(bool linkable, AVBufferRef *framesCtx)
Definition accel.cpp:399
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:230
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_WARN(...)
Definition logger.h:229
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:83
static std::list< HardwareAPI > apiListDec
Definition accel.cpp:42
void emitSignal(Args... args)
Definition jami_signal.h:64
@ CODEC_ENCODER
Definition media_codec.h:39
@ CODEC_DECODER
Definition media_codec.h:40
std::list< std::pair< std::string, DeviceState > > possible_devices
Definition accel.cpp:38
std::vector< AVCodecID > supportedCodecs
Definition accel.cpp:37
AVHWDeviceType hwType
Definition accel.cpp:34
AVPixelFormat format
Definition accel.cpp:35
AVPixelFormat swFormat
Definition accel.cpp:36