diff --git a/src/media/media_decoder.cpp b/src/media/media_decoder.cpp index 673721ebb07f47fa3504063da915ebe7ca95a947..e7455c07f6b38bf2b53ebd402e016076e738470a 100644 --- a/src/media/media_decoder.cpp +++ b/src/media/media_decoder.cpp @@ -271,12 +271,69 @@ MediaDecoder::setupStream() int ret = 0; avcodec_free_context(&decoderCtx_); + if(prepareDecoderContext() < 0) + return -1; // failed + #ifdef RING_ACCEL // if there was a fallback to software decoding, do not enable accel // it has been disabled already by the video_receive_thread/video_input enableAccel_ &= Manager::instance().videoPreferences.getDecodingAccelerated(); + + if (enableAccel_) { + auto APIs = video::HardwareAccel::getCompatibleAccel(decoderCtx_->codec_id, + decoderCtx_->width, decoderCtx_->height, CODEC_DECODER); + if (!APIs.empty()) { + for (const auto& it : APIs) { + accel_ = std::make_unique<video::HardwareAccel>(it); // save accel + auto ret = accel_->initAPI(false, nullptr); + if (ret < 0) { + accel_ = nullptr; + continue; + } + if(prepareDecoderContext() < 0) + return -1; // failed + accel_->setDetails(decoderCtx_); + decoderCtx_->opaque = accel_.get(); + decoderCtx_->pix_fmt = accel_->getFormat(); + if (avcodec_open2(decoderCtx_, inputDecoder_, &options_) < 0) { + // Failed to open codec + JAMI_WARN("Fail to open hardware decoder for %s with %s ", avcodec_get_name(decoderCtx_->codec_id), it.getName().c_str()); + avcodec_free_context(&decoderCtx_); + decoderCtx_ = nullptr; + accel_.reset(); + continue; + } else { + // Succeed to open codec + JAMI_WARN("Using hardware decoding for %s with %s ", avcodec_get_name(decoderCtx_->codec_id), it.getName().c_str()); + break; + } + } + } + } #endif + JAMI_DBG() << "Decoding " << av_get_media_type_string(avStream_->codecpar->codec_type) << " using " << inputDecoder_->long_name << " (" << inputDecoder_->name << ")"; + + decoderCtx_->thread_count = std::max(1u, std::min(8u, std::thread::hardware_concurrency()/2)); + if (emulateRate_) + JAMI_DBG() << "Using framerate emulation"; + startTime_ = av_gettime(); // used to set pts after decoding, and for rate emulation + + if(!accel_) { + JAMI_WARN("Not using hardware decoding for %s", avcodec_get_name(decoderCtx_->codec_id)); + ret = avcodec_open2(decoderCtx_, inputDecoder_, nullptr); + } + if (ret < 0) { + JAMI_ERR() << "Could not open codec: " << libav_utils::getError(ret); + return -1; + } + + return 0; +} + +int +MediaDecoder::prepareDecoderContext() +{ inputDecoder_ = findDecoder(avStream_->codecpar->codec_id); if (!inputDecoder_) { JAMI_ERR() << "Unsupported codec"; @@ -297,36 +354,7 @@ MediaDecoder::setupStream() decoderCtx_->framerate = av_inv_q(decoderCtx_->time_base); if (decoderCtx_->framerate.num == 0 || decoderCtx_->framerate.den == 0) decoderCtx_->framerate = {30, 1}; - -#ifdef RING_ACCEL - if (enableAccel_) { - accel_ = video::HardwareAccel::setupDecoder(decoderCtx_->codec_id, - decoderCtx_->width, decoderCtx_->height); - if (accel_) { - accel_->setDetails(decoderCtx_); - decoderCtx_->opaque = accel_.get(); - } - } else if (Manager::instance().videoPreferences.getDecodingAccelerated()) { - JAMI_WARN() << "Hardware decoding disabled because of previous failure"; - } else { - JAMI_WARN() << "Hardware decoding disabled by user preference"; - } -#endif - } - - JAMI_DBG() << "Decoding " << av_get_media_type_string(avStream_->codecpar->codec_type) << " using " << inputDecoder_->long_name << " (" << inputDecoder_->name << ")"; - - decoderCtx_->thread_count = std::max(1u, std::min(8u, std::thread::hardware_concurrency()/2)); - if (emulateRate_) - JAMI_DBG() << "Using framerate emulation"; - startTime_ = av_gettime(); // used to set pts after decoding, and for rate emulation - - ret = avcodec_open2(decoderCtx_, inputDecoder_, nullptr); - if (ret < 0) { - JAMI_ERR() << "Could not open codec: " << libav_utils::getError(ret); - return -1; } - return 0; } diff --git a/src/media/media_decoder.h b/src/media/media_decoder.h index a85df45e18faa23e839c172b6691a7c0cc5d91ff..b2687ae9a51a9abc91b89d7878ac86f34da5729c 100644 --- a/src/media/media_decoder.h +++ b/src/media/media_decoder.h @@ -184,6 +184,7 @@ private: unsigned short accelFailures_ = 0; #endif MediaObserver callback_; + int prepareDecoderContext(); protected: AVDictionary *options_ = nullptr; diff --git a/src/media/media_encoder.cpp b/src/media/media_encoder.cpp index 182ebea4555e6a8626438a19e39f36bc03695f6d..2d3b15816458ccffe6efe3c497415d7613afe06c 100644 --- a/src/media/media_encoder.cpp +++ b/src/media/media_encoder.cpp @@ -198,7 +198,6 @@ MediaEncoder::initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* fr else if(systemCodecInfo.mediaType == MEDIA_AUDIO) mediaType = AVMEDIA_TYPE_AUDIO; - encoderCtx = initCodec(mediaType, static_cast<AVCodecID>(systemCodecInfo.avcodecId), framesCtx, SystemCodecInfo::DEFAULT_VIDEO_BITRATE); // add video stream to outputformat context AVStream* stream = avformat_new_stream(outputCtx_, outputCodec_); @@ -206,10 +205,59 @@ MediaEncoder::initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* fr throw MediaEncoderException("Could not allocate stream"); currentStreamIdx_ = stream->index; +#ifdef RING_ACCEL + // Get compatible list of Hardware API + if (enableAccel_ && mediaType == AVMEDIA_TYPE_VIDEO) { + auto APIs = video::HardwareAccel::getCompatibleAccel(static_cast<AVCodecID>(systemCodecInfo.avcodecId), + videoOpts_.width, videoOpts_.height, CODEC_ENCODER); + + if (APIs.size() > 0) { + for (const auto& it : APIs) { + accel_ = std::make_unique<video::HardwareAccel>(it); // save accel + // Init codec need accel_ to init encoderCtx accelerated + encoderCtx = initCodec(mediaType, static_cast<AVCodecID>(systemCodecInfo.avcodecId), SystemCodecInfo::DEFAULT_VIDEO_BITRATE); + encoderCtx->opaque = accel_.get(); + // Check if pixel format from encoder match pixel format from decoder frame context + // if it mismatch, it means that we are using two different hardware API (nvenc and vaapi for example) + // in this case we don't want link the APIs + if (framesCtx) { + auto hw = reinterpret_cast<AVHWFramesContext*>(framesCtx->data); + if (encoderCtx->pix_fmt != hw->format) + linkableHW_ = false; + } + auto ret = accel_->initAPI(linkableHW_, framesCtx); + if (ret < 0) { + accel_.reset(); + encoderCtx = nullptr; + continue; + } + accel_->setDetails(encoderCtx); + if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0) { + // Fail opening codec + JAMI_WARN("Fail to open hardware encoder %s with %s ", avcodec_get_name(static_cast<AVCodecID>(systemCodecInfo.avcodecId)), it.getName().c_str()); + avcodec_free_context(&encoderCtx); + encoderCtx = nullptr; + accel_ = nullptr; + continue; + } else { + // Success opening codec + JAMI_WARN("Using hardware encoding for %s with %s ", avcodec_get_name(static_cast<AVCodecID>(systemCodecInfo.avcodecId)), it.getName().c_str()); + encoders_.push_back(encoderCtx); + break; + } + } + } + } +#endif - readConfig(encoderCtx); - if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0) - throw MediaEncoderException("Could not open encoder"); + if (!encoderCtx) { + JAMI_WARN("Not using hardware encoding for %s", avcodec_get_name(static_cast<AVCodecID>(systemCodecInfo.avcodecId))); + encoderCtx = initCodec(mediaType, static_cast<AVCodecID>(systemCodecInfo.avcodecId), SystemCodecInfo::DEFAULT_VIDEO_BITRATE); + readConfig(encoderCtx); + encoders_.push_back(encoderCtx); + if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0) + throw MediaEncoderException("Could not open encoder"); + } #ifndef _WIN32 avcodec_parameters_from_context(stream->codecpar, encoderCtx); @@ -651,15 +699,13 @@ MediaEncoder::getStream(const std::string& name, int streamIdx) const } AVCodecContext* -MediaEncoder::initCodec(AVMediaType mediaType, AVCodecID avcodecId, AVBufferRef* framesCtx, uint64_t br) +MediaEncoder::initCodec(AVMediaType mediaType, AVCodecID avcodecId, uint64_t br) { outputCodec_ = nullptr; #ifdef RING_ACCEL if (mediaType == AVMEDIA_TYPE_VIDEO) { if (enableAccel_) { - if (accel_ = video::HardwareAccel::setupEncoder( - static_cast<AVCodecID>(avcodecId), - videoOpts_.width, videoOpts_.height, linkableHW_, framesCtx)) { + if (accel_) { outputCodec_ = avcodec_find_encoder_by_name(accel_->getCodecName().c_str()); } } else { @@ -683,14 +729,6 @@ MediaEncoder::initCodec(AVMediaType mediaType, AVCodecID avcodecId, AVBufferRef* } AVCodecContext* encoderCtx = prepareEncoderContext(outputCodec_, mediaType == AVMEDIA_TYPE_VIDEO); - encoders_.push_back(encoderCtx); - -#ifdef RING_ACCEL - if (accel_) { - accel_->setDetails(encoderCtx); - encoderCtx->opaque = accel_.get(); - } -#endif // Only clamp video bitrate if (mediaType == AVMEDIA_TYPE_VIDEO && br > 0) { @@ -743,7 +781,7 @@ MediaEncoder::setBitrate(uint64_t br) else { // restart encoder on runtime doesn't work for VP8 // stopEncoder(); - // encoderCtx = initCodec(codecType, codecId, NULL, br); + // encoderCtx = initCodec(codecType, codecId, br); // if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0) // throw MediaEncoderException("Could not open encoder"); } diff --git a/src/media/media_encoder.h b/src/media/media_encoder.h index 35ead17d993c3a6e0bebf0ebdd8425e4c10f69cf..b223afea036452edf60645af665cd9c5ca780c1c 100644 --- a/src/media/media_encoder.h +++ b/src/media/media_encoder.h @@ -120,7 +120,7 @@ private: void startIO(); AVCodecContext* getCurrentVideoAVCtx(); void stopEncoder(); - AVCodecContext* initCodec(AVMediaType mediaType, AVCodecID avcodecId, AVBufferRef* framesCtx, uint64_t br); + AVCodecContext* initCodec(AVMediaType mediaType, AVCodecID avcodecId, uint64_t br); std::vector<AVCodecContext*> encoders_; AVFormatContext *outputCtx_ = nullptr; diff --git a/src/media/video/accel.cpp b/src/media/video/accel.cpp index 3894d8124424033fdd25b589c0b76a5fbe1b2114..906d52b6355b0565c933e9f9f90eac3e0c9c8361 100644 --- a/src/media/video/accel.cpp +++ b/src/media/video/accel.cpp @@ -2,6 +2,7 @@ * Copyright (C) 2004-2020 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 @@ -19,6 +20,7 @@ */ #include <algorithm> +#include <thread> // hardware_concurrency #include "media_buffer.h" #include "string_utils.h" @@ -29,15 +31,38 @@ namespace jami { namespace video { -struct HardwareAPI -{ - std::string name; - AVHWDeviceType hwType; - AVPixelFormat format; - AVPixelFormat swFormat; - std::vector<AVCodecID> supportedCodecs; +static const std::list<HardwareAPI> apiListDec = { + { "nvdec", AV_HWDEVICE_TYPE_CUDA, AV_PIX_FMT_CUDA, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_H265, AV_CODEC_ID_VP8, AV_CODEC_ID_MJPEG }, { "0", "1", "2" } }, + { "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, AV_CODEC_ID_MJPEG }, { "/dev/dri/renderD128", "/dev/dri/renderD129", ":0" } }, + { "vdpau", AV_HWDEVICE_TYPE_VDPAU, AV_PIX_FMT_VDPAU, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4 }, { } }, + { "videotoolbox", AV_HWDEVICE_TYPE_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4 }, { } }, + { "qsv", AV_HWDEVICE_TYPE_QSV, AV_PIX_FMT_QSV, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_H265, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8, AV_CODEC_ID_VP9 }, { } }, +}; + +static const std::list<HardwareAPI> apiListEnc = { + { "nvenc", AV_HWDEVICE_TYPE_CUDA, AV_PIX_FMT_CUDA, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_H265 }, { "0", "1", "2" } }, + { "vaapi", AV_HWDEVICE_TYPE_VAAPI, AV_PIX_FMT_VAAPI, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8 }, { "/dev/dri/renderD128", "/dev/dri/renderD129", ":0" } }, + { "videotoolbox", AV_HWDEVICE_TYPE_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264 }, { } }, + { "qsv", AV_HWDEVICE_TYPE_QSV, AV_PIX_FMT_QSV, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_H265, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8 }, { } }, }; +HardwareAccel::HardwareAccel(AVCodecID id, const std::string& name, AVHWDeviceType hwType, AVPixelFormat format, AVPixelFormat swFormat, CodecType type) + : id_(id) + , name_(name) + , hwType_(hwType) + , format_(format) + , swFormat_(swFormat) + , type_(type) +{} + +HardwareAccel::~HardwareAccel() +{ + if (deviceCtx_) + av_buffer_unref(&deviceCtx_); + if (framesCtx_) + av_buffer_unref(&framesCtx_); +} + static AVPixelFormat getFormatCb(AVCodecContext* codecCtx, const AVPixelFormat* formats) { @@ -59,23 +84,81 @@ getFormatCb(AVCodecContext* codecCtx, const AVPixelFormat* formats) return fallback; } -HardwareAccel::HardwareAccel(AVCodecID id, const std::string& name, AVHWDeviceType hwType, AVPixelFormat format, AVPixelFormat swFormat, CodecType type) - : id_(id) - , name_(name) - , hwType_(hwType) - , format_(format) - , swFormat_(swFormat) - , type_(type) -{} - -HardwareAccel::~HardwareAccel() +int +HardwareAccel::test_device(const char* name, + const char* device, int flags) { - if (deviceCtx_) + 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_); - if (framesCtx_) - av_buffer_unref(&framesCtx_); + return -1; + } + JAMI_DBG("Device type %s successfully created.", name); + + return 0; } +int +HardwareAccel::test_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 test for %s with default device.", (type_ == CODEC_ENCODER) ? "encoding" : "decoding", name); + err = test_device(name, nullptr, 0); + if (err == 0) { + JAMI_DBG("-- Test passed for %s with default device.", name); + dev = "default"; + return 0; + } else { + JAMI_DBG("-- Test failed for %s with default device.", name); + } + + for (const auto& device : possible_devices_) { + JAMI_WARN("-- Starting %s test for %s with device %s.", (type_ == CODEC_ENCODER) ? "encoding" : "decoding", name, device.c_str()); + err = test_device(name, device.c_str(), 0); + if (err == 0) { + JAMI_DBG("-- Test passed for %s with device %s.", + name, device.c_str()); + dev = device; + return 0; + } + else { + JAMI_DBG("-- Test failed for %s with device %s.", + name, device.c_str()); + } + } + return -1; +} + + std::string HardwareAccel::getCodecName() const { @@ -116,7 +199,7 @@ HardwareAccel::transfer(const VideoFrame& frame) 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); + JAMI_ERR() << "Failed to allocate hardware buffer: " << libav_utils::getError(ret).c_str(); return nullptr; } @@ -126,7 +209,7 @@ HardwareAccel::transfer(const VideoFrame& frame) } if ((ret = av_hwframe_transfer_data(hwFrame, input, 0)) < 0) { - JAMI_ERR() << "Failed to push frame to GPU: " << libav_utils::getError(ret); + JAMI_ERR() << "Failed to push frame to GPU: " << libav_utils::getError(ret).c_str(); return nullptr; } @@ -153,16 +236,7 @@ HardwareAccel::setDetails(AVCodecContext* codecCtx) } bool -HardwareAccel::initDevice() -{ - int ret = av_hwdevice_ctx_create(&deviceCtx_, hwType_, nullptr, nullptr, 0); - if (ret < 0) - JAMI_ERR("Creating hardware device context failed: %s (%d)", libav_utils::getError(ret).c_str(), ret); - return ret >= 0; -} - -bool -HardwareAccel::initFrame(int width, int height) +HardwareAccel::initFrame() { int ret = 0; if (!deviceCtx_) { @@ -177,8 +251,8 @@ HardwareAccel::initFrame(int width, int height) auto ctx = reinterpret_cast<AVHWFramesContext*>(framesCtx_->data); ctx->format = format_; ctx->sw_format = swFormat_; - ctx->width = width; - ctx->height = height; + ctx->width = width_; + ctx->height = height_; ctx->initial_pool_size = 20; // TODO try other values if ((ret = av_hwframe_ctx_init(framesCtx_)) < 0) { @@ -238,67 +312,40 @@ HardwareAccel::transferToMainMemory(const VideoFrame& frame, AVPixelFormat desir return out; } -std::unique_ptr<HardwareAccel> -HardwareAccel::setupDecoder(AVCodecID id, int width, int height) +int +HardwareAccel::initAPI(bool linkable, AVBufferRef* framesCtx) { - static const HardwareAPI apiList[] = { - { "nvdec", AV_HWDEVICE_TYPE_CUDA, AV_PIX_FMT_CUDA, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_H265, AV_CODEC_ID_VP8, AV_CODEC_ID_MJPEG } }, - { "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, AV_CODEC_ID_MJPEG } }, - { "vdpau", AV_HWDEVICE_TYPE_VDPAU, AV_PIX_FMT_VDPAU, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4 } }, - { "videotoolbox", AV_HWDEVICE_TYPE_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4 } }, - { "qsv", AV_HWDEVICE_TYPE_QSV, AV_PIX_FMT_QSV, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_H265, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8, AV_CODEC_ID_VP9 } }, - }; - - for (const auto& api : apiList) { - if (std::find(api.supportedCodecs.begin(), api.supportedCodecs.end(), id) != api.supportedCodecs.end()) { - auto accel = std::make_unique<HardwareAccel>(id, api.name, api.hwType, api.format, api.swFormat, CODEC_DECODER); - if (accel->initDevice()) { - // we don't need frame context for videotoolbox - if (api.format == AV_PIX_FMT_VIDEOTOOLBOX || - accel->initFrame(width, height)) { - JAMI_DBG() << "Attempting to use hardware decoder " << accel->getCodecName() << " with " << api.name; - return accel; - } - } + const auto& codecName = getCodecName(); + std::string device; + auto ret = test_device_type(device); + if(ret == 0) { + bool link = false; + if (linkable && framesCtx) + link = linkHardware(framesCtx); + // we don't need frame context for videotoolbox + if (format_ == AV_PIX_FMT_VIDEOTOOLBOX || link || initFrame()) { + return 0; } } - - return nullptr; + return -1; } -std::unique_ptr<HardwareAccel> -HardwareAccel::setupEncoder(AVCodecID id, int width, int height, bool linkable, AVBufferRef* framesCtx) +std::list<HardwareAccel> +HardwareAccel::getCompatibleAccel(AVCodecID id, int width, int height, CodecType type) { - static const HardwareAPI apiList[] = { - { "nvenc", AV_HWDEVICE_TYPE_CUDA, AV_PIX_FMT_CUDA, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_H265 } }, - { "vaapi", AV_HWDEVICE_TYPE_VAAPI, AV_PIX_FMT_VAAPI, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8 } }, - { "videotoolbox", AV_HWDEVICE_TYPE_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264 } }, - { "qsv", AV_HWDEVICE_TYPE_QSV, AV_PIX_FMT_QSV, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_H265, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8 } }, - }; - - for (auto api : apiList) { + 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 accel = std::make_unique<HardwareAccel>(id, api.name, api.hwType, api.format, api.swFormat, CODEC_ENCODER); - const auto& codecName = accel->getCodecName(); - if (avcodec_find_encoder_by_name(codecName.c_str())) { - if (accel->initDevice()) { - bool link = false; - if (linkable) - link = accel->linkHardware(framesCtx); - // we don't need frame context for videotoolbox - if (api.format == AV_PIX_FMT_VIDEOTOOLBOX || - link || accel->initFrame(width, height)) { - JAMI_DBG() << "Attempting to use hardware encoder " << codecName << " with " << api.name; - return accel; - } - } - } + auto accel = HardwareAccel(id, api.name, api.hwType, api.format, api.swFormat, type); + accel.height_ = height; + accel.width_ = width; + accel.possible_devices_= api.possible_devices; + l.emplace_back(std::move(accel)); } } - - JAMI_WARN() << "Not using hardware encoding"; - return nullptr; + return l; } }} // namespace jami::video diff --git a/src/media/video/accel.h b/src/media/video/accel.h index 099a20603cfeb94f070c8f4d2f405748e724c41e..8776b77598f51959c145bcbdac72ad2266495bed 100644 --- a/src/media/video/accel.h +++ b/src/media/video/accel.h @@ -2,6 +2,7 @@ * Copyright (C) 2004-2020 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 @@ -26,6 +27,7 @@ #include <memory> #include <string> #include <vector> +#include <list> extern "C" { #include <libavutil/hwcontext.h> @@ -33,22 +35,21 @@ extern "C" { namespace jami { namespace video { +struct HardwareAPI +{ + std::string name; + AVHWDeviceType hwType; + AVPixelFormat format; + AVPixelFormat swFormat; + std::vector<AVCodecID> supportedCodecs; + std::set<std::string> possible_devices; +}; + /** * @brief Provides an abstraction layer to the hardware acceleration APIs in FFmpeg. */ class HardwareAccel { public: - /** - * @brief Static factory method for hardware decoding. - */ - static std::unique_ptr<HardwareAccel> setupDecoder(AVCodecID id, int width, int height); - - /** - * @brief Static factory method for hardware encoding. - */ - static std::unique_ptr<HardwareAccel> setupEncoder(AVCodecID id, int width, int height, bool linkable, - AVBufferRef* framesCtx = nullptr); - /** * @brief Transfers hardware frame to main memory. * @@ -146,9 +147,13 @@ public: */ bool linkHardware(AVBufferRef* framesCtx); + + static std::list<HardwareAccel> getCompatibleAccel(AVCodecID id, int width, int height, CodecType type); + int initAPI(bool linkable, AVBufferRef* framesCtx); + private: - bool initDevice(); - bool initFrame(int width, int height); + bool initDevice(const std::string& device); + bool initFrame(); AVCodecID id_ {AV_CODEC_ID_NONE}; std::string name_; @@ -157,9 +162,16 @@ private: AVPixelFormat swFormat_ {AV_PIX_FMT_NONE}; CodecType type_ {CODEC_NONE}; bool linked_ {false}; + int width_ {0}; + int height_ {0}; AVBufferRef* deviceCtx_ {nullptr}; AVBufferRef* framesCtx_ {nullptr}; + + int test_device(const char* name, const char* device, int flags); + int test_device_type(std::string& dev); + + std::set<std::string> possible_devices_; }; }} // namespace jami::video