diff --git a/source/common.hpp b/source/common.hpp index 6ac7b8e..6387b5b 100644 --- a/source/common.hpp +++ b/source/common.hpp @@ -32,6 +32,7 @@ // Common C++ includes #include +#include #include #include #include diff --git a/source/encoders/encoder-ffmpeg.cpp b/source/encoders/encoder-ffmpeg.cpp index 4d61221..f1def38 100644 --- a/source/encoders/encoder-ffmpeg.cpp +++ b/source/encoders/encoder-ffmpeg.cpp @@ -439,16 +439,18 @@ void ffmpeg_instance::initialize_sw(obs_data_t* settings) } } - _context->width = static_cast(obs_encoder_get_width(_self)); - _context->height = static_cast(obs_encoder_get_height(_self)); - ::ffmpeg::tools::setup_obs_color(voi->colorspace, voi->range, _context); + // Setup from OBS information. + ::ffmpeg::tools::context_setup_from_obs(voi, _context); - _context->pix_fmt = _pixfmt_target; - _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 = static_cast(voi->fps_num); - _context->framerate.den = _context->time_base.num = static_cast(voi->fps_den); + // Override with other information. + _context->width = static_cast(obs_encoder_get_width(_self)); + _context->height = static_cast(obs_encoder_get_height(_self)); + _context->pix_fmt = _pixfmt_target; + + // Prevent pixelation by sampling "center" instead of corners. This creates + // a smoother look, which may not be H.264/AVC standard compliant, however it + // provides better support for scaling algorithms, such as Bicubic. + _context->chroma_sample_location = AVCHROMA_LOC_CENTER; _scaler.set_source_size(static_cast(_context->width), static_cast(_context->height)); _scaler.set_source_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace); @@ -473,36 +475,37 @@ void ffmpeg_instance::initialize_sw(obs_data_t* settings) void ffmpeg_instance::initialize_hw(obs_data_t*) { -#ifdef D_PLATFORM_WINDOWS +#ifndef D_PLATFORM_WINDOWS + throw std::runtime_error("OBS Studio currently does not support zero copy encoding for this platform."); +#else // Initialize Video Encoding - auto voi = video_output_get_info(obs_encoder_video(_self)); + const video_output_info* voi = video_output_get_info(obs_encoder_video(_self)); - _context->width = static_cast(voi->width); - _context->height = static_cast(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 = static_cast(voi->fps_num); - _context->framerate.den = _context->time_base.num = static_cast(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); + // Apply pixel format settings. + ::ffmpeg::tools::context_setup_from_obs(voi, _context); + _context->sw_pix_fmt = _context->pix_fmt; _context->pix_fmt = AV_PIX_FMT_D3D11; + // Try to create a hardware context. _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("Allocating hardware context failed, chosen pixel format is likely not supported."); + if (!_context->hw_frames_ctx) { + throw std::runtime_error("Creating hardware context failed."); + } + // Initialize Hardware Context AVHWFramesContext* ctx = reinterpret_cast(_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("Initializing hardware context failed, chosen pixel format is likely not supported."); -#else - throw std::runtime_error("OBS Studio currently does not support zero copy encoding for this platform."); + if (int32_t res = av_hwframe_ctx_init(_context->hw_frames_ctx); res < 0) { + std::array buffer; + size_t len = static_cast(snprintf(buffer.data(), buffer.size(), + "Initializing hardware context failed with error: %s (%" PRIu32 ")", + ::ffmpeg::tools::get_error_description(res), res)); + throw std::runtime_error(std::string(buffer.data(), buffer.data() + len)); + } #endif } diff --git a/source/ffmpeg/tools.cpp b/source/ffmpeg/tools.cpp index c1e133b..6a1ebd2 100644 --- a/source/ffmpeg/tools.cpp +++ b/source/ffmpeg/tools.cpp @@ -128,21 +128,7 @@ AVPixelFormat tools::get_least_lossy_format(const AVPixelFormat* haystack, AVPix return avcodec_find_best_pix_fmt_of_list(haystack, needle, 0, &data_loss); } -AVColorSpace tools::obs_videocolorspace_to_avcolorspace(video_colorspace v) -{ - switch (v) { - case VIDEO_CS_DEFAULT: - case VIDEO_CS_709: - return AVCOL_SPC_BT709; - case VIDEO_CS_601: - return AVCOL_SPC_BT470BG; - case VIDEO_CS_SRGB: - return AVCOL_SPC_RGB; - } - throw std::invalid_argument("unknown color space"); -} - -AVColorRange tools::obs_videorangetype_to_avcolorrange(video_range_type v) +AVColorRange tools::obs_to_av_color_range(video_range_type v) { switch (v) { case VIDEO_RANGE_DEFAULT: @@ -151,7 +137,50 @@ AVColorRange tools::obs_videorangetype_to_avcolorrange(video_range_type v) case VIDEO_RANGE_FULL: return AVCOL_RANGE_JPEG; } - throw std::invalid_argument("unknown range"); + throw std::invalid_argument("Unknown Color Range"); +} + +AVColorSpace tools::obs_to_av_color_space(video_colorspace v) +{ + switch (v) { + case VIDEO_CS_601: + return AVCOL_SPC_BT470BG; + case VIDEO_CS_DEFAULT: + case VIDEO_CS_709: + case VIDEO_CS_SRGB: + return AVCOL_SPC_BT709; + default: + throw std::invalid_argument("Unknown Color Space"); + } +} + +AVColorPrimaries ffmpeg::tools::obs_to_av_color_primary(video_colorspace v) +{ + switch (v) { + case VIDEO_CS_601: + return AVCOL_PRI_BT470BG; + case VIDEO_CS_DEFAULT: + case VIDEO_CS_709: + case VIDEO_CS_SRGB: + return AVCOL_PRI_BT709; + default: + throw std::invalid_argument("Unknown Color Primaries"); + } +} + +AVColorTransferCharacteristic ffmpeg::tools::obs_to_av_color_transfer_characteristics(video_colorspace v) +{ + switch (v) { + case VIDEO_CS_601: + return AVCOL_TRC_LINEAR; + case VIDEO_CS_DEFAULT: + case VIDEO_CS_709: + return AVCOL_TRC_BT709; + case VIDEO_CS_SRGB: + return AVCOL_TRC_IEC61966_2_1; + default: + throw std::invalid_argument("Unknown Color Transfer Characteristics"); + } } bool tools::can_hardware_encode(const AVCodec* codec) @@ -204,43 +233,28 @@ std::vector tools::get_software_formats(const AVPixelFormat* list return std::move(fmts); } -void tools::setup_obs_color(video_colorspace colorspace, video_range_type range, AVCodecContext* context) +void tools::context_setup_from_obs(const video_output_info* voi, AVCodecContext* context) { - std::map> colorspaces = - { - {VIDEO_CS_601, {AVCOL_SPC_BT470BG, AVCOL_PRI_BT470BG, AVCOL_TRC_SMPTE170M}}, - {VIDEO_CS_709, {AVCOL_SPC_BT709, AVCOL_PRI_BT709, AVCOL_TRC_BT709}}, - {VIDEO_CS_SRGB, {AVCOL_SPC_RGB, AVCOL_PRI_BT709, AVCOL_TRC_IEC61966_2_1}}, - }; - std::map colorranges = { - {VIDEO_RANGE_PARTIAL, AVCOL_RANGE_MPEG}, - {VIDEO_RANGE_FULL, AVCOL_RANGE_JPEG}, - }; + // Resolution + context->width = static_cast(voi->width); + context->height = static_cast(voi->height); - { - if (colorspace == VIDEO_CS_DEFAULT) - colorspace = VIDEO_CS_601; - if (range == VIDEO_RANGE_DEFAULT) - range = VIDEO_RANGE_PARTIAL; - } + // Framerate + context->ticks_per_frame = 1; + context->framerate.num = context->time_base.den = static_cast(voi->fps_num); + context->framerate.den = context->time_base.num = static_cast(voi->fps_den); - { - auto found = colorspaces.find(colorspace); - if (found != colorspaces.end()) { - context->colorspace = std::get(found->second); - context->color_primaries = std::get(found->second); - context->color_trc = std::get(found->second); - } - } - { - auto found = colorranges.find(range); - if (found != colorranges.end()) { - context->color_range = found->second; - } - } + // Aspect Ratio, Progressive + context->sample_aspect_ratio.num = 1; + context->sample_aspect_ratio.den = 1; + context->field_order = AV_FIELD_PROGRESSIVE; - // Downscaling should result in downscaling, not pixelation - context->chroma_sample_location = AVCHROMA_LOC_CENTER; + // Decipher Pixel information + context->pix_fmt = obs_videoformat_to_avpixelformat(voi->format); + context->color_range = obs_to_av_color_range(voi->range); + context->colorspace = obs_to_av_color_space(voi->colorspace); + context->color_primaries = obs_to_av_color_primary(voi->colorspace); + context->color_trc = obs_to_av_color_transfer_characteristics(voi->colorspace); } const char* tools::get_std_compliance_name(int compliance) diff --git a/source/ffmpeg/tools.hpp b/source/ffmpeg/tools.hpp index 3d1249f..ee6561f 100644 --- a/source/ffmpeg/tools.hpp +++ b/source/ffmpeg/tools.hpp @@ -43,20 +43,20 @@ namespace ffmpeg::tools { const char* get_error_description(int error); AVPixelFormat obs_videoformat_to_avpixelformat(video_format v); - - video_format avpixelformat_to_obs_videoformat(AVPixelFormat v); + video_format avpixelformat_to_obs_videoformat(AVPixelFormat v); AVPixelFormat get_least_lossy_format(const AVPixelFormat* haystack, AVPixelFormat needle); - AVColorSpace obs_videocolorspace_to_avcolorspace(video_colorspace v); - - AVColorRange obs_videorangetype_to_avcolorrange(video_range_type v); + AVColorRange obs_to_av_color_range(video_range_type v); + AVColorSpace obs_to_av_color_space(video_colorspace v); + AVColorPrimaries obs_to_av_color_primary(video_colorspace v); + AVColorTransferCharacteristic obs_to_av_color_transfer_characteristics(video_colorspace v); bool can_hardware_encode(const AVCodec* codec); std::vector get_software_formats(const AVPixelFormat* list); - void setup_obs_color(video_colorspace colorspace, video_range_type range, AVCodecContext* context); + void context_setup_from_obs(const video_output_info* voi, AVCodecContext* context); const char* get_std_compliance_name(int compliance);