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: 2025-12-18 10:07:43 Functions: 7 13 53.8 %

          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

Generated by: LCOV version 1.14