mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-12 22:09:25 +08:00
accel: add hardware encoding
Adds VAAPI and VideoToolbox hardware encoders. Abstracts hardware related field accesses from the encoder and decoder to put them in HardwareAccel. They are freed in ~HardwareAccel. Limits bitrate when hardware encoding h264, else it can easily go up to a few MiB/s. Change-Id: I7d847d8ab3e4c9692341f038ce6d5dd76562e606
This commit is contained in:

committed by
Adrien Béraud

parent
fda668f9f1
commit
1293273396
@ -177,7 +177,10 @@ FFMPEGCONF += \
|
||||
--enable-hwaccel=mpeg4_vaapi \
|
||||
--enable-hwaccel=h263_vaapi \
|
||||
--enable-hwaccel=vp8_vaapi \
|
||||
--enable-hwaccel=mjpeg_vaapi
|
||||
--enable-hwaccel=mjpeg_vaapi \
|
||||
--enable-encoder=h264_vaapi \
|
||||
--enable-encoder=vp8_vaapi \
|
||||
--enable-encoder=mjpeg_vaapi
|
||||
endif
|
||||
endif
|
||||
|
||||
@ -194,6 +197,7 @@ FFMPEGCONF += \
|
||||
--enable-hwaccel=h263_videotoolbox \
|
||||
--enable-hwaccel=h264_videotoolbox \
|
||||
--enable-hwaccel=mpeg4_videotoolbox \
|
||||
--enable-encoder=h264_videotoolbox \
|
||||
--disable-securetransport
|
||||
endif
|
||||
|
||||
|
@ -61,10 +61,6 @@ MediaDecoder::MediaDecoder() :
|
||||
|
||||
MediaDecoder::~MediaDecoder()
|
||||
{
|
||||
#ifdef RING_ACCEL
|
||||
if (decoderCtx_ && decoderCtx_->hw_device_ctx)
|
||||
av_buffer_unref(&decoderCtx_->hw_device_ctx);
|
||||
#endif
|
||||
if (decoderCtx_)
|
||||
avcodec_free_context(&decoderCtx_);
|
||||
if (inputCtx_)
|
||||
@ -231,8 +227,11 @@ MediaDecoder::setupStream(AVMediaType mediaType)
|
||||
#ifdef RING_ACCEL
|
||||
if (mediaType == AVMEDIA_TYPE_VIDEO) {
|
||||
if (enableAccel_) {
|
||||
accel_ = video::HardwareAccel::setupDecoder(decoderCtx_);
|
||||
decoderCtx_->opaque = accel_.get();
|
||||
accel_ = video::HardwareAccel::setupDecoder(decoderCtx_->codec_id);
|
||||
if (accel_) {
|
||||
accel_->setDetails(decoderCtx_, &options_);
|
||||
decoderCtx_->opaque = accel_.get();
|
||||
}
|
||||
} else if (Manager::instance().videoPreferences.getDecodingAccelerated()) {
|
||||
RING_WARN() << "Hardware decoding disabled because of previous failure";
|
||||
} else {
|
||||
@ -479,7 +478,7 @@ MediaDecoder::getStream(std::string name) const
|
||||
#ifdef RING_ACCEL
|
||||
// accel_ is null if not using accelerated codecs
|
||||
if (accel_)
|
||||
ms.format = AV_PIX_FMT_NV12; // TODO option me!
|
||||
ms.format = accel_->getSoftwareFormat();
|
||||
#endif
|
||||
return ms;
|
||||
}
|
||||
|
@ -25,9 +25,15 @@
|
||||
#include "media_encoder.h"
|
||||
#include "media_buffer.h"
|
||||
|
||||
#include "client/ring_signal.h"
|
||||
#include "fileutils.h"
|
||||
#include "string_utils.h"
|
||||
#include "logger.h"
|
||||
#include "manager.h"
|
||||
#include "string_utils.h"
|
||||
|
||||
#ifdef RING_ACCEL
|
||||
#include "video/accel.h"
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/parseutils.h>
|
||||
@ -171,6 +177,10 @@ MediaEncoder::openOutput(const std::string& filename, const std::string& format)
|
||||
avformat_alloc_output_context2(&outputCtx_, nullptr, nullptr, filename.c_str());
|
||||
else
|
||||
avformat_alloc_output_context2(&outputCtx_, nullptr, format.c_str(), filename.c_str());
|
||||
|
||||
#ifdef RING_ACCEL
|
||||
enableAccel_ = Manager::instance().videoPreferences.getEncodingAccelerated();
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
@ -178,21 +188,44 @@ MediaEncoder::addStream(const SystemCodecInfo& systemCodecInfo)
|
||||
{
|
||||
AVCodec* outputCodec = nullptr;
|
||||
AVCodecContext* encoderCtx = nullptr;
|
||||
/* find the video encoder */
|
||||
if (systemCodecInfo.avcodecId == AV_CODEC_ID_H263)
|
||||
// For H263 encoding, we force the use of AV_CODEC_ID_H263P (H263-1998)
|
||||
// H263-1998 can manage all frame sizes while H263 don't
|
||||
// AV_CODEC_ID_H263 decoder will be used for decoding
|
||||
outputCodec = avcodec_find_encoder(AV_CODEC_ID_H263P);
|
||||
else
|
||||
outputCodec = avcodec_find_encoder(static_cast<AVCodecID>(systemCodecInfo.avcodecId));
|
||||
#ifdef RING_ACCEL
|
||||
if (systemCodecInfo.mediaType == MEDIA_VIDEO) {
|
||||
if (enableAccel_) {
|
||||
if (accel_ = video::HardwareAccel::setupEncoder(
|
||||
static_cast<AVCodecID>(systemCodecInfo.avcodecId), device_.width, device_.height)) {
|
||||
outputCodec = avcodec_find_encoder_by_name(accel_->getCodecName().c_str());
|
||||
}
|
||||
} else {
|
||||
RING_WARN() << "Hardware encoding disabled";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!outputCodec) {
|
||||
RING_ERR("Encoder \"%s\" not found!", systemCodecInfo.name.c_str());
|
||||
throw MediaEncoderException("No output encoder");
|
||||
/* find the video encoder */
|
||||
if (systemCodecInfo.avcodecId == AV_CODEC_ID_H263)
|
||||
// For H263 encoding, we force the use of AV_CODEC_ID_H263P (H263-1998)
|
||||
// H263-1998 can manage all frame sizes while H263 don't
|
||||
// AV_CODEC_ID_H263 decoder will be used for decoding
|
||||
outputCodec = avcodec_find_encoder(AV_CODEC_ID_H263P);
|
||||
else
|
||||
outputCodec = avcodec_find_encoder(static_cast<AVCodecID>(systemCodecInfo.avcodecId));
|
||||
if (!outputCodec) {
|
||||
RING_ERR("Encoder \"%s\" not found!", systemCodecInfo.name.c_str());
|
||||
throw MediaEncoderException("No output encoder");
|
||||
}
|
||||
}
|
||||
|
||||
encoderCtx = prepareEncoderContext(outputCodec, systemCodecInfo.mediaType == MEDIA_VIDEO);
|
||||
encoders_.push_back(encoderCtx);
|
||||
|
||||
#ifdef RING_ACCEL
|
||||
if (accel_) {
|
||||
accel_->setDetails(encoderCtx, &options_);
|
||||
encoderCtx->opaque = accel_.get();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto maxBitrate = 1000 * std::atoi(libav_utils::getDictValue(options_, "max_rate"));
|
||||
auto bufSize = 2 * maxBitrate; // as recommended (TODO: make it customizable)
|
||||
auto crf = std::atoi(libav_utils::getDictValue(options_, "crf"));
|
||||
@ -201,6 +234,12 @@ MediaEncoder::addStream(const SystemCodecInfo& systemCodecInfo)
|
||||
if (systemCodecInfo.avcodecId == AV_CODEC_ID_H264) {
|
||||
auto profileLevelId = libav_utils::getDictValue(options_, "parameters");
|
||||
extractProfileLevelID(profileLevelId, encoderCtx);
|
||||
#ifdef RING_ACCEL
|
||||
if (accel_)
|
||||
// limit the bitrate else it will easily go up to a few MiB/s
|
||||
encoderCtx->bit_rate = maxBitrate;
|
||||
else
|
||||
#endif
|
||||
forcePresetX264(encoderCtx);
|
||||
// For H264 :
|
||||
// Streaming => VBV (constrained encoding) + CRF (Constant Rate Factor)
|
||||
@ -274,7 +313,15 @@ MediaEncoder::addStream(const SystemCodecInfo& systemCodecInfo)
|
||||
// allocate buffers for both scaled (pre-encoder) and encoded frames
|
||||
const int width = encoderCtx->width;
|
||||
const int height = encoderCtx->height;
|
||||
const int format = encoderCtx->pix_fmt;
|
||||
int format = encoderCtx->pix_fmt;
|
||||
#ifdef RING_ACCEL
|
||||
if (accel_) {
|
||||
// hardware encoders require a specific pixel format
|
||||
auto desc = av_pix_fmt_desc_get(encoderCtx->pix_fmt);
|
||||
if (desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL))
|
||||
format = accel_->getSoftwareFormat();
|
||||
}
|
||||
#endif
|
||||
scaledFrameBufferSize_ = videoFrameSize(format, width, height);
|
||||
if (scaledFrameBufferSize_ < 0)
|
||||
throw MediaEncoderException(("Could not compute buffer size: " + libav_utils::getError(scaledFrameBufferSize_)).c_str());
|
||||
@ -339,7 +386,11 @@ MediaEncoder::encode(VideoFrame& input, bool is_keyframe,
|
||||
|
||||
scaler_.scale_with_aspect(input, scaledFrame_);
|
||||
|
||||
auto frame = scaledFrame_.pointer();
|
||||
// Copy frame so the VideoScaler can still use the software frame (input)
|
||||
VideoFrame copy;
|
||||
copy.copyFrom(scaledFrame_);
|
||||
|
||||
auto frame = copy.pointer();
|
||||
AVCodecContext* enc = encoders_[currentStreamIdx_];
|
||||
// ideally, time base is the inverse of framerate, but this may not always be the case
|
||||
if (enc->framerate.num == enc->time_base.den && enc->framerate.den == enc->time_base.num)
|
||||
@ -355,6 +406,19 @@ MediaEncoder::encode(VideoFrame& input, bool is_keyframe,
|
||||
frame->key_frame = 0;
|
||||
}
|
||||
|
||||
#ifdef RING_ACCEL
|
||||
// NOTE needs to be at same scope as call to encode
|
||||
std::unique_ptr<VideoFrame> framePtr;
|
||||
if (accel_) {
|
||||
framePtr = accel_->transfer(copy);
|
||||
if (!framePtr) {
|
||||
RING_ERR() << "Hardware encoding failure";
|
||||
return -1;
|
||||
}
|
||||
frame = framePtr->pointer();
|
||||
}
|
||||
#endif
|
||||
|
||||
return encode(frame, currentStreamIdx_);
|
||||
}
|
||||
#endif // RING_VIDEO
|
||||
@ -465,10 +529,7 @@ MediaEncoder::prepareEncoderContext(AVCodec* outputCodec, bool is_video)
|
||||
{
|
||||
AVCodecContext* encoderCtx = avcodec_alloc_context3(outputCodec);
|
||||
|
||||
auto encoderName = encoderCtx->av_class->item_name ?
|
||||
encoderCtx->av_class->item_name(encoderCtx) : nullptr;
|
||||
if (encoderName == nullptr)
|
||||
encoderName = "encoder?";
|
||||
auto encoderName = outputCodec->name; // guaranteed to be non null if AVCodec is not null
|
||||
|
||||
encoderCtx->thread_count = std::min(std::thread::hardware_concurrency(), is_video ? 16u : 4u);
|
||||
RING_DBG("[%s] Using %d threads", encoderName, encoderCtx->thread_count);
|
||||
@ -506,7 +567,11 @@ MediaEncoder::prepareEncoderContext(AVCodec* outputCodec, bool is_video)
|
||||
|
||||
// emit one intra frame every gop_size frames
|
||||
encoderCtx->max_b_frames = 0;
|
||||
encoderCtx->pix_fmt = AV_PIX_FMT_YUV420P; // TODO: option me !
|
||||
encoderCtx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
#ifdef RING_ACCEL
|
||||
if (accel_)
|
||||
encoderCtx->pix_fmt = accel_->getFormat();
|
||||
#endif
|
||||
|
||||
// Fri Jul 22 11:37:59 EDT 2011:tmatth:XXX: DON'T set this, we want our
|
||||
// pps and sps to be sent in-band for RTP
|
||||
@ -617,6 +682,20 @@ MediaEncoder::useCodec(const ring::AccountCodecInfo* codec) const noexcept
|
||||
return codec_.get() == codec;
|
||||
}
|
||||
|
||||
#ifdef RING_ACCEL
|
||||
void
|
||||
MediaEncoder::enableAccel(bool enableAccel)
|
||||
{
|
||||
enableAccel_ = enableAccel;
|
||||
emitSignal<DRing::ConfigurationSignal::HardwareEncodingChanged>(enableAccel_);
|
||||
if (!enableAccel_) {
|
||||
accel_.reset();
|
||||
for (auto enc : encoders_)
|
||||
enc->opaque = nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
unsigned
|
||||
MediaEncoder::getStreamCount() const
|
||||
{
|
||||
@ -637,7 +716,12 @@ MediaEncoder::getStream(const std::string& name, int streamIdx) const
|
||||
return {};
|
||||
auto enc = encoders_[streamIdx];
|
||||
// TODO set firstTimestamp
|
||||
return MediaStream(name, enc);
|
||||
auto ms = MediaStream(name, enc);
|
||||
#ifdef RING_ACCEL
|
||||
if (accel_)
|
||||
ms.format = accel_->getSoftwareFormat();
|
||||
#endif
|
||||
return ms;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -50,6 +50,12 @@ namespace ring {
|
||||
struct MediaDescription;
|
||||
struct AccountCodecInfo;
|
||||
|
||||
#ifdef RING_ACCEL
|
||||
namespace video {
|
||||
class HardwareAccel;
|
||||
}
|
||||
#endif
|
||||
|
||||
class MediaEncoderException : public std::runtime_error {
|
||||
public:
|
||||
MediaEncoderException(const char *msg) : std::runtime_error(msg) {}
|
||||
@ -94,6 +100,10 @@ public:
|
||||
|
||||
bool useCodec(const AccountCodecInfo* codec) const noexcept;
|
||||
|
||||
#ifdef RING_ACCEL
|
||||
void enableAccel(bool enableAccel);
|
||||
#endif
|
||||
|
||||
unsigned getStreamCount() const;
|
||||
MediaStream getStream(const std::string& name, int streamIdx = -1) const;
|
||||
|
||||
@ -116,6 +126,11 @@ private:
|
||||
std::vector<uint8_t> scaledFrameBuffer_;
|
||||
int scaledFrameBufferSize_ = 0;
|
||||
|
||||
#ifdef RING_ACCEL
|
||||
bool enableAccel_ = true;
|
||||
std::unique_ptr<video::HardwareAccel> accel_;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void readConfig(AVDictionary** dict, AVCodecContext* encoderCtx);
|
||||
AVDictionary *options_ = nullptr;
|
||||
|
@ -37,6 +37,7 @@ struct HardwareAPI
|
||||
{
|
||||
std::string name;
|
||||
AVPixelFormat format;
|
||||
AVPixelFormat swFormat;
|
||||
std::vector<AVCodecID> supportedCodecs;
|
||||
};
|
||||
|
||||
@ -52,7 +53,7 @@ getFormatCb(AVCodecContext* codecCtx, const AVPixelFormat* formats)
|
||||
// found hardware format for codec with api
|
||||
RING_DBG() << "Found compatible hardware format for "
|
||||
<< avcodec_get_name(static_cast<AVCodecID>(accel->getCodecId()))
|
||||
<< " with " << accel->getName();
|
||||
<< " decoder with " << accel->getName();
|
||||
return formats[i];
|
||||
}
|
||||
}
|
||||
@ -61,54 +62,146 @@ getFormatCb(AVCodecContext* codecCtx, const AVPixelFormat* formats)
|
||||
return fallback;
|
||||
}
|
||||
|
||||
HardwareAccel::HardwareAccel(AVCodecID id, const std::string& name, AVPixelFormat format)
|
||||
HardwareAccel::HardwareAccel(AVCodecID id, const std::string& name, AVPixelFormat format, AVPixelFormat swFormat, CodecType type)
|
||||
: id_(id)
|
||||
, name_(name)
|
||||
, format_(format)
|
||||
, swFormat_(swFormat)
|
||||
, type_(type)
|
||||
{}
|
||||
|
||||
HardwareAccel::~HardwareAccel()
|
||||
{
|
||||
if (deviceCtx_)
|
||||
av_buffer_unref(&deviceCtx_);
|
||||
if (framesCtx_)
|
||||
av_buffer_unref(&framesCtx_);
|
||||
}
|
||||
|
||||
std::string
|
||||
HardwareAccel::getCodecName() const
|
||||
{
|
||||
if (type_ == CODEC_DECODER) {
|
||||
return avcodec_get_name(id_);
|
||||
} else if (type_ == CODEC_ENCODER) {
|
||||
std::stringstream ss;
|
||||
ss << avcodec_get_name(id_) << '_' << name_;
|
||||
return ss.str();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::unique_ptr<VideoFrame>
|
||||
HardwareAccel::transfer(const VideoFrame& frame)
|
||||
{
|
||||
auto input = frame.pointer();
|
||||
if (input->format != format_) {
|
||||
RING_ERR("Frame format mismatch: expected %s, got %s",
|
||||
av_get_pix_fmt_name(static_cast<AVPixelFormat>(format_)),
|
||||
av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format)));
|
||||
int ret = 0;
|
||||
if (type_ == CODEC_DECODER) {
|
||||
auto input = frame.pointer();
|
||||
if (input->format != format_) {
|
||||
RING_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, AV_PIX_FMT_NV12);
|
||||
} else if (type_ == CODEC_ENCODER) {
|
||||
auto input = frame.pointer();
|
||||
if (input->format != swFormat_) {
|
||||
RING_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) {
|
||||
RING_ERR() << "Failed to allocate hardware buffer: " << libav_utils::getError(ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!hwFrame->hw_frames_ctx) {
|
||||
RING_ERR() << "Failed to allocate hardware buffer: Cannot allocate memory";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if ((ret = av_hwframe_transfer_data(hwFrame, input, 0)) < 0) {
|
||||
RING_ERR() << "Failed to push frame to GPU: " << libav_utils::getError(ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
hwFrame->pts = input->pts; // transfer does not copy timestamp
|
||||
return framePtr;
|
||||
} else {
|
||||
RING_ERR() << "Invalid hardware accelerator";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return transferToMainMemory(frame, AV_PIX_FMT_NV12);
|
||||
}
|
||||
|
||||
static int
|
||||
initDevice(const HardwareAPI& api, AVCodecContext* codecCtx)
|
||||
void
|
||||
HardwareAccel::setDetails(AVCodecContext* codecCtx, AVDictionary** /*d*/)
|
||||
{
|
||||
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) {
|
||||
codecCtx->hw_device_ctx = av_buffer_ref(deviceCtx_);
|
||||
codecCtx->hw_frames_ctx = av_buffer_ref(framesCtx_);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
HardwareAccel::initDevice()
|
||||
{
|
||||
int ret = 0;
|
||||
AVBufferRef* hardwareDeviceCtx = nullptr;
|
||||
auto hwType = av_hwdevice_find_type_by_name(api.name.c_str());
|
||||
auto hwType = av_hwdevice_find_type_by_name(name_.c_str());
|
||||
#ifdef HAVE_VAAPI_ACCEL_DRM
|
||||
// default DRM device may not work on multi GPU computers, so check all possible values
|
||||
if (api.name == "vaapi") {
|
||||
if (name_ == "vaapi") {
|
||||
const std::string path = "/dev/dri/";
|
||||
auto files = ring::fileutils::readDirectory(path);
|
||||
// renderD* is preferred over card*
|
||||
std::sort(files.rbegin(), files.rend());
|
||||
for (auto& entry : files) {
|
||||
std::string deviceName = path + entry;
|
||||
if ((ret = av_hwdevice_ctx_create(&hardwareDeviceCtx, hwType, deviceName.c_str(), nullptr, 0)) >= 0) {
|
||||
codecCtx->hw_device_ctx = hardwareDeviceCtx;
|
||||
return ret;
|
||||
if ((ret = av_hwdevice_ctx_create(&deviceCtx_, hwType, deviceName.c_str(), nullptr, 0)) >= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// default device (nullptr) works for most cases
|
||||
if ((ret = av_hwdevice_ctx_create(&hardwareDeviceCtx, hwType, nullptr, nullptr, 0)) >= 0) {
|
||||
codecCtx->hw_device_ctx = hardwareDeviceCtx;
|
||||
ret = av_hwdevice_ctx_create(&deviceCtx_, hwType, nullptr, nullptr, 0);
|
||||
return ret >= 0;
|
||||
}
|
||||
|
||||
bool
|
||||
HardwareAccel::initFrame(int width, int height)
|
||||
{
|
||||
int ret = 0;
|
||||
if (!deviceCtx_) {
|
||||
RING_ERR() << "Cannot initialize hardware frames without a valid hardware device";
|
||||
return false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
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)
|
||||
av_buffer_unref(&framesCtx_);
|
||||
|
||||
return ret >= 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<VideoFrame>
|
||||
@ -137,21 +230,20 @@ HardwareAccel::transferToMainMemory(const VideoFrame& frame, AVPixelFormat desir
|
||||
}
|
||||
|
||||
std::unique_ptr<HardwareAccel>
|
||||
HardwareAccel::setupDecoder(AVCodecContext* codecCtx)
|
||||
HardwareAccel::setupDecoder(AVCodecID id)
|
||||
{
|
||||
static const HardwareAPI apiList[] = {
|
||||
{ "vaapi", AV_PIX_FMT_VAAPI, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4, AV_CODEC_ID_VP8, AV_CODEC_ID_MJPEG } },
|
||||
{ "vdpau", AV_PIX_FMT_VDPAU, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4 } },
|
||||
{ "videotoolbox", AV_PIX_FMT_VIDEOTOOLBOX, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4 } },
|
||||
{ "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_PIX_FMT_VDPAU, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4 } },
|
||||
{ "videotoolbox", AV_PIX_FMT_VIDEOTOOLBOX, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4 } },
|
||||
};
|
||||
|
||||
for (const auto& api : apiList) {
|
||||
if (std::find(api.supportedCodecs.begin(), api.supportedCodecs.end(), codecCtx->codec_id) != api.supportedCodecs.end()) {
|
||||
if (initDevice(api, codecCtx) >= 0) {
|
||||
codecCtx->get_format = getFormatCb;
|
||||
codecCtx->thread_safe_callbacks = 1;
|
||||
RING_DBG() << "Attempting to use hardware accelerated decoding with " << api.name;
|
||||
return std::make_unique<HardwareAccel>(codecCtx->codec_id, api.name, api.format);
|
||||
if (std::find(api.supportedCodecs.begin(), api.supportedCodecs.end(), id) != api.supportedCodecs.end()) {
|
||||
auto accel = std::make_unique<HardwareAccel>(id, api.name, api.format, api.swFormat, CODEC_DECODER);
|
||||
if (accel->initDevice()) {
|
||||
RING_DBG() << "Attempting to use hardware decoder " << accel->getCodecName() << " with " << api.name;
|
||||
return accel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -159,4 +251,30 @@ HardwareAccel::setupDecoder(AVCodecContext* codecCtx)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<HardwareAccel>
|
||||
HardwareAccel::setupEncoder(AVCodecID id, int width, int height)
|
||||
{
|
||||
static const HardwareAPI apiList[] = {
|
||||
{ "vaapi", AV_PIX_FMT_VAAPI, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264, AV_CODEC_ID_MJPEG, AV_CODEC_ID_VP8 } },
|
||||
{ "videotoolbox", AV_PIX_FMT_VIDEOTOOLBOX, AV_PIX_FMT_NV12, { AV_CODEC_ID_H264 } },
|
||||
};
|
||||
|
||||
for (auto api : apiList) {
|
||||
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.format, api.swFormat, CODEC_ENCODER);
|
||||
const auto& codecName = accel->getCodecName();
|
||||
if (avcodec_find_encoder_by_name(codecName.c_str())) {
|
||||
if (accel->initDevice() && accel->initFrame(width, height)) {
|
||||
RING_DBG() << "Attempting to use hardware encoder " << codecName;
|
||||
return accel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RING_WARN() << "Not using hardware encoding";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}} // namespace ring::video
|
||||
|
@ -21,6 +21,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "libav_deps.h"
|
||||
#include "media_codec.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@ -36,7 +37,12 @@ public:
|
||||
/**
|
||||
* Static factory method for hardware decoding.
|
||||
*/
|
||||
static std::unique_ptr<HardwareAccel> setupDecoder(AVCodecContext* codecCtx);
|
||||
static std::unique_ptr<HardwareAccel> setupDecoder(AVCodecID id);
|
||||
|
||||
/**
|
||||
* Static factory method for hardware encoding.
|
||||
*/
|
||||
static std::unique_ptr<HardwareAccel> setupEncoder(AVCodecID id, int width, int height);
|
||||
|
||||
/**
|
||||
* Transfers a hardware decoded frame back to main memory. Should be called after
|
||||
@ -50,25 +56,72 @@ public:
|
||||
/**
|
||||
* Made public so std::unique_ptr can access it. Should not be called.
|
||||
*/
|
||||
HardwareAccel(AVCodecID id, const std::string& name, AVPixelFormat format);
|
||||
HardwareAccel(AVCodecID id, const std::string& name, AVPixelFormat format, AVPixelFormat swFormat, CodecType type);
|
||||
|
||||
/**
|
||||
* Dereferences hardware contexts.
|
||||
*/
|
||||
~HardwareAccel();
|
||||
|
||||
/**
|
||||
* Codec that is being accelerated.
|
||||
*/
|
||||
AVCodecID getCodecId() const { return id_; };
|
||||
|
||||
/**
|
||||
* Name of the hardware layer/API being used.
|
||||
*/
|
||||
std::string getName() const { return name_; };
|
||||
|
||||
/**
|
||||
* Hardware format.
|
||||
*/
|
||||
AVPixelFormat getFormat() const { return format_; };
|
||||
|
||||
/**
|
||||
* Software format. For encoding it is the format expected by the hardware. For decoding
|
||||
* it is the format output by the hardware.
|
||||
*/
|
||||
AVPixelFormat getSoftwareFormat() const { return swFormat_; }
|
||||
|
||||
/**
|
||||
* Gets the name of the codec.
|
||||
* Decoding: equivalent to avcodec_get_name(id_)
|
||||
* Encoding: avcodec_get_name(id_) + '_' + name_
|
||||
*/
|
||||
std::string getCodecName() const;
|
||||
|
||||
/**
|
||||
* Set some extra details in the codec context. Should be called after a successful
|
||||
* setup (setupDecoder or setupEncoder).
|
||||
* For decoding, sets the hw_device_ctx and get_format callback. For encoding, sets
|
||||
* hw_device_ctx and hw_frames_ctx, and may set some hardware specific options in
|
||||
* the dictionary.
|
||||
*/
|
||||
void setDetails(AVCodecContext* codecCtx, AVDictionary** d);
|
||||
|
||||
/**
|
||||
* Transfers a hardware decoded frame back to main memory. Should be called after
|
||||
* the frame is decoded using avcodec_send_packet/avcodec_receive_frame.
|
||||
* the frame is decoded using avcodec_send_packet/avcodec_receive_frame or before
|
||||
* the frame is encoded using avcodec_send_frame/avcodec_receive_packet.
|
||||
*
|
||||
* @frame: Refrerence to the decoded hardware frame.
|
||||
* @returns: Software frame.
|
||||
* @frame: Hardware frame when decoding, software frame when encoding.
|
||||
* @returns: Software frame when decoding, hardware frame when encoding.
|
||||
*/
|
||||
std::unique_ptr<VideoFrame> transfer(const VideoFrame& frame);
|
||||
|
||||
private:
|
||||
AVCodecID id_;
|
||||
bool initDevice();
|
||||
bool initFrame(int width, int height);
|
||||
|
||||
AVCodecID id_ {AV_CODEC_ID_NONE};
|
||||
std::string name_;
|
||||
AVPixelFormat format_;
|
||||
AVPixelFormat format_ {AV_PIX_FMT_NONE};
|
||||
AVPixelFormat swFormat_ {AV_PIX_FMT_NONE};
|
||||
CodecType type_ {CODEC_NONE};
|
||||
|
||||
AVBufferRef* deviceCtx_ {nullptr};
|
||||
AVBufferRef* framesCtx_ {nullptr};
|
||||
};
|
||||
|
||||
}} // namespace ring::video
|
||||
|
@ -138,6 +138,7 @@ static const char * const TOGGLE_PICKUP_HANGUP_SHORT_KEY = "togglePickupHangup";
|
||||
// video preferences
|
||||
constexpr const char * const VideoPreferences::CONFIG_LABEL;
|
||||
static const char * const DECODING_ACCELERATED_KEY = "decodingAccelerated";
|
||||
static const char * const ENCODING_ACCELERATED_KEY = "encodingAccelerated";
|
||||
#endif
|
||||
|
||||
static const char * const DFT_PULSE_LENGTH_STR = "250"; /** Default DTMF length */
|
||||
@ -562,6 +563,7 @@ void ShortcutPreferences::unserialize(const YAML::Node &in)
|
||||
#ifdef RING_VIDEO
|
||||
VideoPreferences::VideoPreferences()
|
||||
: decodingAccelerated_(true)
|
||||
, encodingAccelerated_(false)
|
||||
{
|
||||
}
|
||||
|
||||
@ -570,6 +572,7 @@ void VideoPreferences::serialize(YAML::Emitter &out)
|
||||
out << YAML::Key << CONFIG_LABEL << YAML::Value << YAML::BeginMap;
|
||||
#ifdef RING_ACCEL
|
||||
out << YAML::Key << DECODING_ACCELERATED_KEY << YAML::Value << decodingAccelerated_;
|
||||
out << YAML::Key << ENCODING_ACCELERATED_KEY << YAML::Value << encodingAccelerated_;
|
||||
#endif
|
||||
getVideoDeviceMonitor().serialize(out);
|
||||
out << YAML::EndMap;
|
||||
@ -582,7 +585,11 @@ void VideoPreferences::unserialize(const YAML::Node &in)
|
||||
// value may or may not be present
|
||||
try {
|
||||
parseValue(node, DECODING_ACCELERATED_KEY, decodingAccelerated_);
|
||||
} catch (...) { decodingAccelerated_ = true; }
|
||||
parseValue(node, ENCODING_ACCELERATED_KEY, encodingAccelerated_);
|
||||
} catch (...) {
|
||||
decodingAccelerated_ = true;
|
||||
encodingAccelerated_ = false;
|
||||
}
|
||||
#endif
|
||||
getVideoDeviceMonitor().unserialize(in);
|
||||
}
|
||||
|
@ -466,8 +466,18 @@ class VideoPreferences : public Serializable {
|
||||
emitSignal<DRing::ConfigurationSignal::HardwareDecodingChanged>(decodingAccelerated_);
|
||||
}
|
||||
|
||||
bool getEncodingAccelerated() const {
|
||||
return encodingAccelerated_;
|
||||
}
|
||||
|
||||
void setEncodingAccelerated(bool encodingAccelerated) {
|
||||
encodingAccelerated_ = encodingAccelerated;
|
||||
emitSignal<DRing::ConfigurationSignal::HardwareEncodingChanged>(encodingAccelerated_);
|
||||
}
|
||||
|
||||
private:
|
||||
bool decodingAccelerated_;
|
||||
bool encodingAccelerated_;
|
||||
constexpr static const char* const CONFIG_LABEL = "video";
|
||||
};
|
||||
#endif // RING_VIDEO
|
||||
|
Reference in New Issue
Block a user