Skip to content
Snippets Groups Projects
Commit ea8c5921 authored by Philippe Gorley's avatar Philippe Gorley Committed by Anthony Léonard
Browse files

accel: refactor to fit new hwaccel api


libavutil now covers the simple cases for creating and managing devices.
As Ring does not need to fine-tune these processes, most of the accel
code can be and has been removed.

Most hardware decoders output NV12, so skip extra conversions by
outputting NV12. Said pixel format is supported by everything that isn't
excessively old.

Change-Id: I10c440026fc3b289dbba7ecbca47e55c57147207
Reviewed-by: default avatarAnthony Léonard <anthony.leonard@savoirfairelinux.com>
parent 64322318
No related branches found
No related tags found
No related merge requests found
...@@ -48,6 +48,8 @@ namespace ring { ...@@ -48,6 +48,8 @@ namespace ring {
const unsigned jitterBufferMaxSize_ {1500}; const unsigned jitterBufferMaxSize_ {1500};
// maximum time a packet can be queued // maximum time a packet can be queued
const constexpr auto jitterBufferMaxDelay_ = std::chrono::milliseconds(50); 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() : MediaDecoder::MediaDecoder() :
inputCtx_(avformat_alloc_context()), inputCtx_(avformat_alloc_context()),
...@@ -57,6 +59,8 @@ MediaDecoder::MediaDecoder() : ...@@ -57,6 +59,8 @@ MediaDecoder::MediaDecoder() :
MediaDecoder::~MediaDecoder() MediaDecoder::~MediaDecoder()
{ {
if (decoderCtx_->hw_device_ctx)
av_buffer_unref(&decoderCtx_->hw_device_ctx);
if (decoderCtx_) if (decoderCtx_)
avcodec_close(decoderCtx_); avcodec_close(decoderCtx_);
if (inputCtx_) if (inputCtx_)
...@@ -106,7 +110,7 @@ int MediaDecoder::openInput(const DeviceParams& params) ...@@ -106,7 +110,7 @@ int MediaDecoder::openInput(const DeviceParams& params)
#ifdef RING_ACCEL #ifdef RING_ACCEL
// if there was a fallback to software decoding, do not enable 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(); enableAccel_ &= Manager::instance().getDecodingAccelerated();
#endif #endif
...@@ -301,21 +305,21 @@ int MediaDecoder::setupFromVideoData() ...@@ -301,21 +305,21 @@ int MediaDecoder::setupFromVideoData()
decoderCtx_->thread_count = std::max(1u, std::min(8u, std::thread::hardware_concurrency()/2)); 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 #ifdef RING_ACCEL
if (enableAccel_) { if (enableAccel_) {
accel_ = video::makeHardwareAccel(decoderCtx_); accel_ = video::setupHardwareDecoding(decoderCtx_);
decoderCtx_->opaque = accel_.get(); decoderCtx_->opaque = &accel_;
} else if (Manager::instance().getDecodingAccelerated()) { } else if (Manager::instance().getDecodingAccelerated()) {
RING_WARN("Hardware accelerated decoding disabled because of previous failure"); RING_WARN("Hardware accelerated decoding disabled because of previous failure");
} else { } else {
RING_WARN("Hardware accelerated decoding disabled by user preference"); RING_WARN("Hardware accelerated decoding disabled by user preference");
} }
#endif // RING_ACCEL #endif
if (emulateRate_) {
RING_DBG("Using framerate emulation");
startTime_ = av_gettime();
}
ret = avcodec_open2(decoderCtx_, inputDecoder_, NULL); ret = avcodec_open2(decoderCtx_, inputDecoder_, NULL);
if (ret) { if (ret) {
...@@ -353,18 +357,10 @@ MediaDecoder::decode(VideoFrame& result) ...@@ -353,18 +357,10 @@ MediaDecoder::decode(VideoFrame& result)
int frameFinished = 0; int frameFinished = 0;
ret = avcodec_send_packet(decoderCtx_, &inpacket); ret = avcodec_send_packet(decoderCtx_, &inpacket);
if (ret < 0) { if (ret < 0) {
#ifdef RING_ACCEL
if (accel_ && accel_->hasFailed())
return Status::RestartRequired;
#endif
return ret == AVERROR_EOF ? Status::Success : Status::DecodeError; return ret == AVERROR_EOF ? Status::Success : Status::DecodeError;
} }
ret = avcodec_receive_frame(decoderCtx_, frame); ret = avcodec_receive_frame(decoderCtx_, frame);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) { if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
#ifdef RING_ACCEL
if (accel_ && accel_->hasFailed())
return Status::RestartRequired;
#endif
return Status::DecodeError; return Status::DecodeError;
} }
if (ret >= 0) if (ret >= 0)
...@@ -375,13 +371,18 @@ MediaDecoder::decode(VideoFrame& result) ...@@ -375,13 +371,18 @@ MediaDecoder::decode(VideoFrame& result)
if (frameFinished) { if (frameFinished) {
frame->format = (AVPixelFormat) correctPixFmt(frame->format); frame->format = (AVPixelFormat) correctPixFmt(frame->format);
#ifdef RING_ACCEL #ifdef RING_ACCEL
if (accel_) { if (!accel_.name.empty()) {
if (!accel_->hasFailed()) ret = video::transferFrameData(accel_, decoderCtx_, result);
accel_->extractData(result); if (ret < 0) {
else ++accelFailures_;
if (accelFailures_ >= MAX_ACCEL_FAILURES) {
RING_ERR("Hardware decoding failure");
accelFailures_ = 0; // reset error count for next time
return Status::RestartRequired; return Status::RestartRequired;
} }
#endif // RING_ACCEL }
}
#endif
if (emulateRate_ and frame->pts != AV_NOPTS_VALUE) { if (emulateRate_ and frame->pts != AV_NOPTS_VALUE) {
auto frame_time = getTimeBase()*(frame->pts - avStream_->start_time); auto frame_time = getTimeBase()*(frame->pts - avStream_->start_time);
auto target = startTime_ + static_cast<std::int64_t>(frame_time.real() * 1e6); auto target = startTime_ + static_cast<std::int64_t>(frame_time.real() * 1e6);
...@@ -457,7 +458,9 @@ MediaDecoder::enableAccel(bool enableAccel) ...@@ -457,7 +458,9 @@ MediaDecoder::enableAccel(bool enableAccel)
{ {
enableAccel_ = enableAccel; enableAccel_ = enableAccel;
if (!enableAccel) { if (!enableAccel) {
accel_.reset(); accel_ = {};
if (decoderCtx_->hw_device_ctx)
av_buffer_unref(&decoderCtx_->hw_device_ctx);
if (decoderCtx_) if (decoderCtx_)
decoderCtx_->opaque = nullptr; decoderCtx_->opaque = nullptr;
} }
...@@ -487,9 +490,9 @@ MediaDecoder::flush(VideoFrame& result) ...@@ -487,9 +490,9 @@ MediaDecoder::flush(VideoFrame& result)
#ifdef RING_ACCEL #ifdef RING_ACCEL
// flush is called when closing the stream // flush is called when closing the stream
// so don't restart the media decoder // so don't restart the media decoder
if (accel_ && !accel_->hasFailed()) if (!accel_.name.empty() && accelFailures_ < MAX_ACCEL_FAILURES)
accel_->extractData(result); video::transferFrameData(accel_, decoderCtx_, result);
#endif // RING_ACCEL #endif
return Status::FrameFinished; return Status::FrameFinished;
} }
......
...@@ -26,6 +26,10 @@ ...@@ -26,6 +26,10 @@
#include "video/video_scaler.h" #include "video/video_scaler.h"
#endif // RING_VIDEO #endif // RING_VIDEO
#ifdef RING_ACCEL
#include "video/accel.h"
#endif
#include "audio/audiobuffer.h" #include "audio/audiobuffer.h"
#include "rational.h" #include "rational.h"
...@@ -44,12 +48,6 @@ class AVCodec; ...@@ -44,12 +48,6 @@ class AVCodec;
namespace ring { namespace ring {
#ifdef RING_ACCEL
namespace video {
class HardwareAccel;
}
#endif
struct AudioFrame; struct AudioFrame;
class AudioFormat; class AudioFormat;
class RingBuffer; class RingBuffer;
...@@ -120,7 +118,8 @@ class MediaDecoder { ...@@ -120,7 +118,8 @@ class MediaDecoder {
#ifdef RING_ACCEL #ifdef RING_ACCEL
bool enableAccel_ = true; bool enableAccel_ = true;
std::unique_ptr<video::HardwareAccel> accel_; video::HardwareAccel accel_;
unsigned short accelFailures_ = 0;
#endif #endif
protected: protected:
......
...@@ -18,232 +18,129 @@ ...@@ -18,232 +18,129 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "libav_deps.h" // MUST BE INCLUDED FIRST extern "C" {
#include "media_buffer.h" #include <libavutil/hwcontext.h>
}
#include "accel.h"
#ifdef RING_VAAPI
#include "v4l2/vaapi.h"
#endif
#ifdef RING_VDPAU
#include "v4l2/vdpau.h"
#endif
#ifdef RING_VIDEOTOOLBOX #include <algorithm>
#include "osxvideo/videotoolbox.h"
#endif
#include "media_buffer.h"
#include "string_utils.h" #include "string_utils.h"
#include "fileutils.h"
#include "logger.h" #include "logger.h"
#include "accel.h"
#include <sstream> #include "config.h"
#include <algorithm>
namespace ring { namespace video { namespace ring { namespace video {
static constexpr const unsigned MAX_ACCEL_FAILURES { 5 };
static AVPixelFormat static AVPixelFormat
getFormatCb(AVCodecContext* codecCtx, const AVPixelFormat* formats) getFormatCb(AVCodecContext* codecCtx, const AVPixelFormat* formats)
{ {
auto accel = static_cast<HardwareAccel*>(codecCtx->opaque); 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; AVPixelFormat fallback = AV_PIX_FMT_NONE;
for (int i = 0; formats[i] != AV_PIX_FMT_NONE; ++i) { for (int i = 0; formats[i] != AV_PIX_FMT_NONE; ++i) {
fallback = formats[i]; fallback = formats[i];
if (formats[i] == accel->format()) { if (formats[i] == accel->format) {
accel->setWidth(codecCtx->coded_width); return formats[i];
accel->setHeight(codecCtx->coded_height);
accel->setProfile(codecCtx->profile);
accel->setCodecCtx(codecCtx);
if (accel->init())
return accel->format();
} }
} }
accel->fail(true); RING_WARN("'%s' acceleration not supported, falling back to software decoding", accel->name.c_str());
RING_WARN("Falling back to software decoding"); accel->name = {}; // don't use accel
codecCtx->get_format = avcodec_default_get_format;
codecCtx->get_buffer2 = avcodec_default_get_buffer2;
return fallback; return fallback;
} }
static int int
allocateBufferCb(AVCodecContext* codecCtx, AVFrame* frame, int flags) transferFrameData(HardwareAccel accel, AVCodecContext* codecCtx, VideoFrame& frame)
{ {
if (auto accel = static_cast<HardwareAccel*>(codecCtx->opaque)) { if (accel.name.empty())
if (!accel->hasFailed() && accel->allocateBuffer(frame, flags) == 0) { return -1;
accel->succeedAllocation();
return 0;
}
accel->failAllocation(); 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) // most hardware accelerations output NV12, so skip extra conversions
: name_(name) output->format = AV_PIX_FMT_NV12;
, format_(format) int ret = av_hwframe_transfer_data(output, input, 0);
{}
void // move output into input so the caller receives extracted image data
HardwareAccel::failAllocation() // but we have to delete input's data first
{ av_frame_unref(input);
++allocationFails_; av_frame_move_ref(input, output);
fail(false);
}
void return ret;
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;
}
}
} }
bool static int
HardwareAccel::extractData(VideoFrame& input) openDevice(HardwareAccel accel, AVBufferRef** hardwareDeviceCtx)
{ {
try { int ret;
auto inFrame = input.pointer(); auto hwType = av_hwdevice_find_type_by_name(accel.name.c_str());
#ifdef HAVE_VAAPI_ACCEL_DRM
if (inFrame->format != format_) { // default DRM device may not work on multi GPU computers, so check all possible values
std::stringstream buf; if (accel.name == "vaapi") {
buf << "Frame format mismatch: expected " << av_get_pix_fmt_name(format_); const std::string path = "/dev/dri/";
buf << ", got " << av_get_pix_fmt_name((AVPixelFormat)inFrame->format); auto files = ring::fileutils::readDirectory(path);
throw std::runtime_error(buf.str()); // 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;
} }
succeedExtraction();
return true;
} }
#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());
template <class T> return ret;
static std::unique_ptr<HardwareAccel>
makeHardwareAccel(const std::string name, const AVPixelFormat format) {
return std::unique_ptr<HardwareAccel>(new T(name, format));
} }
std::unique_ptr<HardwareAccel> const HardwareAccel
makeHardwareAccel(AVCodecContext* codecCtx) setupHardwareDecoding(AVCodecContext* codecCtx)
{ {
enum class AccelID { /**
NoAccel, * This array represents FFmpeg's hwaccels, along with their pixel format
Vdpau, * and their potentially supported codecs. Each item contains:
Vaapi, * - Name (must match the name used in FFmpeg)
VideoToolbox, * - 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
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.
*/ */
const AccelInfo accels[] = { const HardwareAccel accels[] = {
#ifdef RING_VAAPI { "vaapi", AV_PIX_FMT_VAAPI, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4, AV_CODEC_ID_H263 } },
{ AccelID::Vaapi, "vaapi", AV_PIX_FMT_VAAPI, makeHardwareAccel<VaapiAccel> }, { "vdpau", AV_PIX_FMT_VDPAU, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4, AV_CODEC_ID_H263 } },
#endif { "videotoolbox", AV_PIX_FMT_VIDEOTOOLBOX, { AV_CODEC_ID_H264, AV_CODEC_ID_MPEG4, AV_CODEC_ID_H263 } },
#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 },
}; };
std::vector<AccelID> possibleAccels = {}; AVBufferRef* hardwareDeviceCtx = nullptr;
switch (codecCtx->codec_id) { for (auto accel : accels) {
case AV_CODEC_ID_H264: if (std::find(accel.supportedCodecs.begin(), accel.supportedCodecs.end(),
possibleAccels.push_back(AccelID::Vdpau); static_cast<AVCodecID>(codecCtx->codec_id)) != accel.supportedCodecs.end()) {
possibleAccels.push_back(AccelID::Vaapi); if (openDevice(accel, &hardwareDeviceCtx) >= 0) {
possibleAccels.push_back(AccelID::VideoToolbox); codecCtx->hw_device_ctx = av_buffer_ref(hardwareDeviceCtx);
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_format = getFormatCb;
codecCtx->get_buffer2 = allocateBufferCb;
codecCtx->thread_safe_callbacks = 1; codecCtx->thread_safe_callbacks = 1;
RING_DBG("Attempting to use '%s' hardware acceleration", accel->name().c_str());
return accel; return accel;
} }
} }
} }
}
RING_WARN("Not using hardware acceleration"); RING_WARN("Not using hardware accelerated decoding");
return nullptr; return {};
} }
}} // namespace ring::video }} // namespace ring::video
...@@ -21,57 +21,19 @@ ...@@ -21,57 +21,19 @@
#pragma once #pragma once
#include "libav_deps.h" #include "libav_deps.h"
#include "media_buffer.h"
#include "config.h"
#include <string> #include <string>
#include <memory> #include <vector>
namespace ring { namespace video { namespace ring { namespace video {
class HardwareAccel { struct HardwareAccel {
public: std::string name;
HardwareAccel(const std::string& name, const AVPixelFormat format); AVPixelFormat format;
virtual ~HardwareAccel() {}; std::vector<AVCodecID> supportedCodecs;
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;
}; };
// HardwareAccel factory const HardwareAccel setupHardwareDecoding(AVCodecContext* codecCtx);
// Checks if codec acceleration is possible int transferFrameData(HardwareAccel accel, AVCodecContext* codecCtx, VideoFrame& frame);
std::unique_ptr<HardwareAccel> makeHardwareAccel(AVCodecContext* codecCtx);
}} // namespace ring::video }} // namespace ring::video
...@@ -6,8 +6,4 @@ libosxvideo_la_SOURCES = \ ...@@ -6,8 +6,4 @@ libosxvideo_la_SOURCES = \
video_device_impl.mm \ video_device_impl.mm \
video_device_monitor_impl.mm video_device_monitor_impl.mm
if RING_ACCEL
libosxvideo_la_SOURCES += videotoolbox.h videotoolbox.mm
endif
AM_OBJCXXFLAGS = -std=c++11 AM_OBJCXXFLAGS = -std=c++11
/*
* 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
/*
* 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
...@@ -6,15 +6,7 @@ libv4l2_la_SOURCES = \ ...@@ -6,15 +6,7 @@ libv4l2_la_SOURCES = \
video_device_impl.cpp \ video_device_impl.cpp \
video_device_monitor_impl.cpp video_device_monitor_impl.cpp
if RING_VDPAU AM_CXXFLAGS = @LIBAVCODEC_CFLAGS@ @LIBAVFORMAT_CFLAGS@ @LIBAVDEVICE_CFLAGS@ @LIBSWSCALE_CFLAGS@ @LIBAVUTIL_CFLAGS@
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 += @UDEV_CFLAGS@ @VDPAU_CFLAGS@ @LIBVA_CFLAGS@ @LIBVA_DRM_CFLAGS@ @LIBVA_X11_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@ libv4l2_la_LIBADD = @LIBAVCODEC_LIBS@ @LIBAVFORMAT_LIBS@ @LIBAVDEVICE_LIBS@ @LIBSWSCALE_LIBS@ @LIBAVUTIL_LIBS@
......
/*
* 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
/*
* 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
/*
* 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
/*
* 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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment