From bd02113e9230de5af99d0b22e1d41143f58df14a Mon Sep 17 00:00:00 2001 From: philippegorley <philippe.gorley@savoirfairelinux.com> Date: Tue, 16 May 2017 14:22:31 -0400 Subject: [PATCH] video: mac hardware acceleration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds VideoToolbox and VDA hardware accelerations. VideoToolbox supports H.264, H.263 and MPEG4, while VDA only supports H.264. VDA is implemented in case libav is used instead of FFmpeg, as only the latter implements VideoToolbox. This being said, Ring will prefer VideoToolbox. VideoToolbox is OSX 10.8+ and iOS 8+. VDA is OSX 10.6.3+. Both have their respective configure switches. Change-Id: I588fcbb92809a9d6a56bb9b6a7ac3a59874c0186 Tuleap: #1090 Reviewed-by: Anthony Léonard <anthony.leonard@savoirfairelinux.com> --- configure.ac | 34 +++++ contrib/src/ffmpeg/rules.mak | 8 +- contrib/src/libav/rules.mak | 6 +- src/media/video/accel.cpp | 18 +++ src/media/video/osxvideo/Makefile.am | 4 + src/media/video/osxvideo/videotoolbox.h | 64 +++++++++ src/media/video/osxvideo/videotoolbox.mm | 176 +++++++++++++++++++++++ 7 files changed, 306 insertions(+), 4 deletions(-) create mode 100644 src/media/video/osxvideo/videotoolbox.h create mode 100644 src/media/video/osxvideo/videotoolbox.mm diff --git a/configure.ac b/configure.ac index 9b3689ef41..70e6de1e4c 100644 --- a/configure.ac +++ b/configure.ac @@ -463,10 +463,30 @@ AS_IF([test "${SYS}" = linux && test -z "${HAVE_ANDROID_FALSE}"], [ ], [vdpau_available="no"]) ]) ]) +AS_IF([test "${SYS}" = darwin], [ + vt_available="no" + vda_available="no" + AC_CHECK_HEADER([VideoToolbox/VideoToolbox.h], [ + AC_CHECK_HEADER([libavcodec/videotoolbox.h], [ + AC_DEFINE([HAVE_VIDEOTOOLBOX_ACCEL], [1], [VideoToolbox found]) + vt_available="yes" + ], []) + ], []) + AC_CHECK_HEADER([VideoDecodeAcceleration/VDADecoder.h], [ + AC_CHECK_HEADER([libavcodec/vda.h], [ + AC_DEFINE([HAVE_VDA_ACCEL], [1], [VDA found]) + vda_available="yes" + ], []) + ], []) +]) AC_ARG_ENABLE([accel], AS_HELP_STRING([--disable-accel], [Disable all hardware accelerations])) AC_ARG_ENABLE([vdpau], AS_HELP_STRING([--disable-vdpau], [Disable VDPAU hardware acceleration])) AC_ARG_ENABLE([vaapi], AS_HELP_STRING([--disable-vaapi], [Disable VAAPI hardware acceleration])) +AC_ARG_ENABLE([videotoolbox], AS_HELP_STRING([--disable-videotoolbox], [Disable VideoToolbox hardware acceleration])) +AC_ARG_ENABLE([vda], AS_HELP_STRING([--disable-vda], [Disable VDA hardware acceleration])) + +dnl video acceleration only works if there's video AS_IF([test "x$enable_video" != "xno" -a "x$enable_accel" != "xno"], [ ring_accel="yes" AC_DEFINE([RING_ACCEL], [1], [Hardware acceleration is enabled in Ring]) @@ -482,10 +502,24 @@ AS_IF([test "x$enable_video" != "xno" -a "x$enable_accel" != "xno"], [ AC_DEFINE([RING_VDPAU], [1], [VDPAU is available in Ring]) ]) ]) + AS_IF([test "x$enable_videotoolbox" != "xno"], [ + AS_IF([test "x${vt_available}" = "xyes"], [ + ring_vt="yes" + AC_DEFINE([RING_VIDEOTOOLBOX], [1], [VideoToolbox is available in Ring]) + ]) + ]) + AS_IF([test "x$enable_vda" != "xno"], [ + AS_IF([test "x${vda_available}" = "xyes"], [ + ring_vda="yes" + AC_DEFINE([RING_VDA], [1], [VDA is available in Ring]) + ]) + ]) ]) AM_CONDITIONAL([RING_ACCEL], [test "x${ring_accel}" = "xyes"]) AM_CONDITIONAL([RING_VAAPI], [test "x${ring_vaapi}" = "xyes"]) AM_CONDITIONAL([RING_VDPAU], [test "x${ring_vdpau}" = "xyes"]) +AM_CONDITIONAL([RING_VIDEOTOOLBOX], [test "x${ring_vt}" = "xyes"]) +AM_CONDITIONAL([RING_VDA], [test "x${ring_vda}" = "xyes"]) dnl check for GnuTLS PKG_CHECK_MODULES([GNUTLS], [gnutls >= 3.4.14], [HAVE_GNUTLS=1], [HAVE_GNUTLS=0]) diff --git a/contrib/src/ffmpeg/rules.mak b/contrib/src/ffmpeg/rules.mak index 7c6c90b5b9..a3807731f4 100644 --- a/contrib/src/ffmpeg/rules.mak +++ b/contrib/src/ffmpeg/rules.mak @@ -142,7 +142,13 @@ endif ifdef HAVE_MACOSX FFMPEGCONF += \ --enable-indev=avfcapture \ - --enable-indev=avfgrab + --enable-indev=avfgrab \ + --enable-videotoolbox \ + --enable-hwaccel=h263_videotoolbox \ + --enable-hwaccel=h264_videotoolbox \ + --enable-hwaccel=mpeg4_videotoolbox \ + --enable-vda \ + --enable-hwaccel=h264_vda endif ifdef HAVE_IOS diff --git a/contrib/src/libav/rules.mak b/contrib/src/libav/rules.mak index 9f51348681..307ff9f95f 100644 --- a/contrib/src/libav/rules.mak +++ b/contrib/src/libav/rules.mak @@ -137,9 +137,9 @@ ifdef HAVE_NEON LIBAVCONF += --as="$(AS)" endif endif -#ifdef HAVE_MACOSX -#LIBAVCONF += --enable-vda -#endif +ifdef HAVE_MACOSX +LIBAVCONF += --enable-vda --enable-hwaccel=h264_vda +endif # Linux ifdef HAVE_LINUX diff --git a/src/media/video/accel.cpp b/src/media/video/accel.cpp index e17f49f776..b074292f2f 100644 --- a/src/media/video/accel.cpp +++ b/src/media/video/accel.cpp @@ -31,6 +31,10 @@ #include "v4l2/vdpau.h" #endif +#if defined(RING_VIDEOTOOLBOX) || defined(RING_VDA) +#include "osxvideo/videotoolbox.h" +#endif + #include "string_utils.h" #include "logger.h" @@ -170,6 +174,8 @@ makeHardwareAccel(AVCodecContext* codecCtx) enum class AccelID { Vdpau, Vaapi, + VideoToolbox, + Vda, }; struct AccelInfo { @@ -198,16 +204,28 @@ makeHardwareAccel(AVCodecContext* codecCtx) #endif #ifdef RING_VDPAU { AccelID::Vdpau, "vdpau", AV_PIX_FMT_VDPAU, makeHardwareAccel<VdpauAccel> }, +#endif +#ifdef RING_VIDEOTOOLBOX + { AccelID::VideoToolbox, "videotoolbox", AV_PIX_FMT_VIDEOTOOLBOX, makeHardwareAccel<VideoToolboxAccel> }, +#endif +#ifdef RING_VDA + { AccelID::Vda, "vda", AV_PIX_FMT_VDA, makeHardwareAccel<VideoToolboxAccel> }, #endif }; std::vector<AccelID> possibleAccels = {}; switch (codecCtx->codec_id) { case AV_CODEC_ID_H264: + possibleAccels.push_back(AccelID::Vdpau); + possibleAccels.push_back(AccelID::Vaapi); + possibleAccels.push_back(AccelID::VideoToolbox); + possibleAccels.push_back(AccelID::Vda); + break; case AV_CODEC_ID_MPEG4: case AV_CODEC_ID_H263P: possibleAccels.push_back(AccelID::Vdpau); possibleAccels.push_back(AccelID::Vaapi); + possibleAccels.push_back(AccelID::VideoToolbox); break; case AV_CODEC_ID_VP8: break; diff --git a/src/media/video/osxvideo/Makefile.am b/src/media/video/osxvideo/Makefile.am index f471e28a58..d228e21e7e 100644 --- a/src/media/video/osxvideo/Makefile.am +++ b/src/media/video/osxvideo/Makefile.am @@ -6,4 +6,8 @@ libosxvideo_la_SOURCES = \ video_device_impl.mm \ video_device_monitor_impl.mm +if RING_ACCEL +libosxvideo_la_SOURCES += videotoolbox.h videotoolbox.mm +endif + AM_OBJCXXFLAGS = -std=c++11 diff --git a/src/media/video/osxvideo/videotoolbox.h b/src/media/video/osxvideo/videotoolbox.h new file mode 100644 index 0000000000..587311b4b4 --- /dev/null +++ b/src/media/video/osxvideo/videotoolbox.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016-2017 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_VIDEOTOOLBOX) || defined(RING_VDA) + +extern "C" { +#include <libavcodec/avcodec.h> +#ifdef RING_VIDEOTOOLBOX +#include <libavcodec/videotoolbox.h> +#endif +#ifdef RING_VDA +#include <libavcodec/vda.h> +#endif +#include <libavutil/imgutils.h> +} + +#include "video/accel.h" + +#include <memory> +#include <functional> + +namespace ring { namespace video { + +class VideoToolboxAccel : public HardwareAccel { + public: + VideoToolboxAccel(const std::string name, const AVPixelFormat format); + ~VideoToolboxAccel(); + + bool checkAvailability() override; + bool init() override; + int allocateBuffer(AVFrame* frame, int flags) override; + void extractData(VideoFrame& input, VideoFrame& output) override; + + private: + bool usingVT_ = false; + std::string decoderName_; +}; + +}} // namespace ring::video + +#endif // defined(RING_VIDEOTOOLBOX) || defined(RING_VDA) diff --git a/src/media/video/osxvideo/videotoolbox.mm b/src/media/video/osxvideo/videotoolbox.mm new file mode 100644 index 0000000000..d6af306724 --- /dev/null +++ b/src/media/video/osxvideo/videotoolbox.mm @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2016-2017 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_VIDEOTOOLBOX) || defined(RING_VDA) + +#include <string> +#include <sstream> +#include <array> + +#include "video/osxvideo/videotoolbox.h" +#include "video/accel.h" + +#include "logger.h" + +namespace ring { namespace video { + +VideoToolboxAccel::VideoToolboxAccel(const std::string name, const AVPixelFormat format) + : HardwareAccel(name, format) +{ +} + +VideoToolboxAccel::~VideoToolboxAccel() +{ + if (codecCtx_) { + if (usingVT_) { +#ifdef RING_VIDEOTOOLBOX + av_videotoolbox_default_free(codecCtx_); +#endif + } else { +#ifdef RING_VDA + av_vda_default_free(codecCtx_); +#endif + } + } +} + +int +VideoToolboxAccel::allocateBuffer(AVFrame* frame, int flags) +{ + // do nothing, as this is done during extractData for VideoT and VDA + (void) frame; // unused + (void) flags; // unused + return 0; +} + +void +VideoToolboxAccel::extractData(VideoFrame& input, VideoFrame& output) +{ + auto inFrame = input.pointer(); + auto outFrame = output.pointer(); + auto pixelBuffer = reinterpret_cast<CVPixelBufferRef>(inFrame->data[3]); + auto pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer); + + switch (pixelFormat) { + case kCVPixelFormatType_420YpCbCr8Planar: + outFrame->format = AV_PIX_FMT_YUV420P; + break; + case kCVPixelFormatType_32BGRA: + outFrame->format = AV_PIX_FMT_BGRA; + break; + case kCVPixelFormatType_422YpCbCr8: + outFrame->format = AV_PIX_FMT_UYVY422; + break; + case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: // OS X 10.7+ + outFrame->format = AV_PIX_FMT_NV12; + break; + default: + char codecTag[32]; + av_get_codec_tag_string(codecTag, sizeof(codecTag), codecCtx_->codec_tag); + std::stringstream buf; + buf << decoderName_ << " (" << codecTag << "): unsupported pixel format ("; + buf << av_get_pix_fmt_name(format_) << ")"; + throw std::runtime_error(buf.str()); + } + + outFrame->width = inFrame->width; + outFrame->height = inFrame->height; + // align on 32 bytes + if (av_frame_get_buffer(outFrame, 32) < 0) { + std::stringstream buf; + buf << "Could not allocate a buffer for " << decoderName_; + throw std::runtime_error(buf.str()); + } + + if (CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly) != kCVReturnSuccess) { + throw std::runtime_error("Could not lock the pixel buffer"); + } + + // av_image_copy function takes a 4 element array (according to its signature) + std::array<uint8_t*, 4> buffer = {}; + std::array<int, 4> lineSize = {}; + if (CVPixelBufferIsPlanar(pixelBuffer)) { + int planeCount = CVPixelBufferGetPlaneCount(pixelBuffer); + for (int i = 0; i < planeCount; i++) { + buffer[i] = static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, i)); + lineSize[i] = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, i); + } + } else { + buffer[0] = static_cast<uint8_t*>(CVPixelBufferGetBaseAddress(pixelBuffer)); + lineSize[0] = CVPixelBufferGetBytesPerRow(pixelBuffer); + } + + av_image_copy(outFrame->data, outFrame->linesize, + const_cast<const uint8_t**>(static_cast<uint8_t**>(buffer.data())), + lineSize.data(), static_cast<AVPixelFormat>(outFrame->format), + inFrame->width, inFrame->height); + + if (av_frame_copy_props(outFrame, inFrame) < 0) { + av_frame_unref(outFrame); + } + + CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); +} + +bool +VideoToolboxAccel::checkAvailability() +{ + // VideoToolbox is always present on Mac 10.8+ and iOS 8+ + // VDA is always present on Mac 10.6.3+ + return true; +} + +bool +VideoToolboxAccel::init() +{ + decoderName_ = ""; + bool success = false; +#ifdef RING_VIDEOTOOLBOX + if (int ret = av_videotoolbox_default_init(codecCtx_) == 0) { + success = true; + usingVT_ = true; + decoderName_ = "VideoToolbox"; + } +#endif +#ifdef RING_VDA + if (!success) { + if (int ret = av_vda_default_init(codecCtx_) == 0) { + success = true; + usingVT_ = false; + decoderName_ = "VDA"; + } + } +#endif + + if (success) + RING_DBG("%s decoder initialized", decoderName_.c_str()); + else + RING_ERR("Failed to initialize Mac hardware accelerator"); + + return success; +} + +}} + +#endif // defined(RING_VIDEOTOOLBOX) || defined(RING_VDA) -- GitLab