Line data Source code
1 : /*
2 : * Copyright (C) 2004-2024 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 :
45 : static std::list<HardwareAPI> apiListDec = {
46 : {"nvdec",
47 : AV_HWDEVICE_TYPE_CUDA,
48 : AV_PIX_FMT_CUDA,
49 : AV_PIX_FMT_NV12,
50 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_VP8, AV_CODEC_ID_MJPEG},
51 : {{"default", DeviceState::NOT_TESTED}, {"1", DeviceState::NOT_TESTED}, {"2", DeviceState::NOT_TESTED}},
52 : false},
53 : {"vaapi",
54 : AV_HWDEVICE_TYPE_VAAPI,
55 : AV_PIX_FMT_VAAPI,
56 : AV_PIX_FMT_NV12,
57 : {AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4, AV_CODEC_ID_VP8},
58 : {{"default", DeviceState::NOT_TESTED}, {"/dev/dri/renderD128", DeviceState::NOT_TESTED},
59 : {"/dev/dri/renderD129", DeviceState::NOT_TESTED}, {":0", DeviceState::NOT_TESTED}},
60 : false},
61 : {"vdpau",
62 : AV_HWDEVICE_TYPE_VDPAU,
63 : AV_PIX_FMT_VDPAU,
64 : AV_PIX_FMT_NV12,
65 : {AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4},
66 : {{"default", DeviceState::NOT_TESTED}},
67 : false},
68 : {"videotoolbox",
69 : AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
70 : AV_PIX_FMT_VIDEOTOOLBOX,
71 : AV_PIX_FMT_NV12,
72 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MPEG4},
73 : {{"default", DeviceState::NOT_TESTED}},
74 : false},
75 : {"qsv",
76 : AV_HWDEVICE_TYPE_QSV,
77 : AV_PIX_FMT_QSV,
78 : AV_PIX_FMT_NV12,
79 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8, AV_CODEC_ID_VP9},
80 : {{"default", DeviceState::NOT_TESTED}},
81 : false},
82 : };
83 :
84 : static std::list<HardwareAPI> apiListEnc = {
85 : {"nvenc",
86 : AV_HWDEVICE_TYPE_CUDA,
87 : AV_PIX_FMT_CUDA,
88 : AV_PIX_FMT_NV12,
89 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC},
90 : {{"default", DeviceState::NOT_TESTED}, {"1", DeviceState::NOT_TESTED}, {"2", DeviceState::NOT_TESTED}},
91 : true},
92 : {"vaapi",
93 : AV_HWDEVICE_TYPE_VAAPI,
94 : AV_PIX_FMT_VAAPI,
95 : AV_PIX_FMT_NV12,
96 : {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_VP8},
97 : {{"default", DeviceState::NOT_TESTED}, {"/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 448 : HardwareAccel::HardwareAccel(AVCodecID id,
119 : const std::string& name,
120 : AVHWDeviceType hwType,
121 : AVPixelFormat format,
122 : AVPixelFormat swFormat,
123 : CodecType type,
124 448 : bool dynBitrate)
125 448 : : id_(id)
126 448 : , name_(name)
127 448 : , hwType_(hwType)
128 448 : , format_(format)
129 448 : , swFormat_(swFormat)
130 448 : , type_(type)
131 448 : , dynBitrate_(dynBitrate)
132 448 : {}
133 :
134 1344 : HardwareAccel::~HardwareAccel()
135 : {
136 1344 : if (deviceCtx_)
137 0 : av_buffer_unref(&deviceCtx_);
138 1344 : if (framesCtx_)
139 0 : av_buffer_unref(&framesCtx_);
140 1344 : }
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()))
152 0 : << " decoder with " << 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 75 : HardwareAccel::init_device(const char* name, const char* device, int flags)
163 : {
164 75 : const AVHWDeviceContext* dev = nullptr;
165 :
166 : // Create device ctx
167 : int err;
168 75 : err = av_hwdevice_ctx_create(&deviceCtx_, hwType_, device, NULL, flags);
169 75 : if (err < 0) {
170 75 : JAMI_DBG("Failed to create %s device: %d.\n", name, err);
171 75 : 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 448 : HardwareAccel::init_device_type(std::string& dev)
188 : {
189 : AVHWDeviceType check;
190 : const char* name;
191 : int err;
192 :
193 448 : name = av_hwdevice_get_type_name(hwType_);
194 448 : if (!name) {
195 0 : JAMI_DBG("No name available for device type %d.", hwType_);
196 0 : return -1;
197 : }
198 :
199 448 : check = av_hwdevice_find_type_by_name(name);
200 448 : 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 448 : JAMI_WARN("-- Starting %s init for %s with default device.",
206 : (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
207 : name);
208 448 : if (possible_devices_->front().second != DeviceState::NOT_USABLE) {
209 28 : if (name_ == "qsv")
210 0 : err = init_device(name, "auto", 0);
211 : else
212 28 : err = init_device(name, nullptr, 0);
213 28 : 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 28 : possible_devices_->front().second = DeviceState::NOT_USABLE;
220 28 : JAMI_DBG("-- Init failed for %s with default device.", name);
221 : }
222 : }
223 :
224 1646 : for (auto& device : *possible_devices_) {
225 1198 : if (device.second == DeviceState::NOT_USABLE)
226 1151 : continue;
227 47 : JAMI_WARN("-- Init %s for %s with device %s.",
228 : (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
229 : name,
230 : device.first.c_str());
231 47 : err = init_device(name, device.first.c_str(), 0);
232 47 : 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 47 : device.second = DeviceState::NOT_USABLE;
239 47 : JAMI_DBG("-- Init failed for %s with device %s.", name, device.first.c_str());
240 : }
241 : }
242 448 : return -1;
243 : }
244 :
245 : std::string
246 448 : HardwareAccel::getCodecName() const
247 : {
248 448 : if (type_ == CODEC_DECODER) {
249 448 : 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 : auto input = frame.pointer();
262 0 : if (input->format != format_) {
263 0 : JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(format_)
264 0 : << ", got "
265 0 : << 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 : auto input = frame.pointer();
272 0 : if (input->format != swFormat_) {
273 0 : JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(swFormat_)
274 0 : << ", 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: "
284 0 : << libav_utils::getError(ret).c_str();
285 0 : return nullptr;
286 : }
287 :
288 0 : if (!hwFrame->hw_frames_ctx) {
289 0 : JAMI_ERR() << "Failed to allocate hardware buffer: Unable to allocate memory";
290 0 : return nullptr;
291 : }
292 :
293 0 : if ((ret = av_hwframe_transfer_data(hwFrame, input, 0)) < 0) {
294 0 : JAMI_ERR() << "Failed to push frame to GPU: " << libav_utils::getError(ret).c_str();
295 0 : return nullptr;
296 : }
297 :
298 0 : hwFrame->pts = input->pts; // transfer does not copy timestamp
299 0 : return framePtr;
300 0 : } else {
301 0 : JAMI_ERR() << "Invalid hardware accelerator";
302 0 : return nullptr;
303 : }
304 : }
305 :
306 : void
307 0 : HardwareAccel::setDetails(AVCodecContext* codecCtx)
308 : {
309 0 : if (type_ == CODEC_DECODER) {
310 0 : codecCtx->hw_device_ctx = av_buffer_ref(deviceCtx_);
311 0 : codecCtx->get_format = getFormatCb;
312 0 : } else if (type_ == CODEC_ENCODER) {
313 0 : if (framesCtx_)
314 : // encoder doesn't need a device context, only a frame context
315 0 : codecCtx->hw_frames_ctx = av_buffer_ref(framesCtx_);
316 : }
317 0 : }
318 :
319 : bool
320 0 : HardwareAccel::initFrame()
321 : {
322 0 : int ret = 0;
323 0 : if (!deviceCtx_) {
324 0 : JAMI_ERR() << "Unable to initialize hardware frames without a valid hardware device";
325 0 : return false;
326 : }
327 :
328 0 : framesCtx_ = av_hwframe_ctx_alloc(deviceCtx_);
329 0 : if (!framesCtx_)
330 0 : return false;
331 :
332 0 : auto ctx = reinterpret_cast<AVHWFramesContext*>(framesCtx_->data);
333 0 : ctx->format = format_;
334 0 : ctx->sw_format = swFormat_;
335 0 : ctx->width = width_;
336 0 : ctx->height = height_;
337 0 : ctx->initial_pool_size = 20; // TODO try other values
338 :
339 0 : if ((ret = av_hwframe_ctx_init(framesCtx_)) < 0) {
340 0 : JAMI_ERR("Failed to initialize hardware frame context: %s (%d)",
341 : libav_utils::getError(ret).c_str(),
342 : ret);
343 0 : av_buffer_unref(&framesCtx_);
344 : }
345 :
346 0 : return ret >= 0;
347 : }
348 :
349 : bool
350 0 : HardwareAccel::linkHardware(AVBufferRef* framesCtx)
351 : {
352 0 : if (framesCtx) {
353 : // Force sw_format to match swFormat_. Frame is never transferred to main
354 : // memory when hardware is linked, so the sw_format doesn't matter.
355 0 : auto hw = reinterpret_cast<AVHWFramesContext*>(framesCtx->data);
356 0 : hw->sw_format = swFormat_;
357 :
358 0 : if (framesCtx_)
359 0 : av_buffer_unref(&framesCtx_);
360 0 : framesCtx_ = av_buffer_ref(framesCtx);
361 0 : if ((linked_ = (framesCtx_ != nullptr))) {
362 0 : JAMI_DBG() << "Hardware transcoding pipeline successfully set up for"
363 0 : << " encoder '" << getCodecName() << "'";
364 : }
365 0 : return linked_;
366 : } else {
367 0 : return false;
368 : }
369 : }
370 :
371 : std::unique_ptr<VideoFrame>
372 0 : HardwareAccel::transferToMainMemory(const VideoFrame& frame, AVPixelFormat desiredFormat)
373 : {
374 0 : auto input = frame.pointer();
375 0 : if (not input)
376 0 : throw std::runtime_error("Unable to transfer null frame");
377 :
378 0 : auto desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format));
379 0 : if (!desc) {
380 0 : throw std::runtime_error("Unable to transfer frame with invalid format");
381 : }
382 :
383 0 : auto out = std::make_unique<VideoFrame>();
384 0 : if (not(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
385 0 : out->copyFrom(frame);
386 0 : return out;
387 : }
388 :
389 0 : auto output = out->pointer();
390 0 : output->format = desiredFormat;
391 :
392 0 : int ret = av_hwframe_transfer_data(output, input, 0);
393 0 : if (ret < 0) {
394 0 : throw std::runtime_error("Unable to transfer the frame from GPU");
395 : }
396 :
397 0 : output->pts = input->pts;
398 0 : if (AVFrameSideData* side_data = av_frame_get_side_data(input, AV_FRAME_DATA_DISPLAYMATRIX))
399 0 : av_frame_new_side_data_from_buf(output,
400 : AV_FRAME_DATA_DISPLAYMATRIX,
401 0 : av_buffer_ref(side_data->buf));
402 0 : return out;
403 0 : }
404 :
405 : int
406 448 : HardwareAccel::initAPI(bool linkable, AVBufferRef* framesCtx)
407 : {
408 448 : const auto& codecName = getCodecName();
409 448 : std::string device;
410 448 : auto ret = init_device_type(device);
411 448 : if (ret == 0) {
412 0 : bool link = false;
413 0 : if (linkable && framesCtx)
414 0 : link = linkHardware(framesCtx);
415 : // we don't need frame context for videotoolbox
416 0 : if (hwType_ == AV_HWDEVICE_TYPE_VIDEOTOOLBOX || link || initFrame()) {
417 0 : return 0;
418 : }
419 : }
420 448 : return -1;
421 448 : }
422 :
423 : std::list<HardwareAccel>
424 358 : HardwareAccel::getCompatibleAccel(AVCodecID id, int width, int height, CodecType type)
425 : {
426 358 : std::list<HardwareAccel> l;
427 358 : const auto& list = (type == CODEC_ENCODER) ? &apiListEnc : &apiListDec;
428 2148 : for (auto& api : *list) {
429 1790 : const auto& it = std::find(api.supportedCodecs.begin(), api.supportedCodecs.end(), id);
430 1790 : if (it != api.supportedCodecs.end()) {
431 759 : auto hwtype = AV_HWDEVICE_TYPE_NONE;
432 3036 : while ((hwtype = av_hwdevice_iterate_types(hwtype)) != AV_HWDEVICE_TYPE_NONE) {
433 2277 : if (hwtype == api.hwType) {
434 : auto accel = HardwareAccel(id,
435 448 : api.name,
436 : api.hwType,
437 : api.format,
438 : api.swFormat,
439 : type,
440 448 : api.dynBitrate);
441 448 : accel.height_ = height;
442 448 : accel.width_ = width;
443 448 : accel.possible_devices_ = &api.possible_devices;
444 448 : l.emplace_back(std::move(accel));
445 448 : }
446 : }
447 : }
448 : }
449 716 : return l;
450 0 : }
451 :
452 : } // namespace video
453 : } // namespace jami
|