Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ffmpeg/tools: Set correct information when encoding #353

Merged
merged 3 commits into from
Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions source/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

// Common C++ includes
#include <algorithm>
#include <array>
#include <limits>
#include <map>
#include <memory>
Expand Down
69 changes: 37 additions & 32 deletions source/encoders/encoder-ffmpeg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ ffmpeg_instance::ffmpeg_instance(obs_data_t* settings, obs_encoder_t* self, bool
if (is_hw) {
// Abort if user specified manual override.
if ((static_cast<AVPixelFormat>(obs_data_get_int(settings, KEY_FFMPEG_COLORFORMAT)) != AV_PIX_FMT_NONE)
|| (obs_data_get_int(settings, KEY_FFMPEG_GPU) != -1) || (obs_encoder_scaling_enabled(_self))) {
|| (obs_data_get_int(settings, KEY_FFMPEG_GPU) != -1) || (obs_encoder_scaling_enabled(_self))
|| (video_output_get_info(obs_encoder_video(_self))->format != VIDEO_FORMAT_NV12)) {
throw std::runtime_error(
"Selected settings prevent the use of hardware encoding, falling back to software.");
}
Expand Down Expand Up @@ -439,16 +440,18 @@ void ffmpeg_instance::initialize_sw(obs_data_t* settings)
}
}

_context->width = static_cast<int>(obs_encoder_get_width(_self));
_context->height = static_cast<int>(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<int>(voi->fps_num);
_context->framerate.den = _context->time_base.num = static_cast<int>(voi->fps_den);
// Override with other information.
_context->width = static_cast<int>(obs_encoder_get_width(_self));
_context->height = static_cast<int>(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<uint32_t>(_context->width), static_cast<uint32_t>(_context->height));
_scaler.set_source_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace);
Expand All @@ -473,36 +476,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));

_context->width = static_cast<int>(voi->width);
_context->height = static_cast<int>(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<int>(voi->fps_num);
_context->framerate.den = _context->time_base.num = static_cast<int>(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);
const video_output_info* voi = video_output_get_info(obs_encoder_video(_self));

// 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<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("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<char, 2048> buffer;
size_t len = static_cast<size_t>(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
}

Expand Down Expand Up @@ -583,9 +587,10 @@ bool ffmpeg_instance::get_sei_data(uint8_t** data, size_t* size)

void ffmpeg_instance::get_video_info(struct video_scale_info* info)
{
info->width = _scaler.get_source_width();
info->height = _scaler.get_source_height();
info->format = ::ffmpeg::tools::avpixelformat_to_obs_videoformat(_scaler.get_source_format());
if (!is_hardware_encode()) {
// Override input with supported format if software encode.
info->format = ::ffmpeg::tools::avpixelformat_to_obs_videoformat(_scaler.get_source_format());
}
}

int ffmpeg_instance::receive_packet(bool* received_packet, struct encoder_packet* packet)
Expand Down
108 changes: 61 additions & 47 deletions source/ffmpeg/tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,30 +128,59 @@ 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)
AVColorRange tools::obs_to_av_color_range(video_range_type v)
{
switch (v) {
case VIDEO_RANGE_DEFAULT:
case VIDEO_RANGE_PARTIAL:
return AVCOL_RANGE_MPEG;
case VIDEO_RANGE_FULL:
return AVCOL_RANGE_JPEG;
}
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_SPC_BT470BG;
return AVCOL_PRI_BT470BG;
case VIDEO_CS_DEFAULT:
case VIDEO_CS_709:
case VIDEO_CS_SRGB:
return AVCOL_SPC_RGB;
return AVCOL_PRI_BT709;
default:
throw std::invalid_argument("Unknown Color Primaries");
}
throw std::invalid_argument("unknown color space");
}

AVColorRange tools::obs_videorangetype_to_avcolorrange(video_range_type v)
AVColorTransferCharacteristic ffmpeg::tools::obs_to_av_color_transfer_characteristics(video_colorspace v)
{
switch (v) {
case VIDEO_RANGE_DEFAULT:
case VIDEO_RANGE_PARTIAL:
return AVCOL_RANGE_MPEG;
case VIDEO_RANGE_FULL:
return AVCOL_RANGE_JPEG;
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");
}
throw std::invalid_argument("unknown range");
}

bool tools::can_hardware_encode(const AVCodec* codec)
Expand Down Expand Up @@ -204,43 +233,28 @@ std::vector<AVPixelFormat> 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<video_colorspace, std::tuple<AVColorSpace, AVColorPrimaries, AVColorTransferCharacteristic>> 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<video_range_type, AVColorRange> colorranges = {
{VIDEO_RANGE_PARTIAL, AVCOL_RANGE_MPEG},
{VIDEO_RANGE_FULL, AVCOL_RANGE_JPEG},
};

{
if (colorspace == VIDEO_CS_DEFAULT)
colorspace = VIDEO_CS_601;
if (range == VIDEO_RANGE_DEFAULT)
range = VIDEO_RANGE_PARTIAL;
}

{
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;
// Resolution
context->width = static_cast<int>(voi->width);
context->height = static_cast<int>(voi->height);

// Framerate
context->ticks_per_frame = 1;
context->framerate.num = context->time_base.den = static_cast<int>(voi->fps_num);
context->framerate.den = context->time_base.num = static_cast<int>(voi->fps_den);

// Aspect Ratio, Progressive
context->sample_aspect_ratio.num = 1;
context->sample_aspect_ratio.den = 1;
context->field_order = AV_FIELD_PROGRESSIVE;

// 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)
Expand Down
12 changes: 6 additions & 6 deletions source/ffmpeg/tools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<AVPixelFormat> 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);

Expand Down