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",
222 JAMI_DBG(
"[%p] Initializing stream: codec type %d, name %s, lib %s",
228 std::lock_guard
lk(encMutex_);
231 throw MediaEncoderException(
"Unable to allocate stream");
234 if (outputCtx_->nb_streams == 0) {
235 JAMI_ERR(
"[%p] Unable to init, output context has no coding sessions!",
this);
236 throw MediaEncoderException(
"Unable to init, output context has no coding sessions!");
247 throw MediaEncoderException(
"Unsuported media type");
252 for (
unsigned i = 0;
i < outputCtx_->nb_streams;
i++) {
253 stream = outputCtx_->streams[
i];
254 if (stream->codecpar->codec_type == mediaType) {
263 if (stream ==
nullptr) {
264 JAMI_ERR(
"[%p] Unable to init, output context has no coding sessions for %s",
267 throw MediaEncoderException(
"Unable to allocate stream");
270 currentStreamIdx_ = stream->index;
279 for (
const auto&
it :
APIs) {
280 accel_ = std::make_unique<video::HardwareAccel>(
it);
303 JAMI_WARN(
"Fail to open hardware encoder %s with %s ",
305 it.getName().c_str());
312 JAMI_WARN(
"Using hardware encoding for %s with %s ",
314 it.getName().c_str());
323 JAMI_WARN(
"Not using hardware encoding for %s",
331 throw MediaEncoderException(
"Unable to open encoder");
337 stream->avg_frame_rate =
encoderCtx->framerate;
349 format =
accel_->getSoftwareFormat();
353 if (scaledFrameBufferSize_ < 0)
354 throw MediaEncoderException(
358 throw MediaEncoderException(
"buffer too small");
360 scaledFrameBuffer_.resize(scaledFrameBufferSize_);
362 scaledFrame_->setFromMemory(scaledFrameBuffer_.data(), format, width, height);
366 return stream->index;
370MediaEncoder::openIOContext()
373 outputCtx_->pb = ioCtx_;
374 outputCtx_->packet_size = outputCtx_->pb->buffer_size;
377#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
378 const char* filename = outputCtx_->url;
380 const char* filename = outputCtx_->filename;
385 throw MediaEncoderException(fmt::format(
"Unable to open IO context for '{}': {}", filename,
libav_utils::getError(
ret)));
392MediaEncoder::startIO()
397 JAMI_ERR(
"Unable to write header for output file... check codec parameters");
398 throw MediaEncoderException(
"Failed to write output file header");
401#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
415 auto width = (input->width() >> 3) << 3;
416 auto height = (input->height() >> 3) << 3;
423 initStream(
videoCodec_, input->pointer()->hw_frames_ctx);
427 std::shared_ptr<VideoFrame>
output;
430 JAMI_ERR(
"Fail to get hardware frame");
445 if (
enc->framerate.num !=
enc->time_base.den ||
enc->framerate.den !=
enc->time_base.num)
471 frame.pointer()->pts = sent_samples;
472 sent_samples +=
frame.pointer()->nb_samples;
480 if (!initialized_ &&
frame) {
482 bool isVideo = (
frame->width > 0 &&
frame->height > 0);
495 if (
static_cast<size_t>(
streamIdx) >= encoders_.size())
539 and static_cast<unsigned int>(
streamIdx) < outputCtx_->nb_streams) {
545 outputCtx_->streams[
streamIdx]->time_base);
549 outputCtx_->streams[
streamIdx]->time_base);
563 for (
size_t i = 0;
i < outputCtx_->nb_streams; ++
i) {
565 JAMI_ERR() <<
"Unable to flush stream #" <<
i;
576 const auto sdp_size = outputCtx_->streams[currentStreamIdx_]->codecpar->extradata_size + 2048;
586 result +=
line.substr(0,
line.length() - 1);
590 JAMI_DBG(
"Sending SDP:\n%s", result.c_str());
602 encoderCtx->thread_count = std::min(std::thread::hardware_concurrency(),
is_video ? 16u : 4u);
624#if defined(TARGET_OS_IOS) && TARGET_OS_IOS
627#elif !defined(__APPLE__)
666 JAMI_WARN(
"Failed to set preset to 'fast'");
668 JAMI_WARN(
"Failed to set level to 'auto'");
670 JAMI_WARN(
"Failed to set zerolatency to '1'");
674#if (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
681 const char*
tune =
"zerolatency";
688MediaEncoder::extractProfileLevelID(
const std::string& parameters,
AVCodecContext* ctx)
696 if (parameters.empty())
699 const std::string
target(
"profile-level-id=");
701 if (
needle == std::string::npos)
711 std::stringstream
ss;
713 ss >> std::hex >> result;
716 const unsigned char profile_iop = ((result >> 8) & 0xff);
717 ctx->level = result & 0xff;
732 JAMI_DBG(
"Using profile %s (%x) and level %d",
746 for (
auto enc : encoders_)
772 ms.format =
accel_->getSoftwareFormat();
780 outputCodec_ =
nullptr;
788 JAMI_WARN() <<
"Hardware encoding disabled";
803 throw MediaEncoderException(
"No output encoder");
813 JAMI_WARNING(
"Requested bitrate {:d} too low, setting to {:d}",
818 JAMI_WARNING(
"Requested bitrate {:d} too high, setting to {:d}",
857 std::lock_guard
lk(encMutex_);
891 std::lock_guard
lk(encMutex_);
902 pl = std::clamp((
int)
pl, 0, 100);
927 JAMI_DEBUG(
"H264 encoder setup: crf={:d}, maxrate={:d} kbit/s, bufsize={:d} kbit",
938 JAMI_DEBUG(
"H264 encoder setup cbr: bitrate={:d} kbit/s",
br);
958 JAMI_DEBUG(
"H265 encoder setup: crf={:d}, maxrate={:d} kbit/s, bufsize={:d} kbit",
968 JAMI_DEBUG(
"H265 encoder setup cbr: bitrate={:d} kbit/s",
br);
1013 crf = std::clamp((
int)
crf, 4, 56);
1018 JAMI_DEBUG(
"VP8 encoder setup: crf={:d}, maxrate={:d}, bufsize={:d}",
1034 JAMI_DEBUG(
"MPEG4 encoder setup: maxrate={:d}, bufsize={:d}", maxBitrate,
bufSize);
1046 JAMI_DEBUG(
"H263 encoder setup: maxrate={:d}, bufsize={:d}", maxBitrate,
bufSize);
1063 if (
accel_->getName() ==
"nvenc"sv) {
1065 }
else if (
accel_->getName() ==
"vaapi"sv) {
1069 }
else if (
accel_->getName() ==
"videotoolbox"sv) {
1071 }
else if (
accel_->getName() ==
"qsv"sv) {
1080MediaEncoder::getCurrentVideoAVCtx()
1082 for (
auto it : encoders_) {
1090MediaEncoder::getCurrentAudioAVCtx()
1092 for (
auto it : encoders_) {
1100MediaEncoder::stopEncoder()
1103 for (
auto it = encoders_.begin();
it != encoders_.end();
it++) {
1105 encoders_.erase(
it);
1120 return accel_->dynBitrate();
1144 if (std::filesystem::is_regular_file(path,
ec)) {
1145 JAMI_WARN(
"encoder.json file found, default settings will be erased");
1148 std::ifstream
file(path);
1150 if (!
root.isObject()) {
1151 JAMI_ERR() <<
"Invalid encoder configuration: root is not an object";
1154 const auto& config =
root[name];
1155 if (config.isNull()) {
1156 JAMI_WARN() <<
"Encoder '" << name <<
"' not found in configuration file";
1159 if (!config.isObject()) {
1160 JAMI_ERR() <<
"Invalid encoder configuration: '" << name <<
"' is not an object";
1163 for (Json::Value::const_iterator
it = config.begin();
it != config.end(); ++
it) {
1164 Json::Value v = *
it;
1165 if (!
it.key().isConvertibleTo(Json::ValueType::stringValue)
1166 || !v.isConvertibleTo(Json::ValueType::stringValue)) {
1167 JAMI_ERR() <<
"Invalid configuration for '" << name <<
"'";
1170 const auto& key =
it.key().asString();
1171 const auto&
value = v.asString();
1177 JAMI_ERR() <<
"Failed to set option " << key <<
" in " << name
1181 }
catch (
const Json::Exception&
e) {
1182 JAMI_ERR() <<
"Failed to load encoder configuration file: " <<
e.what();
1198 std::unique_ptr<video::HardwareAccel>
accel;
1200 for (
const auto&
it :
APIs) {
1201 accel = std::make_unique<video::HardwareAccel>(
it);
1206 encoderCtx->thread_count = std::min(std::thread::hardware_concurrency(), 16u);
1224 auto ret =
accel->initAPI(
false,
nullptr);
1234 JAMI_WARN(
"Fail to open hardware encoder H265 with %s ",
it.getName().c_str());
1244 return it.getName();
1254MediaEncoder::getHWFrame(
const std::shared_ptr<VideoFrame>& input,
1255 std::shared_ptr<VideoFrame>&
output)
1258#if defined(TARGET_OS_IOS) && TARGET_OS_IOS
1262 if (input->format() !=
pix) {
1263 output = scaler_.convertFormat(*input.get(),
pix);
1271#elif !defined(__APPLE__) && defined(RING_ACCEL)
1293 }
catch (
const std::runtime_error&
e) {
1294 JAMI_ERR(
"Accel failure: %s",
e.what());
1302std::shared_ptr<VideoFrame>
1303MediaEncoder::getUnlinkedHWFrame(
const VideoFrame& input)
1315std::shared_ptr<VideoFrame>
1316MediaEncoder::getHWFrameFromSWFrame(
const VideoFrame& input)
1318 std::shared_ptr<VideoFrame>
framePtr;
1320 if (input.format() !=
pix) {
1330std::shared_ptr<VideoFrame>
1331MediaEncoder::getScaledSWFrame(
const VideoFrame& input)
1347 initialized_ =
false;
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