Line data Source code
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 :
28 : namespace jami {
29 : namespace video {
30 :
31 : struct HardwareAPI
32 : {
33 : std::string name;
34 : AVHWDeviceType hwType;
35 : AVPixelFormat format;
36 : AVPixelFormat swFormat;
37 : std::vector<AVCodecID> supportedCodecs;
38 : std::list<std::pair<std::string, DeviceState>> possible_devices;
39 : bool dynBitrate;
40 : };
41 :
42 : static std::list<HardwareAPI> apiListDec = {
43 : {"nvdec",
44 : AV_HWDEVICE_TYPE_CUDA,
45 : AV_PIX_FMT_CUDA,
46 : AV_PIX_FMT_NV12,
47 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_VP8, AV_CODEC_ID_MJPEG},
48 : {{"default", DeviceState::NOT_TESTED}, {"1", DeviceState::NOT_TESTED}, {"2", DeviceState::NOT_TESTED}},
49 : false},
50 : {"vaapi",
51 : AV_HWDEVICE_TYPE_VAAPI,
52 : AV_PIX_FMT_VAAPI,
53 : AV_PIX_FMT_NV12,
54 : {AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4, AV_CODEC_ID_VP8},
55 : {{"default", DeviceState::NOT_TESTED},
56 : {"/dev/dri/renderD128", DeviceState::NOT_TESTED},
57 : {"/dev/dri/renderD129", DeviceState::NOT_TESTED},
58 : {":0", DeviceState::NOT_TESTED}},
59 : false},
60 : {"vdpau",
61 : AV_HWDEVICE_TYPE_VDPAU,
62 : AV_PIX_FMT_VDPAU,
63 : AV_PIX_FMT_NV12,
64 : {AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4},
65 : {{"default", DeviceState::NOT_TESTED}},
66 : false},
67 : {"videotoolbox",
68 : AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
69 : AV_PIX_FMT_VIDEOTOOLBOX,
70 : AV_PIX_FMT_NV12,
71 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MPEG4},
72 : {{"default", DeviceState::NOT_TESTED}},
73 : false},
74 : {"qsv",
75 : AV_HWDEVICE_TYPE_QSV,
76 : AV_PIX_FMT_QSV,
77 : AV_PIX_FMT_NV12,
78 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8, AV_CODEC_ID_VP9},
79 : {{"default", DeviceState::NOT_TESTED}},
80 : false},
81 : };
82 :
83 : static std::list<HardwareAPI> apiListEnc = {
84 : {"nvenc",
85 : AV_HWDEVICE_TYPE_CUDA,
86 : AV_PIX_FMT_CUDA,
87 : AV_PIX_FMT_NV12,
88 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC},
89 : {{"default", DeviceState::NOT_TESTED}, {"1", DeviceState::NOT_TESTED}, {"2", DeviceState::NOT_TESTED}},
90 : true},
91 : {"vaapi",
92 : AV_HWDEVICE_TYPE_VAAPI,
93 : AV_PIX_FMT_VAAPI,
94 : AV_PIX_FMT_NV12,
95 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_VP8},
96 : {{"default", DeviceState::NOT_TESTED},
97 : {"/dev/dri/renderD128", DeviceState::NOT_TESTED},
98 : {"/dev/dri/renderD129", DeviceState::NOT_TESTED},
99 : {":0", DeviceState::NOT_TESTED}},
100 : false},
101 : {"videotoolbox",
102 : AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
103 : AV_PIX_FMT_VIDEOTOOLBOX,
104 : AV_PIX_FMT_NV12,
105 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC},
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 :
118 413 : HardwareAccel::HardwareAccel(AVCodecID id,
119 : const std::string& name,
120 : AVHWDeviceType hwType,
121 : AVPixelFormat format,
122 : AVPixelFormat swFormat,
123 : CodecType type,
124 413 : bool dynBitrate)
125 413 : : id_(id)
126 413 : , name_(name)
127 413 : , hwType_(hwType)
128 413 : , format_(format)
129 413 : , swFormat_(swFormat)
130 413 : , type_(type)
131 413 : , dynBitrate_(dynBitrate)
132 413 : {}
133 :
134 1239 : HardwareAccel::~HardwareAccel()
135 : {
136 1239 : if (deviceCtx_)
137 0 : av_buffer_unref(&deviceCtx_);
138 1239 : if (framesCtx_)
139 0 : av_buffer_unref(&framesCtx_);
140 1239 : }
141 :
142 : static AVPixelFormat
143 0 : getFormatCb(AVCodecContext* codecCtx, const AVPixelFormat* formats)
144 : {
145 0 : auto* accel = static_cast<HardwareAccel*>(codecCtx->opaque);
146 :
147 0 : for (int i = 0; formats[i] != AV_PIX_FMT_NONE; ++i) {
148 0 : if (accel && formats[i] == accel->getFormat()) {
149 : // found hardware format for codec with api
150 0 : JAMI_LOG("Found compatible hardware format for {} decoder with {}",
151 : avcodec_get_name(static_cast<AVCodecID>(accel->getCodecId())),
152 : accel->getName());
153 : // hardware tends to under-report supported levels
154 0 : codecCtx->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
155 0 : return formats[i];
156 : }
157 : }
158 0 : return AV_PIX_FMT_NONE;
159 : }
160 :
161 : int
162 69 : HardwareAccel::init_device(const char* name, const char* device, int flags)
163 : {
164 69 : const AVHWDeviceContext* dev = nullptr;
165 :
166 : // Create device ctx
167 : int err;
168 69 : err = av_hwdevice_ctx_create(&deviceCtx_, hwType_, device, NULL, flags);
169 69 : if (err < 0) {
170 276 : JAMI_LOG("Failed to create {} device: {}.", name, err);
171 69 : return 1;
172 : }
173 :
174 : // Verify that the device create correspond to api
175 0 : dev = (AVHWDeviceContext*) deviceCtx_->data;
176 0 : if (dev->type != hwType_) {
177 0 : JAMI_LOG("Device created as type {} has type {}.",
178 : av_hwdevice_get_type_name(hwType_),
179 : av_hwdevice_get_type_name(dev->type));
180 0 : av_buffer_unref(&deviceCtx_);
181 0 : return -1;
182 : }
183 0 : JAMI_LOG("Device type {} successfully created.", name);
184 :
185 0 : return 0;
186 : }
187 :
188 : int
189 413 : HardwareAccel::init_device_type(std::string& dev)
190 : {
191 : int err;
192 :
193 413 : const char* name = av_hwdevice_get_type_name(hwType_);
194 413 : if (!name) {
195 0 : JAMI_LOG("No name available for device type {}.", static_cast<int>(hwType_));
196 0 : return -1;
197 : }
198 :
199 413 : AVHWDeviceType check = av_hwdevice_find_type_by_name(name);
200 413 : if (check != hwType_) {
201 0 : JAMI_LOG("Type {} maps to name {} maps to type {}.", static_cast<int>(hwType_), name, static_cast<int>(check));
202 0 : return -1;
203 : }
204 :
205 1652 : JAMI_WARNING("-- Starting {} init for {} with default device.",
206 : (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
207 : name);
208 413 : if (possible_devices_->front().second != DeviceState::NOT_USABLE) {
209 23 : if (name_ == "qsv")
210 0 : err = init_device(name, "auto", 0);
211 : else
212 23 : err = init_device(name, nullptr, 0);
213 23 : if (err == 0) {
214 0 : JAMI_LOG("-- Init passed for {} with default device.", name);
215 0 : possible_devices_->front().second = DeviceState::USABLE;
216 0 : dev = "default";
217 0 : return 0;
218 : } else {
219 23 : possible_devices_->front().second = DeviceState::NOT_USABLE;
220 92 : JAMI_LOG("-- Init failed for {} with default device.", name);
221 : }
222 : }
223 :
224 1516 : for (auto& device : *possible_devices_) {
225 1103 : if (device.second == DeviceState::NOT_USABLE)
226 1057 : continue;
227 184 : JAMI_WARNING("-- Init {} for {} with device {}.",
228 : (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
229 : name,
230 : device.first);
231 46 : err = init_device(name, device.first.c_str(), 0);
232 46 : if (err == 0) {
233 0 : JAMI_LOG("-- Init passed for {} with device {}.", name, device.first);
234 0 : device.second = DeviceState::USABLE;
235 0 : dev = device.first;
236 0 : return 0;
237 : } else {
238 46 : device.second = DeviceState::NOT_USABLE;
239 184 : JAMI_LOG("-- Init failed for {} with device {}.", name, device.first);
240 : }
241 : }
242 413 : return -1;
243 : }
244 :
245 : std::string
246 413 : HardwareAccel::getCodecName() const
247 : {
248 413 : if (type_ == CODEC_DECODER) {
249 826 : return avcodec_get_name(id_);
250 0 : } else if (type_ == CODEC_ENCODER) {
251 0 : return fmt::format("{}_{}", avcodec_get_name(id_), name_);
252 : }
253 0 : return {};
254 : }
255 :
256 : std::unique_ptr<VideoFrame>
257 0 : HardwareAccel::transfer(const VideoFrame& frame)
258 : {
259 0 : int ret = 0;
260 0 : if (type_ == CODEC_DECODER) {
261 0 : const auto* input = frame.pointer();
262 0 : if (input->format != format_) {
263 0 : JAMI_ERROR("Frame format mismatch: expected {}, got {}",
264 : av_get_pix_fmt_name(format_),
265 : av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format)));
266 0 : return nullptr;
267 : }
268 :
269 0 : return transferToMainMemory(frame, swFormat_);
270 0 : } else if (type_ == CODEC_ENCODER) {
271 0 : const auto* input = frame.pointer();
272 0 : if (input->format != swFormat_) {
273 0 : JAMI_ERROR("Frame format mismatch: expected {}, got {}",
274 : av_get_pix_fmt_name(swFormat_),
275 : av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format)));
276 0 : return nullptr;
277 : }
278 :
279 0 : auto framePtr = std::make_unique<VideoFrame>();
280 0 : auto* hwFrame = framePtr->pointer();
281 :
282 0 : if ((ret = av_hwframe_get_buffer(framesCtx_, hwFrame, 0)) < 0) {
283 0 : JAMI_ERROR("Failed to allocate hardware buffer: {}", libav_utils::getError(ret));
284 0 : return nullptr;
285 : }
286 :
287 0 : if (!hwFrame->hw_frames_ctx) {
288 0 : JAMI_ERROR("Failed to allocate hardware buffer: Unable to allocate memory");
289 0 : return nullptr;
290 : }
291 :
292 0 : if ((ret = av_hwframe_transfer_data(hwFrame, input, 0)) < 0) {
293 0 : JAMI_ERROR("Failed to push frame to GPU: {}", libav_utils::getError(ret));
294 0 : return nullptr;
295 : }
296 :
297 0 : hwFrame->pts = input->pts; // transfer does not copy timestamp
298 0 : return framePtr;
299 0 : } else {
300 0 : JAMI_ERROR("Invalid hardware accelerator");
301 0 : return nullptr;
302 : }
303 : }
304 :
305 : void
306 0 : HardwareAccel::setDetails(AVCodecContext* codecCtx)
307 : {
308 0 : if (type_ == CODEC_DECODER) {
309 0 : codecCtx->hw_device_ctx = av_buffer_ref(deviceCtx_);
310 0 : codecCtx->get_format = getFormatCb;
311 0 : } else if (type_ == CODEC_ENCODER) {
312 0 : if (framesCtx_)
313 : // encoder doesn't need a device context, only a frame context
314 0 : codecCtx->hw_frames_ctx = av_buffer_ref(framesCtx_);
315 : }
316 0 : }
317 :
318 : bool
319 0 : HardwareAccel::initFrame()
320 : {
321 0 : int ret = 0;
322 0 : if (!deviceCtx_) {
323 0 : JAMI_ERROR("Unable to initialize hardware frames without a valid hardware device");
324 0 : return false;
325 : }
326 :
327 0 : framesCtx_ = av_hwframe_ctx_alloc(deviceCtx_);
328 0 : if (!framesCtx_)
329 0 : return false;
330 :
331 0 : auto* ctx = reinterpret_cast<AVHWFramesContext*>(framesCtx_->data);
332 0 : ctx->format = format_;
333 0 : ctx->sw_format = swFormat_;
334 0 : ctx->width = width_;
335 0 : ctx->height = height_;
336 0 : ctx->initial_pool_size = 20; // TODO try other values
337 :
338 0 : if ((ret = av_hwframe_ctx_init(framesCtx_)) < 0) {
339 0 : JAMI_ERROR("Failed to initialize hardware frame context: {} ({})", libav_utils::getError(ret), ret);
340 0 : av_buffer_unref(&framesCtx_);
341 : }
342 :
343 0 : return ret >= 0;
344 : }
345 :
346 : bool
347 0 : HardwareAccel::linkHardware(AVBufferRef* framesCtx)
348 : {
349 0 : if (framesCtx) {
350 : // Force sw_format to match swFormat_. Frame is never transferred to main
351 : // memory when hardware is linked, so the sw_format doesn't matter.
352 0 : auto* hw = reinterpret_cast<AVHWFramesContext*>(framesCtx->data);
353 0 : hw->sw_format = swFormat_;
354 :
355 0 : if (framesCtx_)
356 0 : av_buffer_unref(&framesCtx_);
357 0 : framesCtx_ = av_buffer_ref(framesCtx);
358 0 : if ((linked_ = (framesCtx_ != nullptr))) {
359 0 : JAMI_LOG("Hardware transcoding pipeline successfully set up for encoder '{}'", getCodecName());
360 : }
361 0 : return linked_;
362 : } else {
363 0 : return false;
364 : }
365 : }
366 :
367 : std::unique_ptr<VideoFrame>
368 0 : HardwareAccel::transferToMainMemory(const VideoFrame& frame, AVPixelFormat desiredFormat)
369 : {
370 0 : const auto* input = frame.pointer();
371 0 : if (not input)
372 0 : throw std::runtime_error("Unable to transfer null frame");
373 :
374 0 : const auto* desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format));
375 0 : if (!desc) {
376 0 : throw std::runtime_error("Unable to transfer frame with invalid format");
377 : }
378 :
379 0 : auto out = std::make_unique<VideoFrame>();
380 0 : if (not(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
381 0 : out->copyFrom(frame);
382 0 : return out;
383 : }
384 :
385 0 : auto* output = out->pointer();
386 0 : output->format = desiredFormat;
387 :
388 0 : int ret = av_hwframe_transfer_data(output, input, 0);
389 0 : if (ret < 0) {
390 0 : throw std::runtime_error("Unable to transfer the frame from GPU");
391 : }
392 :
393 0 : output->pts = input->pts;
394 0 : if (AVFrameSideData* side_data = av_frame_get_side_data(input, AV_FRAME_DATA_DISPLAYMATRIX))
395 0 : av_frame_new_side_data_from_buf(output, AV_FRAME_DATA_DISPLAYMATRIX, av_buffer_ref(side_data->buf));
396 0 : return out;
397 0 : }
398 :
399 : int
400 413 : HardwareAccel::initAPI(bool linkable, AVBufferRef* framesCtx)
401 : {
402 413 : const auto& codecName = getCodecName();
403 413 : std::string device;
404 413 : auto ret = init_device_type(device);
405 413 : if (ret == 0) {
406 0 : bool link = false;
407 0 : if (linkable && framesCtx)
408 0 : link = linkHardware(framesCtx);
409 : // we don't need frame context for videotoolbox
410 0 : if (hwType_ == AV_HWDEVICE_TYPE_VIDEOTOOLBOX || link || initFrame()) {
411 0 : return 0;
412 : }
413 : }
414 413 : return -1;
415 413 : }
416 :
417 : std::list<HardwareAccel>
418 323 : HardwareAccel::getCompatibleAccel(AVCodecID id, int width, int height, CodecType type)
419 : {
420 323 : std::list<HardwareAccel> l;
421 323 : const auto& list = (type == CODEC_ENCODER) ? &apiListEnc : &apiListDec;
422 1938 : for (auto& api : *list) {
423 1615 : const auto& it = std::find(api.supportedCodecs.begin(), api.supportedCodecs.end(), id);
424 1615 : if (it != api.supportedCodecs.end()) {
425 694 : auto hwtype = AV_HWDEVICE_TYPE_NONE;
426 2776 : while ((hwtype = av_hwdevice_iterate_types(hwtype)) != AV_HWDEVICE_TYPE_NONE) {
427 2082 : if (hwtype == api.hwType) {
428 413 : auto accel = HardwareAccel(id, api.name, api.hwType, api.format, api.swFormat, type, api.dynBitrate);
429 413 : accel.height_ = height;
430 413 : accel.width_ = width;
431 413 : accel.possible_devices_ = &api.possible_devices;
432 413 : l.emplace_back(std::move(accel));
433 413 : }
434 : }
435 : }
436 : }
437 646 : return l;
438 0 : }
439 :
440 : } // namespace video
441 : } // namespace jami
|