Skip to content
Snippets Groups Projects
Select Git revision
  • e20b46a85ba9597fa36260aa82dad77cb55164e8
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/windowsReleaseTest
  • release/releaseTest
  • release/releaseWindowsTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 4.0.0
  • 2.2.0
  • 2.1.0
  • 2.0.1
  • 2.0.0
  • 1.4.1
  • 1.4.0
  • 1.3.0
  • 1.2.0
  • 1.1.0
31 results

accel.cpp

Blame
    • Adrien Béraud's avatar
      e20b46a8
      build: add missing HAVE_CONFIG_H · e20b46a8
      Adrien Béraud authored
      Support building without config.h if the build system supports it.
      Currently, some file have a config.h header guard and some files don't.
      
      Make sure all files use the config.h header guard.
      
      Change-Id: I461255f8d3773d47e8dc19fff9f2a12b3cab1965
      e20b46a8
      History
      build: add missing HAVE_CONFIG_H
      Adrien Béraud authored
      Support building without config.h if the build system supports it.
      Currently, some file have a config.h header guard and some files don't.
      
      Make sure all files use the config.h header guard.
      
      Change-Id: I461255f8d3773d47e8dc19fff9f2a12b3cab1965
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    accel.cpp 14.65 KiB
    /*
     *  Copyright (C) 2004-2022 Savoir-faire Linux Inc.
     *
     *  Author: Philippe Gorley <philippe.gorley@savoirfairelinux.com>
     *  Author: Pierre Lespagnol <pierre.lespagnol@savoirfairelinux.com>
     *
     *  This program is free software; you can redistribute it and/or modify
     *  it under the terms of the GNU General Public License as published by
     *  the Free Software Foundation; either version 3 of the License, or
     *  (at your option) any later version.
     *
     *  This program is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     *  GNU General Public License for more details.
     *
     *  You should have received a copy of the GNU General Public License
     *  along with this program; if not, write to the Free Software
     *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
     */
    
    #include <algorithm>
    
    #ifdef HAVE_CONFIG_H
    #include "config.h"
    #endif
    
    #include "media_buffer.h"
    #include "string_utils.h"
    #include "fileutils.h"
    #include "logger.h"
    #include "accel.h"
    
    namespace jami {
    namespace video {
    
    struct HardwareAPI
    {
        std::string name;
        AVHWDeviceType hwType;
        AVPixelFormat format;
        AVPixelFormat swFormat;
        std::vector<AVCodecID> supportedCodecs;
        std::list<std::pair<std::string, DeviceState>> possible_devices;
        bool dynBitrate;
    };
    
    
    static std::list<HardwareAPI> apiListDec = {
        {"nvdec",
         AV_HWDEVICE_TYPE_CUDA,
         AV_PIX_FMT_CUDA,
         AV_PIX_FMT_NV12,
         {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_VP8, AV_CODEC_ID_MJPEG},
         {{"default", DeviceState::NOT_TESTED}, {"1", DeviceState::NOT_TESTED}, {"2", DeviceState::NOT_TESTED}},
         false},
        {"vaapi",
         AV_HWDEVICE_TYPE_VAAPI,
         AV_PIX_FMT_VAAPI,
         AV_PIX_FMT_NV12,
         {AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4, AV_CODEC_ID_VP8},
         {{"default", DeviceState::NOT_TESTED}, {"/dev/dri/renderD128", DeviceState::NOT_TESTED},
          {"/dev/dri/renderD129", DeviceState::NOT_TESTED}, {":0", DeviceState::NOT_TESTED}},
         false},
        {"vdpau",
         AV_HWDEVICE_TYPE_VDPAU,
         AV_PIX_FMT_VDPAU,
         AV_PIX_FMT_NV12,
         {AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4},
         {{"default", DeviceState::NOT_TESTED}},
         false},
        {"videotoolbox",
         AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
         AV_PIX_FMT_VIDEOTOOLBOX,
         AV_PIX_FMT_NV12,
         {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MPEG4},
         {{"default", DeviceState::NOT_TESTED}},
         false},
        {"qsv",
         AV_HWDEVICE_TYPE_QSV,
         AV_PIX_FMT_QSV,
         AV_PIX_FMT_NV12,
         {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8, AV_CODEC_ID_VP9},
         {{"default", DeviceState::NOT_TESTED}},
         false},
    };
    
    static std::list<HardwareAPI> apiListEnc = {
        {"nvenc",
         AV_HWDEVICE_TYPE_CUDA,
         AV_PIX_FMT_CUDA,
         AV_PIX_FMT_NV12,
         {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC},
         {{"default", DeviceState::NOT_TESTED}, {"1", DeviceState::NOT_TESTED}, {"2", DeviceState::NOT_TESTED}},
         true},
        {"vaapi",
         AV_HWDEVICE_TYPE_VAAPI,
         AV_PIX_FMT_VAAPI,
         AV_PIX_FMT_NV12,
         {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_VP8},
         {{"default", DeviceState::NOT_TESTED}, {"/dev/dri/renderD128", DeviceState::NOT_TESTED},
          {"/dev/dri/renderD129", DeviceState::NOT_TESTED},
          {":0", DeviceState::NOT_TESTED}},
         false},
        {"videotoolbox",
         AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
         AV_PIX_FMT_VIDEOTOOLBOX,
         AV_PIX_FMT_NV12,
         {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC},
         {{"default", DeviceState::NOT_TESTED}},
         false},
        // Disable temporarily QSVENC
        // {"qsv",
        // AV_HWDEVICE_TYPE_QSV,
        //  AV_PIX_FMT_QSV,
        //  AV_PIX_FMT_NV12,
        // {AV_CODEC_ID_H264, AV_CODEC_ID_HEVC, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8},
        // {{"default", DeviceState::NOT_TESTED}},
        // false},
    };
    
    HardwareAccel::HardwareAccel(AVCodecID id,
                                 const std::string& name,
                                 AVHWDeviceType hwType,
                                 AVPixelFormat format,
                                 AVPixelFormat swFormat,
                                 CodecType type,
                                 bool dynBitrate)
        : id_(id)
        , name_(name)
        , hwType_(hwType)
        , format_(format)
        , swFormat_(swFormat)
        , type_(type)
        , dynBitrate_(dynBitrate)
    {}
    
    HardwareAccel::~HardwareAccel()
    {
        if (deviceCtx_)
            av_buffer_unref(&deviceCtx_);
        if (framesCtx_)
            av_buffer_unref(&framesCtx_);
    }
    
    static AVPixelFormat
    getFormatCb(AVCodecContext* codecCtx, const AVPixelFormat* formats)
    {
        auto accel = static_cast<HardwareAccel*>(codecCtx->opaque);
    
        for (int i = 0; formats[i] != AV_PIX_FMT_NONE; ++i) {
            if (accel && formats[i] == accel->getFormat()) {
                // found hardware format for codec with api
                JAMI_DBG() << "Found compatible hardware format for "
                           << avcodec_get_name(static_cast<AVCodecID>(accel->getCodecId()))
                           << " decoder with " << accel->getName();
                // hardware tends to under-report supported levels
                codecCtx->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
                return formats[i];
            }
        }
        return AV_PIX_FMT_NONE;
    }
    
    int
    HardwareAccel::init_device(const char* name, const char* device, int flags)
    {
        const AVHWDeviceContext* dev = nullptr;
    
        // Create device ctx
        int err;
        err = av_hwdevice_ctx_create(&deviceCtx_, hwType_, device, NULL, flags);
        if (err < 0) {
            JAMI_DBG("Failed to create %s device: %d.\n", name, err);
            return 1;
        }
    
        // Verify that the device create correspond to api
        dev = (AVHWDeviceContext*) deviceCtx_->data;
        if (dev->type != hwType_) {
            JAMI_DBG("Device created as type %d has type %d.", hwType_, dev->type);
            av_buffer_unref(&deviceCtx_);
            return -1;
        }
        JAMI_DBG("Device type %s successfully created.", name);
    
        return 0;
    }
    
    int
    HardwareAccel::init_device_type(std::string& dev)
    {
        AVHWDeviceType check;
        const char* name;
        int err;
    
        name = av_hwdevice_get_type_name(hwType_);
        if (!name) {
            JAMI_DBG("No name available for device type %d.", hwType_);
            return -1;
        }
    
        check = av_hwdevice_find_type_by_name(name);
        if (check != hwType_) {
            JAMI_DBG("Type %d maps to name %s maps to type %d.", hwType_, name, check);
            return -1;
        }
    
        JAMI_WARN("-- Starting %s init for %s with default device.",
                  (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
                  name);
        if (possible_devices_->front().second != DeviceState::NOT_USABLE) {
            if (name_ == "qsv")
                err = init_device(name, "auto", 0);
            else
                err = init_device(name, nullptr, 0);
            if (err == 0) {
                JAMI_DBG("-- Init passed for %s with default device.", name);
                possible_devices_->front().second = DeviceState::USABLE;
                dev = "default";
                return 0;
            } else {
                possible_devices_->front().second = DeviceState::NOT_USABLE;
                JAMI_DBG("-- Init failed for %s with default device.", name);
            }
        }
    
        for (auto& device : *possible_devices_) {
            if (device.second == DeviceState::NOT_USABLE)
                continue;
            JAMI_WARN("-- Init %s for %s with device %s.",
                      (type_ == CODEC_ENCODER) ? "encoding" : "decoding",
                      name,
                      device.first.c_str());
            err = init_device(name, device.first.c_str(), 0);
            if (err == 0) {
                JAMI_DBG("-- Init passed for %s with device %s.", name, device.first.c_str());
                device.second = DeviceState::USABLE;
                dev = device.first;
                return 0;
            } else {
                device.second = DeviceState::NOT_USABLE;
                JAMI_DBG("-- Init failed for %s with device %s.", name, device.first.c_str());
            }
        }
        return -1;
    }
    
    std::string
    HardwareAccel::getCodecName() const
    {
        if (type_ == CODEC_DECODER) {
            return avcodec_get_name(id_);
        } else if (type_ == CODEC_ENCODER) {
            return fmt::format("{}_{}", avcodec_get_name(id_), name_);
        }
        return {};
    }
    
    std::unique_ptr<VideoFrame>
    HardwareAccel::transfer(const VideoFrame& frame)
    {
        int ret = 0;
        if (type_ == CODEC_DECODER) {
            auto input = frame.pointer();
            if (input->format != format_) {
                JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(format_)
                           << ", got "
                           << av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format));
                return nullptr;
            }
    
            return transferToMainMemory(frame, swFormat_);
        } else if (type_ == CODEC_ENCODER) {
            auto input = frame.pointer();
            if (input->format != swFormat_) {
                JAMI_ERR() << "Frame format mismatch: expected " << av_get_pix_fmt_name(swFormat_)
                           << ", got "
                           << av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format));
                return nullptr;
            }
    
            auto framePtr = std::make_unique<VideoFrame>();
            auto hwFrame = framePtr->pointer();
    
            if ((ret = av_hwframe_get_buffer(framesCtx_, hwFrame, 0)) < 0) {
                JAMI_ERR() << "Failed to allocate hardware buffer: "
                           << libav_utils::getError(ret).c_str();
                return nullptr;
            }
    
            if (!hwFrame->hw_frames_ctx) {
                JAMI_ERR() << "Failed to allocate hardware buffer: Cannot allocate memory";
                return nullptr;
            }
    
            if ((ret = av_hwframe_transfer_data(hwFrame, input, 0)) < 0) {
                JAMI_ERR() << "Failed to push frame to GPU: " << libav_utils::getError(ret).c_str();
                return nullptr;
            }
    
            hwFrame->pts = input->pts; // transfer does not copy timestamp
            return framePtr;
        } else {
            JAMI_ERR() << "Invalid hardware accelerator";
            return nullptr;
        }
    }
    
    void
    HardwareAccel::setDetails(AVCodecContext* codecCtx)
    {
        if (type_ == CODEC_DECODER) {
            codecCtx->hw_device_ctx = av_buffer_ref(deviceCtx_);
            codecCtx->get_format = getFormatCb;
            codecCtx->thread_safe_callbacks = 1;
        } else if (type_ == CODEC_ENCODER) {
            if (framesCtx_)
                // encoder doesn't need a device context, only a frame context
                codecCtx->hw_frames_ctx = av_buffer_ref(framesCtx_);
        }
    }
    
    bool
    HardwareAccel::initFrame()
    {
        int ret = 0;
        if (!deviceCtx_) {
            JAMI_ERR() << "Cannot initialize hardware frames without a valid hardware device";
            return false;
        }
    
        framesCtx_ = av_hwframe_ctx_alloc(deviceCtx_);
        if (!framesCtx_)
            return false;
    
        auto ctx = reinterpret_cast<AVHWFramesContext*>(framesCtx_->data);
        ctx->format = format_;
        ctx->sw_format = swFormat_;
        ctx->width = width_;
        ctx->height = height_;
        ctx->initial_pool_size = 20; // TODO try other values
    
        if ((ret = av_hwframe_ctx_init(framesCtx_)) < 0) {
            JAMI_ERR("Failed to initialize hardware frame context: %s (%d)",
                     libav_utils::getError(ret).c_str(),
                     ret);
            av_buffer_unref(&framesCtx_);
        }
    
        return ret >= 0;
    }
    
    bool
    HardwareAccel::linkHardware(AVBufferRef* framesCtx)
    {
        if (framesCtx) {
            // Force sw_format to match swFormat_. Frame is never transferred to main
            // memory when hardware is linked, so the sw_format doesn't matter.
            auto hw = reinterpret_cast<AVHWFramesContext*>(framesCtx->data);
            hw->sw_format = swFormat_;
    
            if (framesCtx_)
                av_buffer_unref(&framesCtx_);
            framesCtx_ = av_buffer_ref(framesCtx);
            if ((linked_ = (framesCtx_ != nullptr))) {
                JAMI_DBG() << "Hardware transcoding pipeline successfully set up for"
                           << " encoder '" << getCodecName() << "'";
            }
            return linked_;
        } else {
            return false;
        }
    }
    
    std::unique_ptr<VideoFrame>
    HardwareAccel::transferToMainMemory(const VideoFrame& frame, AVPixelFormat desiredFormat)
    {
        auto input = frame.pointer();
        if (not input)
            throw std::runtime_error("Cannot transfer null frame");
    
        auto desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format));
        if (!desc) {
            throw std::runtime_error("Cannot transfer frame with invalid format");
        }
    
        auto out = std::make_unique<VideoFrame>();
        if (not(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
            out->copyFrom(frame);
            return out;
        }
    
        auto output = out->pointer();
        output->format = desiredFormat;
    
        int ret = av_hwframe_transfer_data(output, input, 0);
        if (ret < 0) {
            throw std::runtime_error("Cannot transfer the frame from GPU");
        }
    
        output->pts = input->pts;
        if (AVFrameSideData* side_data = av_frame_get_side_data(input, AV_FRAME_DATA_DISPLAYMATRIX))
            av_frame_new_side_data_from_buf(output,
                                            AV_FRAME_DATA_DISPLAYMATRIX,
                                            av_buffer_ref(side_data->buf));
        return out;
    }
    
    int
    HardwareAccel::initAPI(bool linkable, AVBufferRef* framesCtx)
    {
        const auto& codecName = getCodecName();
        std::string device;
        auto ret = init_device_type(device);
        if (ret == 0) {
            bool link = false;
            if (linkable && framesCtx)
                link = linkHardware(framesCtx);
            // we don't need frame context for videotoolbox
            if (hwType_ == AV_HWDEVICE_TYPE_VIDEOTOOLBOX || link || initFrame()) {
                return 0;
            }
        }
        return -1;
    }
    
    std::list<HardwareAccel>
    HardwareAccel::getCompatibleAccel(AVCodecID id, int width, int height, CodecType type)
    {
        std::list<HardwareAccel> l;
        const auto& list = (type == CODEC_ENCODER) ? &apiListEnc : &apiListDec;
        for (auto& api : *list) {
            const auto& it = std::find(api.supportedCodecs.begin(), api.supportedCodecs.end(), id);
            if (it != api.supportedCodecs.end()) {
                auto hwtype = AV_HWDEVICE_TYPE_NONE;
                while ((hwtype = av_hwdevice_iterate_types(hwtype)) != AV_HWDEVICE_TYPE_NONE) {
                    if (hwtype == api.hwType) {
                        auto accel = HardwareAccel(id,
                                                   api.name,
                                                   api.hwType,
                                                   api.format,
                                                   api.swFormat,
                                                   type,
                                                   api.dynBitrate);
                        accel.height_ = height;
                        accel.width_ = width;
                        accel.possible_devices_ = &api.possible_devices;
                        l.emplace_back(std::move(accel));
                    }
                }
            }
        }
        return l;
    }
    
    } // namespace video
    } // namespace jami