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-12-21 08:56:24 Functions: 7 13 53.8 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include <algorithm>
      19             : 
      20             : #ifdef HAVE_CONFIG_H
      21             : #include "config.h"
      22             : #endif
      23             : 
      24             : #include "media_buffer.h"
      25             : #include "string_utils.h"
      26             : #include "fileutils.h"
      27             : #include "logger.h"
      28             : #include "accel.h"
      29             : 
      30             : namespace jami {
      31             : namespace video {
      32             : 
      33             : struct HardwareAPI
      34             : {
      35             :     std::string name;
      36             :     AVHWDeviceType hwType;
      37             :     AVPixelFormat format;
      38             :     AVPixelFormat swFormat;
      39             :     std::vector<AVCodecID> supportedCodecs;
      40             :     std::list<std::pair<std::string, DeviceState>> possible_devices;
      41             :     bool dynBitrate;
      42             : };
      43             : 
      44             : 
      45             : static std::list<HardwareAPI> apiListDec = {
      46             :     {"nvdec",
      47             :      AV_HWDEVICE_TYPE_CUDA,
      48             :      AV_PIX_FMT_CUDA,
      49             :      AV_PIX_FMT_NV12,
      50             :      {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_VP8, AV_CODEC_ID_MJPEG},
      51             :      {{"default", DeviceState::NOT_TESTED}, {"1", DeviceState::NOT_TESTED}, {"2", DeviceState::NOT_TESTED}},
      52             :      false},
      53             :     {"vaapi",
      54             :      AV_HWDEVICE_TYPE_VAAPI,
      55             :      AV_PIX_FMT_VAAPI,
      56             :      AV_PIX_FMT_NV12,
      57             :      {AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4, AV_CODEC_ID_VP8},
      58             :      {{"default", DeviceState::NOT_TESTED}, {"/dev/dri/renderD128", DeviceState::NOT_TESTED},
      59             :       {"/dev/dri/renderD129", DeviceState::NOT_TESTED}, {":0", DeviceState::NOT_TESTED}},
      60             :      false},
      61             :     {"vdpau",
      62             :      AV_HWDEVICE_TYPE_VDPAU,
      63             :      AV_PIX_FMT_VDPAU,
      64             :      AV_PIX_FMT_NV12,
      65             :      {AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4},
      66             :      {{"default", DeviceState::NOT_TESTED}},
      67             :      false},
      68             :     {"videotoolbox",
      69             :      AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
      70             :      AV_PIX_FMT_VIDEOTOOLBOX,
      71             :      AV_PIX_FMT_NV12,
      72             :      {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MPEG4},
      73             :      {{"default", DeviceState::NOT_TESTED}},
      74             :      false},
      75             :     {"qsv",
      76             :      AV_HWDEVICE_TYPE_QSV,
      77             :      AV_PIX_FMT_QSV,
      78             :      AV_PIX_FMT_NV12,
      79             :      {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8, AV_CODEC_ID_VP9},
      80             :      {{"default", DeviceState::NOT_TESTED}},
      81             :      false},
      82             : };
      83             : 
      84             : static std::list<HardwareAPI> apiListEnc = {
      85             :     {"nvenc",
      86             :      AV_HWDEVICE_TYPE_CUDA,
      87             :      AV_PIX_FMT_CUDA,
      88             :      AV_PIX_FMT_NV12,
      89             :      {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC},
      90             :      {{"default", DeviceState::NOT_TESTED}, {"1", DeviceState::NOT_TESTED}, {"2", DeviceState::NOT_TESTED}},
      91             :      true},
      92             :     {"vaapi",
      93             :      AV_HWDEVICE_TYPE_VAAPI,
      94             :      AV_PIX_FMT_VAAPI,
      95             :      AV_PIX_FMT_NV12,
      96             :      {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_VP8},
      97             :      {{"default", DeviceState::NOT_TESTED}, {"/dev/dri/renderD128", DeviceState::NOT_TESTED},
      98             :       {"/dev/dri/renderD129", DeviceState::NOT_TESTED},
      99             :       {":0", DeviceState::NOT_TESTED}},
     100             :      false},
     101             :     {"videotoolbox",
     102             :      AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
     103             :      AV_PIX_FMT_VIDEOTOOLBOX,
     104             :      AV_PIX_FMT_NV12,
     105             :      {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC},
     106             :      {{"default", DeviceState::NOT_TESTED}},
     107             :      false},
     108             :     // Disable temporarily QSVENC
     109             :     // {"qsv",
     110             :     // AV_HWDEVICE_TYPE_QSV,
     111             :     //  AV_PIX_FMT_QSV,
     112             :     //  AV_PIX_FMT_NV12,
     113             :     // {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8},
     114             :     // {{"default", DeviceState::NOT_TESTED}},
     115             :     // false},
     116             : };
     117             : 
     118         445 : HardwareAccel::HardwareAccel(AVCodecID id,
     119             :                              const std::string& name,
     120             :                              AVHWDeviceType hwType,
     121             :                              AVPixelFormat format,
     122             :                              AVPixelFormat swFormat,
     123             :                              CodecType type,
     124         445 :                              bool dynBitrate)
     125         445 :     : id_(id)
     126         445 :     , name_(name)
     127         445 :     , hwType_(hwType)
     128         445 :     , format_(format)
     129         445 :     , swFormat_(swFormat)
     130         445 :     , type_(type)
     131         445 :     , dynBitrate_(dynBitrate)
     132         445 : {}
     133             : 
     134        1335 : HardwareAccel::~HardwareAccel()
     135             : {
     136        1335 :     if (deviceCtx_)
     137           0 :         av_buffer_unref(&deviceCtx_);
     138        1335 :     if (framesCtx_)
     139           0 :         av_buffer_unref(&framesCtx_);
     140        1335 : }
     141             : 
     142             : static AVPixelFormat
     143           0 : getFormatCb(AVCodecContext* codecCtx, const AVPixelFormat* formats)
     144             : {
     145           0 :     auto accel = static_cast<HardwareAccel*>(codecCtx->opaque);
     146             : 
     147           0 :     for (int i = 0; formats[i] != AV_PIX_FMT_NONE; ++i) {
     148           0 :         if (accel && formats[i] == accel->getFormat()) {
     149             :             // found hardware format for codec with api
     150           0 :             JAMI_DBG() << "Found compatible hardware format for "
     151           0 :                        << avcodec_get_name(static_cast<AVCodecID>(accel->getCodecId()))
     152           0 :                        << " decoder with " << accel->getName();
     153             :             // hardware tends to under-report supported levels
     154           0 :             codecCtx->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
     155           0 :             return formats[i];
     156             :         }
     157             :     }
     158           0 :     return AV_PIX_FMT_NONE;
     159             : }
     160             : 
     161             : int
     162          75 : HardwareAccel::init_device(const char* name, const char* device, int flags)
     163             : {
     164          75 :     const AVHWDeviceContext* dev = nullptr;
     165             : 
     166             :     // Create device ctx
     167             :     int err;
     168          75 :     err = av_hwdevice_ctx_create(&deviceCtx_, hwType_, device, NULL, flags);
     169          75 :     if (err < 0) {
     170          75 :         JAMI_DBG("Failed to create %s device: %d.\n", name, err);
     171          75 :         return 1;
     172             :     }
     173             : 
     174             :     // Verify that the device create correspond to api
     175           0 :     dev = (AVHWDeviceContext*) deviceCtx_->data;
     176           0 :     if (dev->type != hwType_) {
     177           0 :         JAMI_DBG("Device created as type %d has type %d.", hwType_, dev->type);
     178           0 :         av_buffer_unref(&deviceCtx_);
     179           0 :         return -1;
     180             :     }
     181           0 :     JAMI_DBG("Device type %s successfully created.", name);
     182             : 
     183           0 :     return 0;
     184             : }
     185             : 
     186             : int
     187         445 : HardwareAccel::init_device_type(std::string& dev)
     188             : {
     189             :     AVHWDeviceType check;
     190             :     const char* name;
     191             :     int err;
     192             : 
     193         445 :     name = av_hwdevice_get_type_name(hwType_);
     194         445 :     if (!name) {
     195           0 :         JAMI_DBG("No name available for device type %d.", hwType_);
     196           0 :         return -1;
     197             :     }
     198             : 
     199         445 :     check = av_hwdevice_find_type_by_name(name);
     200         445 :     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         445 :     JAMI_WARN("-- Starting %s init for %s with default device.",
     206             :               (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
     207             :               name);
     208         445 :     if (possible_devices_->front().second != DeviceState::NOT_USABLE) {
     209          28 :         if (name_ == "qsv")
     210           0 :             err = init_device(name, "auto", 0);
     211             :         else
     212          28 :             err = init_device(name, nullptr, 0);
     213          28 :         if (err == 0) {
     214           0 :             JAMI_DBG("-- Init passed for %s with default device.", name);
     215           0 :             possible_devices_->front().second = DeviceState::USABLE;
     216           0 :             dev = "default";
     217           0 :             return 0;
     218             :         } else {
     219          28 :             possible_devices_->front().second = DeviceState::NOT_USABLE;
     220          28 :             JAMI_DBG("-- Init failed for %s with default device.", name);
     221             :         }
     222             :     }
     223             : 
     224        1635 :     for (auto& device : *possible_devices_) {
     225        1190 :         if (device.second == DeviceState::NOT_USABLE)
     226        1143 :             continue;
     227          47 :         JAMI_WARN("-- Init %s for %s with device %s.",
     228             :                   (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
     229             :                   name,
     230             :                   device.first.c_str());
     231          47 :         err = init_device(name, device.first.c_str(), 0);
     232          47 :         if (err == 0) {
     233           0 :             JAMI_DBG("-- Init passed for %s with device %s.", name, device.first.c_str());
     234           0 :             device.second = DeviceState::USABLE;
     235           0 :             dev = device.first;
     236           0 :             return 0;
     237             :         } else {
     238          47 :             device.second = DeviceState::NOT_USABLE;
     239          47 :             JAMI_DBG("-- Init failed for %s with device %s.", name, device.first.c_str());
     240             :         }
     241             :     }
     242         445 :     return -1;
     243             : }
     244             : 
     245             : std::string
     246         445 : HardwareAccel::getCodecName() const
     247             : {
     248         445 :     if (type_ == CODEC_DECODER) {
     249         445 :         return avcodec_get_name(id_);
     250           0 :     } else if (type_ == CODEC_ENCODER) {
     251           0 :         return fmt::format("{}_{}", avcodec_get_name(id_), name_);
     252             :     }
     253           0 :     return {};
     254             : }
     255             : 
     256             : std::unique_ptr<VideoFrame>
     257           0 : HardwareAccel::transfer(const VideoFrame& frame)
     258             : {
     259           0 :     int ret = 0;
     260           0 :     if (type_ == CODEC_DECODER) {
     261           0 :         auto input = frame.pointer();
     262           0 :         if (input->format != format_) {
     263           0 :             JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(format_)
     264           0 :                        << ", got "
     265           0 :                        << av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format));
     266           0 :             return nullptr;
     267             :         }
     268             : 
     269           0 :         return transferToMainMemory(frame, swFormat_);
     270           0 :     } else if (type_ == CODEC_ENCODER) {
     271           0 :         auto input = frame.pointer();
     272           0 :         if (input->format != swFormat_) {
     273           0 :             JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(swFormat_)
     274           0 :                        << ", got "
     275           0 :                        << av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format));
     276           0 :             return nullptr;
     277             :         }
     278             : 
     279           0 :         auto framePtr = std::make_unique<VideoFrame>();
     280           0 :         auto hwFrame = framePtr->pointer();
     281             : 
     282           0 :         if ((ret = av_hwframe_get_buffer(framesCtx_, hwFrame, 0)) < 0) {
     283           0 :             JAMI_ERR() << "Failed to allocate hardware buffer: "
     284           0 :                        << libav_utils::getError(ret).c_str();
     285           0 :             return nullptr;
     286             :         }
     287             : 
     288           0 :         if (!hwFrame->hw_frames_ctx) {
     289           0 :             JAMI_ERR() << "Failed to allocate hardware buffer: Unable to allocate memory";
     290           0 :             return nullptr;
     291             :         }
     292             : 
     293           0 :         if ((ret = av_hwframe_transfer_data(hwFrame, input, 0)) < 0) {
     294           0 :             JAMI_ERR() << "Failed to push frame to GPU: " << libav_utils::getError(ret).c_str();
     295           0 :             return nullptr;
     296             :         }
     297             : 
     298           0 :         hwFrame->pts = input->pts; // transfer does not copy timestamp
     299           0 :         return framePtr;
     300           0 :     } else {
     301           0 :         JAMI_ERR() << "Invalid hardware accelerator";
     302           0 :         return nullptr;
     303             :     }
     304             : }
     305             : 
     306             : void
     307           0 : HardwareAccel::setDetails(AVCodecContext* codecCtx)
     308             : {
     309           0 :     if (type_ == CODEC_DECODER) {
     310           0 :         codecCtx->hw_device_ctx = av_buffer_ref(deviceCtx_);
     311           0 :         codecCtx->get_format = getFormatCb;
     312           0 :     } else if (type_ == CODEC_ENCODER) {
     313           0 :         if (framesCtx_)
     314             :             // encoder doesn't need a device context, only a frame context
     315           0 :             codecCtx->hw_frames_ctx = av_buffer_ref(framesCtx_);
     316             :     }
     317           0 : }
     318             : 
     319             : bool
     320           0 : HardwareAccel::initFrame()
     321             : {
     322           0 :     int ret = 0;
     323           0 :     if (!deviceCtx_) {
     324           0 :         JAMI_ERR() << "Unable to initialize hardware frames without a valid hardware device";
     325           0 :         return false;
     326             :     }
     327             : 
     328           0 :     framesCtx_ = av_hwframe_ctx_alloc(deviceCtx_);
     329           0 :     if (!framesCtx_)
     330           0 :         return false;
     331             : 
     332           0 :     auto ctx = reinterpret_cast<AVHWFramesContext*>(framesCtx_->data);
     333           0 :     ctx->format = format_;
     334           0 :     ctx->sw_format = swFormat_;
     335           0 :     ctx->width = width_;
     336           0 :     ctx->height = height_;
     337           0 :     ctx->initial_pool_size = 20; // TODO try other values
     338             : 
     339           0 :     if ((ret = av_hwframe_ctx_init(framesCtx_)) < 0) {
     340           0 :         JAMI_ERR("Failed to initialize hardware frame context: %s (%d)",
     341             :                  libav_utils::getError(ret).c_str(),
     342             :                  ret);
     343           0 :         av_buffer_unref(&framesCtx_);
     344             :     }
     345             : 
     346           0 :     return ret >= 0;
     347             : }
     348             : 
     349             : bool
     350           0 : HardwareAccel::linkHardware(AVBufferRef* framesCtx)
     351             : {
     352           0 :     if (framesCtx) {
     353             :         // Force sw_format to match swFormat_. Frame is never transferred to main
     354             :         // memory when hardware is linked, so the sw_format doesn't matter.
     355           0 :         auto hw = reinterpret_cast<AVHWFramesContext*>(framesCtx->data);
     356           0 :         hw->sw_format = swFormat_;
     357             : 
     358           0 :         if (framesCtx_)
     359           0 :             av_buffer_unref(&framesCtx_);
     360           0 :         framesCtx_ = av_buffer_ref(framesCtx);
     361           0 :         if ((linked_ = (framesCtx_ != nullptr))) {
     362           0 :             JAMI_DBG() << "Hardware transcoding pipeline successfully set up for"
     363           0 :                        << " encoder '" << getCodecName() << "'";
     364             :         }
     365           0 :         return linked_;
     366             :     } else {
     367           0 :         return false;
     368             :     }
     369             : }
     370             : 
     371             : std::unique_ptr<VideoFrame>
     372           0 : HardwareAccel::transferToMainMemory(const VideoFrame& frame, AVPixelFormat desiredFormat)
     373             : {
     374           0 :     auto input = frame.pointer();
     375           0 :     if (not input)
     376           0 :         throw std::runtime_error("Unable to transfer null frame");
     377             : 
     378           0 :     auto desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format));
     379           0 :     if (!desc) {
     380           0 :         throw std::runtime_error("Unable to transfer frame with invalid format");
     381             :     }
     382             : 
     383           0 :     auto out = std::make_unique<VideoFrame>();
     384           0 :     if (not(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
     385           0 :         out->copyFrom(frame);
     386           0 :         return out;
     387             :     }
     388             : 
     389           0 :     auto output = out->pointer();
     390           0 :     output->format = desiredFormat;
     391             : 
     392           0 :     int ret = av_hwframe_transfer_data(output, input, 0);
     393           0 :     if (ret < 0) {
     394           0 :         throw std::runtime_error("Unable to transfer the frame from GPU");
     395             :     }
     396             : 
     397           0 :     output->pts = input->pts;
     398           0 :     if (AVFrameSideData* side_data = av_frame_get_side_data(input, AV_FRAME_DATA_DISPLAYMATRIX))
     399           0 :         av_frame_new_side_data_from_buf(output,
     400             :                                         AV_FRAME_DATA_DISPLAYMATRIX,
     401           0 :                                         av_buffer_ref(side_data->buf));
     402           0 :     return out;
     403           0 : }
     404             : 
     405             : int
     406         445 : HardwareAccel::initAPI(bool linkable, AVBufferRef* framesCtx)
     407             : {
     408         445 :     const auto& codecName = getCodecName();
     409         445 :     std::string device;
     410         445 :     auto ret = init_device_type(device);
     411         445 :     if (ret == 0) {
     412           0 :         bool link = false;
     413           0 :         if (linkable && framesCtx)
     414           0 :             link = linkHardware(framesCtx);
     415             :         // we don't need frame context for videotoolbox
     416           0 :         if (hwType_ == AV_HWDEVICE_TYPE_VIDEOTOOLBOX || link || initFrame()) {
     417           0 :             return 0;
     418             :         }
     419             :     }
     420         445 :     return -1;
     421         445 : }
     422             : 
     423             : std::list<HardwareAccel>
     424         356 : HardwareAccel::getCompatibleAccel(AVCodecID id, int width, int height, CodecType type)
     425             : {
     426         356 :     std::list<HardwareAccel> l;
     427         356 :     const auto& list = (type == CODEC_ENCODER) ? &apiListEnc : &apiListDec;
     428        2136 :     for (auto& api : *list) {
     429        1780 :         const auto& it = std::find(api.supportedCodecs.begin(), api.supportedCodecs.end(), id);
     430        1780 :         if (it != api.supportedCodecs.end()) {
     431         754 :             auto hwtype = AV_HWDEVICE_TYPE_NONE;
     432        3016 :             while ((hwtype = av_hwdevice_iterate_types(hwtype)) != AV_HWDEVICE_TYPE_NONE) {
     433        2262 :                 if (hwtype == api.hwType) {
     434             :                     auto accel = HardwareAccel(id,
     435         445 :                                                api.name,
     436             :                                                api.hwType,
     437             :                                                api.format,
     438             :                                                api.swFormat,
     439             :                                                type,
     440         445 :                                                api.dynBitrate);
     441         445 :                     accel.height_ = height;
     442         445 :                     accel.width_ = width;
     443         445 :                     accel.possible_devices_ = &api.possible_devices;
     444         445 :                     l.emplace_back(std::move(accel));
     445         445 :                 }
     446             :             }
     447             :         }
     448             :     }
     449         712 :     return l;
     450           0 : }
     451             : 
     452             : } // namespace video
     453             : } // namespace jami

Generated by: LCOV version 1.14