diff --git a/src/media/media_encoder.cpp b/src/media/media_encoder.cpp index 1f872708d4c044778571956ce4ad645dd54852f9..b4c9270129958ca278010867d77e2b722e45a361 100644 --- a/src/media/media_encoder.cpp +++ b/src/media/media_encoder.cpp @@ -185,140 +185,26 @@ MediaEncoder::initStream(const std::string& codecName, AVBufferRef* framesCtx) int MediaEncoder::initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* framesCtx) { - AVCodec* outputCodec = nullptr; AVCodecContext* encoderCtx = nullptr; -#ifdef RING_ACCEL - if (systemCodecInfo.mediaType == MEDIA_VIDEO) { - if (enableAccel_) { - if (accel_ = video::HardwareAccel::setupEncoder( - static_cast<AVCodecID>(systemCodecInfo.avcodecId), - videoOpts_.width, videoOpts_.height, framesCtx)) { - outputCodec = avcodec_find_encoder_by_name(accel_->getCodecName().c_str()); - } - } else { - JAMI_WARN() << "Hardware encoding disabled"; - } - } -#endif + AVMediaType mediaType; - if (!outputCodec) { - /* find the video encoder */ - if (systemCodecInfo.avcodecId == AV_CODEC_ID_H263) - // For H263 encoding, we force the use of AV_CODEC_ID_H263P (H263-1998) - // H263-1998 can manage all frame sizes while H263 don't - // AV_CODEC_ID_H263 decoder will be used for decoding - outputCodec = avcodec_find_encoder(AV_CODEC_ID_H263P); - else - outputCodec = avcodec_find_encoder(static_cast<AVCodecID>(systemCodecInfo.avcodecId)); - if (!outputCodec) { - JAMI_ERR("Encoder \"%s\" not found!", systemCodecInfo.name.c_str()); - throw MediaEncoderException("No output encoder"); - } - } + if(systemCodecInfo.mediaType == MEDIA_VIDEO) + mediaType = AVMEDIA_TYPE_VIDEO; + else if(systemCodecInfo.mediaType == MEDIA_AUDIO) + mediaType = AVMEDIA_TYPE_AUDIO; - encoderCtx = prepareEncoderContext(outputCodec, systemCodecInfo.mediaType == MEDIA_VIDEO); - encoders_.push_back(encoderCtx); - -#ifdef RING_ACCEL - if (accel_) { - accel_->setDetails(encoderCtx); - encoderCtx->opaque = accel_.get(); - } -#endif - - uint64_t maxBitrate = std::atoi(libav_utils::getDictValue(options_, "max_rate")); - // Only clamp video bitrate - if (systemCodecInfo.mediaType == MEDIA_VIDEO && maxBitrate > 0) { - if (maxBitrate < SystemCodecInfo::DEFAULT_MIN_BITRATE) { - JAMI_WARN("Requested bitrate %lu too low, setting to %u", - maxBitrate, SystemCodecInfo::DEFAULT_MIN_BITRATE); - maxBitrate = SystemCodecInfo::DEFAULT_MIN_BITRATE; - } else if (maxBitrate > SystemCodecInfo::DEFAULT_MAX_BITRATE) { - JAMI_WARN("Requested bitrate %lu too high, setting to %u", - maxBitrate, SystemCodecInfo::DEFAULT_MAX_BITRATE); - maxBitrate = SystemCodecInfo::DEFAULT_MAX_BITRATE; - } - } - maxBitrate *= 1000; // convert to b/s for FFmpeg - uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + log(pow(maxBitrate, LOGREG_PARAM_B))); // CRF = A + B*ln(maxBitrate) - uint64_t bufSize = 2 * maxBitrate; - - /* let x264 preset override our encoder settings */ - if (systemCodecInfo.avcodecId == AV_CODEC_ID_H264) { - auto profileLevelId = libav_utils::getDictValue(options_, "parameters"); - extractProfileLevelID(profileLevelId, encoderCtx); -#ifdef RING_ACCEL -#ifdef ENABLE_VIDEOTOOLBOX - if (accel_) { - maxBitrate = 2000 * std::atoi(libav_utils::getDictValue(options_, "max_rate")); - bufSize = 2 * maxBitrate; - crf = 20; - } -#endif - if (accel_) - // limit the bitrate else it will easily go up to a few MiB/s - encoderCtx->bit_rate = maxBitrate; - else -#endif - forcePresetX264(encoderCtx); - // For H264 : - // Streaming => VBV (constrained encoding) + CRF (Constant Rate Factor) - if (crf == SystemCodecInfo::DEFAULT_NO_QUALITY) - crf = 30; // good value for H264-720p@30 - JAMI_DBG("H264 encoder setup: crf=%u, maxrate=%lu, bufsize=%lu", crf, maxBitrate, bufSize); - libav_utils::setDictValue(&options_, "crf", std::to_string(crf)); - av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN); - encoderCtx->rc_buffer_size = bufSize; - encoderCtx->rc_max_rate = maxBitrate; - } else if (systemCodecInfo.avcodecId == AV_CODEC_ID_VP8) { - // For VP8 : - // 1- if quality is set use it - // bitrate need to be set. The target bitrate becomes the maximum allowed bitrate - // 2- otherwise set rc_max_rate and rc_buffer_size - // Using information given on this page: - // http://www.webmproject.org/docs/encoder-parameters/ - av_opt_set(encoderCtx, "quality", "realtime", AV_OPT_SEARCH_CHILDREN); - av_opt_set_int(encoderCtx, "error-resilient", 1, AV_OPT_SEARCH_CHILDREN); - av_opt_set_int(encoderCtx, "cpu-used", 7, AV_OPT_SEARCH_CHILDREN); // value obtained from testing - av_opt_set_int(encoderCtx, "lag-in-frames", 0, AV_OPT_SEARCH_CHILDREN); - // allow encoder to drop frames if buffers are full and - // to undershoot target bitrate to lessen strain on resources - av_opt_set_int(encoderCtx, "drop-frame", 25, AV_OPT_SEARCH_CHILDREN); - av_opt_set_int(encoderCtx, "undershoot-pct", 95, AV_OPT_SEARCH_CHILDREN); - // don't set encoderCtx->gop_size: let libvpx decide when to insert a keyframe - encoderCtx->slices = 2; // VP8E_SET_TOKEN_PARTITIONS - encoderCtx->qmin = 4; - encoderCtx->qmax = 56; - encoderCtx->rc_buffer_size = maxBitrate; - encoderCtx->bit_rate = maxBitrate; - if (crf != SystemCodecInfo::DEFAULT_NO_QUALITY) { - av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN); - JAMI_DBG("Using quality factor %d", crf); - } else { - JAMI_DBG("Using Max bitrate %lu", maxBitrate); - } - } else if (systemCodecInfo.avcodecId == AV_CODEC_ID_MPEG4) { - // For MPEG4 : - // No CRF avaiable. - // Use CBR (set bitrate) - encoderCtx->rc_buffer_size = maxBitrate; - encoderCtx->bit_rate = encoderCtx->rc_min_rate = encoderCtx->rc_max_rate = maxBitrate; - JAMI_DBG("Using Max bitrate %lu", maxBitrate); - } else if (systemCodecInfo.avcodecId == AV_CODEC_ID_H263) { - encoderCtx->bit_rate = encoderCtx->rc_max_rate = maxBitrate; - encoderCtx->rc_buffer_size = maxBitrate; - JAMI_DBG("Using Max bitrate %lu", maxBitrate); - } + std::lock_guard<std::mutex> lk(encMutex_); + encoderCtx = initCodec(mediaType, static_cast<AVCodecID>(systemCodecInfo.avcodecId), framesCtx, 0); // add video stream to outputformat context - AVStream* stream = avformat_new_stream(outputCtx_, outputCodec); + AVStream* stream = avformat_new_stream(outputCtx_, outputCodec_); if (!stream) throw MediaEncoderException("Could not allocate stream"); currentStreamIdx_ = stream->index; readConfig(&options_, encoderCtx); - if (avcodec_open2(encoderCtx, outputCodec, &options_) < 0) + if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0) throw MediaEncoderException("Could not open encoder"); #ifndef _WIN32 @@ -757,6 +643,207 @@ MediaEncoder::getStream(const std::string& name, int streamIdx) const return ms; } +AVCodecContext* +MediaEncoder::initCodec(AVMediaType mediaType, AVCodecID avcodecId, AVBufferRef* framesCtx, uint64_t br) +{ + outputCodec_ = nullptr; +#ifdef RING_ACCEL + if (mediaType == AVMEDIA_TYPE_VIDEO) { + if (enableAccel_) { + if (accel_ = video::HardwareAccel::setupEncoder( + static_cast<AVCodecID>(avcodecId), + videoOpts_.width, videoOpts_.height, framesCtx)) { + outputCodec_ = avcodec_find_encoder_by_name(accel_->getCodecName().c_str()); + } + } else { + JAMI_WARN() << "Hardware encoding disabled"; + } + } +#endif + + if (!outputCodec_) { + /* find the video encoder */ + if (avcodecId == AV_CODEC_ID_H263) + // For H263 encoding, we force the use of AV_CODEC_ID_H263P (H263-1998) + // H263-1998 can manage all frame sizes while H263 don't + // AV_CODEC_ID_H263 decoder will be used for decoding + outputCodec_ = avcodec_find_encoder(AV_CODEC_ID_H263P); + else + outputCodec_ = avcodec_find_encoder(static_cast<AVCodecID>(avcodecId)); + if (!outputCodec_) { + throw MediaEncoderException("No output encoder"); + } + } + + AVCodecContext* encoderCtx = prepareEncoderContext(outputCodec_, mediaType == AVMEDIA_TYPE_VIDEO); + encoders_.push_back(encoderCtx); + +#ifdef RING_ACCEL + if (accel_) { + accel_->setDetails(encoderCtx); + encoderCtx->opaque = accel_.get(); + } +#endif + + if(!br) + br = std::atoi(libav_utils::getDictValue(options_, "max_rate")); + + // Only clamp video bitrate + if (mediaType == AVMEDIA_TYPE_VIDEO && br > 0) { + if (br < SystemCodecInfo::DEFAULT_MIN_BITRATE) { + JAMI_WARN("Requested bitrate %lu too low, setting to %u", + br, SystemCodecInfo::DEFAULT_MIN_BITRATE); + br = SystemCodecInfo::DEFAULT_MIN_BITRATE; + } else if (br > SystemCodecInfo::DEFAULT_MAX_BITRATE) { + JAMI_WARN("Requested bitrate %lu too high, setting to %u", + br, SystemCodecInfo::DEFAULT_MAX_BITRATE); + br = SystemCodecInfo::DEFAULT_MAX_BITRATE; + } + } + + /* let x264 preset override our encoder settings */ + if (avcodecId == AV_CODEC_ID_H264) { + auto profileLevelId = libav_utils::getDictValue(options_, "parameters"); + extractProfileLevelID(profileLevelId, encoderCtx); + forcePresetX264(encoderCtx); + initH264(encoderCtx, br); + } else if (avcodecId == AV_CODEC_ID_VP8) { + initVP8(encoderCtx, br); + } else if (avcodecId == AV_CODEC_ID_MPEG4) { + initMPEG4(encoderCtx, br); + } else if (avcodecId == AV_CODEC_ID_H263) { + initH263(encoderCtx, br); + } + return encoderCtx; +} + +void +MediaEncoder::setBitrate(uint64_t br) +{ + AVCodecContext* encoderCtx = getCurrentVideoAVCtx(); + AVMediaType codecType = encoderCtx->codec_type; + AVCodecID codecId = encoderCtx->codec_id; + + std::lock_guard<std::mutex> lk(encMutex_); + // No need to restart encoder for h264, h263 and MPEG4 + // Change parameters on the fly + if(codecId == AV_CODEC_ID_H264) + initH264(encoderCtx, br); + else if(codecId == AV_CODEC_ID_H263P) + initH263(encoderCtx, br); + else if(codecId == AV_CODEC_ID_MPEG4) + initMPEG4(encoderCtx, br); + else { + stopEncoder(); + encoderCtx = initCodec(codecType, codecId, NULL, br); + if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0) + throw MediaEncoderException("Could not open encoder"); + } +} + +void +MediaEncoder::initH264(AVCodecContext* encoderCtx, uint64_t br) +{ + uint64_t maxBitrate = 1000 * br; + uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + log(pow(maxBitrate, LOGREG_PARAM_B))); // CRF = A + B*ln(maxBitrate) + uint64_t bufSize = 2 * maxBitrate; +#ifdef RING_ACCEL + if (accel_) { + bufSize = 2 * maxBitrate; + encoderCtx->bit_rate = maxBitrate; + } +#endif + + libav_utils::setDictValue(&options_, "crf", std::to_string(crf)); + av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN); + encoderCtx->rc_buffer_size = bufSize; + encoderCtx->rc_max_rate = maxBitrate; + JAMI_DBG("H264 encoder setup: crf=%u, maxrate=%lu, bufsize=%lu", crf, maxBitrate, bufSize); +} + +void +MediaEncoder::initVP8(AVCodecContext* encoderCtx, uint64_t br) +{ + // 1- if quality is set use it + // bitrate need to be set. The target bitrate becomes the maximum allowed bitrate + // 2- otherwise set rc_max_rate and rc_buffer_size + // Using information given on this page: + // http://www.webmproject.org/docs/encoder-parameters/ + uint64_t maxBitrate = 1000 * br; + uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + log(pow(maxBitrate, LOGREG_PARAM_B))); // CRF = A + B*ln(maxBitrate) + uint64_t bufSize = 2 * maxBitrate; + + av_opt_set(encoderCtx, "quality", "realtime", AV_OPT_SEARCH_CHILDREN); + av_opt_set_int(encoderCtx, "error-resilient", 1, AV_OPT_SEARCH_CHILDREN); + av_opt_set_int(encoderCtx, "cpu-used", 7, AV_OPT_SEARCH_CHILDREN); // value obtained from testing + av_opt_set_int(encoderCtx, "lag-in-frames", 0, AV_OPT_SEARCH_CHILDREN); + // allow encoder to drop frames if buffers are full and + // to undershoot target bitrate to lessen strain on resources + av_opt_set_int(encoderCtx, "drop-frame", 25, AV_OPT_SEARCH_CHILDREN); + av_opt_set_int(encoderCtx, "undershoot-pct", 95, AV_OPT_SEARCH_CHILDREN); + // don't set encoderCtx->gop_size: let libvpx decide when to insert a keyframe + encoderCtx->slices = 2; // VP8E_SET_TOKEN_PARTITIONS + encoderCtx->qmin = 4; + encoderCtx->qmax = 56; + libav_utils::setDictValue(&options_, "crf", std::to_string(crf)); + av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN); + encoderCtx->rc_buffer_size = bufSize; + encoderCtx->rc_max_rate = maxBitrate; + JAMI_DBG("VP8 encoder setup: crf=%u, maxrate=%lu, bufsize=%lu", crf, maxBitrate, maxBitrate); +} + +void +MediaEncoder::initMPEG4(AVCodecContext* encoderCtx, uint64_t br) +{ + uint64_t maxBitrate = 1000 * br; + uint64_t bufSize = 2 * maxBitrate; + + // Use CBR (set bitrate) + encoderCtx->rc_buffer_size = bufSize; + encoderCtx->bit_rate = encoderCtx->rc_min_rate = encoderCtx->rc_max_rate = maxBitrate; + JAMI_DBG("MPEG4 encoder setup: maxrate=%lu, bufsize=%lu", maxBitrate, bufSize); +} + +void +MediaEncoder::initH263(AVCodecContext* encoderCtx, uint64_t br) +{ + uint64_t maxBitrate = 1000 * br; + uint64_t bufSize = 2 * maxBitrate; + + // Use CBR (set bitrate) + encoderCtx->rc_buffer_size = bufSize; + encoderCtx->bit_rate = encoderCtx->rc_min_rate = encoderCtx->rc_max_rate = maxBitrate; + JAMI_DBG("H263 encoder setup: maxrate=%lu, bufsize=%lu", maxBitrate, bufSize); +} + +AVCodecContext* +MediaEncoder::getCurrentVideoAVCtx() +{ + for (auto it : encoders_) { + if (it->codec_type == AVMEDIA_TYPE_VIDEO) + return it; + } + return nullptr; +} + + +void +MediaEncoder::stopEncoder() +{ + flush(); + for (auto it = encoders_.begin(); it != encoders_.end(); it++) { + if ((*it)->codec_type == AVMEDIA_TYPE_VIDEO) + { + encoders_.erase(it); + break; + } + } + AVCodecContext* encoderCtx = getCurrentVideoAVCtx(); + avcodec_close(encoderCtx); + avcodec_free_context(&encoderCtx); + av_free(encoderCtx); +} + void MediaEncoder::readConfig(AVDictionary** dict, AVCodecContext* encoderCtx) { @@ -820,4 +907,4 @@ MediaEncoder::readConfig(AVDictionary** dict, AVCodecContext* encoderCtx) } } -} // namespace jami +} // namespace jami \ No newline at end of file diff --git a/src/media/media_encoder.h b/src/media/media_encoder.h index 5a4913f6edd564c2822d4e4690af430e7c64714e..5dc9d5df10f54d2026443150f6923ac1d9f895a9 100644 --- a/src/media/media_encoder.h +++ b/src/media/media_encoder.h @@ -100,6 +100,8 @@ public: const std::string& getAudioCodec() const { return audioCodec_; } const std::string& getVideoCodec() const { return videoCodec_; } + void setBitrate(uint64_t br); + #ifdef RING_ACCEL void enableAccel(bool enableAccel); #endif @@ -116,6 +118,9 @@ private: int initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* framesCtx); void openIOContext(); void startIO(); + AVCodecContext* getCurrentVideoAVCtx(); + void stopEncoder(); + AVCodecContext* initCodec(AVMediaType mediaType, AVCodecID avcodecId, AVBufferRef* framesCtx, uint64_t br); std::vector<AVCodecContext*> encoders_; AVFormatContext *outputCtx_ = nullptr; @@ -124,6 +129,14 @@ private: unsigned sent_samples = 0; bool initialized_ {false}; bool fileIO_ {false}; + unsigned int currentVideoCodecID_ {0}; + AVCodec* outputCodec_ = nullptr; + std::mutex encMutex_; + + void initH264(AVCodecContext* encoderCtx, uint64_t br); + void initVP8(AVCodecContext* encoderCtx, uint64_t br); + void initMPEG4(AVCodecContext* encoderCtx, uint64_t br); + void initH263(AVCodecContext* encoderCtx, uint64_t br); #ifdef ENABLE_VIDEO video::VideoScaler scaler_; diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp index 9afab5fa018a6d30295607a5173f0eee61be2251..b228b9b4baafcf1c1af948e24baf52fc6591d67e 100644 --- a/src/media/video/video_rtp_session.cpp +++ b/src/media/video/video_rtp_session.cpp @@ -46,8 +46,8 @@ namespace jami { namespace video { using std::string; -constexpr auto DELAY_AFTER_RESTART = std::chrono::seconds(2); -constexpr auto EXPIRY_TIME_RTCP = std::chrono::milliseconds(2000); +constexpr auto DELAY_AFTER_RESTART = std::chrono::milliseconds(1000); +constexpr auto EXPIRY_TIME_RTCP = std::chrono::milliseconds(1000); VideoRtpSession::VideoRtpSession(const string &callID, const DeviceParams& localVideoParams) : @@ -401,16 +401,16 @@ VideoRtpSession::adaptQualityAndBitrate() return; } + // If bitrate has changed, let time to receive fresh RTCP packets auto now = clock::now(); auto restartTimer = now - lastMediaRestart_; - //Sleep 3 seconds while the media restart if (restartTimer < DELAY_AFTER_RESTART) { //JAMI_DBG("[AutoAdapt] Waiting for delay %ld ms", std::chrono::duration_cast<std::chrono::milliseconds>(restartTimer)); return; } - if (rtcpi.jitter > 5000) { - JAMI_DBG("[AutoAdapt] Jitter too high"); + if (rtcpi.jitter > 1000) { + //JAMI_DBG("[AutoAdapt] Jitter too high"); return; } @@ -419,11 +419,12 @@ VideoRtpSession::adaptQualityAndBitrate() //Take action only when two successive drop superior to 1% are catched... //and when jitter is less than 5 seconds auto pondLoss = getPonderateLoss(rtcpi.packetLoss); - //JAMI_DBG("[AutoAdapt] Ponderate packet loss rate: %f%, Last packet loss rate: %f%, Medium Jitter: %dms" , pondLoss, rtcpi.packetLoss, rtcpi.jitter); + //JAMI_DBG("[AutoAdapt] Pondloss: %f%, last loss: %f%", pondLoss, rtcpi.packetLoss); if(pondLoss >= 2.0f) { - videoBitrateInfo_.videoBitrateCurrent = videoBitrateInfo_.videoBitrateCurrent / ((rtcpi.packetLoss / 20)+1); - JAMI_WARN("[AutoAdapt] packet loss rate: %f%%, decrease bitrate from %d Kbps to %d Kbps", rtcpi.packetLoss, oldBitrate, videoBitrateInfo_.videoBitrateCurrent); + videoBitrateInfo_.videoBitrateCurrent = videoBitrateInfo_.videoBitrateCurrent * (1.0f - rtcpi.packetLoss/150.0f); + JAMI_DBG("[AutoAdapt] pondLoss: %f%%, packet loss rate: %f%%, decrease bitrate from %d Kbps to %d Kbps, ratio %f", pondLoss, rtcpi.packetLoss, oldBitrate, videoBitrateInfo_.videoBitrateCurrent, (float) videoBitrateInfo_.videoBitrateCurrent / oldBitrate); + histoLoss_.clear(); } videoBitrateInfo_.videoBitrateCurrent = std::max(videoBitrateInfo_.videoBitrateCurrent, videoBitrateInfo_.videoBitrateMin); @@ -431,16 +432,10 @@ VideoRtpSession::adaptQualityAndBitrate() if(oldBitrate != videoBitrateInfo_.videoBitrateCurrent) { storeVideoBitrateInfo(); - JAMI_DBG("[AutoAdapt] Restart media sender"); - const auto& cid = callID_; - - runOnMainThread([cid]{ - if (auto call = Manager::instance().callFactory.getCall(cid)) - call->restartMediaSender(); - }); - - lastMediaRestart_ = now; + // If encoder no longer exist do nothing + if(sender_->setBitrate(videoBitrateInfo_.videoBitrateCurrent) == 0) + lastMediaRestart_ = now; } } @@ -540,7 +535,7 @@ float VideoRtpSession::getPonderateLoss(float lastLoss) { float pond = 0.0f, pondLoss = 0.0f, totalPond = 0.0f; - constexpr float coefficient_a = -1/2000.0f; + constexpr float coefficient_a = -1/1000.0f; constexpr float coefficient_b = 1.0f; auto now = clock::now(); @@ -553,7 +548,7 @@ VideoRtpSession::getPonderateLoss(float lastLoss) //JAMI_WARN("now - it.first: %ld", std::chrono::duration_cast<std::chrono::milliseconds>(delay)); // 1ms -> 100% - // 2000ms -> 1 + // 1000ms -> 1 if(delay <= EXPIRY_TIME_RTCP) { pond = std::min(delay.count() * coefficient_a + coefficient_b, 1.0f); diff --git a/src/media/video/video_sender.cpp b/src/media/video/video_sender.cpp index ef64ff85182ab1679923a03863f05544999a1efe..ba1dc9c209d658632da7fdd746dd589f12363ccd 100644 --- a/src/media/video/video_sender.cpp +++ b/src/media/video/video_sender.cpp @@ -141,4 +141,17 @@ VideoSender::setChangeOrientationCallback(std::function<void(int)> cb) changeOrientationCallback_ = std::move(cb); } +int +VideoSender::setBitrate(uint64_t br) +{ + // The encoder may be destroy during a bitrate change + // when a codec parameter like auto quality change + if(!videoEncoder_) + return -1; + + videoEncoder_->setBitrate(br); + return 0; + +} + }} // namespace jami::video diff --git a/src/media/video/video_sender.h b/src/media/video/video_sender.h index 2b86ad8f30cbf665d3a91f0477b6851c1ecbf944..a9e77f85067ce4f08786125538eb6f6e851b55bf 100644 --- a/src/media/video/video_sender.h +++ b/src/media/video/video_sender.h @@ -60,6 +60,7 @@ public: uint16_t getLastSeqValue(); void setChangeOrientationCallback(std::function<void(int)> cb); + int setBitrate(uint64_t br); private: static constexpr int KEYFRAMES_AT_START {4}; // Number of keyframes to enforce at stream startup