LCOV - code coverage report
Current view: top level - src/media/video - accel.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 68 203 33.5 %
Date: 2024-04-26 09:41:19 Functions: 7 13 53.8 %

          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

Generated by: LCOV version 1.14