diff --git a/src/media/media_decoder.cpp b/src/media/media_decoder.cpp index 18f146bb9d84069b13aca933899f932bb8f20f1e..7813353f12e8c72fc0104b565260a4b3a6197c96 100644 --- a/src/media/media_decoder.cpp +++ b/src/media/media_decoder.cpp @@ -48,6 +48,8 @@ namespace ring { const unsigned jitterBufferMaxSize_ {1500}; // maximum time a packet can be queued const constexpr auto jitterBufferMaxDelay_ = std::chrono::milliseconds(50); +// maximum number of times accelerated decoding can fail in a row before falling back to software +const constexpr unsigned MAX_ACCEL_FAILURES { 5 }; MediaDecoder::MediaDecoder() : inputCtx_(avformat_alloc_context()), @@ -57,6 +59,8 @@ MediaDecoder::MediaDecoder() : MediaDecoder::~MediaDecoder() { + if (decoderCtx_->hw_device_ctx) + av_buffer_unref(&decoderCtx_->hw_device_ctx); if (decoderCtx_) avcodec_close(decoderCtx_); if (inputCtx_) @@ -106,7 +110,7 @@ int MediaDecoder::openInput(const DeviceParams& params) #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 + // it has been disabled already by the video_receive_thread/video_input enableAccel_ &= Manager::instance().getDecodingAccelerated(); #endif @@ -301,21 +305,21 @@ int MediaDecoder::setupFromVideoData() decoderCtx_->thread_count = std::max(1u, std::min(8u, std::thread::hardware_concurrency()/2)); + if (emulateRate_) { + RING_DBG("Using framerate emulation"); + startTime_ = av_gettime(); + } + #ifdef RING_ACCEL if (enableAccel_) { - accel_ = video::makeHardwareAccel(decoderCtx_); - decoderCtx_->opaque = accel_.get(); + accel_ = video::setupHardwareDecoding(decoderCtx_); + decoderCtx_->opaque = &accel_; } else if (Manager::instance().getDecodingAccelerated()) { RING_WARN("Hardware accelerated decoding disabled because of previous failure"); } else { RING_WARN("Hardware accelerated decoding disabled by user preference"); } -#endif // RING_ACCEL - - if (emulateRate_) { - RING_DBG("Using framerate emulation"); - startTime_ = av_gettime(); - } +#endif ret = avcodec_open2(decoderCtx_, inputDecoder_, NULL); if (ret) { @@ -353,18 +357,10 @@ MediaDecoder::decode(VideoFrame& result) int frameFinished = 0; ret = avcodec_send_packet(decoderCtx_, &inpacket); if (ret < 0) { -#ifdef RING_ACCEL - if (accel_ && accel_->hasFailed()) - return Status::RestartRequired; -#endif return ret == AVERROR_EOF ? Status::Success : Status::DecodeError; } ret = avcodec_receive_frame(decoderCtx_, frame); if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) { -#ifdef RING_ACCEL - if (accel_ && accel_->hasFailed()) - return Status::RestartRequired; -#endif return Status::DecodeError; } if (ret >= 0) @@ -375,13 +371,18 @@ MediaDecoder::decode(VideoFrame& result) if (frameFinished) { frame->format = (AVPixelFormat) correctPixFmt(frame->format); #ifdef RING_ACCEL - if (accel_) { - if (!accel_->hasFailed()) - accel_->extractData(result); - else - return Status::RestartRequired; + if (!accel_.name.empty()) { + ret = video::transferFrameData(accel_, decoderCtx_, result); + if (ret < 0) { + ++accelFailures_; + if (accelFailures_ >= MAX_ACCEL_FAILURES) { + RING_ERR("Hardware decoding failure"); + accelFailures_ = 0; // reset error count for next time + return Status::RestartRequired; + } + } } -#endif // RING_ACCEL +#endif if (emulateRate_ and frame->pts != AV_NOPTS_VALUE) { auto frame_time = getTimeBase()*(frame->pts - avStream_->start_time); auto target = startTime_ + static_cast<std::int64_t>(frame_time.real() * 1e6); @@ -457,7 +458,9 @@ MediaDecoder::enableAccel(bool enableAccel) { enableAccel_ = enableAccel; if (!enableAccel) { - accel_.reset(); + accel_ = {}; + if (decoderCtx_->hw_device_ctx) + av_buffer_unref(&decoderCtx_->hw_device_ctx); if (decoderCtx_) decoderCtx_->opaque = nullptr; } @@ -487,9 +490,9 @@ MediaDecoder::flush(VideoFrame& result) #ifdef RING_ACCEL // flush is called when closing the stream // so don't restart the media decoder - if (accel_ && !accel_->hasFailed()) - accel_->extractData(result); -#endif // RING_ACCEL + if (!accel_.name.empty() && accelFailures_ < MAX_ACCEL_FAILURES) + video::transferFrameData(accel_, decoderCtx_, result); +#endif return Status::FrameFinished; } diff --git a/src/media/media_decoder.h b/src/media/media_decoder.h index c2bb87db99fe8d688ece51546bb2aa90b95e23d9..7835d67ee6a5e428510ba33a98c6eb4b0c8aeccb 100644 --- a/src/media/media_decoder.h +++ b/src/media/media_decoder.h @@ -26,6 +26,10 @@ #include "video/video_scaler.h" #endif // RING_VIDEO +#ifdef RING_ACCEL +#include "video/accel.h" +#endif + #include "audio/audiobuffer.h" #include "rational.h" @@ -44,12 +48,6 @@ class AVCodec; namespace ring { -#ifdef RING_ACCEL -namespace video { -class HardwareAccel; -} -#endif - struct AudioFrame; class AudioFormat; class RingBuffer; @@ -120,7 +118,8 @@ class MediaDecoder { #ifdef RING_ACCEL bool enableAccel_ = true; - std::unique_ptr<video::HardwareAccel> accel_; + video::HardwareAccel accel_; + unsigned short accelFailures_ = 0; #endif protected: diff --git a/src/media/video/accel.cpp b/src/media/video/accel.cpp index d91ae52eed847eed142fb89a8f3c5f9c134ed666..45b60a1a95ebd256e81fdb7b75183e0e82df4bff 100644 --- a/src/media/video/accel.cpp +++ b/src/media/video/accel.cpp @@ -18,232 +18,129 @@ * 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" - -#ifdef RING_VAAPI -#include "v4l2/vaapi.h" -#endif - -#ifdef RING_VDPAU -#include "v4l2/vdpau.h" -#endif +extern "C" { +#include <libavutil/hwcontext.h> +} -#ifdef RING_VIDEOTOOLBOX -#include "osxvideo/videotoolbox.h" -#endif +#include <algorithm> +#include "media_buffer.h" #include "string_utils.h" +#include "fileutils.h" #include "logger.h" - -#include <sstream> -#include <algorithm> +#include "accel.h" +#include "config.h" 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); - } AVPixelFormat fallback = AV_PIX_FMT_NONE; for (int i = 0; formats[i] != AV_PIX_FMT_NONE; ++i) { fallback = formats[i]; - if (formats[i] == accel->format()) { - accel->setWidth(codecCtx->coded_width); - accel->setHeight(codecCtx->coded_height); - accel->setProfile(codecCtx->profile); - accel->setCodecCtx(codecCtx); - if (accel->init()) - return accel->format(); + if (formats[i] == accel->format) { + return formats[i]; } } - accel->fail(true); - RING_WARN("Falling back to software decoding"); - codecCtx->get_format = avcodec_default_get_format; - codecCtx->get_buffer2 = avcodec_default_get_buffer2; + RING_WARN("'%s' acceleration not supported, falling back to software decoding", accel->name.c_str()); + accel->name = {}; // don't use accel return fallback; } -static int -allocateBufferCb(AVCodecContext* codecCtx, AVFrame* frame, int flags) +int +transferFrameData(HardwareAccel accel, AVCodecContext* codecCtx, VideoFrame& frame) { - if (auto accel = static_cast<HardwareAccel*>(codecCtx->opaque)) { - if (!accel->hasFailed() && accel->allocateBuffer(frame, flags) == 0) { - accel->succeedAllocation(); - return 0; - } - - accel->failAllocation(); + if (accel.name.empty()) + return -1; + + auto input = frame.pointer(); + if (input->format != accel.format) { + RING_ERR("Frame format mismatch: expected %s, got %s", + av_get_pix_fmt_name(static_cast<AVPixelFormat>(accel.format)), + av_get_pix_fmt_name(static_cast<AVPixelFormat>(input->format))); + return -1; } - return avcodec_default_get_buffer2(codecCtx, frame, flags); -} + // FFmpeg requires a second frame in which to transfer the data from the GPU buffer to the main memory + auto container = std::unique_ptr<VideoFrame>(new VideoFrame()); + auto output = container->pointer(); -HardwareAccel::HardwareAccel(const std::string& name, const AVPixelFormat format) - : name_(name) - , format_(format) -{} + // most hardware accelerations output NV12, so skip extra conversions + output->format = AV_PIX_FMT_NV12; + int ret = av_hwframe_transfer_data(output, input, 0); -void -HardwareAccel::failAllocation() -{ - ++allocationFails_; - fail(false); -} + // move output into input so the caller receives extracted image data + // but we have to delete input's data first + av_frame_unref(input); + av_frame_move_ref(input, output); -void -HardwareAccel::failExtraction() -{ - ++extractionFails_; - fail(false); -} - -void -HardwareAccel::fail(bool forceFallback) -{ - if (allocationFails_ >= MAX_ACCEL_FAILURES || extractionFails_ >= MAX_ACCEL_FAILURES || forceFallback) { - RING_ERR("Hardware acceleration failure"); - fallback_ = true; - allocationFails_ = 0; - extractionFails_ = 0; - if (codecCtx_) { - codecCtx_->get_format = avcodec_default_get_format; - codecCtx_->get_buffer2 = avcodec_default_get_buffer2; - } - } + return ret; } -bool -HardwareAccel::extractData(VideoFrame& input) +static int +openDevice(HardwareAccel accel, AVBufferRef** hardwareDeviceCtx) { - try { - auto inFrame = input.pointer(); - - if (inFrame->format != format_) { - std::stringstream buf; - buf << "Frame format mismatch: expected " << av_get_pix_fmt_name(format_); - buf << ", got " << av_get_pix_fmt_name((AVPixelFormat)inFrame->format); - throw std::runtime_error(buf.str()); + int ret; + auto hwType = av_hwdevice_find_type_by_name(accel.name.c_str()); +#ifdef HAVE_VAAPI_ACCEL_DRM + // default DRM device may not work on multi GPU computers, so check all possible values + if (accel.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) { + RING_DBG("Using '%s' hardware acceleration with device '%s'", accel.name.c_str(), deviceName.c_str()); + return ret; + } } - - // FFmpeg requires a second frame in which to transfer the data - // from the GPU buffer to the main memory - auto output = std::unique_ptr<VideoFrame>(new VideoFrame()); - auto outFrame = output->pointer(); - outFrame->format = AV_PIX_FMT_YUV420P; - - extractData(input, *output); - - // move outFrame into inFrame so the caller receives extracted image data - // but we have to delete inFrame first - av_frame_unref(inFrame); - av_frame_move_ref(inFrame, outFrame); - } catch (const std::runtime_error& e) { - failExtraction(); - RING_ERR("%s", e.what()); - return false; } +#endif + // default device (nullptr) works for most cases + if ((ret = av_hwdevice_ctx_create(hardwareDeviceCtx, hwType, nullptr, nullptr, 0)) >= 0) + RING_DBG("Using '%s' hardware acceleration", accel.name.c_str()); - succeedExtraction(); - return true; -} - -template <class T> -static std::unique_ptr<HardwareAccel> -makeHardwareAccel(const std::string name, const AVPixelFormat format) { - return std::unique_ptr<HardwareAccel>(new T(name, format)); + return ret; } -std::unique_ptr<HardwareAccel> -makeHardwareAccel(AVCodecContext* codecCtx) +const HardwareAccel +setupHardwareDecoding(AVCodecContext* codecCtx) { - enum class AccelID { - NoAccel, - Vdpau, - Vaapi, - VideoToolbox, - }; - - struct AccelInfo { - AccelID type; - std::string name; - AVPixelFormat format; - std::unique_ptr<HardwareAccel> (*create)(const std::string name, const AVPixelFormat format); - }; - - /* 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): - * - Create an AccelID and add it to the switch statement - * - Give it a name (this is used for the daemon logs) - * - Specify its AVPixelFormat (the one used by FFmpeg: check pixfmt.h) - * - Add a function pointer that returns an instance (makeHardwareAccel<> does this already) - * Note: the include of the acceleration's header file must be guarded by the same #ifdef as - * in this array. + /** + * This array represents FFmpeg's hwaccels, along with their pixel format + * and their potentially supported codecs. Each item contains: + * - Name (must match the name used in FFmpeg) + * - Pixel format (tells FFmpeg which hwaccel to use) + * - Array of AVCodecID (potential codecs that can be accelerated by the hwaccel) + * Note: an empty name means the video isn't accelerated */ - const AccelInfo accels[] = { -#ifdef RING_VAAPI - { AccelID::Vaapi, "vaapi", AV_PIX_FMT_VAAPI, makeHardwareAccel<VaapiAccel> }, -#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 - { AccelID::NoAccel, "none", AV_PIX_FMT_NONE, nullptr }, + const HardwareAccel accels[] = { + { "vaapi", AV_PIX_FMT_VAAPI, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4, AV_CODEC_ID_H263 } }, + { "vdpau", AV_PIX_FMT_VDPAU, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4, AV_CODEC_ID_H263 } }, + { "videotoolbox", AV_PIX_FMT_VIDEOTOOLBOX, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4, AV_CODEC_ID_H263 } }, }; - 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); - 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; - default: - break; - } - - for (auto& info : accels) { - for (auto& pa : possibleAccels) { - if (info.type == pa) { - auto accel = info.create(info.name, info.format); - // don't break if the check fails, we want to check every possibility - if (accel->checkAvailability()) { - codecCtx->get_format = getFormatCb; - codecCtx->get_buffer2 = allocateBufferCb; - codecCtx->thread_safe_callbacks = 1; - RING_DBG("Attempting to use '%s' hardware acceleration", accel->name().c_str()); - return accel; - } + AVBufferRef* hardwareDeviceCtx = nullptr; + for (auto accel : accels) { + if (std::find(accel.supportedCodecs.begin(), accel.supportedCodecs.end(), + static_cast<AVCodecID>(codecCtx->codec_id)) != accel.supportedCodecs.end()) { + if (openDevice(accel, &hardwareDeviceCtx) >= 0) { + codecCtx->hw_device_ctx = av_buffer_ref(hardwareDeviceCtx); + codecCtx->get_format = getFormatCb; + codecCtx->thread_safe_callbacks = 1; + return accel; } } } - RING_WARN("Not using hardware acceleration"); - return nullptr; + RING_WARN("Not using hardware accelerated decoding"); + return {}; } }} // namespace ring::video diff --git a/src/media/video/accel.h b/src/media/video/accel.h index 265f6d379eefb3eb28c56e8fd63cc050179a6531..f438e8f8cf024d9959df07cb83b0502e98891714 100644 --- a/src/media/video/accel.h +++ b/src/media/video/accel.h @@ -21,57 +21,19 @@ #pragma once #include "libav_deps.h" -#include "media_buffer.h" -#include "config.h" #include <string> -#include <memory> +#include <vector> namespace ring { namespace video { -class HardwareAccel { - public: - HardwareAccel(const std::string& name, const AVPixelFormat format); - virtual ~HardwareAccel() {}; - - AVPixelFormat format() const { return format_; } - std::string name() const { return name_; } - bool hasFailed() const { return fallback_; } - - void setCodecCtx(AVCodecContext* codecCtx) { codecCtx_ = codecCtx; } - void setWidth(int width) { width_ = width; } - void setHeight(int height) { height_ = height; } - void setProfile(int profile) { profile_ = profile; } - - void failAllocation(); - void failExtraction(); - void fail(bool forceFallback); - void succeedAllocation() { allocationFails_ = 0; } - void succeedExtraction() { extractionFails_ = 0; } - - // wrapper to take care of boilerplate before calling the derived class's implementation - bool extractData(VideoFrame& input); - - public: // must be implemented by derived classes - virtual bool checkAvailability() = 0; - virtual bool init() = 0; - virtual int allocateBuffer(AVFrame* frame, int flags) = 0; - virtual void extractData(VideoFrame& input, VideoFrame& output) = 0; - - protected: - AVCodecContext* codecCtx_ = nullptr; - std::string name_; - AVPixelFormat format_; - unsigned allocationFails_ = 0; // how many times in a row allocateBuffer has failed - unsigned extractionFails_ = 0; // how many times in a row extractData has failed - bool fallback_ = false; // set to true when successive failures exceeds MAX_ACCEL_FAILURES - int width_ = -1; - int height_ = -1; - int profile_ = -1; +struct HardwareAccel { + std::string name; + AVPixelFormat format; + std::vector<AVCodecID> supportedCodecs; }; -// HardwareAccel factory -// Checks if codec acceleration is possible -std::unique_ptr<HardwareAccel> makeHardwareAccel(AVCodecContext* codecCtx); +const HardwareAccel setupHardwareDecoding(AVCodecContext* codecCtx); +int transferFrameData(HardwareAccel accel, AVCodecContext* codecCtx, VideoFrame& frame); }} // namespace ring::video diff --git a/src/media/video/osxvideo/Makefile.am b/src/media/video/osxvideo/Makefile.am index d228e21e7ea59d532403a1d3c9a88ad64595c8b4..f471e28a58e26389ddc9deb961778f040789d7d5 100644 --- a/src/media/video/osxvideo/Makefile.am +++ b/src/media/video/osxvideo/Makefile.am @@ -6,8 +6,4 @@ 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 deleted file mode 100644 index 90ea36031050aff04d0d4e5c269a1b0501694789..0000000000000000000000000000000000000000 --- a/src/media/video/osxvideo/videotoolbox.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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" - -#ifdef RING_VIDEOTOOLBOX - -extern "C" { -#include <libavcodec/avcodec.h> -#include <libavutil/hwcontext.h> -#include <libavcodec/videotoolbox.h> -#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: - using AVBufferRefPtr = std::unique_ptr<AVBufferRef, std::function<void(AVBufferRef*)>>; - AVBufferRefPtr deviceBufferRef_; -}; - -}} // namespace ring::video - -#endif // RING_VIDEOTOOLBOX diff --git a/src/media/video/osxvideo/videotoolbox.mm b/src/media/video/osxvideo/videotoolbox.mm deleted file mode 100644 index ab058d772a9a315b759254ad955c85d82fae452c..0000000000000000000000000000000000000000 --- a/src/media/video/osxvideo/videotoolbox.mm +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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" - -#ifdef RING_VIDEOTOOLBOX - -#include <string> -#include <sstream> -#include <array> - -#include "video/osxvideo/videotoolbox.h" -#include "video/accel.h" - -#include "logger.h" - -namespace ring { namespace video { - -static auto avBufferRefDeleter = [](AVBufferRef* buf){ av_buffer_unref(&buf); }; - -VideoToolboxAccel::VideoToolboxAccel(const std::string name, const AVPixelFormat format) - : HardwareAccel(name, format) - , deviceBufferRef_(nullptr, avBufferRefDeleter) -{ -} - -VideoToolboxAccel::~VideoToolboxAccel() -{ - if (codecCtx_) - av_videotoolbox_default_free(codecCtx_); -} - -int -VideoToolboxAccel::allocateBuffer(AVFrame* frame, int flags) -{ - // do nothing, as this is done during extractData - (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 << "VideoToolbox (" << 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 VideoToolbox"; - 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() -{ - AVBufferRef* hardwareDeviceCtx; - if (av_hwdevice_ctx_create(&hardwareDeviceCtx, AV_HWDEVICE_TYPE_VIDEOTOOLBOX, nullptr, nullptr, 0) == 0) { - deviceBufferRef_.reset(hardwareDeviceCtx); - return true; - } - - av_buffer_unref(&hardwareDeviceCtx); - return false; -} - -bool -VideoToolboxAccel::init() -{ - if (av_videotoolbox_default_init(codecCtx_) >= 0) { - RING_DBG("VideoToolbox decoder initialized"); - codecCtx_->hw_device_ctx = av_buffer_ref(deviceBufferRef_.get()); - return true; - } else { - RING_ERR("Failed to initialize VideoToolbox decoder"); - return false; - } -} - -}} - -#endif // RING_VIDEOTOOLBOX diff --git a/src/media/video/v4l2/Makefile.am b/src/media/video/v4l2/Makefile.am index 05ff5a226175f08871c987202b559fffed544603..faaa97fe15d7dd8807c13980eeff8a4e0e457594 100644 --- a/src/media/video/v4l2/Makefile.am +++ b/src/media/video/v4l2/Makefile.am @@ -6,15 +6,7 @@ libv4l2_la_SOURCES = \ video_device_impl.cpp \ video_device_monitor_impl.cpp -if RING_VDPAU -libv4l2_la_SOURCES += vdpau.h vdpau.cpp -endif - -if RING_VAAPI -libv4l2_la_SOURCES += vaapi.h vaapi.cpp -endif - -AM_CXXFLAGS = @LIBAVCODEC_CFLAGS@ @LIBAVFORMAT_CFLAGS@ @LIBAVDEVICE_CFLAGS@ @LIBSWSCALE_CFLAGS@ +AM_CXXFLAGS = @LIBAVCODEC_CFLAGS@ @LIBAVFORMAT_CFLAGS@ @LIBAVDEVICE_CFLAGS@ @LIBSWSCALE_CFLAGS@ @LIBAVUTIL_CFLAGS@ AM_CXXFLAGS += @UDEV_CFLAGS@ @VDPAU_CFLAGS@ @LIBVA_CFLAGS@ @LIBVA_DRM_CFLAGS@ @LIBVA_X11_CFLAGS@ libv4l2_la_LIBADD = @LIBAVCODEC_LIBS@ @LIBAVFORMAT_LIBS@ @LIBAVDEVICE_LIBS@ @LIBSWSCALE_LIBS@ @LIBAVUTIL_LIBS@ diff --git a/src/media/video/v4l2/vaapi.cpp b/src/media/video/v4l2/vaapi.cpp deleted file mode 100644 index b222e8959a6ff9acc367f04f2588419a1880f1af..0000000000000000000000000000000000000000 --- a/src/media/video/v4l2/vaapi.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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" - -#ifdef RING_VAAPI - -#include "video/v4l2/vaapi.h" -#include "video/accel.h" - -#include "fileutils.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(const std::string name, const AVPixelFormat format) - : HardwareAccel(name, format) - , deviceBufferRef_(nullptr, avBufferRefDeleter) - , framesBufferRef_(nullptr, avBufferRefDeleter) -{ -} - -VaapiAccel::~VaapiAccel() -{ -} - -int -VaapiAccel::allocateBuffer(AVFrame* frame, int flags) -{ - (void) flags; // unused - return av_hwframe_get_buffer(framesBufferRef_.get(), frame, 0); -} - -void -VaapiAccel::extractData(VideoFrame& input, VideoFrame& output) -{ - auto inFrame = input.pointer(); - auto outFrame = output.pointer(); - - if (av_hwframe_transfer_data(outFrame, inFrame, 0) < 0) { - throw std::runtime_error("Unable to extract data from VAAPI frame"); - } - - if (av_frame_copy_props(outFrame, inFrame) < 0 ) { - av_frame_unref(outFrame); - } -} - -bool -VaapiAccel::checkAvailability() -{ - AVBufferRef* hardwareDeviceCtx = nullptr; -#ifdef HAVE_VAAPI_ACCEL_DRM - 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 (av_hwdevice_ctx_create(&hardwareDeviceCtx, AV_HWDEVICE_TYPE_VAAPI, deviceName.c_str(), nullptr, 0) >= 0) { - deviceName_ = deviceName; - break; - } - } - if (hardwareDeviceCtx == nullptr) - return false; -#elif HAVE_VAAPI_ACCEL_X11 - if (av_hwdevice_ctx_create(&hardwareDeviceCtx, AV_HWDEVICE_TYPE_VAAPI, nullptr, nullptr, 0) < 0) { - return false; - } -#endif - - deviceBufferRef_.reset(hardwareDeviceCtx); - return true; -} - -bool -VaapiAccel::init() -{ - 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 = format_; - 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; - } - - codecCtx_->hw_frames_ctx = av_buffer_ref(framesBufferRef_.get()); - - if (!deviceName_.empty()) - RING_DBG("VAAPI decoder initialized via device: %s", deviceName_.c_str()); - else - RING_DBG("VAAPI decoder initialized"); - return true; -} - -}} - -#endif // RING_VAAPI diff --git a/src/media/video/v4l2/vaapi.h b/src/media/video/v4l2/vaapi.h deleted file mode 100644 index db061eefcb3443bad76a291ef7bde090704983d6..0000000000000000000000000000000000000000 --- a/src/media/video/v4l2/vaapi.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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" - -#ifdef RING_VAAPI - -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(const std::string name, const AVPixelFormat format); - ~VaapiAccel(); - - bool checkAvailability() override; - bool init() override; - int allocateBuffer(AVFrame* frame, int flags) override; - void extractData(VideoFrame& input, VideoFrame& output) override; - - private: - using AVBufferRefPtr = std::unique_ptr<AVBufferRef, std::function<void(AVBufferRef*)>>; - AVBufferRefPtr deviceBufferRef_; - AVBufferRefPtr framesBufferRef_; - - std::string deviceName_; -}; - -}} // namespace ring::video - -#endif // RING_VAAPI diff --git a/src/media/video/v4l2/vdpau.cpp b/src/media/video/v4l2/vdpau.cpp deleted file mode 100644 index dbb5c21fe25b0d1aee7e9ba7dc375b06d4cc5cf6..0000000000000000000000000000000000000000 --- a/src/media/video/v4l2/vdpau.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 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" - -#ifdef RING_VDPAU - -#include "video/v4l2/vdpau.h" -#include "video/accel.h" - -#include "fileutils.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); }; - -VdpauAccel::VdpauAccel(const std::string name, const AVPixelFormat format) - : HardwareAccel(name, format) - , deviceBufferRef_(nullptr, avBufferRefDeleter) - , framesBufferRef_(nullptr, avBufferRefDeleter) -{ -} - -VdpauAccel::~VdpauAccel() -{ -} - -int -VdpauAccel::allocateBuffer(AVFrame* frame, int flags) -{ - (void) flags; - return av_hwframe_get_buffer(framesBufferRef_.get(), frame, 0); -} - -void -VdpauAccel::extractData(VideoFrame& input, VideoFrame& output) -{ - auto inFrame = input.pointer(); - auto outFrame = output.pointer(); - - if (av_hwframe_transfer_data(outFrame, inFrame, 0) < 0) { - throw std::runtime_error("Unable to extract data from VDPAU frame"); - } - - if (av_frame_copy_props(outFrame, inFrame) < 0 ) { - av_frame_unref(outFrame); - } -} - -bool -VdpauAccel::checkAvailability() -{ - AVBufferRef* hardwareDeviceCtx; - if (av_hwdevice_ctx_create(&hardwareDeviceCtx, AV_HWDEVICE_TYPE_VDPAU, nullptr, nullptr, 0) == 0) { - deviceBufferRef_.reset(hardwareDeviceCtx); - return true; - } - - av_buffer_unref(&hardwareDeviceCtx); - return false; -} - -bool -VdpauAccel::init() -{ - auto device = reinterpret_cast<AVHWDeviceContext*>(deviceBufferRef_->data); - auto hardwareContext = static_cast<AVVDPAUDeviceContext*>(device->hwctx); - - framesBufferRef_.reset(av_hwframe_ctx_alloc(deviceBufferRef_.get())); - auto frames = reinterpret_cast<AVHWFramesContext*>(framesBufferRef_->data); - frames->format = AV_PIX_FMT_VDPAU; - frames->sw_format = AV_PIX_FMT_YUV420P; - frames->width = width_; - frames->height = height_; - - if (av_hwframe_ctx_init(framesBufferRef_.get()) < 0) { - RING_ERR("Failed to initialize VDPAU frame context"); - return false; - } - - if (av_vdpau_bind_context(codecCtx_, hardwareContext->device, hardwareContext->get_proc_address, 0)) { - RING_ERR("Could not bind VDPAU context"); - return false; - } - - RING_DBG("VDPAU decoder initialized"); - - return true; -} - -}} // namespace ring::video - -#endif // RING_VDPAU diff --git a/src/media/video/v4l2/vdpau.h b/src/media/video/v4l2/vdpau.h deleted file mode 100644 index d3ba0a0bf31b342aa67daad2a57e78280774eb42..0000000000000000000000000000000000000000 --- a/src/media/video/v4l2/vdpau.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 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" - -#ifdef RING_VDPAU - -extern "C" { -#include <stdint.h> - -#include <libavcodec/vdpau.h> -#include <libavutil/buffer.h> -#include <libavutil/frame.h> -#include <libavutil/hwcontext.h> -#include <libavutil/hwcontext_vdpau.h> -} - -#include "video/accel.h" - -#include <memory> -#include <functional> - -namespace ring { namespace video { - -class VdpauAccel : public HardwareAccel { - public: - VdpauAccel(const std::string name, const AVPixelFormat format); - ~VdpauAccel(); - - bool checkAvailability() override; - bool init() override; - int allocateBuffer(AVFrame* frame, int flags) override; - void extractData(VideoFrame& input, VideoFrame& output) override; - - private: - using AVBufferRefPtr = std::unique_ptr<AVBufferRef, std::function<void(AVBufferRef*)>>; - AVBufferRefPtr deviceBufferRef_; - AVBufferRefPtr framesBufferRef_; -}; - -}} // namespace ring::video - -#endif // RING_VDPAU