LCOV - code coverage report
Current view: top level - src/media/video - accel.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 34.7 % 193 67
Test Date: 2026-06-13 09:18:46 Functions: 30.9 % 55 17

            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
        

Generated by: LCOV version 2.0-1