2019-07-06 13:08:23 +02:00
|
|
|
// FFMPEG Video Encoder Integration for OBS Studio
|
2019-07-22 13:53:25 +02:00
|
|
|
// Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com>
|
2019-07-06 13:08:23 +02:00
|
|
|
//
|
2019-07-22 13:53:25 +02:00
|
|
|
// 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:
|
2019-07-06 13:08:23 +02:00
|
|
|
//
|
2019-07-22 13:53:25 +02:00
|
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
|
|
|
// copies or substantial portions of the Software.
|
2019-07-06 13:08:23 +02:00
|
|
|
//
|
2019-07-22 13:53:25 +02:00
|
|
|
// 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.
|
2019-07-06 13:08:23 +02:00
|
|
|
|
|
|
|
|
#include "generic.hpp"
|
|
|
|
|
#include <iomanip>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
#include <thread>
|
2019-07-07 00:34:56 +02:00
|
|
|
#include <util/profiler.hpp>
|
2019-07-06 13:08:23 +02:00
|
|
|
#include "ffmpeg/tools.hpp"
|
|
|
|
|
#include "plugin.hpp"
|
|
|
|
|
#include "utility.hpp"
|
|
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
|
#include <obs-module.h>
|
|
|
|
|
#pragma warning(push)
|
|
|
|
|
#pragma warning(disable : 4244)
|
2019-07-21 11:51:42 +02:00
|
|
|
#include <libavutil/dict.h>
|
|
|
|
|
#include <libavutil/frame.h>
|
|
|
|
|
#include <libavutil/opt.h>
|
|
|
|
|
#include <libavutil/pixdesc.h>
|
2019-07-06 13:08:23 +02:00
|
|
|
#pragma warning(pop)
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 14:26:51 +02:00
|
|
|
// Generic
|
|
|
|
|
#define P_AUTOMATIC "Automatic"
|
2019-07-06 13:08:23 +02:00
|
|
|
|
|
|
|
|
// FFmpeg
|
|
|
|
|
#define P_FFMPEG "FFmpeg"
|
|
|
|
|
#define P_FFMPEG_CUSTOMSETTINGS "FFmpeg.CustomSettings"
|
2019-07-07 14:33:58 +02:00
|
|
|
#define P_FFMPEG_THREADS "FFmpeg.Threads"
|
2019-07-07 14:26:51 +02:00
|
|
|
#define P_FFMPEG_COLORFORMAT "FFmpeg.ColorFormat"
|
2019-07-06 13:08:23 +02:00
|
|
|
#define P_FFMPEG_STANDARDCOMPLIANCE "FFmpeg.StandardCompliance"
|
|
|
|
|
|
|
|
|
|
enum class keyframe_type { Seconds, Frames };
|
|
|
|
|
|
|
|
|
|
encoder::generic_factory::generic_factory(AVCodec* codec) : avcodec_ptr(codec), info() {}
|
|
|
|
|
|
|
|
|
|
encoder::generic_factory::~generic_factory() {}
|
|
|
|
|
|
|
|
|
|
void encoder::generic_factory::register_encoder()
|
|
|
|
|
{
|
|
|
|
|
// Generate unique name from given Id
|
|
|
|
|
{
|
|
|
|
|
std::stringstream sstr;
|
|
|
|
|
sstr << "ffmpeg-" << avcodec_ptr->name << "-0x" << std::uppercase << std::setfill('0') << std::setw(8)
|
|
|
|
|
<< std::hex << avcodec_ptr->capabilities;
|
|
|
|
|
this->info.uid = sstr.str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Also generate a human readable name while we're at it.
|
|
|
|
|
// TODO: Figure out a way to translate from names to other names.
|
|
|
|
|
{
|
|
|
|
|
std::stringstream sstr;
|
2019-07-21 11:53:51 +02:00
|
|
|
sstr << (avcodec_ptr->long_name ? avcodec_ptr->long_name : avcodec_ptr->name) << " ("
|
2019-07-06 13:08:23 +02:00
|
|
|
<< avcodec_ptr->name << ")";
|
|
|
|
|
std::string caps = ffmpeg::tools::translate_encoder_capabilities(avcodec_ptr->capabilities);
|
|
|
|
|
if (caps.length() != 0) {
|
2019-07-21 11:53:51 +02:00
|
|
|
sstr << " [" << caps << "]";
|
2019-07-06 13:08:23 +02:00
|
|
|
}
|
|
|
|
|
this->info.readable_name = sstr.str();
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 00:34:56 +02:00
|
|
|
// Assign Ids.
|
|
|
|
|
{
|
|
|
|
|
const AVCodecDescriptor* desc = avcodec_descriptor_get(this->avcodec_ptr->id);
|
|
|
|
|
if (desc) {
|
|
|
|
|
this->info.codec = desc->name;
|
|
|
|
|
} else {
|
|
|
|
|
this->info.codec = avcodec_ptr->name;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-06 13:08:23 +02:00
|
|
|
this->info.oei.id = this->info.uid.c_str();
|
|
|
|
|
this->info.oei.codec = this->info.codec.c_str();
|
|
|
|
|
|
2019-07-21 11:51:42 +02:00
|
|
|
// Is this a deprecated encoder?
|
|
|
|
|
#ifndef _DEBUG
|
|
|
|
|
if (!obsffmpeg::has_codec_handler(avcodec_ptr->name)) {
|
|
|
|
|
this->info.oei.caps |= OBS_ENCODER_CAP_DEPRECATED;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-07-06 13:08:23 +02:00
|
|
|
// Detect encoder type (only Video and Audio supported)
|
|
|
|
|
if (avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_VIDEO) {
|
|
|
|
|
this->info.oei.type = obs_encoder_type::OBS_ENCODER_VIDEO;
|
|
|
|
|
} else if (avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_AUDIO) {
|
|
|
|
|
this->info.oei.type = obs_encoder_type::OBS_ENCODER_AUDIO;
|
|
|
|
|
} else {
|
|
|
|
|
throw std::invalid_argument("unsupported codec type");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Register functions.
|
|
|
|
|
this->info.oei.create = [](obs_data_t* settings, obs_encoder_t* encoder) {
|
|
|
|
|
try {
|
|
|
|
|
return reinterpret_cast<void*>(new generic(settings, encoder));
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
2019-07-07 01:12:06 +02:00
|
|
|
return reinterpret_cast<void*>(0);
|
2019-07-06 13:08:23 +02:00
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
2019-07-07 01:12:06 +02:00
|
|
|
return reinterpret_cast<void*>(0);
|
2019-07-06 13:08:23 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this->info.oei.destroy = [](void* ptr) {
|
|
|
|
|
try {
|
|
|
|
|
delete reinterpret_cast<generic*>(ptr);
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this->info.oei.get_name = [](void* type_data) {
|
|
|
|
|
try {
|
|
|
|
|
return reinterpret_cast<generic_factory*>(type_data)->get_name();
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this->info.oei.get_defaults2 = [](obs_data_t* settings, void* type_data) {
|
|
|
|
|
try {
|
|
|
|
|
reinterpret_cast<generic_factory*>(type_data)->get_defaults(settings);
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this->info.oei.get_properties2 = [](void* ptr, void* type_data) {
|
|
|
|
|
try {
|
|
|
|
|
obs_properties_t* props = obs_properties_create();
|
|
|
|
|
if (type_data != nullptr) {
|
|
|
|
|
reinterpret_cast<generic_factory*>(type_data)->get_properties(props);
|
|
|
|
|
}
|
|
|
|
|
if (ptr != nullptr) {
|
|
|
|
|
reinterpret_cast<generic*>(ptr)->get_properties(props);
|
|
|
|
|
}
|
|
|
|
|
return props;
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this->info.oei.update = [](void* ptr, obs_data_t* settings) {
|
|
|
|
|
try {
|
|
|
|
|
return reinterpret_cast<generic*>(ptr)->update(settings);
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this->info.oei.get_sei_data = [](void* ptr, uint8_t** sei_data, size_t* size) {
|
|
|
|
|
try {
|
|
|
|
|
return reinterpret_cast<generic*>(ptr)->get_sei_data(sei_data, size);
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this->info.oei.get_extra_data = [](void* ptr, uint8_t** extra_data, size_t* size) {
|
|
|
|
|
try {
|
|
|
|
|
return reinterpret_cast<generic*>(ptr)->get_extra_data(extra_data, size);
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
2019-07-06 14:22:15 +02:00
|
|
|
|
|
|
|
|
if (this->avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_VIDEO) {
|
|
|
|
|
this->info.oei.get_video_info = [](void* ptr, struct video_scale_info* info) {
|
|
|
|
|
try {
|
|
|
|
|
reinterpret_cast<generic*>(ptr)->get_video_info(info);
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this->info.oei.encode = [](void* ptr, struct encoder_frame* frame, struct encoder_packet* packet,
|
|
|
|
|
bool* received_packet) {
|
|
|
|
|
try {
|
|
|
|
|
return reinterpret_cast<generic*>(ptr)->video_encode(frame, packet, received_packet);
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this->info.oei.encode_texture = [](void* ptr, uint32_t handle, int64_t pts, uint64_t lock_key,
|
|
|
|
|
uint64_t* next_key, struct encoder_packet* packet,
|
|
|
|
|
bool* received_packet) {
|
|
|
|
|
try {
|
|
|
|
|
return reinterpret_cast<generic*>(ptr)->video_encode_texture(
|
|
|
|
|
handle, pts, lock_key, next_key, packet, received_packet);
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} else if (this->avcodec_ptr->type == AVMediaType::AVMEDIA_TYPE_AUDIO) {
|
|
|
|
|
this->info.oei.get_audio_info = [](void* ptr, struct audio_convert_info* info) {
|
|
|
|
|
try {
|
|
|
|
|
reinterpret_cast<generic*>(ptr)->get_audio_info(info);
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this->info.oei.get_frame_size = [](void* ptr) {
|
|
|
|
|
try {
|
|
|
|
|
return reinterpret_cast<generic*>(ptr)->get_frame_size();
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this->info.oei.encode = [](void* ptr, struct encoder_frame* frame, struct encoder_packet* packet,
|
|
|
|
|
bool* received_packet) {
|
|
|
|
|
try {
|
|
|
|
|
return reinterpret_cast<generic*>(ptr)->audio_encode(frame, packet, received_packet);
|
|
|
|
|
} catch (std::exception const& e) {
|
|
|
|
|
PLOG_ERROR("exception: %s", e.what());
|
|
|
|
|
throw e;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
PLOG_ERROR("unknown exception");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2019-07-06 13:08:23 +02:00
|
|
|
|
|
|
|
|
// Finally store ourself as type data.
|
|
|
|
|
this->info.oei.type_data = this;
|
|
|
|
|
|
|
|
|
|
obs_register_encoder(&this->info.oei);
|
2019-07-21 11:51:42 +02:00
|
|
|
PLOG_DEBUG("Registered encoder #%llX with name '%s' and long name '%s' and caps %llX", avcodec_ptr,
|
|
|
|
|
avcodec_ptr->name, avcodec_ptr->long_name, avcodec_ptr->capabilities);
|
2019-07-06 13:08:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* encoder::generic_factory::get_name()
|
|
|
|
|
{
|
|
|
|
|
return this->info.readable_name.c_str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void encoder::generic_factory::get_defaults(obs_data_t* settings)
|
|
|
|
|
{
|
2019-07-07 13:43:02 +02:00
|
|
|
{ // Handler
|
|
|
|
|
auto ptr = obsffmpeg::find_codec_handler(this->avcodec_ptr->name);
|
|
|
|
|
if (ptr) {
|
|
|
|
|
ptr->get_defaults(settings, this->avcodec_ptr, nullptr);
|
2019-07-06 13:08:23 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{ // Integrated Options
|
|
|
|
|
// FFmpeg
|
|
|
|
|
obs_data_set_default_string(settings, P_FFMPEG_CUSTOMSETTINGS, "");
|
2019-07-10 21:02:12 +02:00
|
|
|
obs_data_set_default_int(settings, P_FFMPEG_COLORFORMAT, static_cast<int64_t>(AV_PIX_FMT_NONE));
|
2019-07-07 14:33:58 +02:00
|
|
|
obs_data_set_default_int(settings, P_FFMPEG_THREADS, 0);
|
2019-07-06 13:08:23 +02:00
|
|
|
obs_data_set_default_int(settings, P_FFMPEG_STANDARDCOMPLIANCE, FF_COMPLIANCE_STRICT);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void encoder::generic_factory::get_properties(obs_properties_t* props)
|
|
|
|
|
{
|
2019-07-07 13:43:02 +02:00
|
|
|
{ // Handler
|
|
|
|
|
auto ptr = obsffmpeg::find_codec_handler(this->avcodec_ptr->name);
|
|
|
|
|
if (ptr) {
|
|
|
|
|
ptr->get_properties(props, this->avcodec_ptr, nullptr);
|
2019-07-06 13:08:23 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
2019-07-22 05:02:44 +02:00
|
|
|
obs_properties_t* grp = props;
|
|
|
|
|
if (!obsffmpeg::are_property_groups_broken()) {
|
|
|
|
|
auto prs = obs_properties_create();
|
|
|
|
|
obs_properties_add_group(props, P_FFMPEG, TRANSLATE(P_FFMPEG), OBS_GROUP_NORMAL, prs);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-06 13:08:23 +02:00
|
|
|
{
|
|
|
|
|
auto p =
|
2019-07-22 05:02:44 +02:00
|
|
|
obs_properties_add_text(grp, P_FFMPEG_CUSTOMSETTINGS, TRANSLATE(P_FFMPEG_CUSTOMSETTINGS),
|
2019-07-06 13:08:23 +02:00
|
|
|
obs_text_type::OBS_TEXT_DEFAULT);
|
|
|
|
|
obs_property_set_long_description(p, TRANSLATE(DESC(P_FFMPEG_CUSTOMSETTINGS)));
|
|
|
|
|
}
|
2019-07-07 14:44:08 +02:00
|
|
|
if (this->avcodec_ptr->pix_fmts) {
|
2019-07-22 05:02:44 +02:00
|
|
|
auto p = obs_properties_add_list(grp, P_FFMPEG_COLORFORMAT, TRANSLATE(P_FFMPEG_COLORFORMAT),
|
2019-07-07 14:26:51 +02:00
|
|
|
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
|
|
|
|
obs_property_set_long_description(p, TRANSLATE(DESC(P_FFMPEG_COLORFORMAT)));
|
2019-07-10 21:02:12 +02:00
|
|
|
obs_property_list_add_int(p, TRANSLATE(P_AUTOMATIC), static_cast<int64_t>(AV_PIX_FMT_NONE));
|
2019-07-07 14:26:51 +02:00
|
|
|
for (auto ptr = this->avcodec_ptr->pix_fmts; *ptr != AV_PIX_FMT_NONE; ptr++) {
|
2019-07-10 21:02:12 +02:00
|
|
|
obs_property_list_add_int(p, ffmpeg::tools::get_pixel_format_name(*ptr),
|
|
|
|
|
static_cast<int64_t>(*ptr));
|
2019-07-07 14:26:51 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-07-22 05:02:44 +02:00
|
|
|
if (this->avcodec_ptr->capabilities & (AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS)) {
|
|
|
|
|
auto p = obs_properties_add_int_slider(grp, P_FFMPEG_THREADS, TRANSLATE(P_FFMPEG_THREADS), 0,
|
2019-07-07 14:33:58 +02:00
|
|
|
std::thread::hardware_concurrency() * 2, 1);
|
|
|
|
|
obs_property_set_long_description(p, TRANSLATE(DESC(P_FFMPEG_THREADS)));
|
|
|
|
|
}
|
2019-07-06 13:08:23 +02:00
|
|
|
{
|
2019-07-22 05:02:44 +02:00
|
|
|
auto p = obs_properties_add_list(grp, P_FFMPEG_STANDARDCOMPLIANCE,
|
2019-07-06 13:08:23 +02:00
|
|
|
TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE), OBS_COMBO_TYPE_LIST,
|
|
|
|
|
OBS_COMBO_FORMAT_INT);
|
|
|
|
|
obs_property_set_long_description(p, TRANSLATE(DESC(P_FFMPEG_STANDARDCOMPLIANCE)));
|
|
|
|
|
obs_property_list_add_int(p, TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE ".VeryStrict"),
|
|
|
|
|
FF_COMPLIANCE_VERY_STRICT);
|
|
|
|
|
obs_property_list_add_int(p, TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE ".Strict"),
|
|
|
|
|
FF_COMPLIANCE_STRICT);
|
|
|
|
|
obs_property_list_add_int(p, TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE ".Normal"),
|
|
|
|
|
FF_COMPLIANCE_NORMAL);
|
|
|
|
|
obs_property_list_add_int(p, TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE ".Unofficial"),
|
|
|
|
|
FF_COMPLIANCE_UNOFFICIAL);
|
|
|
|
|
obs_property_list_add_int(p, TRANSLATE(P_FFMPEG_STANDARDCOMPLIANCE ".Experimental"),
|
|
|
|
|
FF_COMPLIANCE_EXPERIMENTAL);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AVCodec* encoder::generic_factory::get_avcodec()
|
|
|
|
|
{
|
|
|
|
|
return this->avcodec_ptr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 00:34:56 +02:00
|
|
|
encoder::generic::generic(obs_data_t* settings, obs_encoder_t* encoder)
|
|
|
|
|
: self(encoder), lag_in_frames(0), frame_count(0)
|
2019-07-06 13:08:23 +02:00
|
|
|
{
|
|
|
|
|
this->factory = reinterpret_cast<generic_factory*>(obs_encoder_get_type_data(self));
|
|
|
|
|
|
|
|
|
|
// Verify that the codec actually still exists.
|
2019-07-07 13:43:02 +02:00
|
|
|
this->codec = avcodec_find_encoder_by_name(this->factory->get_avcodec()->name);
|
2019-07-06 13:08:23 +02:00
|
|
|
if (!this->codec) {
|
|
|
|
|
PLOG_ERROR("Failed to find encoder for codec '%s'.", this->factory->get_avcodec()->name);
|
|
|
|
|
throw std::runtime_error("failed to find codec");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize context.
|
|
|
|
|
this->context = avcodec_alloc_context3(this->codec);
|
|
|
|
|
if (!this->context) {
|
|
|
|
|
PLOG_ERROR("Failed to create context for encoder '%s'.", this->codec->name);
|
|
|
|
|
throw std::runtime_error("failed to create context");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Settings
|
|
|
|
|
/// Rate Control
|
2019-07-07 00:34:56 +02:00
|
|
|
this->context->strict_std_compliance =
|
|
|
|
|
static_cast<int>(obs_data_get_int(settings, P_FFMPEG_STANDARDCOMPLIANCE));
|
|
|
|
|
this->context->debug = 0;
|
2019-07-06 13:08:23 +02:00
|
|
|
/// Threading
|
2019-07-22 05:02:44 +02:00
|
|
|
if (this->codec->capabilities
|
|
|
|
|
& (AV_CODEC_CAP_AUTO_THREADS | AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS)) {
|
|
|
|
|
if (this->codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
|
|
|
|
|
this->context->thread_type |= FF_THREAD_FRAME;
|
|
|
|
|
}
|
|
|
|
|
if (this->codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
|
|
|
|
|
this->context->thread_type |= FF_THREAD_SLICE;
|
|
|
|
|
}
|
|
|
|
|
int64_t threads = obs_data_get_int(settings, P_FFMPEG_THREADS);
|
|
|
|
|
if (threads > 0) {
|
|
|
|
|
this->context->thread_count = static_cast<int>(threads);
|
|
|
|
|
this->lag_in_frames = this->context->thread_count;
|
|
|
|
|
} else {
|
|
|
|
|
this->context->thread_count = std::thread::hardware_concurrency();
|
|
|
|
|
this->lag_in_frames = this->context->thread_count;
|
|
|
|
|
}
|
2019-07-06 13:08:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Video and Audio exclusive setup
|
|
|
|
|
if (this->codec->type == AVMEDIA_TYPE_VIDEO) {
|
|
|
|
|
// FFmpeg Video Settings
|
|
|
|
|
auto encvideo = obs_encoder_video(this->self);
|
|
|
|
|
auto voi = video_output_get_info(encvideo);
|
2019-07-06 14:22:15 +02:00
|
|
|
|
|
|
|
|
// Resolution
|
|
|
|
|
this->context->width = voi->width;
|
|
|
|
|
this->context->height = voi->height;
|
|
|
|
|
this->swscale.set_source_size(this->context->width, this->context->height);
|
|
|
|
|
this->swscale.set_target_size(this->context->width, this->context->height);
|
|
|
|
|
|
|
|
|
|
// Color
|
|
|
|
|
this->context->colorspace = ffmpeg::tools::obs_videocolorspace_to_avcolorspace(voi->colorspace);
|
|
|
|
|
this->context->color_range = ffmpeg::tools::obs_videorangetype_to_avcolorrange(voi->range);
|
|
|
|
|
this->context->field_order = AV_FIELD_PROGRESSIVE;
|
|
|
|
|
this->swscale.set_source_color(this->context->color_range, this->context->colorspace);
|
|
|
|
|
this->swscale.set_target_color(this->context->color_range, this->context->colorspace);
|
|
|
|
|
|
|
|
|
|
// Pixel Format
|
|
|
|
|
{
|
|
|
|
|
// Due to unsupported color formats and ffmpeg not automatically converting formats from A to B,
|
|
|
|
|
// we have to detect the closest format that we can still use and initialize our swscale instance
|
|
|
|
|
// these formats. This has a massive cost attached unfortunately.
|
|
|
|
|
|
|
|
|
|
AVPixelFormat source = ffmpeg::tools::obs_videoformat_to_avpixelformat(voi->format);
|
|
|
|
|
AVPixelFormat target = AV_PIX_FMT_NONE;
|
|
|
|
|
|
|
|
|
|
int loss = 0;
|
|
|
|
|
target = avcodec_find_best_pix_fmt_of_list(this->codec->pix_fmts, source, false, &loss);
|
|
|
|
|
|
|
|
|
|
this->context->pix_fmt = target;
|
|
|
|
|
this->swscale.set_source_format(source);
|
|
|
|
|
this->swscale.set_target_format(this->context->pix_fmt);
|
2019-07-07 14:26:51 +02:00
|
|
|
|
|
|
|
|
PLOG_INFO("Automatically detected target format '%s' for source format '%s'.",
|
|
|
|
|
ffmpeg::tools::get_pixel_format_name(target),
|
|
|
|
|
ffmpeg::tools::get_pixel_format_name(source));
|
|
|
|
|
}
|
2019-07-10 21:02:12 +02:00
|
|
|
AVPixelFormat color_format_override =
|
|
|
|
|
static_cast<AVPixelFormat>(obs_data_get_int(settings, P_FFMPEG_COLORFORMAT));
|
|
|
|
|
if (color_format_override != AV_PIX_FMT_NONE) {
|
2019-07-07 14:26:51 +02:00
|
|
|
// User specified override for color format.
|
2019-07-10 21:02:12 +02:00
|
|
|
this->context->pix_fmt = color_format_override;
|
2019-07-07 14:26:51 +02:00
|
|
|
this->swscale.set_target_format(this->context->pix_fmt);
|
|
|
|
|
PLOG_INFO("User specified target format override '%s'.",
|
|
|
|
|
ffmpeg::tools::get_pixel_format_name(this->context->pix_fmt));
|
2019-07-06 14:22:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Framerate
|
2019-07-21 13:07:30 +02:00
|
|
|
this->context->time_base.num = voi->fps_den;
|
|
|
|
|
this->context->time_base.den = voi->fps_num;
|
2019-07-06 13:08:23 +02:00
|
|
|
this->context->ticks_per_frame = 1;
|
|
|
|
|
} else if (this->codec->type == AVMEDIA_TYPE_AUDIO) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update settings
|
|
|
|
|
this->update(settings);
|
|
|
|
|
|
|
|
|
|
// Initialize
|
|
|
|
|
int res = avcodec_open2(this->context, this->codec, NULL);
|
|
|
|
|
if (res < 0) {
|
|
|
|
|
PLOG_ERROR("Failed to initialize encoder '%s' due to error code %lld: %s", this->codec->name, res,
|
|
|
|
|
ffmpeg::tools::get_error_description(res));
|
|
|
|
|
throw std::runtime_error(ffmpeg::tools::get_error_description(res));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Video/Audio exclusive setup part 2.
|
|
|
|
|
if (this->codec->type == AVMEDIA_TYPE_VIDEO) {
|
|
|
|
|
// Create Scaler
|
|
|
|
|
if (!swscale.initialize(SWS_FAST_BILINEAR)) {
|
|
|
|
|
PLOG_ERROR(
|
|
|
|
|
" Failed to initialize Software Scaler for pixel format '%s' with color space '%s' and "
|
|
|
|
|
"range '%s'.",
|
|
|
|
|
ffmpeg::tools::get_pixel_format_name(this->context->pix_fmt),
|
|
|
|
|
ffmpeg::tools::get_color_space_name(this->context->colorspace),
|
2019-07-06 14:22:15 +02:00
|
|
|
this->swscale.is_source_full_range() ? "Full" : "Partial");
|
2019-07-06 13:08:23 +02:00
|
|
|
throw std::runtime_error("failed to initialize swscaler.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create Frame queue
|
2019-07-06 14:22:15 +02:00
|
|
|
this->frame_queue.set_pixel_format(this->context->pix_fmt);
|
|
|
|
|
this->frame_queue.set_resolution(this->context->width, this->context->height);
|
2019-07-07 14:26:51 +02:00
|
|
|
this->frame_queue.precache(std::thread::hardware_concurrency() / 4);
|
2019-07-06 13:08:23 +02:00
|
|
|
} else if (this->codec->type == AVMEDIA_TYPE_AUDIO) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create Packet
|
|
|
|
|
this->current_packet = av_packet_alloc();
|
|
|
|
|
if (!this->current_packet) {
|
|
|
|
|
PLOG_ERROR("Failed to allocate packet storage.");
|
|
|
|
|
throw std::runtime_error("Failed to allocate packet storage.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
encoder::generic::~generic()
|
|
|
|
|
{
|
|
|
|
|
this->frame_queue.clear();
|
|
|
|
|
this->frame_queue_used.clear();
|
|
|
|
|
this->swscale.finalize();
|
|
|
|
|
|
|
|
|
|
if (this->context) {
|
|
|
|
|
avcodec_close(this->context);
|
|
|
|
|
avcodec_free_context(&this->context);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 13:43:02 +02:00
|
|
|
void encoder::generic::get_properties(obs_properties_t* props)
|
|
|
|
|
{
|
|
|
|
|
{ // Handler
|
|
|
|
|
auto ptr = obsffmpeg::find_codec_handler(this->codec->name);
|
|
|
|
|
if (ptr) {
|
|
|
|
|
ptr->get_properties(props, this->codec, this->context);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-07 14:44:08 +02:00
|
|
|
|
|
|
|
|
obs_property_set_enabled(obs_properties_get(props, P_FFMPEG_COLORFORMAT), false);
|
|
|
|
|
obs_property_set_enabled(obs_properties_get(props, P_FFMPEG_THREADS), false);
|
|
|
|
|
obs_property_set_enabled(obs_properties_get(props, P_FFMPEG_STANDARDCOMPLIANCE), false);
|
2019-07-07 13:43:02 +02:00
|
|
|
}
|
2019-07-06 13:08:23 +02:00
|
|
|
|
2019-07-07 01:12:06 +02:00
|
|
|
bool encoder::generic::update(obs_data_t* settings)
|
2019-07-06 13:08:23 +02:00
|
|
|
{
|
2019-07-07 13:43:02 +02:00
|
|
|
{ // Handler
|
|
|
|
|
auto ptr = obsffmpeg::find_codec_handler(this->codec->name);
|
|
|
|
|
if (ptr) {
|
|
|
|
|
ptr->update(settings, this->codec, this->context);
|
2019-07-07 01:12:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 13:43:02 +02:00
|
|
|
{ // FFmpeg
|
|
|
|
|
// Apply custom options.
|
2019-07-20 23:38:45 +02:00
|
|
|
av_opt_set_from_string(this->context->priv_data, obs_data_get_string(settings, P_FFMPEG_CUSTOMSETTINGS),
|
|
|
|
|
nullptr, "=", ";");
|
2019-07-07 13:43:02 +02:00
|
|
|
}
|
2019-07-06 13:08:23 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 12:14:46 +02:00
|
|
|
void encoder::generic::get_audio_info(audio_convert_info*) {}
|
|
|
|
|
|
|
|
|
|
size_t encoder::generic::get_frame_size()
|
2019-07-06 13:08:23 +02:00
|
|
|
{
|
2019-07-07 12:14:46 +02:00
|
|
|
return size_t();
|
|
|
|
|
}
|
2019-07-06 14:22:15 +02:00
|
|
|
|
2019-07-07 12:14:46 +02:00
|
|
|
bool encoder::generic::audio_encode(encoder_frame*, encoder_packet*, bool*)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-07-07 00:34:56 +02:00
|
|
|
|
2019-07-07 12:14:46 +02:00
|
|
|
void encoder::generic::get_video_info(video_scale_info*) {}
|
2019-07-07 00:34:56 +02:00
|
|
|
|
2019-07-07 12:14:46 +02:00
|
|
|
bool encoder::generic::get_sei_data(uint8_t**, size_t*)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool encoder::generic::get_extra_data(uint8_t** extra_data, size_t* size)
|
|
|
|
|
{
|
|
|
|
|
if (!this->context->extradata) {
|
|
|
|
|
return false;
|
2019-07-07 00:34:56 +02:00
|
|
|
}
|
2019-07-07 12:14:46 +02:00
|
|
|
*extra_data = this->context->extradata;
|
|
|
|
|
*size = this->context->extradata_size;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-07-07 00:34:56 +02:00
|
|
|
|
2019-07-22 14:00:28 +02:00
|
|
|
static inline void copy_data(encoder_frame* frame, AVFrame* vframe)
|
|
|
|
|
{
|
|
|
|
|
int h_chroma_shift, v_chroma_shift;
|
|
|
|
|
av_pix_fmt_get_chroma_sub_sample(static_cast<AVPixelFormat>(vframe->format), &h_chroma_shift, &v_chroma_shift);
|
|
|
|
|
|
|
|
|
|
for (size_t idx = 0; (idx < MAX_AV_PLANES) && (idx < AV_NUM_DATA_POINTERS); idx++) {
|
|
|
|
|
if (!frame->data[idx] || !vframe->data[idx])
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
size_t plane_height = vframe->height >> (idx ? v_chroma_shift : 0);
|
|
|
|
|
|
|
|
|
|
if (static_cast<uint32_t>(vframe->linesize[idx]) == frame->linesize[idx]) {
|
|
|
|
|
std::memcpy(vframe->data[idx], frame->data[idx], frame->linesize[idx] * plane_height);
|
|
|
|
|
} else {
|
|
|
|
|
size_t ls_in = frame->linesize[idx];
|
|
|
|
|
size_t ls_out = vframe->linesize[idx];
|
|
|
|
|
size_t bytes = ls_in < ls_out ? ls_in : ls_out;
|
|
|
|
|
|
|
|
|
|
uint8_t* to = vframe->data[idx];
|
|
|
|
|
uint8_t* from = frame->data[idx];
|
|
|
|
|
|
|
|
|
|
for (size_t y = 0; y < plane_height; y++) {
|
|
|
|
|
std::memcpy(to, from, bytes);
|
|
|
|
|
to += ls_out;
|
|
|
|
|
from += ls_in;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 12:14:46 +02:00
|
|
|
bool encoder::generic::video_encode(encoder_frame* frame, encoder_packet* packet, bool* received_packet)
|
|
|
|
|
{
|
|
|
|
|
// Convert frame.
|
|
|
|
|
std::shared_ptr<AVFrame> vframe = frame_queue.pop(); // Retrieve an empty frame.
|
|
|
|
|
{
|
|
|
|
|
ScopeProfiler profile("convert");
|
|
|
|
|
|
2019-07-22 14:00:28 +02:00
|
|
|
vframe->height = this->context->height;
|
|
|
|
|
vframe->format = this->context->pix_fmt;
|
2019-07-07 12:14:46 +02:00
|
|
|
vframe->color_range = this->context->color_range;
|
|
|
|
|
vframe->colorspace = this->context->colorspace;
|
|
|
|
|
|
2019-07-22 14:00:28 +02:00
|
|
|
if ((swscale.is_source_full_range() == swscale.is_target_full_range())
|
|
|
|
|
&& (swscale.get_source_colorspace() == swscale.get_target_colorspace())
|
|
|
|
|
&& (swscale.get_source_format() == swscale.get_target_format())) {
|
|
|
|
|
copy_data(frame, vframe.get());
|
|
|
|
|
} else {
|
|
|
|
|
int res = swscale.convert(reinterpret_cast<uint8_t**>(frame->data),
|
|
|
|
|
reinterpret_cast<int*>(frame->linesize), 0, this->context->height,
|
|
|
|
|
vframe->data, vframe->linesize);
|
|
|
|
|
if (res <= 0) {
|
|
|
|
|
PLOG_ERROR("Failed to convert frame: %s (%ld).",
|
|
|
|
|
ffmpeg::tools::get_error_description(res), res);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-07-07 12:14:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 13:50:47 +02:00
|
|
|
// Send and receive frames.
|
2019-07-07 00:34:56 +02:00
|
|
|
{
|
2019-07-07 13:50:47 +02:00
|
|
|
ScopeProfiler profile("loop");
|
|
|
|
|
bool sent_frame = false;
|
|
|
|
|
bool recv_packet = false;
|
|
|
|
|
bool should_lag = (lag_in_frames - frame_count) <= 0;
|
|
|
|
|
|
2019-07-07 14:26:51 +02:00
|
|
|
auto loop_begin = std::chrono::high_resolution_clock::now();
|
|
|
|
|
auto loop_end = loop_begin + std::chrono::milliseconds(50);
|
|
|
|
|
|
|
|
|
|
while ((!sent_frame || (should_lag && !recv_packet))
|
|
|
|
|
&& !(std::chrono::high_resolution_clock::now() > loop_end)) {
|
2019-07-07 15:07:09 +02:00
|
|
|
bool eagain_is_stupid = false;
|
|
|
|
|
|
2019-07-07 13:50:47 +02:00
|
|
|
if (!sent_frame) {
|
|
|
|
|
ScopeProfiler profile_inner("send");
|
|
|
|
|
|
|
|
|
|
vframe->pts = frame->pts;
|
|
|
|
|
|
|
|
|
|
int res = send_frame(vframe);
|
|
|
|
|
switch (res) {
|
|
|
|
|
case 0:
|
|
|
|
|
sent_frame = true;
|
2019-07-07 14:26:51 +02:00
|
|
|
frame_count++;
|
2019-07-07 13:50:47 +02:00
|
|
|
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) {
|
2019-07-07 15:07:09 +02:00
|
|
|
PLOG_WARNING(
|
2019-07-07 13:50:47 +02:00
|
|
|
"Skipped frame due to EAGAIN when a packet was already returned.");
|
|
|
|
|
sent_frame = true;
|
2019-07-07 14:26:51 +02:00
|
|
|
frame_count++;
|
2019-07-07 13:50:47 +02:00
|
|
|
}
|
2019-07-07 15:07:09 +02:00
|
|
|
eagain_is_stupid = true;
|
2019-07-07 13:50:47 +02:00
|
|
|
break;
|
|
|
|
|
case AVERROR(EOF):
|
|
|
|
|
PLOG_ERROR("Skipped frame due to end of stream.");
|
|
|
|
|
sent_frame = true;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
PLOG_ERROR("Failed to encode frame: %s (%ld).",
|
|
|
|
|
ffmpeg::tools::get_error_description(res), res);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-07-07 00:34:56 +02:00
|
|
|
}
|
2019-07-06 14:22:15 +02:00
|
|
|
|
2019-07-07 14:26:51 +02:00
|
|
|
if (!recv_packet) {
|
2019-07-07 13:50:47 +02:00
|
|
|
ScopeProfiler profile_inner("recieve");
|
|
|
|
|
|
|
|
|
|
int res = receive_packet(received_packet, packet);
|
|
|
|
|
switch (res) {
|
|
|
|
|
case 0:
|
2019-07-07 14:26:51 +02:00
|
|
|
recv_packet = true;
|
|
|
|
|
break;
|
2019-07-07 13:50:47 +02:00
|
|
|
case AVERROR(EOF):
|
2019-07-07 14:26:51 +02:00
|
|
|
PLOG_ERROR("Received end of file.");
|
2019-07-07 13:50:47 +02:00
|
|
|
recv_packet = true;
|
|
|
|
|
break;
|
|
|
|
|
case AVERROR(EAGAIN):
|
|
|
|
|
if (sent_frame) {
|
|
|
|
|
recv_packet = true;
|
2019-07-07 15:07:09 +02:00
|
|
|
}
|
|
|
|
|
if (eagain_is_stupid) {
|
|
|
|
|
PLOG_ERROR("Both send and recieve returned EAGAIN, encoder is broken.");
|
|
|
|
|
return false;
|
2019-07-07 13:50:47 +02:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
PLOG_ERROR("Failed to receive packet: %s (%ld).",
|
|
|
|
|
ffmpeg::tools::get_error_description(res), res);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-07-06 14:22:15 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-07 13:50:47 +02:00
|
|
|
if (!sent_frame || !recv_packet) {
|
2019-07-07 00:34:56 +02:00
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
|
|
}
|
2019-07-06 14:22:15 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2019-07-06 13:08:23 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-07 00:34:56 +02:00
|
|
|
bool encoder::generic::video_encode_texture(uint32_t, int64_t, uint64_t, uint64_t*, encoder_packet*, bool*)
|
2019-07-06 13:08:23 +02:00
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-07-07 00:34:56 +02:00
|
|
|
|
|
|
|
|
int encoder::generic::receive_packet(bool* received_packet, struct encoder_packet* packet)
|
|
|
|
|
{
|
|
|
|
|
int res = avcodec_receive_packet(this->context, this->current_packet);
|
|
|
|
|
if (res == 0) {
|
|
|
|
|
packet->type = OBS_ENCODER_VIDEO;
|
|
|
|
|
packet->pts = this->current_packet->pts;
|
2019-07-22 08:59:32 +02:00
|
|
|
packet->dts = this->current_packet->dts;
|
2019-07-07 00:34:56 +02:00
|
|
|
packet->data = this->current_packet->data;
|
|
|
|
|
packet->size = this->current_packet->size;
|
|
|
|
|
packet->keyframe = !!(this->current_packet->flags & AV_PKT_FLAG_KEY);
|
|
|
|
|
packet->drop_priority = 0;
|
|
|
|
|
*received_packet = true;
|
|
|
|
|
|
|
|
|
|
{
|
2019-07-07 12:14:46 +02:00
|
|
|
std::shared_ptr<AVFrame> uframe = frame_queue_used.pop_only();
|
2019-07-07 14:26:51 +02:00
|
|
|
frame_queue.push(uframe);
|
2019-07-07 00:34:56 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 12:14:46 +02:00
|
|
|
int encoder::generic::send_frame(std::shared_ptr<AVFrame> frame)
|
2019-07-07 00:34:56 +02:00
|
|
|
{
|
2019-07-07 12:14:46 +02:00
|
|
|
int res = avcodec_send_frame(this->context, frame.get());
|
2019-07-07 00:34:56 +02:00
|
|
|
switch (res) {
|
|
|
|
|
case 0:
|
|
|
|
|
frame_count++;
|
|
|
|
|
case AVERROR(EAGAIN):
|
|
|
|
|
case AVERROR(EOF):
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|