Line data Source code
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 :
30 : namespace jami {
31 : namespace video {
32 :
33 : struct HardwareAPI
34 : {
35 : std::string name;
36 : AVHWDeviceType hwType;
37 : AVPixelFormat format;
38 : AVPixelFormat swFormat;
39 : std::vector<AVCodecID> supportedCodecs;
40 : std::list<std::pair<std::string, DeviceState>> possible_devices;
41 : bool dynBitrate;
42 : };
43 :
44 : static std::list<HardwareAPI> apiListDec = {
45 : {"nvdec",
46 : AV_HWDEVICE_TYPE_CUDA,
47 : AV_PIX_FMT_CUDA,
48 : AV_PIX_FMT_NV12,
49 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_VP8, AV_CODEC_ID_MJPEG},
50 : {{"default", DeviceState::NOT_TESTED}, {"1", DeviceState::NOT_TESTED}, {"2", DeviceState::NOT_TESTED}},
51 : false},
52 : {"vaapi",
53 : AV_HWDEVICE_TYPE_VAAPI,
54 : AV_PIX_FMT_VAAPI,
55 : AV_PIX_FMT_NV12,
56 : {AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4, AV_CODEC_ID_VP8},
57 : {{"default", DeviceState::NOT_TESTED},
58 : {"/dev/dri/renderD128", DeviceState::NOT_TESTED},
59 : {"/dev/dri/renderD129", DeviceState::NOT_TESTED},
60 : {":0", DeviceState::NOT_TESTED}},
61 : false},
62 : {"vdpau",
63 : AV_HWDEVICE_TYPE_VDPAU,
64 : AV_PIX_FMT_VDPAU,
65 : AV_PIX_FMT_NV12,
66 : {AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4},
67 : {{"default", DeviceState::NOT_TESTED}},
68 : false},
69 : {"videotoolbox",
70 : AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
71 : AV_PIX_FMT_VIDEOTOOLBOX,
72 : AV_PIX_FMT_NV12,
73 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MPEG4},
74 : {{"default", DeviceState::NOT_TESTED}},
75 : false},
76 : {"qsv",
77 : AV_HWDEVICE_TYPE_QSV,
78 : AV_PIX_FMT_QSV,
79 : AV_PIX_FMT_NV12,
80 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8, AV_CODEC_ID_VP9},
81 : {{"default", DeviceState::NOT_TESTED}},
82 : false},
83 : };
84 :
85 : static std::list<HardwareAPI> apiListEnc = {
86 : {"nvenc",
87 : AV_HWDEVICE_TYPE_CUDA,
88 : AV_PIX_FMT_CUDA,
89 : AV_PIX_FMT_NV12,
90 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC},
91 : {{"default", DeviceState::NOT_TESTED}, {"1", DeviceState::NOT_TESTED}, {"2", DeviceState::NOT_TESTED}},
92 : true},
93 : {"vaapi",
94 : AV_HWDEVICE_TYPE_VAAPI,
95 : AV_PIX_FMT_VAAPI,
96 : AV_PIX_FMT_NV12,
97 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_VP8},
98 : {{"default", DeviceState::NOT_TESTED},
99 : {"/dev/dri/renderD128", DeviceState::NOT_TESTED},
100 : {"/dev/dri/renderD129", DeviceState::NOT_TESTED},
101 : {":0", DeviceState::NOT_TESTED}},
102 : false},
103 : {"videotoolbox",
104 : AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
105 : AV_PIX_FMT_VIDEOTOOLBOX,
106 : AV_PIX_FMT_NV12,
107 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC},
108 : {{"default", DeviceState::NOT_TESTED}},
109 : false},
110 : // Disable temporarily QSVENC
111 : // {"qsv",
112 : // AV_HWDEVICE_TYPE_QSV,
113 : // AV_PIX_FMT_QSV,
114 : // AV_PIX_FMT_NV12,
115 : // {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8},
116 : // {{"default", DeviceState::NOT_TESTED}},
117 : // false},
118 : };
119 :
120 422 : HardwareAccel::HardwareAccel(AVCodecID id,
121 : const std::string& name,
122 : AVHWDeviceType hwType,
123 : AVPixelFormat format,
124 : AVPixelFormat swFormat,
125 : CodecType type,
126 422 : bool dynBitrate)
127 422 : : id_(id)
128 422 : , name_(name)
129 422 : , hwType_(hwType)
130 422 : , format_(format)
131 422 : , swFormat_(swFormat)
132 422 : , type_(type)
133 422 : , dynBitrate_(dynBitrate)
134 422 : {}
135 :
136 1266 : HardwareAccel::~HardwareAccel()
137 : {
138 1266 : if (deviceCtx_)
139 0 : av_buffer_unref(&deviceCtx_);
140 1266 : if (framesCtx_)
141 0 : av_buffer_unref(&framesCtx_);
142 1266 : }
143 :
144 : static AVPixelFormat
145 0 : getFormatCb(AVCodecContext* codecCtx, const AVPixelFormat* formats)
146 : {
147 0 : auto accel = static_cast<HardwareAccel*>(codecCtx->opaque);
148 :
149 0 : for (int i = 0; formats[i] != AV_PIX_FMT_NONE; ++i) {
150 0 : if (accel && formats[i] == accel->getFormat()) {
151 : // found hardware format for codec with api
152 0 : JAMI_DBG() << "Found compatible hardware format for "
153 0 : << avcodec_get_name(static_cast<AVCodecID>(accel->getCodecId())) << " decoder with "
154 0 : << accel->getName();
155 : // hardware tends to under-report supported levels
156 0 : codecCtx->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
157 0 : return formats[i];
158 : }
159 : }
160 0 : return AV_PIX_FMT_NONE;
161 : }
162 :
163 : int
164 70 : HardwareAccel::init_device(const char* name, const char* device, int flags)
165 : {
166 70 : const AVHWDeviceContext* dev = nullptr;
167 :
168 : // Create device ctx
169 : int err;
170 70 : err = av_hwdevice_ctx_create(&deviceCtx_, hwType_, device, NULL, flags);
171 70 : if (err < 0) {
172 70 : JAMI_DBG("Failed to create %s device: %d.\n", name, err);
173 70 : return 1;
174 : }
175 :
176 : // Verify that the device create correspond to api
177 0 : dev = (AVHWDeviceContext*) deviceCtx_->data;
178 0 : if (dev->type != hwType_) {
179 0 : JAMI_DBG("Device created as type %d has type %d.", hwType_, dev->type);
180 0 : av_buffer_unref(&deviceCtx_);
181 0 : return -1;
182 : }
183 0 : JAMI_DBG("Device type %s successfully created.", name);
184 :
185 0 : return 0;
186 : }
187 :
188 : int
189 422 : HardwareAccel::init_device_type(std::string& dev)
190 : {
191 : AVHWDeviceType check;
192 : const char* name;
193 : int err;
194 :
195 422 : name = av_hwdevice_get_type_name(hwType_);
196 422 : if (!name) {
197 0 : JAMI_DBG("No name available for device type %d.", hwType_);
198 0 : return -1;
199 : }
200 :
201 422 : check = av_hwdevice_find_type_by_name(name);
202 422 : if (check != hwType_) {
203 0 : JAMI_DBG("Type %d maps to name %s maps to type %d.", hwType_, name, check);
204 0 : return -1;
205 : }
206 :
207 422 : JAMI_WARN("-- Starting %s init for %s with default device.",
208 : (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
209 : name);
210 422 : if (possible_devices_->front().second != DeviceState::NOT_USABLE) {
211 23 : if (name_ == "qsv")
212 0 : err = init_device(name, "auto", 0);
213 : else
214 23 : err = init_device(name, nullptr, 0);
215 23 : if (err == 0) {
216 0 : JAMI_DBG("-- Init passed for %s with default device.", name);
217 0 : possible_devices_->front().second = DeviceState::USABLE;
218 0 : dev = "default";
219 0 : return 0;
220 : } else {
221 23 : possible_devices_->front().second = DeviceState::NOT_USABLE;
222 23 : JAMI_DBG("-- Init failed for %s with default device.", name);
223 : }
224 : }
225 :
226 1549 : for (auto& device : *possible_devices_) {
227 1127 : if (device.second == DeviceState::NOT_USABLE)
228 1080 : continue;
229 47 : JAMI_WARN("-- Init %s for %s with device %s.",
230 : (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
231 : name,
232 : device.first.c_str());
233 47 : err = init_device(name, device.first.c_str(), 0);
234 47 : if (err == 0) {
235 0 : JAMI_DBG("-- Init passed for %s with device %s.", name, device.first.c_str());
236 0 : device.second = DeviceState::USABLE;
237 0 : dev = device.first;
238 0 : return 0;
239 : } else {
240 47 : device.second = DeviceState::NOT_USABLE;
241 47 : JAMI_DBG("-- Init failed for %s with device %s.", name, device.first.c_str());
242 : }
243 : }
244 422 : return -1;
245 : }
246 :
247 : std::string
248 422 : HardwareAccel::getCodecName() const
249 : {
250 422 : if (type_ == CODEC_DECODER) {
251 422 : return avcodec_get_name(id_);
252 0 : } else if (type_ == CODEC_ENCODER) {
253 0 : return fmt::format("{}_{}", avcodec_get_name(id_), name_);
254 : }
255 0 : return {};
256 : }
257 :
258 : std::unique_ptr<VideoFrame>
259 0 : HardwareAccel::transfer(const VideoFrame& frame)
260 : {
261 0 : int ret = 0;
262 0 : if (type_ == CODEC_DECODER) {
263 0 : auto input = frame.pointer();
264 0 : if (input->format != format_) {
265 0 : JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(format_) << ", got "
266 0 : << av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format));
267 0 : return nullptr;
268 : }
269 :
270 0 : return transferToMainMemory(frame, swFormat_);
271 0 : } else if (type_ == CODEC_ENCODER) {
272 0 : auto input = frame.pointer();
273 0 : if (input->format != swFormat_) {
274 0 : JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(swFormat_) << ", got "
275 0 : << 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_ERR() << "Failed to allocate hardware buffer: " << libav_utils::getError(ret).c_str();
284 0 : return nullptr;
285 : }
286 :
287 0 : if (!hwFrame->hw_frames_ctx) {
288 0 : JAMI_ERR() << "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_ERR() << "Failed to push frame to GPU: " << libav_utils::getError(ret).c_str();
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_ERR() << "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_ERR() << "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_ERR("Failed to initialize hardware frame context: %s (%d)", libav_utils::getError(ret).c_str(), 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_DBG() << "Hardware transcoding pipeline successfully set up for" << " encoder '" << getCodecName()
360 0 : << "'";
361 : }
362 0 : return linked_;
363 : } else {
364 0 : return false;
365 : }
366 : }
367 :
368 : std::unique_ptr<VideoFrame>
369 0 : HardwareAccel::transferToMainMemory(const VideoFrame& frame, AVPixelFormat desiredFormat)
370 : {
371 0 : auto input = frame.pointer();
372 0 : if (not input)
373 0 : throw std::runtime_error("Unable to transfer null frame");
374 :
375 0 : auto desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format));
376 0 : if (!desc) {
377 0 : throw std::runtime_error("Unable to transfer frame with invalid format");
378 : }
379 :
380 0 : auto out = std::make_unique<VideoFrame>();
381 0 : if (not(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
382 0 : out->copyFrom(frame);
383 0 : return out;
384 : }
385 :
386 0 : auto output = out->pointer();
387 0 : output->format = desiredFormat;
388 :
389 0 : int ret = av_hwframe_transfer_data(output, input, 0);
390 0 : if (ret < 0) {
391 0 : throw std::runtime_error("Unable to transfer the frame from GPU");
392 : }
393 :
394 0 : output->pts = input->pts;
395 0 : if (AVFrameSideData* side_data = av_frame_get_side_data(input, AV_FRAME_DATA_DISPLAYMATRIX))
396 0 : av_frame_new_side_data_from_buf(output, AV_FRAME_DATA_DISPLAYMATRIX, av_buffer_ref(side_data->buf));
397 0 : return out;
398 0 : }
399 :
400 : int
401 422 : HardwareAccel::initAPI(bool linkable, AVBufferRef* framesCtx)
402 : {
403 422 : const auto& codecName = getCodecName();
404 422 : std::string device;
405 422 : auto ret = init_device_type(device);
406 422 : if (ret == 0) {
407 0 : bool link = false;
408 0 : if (linkable && framesCtx)
409 0 : link = linkHardware(framesCtx);
410 : // we don't need frame context for videotoolbox
411 0 : if (hwType_ == AV_HWDEVICE_TYPE_VIDEOTOOLBOX || link || initFrame()) {
412 0 : return 0;
413 : }
414 : }
415 422 : return -1;
416 422 : }
417 :
418 : std::list<HardwareAccel>
419 330 : HardwareAccel::getCompatibleAccel(AVCodecID id, int width, int height, CodecType type)
420 : {
421 330 : std::list<HardwareAccel> l;
422 330 : const auto& list = (type == CODEC_ENCODER) ? &apiListEnc : &apiListDec;
423 1980 : for (auto& api : *list) {
424 1650 : const auto& it = std::find(api.supportedCodecs.begin(), api.supportedCodecs.end(), id);
425 1650 : if (it != api.supportedCodecs.end()) {
426 709 : auto hwtype = AV_HWDEVICE_TYPE_NONE;
427 2836 : while ((hwtype = av_hwdevice_iterate_types(hwtype)) != AV_HWDEVICE_TYPE_NONE) {
428 2127 : if (hwtype == api.hwType) {
429 422 : auto accel = HardwareAccel(id, api.name, api.hwType, api.format, api.swFormat, type, api.dynBitrate);
430 422 : accel.height_ = height;
431 422 : accel.width_ = width;
432 422 : accel.possible_devices_ = &api.possible_devices;
433 422 : l.emplace_back(std::move(accel));
434 422 : }
435 : }
436 : }
437 : }
438 660 : return l;
439 0 : }
440 :
441 : } // namespace video
442 : } // namespace jami
|