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 398 : HardwareAccel::HardwareAccel(AVCodecID id,
119 : const std::string& name,
120 : AVHWDeviceType hwType,
121 : AVPixelFormat format,
122 : AVPixelFormat swFormat,
123 : CodecType type,
124 398 : bool dynBitrate)
125 398 : : id_(id)
126 398 : , name_(name)
127 398 : , hwType_(hwType)
128 398 : , format_(format)
129 398 : , swFormat_(swFormat)
130 398 : , type_(type)
131 398 : , dynBitrate_(dynBitrate)
132 398 : {}
133 :
134 1194 : HardwareAccel::~HardwareAccel()
135 : {
136 1194 : if (deviceCtx_)
137 0 : av_buffer_unref(&deviceCtx_);
138 1194 : if (framesCtx_)
139 0 : av_buffer_unref(&framesCtx_);
140 1194 : }
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_DBG() << "Found compatible hardware format for "
151 0 : << avcodec_get_name(static_cast<AVCodecID>(accel->getCodecId())) << " decoder with "
152 0 : << 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 52 : HardwareAccel::init_device(const char* name, const char* device, int flags)
163 : {
164 52 : const AVHWDeviceContext* dev = nullptr;
165 :
166 : // Create device ctx
167 : int err;
168 52 : err = av_hwdevice_ctx_create(&deviceCtx_, hwType_, device, NULL, flags);
169 52 : if (err < 0) {
170 52 : JAMI_DBG("Failed to create %s device: %d.\n", name, err);
171 52 : 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_DBG("Device created as type %d has type %d.", hwType_, dev->type);
178 0 : av_buffer_unref(&deviceCtx_);
179 0 : return -1;
180 : }
181 0 : JAMI_DBG("Device type %s successfully created.", name);
182 :
183 0 : return 0;
184 : }
185 :
186 : int
187 398 : HardwareAccel::init_device_type(std::string& dev)
188 : {
189 : AVHWDeviceType check;
190 : const char* name;
191 : int err;
192 :
193 398 : name = av_hwdevice_get_type_name(hwType_);
194 398 : if (!name) {
195 0 : JAMI_DBG("No name available for device type %d.", hwType_);
196 0 : return -1;
197 : }
198 :
199 398 : check = av_hwdevice_find_type_by_name(name);
200 398 : if (check != hwType_) {
201 0 : JAMI_DBG("Type %d maps to name %s maps to type %d.", hwType_, name, check);
202 0 : return -1;
203 : }
204 :
205 398 : JAMI_WARN("-- Starting %s init for %s with default device.",
206 : (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
207 : name);
208 397 : if (possible_devices_->front().second != DeviceState::NOT_USABLE) {
209 19 : if (name_ == "qsv")
210 0 : err = init_device(name, "auto", 0);
211 : else
212 19 : err = init_device(name, nullptr, 0);
213 19 : if (err == 0) {
214 0 : JAMI_DBG("-- Init passed for %s with default device.", name);
215 0 : possible_devices_->front().second = DeviceState::USABLE;
216 0 : dev = "default";
217 0 : return 0;
218 : } else {
219 19 : possible_devices_->front().second = DeviceState::NOT_USABLE;
220 19 : JAMI_DBG("-- Init failed for %s with default device.", name);
221 : }
222 : }
223 :
224 1461 : for (auto& device : *possible_devices_) {
225 1063 : if (device.second == DeviceState::NOT_USABLE)
226 1030 : continue;
227 33 : JAMI_WARN("-- Init %s for %s with device %s.",
228 : (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
229 : name,
230 : device.first.c_str());
231 33 : err = init_device(name, device.first.c_str(), 0);
232 33 : if (err == 0) {
233 0 : JAMI_DBG("-- Init passed for %s with device %s.", name, device.first.c_str());
234 0 : device.second = DeviceState::USABLE;
235 0 : dev = device.first;
236 0 : return 0;
237 : } else {
238 33 : device.second = DeviceState::NOT_USABLE;
239 33 : JAMI_DBG("-- Init failed for %s with device %s.", name, device.first.c_str());
240 : }
241 : }
242 398 : return -1;
243 : }
244 :
245 : std::string
246 397 : HardwareAccel::getCodecName() const
247 : {
248 397 : if (type_ == CODEC_DECODER) {
249 397 : 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_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(format_) << ", got "
264 0 : << av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format));
265 0 : return nullptr;
266 : }
267 :
268 0 : return transferToMainMemory(frame, swFormat_);
269 0 : } else if (type_ == CODEC_ENCODER) {
270 0 : const auto* input = frame.pointer();
271 0 : if (input->format != swFormat_) {
272 0 : JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(swFormat_) << ", got "
273 0 : << av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format));
274 0 : return nullptr;
275 : }
276 :
277 0 : auto framePtr = std::make_unique<VideoFrame>();
278 0 : auto* hwFrame = framePtr->pointer();
279 :
280 0 : if ((ret = av_hwframe_get_buffer(framesCtx_, hwFrame, 0)) < 0) {
281 0 : JAMI_ERR() << "Failed to allocate hardware buffer: " << libav_utils::getError(ret).c_str();
282 0 : return nullptr;
283 : }
284 :
285 0 : if (!hwFrame->hw_frames_ctx) {
286 0 : JAMI_ERR() << "Failed to allocate hardware buffer: Unable to allocate memory";
287 0 : return nullptr;
288 : }
289 :
290 0 : if ((ret = av_hwframe_transfer_data(hwFrame, input, 0)) < 0) {
291 0 : JAMI_ERR() << "Failed to push frame to GPU: " << libav_utils::getError(ret).c_str();
292 0 : return nullptr;
293 : }
294 :
295 0 : hwFrame->pts = input->pts; // transfer does not copy timestamp
296 0 : return framePtr;
297 0 : } else {
298 0 : JAMI_ERR() << "Invalid hardware accelerator";
299 0 : return nullptr;
300 : }
301 : }
302 :
303 : void
304 0 : HardwareAccel::setDetails(AVCodecContext* codecCtx)
305 : {
306 0 : if (type_ == CODEC_DECODER) {
307 0 : codecCtx->hw_device_ctx = av_buffer_ref(deviceCtx_);
308 0 : codecCtx->get_format = getFormatCb;
309 0 : } else if (type_ == CODEC_ENCODER) {
310 0 : if (framesCtx_)
311 : // encoder doesn't need a device context, only a frame context
312 0 : codecCtx->hw_frames_ctx = av_buffer_ref(framesCtx_);
313 : }
314 0 : }
315 :
316 : bool
317 0 : HardwareAccel::initFrame()
318 : {
319 0 : int ret = 0;
320 0 : if (!deviceCtx_) {
321 0 : JAMI_ERR() << "Unable to initialize hardware frames without a valid hardware device";
322 0 : return false;
323 : }
324 :
325 0 : framesCtx_ = av_hwframe_ctx_alloc(deviceCtx_);
326 0 : if (!framesCtx_)
327 0 : return false;
328 :
329 0 : auto* ctx = reinterpret_cast<AVHWFramesContext*>(framesCtx_->data);
330 0 : ctx->format = format_;
331 0 : ctx->sw_format = swFormat_;
332 0 : ctx->width = width_;
333 0 : ctx->height = height_;
334 0 : ctx->initial_pool_size = 20; // TODO try other values
335 :
336 0 : if ((ret = av_hwframe_ctx_init(framesCtx_)) < 0) {
337 0 : JAMI_ERR("Failed to initialize hardware frame context: %s (%d)", libav_utils::getError(ret).c_str(), ret);
338 0 : av_buffer_unref(&framesCtx_);
339 : }
340 :
341 0 : return ret >= 0;
342 : }
343 :
344 : bool
345 0 : HardwareAccel::linkHardware(AVBufferRef* framesCtx)
346 : {
347 0 : 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 0 : auto* hw = reinterpret_cast<AVHWFramesContext*>(framesCtx->data);
351 0 : hw->sw_format = swFormat_;
352 :
353 0 : if (framesCtx_)
354 0 : av_buffer_unref(&framesCtx_);
355 0 : framesCtx_ = av_buffer_ref(framesCtx);
356 0 : if ((linked_ = (framesCtx_ != nullptr))) {
357 0 : JAMI_DBG() << "Hardware transcoding pipeline successfully set up for" << " encoder '" << getCodecName()
358 0 : << "'";
359 : }
360 0 : return linked_;
361 : } else {
362 0 : return false;
363 : }
364 : }
365 :
366 : std::unique_ptr<VideoFrame>
367 0 : HardwareAccel::transferToMainMemory(const VideoFrame& frame, AVPixelFormat desiredFormat)
368 : {
369 0 : const auto* input = frame.pointer();
370 0 : if (not input)
371 0 : throw std::runtime_error("Unable to transfer null frame");
372 :
373 0 : const auto* desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format));
374 0 : if (!desc) {
375 0 : throw std::runtime_error("Unable to transfer frame with invalid format");
376 : }
377 :
378 0 : auto out = std::make_unique<VideoFrame>();
379 0 : if (not(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
380 0 : out->copyFrom(frame);
381 0 : return out;
382 : }
383 :
384 0 : auto* output = out->pointer();
385 0 : output->format = desiredFormat;
386 :
387 0 : int ret = av_hwframe_transfer_data(output, input, 0);
388 0 : if (ret < 0) {
389 0 : throw std::runtime_error("Unable to transfer the frame from GPU");
390 : }
391 :
392 0 : output->pts = input->pts;
393 0 : if (AVFrameSideData* side_data = av_frame_get_side_data(input, AV_FRAME_DATA_DISPLAYMATRIX))
394 0 : av_frame_new_side_data_from_buf(output, AV_FRAME_DATA_DISPLAYMATRIX, av_buffer_ref(side_data->buf));
395 0 : return out;
396 0 : }
397 :
398 : int
399 397 : HardwareAccel::initAPI(bool linkable, AVBufferRef* framesCtx)
400 : {
401 397 : const auto& codecName = getCodecName();
402 398 : std::string device;
403 398 : auto ret = init_device_type(device);
404 398 : if (ret == 0) {
405 0 : bool link = false;
406 0 : if (linkable && framesCtx)
407 0 : link = linkHardware(framesCtx);
408 : // we don't need frame context for videotoolbox
409 0 : if (hwType_ == AV_HWDEVICE_TYPE_VIDEOTOOLBOX || link || initFrame()) {
410 0 : return 0;
411 : }
412 : }
413 398 : return -1;
414 398 : }
415 :
416 : std::list<HardwareAccel>
417 308 : HardwareAccel::getCompatibleAccel(AVCodecID id, int width, int height, CodecType type)
418 : {
419 308 : std::list<HardwareAccel> l;
420 308 : const auto& list = (type == CODEC_ENCODER) ? &apiListEnc : &apiListDec;
421 1847 : for (auto& api : *list) {
422 1540 : const auto& it = std::find(api.supportedCodecs.begin(), api.supportedCodecs.end(), id);
423 1540 : if (it != api.supportedCodecs.end()) {
424 669 : auto hwtype = AV_HWDEVICE_TYPE_NONE;
425 2676 : while ((hwtype = av_hwdevice_iterate_types(hwtype)) != AV_HWDEVICE_TYPE_NONE) {
426 2007 : if (hwtype == api.hwType) {
427 398 : auto accel = HardwareAccel(id, api.name, api.hwType, api.format, api.swFormat, type, api.dynBitrate);
428 398 : accel.height_ = height;
429 398 : accel.width_ = width;
430 398 : accel.possible_devices_ = &api.possible_devices;
431 398 : l.emplace_back(std::move(accel));
432 398 : }
433 : }
434 : }
435 : }
436 616 : return l;
437 0 : }
438 :
439 : } // namespace video
440 : } // namespace jami
|