LCOV - code coverage report
Current view: top level - foo/src/media/video - accel.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 67 198 33.8 %
Date: 2026-02-28 10:41:24 Functions: 7 13 53.8 %

          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

Generated by: LCOV version 1.14