13 Commits

Author SHA1 Message Date
Michael Fabian 'Xaymar' Dirks 545dcd6d50 encoder: Add support for true hardware encoding
This is the last step towards truly efficient encoding on AMD, Nvidia and Intel GPUs. With this we have no software overhead and can directly encode the content that OBS gives us, without going through any intermediate CPU layer. This is effectively what @jp9000 did for the OBS-integrated nvenc, but thanks to FFmpeg it works on all encoders that support D3D11VA acceleration.

With the change, the encoding should now work flawlessly even in very constrained situations (unless OBS itself is being starved of resources). Especially people streaming and recording Ubisoft games will likely see a drastic increase in encoding capability, and thanks to the new options will also be able to get a much higher quality stream and recording with the same hardware.
2019-09-29 19:29:00 +02:00
Michael Fabian 'Xaymar' Dirks 0461b20e1b ui/handler: Allow Codec handlers to select the proper device 2019-09-29 19:16:26 +02:00
Michael Fabian 'Xaymar' Dirks b3a6dbb1b4 hwapi: Add API handlers to deal with some heavy lifting code
Makes our life easier when actually dealing with hardware encoding.
2019-09-29 19:15:45 +02:00
Michael Fabian 'Xaymar' Dirks fe71944199 ui/prores_aw_handler: Workaround for FFmpeg bug with ProRes in Matroska
In current FFmpeg, whenever Matroska with ProRes is demuxed it creates an atom that is just 8 bytes short of the true size necessary.

We can work around this by padding the actual packet by 8 0x00 bytes, which should result in older FFmpeg versions working fine.

An FFmpeg patch is available: http://ffmpeg.org/pipermail/ffmpeg-devel/2019-September/250724.html
2019-09-29 07:27:22 +02:00
Michael Fabian 'Xaymar' Dirks cbd39a8c2a encoder: Cache the UI handler and allow packet processing 2019-09-29 06:58:01 +02:00
Michael Fabian 'Xaymar' Dirks 62eae3827b ui/handler: Add function for packet processing 2019-09-29 06:55:42 +02:00
Michael Fabian 'Xaymar' Dirks 38e7639862 ffmpeg/tools: Actually use the score 2019-09-28 02:10:37 +02:00
Michael Fabian 'Xaymar' Dirks 6bc1cb9c88 ui/nvenc*: Print all settings to the log file 2019-09-28 01:54:28 +02:00
Michael Fabian 'Xaymar' Dirks c63900d575 encoder: Also log custom overrides 2019-09-28 01:48:56 +02:00
Michael Fabian 'Xaymar' Dirks 9efda8af8d ui/prores_aw_handler: Print profile to log 2019-09-27 16:29:01 +02:00
Michael Fabian 'Xaymar' Dirks 0c9764a15c encoder: Use codec name instead of encoder info 2019-09-27 16:28:44 +02:00
Michael Fabian 'Xaymar' Dirks 5c5a235502 ui/handler: Always have a no-op function 2019-09-27 16:16:39 +02:00
Michael Fabian 'Xaymar' Dirks 2ebf90ffd7 encoder, ffmpeg/tools: Fix and improve initialization behavior
Correctly sets all color settings for the context and frames, which should result in better playback in players that support these. Unfortunately it does not fix the bug that VLC and MPC-HC incorrectly assume that the ProRes encoded content is in Partial range, however most editing software does correctly detect it.
2019-09-27 16:16:14 +02:00
19 changed files with 933 additions and 228 deletions
+9 -1
View File
@@ -280,6 +280,8 @@ set(PROJECT_PRIVATE
"${PROJECT_SOURCE_DIR}/source/ffmpeg/swscale.cpp"
"${PROJECT_SOURCE_DIR}/source/ffmpeg/tools.hpp"
"${PROJECT_SOURCE_DIR}/source/ffmpeg/tools.cpp"
"${PROJECT_SOURCE_DIR}/source/hwapi/base.hpp"
"${PROJECT_SOURCE_DIR}/source/hwapi/base.cpp"
"${PROJECT_SOURCE_DIR}/source/ui/handler.hpp"
"${PROJECT_SOURCE_DIR}/source/ui/handler.cpp"
"${PROJECT_SOURCE_DIR}/source/ui/debug_handler.hpp"
@@ -293,6 +295,12 @@ set(PROJECT_PRIVATE
"${PROJECT_SOURCE_DIR}/source/ui/nvenc_hevc_handler.hpp"
"${PROJECT_SOURCE_DIR}/source/ui/nvenc_hevc_handler.cpp"
)
if(WIN32)
list(APPEND PROJECT_PRIVATE
"${PROJECT_SOURCE_DIR}/source/hwapi/d3d11.hpp"
"${PROJECT_SOURCE_DIR}/source/hwapi/d3d11.cpp"
)
endif()
# Source Grouping
source_group(TREE "${PROJECT_SOURCE_DIR}" PREFIX "Data Files" FILES ${PROJECT_DATA})
@@ -357,7 +365,7 @@ endif()
# Link Libraries
target_link_libraries(${PROJECT_NAME}
"${PROJECT_LIBRARIES}"
${PROJECT_LIBRARIES}
${FFMPEG_LIBRARIES}
)
+274 -196
View File
@@ -52,6 +52,11 @@ extern "C" {
// - encode_texture/encode
// I don't understand what get_video_info is actually for in this order, as this postpones initialization to encode...
#ifdef WIN32
#define HARDWARE_ENCODING
#include "hwapi/d3d11.hpp"
#endif
// FFmpeg
#define ST_FFMPEG "FFmpeg"
#define ST_FFMPEG_CUSTOMSETTINGS "FFmpeg.CustomSettings"
@@ -79,7 +84,7 @@ static void* _create_texture(obs_data_t* settings, obs_encoder_t* encoder) noexc
PLOG_INFO("%s %llX %llX", __FUNCTION_NAME__, settings, encoder);
#endif
return reinterpret_cast<void*>(new obsffmpeg::encoder(settings, encoder, true));
} catch (const obsffmpeg::unsupported_gpu_exception& ex) {
} catch (const obsffmpeg::unsupported_gpu_exception&) {
obsffmpeg::encoder_factory* fac =
reinterpret_cast<obsffmpeg::encoder_factory*>(obs_encoder_get_type_data(encoder));
PLOG_WARNING("<%s> GPU not supported for hardware encoding, falling back to software.",
@@ -282,6 +287,9 @@ static bool _encode_audio(void* ptr, struct encoder_frame* frame, struct encoder
obsffmpeg::encoder_factory::encoder_factory(const AVCodec* codec) : avcodec_ptr(codec), info(), info_fallback()
{
// Find Codec UI handler.
_handler = obsffmpeg::find_codec_handler(avcodec_ptr->name);
// Unique Id is FFmpeg name.
info.uid = avcodec_ptr->name;
@@ -298,8 +306,8 @@ obsffmpeg::encoder_factory::encoder_factory(const AVCodec* codec) : avcodec_ptr(
info.readable_name = sstr.str();
// Allow UI Handler to replace visible name.
obsffmpeg::find_codec_handler(avcodec_ptr->name)
->override_visible_name(avcodec_ptr, info.readable_name);
if (_handler)
_handler->override_visible_name(avcodec_ptr, info.readable_name);
}
// Assign Ids.
@@ -325,6 +333,7 @@ obsffmpeg::encoder_factory::encoder_factory(const AVCodec* codec) : avcodec_ptr(
#endif
// Hardware encoder?
#ifdef HARDWARE_ENCODING
if (ffmpeg::tools::can_hardware_encode(avcodec_ptr)) {
info_fallback.uid = info.uid + "_sw";
info_fallback.codec = info.codec;
@@ -338,6 +347,7 @@ obsffmpeg::encoder_factory::encoder_factory(const AVCodec* codec) : avcodec_ptr(
info.oei.caps |= OBS_ENCODER_CAP_PASS_TEXTURE;
}
#endif
}
obsffmpeg::encoder_factory::~encoder_factory() {}
@@ -405,12 +415,8 @@ void obsffmpeg::encoder_factory::register_encoder()
void obsffmpeg::encoder_factory::get_defaults(obs_data_t* settings)
{
{ // Handler
auto ptr = obsffmpeg::find_codec_handler(avcodec_ptr->name);
if (ptr) {
ptr->get_defaults(settings, avcodec_ptr, nullptr);
}
}
if (_handler)
_handler->get_defaults(settings, avcodec_ptr, nullptr);
if ((avcodec_ptr->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
obs_data_set_default_int(settings, S_KEYFRAMES_INTERVALTYPE, 0);
@@ -442,12 +448,8 @@ static bool modified_keyframes(obs_properties_t* props, obs_property_t*, obs_dat
void obsffmpeg::encoder_factory::get_properties(obs_properties_t* props)
{
{ // Handler
auto ptr = obsffmpeg::find_codec_handler(avcodec_ptr->name);
if (ptr) {
ptr->get_properties(props, avcodec_ptr, nullptr);
}
}
if (_handler)
_handler->get_properties(props, avcodec_ptr, nullptr);
if ((avcodec_ptr->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
// Key-Frame Options
@@ -546,56 +548,8 @@ const obsffmpeg::encoder_info& obsffmpeg::encoder_factory::get_fallback()
return info_fallback;
}
obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool is_texture_encode)
: _self(encoder), _lag_in_frames(0), _count_send_frames(0), _have_first_frame(false)
void obsffmpeg::encoder::initialize_sw(obs_data_t* settings)
{
if (is_texture_encode) {
throw obsffmpeg::unsupported_gpu_exception("not implemented yet");
}
_factory = reinterpret_cast<encoder_factory*>(obs_encoder_get_type_data(_self));
// Verify that the codec actually still exists.
_codec = avcodec_find_encoder_by_name(_factory->get_avcodec()->name);
if (!_codec) {
PLOG_ERROR("Failed to find encoder for codec '%s'.", _factory->get_avcodec()->name);
throw std::runtime_error("failed to find codec");
}
// Initialize context.
_context = avcodec_alloc_context3(_codec);
if (!_context) {
PLOG_ERROR("Failed to create context for encoder '%s'.", _codec->name);
throw std::runtime_error("failed to create context");
}
// Settings
/// Rate Control
_context->strict_std_compliance = static_cast<int>(obs_data_get_int(settings, ST_FFMPEG_STANDARDCOMPLIANCE));
_context->debug = 0;
/// Threading
if (_codec->capabilities
& (AV_CODEC_CAP_AUTO_THREADS | AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS)) {
if (_codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
_context->thread_type |= FF_THREAD_FRAME;
}
if (_codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
_context->thread_type |= FF_THREAD_SLICE;
}
int64_t threads = obs_data_get_int(settings, ST_FFMPEG_THREADS);
if (threads > 0) {
_context->thread_count = static_cast<int>(threads);
_lag_in_frames = _context->thread_count;
} else {
_context->thread_count = std::thread::hardware_concurrency();
_lag_in_frames = _context->thread_count;
}
}
// Create 8MB of precached Packet data for use later on.
av_init_packet(&_current_packet);
av_new_packet(&_current_packet, 8 * 1024 * 1024); // 8 MB precached Packet size.
if (_codec->type == AVMEDIA_TYPE_VIDEO) {
// Initialize Video Encoding
auto voi = video_output_get_info(obs_encoder_video(_self));
@@ -609,12 +563,8 @@ obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool i
std::vector<AVPixelFormat> fmts = ffmpeg::tools::get_software_formats(_codec->pix_fmts);
_pixfmt_target = ffmpeg::tools::get_best_compatible_format(fmts.data(), _pixfmt_source);
{ // Allow Handler to override the automatic color format for sanity reasons.
auto ptr = obsffmpeg::find_codec_handler(_codec->name);
if (ptr) {
ptr->override_colorformat(_pixfmt_target, settings, _codec, _context);
}
}
if (_handler) // Allow Handler to override the automatic color format for sanity reasons.
_handler->override_colorformat(_pixfmt_target, settings, _codec, _context);
} else {
// Use user override, guaranteed to be supported.
bool is_format_supported = false;
@@ -634,23 +584,21 @@ obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool i
_context->width = voi->width;
_context->height = voi->height;
_context->colorspace = ffmpeg::tools::obs_videocolorspace_to_avcolorspace(voi->colorspace);
_context->color_range = ffmpeg::tools::obs_videorangetype_to_avcolorrange(voi->range);
ffmpeg::tools::setup_obs_color(voi->colorspace, voi->range, _context);
_context->pix_fmt = _pixfmt_target;
_context->field_order = AV_FIELD_PROGRESSIVE;
_context->time_base.num = voi->fps_den;
_context->time_base.den = voi->fps_num;
_context->ticks_per_frame = 1;
_context->sample_aspect_ratio.num = _context->sample_aspect_ratio.den = 1;
_context->framerate.num = _context->time_base.den = voi->fps_num;
_context->framerate.den = _context->time_base.num = voi->fps_den;
_swscale.set_source_size(_context->width, _context->height);
_swscale.set_source_color(_context->color_range, _context->colorspace);
_swscale.set_source_full_range(voi->range == VIDEO_RANGE_FULL);
_swscale.set_source_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace);
_swscale.set_source_format(_pixfmt_source);
_swscale.set_target_size(_context->width, _context->height);
_swscale.set_target_color(_context->color_range, _context->colorspace);
_swscale.set_target_full_range(voi->range == VIDEO_RANGE_FULL);
_swscale.set_target_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace);
_swscale.set_target_format(_pixfmt_target);
// Create Scaler
@@ -664,24 +612,105 @@ obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool i
<< (_swscale.is_source_full_range() ? "full" : "partial") << " range.";
throw std::runtime_error(sstr.str());
}
// Create Frame queue
_frame_queue.set_pixel_format(_context->pix_fmt);
_frame_queue.set_resolution(_context->width, _context->height);
_frame_queue.precache(2);
}
}
{ // Log Encoder info
const char* id = obs_encoder_get_id(_self);
PLOG_INFO("[%s] Initializing...", id);
PLOG_INFO("[%s] Video Input: %ldx%ld %s %s %s", id, _swscale.get_source_width(),
_swscale.get_source_height(),
ffmpeg::tools::get_pixel_format_name(_swscale.get_source_format()),
void obsffmpeg::encoder::initialize_hw(obs_data_t* settings)
{
// Initialize Video Encoding
auto voi = video_output_get_info(obs_encoder_video(_self));
_context->width = voi->width;
_context->height = voi->height;
_context->field_order = AV_FIELD_PROGRESSIVE;
_context->ticks_per_frame = 1;
_context->sample_aspect_ratio.num = _context->sample_aspect_ratio.den = 1;
_context->framerate.num = _context->time_base.den = voi->fps_num;
_context->framerate.den = _context->time_base.num = voi->fps_den;
ffmpeg::tools::setup_obs_color(voi->colorspace, voi->range, _context);
_context->sw_pix_fmt = ffmpeg::tools::obs_videoformat_to_avpixelformat(voi->format);
#ifdef WIN32
_context->pix_fmt = AV_PIX_FMT_D3D11;
#endif
_context->hw_device_ctx = _hwinst->create_device_context();
_context->hw_frames_ctx = av_hwframe_ctx_alloc(_context->hw_device_ctx);
if (!_context->hw_frames_ctx)
throw std::runtime_error("Failed to allocate AVHWFramesContext.");
AVHWFramesContext* ctx = reinterpret_cast<AVHWFramesContext*>(_context->hw_frames_ctx->data);
ctx->width = _context->width;
ctx->height = _context->height;
ctx->format = _context->pix_fmt;
ctx->sw_format = _context->sw_pix_fmt;
if (av_hwframe_ctx_init(_context->hw_frames_ctx) < 0)
throw std::runtime_error("Failed to initialize AVHWFramesContext.");
}
obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool is_texture_encode)
: _self(encoder), _lag_in_frames(0), _count_send_frames(0), _have_first_frame(false)
{
// Initial set up.
_factory = reinterpret_cast<encoder_factory*>(obs_encoder_get_type_data(_self));
_codec = _factory->get_avcodec();
_handler = obsffmpeg::find_codec_handler(_codec->name);
if (is_texture_encode) {
#ifdef WIN32
_hwapi = std::make_shared<obsffmpeg::hwapi::d3d11>();
#endif
obsffmpeg::hwapi::device dev;
if (_handler)
dev = _handler->find_hw_device(_hwapi, _codec, _context);
try {
_hwinst = _hwapi->create(dev);
} catch (...) {
throw obsffmpeg::unsupported_gpu_exception("Creating GPU context failed.");
}
}
// Initialize context.
_context = avcodec_alloc_context3(_codec);
if (!_context) {
PLOG_ERROR("Failed to create context for encoder '%s'.", _codec->name);
throw std::runtime_error("failed to create context");
}
// Create 8MB of precached Packet data for use later on.
av_init_packet(&_current_packet);
av_new_packet(&_current_packet, 8 * 1024 * 1024); // 8 MB precached Packet size.
if (!is_texture_encode) {
initialize_sw(settings);
} else {
try {
initialize_hw(settings);
} catch (...) {
throw obsffmpeg::unsupported_gpu_exception("Initializing hardware context failed.");
}
}
// Log Encoder info
PLOG_INFO("[%s] Initializing...", _codec->name);
PLOG_INFO("[%s] Video Input: %ldx%ld %s %s %s", _codec->name, _swscale.get_source_width(),
_swscale.get_source_height(), ffmpeg::tools::get_pixel_format_name(_swscale.get_source_format()),
ffmpeg::tools::get_color_space_name(_swscale.get_source_colorspace()),
_swscale.is_source_full_range() ? "Full" : "Partial");
PLOG_INFO("[%s] Video Output: %ldx%ld %s %s %s", id, _swscale.get_target_width(),
_swscale.get_target_height(),
ffmpeg::tools::get_pixel_format_name(_swscale.get_target_format()),
PLOG_INFO("[%s] Video Output: %ldx%ld %s %s %s", _codec->name, _swscale.get_target_width(),
_swscale.get_target_height(), ffmpeg::tools::get_pixel_format_name(_swscale.get_target_format()),
ffmpeg::tools::get_color_space_name(_swscale.get_target_colorspace()),
_swscale.is_target_full_range() ? "Full" : "Partial");
PLOG_INFO("[%s] Framerate: %ld/%ld (%f", id, _context->time_base.num, _context->time_base.den,
_context->time_base.num / _context->time_base.den);
}
PLOG_INFO("[%s] Framerate: %ld/%ld (%f FPS)", _codec->name, _context->time_base.den, _context->time_base.num,
static_cast<double_t>(_context->time_base.den) / static_cast<double_t>(_context->time_base.num));
PLOG_INFO("[%s] Custom Settings: %s", _codec->name, obs_data_get_string(settings, ST_FFMPEG_CUSTOMSETTINGS));
// Update settings
update(settings);
@@ -694,11 +723,6 @@ obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool i
<< "' failed with error: " << ffmpeg::tools::get_error_description(res) << " (code " << res << ")";
throw std::runtime_error(sstr.str());
}
// Create Frame queue
_frame_queue.set_pixel_format(_context->pix_fmt);
_frame_queue.set_resolution(_context->width, _context->height);
_frame_queue.precache(2);
}
obsffmpeg::encoder::~encoder()
@@ -727,12 +751,8 @@ obsffmpeg::encoder::~encoder()
void obsffmpeg::encoder::get_properties(obs_properties_t* props)
{
{ // Handler
auto ptr = obsffmpeg::find_codec_handler(_codec->name);
if (ptr) {
ptr->get_properties(props, _codec, _context);
}
}
if (_handler)
_handler->get_properties(props, _codec, _context);
obs_property_set_enabled(obs_properties_get(props, S_KEYFRAMES), false);
obs_property_set_enabled(obs_properties_get(props, S_KEYFRAMES_INTERVALTYPE), false);
@@ -746,13 +766,32 @@ void obsffmpeg::encoder::get_properties(obs_properties_t* props)
bool obsffmpeg::encoder::update(obs_data_t* settings)
{
{ // Handler
auto ptr = obsffmpeg::find_codec_handler(_codec->name);
if (ptr) {
ptr->update(settings, _codec, _context);
// Settings
/// Rate Control
_context->strict_std_compliance = static_cast<int>(obs_data_get_int(settings, ST_FFMPEG_STANDARDCOMPLIANCE));
_context->debug = 0;
/// Threading
if (_codec->capabilities
& (AV_CODEC_CAP_AUTO_THREADS | AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS)) {
if (_codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
_context->thread_type |= FF_THREAD_FRAME;
}
if (_codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
_context->thread_type |= FF_THREAD_SLICE;
}
int64_t threads = obs_data_get_int(settings, ST_FFMPEG_THREADS);
if (threads > 0) {
_context->thread_count = static_cast<int>(threads);
_lag_in_frames = _context->thread_count;
} else {
_context->thread_count = std::thread::hardware_concurrency();
_lag_in_frames = _context->thread_count;
}
}
if (_handler)
_handler->update(settings, _codec, _context);
if ((_codec->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
// Key-Frame Options
obs_video_info ovi;
@@ -778,14 +817,11 @@ bool obsffmpeg::encoder::update(obs_data_t* settings)
nullptr, "=", ";");
}
{ // Handler Logging
auto ptr = obsffmpeg::find_codec_handler(_codec->name);
if (ptr) {
ptr->log_options(settings, _codec, _context);
}
}
// Handler Logging
if (_handler)
_handler->log_options(settings, _codec, _context);
return false;
return true;
}
void obsffmpeg::encoder::get_audio_info(audio_convert_info*) {}
@@ -870,6 +906,8 @@ bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* pack
vframe->format = _context->pix_fmt;
vframe->color_range = _context->color_range;
vframe->colorspace = _context->colorspace;
vframe->color_primaries = _context->color_primaries;
vframe->color_trc = _context->color_trc;
vframe->pts = frame->pts;
if ((_swscale.is_source_full_range() == _swscale.is_target_full_range())
@@ -888,7 +926,127 @@ bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* pack
}
}
// Send and receive frames.
if (!encode_avframe(vframe, packet, received_packet))
return false;
if (vframe != nullptr) {
_frame_queue.push(vframe);
}
return true;
}
bool obsffmpeg::encoder::video_encode_texture(uint32_t handle, int64_t pts, uint64_t lock_key, uint64_t* next_lock_key,
encoder_packet* packet, bool* received_packet)
{
if (handle == GS_INVALID_HANDLE) {
PLOG_ERROR("Received invalid handle.");
*next_lock_key = lock_key;
return false;
}
std::shared_ptr<AVFrame> frame{av_frame_alloc(), [](void* ptr) {
av_frame_unref(reinterpret_cast<AVFrame*>(ptr));
av_frame_free(reinterpret_cast<AVFrame**>(&ptr));
}};
std::shared_ptr<AVFrame> vframe = _hwinst->avframe_from_obs(_context->hw_frames_ctx, handle, lock_key, next_lock_key);
vframe->color_range = _context->color_range;
vframe->colorspace = _context->colorspace;
vframe->color_primaries = _context->color_primaries;
vframe->color_trc = _context->color_trc;
vframe->pts = pts;
if (!encode_avframe(vframe, packet, received_packet))
return false;
*next_lock_key = lock_key;
return true;
}
int obsffmpeg::encoder::receive_packet(bool* received_packet, struct encoder_packet* packet)
{
av_packet_unref(&_current_packet);
int res = avcodec_receive_packet(_context, &_current_packet);
if (res == 0) {
if (!_have_first_frame) {
if (_codec->id == AV_CODEC_ID_H264) {
uint8_t* tmp_packet;
uint8_t* tmp_header;
uint8_t* tmp_sei;
size_t sz_packet, sz_header, sz_sei;
obs_extract_avc_headers(_current_packet.data, _current_packet.size, &tmp_packet,
&sz_packet, &tmp_header, &sz_header, &tmp_sei, &sz_sei);
if (sz_header) {
_extra_data.resize(sz_header);
std::memcpy(_extra_data.data(), tmp_header, sz_header);
}
if (sz_sei) {
_sei_data.resize(sz_sei);
std::memcpy(_sei_data.data(), tmp_sei, sz_sei);
}
// Not required, we only need the Extra Data and SEI Data anyway.
//std::memcpy(_current_packet.data, tmp_packet, sz_packet);
//_current_packet.size = static_cast<int>(sz_packet);
bfree(tmp_packet);
bfree(tmp_header);
bfree(tmp_sei);
} else if (_codec->id == AV_CODEC_ID_HEVC) {
obsffmpeg::codecs::hevc::extract_header_sei(_current_packet.data, _current_packet.size,
_extra_data, _sei_data);
} else if (_context->extradata != nullptr) {
_extra_data.resize(_context->extradata_size);
std::memcpy(_extra_data.data(), _context->extradata, _context->extradata_size);
}
_have_first_frame = true;
}
// Allow Handler Post-Processing
if (_handler)
_handler->process_avpacket(_current_packet, _codec, _context);
packet->type = OBS_ENCODER_VIDEO;
packet->pts = _current_packet.pts;
packet->dts = _current_packet.dts;
packet->data = _current_packet.data;
packet->size = _current_packet.size;
packet->keyframe = !!(_current_packet.flags & AV_PKT_FLAG_KEY);
packet->drop_priority = packet->keyframe ? 0 : 1;
*received_packet = true;
{
std::shared_ptr<AVFrame> uframe = _frame_queue_used.pop_only();
_frame_queue.push(uframe);
}
}
return res;
}
int obsffmpeg::encoder::send_frame(std::shared_ptr<AVFrame> const frame)
{
int res = avcodec_send_frame(_context, frame.get());
switch (res) {
case 0:
if (!_hwapi)
_frame_queue_used.push(frame);
_count_send_frames++;
case AVERROR(EAGAIN):
case AVERROR(EOF):
break;
}
return res;
}
bool obsffmpeg::encoder::encode_avframe(std::shared_ptr<AVFrame>& frame, encoder_packet* packet, bool* received_packet)
{
#ifdef _DEBUG
ScopeProfiler profile("loop");
@@ -909,18 +1067,17 @@ bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* pack
#ifdef _DEBUG
ScopeProfiler profile_inner("send");
#endif
int res = send_frame(vframe);
int res = send_frame(frame);
switch (res) {
case 0:
sent_frame = true;
vframe = nullptr;
frame = nullptr;
break;
case AVERROR(EAGAIN):
// This means we should call receive_packet again, but what do we do with that data?
// Why can't we queue on both? Do I really have to implement threading for this stuff?
if (*received_packet == true) {
PLOG_WARNING(
"Skipped frame due to EAGAIN when a packet was already returned.");
PLOG_WARNING("Skipped frame due to EAGAIN when a packet was already returned.");
sent_frame = true;
}
eagain_is_stupid = true;
@@ -969,85 +1126,6 @@ bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* pack
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
}
if (vframe != nullptr) {
_frame_queue.push(vframe);
}
return true;
}
bool obsffmpeg::encoder::video_encode_texture(uint32_t, int64_t, uint64_t, uint64_t*, encoder_packet*, bool*)
{
return false;
}
int obsffmpeg::encoder::receive_packet(bool* received_packet, struct encoder_packet* packet)
{
int res = avcodec_receive_packet(_context, &_current_packet);
if (res == 0) {
if (!_have_first_frame) {
if (_codec->id == AV_CODEC_ID_H264) {
uint8_t* tmp_packet;
uint8_t* tmp_header;
uint8_t* tmp_sei;
size_t sz_packet, sz_header, sz_sei;
obs_extract_avc_headers(_current_packet.data, _current_packet.size, &tmp_packet,
&sz_packet, &tmp_header, &sz_header, &tmp_sei, &sz_sei);
if (sz_header) {
_extra_data.resize(sz_header);
std::memcpy(_extra_data.data(), tmp_header, sz_header);
}
if (sz_sei) {
_sei_data.resize(sz_sei);
std::memcpy(_sei_data.data(), tmp_sei, sz_sei);
}
std::memcpy(_current_packet.data, tmp_packet, sz_packet);
_current_packet.size = static_cast<int>(sz_packet);
bfree(tmp_packet);
bfree(tmp_header);
bfree(tmp_sei);
} else if (_codec->id == AV_CODEC_ID_HEVC) {
obsffmpeg::codecs::hevc::extract_header_sei(_current_packet.data, _current_packet.size,
_extra_data, _sei_data);
}
_have_first_frame = true;
}
packet->type = OBS_ENCODER_VIDEO;
packet->pts = _current_packet.pts;
packet->dts = _current_packet.dts;
packet->data = _current_packet.data;
packet->size = _current_packet.size;
packet->keyframe = !!(_current_packet.flags & AV_PKT_FLAG_KEY);
packet->drop_priority = packet->keyframe ? 0 : 1;
*received_packet = true;
{
std::shared_ptr<AVFrame> uframe = _frame_queue_used.pop_only();
_frame_queue.push(uframe);
}
}
return res;
}
int obsffmpeg::encoder::send_frame(std::shared_ptr<AVFrame> const frame)
{
int res = avcodec_send_frame(_context, frame.get());
switch (res) {
case 0:
_frame_queue_used.push(frame);
_count_send_frames++;
case AVERROR(EAGAIN):
case AVERROR(EOF):
break;
}
return res;
}
+16
View File
@@ -27,6 +27,8 @@
#include <vector>
#include "ffmpeg/avframe-queue.hpp"
#include "ffmpeg/swscale.hpp"
#include "hwapi/base.hpp"
#include "ui/handler.hpp"
extern "C" {
#include <obs-properties.h>
@@ -56,6 +58,8 @@ namespace obsffmpeg {
encoder_info info_fallback;
const AVCodec* avcodec_ptr;
std::shared_ptr<obsffmpeg::ui::handler> _handler;
public:
encoder_factory(const AVCodec* codec);
virtual ~encoder_factory();
@@ -79,6 +83,12 @@ namespace obsffmpeg {
const AVCodec* _codec;
AVCodecContext* _context;
AVHWFramesContext* _hwcontext;
std::shared_ptr<obsffmpeg::ui::handler> _handler;
std::shared_ptr<obsffmpeg::hwapi::base> _hwapi;
std::shared_ptr<obsffmpeg::hwapi::instance> _hwinst;
ffmpeg::avframe_queue _frame_queue;
ffmpeg::avframe_queue _frame_queue_used;
@@ -93,6 +103,9 @@ namespace obsffmpeg {
std::vector<uint8_t> _extra_data;
std::vector<uint8_t> _sei_data;
void initialize_sw(obs_data_t* settings);
void initialize_hw(obs_data_t* settings);
public:
encoder(obs_data_t* settings, obs_encoder_t* encoder, bool is_texture_encode = false);
virtual ~encoder();
@@ -125,5 +138,8 @@ namespace obsffmpeg {
int receive_packet(bool* received_packet, struct encoder_packet* packet);
int send_frame(std::shared_ptr<AVFrame> frame);
bool encode_avframe(std::shared_ptr<AVFrame>& frame, struct encoder_packet* packet,
bool* received_packet);
};
} // namespace obsffmpeg
+36 -3
View File
@@ -220,10 +220,10 @@ AVColorRange ffmpeg::tools::obs_videorangetype_to_avcolorrange(video_range_type
{
switch (v) {
case VIDEO_RANGE_DEFAULT:
case VIDEO_RANGE_FULL:
return AVCOL_RANGE_JPEG;
case VIDEO_RANGE_PARTIAL:
return AVCOL_RANGE_MPEG;
case VIDEO_RANGE_FULL:
return AVCOL_RANGE_JPEG;
}
throw std::invalid_argument("unknown range");
}
@@ -365,7 +365,7 @@ AVPixelFormat ffmpeg::tools::get_best_compatible_format(const AVPixelFormat* lis
for (auto fmt = list; fmt && (*fmt != AV_PIX_FMT_NONE); fmt++) {
auto found = format_compatibility.find(std::pair{source, *fmt});
if (found != format_compatibility.end()) {
if (found != format_compatibility.end() && (score < found->second)) {
score = found->second;
best = *fmt;
}
@@ -378,3 +378,36 @@ AVPixelFormat ffmpeg::tools::get_best_compatible_format(const AVPixelFormat* lis
return best;
}
void ffmpeg::tools::setup_obs_color(video_colorspace colorspace, video_range_type range, AVCodecContext* context)
{
std::map<video_colorspace, std::tuple<AVColorSpace, AVColorPrimaries, AVColorTransferCharacteristic>>
colorspaces = {
{VIDEO_CS_DEFAULT, {AVCOL_SPC_BT470BG, AVCOL_PRI_BT470BG, AVCOL_TRC_SMPTE170M}},
{VIDEO_CS_601, {AVCOL_SPC_BT470BG, AVCOL_PRI_BT470BG, AVCOL_TRC_SMPTE170M}},
{VIDEO_CS_709, {AVCOL_SPC_BT709, AVCOL_PRI_BT709, AVCOL_TRC_BT709}},
};
std::map<video_range_type, AVColorRange> colorranges = {
{VIDEO_RANGE_DEFAULT, AVCOL_RANGE_MPEG},
{VIDEO_RANGE_PARTIAL, AVCOL_RANGE_MPEG},
{VIDEO_RANGE_FULL, AVCOL_RANGE_JPEG},
};
{
auto found = colorspaces.find(colorspace);
if (found != colorspaces.end()) {
context->colorspace = std::get<AVColorSpace>(found->second);
context->color_primaries = std::get<AVColorPrimaries>(found->second);
context->color_trc = std::get<AVColorTransferCharacteristic>(found->second);
}
}
{
auto found = colorranges.find(range);
if (found != colorranges.end()) {
context->color_range = found->second;
}
}
// Downscaling should result in downscaling, not pixelation
context->chroma_sample_location = AVCHROMA_LOC_CENTER;
}
+3 -1
View File
@@ -28,8 +28,8 @@
#include <vector>
extern "C" {
#include <libavutil/pixfmt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/pixfmt.h>
}
namespace ffmpeg {
@@ -57,6 +57,8 @@ namespace ffmpeg {
std::vector<AVPixelFormat> get_software_formats(const AVPixelFormat* list);
AVPixelFormat get_best_compatible_format(const AVPixelFormat* list, AVPixelFormat source);
void setup_obs_color(video_colorspace colorspace, video_range_type range, AVCodecContext* context);
} // namespace tools
} // namespace ffmpeg
+22
View File
@@ -0,0 +1,22 @@
// FFMPEG Video Encoder Integration for OBS Studio
// Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "base.hpp"
+61
View File
@@ -0,0 +1,61 @@
// FFMPEG Video Encoder Integration for OBS Studio
// Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include <cinttypes>
#include <list>
#include <string>
#include <utility>
extern "C" {
#pragma warning(push)
#pragma warning(disable : 4244)
#include <libavutil/frame.h>
#include <libavutil/hwcontext.h>
#pragma warning(pop)
}
namespace obsffmpeg {
namespace hwapi {
struct device {
std::pair<int64_t, int64_t> id;
std::string name;
};
class instance;
class base {
public:
virtual std::list<obsffmpeg::hwapi::device> enumerate_adapters() = 0;
virtual std::shared_ptr<obsffmpeg::hwapi::instance> create(obsffmpeg::hwapi::device target) = 0;
};
class instance {
public:
virtual AVBufferRef* create_device_context() = 0;
virtual std::shared_ptr<AVFrame> avframe_from_obs(AVBufferRef* frames, uint32_t handle, uint64_t lock_key,
uint64_t* next_lock_key) = 0;
};
} // namespace hwapi
} // namespace obsffmpeg
+210
View File
@@ -0,0 +1,210 @@
// FFMPEG Video Encoder Integration for OBS Studio
// Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "d3d11.hpp"
#include <sstream>
#include <vector>
extern "C" {
#pragma warning(push)
#pragma warning(disable : 4244)
#include <libavutil/hwcontext_d3d11va.h>
#pragma warning(pop)
}
obsffmpeg::hwapi::d3d11::d3d11() : _dxgi_module(0), _d3d11_module(0)
{
_dxgi_module = LoadLibraryW(L"dxgi.dll");
if (!_dxgi_module)
throw std::runtime_error("Unable to load DXGI");
_d3d11_module = LoadLibraryW(L"d3d11.dll");
if (!_d3d11_module)
throw std::runtime_error("Unable to load D3D11");
_CreateDXGIFactory = reinterpret_cast<CreateDXGIFactory_t>(GetProcAddress(_dxgi_module, "CreateDXGIFactory"));
_CreateDXGIFactory1 =
reinterpret_cast<CreateDXGIFactory1_t>(GetProcAddress(_dxgi_module, "CreateDXGIFactory1"));
_D3D11CreateDevice = reinterpret_cast<D3D11CreateDevice_t>(GetProcAddress(_d3d11_module, "D3D11CreateDevice"));
if (!_CreateDXGIFactory && !_CreateDXGIFactory1)
throw std::runtime_error("DXGI not supported");
if (!_D3D11CreateDevice)
throw std::runtime_error("D3D11 not supported");
HRESULT hr = _CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&_dxgifactory);
if (FAILED(hr)) {
std::stringstream sstr;
sstr << "Failed to create DXGI Factory (" << hr << ")";
throw std::runtime_error(sstr.str());
}
}
obsffmpeg::hwapi::d3d11::~d3d11()
{
FreeLibrary(_dxgi_module);
FreeLibrary(_d3d11_module);
}
std::list<obsffmpeg::hwapi::device> obsffmpeg::hwapi::d3d11::enumerate_adapters()
{
std::list<device> adapters;
// Enumerate Adapters
IDXGIAdapter1* dxgi_adapter = nullptr;
for (UINT idx = 0; !FAILED(_dxgifactory->EnumAdapters1(idx, &dxgi_adapter)); idx++) {
DXGI_ADAPTER_DESC1 desc = DXGI_ADAPTER_DESC1();
dxgi_adapter->GetDesc1(&desc);
std::vector<char> buf(1024);
size_t len = snprintf(buf.data(), buf.size(), "%ls (VEN_%04x/DEV_%04x/SUB_%04x/REV_%04x)",
desc.Description, desc.VendorId, desc.DeviceId, desc.SubSysId, desc.Revision);
device dev;
dev.name = std::string(buf.data(), buf.data() + len);
dev.id.first = desc.AdapterLuid.HighPart;
dev.id.second = desc.AdapterLuid.LowPart;
adapters.push_back(dev);
}
return std::move(adapters);
}
std::shared_ptr<obsffmpeg::hwapi::instance> obsffmpeg::hwapi::d3d11::create(obsffmpeg::hwapi::device target)
{
std::shared_ptr<d3d11_instance> inst;
ATL::CComPtr<ID3D11Device> device;
ATL::CComPtr<ID3D11DeviceContext> context;
IDXGIAdapter1* adapter = nullptr;
// Find the correct "Adapter" (device).
IDXGIAdapter1* dxgi_adapter = nullptr;
for (UINT idx = 0; !FAILED(_dxgifactory->EnumAdapters1(idx, &dxgi_adapter)); idx++) {
DXGI_ADAPTER_DESC1 desc = DXGI_ADAPTER_DESC1();
dxgi_adapter->GetDesc1(&desc);
if ((desc.AdapterLuid.LowPart == target.id.second) && (desc.AdapterLuid.HighPart == target.id.first)) {
adapter = dxgi_adapter;
break;
}
}
// Create a D3D11 Device
UINT device_flags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
std::vector<D3D_FEATURE_LEVEL> feature_levels = {D3D_FEATURE_LEVEL_12_1, D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1};
if (FAILED(_D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_HARDWARE, NULL, device_flags, feature_levels.data(),
static_cast<UINT>(feature_levels.size()), D3D11_SDK_VERSION, &device, NULL,
&context))) {
throw std::runtime_error("Failed to create D3D11 device for target.");
}
return std::make_shared<d3d11_instance>(device, context);
}
struct D3D11AVFrame {
ATL::CComPtr<ID3D11Texture2D> handle;
};
obsffmpeg::hwapi::d3d11_instance::d3d11_instance(ATL::CComPtr<ID3D11Device> device,
ATL::CComPtr<ID3D11DeviceContext> context)
{
_device = device;
_context = context;
}
obsffmpeg::hwapi::d3d11_instance::~d3d11_instance() {}
AVBufferRef* obsffmpeg::hwapi::d3d11_instance::create_device_context()
{
AVBufferRef* dctx_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA);
if (!dctx_ref)
throw std::runtime_error("Failed to allocate AVHWDeviceContext.");
AVHWDeviceContext* dctx = reinterpret_cast<AVHWDeviceContext*>(dctx_ref->data);
AVD3D11VADeviceContext* d3d11va = reinterpret_cast<AVD3D11VADeviceContext*>(dctx->hwctx);
// TODO: Determine if these need an additional reference.
d3d11va->device = _device;
d3d11va->device->AddRef();
d3d11va->device_context = _context;
d3d11va->device_context->AddRef();
if (av_hwdevice_ctx_init(dctx_ref) < 0)
throw std::runtime_error("Failed to initialize AVHWDeviceContext.");
return dctx_ref;
}
std::shared_ptr<AVFrame> obsffmpeg::hwapi::d3d11_instance::avframe_from_obs(AVBufferRef* frames, uint32_t handle,
uint64_t lock_key, uint64_t* next_lock_key)
{
AVFrame* frame = av_frame_alloc();
ATL::CComPtr<IDXGIKeyedMutex> mutex;
ATL::CComPtr<ID3D11Texture2D> input;
D3D11_TEXTURE2D_DESC input_desc;
D3D11_TEXTURE2D_DESC output_desc;
//ATL::CComPtr<ID3D11Texture2D> output;
if (FAILED(_device->OpenSharedResource(reinterpret_cast<HANDLE>(static_cast<uintptr_t>(handle)),
__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&input)))) {
throw std::runtime_error("Failed to open shared texture resource.");
}
if (FAILED(input->QueryInterface(__uuidof(IDXGIKeyedMutex), reinterpret_cast<void**>(&mutex)))) {
throw std::runtime_error("Failed to retrieve mutex for texture resource.");
}
if (FAILED(mutex->AcquireSync(lock_key, 1000))) {
throw std::runtime_error("Failed to acquire lock on input texture.");
}
// Set some parameters on the input texture, and get its description.
UINT evict = input->GetEvictionPriority();
input->SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM);
input->GetDesc(&input_desc);
if (av_hwframe_get_buffer(frames, frame, 0) < 0) {
throw std::runtime_error("Failed to create AVFrame.");
}
reinterpret_cast<ID3D11Texture2D*>(frame->data[0])->GetDesc(&output_desc);
// Clone the content of the input texture.
_context->CopyResource(reinterpret_cast<ID3D11Texture2D*>(frame->data[0]), input);
// Restore original parameters on input.
input->SetEvictionPriority(evict);
if (FAILED(mutex->ReleaseSync(lock_key))) {
throw std::runtime_error("Failed to release lock on input texture.");
}
// TODO: Determine if this is necessary.
mutex->ReleaseSync(*next_lock_key);
return std::shared_ptr<AVFrame>(frame, [](AVFrame* frame) {
av_frame_unref(frame);
av_frame_free(&frame);
});
}
+73
View File
@@ -0,0 +1,73 @@
// FFMPEG Video Encoder Integration for OBS Studio
// Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <atlutil.h>
#include <d3d11.h>
#include <dxgi.h>
#include <windows.h>
#include "base.hpp"
namespace obsffmpeg {
namespace hwapi {
class d3d11 : public base {
typedef HRESULT(__stdcall* CreateDXGIFactory_t)(REFIID, void**);
typedef HRESULT(__stdcall* CreateDXGIFactory1_t)(REFIID, void**);
typedef HRESULT(__stdcall* D3D11CreateDevice_t)(_In_opt_ IDXGIAdapter*, D3D_DRIVER_TYPE,
HMODULE, UINT, CONST D3D_FEATURE_LEVEL*, UINT,
UINT, _Out_opt_ ID3D11Device**,
_Out_opt_ D3D_FEATURE_LEVEL*,
_Out_opt_ ID3D11DeviceContext**);
HMODULE _dxgi_module;
CreateDXGIFactory_t _CreateDXGIFactory;
CreateDXGIFactory1_t _CreateDXGIFactory1;
HMODULE _d3d11_module;
D3D11CreateDevice_t _D3D11CreateDevice;
ATL::CComPtr<IDXGIFactory1> _dxgifactory;
public:
d3d11();
virtual ~d3d11();
virtual std::list<obsffmpeg::hwapi::device> enumerate_adapters() override;
virtual std::shared_ptr<obsffmpeg::hwapi::instance>
create(obsffmpeg::hwapi::device target) override;
};
class d3d11_instance : public instance {
ATL::CComPtr<ID3D11Device> _device;
ATL::CComPtr<ID3D11DeviceContext> _context;
public:
d3d11_instance(ATL::CComPtr<ID3D11Device> device, ATL::CComPtr<ID3D11DeviceContext> context);
virtual ~d3d11_instance();
virtual AVBufferRef* create_device_context() override;
virtual std::shared_ptr<AVFrame> avframe_from_obs(AVBufferRef* frames, uint32_t handle,
uint64_t lock_key,
uint64_t* next_lock_key) override;
};
} // namespace hwapi
} // namespace obsffmpeg
+14
View File
@@ -29,6 +29,18 @@ void obsffmpeg::ui::handler::override_colorformat(AVPixelFormat& target_format,
const AVCodec* codec, AVCodecContext* context)
{}
void obsffmpeg::ui::handler::get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) {}
void obsffmpeg::ui::handler::get_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context) {}
obsffmpeg::hwapi::device obsffmpeg::ui::handler::find_hw_device(std::shared_ptr<obsffmpeg::hwapi::base> api,
const AVCodec* codec, AVCodecContext* context)
{
return obsffmpeg::hwapi::device();
}
void obsffmpeg::ui::handler::update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) {}
void obsffmpeg::ui::handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) {}
void obsffmpeg::ui::handler::import_from_ffmpeg(const std::string ffmpeg, obs_data_t* settings, const AVCodec* codec,
@@ -40,3 +52,5 @@ std::string obsffmpeg::ui::handler::export_for_ffmpeg(obs_data_t* settings, cons
{
return std::string();
}
void obsffmpeg::ui::handler::process_avpacket(AVPacket& packet, const AVCodec* codec, AVCodecContext* context) {}
+9 -4
View File
@@ -22,6 +22,7 @@
#pragma once
#include <string>
#include "hwapi/base.hpp"
extern "C" {
#include <obs.h>
@@ -46,13 +47,15 @@ namespace obsffmpeg {
virtual void override_colorformat(AVPixelFormat& target_format, obs_data_t* settings,
const AVCodec* codec, AVCodecContext* context);
virtual void get_defaults(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) = 0;
virtual void get_defaults(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
virtual void get_properties(obs_properties_t* props, const AVCodec* codec,
AVCodecContext* context) = 0;
AVCodecContext* context);
virtual void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context) = 0;
virtual obsffmpeg::hwapi::device find_hw_device(std::shared_ptr<obsffmpeg::hwapi::base> api,
const AVCodec* codec, AVCodecContext* context);
virtual void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
virtual void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
@@ -61,6 +64,8 @@ namespace obsffmpeg {
virtual std::string export_for_ffmpeg(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context);
virtual void process_avpacket(AVPacket& packet, const AVCodec* codec, AVCodecContext* context);
};
} // namespace ui
} // namespace obsffmpeg
+17
View File
@@ -57,6 +57,7 @@ Useless except for strict_gop maybe?
*/
using namespace obsffmpeg::codecs::h264;
using namespace obsffmpeg::nvenc;
std::map<profile, std::string> profiles{
{profile::BASELINE, "baseline"},
@@ -125,6 +126,22 @@ void obsffmpeg::ui::nvenc_h264_handler::update(obs_data_t* settings, const AVCod
}
}
void obsffmpeg::ui::nvenc_h264_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
{
nvenc::log_options(settings, codec, context);
profile cfg_profile = static_cast<profile>(obs_data_get_int(settings, P_H264_PROFILE));
level cfg_level = static_cast<level>(obs_data_get_int(settings, P_H264_LEVEL));
auto found1 = profiles.find(cfg_profile);
if (found1 != profiles.end())
PLOG_INFO("[%s] H.264 Profile: %s", codec->name, found1->second.c_str());
auto found2 = levels.find(cfg_level);
if (found2 != levels.end())
PLOG_INFO("[%s] H.264 Level: %s", codec->name, found2->second.c_str());
}
void obsffmpeg::ui::nvenc_h264_handler::get_encoder_properties(obs_properties_t* props, const AVCodec* codec)
{
nvenc::get_properties_pre(props, codec);
+3
View File
@@ -45,6 +45,9 @@ namespace obsffmpeg {
virtual void update(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override;
virtual void log_options(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override;
private:
void get_encoder_properties(obs_properties_t* props, const AVCodec* codec);
+21
View File
@@ -133,6 +133,27 @@ void obsffmpeg::ui::nvenc_hevc_handler::update(obs_data_t* settings, const AVCod
}
}
void obsffmpeg::ui::nvenc_hevc_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
{
nvenc::log_options(settings, codec, context);
profile cfg_profile = static_cast<profile>(obs_data_get_int(settings, P_HEVC_PROFILE));
tier cfg_tier = static_cast<tier>(obs_data_get_int(settings, P_HEVC_TIER));
level cfg_level = static_cast<level>(obs_data_get_int(settings, P_HEVC_LEVEL));
auto found1 = profiles.find(cfg_profile);
if (found1 != profiles.end())
PLOG_INFO("[%s] H.265 Profile: %s", codec->name, found1->second.c_str());
auto found2 = levels.find(cfg_level);
if (found2 != levels.end())
PLOG_INFO("[%s] H.265 Level: %s", codec->name, found2->second.c_str());
auto found3 = tiers.find(cfg_tier);
if (found3 != tiers.end())
PLOG_INFO("[%s] H.265 Tier: %s", codec->name, found3->second.c_str());
}
void obsffmpeg::ui::nvenc_hevc_handler::get_encoder_properties(obs_properties_t* props, const AVCodec* codec)
{
nvenc::get_properties_pre(props, codec);
+3
View File
@@ -45,6 +45,9 @@ namespace obsffmpeg {
virtual void update(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override;
virtual void log_options(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override;
private:
void get_encoder_properties(obs_properties_t* props, const AVCodec* codec);
+118 -8
View File
@@ -57,7 +57,7 @@ extern "C" {
#define ST_RATECONTROL_QP_I ST_RATECONTROL_QP ".I"
#define ST_RATECONTROL_QP_I_INITIAL ST_RATECONTROL_QP_I ".Initial"
#define ST_RATECONTROL_QP_P ST_RATECONTROL_QP ".P"
#define ST_RATECONTROL_QP_ST_INITIAL ST_RATECONTROL_QP_P ".Initial"
#define ST_RATECONTROL_QP_P_INITIAL ST_RATECONTROL_QP_P ".Initial"
#define ST_RATECONTROL_QP_B ST_RATECONTROL_QP ".B"
#define ST_RATECONTROL_QP_B_INITIAL ST_RATECONTROL_QP_B ".Initial"
@@ -152,7 +152,7 @@ void obsffmpeg::nvenc::get_defaults(obs_data_t* settings, const AVCodec*, AVCode
obs_data_set_default_int(settings, ST_RATECONTROL_QP_I, 21);
obs_data_set_default_int(settings, ST_RATECONTROL_QP_I_INITIAL, -1);
obs_data_set_default_int(settings, ST_RATECONTROL_QP_P, 21);
obs_data_set_default_int(settings, ST_RATECONTROL_QP_ST_INITIAL, -1);
obs_data_set_default_int(settings, ST_RATECONTROL_QP_P_INITIAL, -1);
obs_data_set_default_int(settings, ST_RATECONTROL_QP_B, 21);
obs_data_set_default_int(settings, ST_RATECONTROL_QP_B_INITIAL, -1);
@@ -211,7 +211,7 @@ static bool modified_ratecontrol(obs_properties_t* props, obs_property_t*, obs_d
obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_P), have_qp);
obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_B), have_qp);
obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_I_INITIAL), have_qp_init);
obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_ST_INITIAL), have_qp_init);
obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_P_INITIAL), have_qp_init);
obs_property_set_visible(obs_properties_get(props, ST_RATECONTROL_QP_B_INITIAL), have_qp_init);
return true;
@@ -379,9 +379,9 @@ void obsffmpeg::nvenc::get_properties_post(obs_properties_t* props, const AVCode
obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QP_P)));
}
{
auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QP_ST_INITIAL,
TRANSLATE(ST_RATECONTROL_QP_ST_INITIAL), -1, 51, 1);
obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QP_ST_INITIAL)));
auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QP_P_INITIAL,
TRANSLATE(ST_RATECONTROL_QP_P_INITIAL), -1, 51, 1);
obs_property_set_long_description(p, TRANSLATE(DESC(ST_RATECONTROL_QP_P_INITIAL)));
}
{
auto p = obs_properties_add_int_slider(grp, ST_RATECONTROL_QP_B, TRANSLATE(ST_RATECONTROL_QP_B),
@@ -483,7 +483,7 @@ void obsffmpeg::nvenc::get_runtime_properties(obs_properties_t* props, const AVC
obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_I), false);
obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_I_INITIAL), false);
obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_P), false);
obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_ST_INITIAL), false);
obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_P_INITIAL), false);
obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_B), false);
obs_property_set_enabled(obs_properties_get(props, ST_RATECONTROL_QP_B_INITIAL), false);
obs_property_set_enabled(obs_properties_get(props, ST_AQ), false);
@@ -597,7 +597,7 @@ void obsffmpeg::nvenc::update(obs_data_t* settings, const AVCodec* codec, AVCode
av_opt_set_int(context->priv_data, "init_qpI",
obs_data_get_int(settings, ST_RATECONTROL_QP_I_INITIAL), 0);
av_opt_set_int(context->priv_data, "init_qpP",
obs_data_get_int(settings, ST_RATECONTROL_QP_ST_INITIAL), 0);
obs_data_get_int(settings, ST_RATECONTROL_QP_P_INITIAL), 0);
av_opt_set_int(context->priv_data, "init_qpB",
obs_data_get_int(settings, ST_RATECONTROL_QP_B_INITIAL), 0);
}
@@ -646,3 +646,113 @@ void obsffmpeg::nvenc::update(obs_data_t* settings, const AVCodec* codec, AVCode
}
}
}
void obsffmpeg::nvenc::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
{
preset cfg_preset = static_cast<preset>(obs_data_get_int(settings, ST_PRESET));
auto found1 = preset_to_opt.find(cfg_preset);
if (found1 != preset_to_opt.end())
PLOG_INFO("[%s] Preset: %s", codec->name, found1->second.c_str());
ratecontrolmode cfg_rc_mode = static_cast<ratecontrolmode>(obs_data_get_int(settings, ST_RATECONTROL_MODE));
int64_t cfg_rc_2pass = obs_data_get_int(settings, ST_RATECONTROL_TWOPASS);
int64_t cfg_rc_lahead = obs_data_get_int(settings, ST_RATECONTROL_LOOKAHEAD);
bool cfg_rc_adapti = obs_data_get_bool(settings, ST_RATECONTROL_ADAPTIVEI);
bool cfg_rc_adaptb = obs_data_get_bool(settings, ST_RATECONTROL_ADAPTIVEB);
auto found2 = ratecontrolmode_to_opt.find(cfg_rc_mode);
if (found2 != ratecontrolmode_to_opt.end())
PLOG_INFO("[%s] Rate Control: %s", codec->name, found2->second.c_str());
PLOG_INFO("[%s] Two Pass: %s", codec->name,
cfg_rc_2pass == 1 ? "Enabled" : (cfg_rc_2pass == 0 ? "Disabled" : "Default"));
PLOG_INFO("[%s] Lookahead: %" PRId64 " Frames", codec->name, cfg_rc_lahead);
if (cfg_rc_adapti && cfg_rc_lahead > 0)
PLOG_INFO("[%s] Adaptive I-Frames Enabled", codec->name);
if (cfg_rc_adaptb && cfg_rc_lahead > 0)
PLOG_INFO("[%s] Adaptive B-Frames Enabled", codec->name);
int64_t cfg_rc_bitrate = obs_data_get_int(settings, ST_RATECONTROL_BITRATE_TARGET);
int64_t cfg_rc_max_bitrate = obs_data_get_int(settings, ST_RATECONTROL_BITRATE_MAXIMUM);
int64_t cfg_rc_bufsize = obs_data_get_int(settings, S_RATECONTROL_BUFFERSIZE);
bool cfg_rc_quality = obs_data_get_bool(settings, ST_RATECONTROL_QUALITY);
int64_t cfg_rc_quality_min = obs_data_get_int(settings, ST_RATECONTROL_QUALITY_MINIMUM);
int64_t cfg_rc_quality_max = obs_data_get_int(settings, ST_RATECONTROL_QUALITY_MAXIMUM);
double_t cfg_rc_quality_tgt = obs_data_get_int(settings, ST_RATECONTROL_QUALITY_TARGET);
int64_t cfg_rc_qp_i = obs_data_get_int(settings, ST_RATECONTROL_QP_I);
int64_t cfg_rc_qp_p = obs_data_get_int(settings, ST_RATECONTROL_QP_P);
int64_t cfg_rc_qp_b = obs_data_get_int(settings, ST_RATECONTROL_QP_B);
int64_t cfg_rc_qp_i_init = obs_data_get_int(settings, ST_RATECONTROL_QP_I_INITIAL);
int64_t cfg_rc_qp_p_init = obs_data_get_int(settings, ST_RATECONTROL_QP_P_INITIAL);
int64_t cfg_rc_qp_b_init = obs_data_get_int(settings, ST_RATECONTROL_QP_B_INITIAL);
{
bool have_bitrate = false;
bool have_bitrate_max = false;
bool have_quality = false;
bool have_qp = false;
bool have_qp_init = false;
switch (cfg_rc_mode) {
case ratecontrolmode::CQP:
have_qp = true;
break;
case ratecontrolmode::CBR:
case ratecontrolmode::CBR_HQ:
case ratecontrolmode::CBR_LD_HQ:
have_bitrate = true;
av_opt_set_int(context->priv_data, "cbr", 1, 0);
break;
case ratecontrolmode::VBR:
case ratecontrolmode::VBR_HQ:
have_bitrate_max = true;
have_bitrate = true;
have_quality = true;
have_qp_init = true;
break;
}
PLOG_INFO("[%s] Buffer Size: %" PRId64, codec->name, cfg_rc_bufsize);
if (have_bitrate)
PLOG_INFO("[%s] Bitrate Target: %" PRId64, codec->name, cfg_rc_bitrate);
if (have_bitrate_max)
PLOG_INFO("[%s] Bitrate Maximum: %" PRId64, codec->name, cfg_rc_max_bitrate);
if (have_quality && cfg_rc_quality) {
PLOG_INFO("[%s] Quality Limits:", codec->name);
PLOG_INFO("[%s] Minimum: %" PRId64, codec->name, cfg_rc_quality_min);
PLOG_INFO("[%s] Maximum: %" PRId64, codec->name, cfg_rc_quality_max);
PLOG_INFO("[%s] Target: %f", codec->name, cfg_rc_quality_tgt);
}
if (have_qp)
PLOG_INFO("[%s] QP Values: %" PRId64 "/%" PRId64 "/%" PRId64, codec->name, cfg_rc_qp_i,
cfg_rc_qp_p, cfg_rc_qp_b);
if (have_qp_init)
PLOG_INFO("[%s] Initial QP Values: %" PRId64 "/%" PRId64 "/%" PRId64, codec->name,
cfg_rc_qp_i_init, cfg_rc_qp_p_init, cfg_rc_qp_b_init);
}
bool cfg_aq_spatial = obs_data_get_bool(settings, ST_AQ_SPATIAL);
int64_t cfg_aq_strength = obs_data_get_int(settings, ST_AQ_STRENGTH);
bool cfg_aq_temporal = obs_data_get_bool(settings, ST_AQ_TEMPORAL);
if (cfg_aq_spatial)
PLOG_INFO("[%s] Spatial AQ Enabled: Strength %" PRId64, codec->name, cfg_aq_strength);
if (cfg_aq_temporal)
PLOG_INFO("[%s] Temporal AQ Enabled", codec->name);
int64_t cfg_bf = obs_data_get_int(settings, ST_OTHER_BFRAMES);
b_ref_mode cfg_bf_mode = static_cast<b_ref_mode>(obs_data_get_int(settings, ST_OTHER_BFRAME_REFERENCEMODE));
bool cfg_zerolatency = obs_data_get_bool(settings, ST_OTHER_ZEROLATENCY);
bool cfg_weightp = obs_data_get_bool(settings, ST_OTHER_WEIGHTED_PREDICTION);
bool cfg_nonrefp = obs_data_get_bool(settings, ST_OTHER_NONREFERENCE_PFRAMES);
PLOG_INFO("[%s] B-Frames: %" PRId64, codec->name, cfg_bf);
auto found3 = b_ref_mode_to_opt.find(cfg_bf_mode);
if (found3 != b_ref_mode_to_opt.end())
PLOG_INFO("[%s] Reference Mode: %s", codec->name, found3->second.c_str());
if (cfg_zerolatency)
PLOG_INFO("[%s] Zero Latency Enabled", codec->name);
if (cfg_weightp)
PLOG_INFO("[%s] Weighted Prediction Enabled", codec->name);
if (cfg_nonrefp)
PLOG_INFO("[%s] Non-Ref P-Frames Enabled", codec->name);
}
+2
View File
@@ -84,5 +84,7 @@ namespace obsffmpeg {
void get_runtime_properties(obs_properties_t* props, const AVCodec* codec, AVCodecContext* context);
void update(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
void log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context);
} // namespace nvenc
} // namespace obsffmpeg
+22
View File
@@ -104,3 +104,25 @@ void obsffmpeg::ui::prores_aw_handler::update(obs_data_t* settings, const AVCode
{
context->profile = static_cast<int>(obs_data_get_int(settings, P_PRORES_PROFILE));
}
void obsffmpeg::ui::prores_aw_handler::log_options(obs_data_t* settings, const AVCodec* codec, AVCodecContext* context)
{
for (auto ptr = codec->profiles; ptr->profile != FF_PROFILE_UNKNOWN; ptr++) {
if (ptr->profile == static_cast<int>(obs_data_get_int(settings, P_PRORES_PROFILE)))
PLOG_INFO("[%s] Profile: %s", codec->name, ptr->name);
}
}
void obsffmpeg::ui::prores_aw_handler::process_avpacket(AVPacket& packet, const AVCodec* codec, AVCodecContext* context)
{
//FFmpeg Bug:
// When ProRes content is stored in Matroska, FFmpeg strips the size
// from the atom. Later when the ProRes content is demuxed from Matroska,
// FFmpeg creates an atom with the incorrect size, as the ATOM size
// should be content + atom, but FFmpeg set it to only be content. This
// difference leads to decoders to be off by 8 bytes.
//Fix (until FFmpeg stops being broken):
// Pad the packet with 8 bytes of 0x00.
av_grow_packet(&packet, 8);
}
+5
View File
@@ -45,6 +45,11 @@ namespace obsffmpeg {
virtual void update(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override;
virtual void log_options(obs_data_t* settings, const AVCodec* codec,
AVCodecContext* context) override;
virtual void process_avpacket(AVPacket& packet, const AVCodec* codec, AVCodecContext* context) override;
};
} // namespace ui
} // namespace obsffmpeg