diff --git a/configure.ac b/configure.ac index 189958728d29cb9b7cee602753e3a5e8bc5156b5..2c0cdc027a16180a7a9e9d31a598c601cb1c5c9c 100644 --- a/configure.ac +++ b/configure.ac @@ -120,7 +120,6 @@ AS_IF([test "$SYS" = linux],[ AC_MSG_RESULT([no]) ]) ]) - AM_CONDITIONAL(HAVE_ANDROID, test "${HAVE_ANDROID}" = "1") dnl override platform specific check for dependent libraries @@ -430,6 +429,28 @@ AS_IF([test "x$enable_video" != "xno"], dnl check for GnuTLS PKG_CHECK_MODULES([GNUTLS], [gnutls >= 3.4.14], [HAVE_GNUTLS=1], [HAVE_GNUTLS=0]) +dnl hardware acceleration is enabled by default +AC_ARG_ENABLE([accel], AS_HELP_STRING([--disable-accel], [Disable hardware acceleration])) +AS_IF([test "x$enable_accel" != "xno"], [ + AC_DEFINE([RING_ACCEL], [1], [Allows use of hardware acceleration]) + AM_CONDITIONAL(RING_ACCEL, true) + AS_IF([test "$SYS" = linux && test -z "${HAVE_ANDROID_FALSE}"],[ + PKG_CHECK_MODULES(LIBVA, [libva], , + [AC_MSG_ERROR([Missing libva package])]) + PKG_CHECK_MODULES([LIBVA_DRM], [libva-drm], [ + AC_DEFINE([HAVE_VAAPI_ACCEL_DRM], [1], [Have vaapi drm]) + ], [AC_MSG_ERROR([Could not find libva-drm]) + ]) + AC_CHECK_HEADER([X11/Xlib.h], [ + AC_CHECK_LIB(X11, XOpenDisplay, [], [AC_MSG_ERROR([Could not find X11])]) + AC_DEFINE([HAVE_X11], [1], [Have x11]) + PKG_CHECK_MODULES(LIBVA_X11, [libva-x11], [ + AC_DEFINE([HAVE_VAAPI_ACCEL_X11], [1], [Have vaapi x11]) + ], [AC_MSG_ERROR([Could not find libva-x11]) + ]) + ]) + ]) +], AM_CONDITIONAL(RING_ACCEL, false)) # PTHREAD # required dependency(ies): libxpat diff --git a/contrib/src/ffmpeg/0004-avformat-fix-find_stream_info-not-considering-extradata.patch b/contrib/src/ffmpeg/0004-avformat-fix-find_stream_info-not-considering-extradata.patch new file mode 100644 index 0000000000000000000000000000000000000000..dda9e0c4d65a8dd1e3c4aa8bd9331ba440822b73 --- /dev/null +++ b/contrib/src/ffmpeg/0004-avformat-fix-find_stream_info-not-considering-extradata.patch @@ -0,0 +1,41 @@ +From 60873bf992eab1d3bad8dd0fd11336363d44854d Mon Sep 17 00:00:00 2001 +From: Anssi Hannula <anssi.hannula@iki.fi> +Date: Tue, 26 Jul 2016 13:23:43 +0300 +Subject: [PATCH 1572/1572] avformat/utils: Fix find_stream_info not + considering the extradata it found + +Commit 9200514ad8717c6 ("lavf: replace AVStream.codec with +AVStream.codecpar") merged in commit 6f69f7a8bf6a0d01 changed +avformat_find_stream_info() to put the extradata it got from +st->parser->parser->split() to st->internal->avctx instead of st->codec +(extradata in st->internal->avctx will be later copied to st->codecpar). + +However, in the same function, the "is stream ready?" check was changed +to check for extradata in st->codecpar instead of st->codec, even +though st->codecpar is not yet updated at that point. + +Extradata retrieved from split() is therefore not considered anymore, +and avformat_find_stream_info() will therefore needlessly continue +probing in some cases. + +Fix that by checking for the extradata at st->internal->avctx where it +is actually put. +--- + libavformat/utils.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libavformat/utils.c b/libavformat/utils.c +index e5a99ff..5a902ea 100644 +--- a/libavformat/utils.c ++++ b/libavformat/utils.c +@@ -3432,7 +3432,7 @@ FF_ENABLE_DEPRECATION_WARNINGS + break; + } + if (st->parser && st->parser->parser->split && +- !st->codecpar->extradata) ++ !st->internal->avctx->extradata) + break; + if (st->first_dts == AV_NOPTS_VALUE && + !(ic->iformat->flags & AVFMT_NOTIMESTAMPS) && +-- +2.7.4 diff --git a/contrib/src/ffmpeg/rules.mak b/contrib/src/ffmpeg/rules.mak index 91f3fd35db9315ad05e6f79a6870f23e77c38cf6..007aed9fe6a4ea390c18fca96172268a801f152a 100644 --- a/contrib/src/ffmpeg/rules.mak +++ b/contrib/src/ffmpeg/rules.mak @@ -1,10 +1,14 @@ -FFMPEG_HASH := c40983a6f631d22fede713d535bb9c31d5c9740c +FFMPEG_HASH := c46d22a4a58467bdc7885685b06a2114dd181c43 FFMPEG_URL := https://git.ffmpeg.org/gitweb/ffmpeg.git/snapshot/$(FFMPEG_HASH).tar.gz ifdef HAVE_WIN32 PKGS += ffmpeg endif +ifdef HAVE_LINUX +PKGS += ffmpeg +endif + FFMPEGCONF = \ --cc="$(CC)" \ --pkg-config="$(PKG_CONFIG)" @@ -89,7 +93,19 @@ FFMPEGCONF += \ --enable-dxva2 endif -DEPS_ffmpeg = iconv zlib x264 vpx opus speex $(DEPS_vpx) +ifdef HAVE_LINUX +FFMPEGCONF += \ + --enable-vaapi \ + --enable-hwaccel=h264_vaapi \ + --enable-hwaccel=mpeg4_vaapi \ + --enable-hwaccel=h263_vaapi +endif + +ifdef HAVE_MACOSX +FFMPEGCONF += \ + --enable-indev=avfcapture \ + --enable-indev=avfgrab +endif ifdef HAVE_IOS FFMPEGCONF += \ @@ -100,6 +116,8 @@ FFMPEGCONF += \ --enable-indev=avfoundation endif +DEPS_ffmpeg = iconv zlib x264 vpx opus speex $(DEPS_vpx) + # Linux ifdef HAVE_LINUX FFMPEGCONF += --target-os=linux --enable-pic @@ -152,7 +170,7 @@ FFMPEGCONF += --target-os=mingw32 --enable-memalign-hack FFMPEGCONF += --enable-w32threads --disable-decoder=dca endif -ifeq ($(call need_pkg,"ffmpeg >= 2.6.1"),) +ifeq ($(call need_pkg,"ffmpeg >= 3.1.3"),) PKGS_FOUND += ffmpeg endif @@ -168,6 +186,7 @@ ffmpeg: ffmpeg-$(FFMPEG_HASH).tar.xz .sum-ffmpeg mkdir -p $@-$(FFMPEG_HASH) (cd $@-$(FFMPEG_HASH) && tar xv --strip-components=1 -f ../$<) $(UPDATE_AUTOCONFIG) + $(APPLY) $(SRC)/ffmpeg/0004-avformat-fix-find_stream_info-not-considering-extradata.patch $(MOVE) .ffmpeg: ffmpeg diff --git a/contrib/src/libav/rules.mak b/contrib/src/libav/rules.mak index 815d8fcb25b9e997bfb5211dca0bb3d3bc8909a5..c0de28ac0caefa105dc35922de7702e4bf8a55ef 100644 --- a/contrib/src/libav/rules.mak +++ b/contrib/src/libav/rules.mak @@ -2,9 +2,11 @@ LIBAV_HASH := f851477889ae48e2f17073cf7486e1d5561b7ae4 LIBAV_URL := https://git.libav.org/?p=libav.git;a=snapshot;h=$(LIBAV_HASH);sf=tgz +ifndef HAVE_LINUX ifndef HAVE_WIN32 PKGS += libav endif +endif #disable everything #ensure to add this option first diff --git a/src/media/media_decoder.cpp b/src/media/media_decoder.cpp index 952b184b06c19264abada7bca02c8e67164efe39..fdc54f795d233fa6e0a3fcfb03fc4236e88efd4c 100644 --- a/src/media/media_decoder.cpp +++ b/src/media/media_decoder.cpp @@ -27,6 +27,10 @@ #include "audio/ringbuffer.h" #include "audio/resampler.h" +#if defined(RING_VIDEO) && defined(RING_ACCEL) +#include "video/accel.h" +#endif + #include "string_utils.h" #include "logger.h" @@ -91,6 +95,9 @@ int MediaDecoder::openInput(const DeviceParams& params) } RING_DBG("Trying to open device %s with format %s, pixel format %s, size %dx%d, rate %lf", params.input.c_str(), params.format.c_str(), params.pixel_format.c_str(), params.width, params.height, params.framerate.real()); + + enableAccel_ = (params.enableAccel == "1"); + int ret = avformat_open_input( &inputCtx_, params.input.c_str(), @@ -259,6 +266,11 @@ int MediaDecoder::setupFromVideoData() decoderCtx_->thread_count = std::thread::hardware_concurrency(); +#ifdef RING_ACCEL + accel_ = video::makeHardwareAccel(decoderCtx_); + decoderCtx_->opaque = accel_.get(); +#endif // RING_ACCEL + // find the decoder for the video stream inputDecoder_ = avcodec_find_decoder(decoderCtx_->codec_id); if (!inputDecoder_) { @@ -314,7 +326,6 @@ MediaDecoder::decode(VideoFrame& result) int frameFinished = 0; int len = avcodec_decode_video2(decoderCtx_, frame, &frameFinished, &inpacket); - av_packet_unref(&inpacket); if (len <= 0) @@ -322,6 +333,10 @@ MediaDecoder::decode(VideoFrame& result) if (frameFinished) { frame->format = (AVPixelFormat) correctPixFmt(frame->format); +#if defined(RING_VIDEO) && defined(RING_ACCEL) + if (accel_ && !accel_->extractData(decoderCtx_, result)) + return Status::DecodeError; +#endif // RING_ACCEL if (emulateRate_ and frame->pkt_pts != AV_NOPTS_VALUE) { auto frame_time = getTimeBase()*(frame->pkt_pts - avStream_->start_time); auto target = startTime_ + static_cast<std::int64_t>(frame_time.real() * 1e6); diff --git a/src/media/media_decoder.h b/src/media/media_decoder.h index 7012bc38bc0c66160435a610d10ff92103111dac..fa22ab2b2d4755f8ef5c3766bbdf846ce45243a4 100644 --- a/src/media/media_decoder.h +++ b/src/media/media_decoder.h @@ -44,6 +44,12 @@ class AVCodec; namespace ring { +#if defined(RING_VIDEO) && defined(RING_ACCEL) +namespace video { +class HardwareAccel; +} +#endif + class AudioFrame; class AudioFormat; class RingBuffer; @@ -114,6 +120,11 @@ class MediaDecoder { // maximum time a packet can be queued (in ms) const unsigned jitterBufferMaxDelay_ {100000}; + bool enableAccel_ = true; +#if defined(RING_VIDEO) && defined(RING_ACCEL) + std::unique_ptr<video::HardwareAccel> accel_; +#endif + protected: AVDictionary *options_ = nullptr; }; diff --git a/src/media/media_device.h b/src/media/media_device.h index 9fb685943417165da45b7707e6a71f5dde197915..abf4826e030014b833a4f5201f6c4548bb48c6ae 100644 --- a/src/media/media_device.h +++ b/src/media/media_device.h @@ -45,6 +45,7 @@ struct DeviceParams { std::string sdp_flags {}; unsigned offset_x {}; unsigned offset_y {}; + std::string enableAccel {}; }; } diff --git a/src/media/video/Makefile.am b/src/media/video/Makefile.am index 80e8469480d066e917829b4947629a2e46e8a57f..a0c53124c58ec623dd6ade02ebb470f20ce3503b 100644 --- a/src/media/video/Makefile.am +++ b/src/media/video/Makefile.am @@ -35,6 +35,10 @@ libvideo_la_SOURCES = \ video_rtp_session.cpp video_rtp_session.h \ sinkclient.cpp sinkclient.h +if RING_ACCEL +libvideo_la_SOURCES += accel.cpp accel.h +endif + libvideo_la_LIBADD = @LIBAVCODEC_LIBS@ @LIBAVFORMAT_LIBS@ @LIBAVDEVICE_LIBS@ @LIBSWSCALE_LIBS@ @LIBAVUTIL_LIBS@ AM_CXXFLAGS=@LIBAVCODEC_CFLAGS@ @LIBAVFORMAT_CFLAGS@ @LIBAVDEVICE_CFLAGS@ @LIBSWSCALE_CFLAGS@ diff --git a/src/media/video/accel.cpp b/src/media/video/accel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..118a28e077586983c07fdefc1f0d73e6444cfd1e --- /dev/null +++ b/src/media/video/accel.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2016 Savoir-faire Linux Inc. + * + * Author: Philippe Gorley <philippe.gorley@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 "libav_deps.h" // MUST BE INCLUDED FIRST +#include "media_buffer.h" + +#include "accel.h" + +#if defined(HAVE_VAAPI_ACCEL_X11) || defined(HAVE_VAAPI_ACCEL_DRM) +#include "v4l2/vaapi.h" +#endif + +#include "string_utils.h" +#include "logger.h" + +#include <initializer_list> +#include <algorithm> + +namespace ring { namespace video { + +static constexpr const unsigned MAX_ACCEL_FAILURES { 5 }; + +static AVPixelFormat +getFormatCb(AVCodecContext* codecCtx, const AVPixelFormat* formats) +{ + auto accel = static_cast<HardwareAccel*>(codecCtx->opaque); + if (!accel) { + // invalid state, try to recover + return avcodec_default_get_format(codecCtx, formats); + } + + for (int i = 0; formats[i] != AV_PIX_FMT_NONE; i++) { + if (formats[i] == accel->format()) { + accel->setWidth(codecCtx->coded_width); + accel->setHeight(codecCtx->coded_height); + accel->setProfile(codecCtx->profile); + if (accel->init(codecCtx)) + return accel->format(); + break; + } + } + + accel->fail(true); + RING_WARN("Falling back to software decoding"); + codecCtx->get_format = avcodec_default_get_format; + codecCtx->get_buffer2 = avcodec_default_get_buffer2; + for (int i = 0; formats[i] != AV_PIX_FMT_NONE; i++) { + auto desc = av_pix_fmt_desc_get(formats[i]); + if (desc && !(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) { + return formats[i]; + } + } + + return AV_PIX_FMT_NONE; +} + +static int +allocateBufferCb(AVCodecContext* codecCtx, AVFrame* frame, int flags) +{ + if (auto accel = static_cast<HardwareAccel*>(codecCtx->opaque)) { + if (!accel->hasFailed() && accel->allocateBuffer(codecCtx, frame, flags) == 0) { + accel->succeed(); + return 0; + } + + accel->fail(); + } + + return avcodec_default_get_buffer2(codecCtx, frame, flags); +} + +template <class T> +static std::unique_ptr<HardwareAccel> +makeHardwareAccel(const AccelInfo& info) { + return std::unique_ptr<HardwareAccel>(new T(info)); +} + +static const AccelInfo* +getAccelInfo(std::initializer_list<AccelID> codecAccels) +{ + /* Each item in this array reprensents a fully implemented hardware acceleration in Ring. + * Each item should be enclosed in an #ifdef to prevent its compilation on an + * unsupported platform (VAAPI for Linux Intel won't compile on a Mac). + * A new item should be added when support for an acceleration has been added to Ring, + * which is also supported by FFmpeg. + * Steps to add an acceleration (after its implementation): + * - If it doesn't yet exist, add a unique AccelID + * - Specify its AVPixelFormat (the one used by FFmpeg) + * - Give it a name (this is used for the daemon logs) + * - Add a function pointer that returns an instance (makeHardwareAccel does this already) + * Note: the acceleration's header file must be guarded by the same #ifdef as + * in this array. + */ + static const AccelInfo accels[] = { +#if defined(HAVE_VAAPI_ACCEL_X11) || defined(HAVE_VAAPI_ACCEL_DRM) + { AccelID::Vaapi, AV_PIX_FMT_VAAPI, "vaapi", makeHardwareAccel<VaapiAccel> }, +#endif + }; + + for (auto& accel : accels) { + for (auto& ca : codecAccels) { + if (accel.type == ca) { + RING_DBG("Found '%s' hardware acceleration", accel.name.c_str()); + return &accel; + } + } + } + + RING_DBG("Did not find a matching hardware acceleration"); + return nullptr; +} + +HardwareAccel::HardwareAccel(const AccelInfo& info) + : type_(info.type) + , format_(info.format) + , name_(info.name) +{ + failCount_ = 0; + fallback_ = false; + width_ = -1; + height_ = -1; + profile_ = -1; +} + +void +HardwareAccel::fail(bool forceFallback) +{ + ++failCount_; + if (failCount_ >= MAX_ACCEL_FAILURES || forceFallback) { + fallback_ = true; + failCount_ = 0; + // force reinit of media decoder to correctly set thread count + } +} + +std::unique_ptr<HardwareAccel> +makeHardwareAccel(AVCodecContext* codecCtx) +{ + const AccelInfo* info = nullptr; + + switch (codecCtx->codec_id) { + case AV_CODEC_ID_H264: + info = getAccelInfo({ + AccelID::Vdpau, + AccelID::VideoToolbox, + AccelID::Dxva2, + AccelID::Vaapi, + AccelID::Vda + }); + break; + case AV_CODEC_ID_MPEG4: + case AV_CODEC_ID_H263: + case AV_CODEC_ID_H263P: + info = getAccelInfo({ + AccelID::Vdpau, + AccelID::VideoToolbox, + AccelID::Vaapi + }); + break; + default: + break; + } + + if (info && info->type != AccelID::NoAccel) { + if (auto accel = info->create(*info)) { + codecCtx->get_format = getFormatCb; + codecCtx->get_buffer2 = allocateBufferCb; + codecCtx->thread_safe_callbacks = 1; + codecCtx->thread_count = 1; + RING_DBG("Hardware acceleration setup has succeeded"); + return accel; + } else + RING_ERR("Failed to create %s hardware acceleration", info->name.c_str()); + } + + RING_WARN("Not using hardware acceleration"); + return nullptr; +} + +}} // namespace ring::video diff --git a/src/media/video/accel.h b/src/media/video/accel.h new file mode 100644 index 0000000000000000000000000000000000000000..f0507b6d22da517e69cd44bd199ef76ee26a9fe5 --- /dev/null +++ b/src/media/video/accel.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016 Savoir-faire Linux Inc. + * + * Author: Philippe Gorley <philippe.gorley@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. + */ + +#pragma once + +#include "libav_deps.h" +#include "media_buffer.h" +#include "config.h" + +#include <string> +#include <memory> + +namespace ring { namespace video { + +class HardwareAccel; + +enum class AccelID { + NoAccel = 0, + Vdpau, + VideoToolbox, + Dxva2, + Vaapi, + Vda +}; + +struct AccelInfo { + AccelID type; + AVPixelFormat format; + std::string name; + std::unique_ptr<HardwareAccel> (*create)(const AccelInfo& info); +}; + +class HardwareAccel { + public: + HardwareAccel(const AccelInfo& info); + virtual ~HardwareAccel() {}; + + AVPixelFormat format() const { return format_; } + std::string name() const { return name_; } + bool hasFailed() const { return fallback_; } + + void setWidth(int width) { width_ = width; } + void setHeight(int height) { height_ = height; } + void setProfile(int profile) { profile_ = profile; } + + void fail(bool forceFallback = false); + void succeed() { failCount_ = 0; } // call on success of allocateBuffer or extractData + + public: // must be implemented by derived classes + virtual bool init(AVCodecContext* codecCtx) = 0; + virtual int allocateBuffer(AVCodecContext* codecCtx, AVFrame* frame, int flags) = 0; + virtual bool extractData(AVCodecContext* codecCtx, VideoFrame& container) = 0; + + protected: + AccelID type_; + AVPixelFormat format_; + std::string name_; + unsigned failCount_; // how many failures in a row, reset on success + bool fallback_; // true when failCount_ exceeds a certain number + int width_; + int height_; + int profile_; +}; + +// HardwareAccel factory +// Checks if codec acceleration is possible +std::unique_ptr<HardwareAccel> makeHardwareAccel(AVCodecContext* codecCtx); + +}} // namespace ring::video diff --git a/src/media/video/v4l2/Makefile.am b/src/media/video/v4l2/Makefile.am index 99339d30508420d98aece34613f5c4b785398f5e..3c3626d83a49e492d5198f7af796a482cf961c52 100644 --- a/src/media/video/v4l2/Makefile.am +++ b/src/media/video/v4l2/Makefile.am @@ -6,5 +6,9 @@ libv4l2_la_SOURCES = \ video_device_impl.cpp \ video_device_monitor_impl.cpp -AM_CXXFLAGS = @UDEV_CFLAGS@ -libv4l2_la_LIBADD = @UDEV_LIBS@ +if RING_ACCEL +libv4l2_la_SOURCES += vaapi.h vaapi.cpp +endif + +AM_CXXFLAGS = @UDEV_CFLAGS@ @LIBVA_CFLAGS@ @LIBVA_DRM_CFLAGS@ @LIBVA_X11_CFLAGS@ +libv4l2_la_LIBADD = @UDEV_LIBS@ @X11_LIBS@ @LIBVA_LIBS@ @LIBVA_DRM_LIBS@ @LIBVA_X11_LIBS@ diff --git a/src/media/video/v4l2/vaapi.cpp b/src/media/video/v4l2/vaapi.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6139e5abaabfe563341749d2c6806340f04527c9 --- /dev/null +++ b/src/media/video/v4l2/vaapi.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2016 Savoir-faire Linux Inc. + * + * Author: Philippe Gorley <philippe.gorley@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 "libav_deps.h" // MUST BE INCLUDED FIRST + +#include "config.h" + +#if defined(RING_VIDEO) && defined(RING_ACCEL) + +#include "video/v4l2/vaapi.h" +#include "video/accel.h" + +#include <sstream> +#include <stdexcept> +#include <map> +#include <algorithm> +#include <vector> + +#include "logger.h" + +namespace ring { namespace video { + +static auto avBufferRefDeleter = [](AVBufferRef* buf){ av_buffer_unref(&buf); }; + +VaapiAccel::VaapiAccel(AccelInfo info) : HardwareAccel(info) + , deviceBufferRef_(nullptr, avBufferRefDeleter) + , framesBufferRef_(nullptr, avBufferRefDeleter) +{ +} + +VaapiAccel::~VaapiAccel() +{ +} + +int +VaapiAccel::allocateBuffer(AVCodecContext* codecCtx, AVFrame* frame, int flags) +{ + return av_hwframe_get_buffer(framesBufferRef_.get(), frame, 0); +} + +bool +VaapiAccel::extractData(AVCodecContext* codecCtx, VideoFrame& container) +{ + try { + auto input = container.pointer(); + + if (input->format != format_) { + std::stringstream buf; + buf << "Frame format mismatch: expected " << av_get_pix_fmt_name(format_); + buf << ", got " << av_get_pix_fmt_name((AVPixelFormat)input->format); + throw std::runtime_error(buf.str()); + } + + auto outContainer = new VideoFrame(); + auto output = outContainer->pointer(); + output->format = AV_PIX_FMT_YUV420P; + + if (av_hwframe_transfer_data(output, input, 0) < 0) { + throw std::runtime_error("Unable to extract data from VAAPI frame"); + } + + if (av_frame_copy_props(output, input) < 0 ) { + av_frame_unref(output); + } + + av_frame_unref(input); + av_frame_move_ref(input, output); + } catch (const std::runtime_error& e) { + fail(); + RING_ERR("%s", e.what()); + return false; + } + + succeed(); + return true; +} + +bool +VaapiAccel::init(AVCodecContext* codecCtx) +{ + vaProfile_ = VAProfileNone; + vaEntryPoint_ = VAEntrypointVLD; + using ProfileMap = std::map<int, VAProfile>; + ProfileMap h264 = { + { FF_PROFILE_H264_CONSTRAINED_BASELINE, VAProfileH264ConstrainedBaseline }, + { FF_PROFILE_H264_BASELINE, VAProfileH264Baseline }, + { FF_PROFILE_H264_MAIN, VAProfileH264Main }, + { FF_PROFILE_H264_HIGH, VAProfileH264High } + }; + ProfileMap mpeg4 = { + { FF_PROFILE_MPEG4_SIMPLE, VAProfileMPEG4Simple }, + { FF_PROFILE_MPEG4_ADVANCED_SIMPLE, VAProfileMPEG4AdvancedSimple }, + { FF_PROFILE_MPEG4_MAIN, VAProfileMPEG4Main } + }; + ProfileMap h263 = { + { FF_PROFILE_UNKNOWN, VAProfileH263Baseline } + }; + + std::map<int, ProfileMap> profileMap = { + { AV_CODEC_ID_H264, h264 }, + { AV_CODEC_ID_MPEG4, mpeg4 }, + { AV_CODEC_ID_H263, h263 }, + { AV_CODEC_ID_H263P, h263 } // no clue if this'll work, #ffmpeg isn't answering me + }; + + VAStatus status; + +#ifdef HAVE_VAAPI_ACCEL_DRM + const char* deviceName = "/dev/dri/card0"; // check for renderDX first? +#else + const char* deviceName = nullptr; // use default device +#endif + + AVBufferRef* hardwareDeviceCtx; + if (av_hwdevice_ctx_create(&hardwareDeviceCtx, AV_HWDEVICE_TYPE_VAAPI, deviceName, nullptr, 0) < 0) { + RING_ERR("Failed to create VAAPI device"); + av_buffer_unref(&hardwareDeviceCtx); + return false; + } + + deviceBufferRef_.reset(av_buffer_ref(hardwareDeviceCtx)); + + auto device = reinterpret_cast<AVHWDeviceContext*>(deviceBufferRef_->data); + vaConfig_ = VA_INVALID_ID; + vaContext_ = VA_INVALID_ID; + auto hardwareContext = static_cast<AVVAAPIDeviceContext*>(device->hwctx); + + int numProfiles = vaMaxNumProfiles(hardwareContext->display); + auto profiles = std::vector<VAProfile>(numProfiles); + status = vaQueryConfigProfiles(hardwareContext->display, profiles.data(), &numProfiles); + if (status != VA_STATUS_SUCCESS) { + RING_ERR("Failed to query profiles: %s", vaErrorStr(status)); + return false; + } + + VAProfile codecProfile; + auto itOuter = profileMap.find(codecCtx->codec_id); + if (itOuter != profileMap.end()) { + auto innerMap = itOuter->second; + auto itInner = innerMap.find(codecCtx->profile); + if (itInner != innerMap.end()) { + codecProfile = itInner->second; + } + } + + auto iter = std::find_if(std::begin(profiles), + std::end(profiles), + [codecProfile](const VAProfile& p){ return p == codecProfile; }); + + if (iter == std::end(profiles)) { + RING_ERR("VAAPI does not support selected codec"); + return false; + } + + vaProfile_ = *iter; + + status = vaCreateConfig(hardwareContext->display, vaProfile_, vaEntryPoint_, 0, 0, &vaConfig_); + if (status != VA_STATUS_SUCCESS) { + RING_ERR("Failed to create VAAPI configuration: %s", vaErrorStr(status)); + return false; + } + + auto hardwareConfig = static_cast<AVVAAPIHWConfig*>(av_hwdevice_hwconfig_alloc(deviceBufferRef_.get())); + hardwareConfig->config_id = vaConfig_; + + auto constraints = av_hwdevice_get_hwframe_constraints(deviceBufferRef_.get(), hardwareConfig); + if (width_ < constraints->min_width + || width_ > constraints->max_width + || height_ < constraints->min_height + || height_ > constraints->max_height) { + av_hwframe_constraints_free(&constraints); + av_freep(&hardwareConfig); + RING_ERR("Hardware does not support image size with VAAPI: %dx%d", width_, height_); + return false; + } + + int numSurfaces = 16; // based on codec instead? + if (codecCtx->active_thread_type & FF_THREAD_FRAME) + numSurfaces += codecCtx->thread_count; // need extra surface per thread + + framesBufferRef_.reset(av_hwframe_ctx_alloc(deviceBufferRef_.get())); + auto frames = reinterpret_cast<AVHWFramesContext*>(framesBufferRef_->data); + frames->format = AV_PIX_FMT_VAAPI; + frames->sw_format = AV_PIX_FMT_YUV420P; + frames->width = width_; + frames->height = height_; + frames->initial_pool_size = numSurfaces; + + if (av_hwframe_ctx_init(framesBufferRef_.get()) < 0) { + RING_ERR("Failed to initialize VAAPI frame context"); + return false; + } + + auto framesContext = static_cast<AVVAAPIFramesContext*>(frames->hwctx); + status = vaCreateContext(hardwareContext->display, vaConfig_, width_, height_, + VA_PROGRESSIVE, framesContext->surface_ids, framesContext->nb_surfaces, &vaContext_); + if (status != VA_STATUS_SUCCESS) { + RING_ERR("Failed to create VAAPI context: %s", vaErrorStr(status)); + return false; + } + + RING_DBG("VAAPI decoder initialized"); + + ffmpegAccelCtx_.display = hardwareContext->display; + ffmpegAccelCtx_.config_id = vaConfig_; + ffmpegAccelCtx_.context_id = vaContext_; + codecCtx->hwaccel_context = (void*)&ffmpegAccelCtx_; + return true; +} + +}} + +#endif // defined(RING_VIDEO) && defined(RING_ACCEL) diff --git a/src/media/video/v4l2/vaapi.h b/src/media/video/v4l2/vaapi.h new file mode 100644 index 0000000000000000000000000000000000000000..989f26731767b8d61d4effe2dc94bc9e0279ecb1 --- /dev/null +++ b/src/media/video/v4l2/vaapi.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 Savoir-faire Linux Inc. + * + * Author: Philippe Gorley <philippe.gorley@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. + */ + +#pragma once + +#include "libav_deps.h" // MUST BE INCLUDED FIRST + +#include "config.h" + +#if defined(RING_VIDEO) && defined(RING_ACCEL) + +extern "C" { +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include <va/va.h> +#ifdef HAVE_VAAPI_ACCEL_DRM +# include <va/va_drm.h> +#endif +#ifdef HAVE_VAAPI_ACCEL_X11 +# include <va/va_x11.h> +#endif + +#include <libavutil/avconfig.h> +#include <libavutil/buffer.h> +#include <libavutil/frame.h> +#include <libavutil/hwcontext.h> +#include <libavutil/hwcontext_vaapi.h> + +#include <libavcodec/vaapi.h> +} + +#include "video/accel.h" + +#include <memory> +#include <functional> + +namespace ring { namespace video { + +class VaapiAccel : public HardwareAccel { + public: + VaapiAccel(AccelInfo info); + ~VaapiAccel(); + + bool init(AVCodecContext* codecCtx) override; + int allocateBuffer(AVCodecContext* codecCtx, AVFrame* frame, int flags) override; + bool extractData(AVCodecContext* codecCtx, VideoFrame& container) override; + + private: + using AVBufferRefPtr = std::unique_ptr<AVBufferRef, std::function<void(AVBufferRef*)>>; + AVBufferRefPtr deviceBufferRef_; + AVBufferRefPtr framesBufferRef_; + + VAProfile vaProfile_; + VAEntrypoint vaEntryPoint_; + VAConfigID vaConfig_; + VAContextID vaContext_; + + struct vaapi_context ffmpegAccelCtx_; +}; + +}} // namespace ring::video + +#endif // defined(RING_VIDEO) && defined(RING_ACCEL) diff --git a/src/media/video/video_base.cpp b/src/media/video/video_base.cpp index 63d1e01457031147b982860aebbb80c7d5335de0..366447a1850516bc8b3641f8f08a1e72e18ae326 100644 --- a/src/media/video/video_base.cpp +++ b/src/media/video/video_base.cpp @@ -81,6 +81,7 @@ VideoSettings::VideoSettings(const std::map<std::string, std::string>& settings) channel = extractString(settings, "channel"); video_size = extractString(settings, "size"); framerate = extractString(settings, "rate"); + enableAccel = extractString(settings, "enableAccel"); } std::map<std::string, std::string> @@ -90,7 +91,8 @@ VideoSettings::to_map() const {"name", name}, {"size", video_size}, {"channel", channel}, - {"rate", framerate} + {"rate", framerate}, + {"enableAccel", enableAccel} }; } @@ -105,6 +107,7 @@ convert<ring::video::VideoSettings>::encode(const ring::video::VideoSettings& rh node["video_size"] = rhs.video_size; node["channel"] = rhs.channel; node["framerate"] = rhs.framerate; + node["enableAccel"] = rhs.enableAccel; return node; } @@ -118,6 +121,11 @@ convert<ring::video::VideoSettings>::decode(const Node& node, ring::video::Video rhs.video_size = node["video_size"].as<std::string>(); rhs.channel = node["channel"].as<std::string>(); rhs.framerate = node["framerate"].as<std::string>(); + // optional setting that may or may not be there + try { + rhs.enableAccel = node["enableAccel"].as<std::string>(); + } catch (...) {} + return true; } diff --git a/src/media/video/video_base.h b/src/media/video/video_base.h index 46d28704092e7370d2180889fbd5d039878d94bd..54e3d531f766f553d24dc9983adc8397044ec54a 100644 --- a/src/media/video/video_base.h +++ b/src/media/video/video_base.h @@ -159,6 +159,7 @@ struct VideoSettings std::string channel {}; std::string video_size {}; std::string framerate {}; + std::string enableAccel {}; }; }} // namespace ring::video diff --git a/src/media/video/video_device.h b/src/media/video/video_device.h index 504501f810b5d0e0db89c2852c193164379389a1..b4e9f69119b7e500dc247d0dfa3cabe9dfdccb12 100644 --- a/src/media/video/video_device.h +++ b/src/media/video/video_device.h @@ -128,6 +128,8 @@ public: settings.framerate.c_str()); } + settings.enableAccel = "1"; + return settings; } @@ -141,6 +143,7 @@ public: settings.channel = params.channel_name; settings.video_size = sizeToString(params.width, params.height); settings.framerate = ring::to_string(params.framerate.real()); + settings.enableAccel = params.enableAccel; return settings; } @@ -159,6 +162,7 @@ public: params.width = size.first; params.height = size.second; params.framerate = rateFromString(settings.channel, size, settings.framerate); + params.enableAccel = settings.enableAccel; setDeviceParams(params); }