LCOV - code coverage report
Current view: top level - foo/src/media - media_encoder.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 381 724 52.6 %
Date: 2026-04-01 09:29:43 Functions: 43 72 59.7 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : #include "libav_deps.h" // MUST BE INCLUDED FIRST
      18             : #include "media_codec.h"
      19             : #include "media_encoder.h"
      20             : #include "media_buffer.h"
      21             : 
      22             : #include "client/jami_signal.h"
      23             : #include "fileutils.h"
      24             : #include "logger.h"
      25             : #include "manager.h"
      26             : #include "string_utils.h"
      27             : #include "system_codec_container.h"
      28             : 
      29             : #ifdef ENABLE_HWACCEL
      30             : #include "video/accel.h"
      31             : #endif
      32             : 
      33             : extern "C" {
      34             : #include <libavutil/parseutils.h>
      35             : }
      36             : 
      37             : #include <algorithm>
      38             : #include <fstream>
      39             : #include <iostream>
      40             : #include <json/json.h>
      41             : #include <sstream>
      42             : #include <thread> // hardware_concurrency
      43             : #include <string_view>
      44             : #include <cmath>
      45             : 
      46             : // Define following line if you need to debug libav SDP
      47             : // #define DEBUG_SDP 1
      48             : 
      49             : using namespace std::literals;
      50             : 
      51             : namespace jami {
      52             : 
      53             : constexpr double LOGREG_PARAM_A {101};
      54             : constexpr double LOGREG_PARAM_B {-5.};
      55             : 
      56             : constexpr double LOGREG_PARAM_A_HEVC {96};
      57             : constexpr double LOGREG_PARAM_B_HEVC {-5.};
      58             : 
      59         317 : MediaEncoder::MediaEncoder()
      60         317 :     : outputCtx_(avformat_alloc_context())
      61             : {
      62         317 :     JAMI_DBG("[%p] New instance created", this);
      63         317 : }
      64             : 
      65         317 : MediaEncoder::~MediaEncoder()
      66             : {
      67         317 :     if (outputCtx_) {
      68         317 :         if (outputCtx_->priv_data && outputCtx_->pb)
      69          48 :             av_write_trailer(outputCtx_);
      70         317 :         if (fileIO_) {
      71           1 :             avio_close(outputCtx_->pb);
      72             :         }
      73         543 :         for (auto* encoderCtx : encoders_) {
      74         226 :             if (encoderCtx) {
      75             : #ifndef _MSC_VER
      76         226 :                 avcodec_free_context(&encoderCtx);
      77             : #else
      78             :                 avcodec_close(encoderCtx);
      79             : #endif
      80             :             }
      81             :         }
      82         317 :         avformat_free_context(outputCtx_);
      83             :     }
      84         317 :     av_dict_free(&options_);
      85             : 
      86         317 :     JAMI_DBG("[%p] Instance destroyed", this);
      87         317 : }
      88             : 
      89             : void
      90         320 : MediaEncoder::setOptions(const MediaStream& opts)
      91             : {
      92         320 :     if (!opts.isValid()) {
      93           0 :         JAMI_ERR() << "Invalid options";
      94           0 :         return;
      95             :     }
      96             : 
      97         320 :     if (opts.isVideo) {
      98         142 :         videoOpts_ = opts;
      99             :         // Make sure width and height are even (required by x264)
     100             :         // This is especially for image/gif streaming, as video files and cameras usually have even
     101             :         // resolutions
     102         142 :         videoOpts_.width = ((videoOpts_.width >> 3) << 3);
     103         142 :         videoOpts_.height = ((videoOpts_.height >> 3) << 3);
     104         142 :         if (!videoOpts_.frameRate)
     105          92 :             videoOpts_.frameRate = 30;
     106         142 :         if (!videoOpts_.bitrate) {
     107          49 :             videoOpts_.bitrate = SystemCodecInfo::DEFAULT_VIDEO_BITRATE;
     108             :         }
     109             :     } else {
     110         178 :         audioOpts_ = opts;
     111             :     }
     112             : }
     113             : 
     114             : void
     115         316 : MediaEncoder::setOptions(const MediaDescription& args)
     116             : {
     117             :     int ret;
     118         632 :     if (args.payload_type
     119         630 :         and (ret = av_opt_set_int(reinterpret_cast<void*>(outputCtx_),
     120             :                                   "payload_type",
     121         314 :                                   args.payload_type,
     122             :                                   AV_OPT_SEARCH_CHILDREN)
     123         314 :                    < 0))
     124           0 :         JAMI_ERR() << "Failed to set payload type: " << libav_utils::getError(ret);
     125             : 
     126         316 :     if (not args.parameters.empty())
     127         139 :         libav_utils::setDictValue(&options_, "parameters", args.parameters);
     128             : 
     129         316 :     mode_ = args.mode;
     130         316 :     linkableHW_ = args.linkableHW;
     131         316 :     fecEnabled_ = args.fecEnabled;
     132         316 : }
     133             : 
     134             : void
     135           2 : MediaEncoder::setMetadata(const std::string& title, const std::string& description)
     136             : {
     137           2 :     if (not title.empty())
     138           2 :         libav_utils::setDictValue(&outputCtx_->metadata, "title", title);
     139           2 :     if (not description.empty())
     140           2 :         libav_utils::setDictValue(&outputCtx_->metadata, "description", description);
     141           2 : }
     142             : 
     143             : void
     144         314 : MediaEncoder::setInitSeqVal(uint16_t seqVal)
     145             : {
     146             :     // only set not default value (!=0)
     147         314 :     if (seqVal != 0)
     148         139 :         av_opt_set_int(outputCtx_, "seq", seqVal, AV_OPT_SEARCH_CHILDREN);
     149         314 : }
     150             : 
     151             : uint16_t
     152           0 : MediaEncoder::getLastSeqValue()
     153             : {
     154             :     int64_t retVal;
     155           0 :     if (av_opt_get_int(outputCtx_, "seq", AV_OPT_SEARCH_CHILDREN, &retVal) >= 0)
     156           0 :         return (uint16_t) retVal;
     157             :     else
     158           0 :         return 0;
     159             : }
     160             : 
     161             : void
     162         317 : MediaEncoder::openOutput(const std::string& filename, const std::string& format)
     163             : {
     164         317 :     avformat_free_context(outputCtx_);
     165         948 :     int result = avformat_alloc_output_context2(&outputCtx_,
     166             :                                                 nullptr,
     167         631 :                                                 format.empty() ? nullptr : format.c_str(),
     168             :                                                 filename.c_str());
     169         317 :     if (result < 0)
     170           0 :         JAMI_ERR() << "Unable to open " << filename << ": " << libav_utils::getError(-result);
     171         317 : }
     172             : 
     173             : int
     174         320 : MediaEncoder::addStream(const SystemCodecInfo& systemCodecInfo)
     175             : {
     176         320 :     if (systemCodecInfo.mediaType == MEDIA_AUDIO) {
     177         178 :         audioCodec_ = systemCodecInfo.name;
     178             :     } else {
     179         142 :         videoCodec_ = systemCodecInfo.name;
     180             :     }
     181             : 
     182         320 :     auto* stream = avformat_new_stream(outputCtx_, outputCodec_);
     183             : 
     184         320 :     if (stream == nullptr) {
     185           0 :         JAMI_ERR("[%p] Failed to create coding instance for %s", this, systemCodecInfo.name.c_str());
     186           0 :         return -1;
     187             :     }
     188             : 
     189         320 :     JAMI_DBG("[%p] Created new coding instance for %s @ index %d", this, systemCodecInfo.name.c_str(), stream->index);
     190             :     // Only init audio now, video will be intialized when
     191             :     // encoding the first frame.
     192         320 :     if (systemCodecInfo.mediaType == MEDIA_AUDIO) {
     193         178 :         return initStream(systemCodecInfo);
     194             :     }
     195             : 
     196             :     // If audio options are valid, it means this session is used
     197             :     // for both audio and video streams, thus the video will be
     198             :     // at index 1, otherwise it will be at index 0.
     199             :     // TODO. Hacky, needs better solution.
     200         142 :     if (audioOpts_.isValid())
     201           3 :         return 1;
     202             :     else
     203         139 :         return 0;
     204             : }
     205             : 
     206             : int
     207          48 : MediaEncoder::initStream(const std::string& codecName, AVBufferRef* framesCtx)
     208             : {
     209          48 :     const auto codecInfo = getSystemCodecContainer()->searchCodecByName(codecName, MEDIA_ALL);
     210          48 :     if (codecInfo)
     211          48 :         return initStream(*codecInfo, framesCtx);
     212             :     else
     213           0 :         return -1;
     214          48 : }
     215             : 
     216             : int
     217         226 : MediaEncoder::initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* framesCtx)
     218             : {
     219         226 :     JAMI_DBG("[%p] Initializing stream: codec type %d, name %s, lib %s",
     220             :              this,
     221             :              systemCodecInfo.codecType,
     222             :              systemCodecInfo.name.c_str(),
     223             :              systemCodecInfo.libName.c_str());
     224             : 
     225         226 :     std::lock_guard lk(encMutex_);
     226             : 
     227         226 :     if (!outputCtx_)
     228           0 :         throw MediaEncoderException("Unable to allocate stream");
     229             : 
     230             :     // Must already have codec instance(s)
     231         226 :     if (outputCtx_->nb_streams == 0) {
     232           0 :         JAMI_ERR("[%p] Unable to init, output context has no coding sessions!", this);
     233           0 :         throw MediaEncoderException("Unable to init, output context has no coding sessions!");
     234             :     }
     235             : 
     236         226 :     AVCodecContext* encoderCtx = nullptr;
     237             :     AVMediaType mediaType;
     238             : 
     239         226 :     if (systemCodecInfo.mediaType == MEDIA_VIDEO)
     240          48 :         mediaType = AVMEDIA_TYPE_VIDEO;
     241         178 :     else if (systemCodecInfo.mediaType == MEDIA_AUDIO)
     242         178 :         mediaType = AVMEDIA_TYPE_AUDIO;
     243             :     else
     244           0 :         throw MediaEncoderException("Unsuported media type");
     245             : 
     246         226 :     AVStream* stream {nullptr};
     247             : 
     248             :     // Only supports one audio and one video streams at most per instance.
     249         453 :     for (unsigned i = 0; i < outputCtx_->nb_streams; i++) {
     250         227 :         stream = outputCtx_->streams[i];
     251         227 :         if (stream->codecpar->codec_type == mediaType) {
     252           0 :             if (mediaType == AVMEDIA_TYPE_VIDEO) {
     253           0 :                 stream->codecpar->width = videoOpts_.width;
     254           0 :                 stream->codecpar->height = videoOpts_.height;
     255             :             }
     256           0 :             break;
     257             :         }
     258             :     }
     259             : 
     260         226 :     if (stream == nullptr) {
     261           0 :         JAMI_ERR("[%p] Unable to init, output context has no coding sessions for %s",
     262             :                  this,
     263             :                  systemCodecInfo.name.c_str());
     264           0 :         throw MediaEncoderException("Unable to allocate stream");
     265             :     }
     266             : 
     267         226 :     currentStreamIdx_ = stream->index;
     268             : #ifdef ENABLE_HWACCEL
     269             :     // Get compatible list of Hardware API
     270         226 :     if (enableAccel_ && mediaType == AVMEDIA_TYPE_VIDEO) {
     271           0 :         auto APIs = video::HardwareAccel::getCompatibleAccel(static_cast<AVCodecID>(systemCodecInfo.avcodecId),
     272             :                                                              videoOpts_.width,
     273             :                                                              videoOpts_.height,
     274           0 :                                                              CODEC_ENCODER);
     275           0 :         for (const auto& it : APIs) {
     276           0 :             accel_ = std::make_unique<video::HardwareAccel>(it); // save accel
     277             :             // Init codec need accel_ to init encoderCtx accelerated
     278           0 :             encoderCtx = initCodec(mediaType, static_cast<AVCodecID>(systemCodecInfo.avcodecId), videoOpts_.bitrate);
     279           0 :             encoderCtx->opaque = accel_.get();
     280             :             // Check if pixel format from encoder match pixel format from decoder frame context
     281             :             // if it mismatch, it means that we are using two different hardware API (nvenc and
     282             :             // vaapi for example) in this case we don't want link the APIs
     283           0 :             if (framesCtx) {
     284           0 :                 auto* hw = reinterpret_cast<AVHWFramesContext*>(framesCtx->data);
     285           0 :                 if (encoderCtx->pix_fmt != hw->format)
     286           0 :                     linkableHW_ = false;
     287             :             }
     288           0 :             auto ret = accel_->initAPI(linkableHW_, framesCtx);
     289           0 :             if (ret < 0) {
     290           0 :                 accel_.reset();
     291           0 :                 encoderCtx = nullptr;
     292           0 :                 continue;
     293             :             }
     294           0 :             accel_->setDetails(encoderCtx);
     295           0 :             if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0) {
     296             :                 // Failed to open codec
     297           0 :                 JAMI_WARN("Fail to open hardware encoder %s with %s ",
     298             :                           avcodec_get_name(static_cast<AVCodecID>(systemCodecInfo.avcodecId)),
     299             :                           it.getName().c_str());
     300           0 :                 avcodec_free_context(&encoderCtx);
     301           0 :                 encoderCtx = nullptr;
     302           0 :                 accel_ = nullptr;
     303           0 :                 continue;
     304             :             } else {
     305             :                 // Succeed to open codec
     306           0 :                 JAMI_WARN("Using hardware encoding for %s with %s ",
     307             :                           avcodec_get_name(static_cast<AVCodecID>(systemCodecInfo.avcodecId)),
     308             :                           it.getName().c_str());
     309           0 :                 encoders_.emplace_back(encoderCtx);
     310           0 :                 break;
     311             :             }
     312             :         }
     313           0 :     }
     314             : #endif
     315             : 
     316         226 :     if (!encoderCtx) {
     317         226 :         JAMI_WARN("Not using hardware encoding for %s",
     318             :                   avcodec_get_name(static_cast<AVCodecID>(systemCodecInfo.avcodecId)));
     319         226 :         encoderCtx = initCodec(mediaType, static_cast<AVCodecID>(systemCodecInfo.avcodecId), videoOpts_.bitrate);
     320         226 :         readConfig(encoderCtx);
     321         226 :         encoders_.emplace_back(encoderCtx);
     322         226 :         if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0)
     323           0 :             throw MediaEncoderException("Unable to open encoder");
     324             :     }
     325             : 
     326         226 :     avcodec_parameters_from_context(stream->codecpar, encoderCtx);
     327             : 
     328             :     // framerate is not copied from encoderCtx to stream
     329         226 :     stream->avg_frame_rate = encoderCtx->framerate;
     330             : #ifdef ENABLE_VIDEO
     331         226 :     if (systemCodecInfo.mediaType == MEDIA_VIDEO) {
     332             :         // allocate buffers for both scaled (pre-encoder) and encoded frames
     333          48 :         const int width = encoderCtx->width;
     334          48 :         const int height = encoderCtx->height;
     335          48 :         int format = encoderCtx->pix_fmt;
     336             : #ifdef ENABLE_HWACCEL
     337          48 :         if (accel_) {
     338             :             // hardware encoders require a specific pixel format
     339           0 :             const auto* desc = av_pix_fmt_desc_get(encoderCtx->pix_fmt);
     340           0 :             if (desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL))
     341           0 :                 format = accel_->getSoftwareFormat();
     342             :         }
     343             : #endif
     344          48 :         scaledFrameBufferSize_ = videoFrameSize(format, width, height);
     345          48 :         if (scaledFrameBufferSize_ < 0)
     346           0 :             throw MediaEncoderException(
     347           0 :                 ("Unable to compute buffer size: " + libav_utils::getError(scaledFrameBufferSize_)).c_str());
     348          48 :         else if (scaledFrameBufferSize_ <= AV_INPUT_BUFFER_MIN_SIZE)
     349           0 :             throw MediaEncoderException("buffer too small");
     350             : 
     351          48 :         scaledFrameBuffer_.resize(scaledFrameBufferSize_);
     352          48 :         scaledFrame_ = std::make_shared<VideoFrame>();
     353          48 :         scaledFrame_->setFromMemory(scaledFrameBuffer_.data(), format, width, height);
     354             :     }
     355             : #endif // ENABLE_VIDEO
     356             : 
     357         226 :     return stream->index;
     358         226 : }
     359             : 
     360             : void
     361          48 : MediaEncoder::openIOContext()
     362             : {
     363          48 :     if (ioCtx_) {
     364          47 :         outputCtx_->pb = ioCtx_;
     365          47 :         outputCtx_->packet_size = outputCtx_->pb->buffer_size;
     366             :     } else {
     367           1 :         int ret = 0;
     368             : #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
     369           1 :         const char* filename = outputCtx_->url;
     370             : #else
     371             :         const char* filename = outputCtx_->filename;
     372             : #endif
     373           1 :         if (!(outputCtx_->oformat->flags & AVFMT_NOFILE)) {
     374           1 :             fileIO_ = true;
     375           1 :             if ((ret = avio_open(&outputCtx_->pb, filename, AVIO_FLAG_WRITE)) < 0) {
     376           0 :                 throw MediaEncoderException(
     377           0 :                     fmt::format("Unable to open IO context for '{}': {}", filename, libav_utils::getError(ret)));
     378             :             }
     379             :         }
     380             :     }
     381          48 : }
     382             : 
     383             : void
     384          48 : MediaEncoder::startIO()
     385             : {
     386          48 :     if (!outputCtx_->pb)
     387          48 :         openIOContext();
     388          48 :     if (avformat_write_header(outputCtx_, options_ ? &options_ : nullptr)) {
     389           0 :         JAMI_ERR("Unable to write header for output file... check codec parameters");
     390           0 :         throw MediaEncoderException("Failed to write output file header");
     391             :     }
     392             : 
     393             : #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
     394          48 :     av_dump_format(outputCtx_, 0, outputCtx_->url, 1);
     395             : #else
     396             :     av_dump_format(outputCtx_, 0, outputCtx_->filename, 1);
     397             : #endif
     398          48 :     initialized_ = true;
     399          48 : }
     400             : 
     401             : #ifdef ENABLE_VIDEO
     402             : int
     403        5104 : MediaEncoder::encode(const std::shared_ptr<VideoFrame>& input, bool is_keyframe, int64_t frame_number)
     404             : {
     405        5104 :     auto width = (input->width() >> 3) << 3;
     406        5104 :     auto height = (input->height() >> 3) << 3;
     407        5104 :     if (initialized_ && (getWidth() != width || getHeight() != height)) {
     408           0 :         resetStreams(width, height);
     409           0 :         is_keyframe = true;
     410             :     }
     411             : 
     412        5104 :     if (!initialized_) {
     413          47 :         initStream(videoCodec_, input->pointer()->hw_frames_ctx);
     414          47 :         startIO();
     415             :     }
     416             : 
     417        5104 :     std::shared_ptr<VideoFrame> output;
     418             : #ifdef ENABLE_HWACCEL
     419        5104 :     if (getHWFrame(input, output) < 0) {
     420           0 :         JAMI_ERR("Fail to get hardware frame");
     421           0 :         return -1;
     422             :     }
     423             : #else
     424             :     output = getScaledSWFrame(*input.get());
     425             : #endif // ENABLE_HWACCEL
     426             : 
     427        5104 :     if (!output) {
     428           0 :         JAMI_ERR("Fail to get frame");
     429           0 :         return -1;
     430             :     }
     431        5104 :     auto* avframe = output->pointer();
     432             : 
     433        5104 :     AVCodecContext* enc = encoders_[currentStreamIdx_];
     434        5104 :     avframe->pts = frame_number;
     435        5104 :     if (enc->framerate.num != enc->time_base.den || enc->framerate.den != enc->time_base.num)
     436           0 :         avframe->pts /= (rational<int64_t>(enc->framerate) * rational<int64_t>(enc->time_base)).real<int64_t>();
     437             : 
     438        5104 :     if (is_keyframe) {
     439          64 :         avframe->pict_type = AV_PICTURE_TYPE_I;
     440          64 :         avframe->key_frame = 1;
     441             :     } else {
     442        5040 :         avframe->pict_type = AV_PICTURE_TYPE_NONE;
     443        5040 :         avframe->key_frame = 0;
     444             :     }
     445             : 
     446        5104 :     return encode(avframe, currentStreamIdx_);
     447        5104 : }
     448             : #endif // ENABLE_VIDEO
     449             : 
     450             : int
     451           0 : MediaEncoder::encodeAudio(AudioFrame& frame)
     452             : {
     453           0 :     if (!initialized_) {
     454             :         // Initialize on first video frame, or first audio frame if no video stream
     455           0 :         if (not videoOpts_.isValid())
     456           0 :             startIO();
     457             :         else
     458           0 :             return 0;
     459             :     }
     460           0 :     frame.pointer()->pts = sent_samples;
     461           0 :     sent_samples += frame.pointer()->nb_samples;
     462           0 :     encode(frame.pointer(), currentStreamIdx_);
     463           0 :     return 0;
     464             : }
     465             : 
     466             : int
     467        5160 : MediaEncoder::encode(AVFrame* frame, int streamIdx)
     468             : {
     469        5160 :     if (!initialized_ && frame) {
     470             :         // Initialize on first video frame, or first audio frame if no video stream
     471           2 :         bool isVideo = (frame->width > 0 && frame->height > 0);
     472           2 :         if (isVideo and videoOpts_.isValid()) {
     473             :             // Has video stream, so init with video frame
     474           1 :             streamIdx = initStream(videoCodec_, frame->hw_frames_ctx);
     475           1 :             startIO();
     476           1 :         } else if (!isVideo and !videoOpts_.isValid()) {
     477             :             // Only audio, for MediaRecorder, which doesn't use encodeAudio
     478           0 :             startIO();
     479             :         } else {
     480           1 :             return 0;
     481             :         }
     482             :     }
     483        5159 :     int ret = 0;
     484        5159 :     if (static_cast<size_t>(streamIdx) >= encoders_.size())
     485           2 :         return -1;
     486        5157 :     AVCodecContext* encoderCtx = encoders_[streamIdx];
     487             :     AVPacket pkt;
     488        5157 :     av_init_packet(&pkt);
     489        5157 :     pkt.data = nullptr; // packet data will be allocated by the encoder
     490        5157 :     pkt.size = 0;
     491             : 
     492        5157 :     if (!encoderCtx)
     493           0 :         return -1;
     494             : 
     495        5157 :     ret = avcodec_send_frame(encoderCtx, frame);
     496        5157 :     if (ret < 0)
     497           0 :         return -1;
     498             : 
     499        5160 :     while (ret >= 0) {
     500        5157 :         ret = avcodec_receive_packet(encoderCtx, &pkt);
     501        5157 :         if (ret == AVERROR(EAGAIN))
     502           0 :             break;
     503        5157 :         if (ret < 0 && ret != AVERROR_EOF) { // we still want to write our frame on EOF
     504           0 :             JAMI_ERR() << "Failed to encode frame: " << libav_utils::getError(ret);
     505           0 :             return ret;
     506             :         }
     507             : 
     508        5157 :         if (pkt.size) {
     509        5154 :             if (send(pkt, streamIdx))
     510        5154 :                 break;
     511             :         }
     512             :     }
     513             : 
     514        5157 :     av_packet_unref(&pkt);
     515        5157 :     return 0;
     516             : }
     517             : 
     518             : bool
     519        5154 : MediaEncoder::send(AVPacket& pkt, int streamIdx)
     520             : {
     521        5154 :     if (!initialized_) {
     522           0 :         streamIdx = initStream(videoCodec_);
     523           0 :         startIO();
     524             :     }
     525        5154 :     if (streamIdx < 0)
     526           0 :         streamIdx = currentStreamIdx_;
     527        5154 :     if (streamIdx >= 0 and static_cast<size_t>(streamIdx) < encoders_.size()
     528       10308 :         and static_cast<unsigned int>(streamIdx) < outputCtx_->nb_streams) {
     529        5154 :         auto* encoderCtx = encoders_[streamIdx];
     530        5154 :         pkt.stream_index = streamIdx;
     531        5154 :         if (pkt.pts != AV_NOPTS_VALUE)
     532        5154 :             pkt.pts = av_rescale_q(pkt.pts, encoderCtx->time_base, outputCtx_->streams[streamIdx]->time_base);
     533        5154 :         if (pkt.dts != AV_NOPTS_VALUE)
     534        5154 :             pkt.dts = av_rescale_q(pkt.dts, encoderCtx->time_base, outputCtx_->streams[streamIdx]->time_base);
     535             :     }
     536             :     // write the compressed frame
     537        5154 :     auto ret = av_write_frame(outputCtx_, &pkt);
     538        5154 :     if (ret < 0) {
     539           0 :         JAMI_ERR() << "av_write_frame failed: " << libav_utils::getError(ret);
     540             :     }
     541        5154 :     return ret >= 0;
     542             : }
     543             : 
     544             : int
     545           3 : MediaEncoder::flush()
     546             : {
     547           3 :     int ret = 0;
     548           9 :     for (size_t i = 0; i < outputCtx_->nb_streams; ++i) {
     549           6 :         if (encode(nullptr, static_cast<int>(i)) < 0) {
     550           2 :             JAMI_ERR() << "Unable to flush stream #" << i;
     551           2 :             ret |= 1 << i; // provide a way for caller to know which streams failed
     552             :         }
     553             :     }
     554           3 :     return -ret;
     555             : }
     556             : 
     557             : std::string
     558           0 : MediaEncoder::print_sdp()
     559             : {
     560             :     /* theora sdp can be huge */
     561           0 :     const auto sdp_size = outputCtx_->streams[currentStreamIdx_]->codecpar->extradata_size + 2048;
     562           0 :     std::string sdp(sdp_size, '\0');
     563           0 :     av_sdp_create(&outputCtx_, 1, &(*sdp.begin()), sdp_size);
     564             : 
     565           0 :     std::string result;
     566           0 :     result.reserve(sdp_size);
     567             : 
     568           0 :     std::string_view steam(sdp), line;
     569           0 :     while (jami::getline(steam, line)) {
     570             :         /* strip windows line ending */
     571           0 :         result += line.substr(0, line.length() - 1);
     572           0 :         result += "\n"sv;
     573             :     }
     574             : #ifdef DEBUG_SDP
     575             :     JAMI_DBG("Sending SDP:\n%s", result.c_str());
     576             : #endif
     577           0 :     return result;
     578           0 : }
     579             : 
     580             : AVCodecContext*
     581         226 : MediaEncoder::prepareEncoderContext(const AVCodec* outputCodec, bool is_video)
     582             : {
     583         226 :     AVCodecContext* encoderCtx = avcodec_alloc_context3(outputCodec);
     584             : 
     585         226 :     const auto* encoderName = outputCodec->name; // guaranteed to be non null if AVCodec is not null
     586             : 
     587         226 :     encoderCtx->thread_count = std::min(static_cast<int>(std::thread::hardware_concurrency()), is_video ? 16 : 4);
     588         226 :     JAMI_DBG("[%s] Using %d threads", encoderName, encoderCtx->thread_count);
     589             : 
     590         226 :     if (is_video) {
     591             :         // resolution must be a multiple of two
     592          48 :         encoderCtx->width = videoOpts_.width;
     593          48 :         encoderCtx->height = videoOpts_.height;
     594             : 
     595             :         // satisfy ffmpeg: denominator must be 16bit or less value
     596             :         // time base = 1/FPS
     597          48 :         av_reduce(&encoderCtx->framerate.num,
     598             :                   &encoderCtx->framerate.den,
     599          48 :                   videoOpts_.frameRate.numerator(),
     600          48 :                   videoOpts_.frameRate.denominator(),
     601             :                   (1U << 16) - 1);
     602          48 :         encoderCtx->time_base = av_inv_q(encoderCtx->framerate);
     603             : 
     604             :         // emit one intra frame every gop_size frames
     605          48 :         encoderCtx->max_b_frames = 0;
     606          48 :         encoderCtx->pix_fmt = AV_PIX_FMT_YUV420P;
     607             :         // Keep YUV format for macOS
     608             : #ifdef ENABLE_HWACCEL
     609             : #if defined(TARGET_OS_IOS) && TARGET_OS_IOS
     610             :         if (accel_)
     611             :             encoderCtx->pix_fmt = accel_->getSoftwareFormat();
     612             : #elif !defined(__APPLE__)
     613          48 :         if (accel_)
     614           0 :             encoderCtx->pix_fmt = accel_->getFormat();
     615             : #endif
     616             : #endif
     617             : 
     618             :         // Fri Jul 22 11:37:59 EDT 2011:tmatth:XXX: DON'T set this, we want our
     619             :         // pps and sps to be sent in-band for RTP
     620             :         // This is to place global headers in extradata instead of every
     621             :         // keyframe.
     622             :         // encoderCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
     623             :     } else {
     624         712 :         JAMI_WARNING("Codec format: {} {} {} {}",
     625             :                      encoderName,
     626             :                      audioOpts_.format,
     627             :                      audioOpts_.sampleRate,
     628             :                      audioOpts_.nbChannels);
     629         178 :         encoderCtx->sample_fmt = (AVSampleFormat) audioOpts_.format;
     630         178 :         encoderCtx->sample_rate = std::max(8000, audioOpts_.sampleRate);
     631         178 :         encoderCtx->time_base = AVRational {1, encoderCtx->sample_rate};
     632         178 :         if (audioOpts_.nbChannels > 2 || audioOpts_.nbChannels < 1) {
     633           0 :             audioOpts_.nbChannels = std::clamp(audioOpts_.nbChannels, 1, 2);
     634           0 :             JAMI_ERR() << "[" << encoderName << "] Clamping invalid channel count: " << audioOpts_.nbChannels;
     635             :         }
     636         178 :         av_channel_layout_default(&encoderCtx->ch_layout, audioOpts_.nbChannels);
     637         178 :         if (audioOpts_.frameSize) {
     638         176 :             encoderCtx->frame_size = audioOpts_.frameSize;
     639         176 :             JAMI_DBG() << "[" << encoderName << "] Frame size " << encoderCtx->frame_size;
     640             :         } else {
     641           2 :             JAMI_WARN() << "[" << encoderName << "] Frame size not set";
     642             :         }
     643             :     }
     644             : 
     645         226 :     return encoderCtx;
     646             : }
     647             : 
     648             : void
     649          47 : MediaEncoder::forcePresetX2645(AVCodecContext* encoderCtx)
     650             : {
     651             : #ifdef ENABLE_HWACCEL
     652          47 :     if (accel_ && accel_->getName() == "nvenc") {
     653           0 :         if (av_opt_set(encoderCtx, "preset", "fast", AV_OPT_SEARCH_CHILDREN))
     654           0 :             JAMI_WARN("Failed to set preset to 'fast'");
     655           0 :         if (av_opt_set(encoderCtx, "level", "auto", AV_OPT_SEARCH_CHILDREN))
     656           0 :             JAMI_WARN("Failed to set level to 'auto'");
     657           0 :         if (av_opt_set_int(encoderCtx, "zerolatency", 1, AV_OPT_SEARCH_CHILDREN))
     658           0 :             JAMI_WARN("Failed to set zerolatency to '1'");
     659             :     } else
     660             : #endif
     661             :     {
     662             : #if (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
     663             :         const char* speedPreset = "ultrafast";
     664             : #else
     665          47 :         const char* speedPreset = "veryfast";
     666             : #endif
     667          47 :         if (av_opt_set(encoderCtx, "preset", speedPreset, AV_OPT_SEARCH_CHILDREN))
     668           0 :             JAMI_WARN("Failed to set preset '%s'", speedPreset);
     669          47 :         const char* tune = "zerolatency";
     670          47 :         if (av_opt_set(encoderCtx, "tune", tune, AV_OPT_SEARCH_CHILDREN))
     671           0 :             JAMI_WARN("Failed to set tune '%s'", tune);
     672             :     }
     673          47 : }
     674             : 
     675             : void
     676          47 : MediaEncoder::extractProfileLevelID(const std::string& parameters, AVCodecContext* ctx)
     677             : {
     678             :     // From RFC3984:
     679             :     // If no profile-level-id is present, the Baseline Profile without
     680             :     // additional constraints at Level 1 MUST be implied.
     681          47 :     ctx->profile = FF_PROFILE_H264_CONSTRAINED_BASELINE;
     682          47 :     ctx->level = 0x0d;
     683             :     // ctx->level = 0x0d; // => 13 aka 1.3
     684          47 :     if (parameters.empty())
     685           0 :         return;
     686             : 
     687          47 :     const std::string target("profile-level-id=");
     688          47 :     size_t needle = parameters.find(target);
     689          47 :     if (needle == std::string::npos)
     690           0 :         return;
     691             : 
     692          47 :     needle += target.length();
     693          47 :     const size_t id_length = 6; /* digits */
     694          47 :     const std::string profileLevelID(parameters.substr(needle, id_length));
     695          47 :     if (profileLevelID.length() != id_length)
     696           0 :         return;
     697             : 
     698             :     int result;
     699          47 :     std::stringstream ss;
     700          47 :     ss << profileLevelID;
     701          47 :     ss >> std::hex >> result;
     702             :     // profile-level id consists of three bytes
     703          47 :     const unsigned char profile_idc = result >> 16;           // 42xxxx -> 42
     704          47 :     const unsigned char profile_iop = ((result >> 8) & 0xff); // xx80xx -> 80
     705          47 :     ctx->level = result & 0xff;                               // xxxx0d -> 0d
     706          47 :     switch (profile_idc) {
     707          47 :     case FF_PROFILE_H264_BASELINE:
     708             :         // check constraint_set_1_flag
     709          47 :         if ((profile_iop & 0x40) >> 6)
     710           0 :             ctx->profile |= FF_PROFILE_H264_CONSTRAINED;
     711          47 :         break;
     712           0 :     case FF_PROFILE_H264_HIGH_10:
     713             :     case FF_PROFILE_H264_HIGH_422:
     714             :     case FF_PROFILE_H264_HIGH_444_PREDICTIVE:
     715             :         // check constraint_set_3_flag
     716           0 :         if ((profile_iop & 0x10) >> 4)
     717           0 :             ctx->profile |= FF_PROFILE_H264_INTRA;
     718           0 :         break;
     719           0 :     default:
     720           0 :         JAMI_DBG("Unrecognized H264 profile byte");
     721             :     }
     722          47 :     JAMI_DBG("Using profile %s (%x) and level %d",
     723             :              avcodec_profile_name(AV_CODEC_ID_H264, ctx->profile),
     724             :              ctx->profile,
     725             :              ctx->level);
     726          47 : }
     727             : 
     728             : #ifdef ENABLE_HWACCEL
     729             : void
     730         141 : MediaEncoder::enableAccel(bool enableAccel)
     731             : {
     732         141 :     enableAccel_ = enableAccel;
     733         141 :     emitSignal<libjami::ConfigurationSignal::HardwareEncodingChanged>(enableAccel_);
     734         141 :     if (!enableAccel_) {
     735         141 :         accel_.reset();
     736         141 :         for (auto* enc : encoders_)
     737           0 :             enc->opaque = nullptr;
     738             :     }
     739         141 : }
     740             : #endif
     741             : 
     742             : unsigned
     743           2 : MediaEncoder::getStreamCount() const
     744             : {
     745           2 :     return (audioOpts_.isValid() + videoOpts_.isValid());
     746             : }
     747             : 
     748             : MediaStream
     749           0 : MediaEncoder::getStream(const std::string& name, int streamIdx) const
     750             : {
     751             :     // if streamIdx is negative, use currentStreamIdx_
     752           0 :     if (streamIdx < 0)
     753           0 :         streamIdx = currentStreamIdx_;
     754             :     // make sure streamIdx is valid
     755           0 :     if (getStreamCount() <= 0 || streamIdx < 0 || encoders_.size() < (unsigned) (streamIdx + 1))
     756           0 :         return {};
     757           0 :     auto* enc = encoders_[streamIdx];
     758             :     // TODO set firstTimestamp
     759           0 :     auto ms = MediaStream(name, enc);
     760             : #ifdef ENABLE_HWACCEL
     761           0 :     if (accel_)
     762           0 :         ms.format = accel_->getSoftwareFormat();
     763             : #endif
     764           0 :     return ms;
     765           0 : }
     766             : 
     767             : int
     768           1 : MediaEncoder::getCurrentAudioAVCtxFrameSize()
     769             : {
     770           1 :     if (auto* ctx = getCurrentAudioAVCtx()) {
     771           1 :         return ctx->frame_size;
     772             :     }
     773           0 :     return 0;
     774             : }
     775             : 
     776             : AVCodecContext*
     777         226 : MediaEncoder::initCodec(AVMediaType mediaType, AVCodecID avcodecId, uint64_t br)
     778             : {
     779         226 :     outputCodec_ = nullptr;
     780             : #ifdef ENABLE_HWACCEL
     781         226 :     if (mediaType == AVMEDIA_TYPE_VIDEO) {
     782          48 :         if (enableAccel_) {
     783           0 :             if (accel_) {
     784           0 :                 outputCodec_ = avcodec_find_encoder_by_name(accel_->getCodecName().c_str());
     785             :             }
     786             :         } else {
     787          48 :             JAMI_WARN() << "Hardware encoding disabled";
     788             :         }
     789             :     }
     790             : #endif
     791             : 
     792         226 :     if (!outputCodec_) {
     793             :         /* find the video encoder */
     794         226 :         if (avcodecId == AV_CODEC_ID_H263)
     795             :             // For H263 encoding, we force the use of AV_CODEC_ID_H263P (H263-1998)
     796             :             // H263-1998 can manage all frame sizes while H263 don't
     797             :             // AV_CODEC_ID_H263 decoder will be used for decoding
     798           0 :             outputCodec_ = avcodec_find_encoder(AV_CODEC_ID_H263P);
     799             :         else
     800         226 :             outputCodec_ = avcodec_find_encoder(static_cast<AVCodecID>(avcodecId));
     801         226 :         if (!outputCodec_) {
     802           0 :             throw MediaEncoderException("No output encoder");
     803             :         }
     804             :     }
     805             : 
     806         226 :     AVCodecContext* encoderCtx = prepareEncoderContext(outputCodec_, mediaType == AVMEDIA_TYPE_VIDEO);
     807             : 
     808             :     // Only clamp video bitrate
     809         226 :     if (mediaType == AVMEDIA_TYPE_VIDEO && br > 0) {
     810          48 :         if (br < SystemCodecInfo::DEFAULT_MIN_BITRATE) {
     811           4 :             JAMI_WARNING("Requested bitrate {:d} too low, setting to {:d}", br, SystemCodecInfo::DEFAULT_MIN_BITRATE);
     812           1 :             br = SystemCodecInfo::DEFAULT_MIN_BITRATE;
     813          47 :         } else if (br > SystemCodecInfo::DEFAULT_MAX_BITRATE) {
     814           0 :             JAMI_WARNING("Requested bitrate {:d} too high, setting to {:d}", br, SystemCodecInfo::DEFAULT_MAX_BITRATE);
     815           0 :             br = SystemCodecInfo::DEFAULT_MAX_BITRATE;
     816             :         }
     817             :     }
     818             : 
     819             :     /* Enable libopus FEC encoding support */
     820         226 :     if (mediaType == AVMEDIA_TYPE_AUDIO) {
     821         178 :         if (avcodecId == AV_CODEC_ID_OPUS) {
     822         178 :             initOpus(encoderCtx);
     823             :         }
     824             :     }
     825             : 
     826             :     /* let x264 preset override our encoder settings */
     827         226 :     if (avcodecId == AV_CODEC_ID_H264) {
     828          47 :         const auto* profileLevelId = libav_utils::getDictValue(options_, "parameters");
     829          47 :         extractProfileLevelID(profileLevelId, encoderCtx);
     830          47 :         forcePresetX2645(encoderCtx);
     831          47 :         encoderCtx->flags2 |= AV_CODEC_FLAG2_LOCAL_HEADER;
     832          47 :         initH264(encoderCtx, br);
     833         179 :     } else if (avcodecId == AV_CODEC_ID_HEVC) {
     834           0 :         encoderCtx->profile = FF_PROFILE_HEVC_MAIN;
     835           0 :         forcePresetX2645(encoderCtx);
     836           0 :         initH265(encoderCtx, br);
     837           0 :         av_opt_set_int(encoderCtx, "b_ref_mode", 0, AV_OPT_SEARCH_CHILDREN);
     838         179 :     } else if (avcodecId == AV_CODEC_ID_VP8) {
     839           1 :         initVP8(encoderCtx, br);
     840         178 :     } else if (avcodecId == AV_CODEC_ID_MPEG4) {
     841           0 :         initMPEG4(encoderCtx, br);
     842         178 :     } else if (avcodecId == AV_CODEC_ID_H263) {
     843           0 :         initH263(encoderCtx, br);
     844             :     }
     845         226 :     initAccel(encoderCtx, br);
     846         226 :     return encoderCtx;
     847             : }
     848             : 
     849             : int
     850          31 : MediaEncoder::setBitrate(uint64_t br)
     851             : {
     852          31 :     std::lock_guard lk(encMutex_);
     853          31 :     AVCodecContext* encoderCtx = getCurrentVideoAVCtx();
     854          31 :     if (not encoderCtx)
     855           0 :         return -1; // NOK
     856             : 
     857          31 :     AVCodecID codecId = encoderCtx->codec_id;
     858             : 
     859          31 :     if (not isDynBitrateSupported(codecId))
     860           0 :         return 0; // Restart needed
     861             : 
     862             :     // No need to restart encoder for h264, h263 and MPEG4
     863             :     // Change parameters on the fly
     864          31 :     if (codecId == AV_CODEC_ID_H264)
     865          31 :         initH264(encoderCtx, br);
     866          31 :     if (codecId == AV_CODEC_ID_HEVC)
     867           0 :         initH265(encoderCtx, br);
     868          31 :     else if (codecId == AV_CODEC_ID_H263P)
     869           0 :         initH263(encoderCtx, br);
     870          31 :     else if (codecId == AV_CODEC_ID_MPEG4)
     871           0 :         initMPEG4(encoderCtx, br);
     872             :     else {
     873             :         // restart encoder on runtime doesn't work for VP8
     874             :         // stopEncoder();
     875             :         // encoderCtx = initCodec(codecType, codecId, br);
     876             :         // if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0)
     877             :         //     throw MediaEncoderException("Unable to open encoder");
     878             :     }
     879          31 :     initAccel(encoderCtx, br);
     880          31 :     return 1; // OK
     881          31 : }
     882             : 
     883             : int
     884           0 : MediaEncoder::setPacketLoss(uint64_t pl)
     885             : {
     886           0 :     std::lock_guard lk(encMutex_);
     887           0 :     AVCodecContext* encoderCtx = getCurrentAudioAVCtx();
     888           0 :     if (not encoderCtx)
     889           0 :         return -1; // NOK
     890             : 
     891           0 :     AVCodecID codecId = encoderCtx->codec_id;
     892             : 
     893           0 :     if (not isDynPacketLossSupported(codecId))
     894           0 :         return 0; // Restart needed
     895             : 
     896             :     // Cap between 0 and 100
     897           0 :     pl = std::clamp((int) pl, 0, 100);
     898             : 
     899             :     // Change parameters on the fly
     900           0 :     if (codecId == AV_CODEC_ID_OPUS)
     901           0 :         av_opt_set_int(encoderCtx, "packet_loss", (int64_t) pl, AV_OPT_SEARCH_CHILDREN);
     902           0 :     return 1; // OK
     903           0 : }
     904             : 
     905             : void
     906          78 : MediaEncoder::initH264(AVCodecContext* encoderCtx, uint64_t br)
     907             : {
     908          78 :     int64_t maxBitrate = static_cast<int64_t>(1000 * br);
     909             :     // 200 Kbit/s    -> CRF40
     910             :     // 6 Mbit/s      -> CRF23
     911          78 :     uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + LOGREG_PARAM_B * std::log(maxBitrate));
     912             :     // bufsize parameter impact the variation of the bitrate, reduce to half the maxrate to limit
     913             :     // peak and congestion
     914             :     // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate
     915          78 :     int64_t bufSize = maxBitrate / 2;
     916             : 
     917             :     // If auto quality disabled use CRF mode
     918          78 :     if (mode_ == RateMode::CRF_CONSTRAINED) {
     919          78 :         av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN);
     920          78 :         av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
     921          78 :         av_opt_set_int(encoderCtx, "bufsize", bufSize, AV_OPT_SEARCH_CHILDREN);
     922         312 :         JAMI_DEBUG("H264 encoder setup: crf={:d}, maxrate={:d} kbit/s, bufsize={:d} kbit",
     923             :                    crf,
     924             :                    maxBitrate / 1000,
     925             :                    bufSize / 1000);
     926           0 :     } else if (mode_ == RateMode::CBR) {
     927           0 :         av_opt_set_int(encoderCtx, "b", maxBitrate, AV_OPT_SEARCH_CHILDREN);
     928           0 :         av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
     929           0 :         av_opt_set_int(encoderCtx, "minrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
     930           0 :         av_opt_set_int(encoderCtx, "bufsize", bufSize, AV_OPT_SEARCH_CHILDREN);
     931           0 :         av_opt_set_int(encoderCtx, "crf", -1, AV_OPT_SEARCH_CHILDREN);
     932             : 
     933           0 :         JAMI_DEBUG("H264 encoder setup cbr: bitrate={:d} kbit/s", br);
     934             :     }
     935          78 : }
     936             : 
     937             : void
     938           0 : MediaEncoder::initH265(AVCodecContext* encoderCtx, uint64_t br)
     939             : {
     940             :     // If auto quality disabled use CRF mode
     941           0 :     if (mode_ == RateMode::CRF_CONSTRAINED) {
     942           0 :         int64_t maxBitrate = static_cast<int64_t>(1000 * br);
     943             :         // H265 use 50% less bitrate compared to H264 (half bitrate is equivalent to a change 6 for
     944             :         // CRF) https://slhck.info/video/2017/02/24/crf-guide.html
     945             :         // 200 Kbit/s    -> CRF35
     946             :         // 6 Mbit/s      -> CRF18
     947           0 :         uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A_HEVC + LOGREG_PARAM_B_HEVC * std::log(maxBitrate));
     948           0 :         int64_t bufSize = maxBitrate / 2;
     949           0 :         av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN);
     950           0 :         av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
     951           0 :         av_opt_set_int(encoderCtx, "bufsize", bufSize, AV_OPT_SEARCH_CHILDREN);
     952           0 :         JAMI_DEBUG("H265 encoder setup: crf={:d}, maxrate={:d} kbit/s, bufsize={:d} kbit",
     953             :                    crf,
     954             :                    maxBitrate / 1000,
     955             :                    bufSize / 1000);
     956           0 :     } else if (mode_ == RateMode::CBR) {
     957           0 :         int64_t signedBr = static_cast<int64_t>(br);
     958           0 :         av_opt_set_int(encoderCtx, "b", signedBr * 1000, AV_OPT_SEARCH_CHILDREN);
     959           0 :         av_opt_set_int(encoderCtx, "maxrate", signedBr * 1000, AV_OPT_SEARCH_CHILDREN);
     960           0 :         av_opt_set_int(encoderCtx, "minrate", signedBr * 1000, AV_OPT_SEARCH_CHILDREN);
     961           0 :         av_opt_set_int(encoderCtx, "bufsize", signedBr * 500, AV_OPT_SEARCH_CHILDREN);
     962           0 :         av_opt_set_int(encoderCtx, "crf", -1, AV_OPT_SEARCH_CHILDREN);
     963           0 :         JAMI_DEBUG("H265 encoder setup cbr: bitrate={:d} kbit/s", br);
     964             :     }
     965           0 : }
     966             : 
     967             : void
     968           1 : MediaEncoder::initVP8(AVCodecContext* encoderCtx, uint64_t br)
     969             : {
     970           1 :     if (mode_ == RateMode::CQ) {
     971           0 :         av_opt_set_int(encoderCtx, "g", 120, AV_OPT_SEARCH_CHILDREN);
     972           0 :         av_opt_set_int(encoderCtx, "lag-in-frames", 0, AV_OPT_SEARCH_CHILDREN);
     973           0 :         av_opt_set(encoderCtx, "deadline", "good", AV_OPT_SEARCH_CHILDREN);
     974           0 :         av_opt_set_int(encoderCtx, "cpu-used", 0, AV_OPT_SEARCH_CHILDREN);
     975           0 :         av_opt_set_int(encoderCtx, "vprofile", 0, AV_OPT_SEARCH_CHILDREN);
     976           0 :         av_opt_set_int(encoderCtx, "qmax", 23, AV_OPT_SEARCH_CHILDREN);
     977           0 :         av_opt_set_int(encoderCtx, "qmin", 0, AV_OPT_SEARCH_CHILDREN);
     978           0 :         av_opt_set_int(encoderCtx, "slices", 4, AV_OPT_SEARCH_CHILDREN);
     979           0 :         av_opt_set_int(encoderCtx, "crf", 18, AV_OPT_SEARCH_CHILDREN);
     980           0 :         JAMI_DEBUG("VP8 encoder setup: crf=18");
     981             :     } else {
     982             :         // 1- if quality is set use it
     983             :         // bitrate need to be set. The target bitrate becomes the maximum allowed bitrate
     984             :         // 2- otherwise set rc_max_rate and rc_buffer_size
     985             :         // Using information given on this page:
     986             :         // http://www.webmproject.org/docs/encoder-parameters/
     987           1 :         int64_t maxBitrate = static_cast<int64_t>(1000 * br);
     988             :         // 200 Kbit/s    -> CRF40
     989             :         // 6 Mbit/s      -> CRF23
     990           1 :         uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + LOGREG_PARAM_B * std::log(maxBitrate));
     991           1 :         int64_t bufSize = maxBitrate / 2;
     992             : 
     993           1 :         av_opt_set(encoderCtx, "quality", "realtime", AV_OPT_SEARCH_CHILDREN);
     994           1 :         av_opt_set_int(encoderCtx, "error-resilient", 1, AV_OPT_SEARCH_CHILDREN);
     995           1 :         av_opt_set_int(encoderCtx, "cpu-used", 7,
     996             :                        AV_OPT_SEARCH_CHILDREN); // value obtained from testing
     997           1 :         av_opt_set_int(encoderCtx, "lag-in-frames", 0, AV_OPT_SEARCH_CHILDREN);
     998             :         // allow encoder to drop frames if buffers are full and
     999             :         // to undershoot target bitrate to lessen strain on resources
    1000           1 :         av_opt_set_int(encoderCtx, "drop-frame", 25, AV_OPT_SEARCH_CHILDREN);
    1001           1 :         av_opt_set_int(encoderCtx, "undershoot-pct", 95, AV_OPT_SEARCH_CHILDREN);
    1002             :         // don't set encoderCtx->gop_size: let libvpx decide when to insert a keyframe
    1003           1 :         av_opt_set_int(encoderCtx, "slices", 2, AV_OPT_SEARCH_CHILDREN); // VP8E_SET_TOKEN_PARTITIONS
    1004           1 :         av_opt_set_int(encoderCtx, "qmax", 56, AV_OPT_SEARCH_CHILDREN);
    1005           1 :         av_opt_set_int(encoderCtx, "qmin", 4, AV_OPT_SEARCH_CHILDREN);
    1006           1 :         crf = std::clamp((int) crf, 4, 56);
    1007           1 :         av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN);
    1008           1 :         av_opt_set_int(encoderCtx, "b", maxBitrate, AV_OPT_SEARCH_CHILDREN);
    1009           1 :         av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
    1010           1 :         av_opt_set_int(encoderCtx, "bufsize", bufSize, AV_OPT_SEARCH_CHILDREN);
    1011           4 :         JAMI_DEBUG("VP8 encoder setup: crf={:d}, maxrate={:d}, bufsize={:d}", crf, maxBitrate / 1000, bufSize / 1000);
    1012             :     }
    1013           1 : }
    1014             : 
    1015             : void
    1016           0 : MediaEncoder::initMPEG4(AVCodecContext* encoderCtx, uint64_t br)
    1017             : {
    1018           0 :     int64_t maxBitrate = static_cast<int64_t>(1000 * br);
    1019           0 :     int bufSize = static_cast<int>(maxBitrate / 2);
    1020             : 
    1021             :     // Use CBR (set bitrate)
    1022           0 :     encoderCtx->rc_buffer_size = bufSize;
    1023           0 :     encoderCtx->bit_rate = encoderCtx->rc_min_rate = encoderCtx->rc_max_rate = maxBitrate;
    1024           0 :     JAMI_DEBUG("MPEG4 encoder setup: maxrate={:d}, bufsize={:d}", maxBitrate, bufSize);
    1025           0 : }
    1026             : 
    1027             : void
    1028           0 : MediaEncoder::initH263(AVCodecContext* encoderCtx, uint64_t br)
    1029             : {
    1030           0 :     int64_t maxBitrate = static_cast<int64_t>(1000 * br);
    1031           0 :     int bufSize = static_cast<int>(maxBitrate) / 2;
    1032             : 
    1033             :     // Use CBR (set bitrate)
    1034           0 :     encoderCtx->rc_buffer_size = bufSize;
    1035           0 :     encoderCtx->bit_rate = encoderCtx->rc_min_rate = encoderCtx->rc_max_rate = maxBitrate;
    1036           0 :     JAMI_DEBUG("H263 encoder setup: maxrate={:d}, bufsize={:d}", maxBitrate, bufSize);
    1037           0 : }
    1038             : 
    1039             : void
    1040         178 : MediaEncoder::initOpus(AVCodecContext* encoderCtx)
    1041             : {
    1042             :     // Enable FEC support by default with 10% packet loss
    1043         178 :     av_opt_set_int(encoderCtx, "fec", fecEnabled_ ? 1 : 0, AV_OPT_SEARCH_CHILDREN);
    1044         178 :     av_opt_set_int(encoderCtx, "packet_loss", 10, AV_OPT_SEARCH_CHILDREN);
    1045         178 : }
    1046             : 
    1047             : void
    1048         257 : MediaEncoder::initAccel(AVCodecContext* encoderCtx, uint64_t br)
    1049             : {
    1050             : #ifdef ENABLE_HWACCEL
    1051         257 :     float val = static_cast<float>(br) * 1000.0f * 0.8f;
    1052         257 :     if (not accel_)
    1053         257 :         return;
    1054           0 :     if (accel_->getName() == "nvenc"sv) {
    1055             :         // Use same parameters as software
    1056           0 :     } else if (accel_->getName() == "vaapi"sv) {
    1057             :         // Use VBR encoding with bitrate target set to 80% of the maxrate
    1058           0 :         av_opt_set_int(encoderCtx, "crf", -1, AV_OPT_SEARCH_CHILDREN);
    1059           0 :         av_opt_set_int(encoderCtx, "b", static_cast<int64_t>(val), AV_OPT_SEARCH_CHILDREN);
    1060           0 :     } else if (accel_->getName() == "videotoolbox"sv) {
    1061           0 :         av_opt_set_int(encoderCtx, "b", static_cast<int64_t>(val), AV_OPT_SEARCH_CHILDREN);
    1062           0 :     } else if (accel_->getName() == "qsv"sv) {
    1063             :         // Use Video Conferencing Mode
    1064           0 :         av_opt_set_int(encoderCtx, "vcm", 1, AV_OPT_SEARCH_CHILDREN);
    1065           0 :         av_opt_set_int(encoderCtx, "b", static_cast<int64_t>(val), AV_OPT_SEARCH_CHILDREN);
    1066             :     }
    1067             : #endif
    1068             : }
    1069             : 
    1070             : AVCodecContext*
    1071          31 : MediaEncoder::getCurrentVideoAVCtx()
    1072             : {
    1073          31 :     for (auto* it : encoders_) {
    1074          31 :         if (it->codec_type == AVMEDIA_TYPE_VIDEO)
    1075          31 :             return it;
    1076             :     }
    1077           0 :     return nullptr;
    1078             : }
    1079             : 
    1080             : AVCodecContext*
    1081           1 : MediaEncoder::getCurrentAudioAVCtx()
    1082             : {
    1083           1 :     for (auto* it : encoders_) {
    1084           1 :         if (it->codec_type == AVMEDIA_TYPE_AUDIO)
    1085           1 :             return it;
    1086             :     }
    1087           0 :     return nullptr;
    1088             : }
    1089             : 
    1090             : void
    1091           0 : MediaEncoder::stopEncoder()
    1092             : {
    1093           0 :     flush();
    1094           0 :     for (auto it = encoders_.begin(); it != encoders_.end(); it++) {
    1095           0 :         if ((*it)->codec_type == AVMEDIA_TYPE_VIDEO) {
    1096           0 :             encoders_.erase(it);
    1097           0 :             break;
    1098             :         }
    1099             :     }
    1100           0 :     AVCodecContext* encoderCtx = getCurrentVideoAVCtx();
    1101           0 :     avcodec_close(encoderCtx);
    1102           0 :     avcodec_free_context(&encoderCtx);
    1103           0 :     av_free(encoderCtx);
    1104           0 : }
    1105             : 
    1106             : bool
    1107          31 : MediaEncoder::isDynBitrateSupported(AVCodecID codecid)
    1108             : {
    1109             : #ifdef ENABLE_HWACCEL
    1110          31 :     if (accel_) {
    1111           0 :         return accel_->dynBitrate();
    1112             :     }
    1113             : #endif
    1114          31 :     if (codecid != AV_CODEC_ID_VP8)
    1115          31 :         return true;
    1116             : 
    1117           0 :     return false;
    1118             : }
    1119             : 
    1120             : bool
    1121           0 : MediaEncoder::isDynPacketLossSupported(AVCodecID codecid)
    1122             : {
    1123           0 :     if (codecid == AV_CODEC_ID_OPUS)
    1124           0 :         return true;
    1125             : 
    1126           0 :     return false;
    1127             : }
    1128             : 
    1129             : void
    1130         226 : MediaEncoder::readConfig(AVCodecContext* encoderCtx)
    1131             : {
    1132         226 :     auto path = fileutils::get_config_dir() / "encoder.json";
    1133         226 :     std::string name = encoderCtx->codec->name;
    1134         226 :     std::error_code ec;
    1135         226 :     if (std::filesystem::is_regular_file(path, ec)) {
    1136           0 :         JAMI_WARN("encoder.json file found, default settings will be erased");
    1137             :         try {
    1138           0 :             Json::Value root;
    1139           0 :             std::ifstream file(path);
    1140           0 :             file >> root;
    1141           0 :             if (!root.isObject()) {
    1142           0 :                 JAMI_ERR() << "Invalid encoder configuration: root is not an object";
    1143           0 :                 return;
    1144             :             }
    1145           0 :             const auto& config = root[name];
    1146           0 :             if (config.isNull()) {
    1147           0 :                 JAMI_WARN() << "Encoder '" << name << "' not found in configuration file";
    1148           0 :                 return;
    1149             :             }
    1150           0 :             if (!config.isObject()) {
    1151           0 :                 JAMI_ERR() << "Invalid encoder configuration: '" << name << "' is not an object";
    1152           0 :                 return;
    1153             :             }
    1154           0 :             for (Json::Value::const_iterator it = config.begin(); it != config.end(); ++it) {
    1155           0 :                 const Json::Value& v = *it;
    1156           0 :                 if (!it.key().isConvertibleTo(Json::ValueType::stringValue)
    1157           0 :                     || !v.isConvertibleTo(Json::ValueType::stringValue)) {
    1158           0 :                     JAMI_ERR() << "Invalid configuration for '" << name << "'";
    1159           0 :                     return;
    1160             :                 }
    1161           0 :                 const auto& key = it.key().asString();
    1162           0 :                 const auto& value = v.asString();
    1163           0 :                 int ret = av_opt_set(reinterpret_cast<void*>(encoderCtx),
    1164             :                                      key.c_str(),
    1165             :                                      value.c_str(),
    1166             :                                      AV_OPT_SEARCH_CHILDREN);
    1167           0 :                 if (ret < 0) {
    1168           0 :                     JAMI_ERR() << "Failed to set option " << key << " in " << name
    1169           0 :                                << " context: " << libav_utils::getError(ret) << "\n";
    1170             :                 }
    1171           0 :             }
    1172           0 :         } catch (const Json::Exception& e) {
    1173           0 :             JAMI_ERR() << "Failed to load encoder configuration file: " << e.what();
    1174           0 :         }
    1175             :     }
    1176         226 : }
    1177             : 
    1178             : std::string
    1179         268 : MediaEncoder::testH265Accel()
    1180             : {
    1181             : #ifdef ENABLE_HWACCEL
    1182         268 :     if (jami::Manager::instance().videoPreferences.getEncodingAccelerated()) {
    1183             :         // Get compatible list of Hardware API
    1184           0 :         auto APIs = video::HardwareAccel::getCompatibleAccel(AV_CODEC_ID_H265, 1280, 720, CODEC_ENCODER);
    1185             : 
    1186           0 :         std::unique_ptr<video::HardwareAccel> accel;
    1187             : 
    1188           0 :         for (const auto& it : APIs) {
    1189           0 :             accel = std::make_unique<video::HardwareAccel>(it); // save accel
    1190             :             // Init codec need accel to init encoderCtx accelerated
    1191           0 :             const auto* outputCodec = avcodec_find_encoder_by_name(accel->getCodecName().c_str());
    1192             : 
    1193           0 :             AVCodecContext* encoderCtx = avcodec_alloc_context3(outputCodec);
    1194           0 :             encoderCtx->thread_count = static_cast<int>(std::min(std::thread::hardware_concurrency(), 16u));
    1195           0 :             encoderCtx->width = 1280;
    1196           0 :             encoderCtx->height = 720;
    1197             :             AVRational framerate;
    1198           0 :             framerate.num = 30;
    1199           0 :             framerate.den = 1;
    1200           0 :             encoderCtx->time_base = av_inv_q(framerate);
    1201           0 :             encoderCtx->pix_fmt = accel->getFormat();
    1202           0 :             encoderCtx->profile = FF_PROFILE_HEVC_MAIN;
    1203           0 :             encoderCtx->opaque = accel.get();
    1204             : 
    1205           0 :             auto br = static_cast<int64_t>(SystemCodecInfo::DEFAULT_VIDEO_BITRATE);
    1206           0 :             av_opt_set_int(encoderCtx, "b", br * 1000, AV_OPT_SEARCH_CHILDREN);
    1207           0 :             av_opt_set_int(encoderCtx, "maxrate", br * 1000, AV_OPT_SEARCH_CHILDREN);
    1208           0 :             av_opt_set_int(encoderCtx, "minrate", br * 1000, AV_OPT_SEARCH_CHILDREN);
    1209           0 :             av_opt_set_int(encoderCtx, "bufsize", br * 500, AV_OPT_SEARCH_CHILDREN);
    1210           0 :             av_opt_set_int(encoderCtx, "crf", -1, AV_OPT_SEARCH_CHILDREN);
    1211             : 
    1212           0 :             auto ret = accel->initAPI(false, nullptr);
    1213           0 :             if (ret < 0) {
    1214           0 :                 accel.reset();
    1215             :                 ;
    1216           0 :                 encoderCtx = nullptr;
    1217           0 :                 continue;
    1218             :             }
    1219           0 :             accel->setDetails(encoderCtx);
    1220           0 :             if (avcodec_open2(encoderCtx, outputCodec, nullptr) < 0) {
    1221             :                 // Failed to open codec
    1222           0 :                 JAMI_WARN("Fail to open hardware encoder H265 with %s ", it.getName().c_str());
    1223           0 :                 avcodec_free_context(&encoderCtx);
    1224           0 :                 encoderCtx = nullptr;
    1225           0 :                 accel = nullptr;
    1226           0 :                 continue;
    1227             :             } else {
    1228             :                 // Succeed to open codec
    1229           0 :                 avcodec_free_context(&encoderCtx);
    1230           0 :                 encoderCtx = nullptr;
    1231           0 :                 accel = nullptr;
    1232           0 :                 return it.getName();
    1233             :             }
    1234             :         }
    1235           0 :     }
    1236             : #endif
    1237         268 :     return "";
    1238             : }
    1239             : 
    1240             : #ifdef ENABLE_VIDEO
    1241             : int
    1242        5104 : MediaEncoder::getHWFrame(const std::shared_ptr<VideoFrame>& input, std::shared_ptr<VideoFrame>& output)
    1243             : {
    1244             :     try {
    1245             : #if defined(TARGET_OS_IOS) && TARGET_OS_IOS
    1246             :         // iOS
    1247             :         if (accel_) {
    1248             :             auto pix = accel_->getSoftwareFormat();
    1249             :             if (input->format() != pix) {
    1250             :                 output = scaler_.convertFormat(*input.get(), pix);
    1251             :             } else {
    1252             :                 // Fully accelerated pipeline, skip main memory
    1253             :                 output = input;
    1254             :             }
    1255             :         } else {
    1256             :             output = getScaledSWFrame(*input.get());
    1257             :         }
    1258             : #elif !defined(__APPLE__) && defined(ENABLE_HWACCEL)
    1259             :         // Other Platforms
    1260        5104 :         const auto* desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format()));
    1261        5104 :         bool isHardware = desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL);
    1262        5104 :         if (accel_ && accel_->isLinked() && isHardware) {
    1263             :             // Fully accelerated pipeline, skip main memory
    1264           0 :             output = input;
    1265        5104 :         } else if (isHardware) {
    1266             :             // Hardware decoded frame, transfer back to main memory
    1267             :             // Transfer to GPU if we have a hardware encoder
    1268             :             // Hardware decoders decode to NV12, but Jami's supported software encoders want YUV420P
    1269           0 :             output = getUnlinkedHWFrame(*input.get());
    1270        5104 :         } else if (accel_) {
    1271             :             // Software decoded frame with a hardware encoder, convert to accepted format first
    1272           0 :             output = getHWFrameFromSWFrame(*input.get());
    1273             :         } else {
    1274        5104 :             output = getScaledSWFrame(*input.get());
    1275             :         }
    1276             : #else
    1277             :         // macOS
    1278             :         output = getScaledSWFrame(*input.get());
    1279             : #endif
    1280           0 :     } catch (const std::runtime_error& e) {
    1281           0 :         JAMI_ERR("Accel failure: %s", e.what());
    1282           0 :         return -1;
    1283           0 :     }
    1284             : 
    1285        5104 :     return 0;
    1286             : }
    1287             : 
    1288             : #ifdef ENABLE_HWACCEL
    1289             : std::shared_ptr<VideoFrame>
    1290           0 : MediaEncoder::getUnlinkedHWFrame(const VideoFrame& input)
    1291             : {
    1292           0 :     AVPixelFormat pix = (accel_ ? accel_->getSoftwareFormat() : AV_PIX_FMT_NV12);
    1293           0 :     std::shared_ptr<VideoFrame> framePtr = video::HardwareAccel::transferToMainMemory(input, pix);
    1294           0 :     if (!accel_) {
    1295           0 :         framePtr = scaler_.convertFormat(*framePtr, AV_PIX_FMT_YUV420P);
    1296             :     } else {
    1297           0 :         framePtr = accel_->transfer(*framePtr);
    1298             :     }
    1299           0 :     return framePtr;
    1300           0 : }
    1301             : 
    1302             : std::shared_ptr<VideoFrame>
    1303           0 : MediaEncoder::getHWFrameFromSWFrame(const VideoFrame& input)
    1304             : {
    1305           0 :     std::shared_ptr<VideoFrame> framePtr;
    1306           0 :     auto pix = accel_->getSoftwareFormat();
    1307           0 :     if (input.format() != pix) {
    1308           0 :         framePtr = scaler_.convertFormat(input, pix);
    1309           0 :         framePtr = accel_->transfer(*framePtr);
    1310             :     } else {
    1311           0 :         framePtr = accel_->transfer(input);
    1312             :     }
    1313           0 :     return framePtr;
    1314           0 : }
    1315             : #endif
    1316             : 
    1317             : std::shared_ptr<VideoFrame>
    1318        5104 : MediaEncoder::getScaledSWFrame(const VideoFrame& input)
    1319             : {
    1320        5104 :     libav_utils::fillWithBlack(scaledFrame_->pointer());
    1321        5104 :     scaler_.scale_with_aspect(input, *scaledFrame_);
    1322        5104 :     return scaledFrame_;
    1323             : }
    1324             : #endif
    1325             : 
    1326             : void
    1327           0 : MediaEncoder::resetStreams(int width, int height)
    1328             : {
    1329           0 :     videoOpts_.width = width;
    1330           0 :     videoOpts_.height = height;
    1331             : 
    1332             :     try {
    1333           0 :         flush();
    1334           0 :         initialized_ = false;
    1335           0 :         if (outputCtx_) {
    1336           0 :             for (auto* encoderCtx : encoders_) {
    1337           0 :                 if (encoderCtx) {
    1338             : #ifndef _MSC_VER
    1339           0 :                     avcodec_free_context(&encoderCtx);
    1340             : #else
    1341             :                     avcodec_close(encoderCtx);
    1342             : #endif
    1343             :                 }
    1344             :             }
    1345           0 :             encoders_.clear();
    1346             :         }
    1347           0 :     } catch (const std::exception& e) {
    1348           0 :         JAMI_WARNING("Couldn't reset streams: {}", e.what());
    1349           0 :     }
    1350           0 : }
    1351             : 
    1352             : } // namespace jami

Generated by: LCOV version 1.14