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