34#include <libavutil/parseutils.h>
49using namespace std::literals;
62 JAMI_DBG(
"[%p] New instance created",
this);
68 if (outputCtx_->priv_data && outputCtx_->pb)
86 JAMI_DBG(
"[%p] Instance destroyed",
this);
92 if (!
opts.isValid()) {
118 if (
args.payload_type
126 if (
not args.parameters.empty())
130 linkableHW_ =
args.linkableHW;
131 fecEnabled_ =
args.fecEnabled;
139 if (
not description.empty())
167 format.empty() ?
nullptr : format.c_str(),
184 if (stream ==
nullptr) {
189 JAMI_DBG(
"[%p] Created new coding instance for %s @ index %d",
this,
systemCodecInfo.name.c_str(), stream->index);
219 JAMI_DBG(
"[%p] Initializing stream: codec type %d, name %s, lib %s",
225 std::lock_guard
lk(encMutex_);
228 throw MediaEncoderException(
"Unable to allocate stream");
231 if (outputCtx_->nb_streams == 0) {
232 JAMI_ERR(
"[%p] Unable to init, output context has no coding sessions!",
this);
233 throw MediaEncoderException(
"Unable to init, output context has no coding sessions!");
244 throw MediaEncoderException(
"Unsuported media type");
249 for (
unsigned i = 0;
i < outputCtx_->nb_streams;
i++) {
250 stream = outputCtx_->streams[
i];
251 if (stream->codecpar->codec_type == mediaType) {
260 if (stream ==
nullptr) {
261 JAMI_ERR(
"[%p] Unable to init, output context has no coding sessions for %s",
264 throw MediaEncoderException(
"Unable to allocate stream");
267 currentStreamIdx_ = stream->index;
275 for (
const auto&
it :
APIs) {
276 accel_ = std::make_unique<video::HardwareAccel>(
it);
297 JAMI_WARN(
"Fail to open hardware encoder %s with %s ",
299 it.getName().c_str());
306 JAMI_WARN(
"Using hardware encoding for %s with %s ",
308 it.getName().c_str());
317 JAMI_WARN(
"Not using hardware encoding for %s",
323 throw MediaEncoderException(
"Unable to open encoder");
329 stream->avg_frame_rate =
encoderCtx->framerate;
341 format =
accel_->getSoftwareFormat();
345 if (scaledFrameBufferSize_ < 0)
346 throw MediaEncoderException(
349 throw MediaEncoderException(
"buffer too small");
351 scaledFrameBuffer_.resize(scaledFrameBufferSize_);
353 scaledFrame_->setFromMemory(scaledFrameBuffer_.data(), format, width, height);
357 return stream->index;
361MediaEncoder::openIOContext()
364 outputCtx_->pb = ioCtx_;
365 outputCtx_->packet_size = outputCtx_->pb->buffer_size;
368#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
369 const char*
filename = outputCtx_->url;
371 const char*
filename = outputCtx_->filename;
376 throw MediaEncoderException(
384MediaEncoder::startIO()
389 JAMI_ERR(
"Unable to write header for output file... check codec parameters");
390 throw MediaEncoderException(
"Failed to write output file header");
393#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
405 auto width = (input->width() >> 3) << 3;
406 auto height = (input->height() >> 3) << 3;
413 initStream(
videoCodec_, input->pointer()->hw_frames_ctx);
417 std::shared_ptr<VideoFrame>
output;
420 JAMI_ERR(
"Fail to get hardware frame");
435 if (
enc->framerate.num !=
enc->time_base.den ||
enc->framerate.den !=
enc->time_base.num)
460 frame.pointer()->pts = sent_samples;
461 sent_samples +=
frame.pointer()->nb_samples;
469 if (!initialized_ &&
frame) {
471 bool isVideo = (
frame->width > 0 &&
frame->height > 0);
484 if (
static_cast<size_t>(
streamIdx) >= encoders_.size())
528 and static_cast<unsigned int>(
streamIdx) < outputCtx_->nb_streams) {
548 for (
size_t i = 0;
i < outputCtx_->nb_streams; ++
i) {
549 if (
encode(
nullptr,
static_cast<int>(
i)) < 0) {
550 JAMI_ERR() <<
"Unable to flush stream #" <<
i;
561 const auto sdp_size = outputCtx_->streams[currentStreamIdx_]->codecpar->extradata_size + 2048;
571 result +=
line.substr(0,
line.length() - 1);
575 JAMI_DBG(
"Sending SDP:\n%s", result.c_str());
587 encoderCtx->thread_count = std::min(
static_cast<int>(std::thread::hardware_concurrency()),
is_video ? 16 : 4);
609#if defined(TARGET_OS_IOS) && TARGET_OS_IOS
612#elif !defined(__APPLE__)
654 JAMI_WARN(
"Failed to set preset to 'fast'");
656 JAMI_WARN(
"Failed to set level to 'auto'");
658 JAMI_WARN(
"Failed to set zerolatency to '1'");
662#if (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
669 const char*
tune =
"zerolatency";
676MediaEncoder::extractProfileLevelID(
const std::string& parameters,
AVCodecContext* ctx)
684 if (parameters.empty())
687 const std::string
target(
"profile-level-id=");
689 if (
needle == std::string::npos)
699 std::stringstream
ss;
701 ss >> std::hex >> result;
704 const unsigned char profile_iop = ((result >> 8) & 0xff);
705 ctx->level = result & 0xff;
720 JAMI_DBG(
"Unrecognized H264 profile byte");
722 JAMI_DBG(
"Using profile %s (%x) and level %d",
736 for (
auto*
enc : encoders_)
762 ms.format =
accel_->getSoftwareFormat();
770 if (
auto*
ctx = getCurrentAudioAVCtx()) {
771 return ctx->frame_size;
779 outputCodec_ =
nullptr;
787 JAMI_WARN() <<
"Hardware encoding disabled";
802 throw MediaEncoderException(
"No output encoder");
852 std::lock_guard
lk(encMutex_);
886 std::lock_guard
lk(encMutex_);
897 pl = std::clamp((
int)
pl, 0, 100);
922 JAMI_DEBUG(
"H264 encoder setup: crf={:d}, maxrate={:d} kbit/s, bufsize={:d} kbit",
933 JAMI_DEBUG(
"H264 encoder setup cbr: bitrate={:d} kbit/s",
br);
952 JAMI_DEBUG(
"H265 encoder setup: crf={:d}, maxrate={:d} kbit/s, bufsize={:d} kbit",
963 JAMI_DEBUG(
"H265 encoder setup cbr: bitrate={:d} kbit/s",
br);
1006 crf = std::clamp((
int)
crf, 4, 56);
1011 JAMI_DEBUG(
"VP8 encoder setup: crf={:d}, maxrate={:d}, bufsize={:d}",
crf, maxBitrate / 1000,
bufSize / 1000);
1019 int bufSize =
static_cast<int>(maxBitrate / 2);
1024 JAMI_DEBUG(
"MPEG4 encoder setup: maxrate={:d}, bufsize={:d}", maxBitrate,
bufSize);
1031 int bufSize =
static_cast<int>(maxBitrate) / 2;
1036 JAMI_DEBUG(
"H263 encoder setup: maxrate={:d}, bufsize={:d}", maxBitrate,
bufSize);
1050#ifdef ENABLE_HWACCEL
1051 float val =
static_cast<float>(
br) * 1000.0f * 0.8f;
1054 if (
accel_->getName() ==
"nvenc"sv) {
1056 }
else if (
accel_->getName() ==
"vaapi"sv) {
1060 }
else if (
accel_->getName() ==
"videotoolbox"sv) {
1062 }
else if (
accel_->getName() ==
"qsv"sv) {
1071MediaEncoder::getCurrentVideoAVCtx()
1073 for (
auto*
it : encoders_) {
1081MediaEncoder::getCurrentAudioAVCtx()
1083 for (
auto*
it : encoders_) {
1091MediaEncoder::stopEncoder()
1094 for (
auto it = encoders_.begin();
it != encoders_.end();
it++) {
1096 encoders_.erase(
it);
1109#ifdef ENABLE_HWACCEL
1111 return accel_->dynBitrate();
1135 if (std::filesystem::is_regular_file(path,
ec)) {
1136 JAMI_WARN(
"encoder.json file found, default settings will be erased");
1139 std::ifstream
file(path);
1141 if (!
root.isObject()) {
1142 JAMI_ERR() <<
"Invalid encoder configuration: root is not an object";
1145 const auto& config =
root[name];
1146 if (config.isNull()) {
1147 JAMI_WARN() <<
"Encoder '" << name <<
"' not found in configuration file";
1150 if (!config.isObject()) {
1151 JAMI_ERR() <<
"Invalid encoder configuration: '" << name <<
"' is not an object";
1154 for (Json::Value::const_iterator
it = config.begin();
it != config.end(); ++
it) {
1155 const Json::Value& v = *
it;
1156 if (!
it.key().isConvertibleTo(Json::ValueType::stringValue)
1157 || !v.isConvertibleTo(Json::ValueType::stringValue)) {
1158 JAMI_ERR() <<
"Invalid configuration for '" << name <<
"'";
1161 const auto& key =
it.key().asString();
1162 const auto& value = v.asString();
1168 JAMI_ERR() <<
"Failed to set option " << key <<
" in " << name
1172 }
catch (
const Json::Exception&
e) {
1173 JAMI_ERR() <<
"Failed to load encoder configuration file: " <<
e.what();
1181#ifdef ENABLE_HWACCEL
1186 std::unique_ptr<video::HardwareAccel>
accel;
1188 for (
const auto&
it :
APIs) {
1189 accel = std::make_unique<video::HardwareAccel>(
it);
1194 encoderCtx->thread_count =
static_cast<int>(std::min(std::thread::hardware_concurrency(), 16u));
1212 auto ret =
accel->initAPI(
false,
nullptr);
1222 JAMI_WARN(
"Fail to open hardware encoder H265 with %s ",
it.getName().c_str());
1232 return it.getName();
1242MediaEncoder::getHWFrame(
const std::shared_ptr<VideoFrame>& input, std::shared_ptr<VideoFrame>&
output)
1245#if defined(TARGET_OS_IOS) && TARGET_OS_IOS
1249 if (input->format() !=
pix) {
1250 output = scaler_.convertFormat(*input.get(),
pix);
1258#elif !defined(__APPLE__) && defined(ENABLE_HWACCEL)
1280 }
catch (
const std::runtime_error&
e) {
1281 JAMI_ERR(
"Accel failure: %s",
e.what());
1288#ifdef ENABLE_HWACCEL
1289std::shared_ptr<VideoFrame>
1290MediaEncoder::getUnlinkedHWFrame(
const VideoFrame& input)
1302std::shared_ptr<VideoFrame>
1303MediaEncoder::getHWFrameFromSWFrame(
const VideoFrame& input)
1305 std::shared_ptr<VideoFrame>
framePtr;
1307 if (input.format() !=
pix) {
1317std::shared_ptr<VideoFrame>
1318MediaEncoder::getScaledSWFrame(
const VideoFrame& input)
1334 initialized_ =
false;
1347 }
catch (
const std::exception&
e) {
static LIBJAMI_TEST_EXPORT Manager & instance()
constexpr I denominator() const
constexpr I numerator() const
static std::list< HardwareAccel > getCompatibleAccel(AVCodecID id, int width, int height, CodecType type)
static std::unique_ptr< VideoFrame > transferToMainMemory(const VideoFrame &frame, AVPixelFormat desiredFormat)
Transfers hardware frame to main memory.
#define JAMI_DEBUG(formatstr,...)
#define JAMI_WARNING(formatstr,...)
const std::filesystem::path & get_config_dir()
void fillWithBlack(AVFrame *frame)
const char * getDictValue(const AVDictionary *d, const std::string &key, int flags)
void setDictValue(AVDictionary **d, const std::string &key, const std::string &value, int flags)
std::string getError(int err)
constexpr double LOGREG_PARAM_A
decltype(getGlobalInstance< SystemCodecContainer >) & getSystemCodecContainer
constexpr double LOGREG_PARAM_B
void emitSignal(Args... args)
libjami::VideoFrame VideoFrame
bool getline(std::string_view &str, std::string_view &line, char delim='\n')
Similar to @getline_full but skips empty results.
constexpr double LOGREG_PARAM_A_HEVC
constexpr double LOGREG_PARAM_B_HEVC
static constexpr unsigned DEFAULT_MAX_BITRATE
static constexpr unsigned DEFAULT_MIN_BITRATE
static constexpr unsigned DEFAULT_VIDEO_BITRATE