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: 380 719 52.9 %
Date: 2025-12-18 10:07:43 Functions: 43 70 61.4 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2025 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/ring_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         329 : MediaEncoder::MediaEncoder()
      60         329 :     : outputCtx_(avformat_alloc_context())
      61             : {
      62         330 :     JAMI_DBG("[%p] New instance created", this);
      63         330 : }
      64             : 
      65         330 : MediaEncoder::~MediaEncoder()
      66             : {
      67         330 :     if (outputCtx_) {
      68         330 :         if (outputCtx_->priv_data && outputCtx_->pb)
      69          50 :             av_write_trailer(outputCtx_);
      70         330 :         if (fileIO_) {
      71           1 :             avio_close(outputCtx_->pb);
      72             :         }
      73         563 :         for (auto encoderCtx : encoders_) {
      74         233 :             if (encoderCtx) {
      75             : #ifndef _MSC_VER
      76         233 :                 avcodec_free_context(&encoderCtx);
      77             : #else
      78             :                 avcodec_close(encoderCtx);
      79             : #endif
      80             :             }
      81             :         }
      82         330 :         avformat_free_context(outputCtx_);
      83             :     }
      84         330 :     av_dict_free(&options_);
      85             : 
      86         330 :     JAMI_DBG("[%p] Instance destroyed", this);
      87         330 : }
      88             : 
      89             : void
      90         333 : MediaEncoder::setOptions(const MediaStream& opts)
      91             : {
      92         333 :     if (!opts.isValid()) {
      93           0 :         JAMI_ERR() << "Invalid options";
      94           0 :         return;
      95             :     }
      96             : 
      97         333 :     if (opts.isVideo) {
      98         150 :         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         150 :         videoOpts_.width = ((videoOpts_.width >> 3) << 3);
     103         150 :         videoOpts_.height = ((videoOpts_.height >> 3) << 3);
     104         150 :         if (!videoOpts_.frameRate)
     105          98 :             videoOpts_.frameRate = 30;
     106         150 :         if (!videoOpts_.bitrate) {
     107          51 :             videoOpts_.bitrate = SystemCodecInfo::DEFAULT_VIDEO_BITRATE;
     108             :         }
     109             :     } else {
     110         183 :         audioOpts_ = opts;
     111             :     }
     112             : }
     113             : 
     114             : void
     115         329 : MediaEncoder::setOptions(const MediaDescription& args)
     116             : {
     117             :     int ret;
     118         658 :     if (args.payload_type
     119         656 :         and (ret = av_opt_set_int(reinterpret_cast<void*>(outputCtx_),
     120             :                                   "payload_type",
     121         327 :                                   args.payload_type,
     122             :                                   AV_OPT_SEARCH_CHILDREN)
     123         327 :                    < 0))
     124           0 :         JAMI_ERR() << "Failed to set payload type: " << libav_utils::getError(ret);
     125             : 
     126         329 :     if (not args.parameters.empty())
     127         147 :         libav_utils::setDictValue(&options_, "parameters", args.parameters);
     128             : 
     129         329 :     mode_ = args.mode;
     130         329 :     linkableHW_ = args.linkableHW;
     131         329 :     fecEnabled_ = args.fecEnabled;
     132         329 : }
     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         327 : MediaEncoder::setInitSeqVal(uint16_t seqVal)
     145             : {
     146             :     // only set not default value (!=0)
     147         327 :     if (seqVal != 0)
     148         147 :         av_opt_set_int(outputCtx_, "seq", seqVal, AV_OPT_SEARCH_CHILDREN);
     149         327 : }
     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         330 : MediaEncoder::openOutput(const std::string& filename, const std::string& format)
     163             : {
     164         330 :     avformat_free_context(outputCtx_);
     165         987 :     int result = avformat_alloc_output_context2(&outputCtx_,
     166             :                                                 nullptr,
     167         657 :                                                 format.empty() ? nullptr : format.c_str(),
     168             :                                                 filename.c_str());
     169         330 :     if (result < 0)
     170           0 :         JAMI_ERR() << "Unable to open " << filename << ": " << libav_utils::getError(-result);
     171         330 : }
     172             : 
     173             : int
     174         333 : MediaEncoder::addStream(const SystemCodecInfo& systemCodecInfo)
     175             : {
     176         333 :     if (systemCodecInfo.mediaType == MEDIA_AUDIO) {
     177         183 :         audioCodec_ = systemCodecInfo.name;
     178             :     } else {
     179         150 :         videoCodec_ = systemCodecInfo.name;
     180             :     }
     181             : 
     182         333 :     auto stream = avformat_new_stream(outputCtx_, outputCodec_);
     183             : 
     184         333 :     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         333 :     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         333 :     if (systemCodecInfo.mediaType == MEDIA_AUDIO) {
     193         183 :         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         150 :     if (audioOpts_.isValid())
     201           3 :         return 1;
     202             :     else
     203         147 :         return 0;
     204             : }
     205             : 
     206             : int
     207          50 : MediaEncoder::initStream(const std::string& codecName, AVBufferRef* framesCtx)
     208             : {
     209          50 :     const auto codecInfo = getSystemCodecContainer()->searchCodecByName(codecName, MEDIA_ALL);
     210          50 :     if (codecInfo)
     211          50 :         return initStream(*codecInfo, framesCtx);
     212             :     else
     213           0 :         return -1;
     214          50 : }
     215             : 
     216             : int
     217         233 : MediaEncoder::initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* framesCtx)
     218             : {
     219         233 :     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         233 :     std::lock_guard lk(encMutex_);
     226             : 
     227         233 :     if (!outputCtx_)
     228           0 :         throw MediaEncoderException("Unable to allocate stream");
     229             : 
     230             :     // Must already have codec instance(s)
     231         233 :     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         233 :     AVCodecContext* encoderCtx = nullptr;
     237             :     AVMediaType mediaType;
     238             : 
     239         233 :     if (systemCodecInfo.mediaType == MEDIA_VIDEO)
     240          50 :         mediaType = AVMEDIA_TYPE_VIDEO;
     241         183 :     else if (systemCodecInfo.mediaType == MEDIA_AUDIO)
     242         183 :         mediaType = AVMEDIA_TYPE_AUDIO;
     243             :     else
     244           0 :         throw MediaEncoderException("Unsuported media type");
     245             : 
     246         233 :     AVStream* stream {nullptr};
     247             : 
     248             :     // Only supports one audio and one video streams at most per instance.
     249         467 :     for (unsigned i = 0; i < outputCtx_->nb_streams; i++) {
     250         234 :         stream = outputCtx_->streams[i];
     251         234 :         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         233 :     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         233 :     currentStreamIdx_ = stream->index;
     268             : #ifdef ENABLE_HWACCEL
     269             :     // Get compatible list of Hardware API
     270         233 :     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         233 :     if (!encoderCtx) {
     317         233 :         JAMI_WARN("Not using hardware encoding for %s",
     318             :                   avcodec_get_name(static_cast<AVCodecID>(systemCodecInfo.avcodecId)));
     319         233 :         encoderCtx = initCodec(mediaType, static_cast<AVCodecID>(systemCodecInfo.avcodecId), videoOpts_.bitrate);
     320         233 :         readConfig(encoderCtx);
     321         233 :         encoders_.emplace_back(encoderCtx);
     322         233 :         if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0)
     323           0 :             throw MediaEncoderException("Unable to open encoder");
     324             :     }
     325             : 
     326         233 :     avcodec_parameters_from_context(stream->codecpar, encoderCtx);
     327             : 
     328             :     // framerate is not copied from encoderCtx to stream
     329         233 :     stream->avg_frame_rate = encoderCtx->framerate;
     330             : #ifdef ENABLE_VIDEO
     331         233 :     if (systemCodecInfo.mediaType == MEDIA_VIDEO) {
     332             :         // allocate buffers for both scaled (pre-encoder) and encoded frames
     333          50 :         const int width = encoderCtx->width;
     334          50 :         const int height = encoderCtx->height;
     335          50 :         int format = encoderCtx->pix_fmt;
     336             : #ifdef ENABLE_HWACCEL
     337          50 :         if (accel_) {
     338             :             // hardware encoders require a specific pixel format
     339           0 :             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          50 :         scaledFrameBufferSize_ = videoFrameSize(format, width, height);
     345          50 :         if (scaledFrameBufferSize_ < 0)
     346           0 :             throw MediaEncoderException(
     347           0 :                 ("Unable to compute buffer size: " + libav_utils::getError(scaledFrameBufferSize_)).c_str());
     348          50 :         else if (scaledFrameBufferSize_ <= AV_INPUT_BUFFER_MIN_SIZE)
     349           0 :             throw MediaEncoderException("buffer too small");
     350             : 
     351          50 :         scaledFrameBuffer_.resize(scaledFrameBufferSize_);
     352          50 :         scaledFrame_ = std::make_shared<VideoFrame>();
     353          50 :         scaledFrame_->setFromMemory(scaledFrameBuffer_.data(), format, width, height);
     354             :     }
     355             : #endif // ENABLE_VIDEO
     356             : 
     357         233 :     return stream->index;
     358         233 : }
     359             : 
     360             : void
     361          50 : MediaEncoder::openIOContext()
     362             : {
     363          50 :     if (ioCtx_) {
     364          49 :         outputCtx_->pb = ioCtx_;
     365          49 :         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          50 : }
     382             : 
     383             : void
     384          50 : MediaEncoder::startIO()
     385             : {
     386          50 :     if (!outputCtx_->pb)
     387          50 :         openIOContext();
     388          50 :     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          50 :     av_dump_format(outputCtx_, 0, outputCtx_->url, 1);
     395             : #else
     396             :     av_dump_format(outputCtx_, 0, outputCtx_->filename, 1);
     397             : #endif
     398          50 :     initialized_ = true;
     399          50 : }
     400             : 
     401             : #ifdef ENABLE_VIDEO
     402             : int
     403        5751 : MediaEncoder::encode(const std::shared_ptr<VideoFrame>& input, bool is_keyframe, int64_t frame_number)
     404             : {
     405        5751 :     auto width = (input->width() >> 3) << 3;
     406        5751 :     auto height = (input->height() >> 3) << 3;
     407        5751 :     if (initialized_ && (getWidth() != width || getHeight() != height)) {
     408           0 :         resetStreams(width, height);
     409           0 :         is_keyframe = true;
     410             :     }
     411             : 
     412        5751 :     if (!initialized_) {
     413          49 :         initStream(videoCodec_, input->pointer()->hw_frames_ctx);
     414          49 :         startIO();
     415             :     }
     416             : 
     417        5751 :     std::shared_ptr<VideoFrame> output;
     418             : #ifdef ENABLE_HWACCEL
     419        5751 :     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        5751 :     if (!output) {
     428           0 :         JAMI_ERR("Fail to get frame");
     429           0 :         return -1;
     430             :     }
     431        5751 :     auto avframe = output->pointer();
     432             : 
     433        5751 :     AVCodecContext* enc = encoders_[currentStreamIdx_];
     434        5751 :     avframe->pts = frame_number;
     435        5751 :     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        5751 :     if (is_keyframe) {
     439          76 :         avframe->pict_type = AV_PICTURE_TYPE_I;
     440          76 :         avframe->key_frame = 1;
     441             :     } else {
     442        5675 :         avframe->pict_type = AV_PICTURE_TYPE_NONE;
     443        5675 :         avframe->key_frame = 0;
     444             :     }
     445             : 
     446        5751 :     return encode(avframe, currentStreamIdx_);
     447        5751 : }
     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        5807 : MediaEncoder::encode(AVFrame* frame, int streamIdx)
     468             : {
     469        5807 :     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        5806 :     int ret = 0;
     484        5806 :     if (static_cast<size_t>(streamIdx) >= encoders_.size())
     485           2 :         return -1;
     486        5804 :     AVCodecContext* encoderCtx = encoders_[streamIdx];
     487             :     AVPacket pkt;
     488        5804 :     av_init_packet(&pkt);
     489        5804 :     pkt.data = nullptr; // packet data will be allocated by the encoder
     490        5804 :     pkt.size = 0;
     491             : 
     492        5804 :     if (!encoderCtx)
     493           0 :         return -1;
     494             : 
     495        5804 :     ret = avcodec_send_frame(encoderCtx, frame);
     496        5804 :     if (ret < 0)
     497           0 :         return -1;
     498             : 
     499        5807 :     while (ret >= 0) {
     500        5804 :         ret = avcodec_receive_packet(encoderCtx, &pkt);
     501        5804 :         if (ret == AVERROR(EAGAIN))
     502           0 :             break;
     503        5804 :         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        5804 :         if (pkt.size) {
     509        5801 :             if (send(pkt, streamIdx))
     510        5801 :                 break;
     511             :         }
     512             :     }
     513             : 
     514        5804 :     av_packet_unref(&pkt);
     515        5804 :     return 0;
     516             : }
     517             : 
     518             : bool
     519        5801 : MediaEncoder::send(AVPacket& pkt, int streamIdx)
     520             : {
     521        5801 :     if (!initialized_) {
     522           0 :         streamIdx = initStream(videoCodec_);
     523           0 :         startIO();
     524             :     }
     525        5801 :     if (streamIdx < 0)
     526           0 :         streamIdx = currentStreamIdx_;
     527        5801 :     if (streamIdx >= 0 and static_cast<size_t>(streamIdx) < encoders_.size()
     528       11602 :         and static_cast<unsigned int>(streamIdx) < outputCtx_->nb_streams) {
     529        5801 :         auto encoderCtx = encoders_[streamIdx];
     530        5801 :         pkt.stream_index = streamIdx;
     531        5801 :         if (pkt.pts != AV_NOPTS_VALUE)
     532        5801 :             pkt.pts = av_rescale_q(pkt.pts, encoderCtx->time_base, outputCtx_->streams[streamIdx]->time_base);
     533        5801 :         if (pkt.dts != AV_NOPTS_VALUE)
     534        5801 :             pkt.dts = av_rescale_q(pkt.dts, encoderCtx->time_base, outputCtx_->streams[streamIdx]->time_base);
     535             :     }
     536             :     // write the compressed frame
     537        5801 :     auto ret = av_write_frame(outputCtx_, &pkt);
     538        5801 :     if (ret < 0) {
     539           0 :         JAMI_ERR() << "av_write_frame failed: " << libav_utils::getError(ret);
     540             :     }
     541        5801 :     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, i) < 0) {
     550           2 :             JAMI_ERR() << "Unable to flush stream #" << i;
     551           2 :             ret |= 1u << 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         233 : MediaEncoder::prepareEncoderContext(const AVCodec* outputCodec, bool is_video)
     582             : {
     583         233 :     AVCodecContext* encoderCtx = avcodec_alloc_context3(outputCodec);
     584             : 
     585         233 :     auto encoderName = outputCodec->name; // guaranteed to be non null if AVCodec is not null
     586             : 
     587         233 :     encoderCtx->thread_count = std::min(std::thread::hardware_concurrency(), is_video ? 16u : 4u);
     588         233 :     JAMI_DBG("[%s] Using %d threads", encoderName, encoderCtx->thread_count);
     589             : 
     590         233 :     if (is_video) {
     591             :         // resolution must be a multiple of two
     592          50 :         encoderCtx->width = videoOpts_.width;
     593          50 :         encoderCtx->height = videoOpts_.height;
     594             : 
     595             :         // satisfy ffmpeg: denominator must be 16bit or less value
     596             :         // time base = 1/FPS
     597          50 :         av_reduce(&encoderCtx->framerate.num,
     598             :                   &encoderCtx->framerate.den,
     599          50 :                   videoOpts_.frameRate.numerator(),
     600          50 :                   videoOpts_.frameRate.denominator(),
     601             :                   (1U << 16) - 1);
     602          50 :         encoderCtx->time_base = av_inv_q(encoderCtx->framerate);
     603             : 
     604             :         // emit one intra frame every gop_size frames
     605          50 :         encoderCtx->max_b_frames = 0;
     606          50 :         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          50 :         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         732 :         JAMI_WARNING("Codec format: {} {} {} {}",
     625             :                      encoderName,
     626             :                      audioOpts_.format,
     627             :                      audioOpts_.sampleRate,
     628             :                      audioOpts_.nbChannels);
     629         183 :         encoderCtx->sample_fmt = (AVSampleFormat) audioOpts_.format;
     630         183 :         encoderCtx->sample_rate = std::max(8000, audioOpts_.sampleRate);
     631         183 :         encoderCtx->time_base = AVRational {1, encoderCtx->sample_rate};
     632         183 :         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         183 :         av_channel_layout_default(&encoderCtx->ch_layout, audioOpts_.nbChannels);
     637         183 :         if (audioOpts_.frameSize) {
     638         181 :             encoderCtx->frame_size = audioOpts_.frameSize;
     639         181 :             JAMI_DBG() << "[" << encoderName << "] Frame size " << encoderCtx->frame_size;
     640             :         } else {
     641           2 :             JAMI_WARN() << "[" << encoderName << "] Frame size not set";
     642             :         }
     643             :     }
     644             : 
     645         233 :     return encoderCtx;
     646             : }
     647             : 
     648             : void
     649          49 : MediaEncoder::forcePresetX2645(AVCodecContext* encoderCtx)
     650             : {
     651             : #ifdef ENABLE_HWACCEL
     652          49 :     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          49 :         const char* speedPreset = "veryfast";
     666             : #endif
     667          49 :         if (av_opt_set(encoderCtx, "preset", speedPreset, AV_OPT_SEARCH_CHILDREN))
     668           0 :             JAMI_WARN("Failed to set preset '%s'", speedPreset);
     669          49 :         const char* tune = "zerolatency";
     670          49 :         if (av_opt_set(encoderCtx, "tune", tune, AV_OPT_SEARCH_CHILDREN))
     671           0 :             JAMI_WARN("Failed to set tune '%s'", tune);
     672             :     }
     673          49 : }
     674             : 
     675             : void
     676          49 : 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          49 :     ctx->profile = FF_PROFILE_H264_CONSTRAINED_BASELINE;
     682          49 :     ctx->level = 0x0d;
     683             :     // ctx->level = 0x0d; // => 13 aka 1.3
     684          49 :     if (parameters.empty())
     685           0 :         return;
     686             : 
     687          49 :     const std::string target("profile-level-id=");
     688          49 :     size_t needle = parameters.find(target);
     689          49 :     if (needle == std::string::npos)
     690           0 :         return;
     691             : 
     692          49 :     needle += target.length();
     693          49 :     const size_t id_length = 6; /* digits */
     694          49 :     const std::string profileLevelID(parameters.substr(needle, id_length));
     695          49 :     if (profileLevelID.length() != id_length)
     696           0 :         return;
     697             : 
     698             :     int result;
     699          49 :     std::stringstream ss;
     700          49 :     ss << profileLevelID;
     701          49 :     ss >> std::hex >> result;
     702             :     // profile-level id consists of three bytes
     703          49 :     const unsigned char profile_idc = result >> 16;           // 42xxxx -> 42
     704          49 :     const unsigned char profile_iop = ((result >> 8) & 0xff); // xx80xx -> 80
     705          49 :     ctx->level = result & 0xff;                               // xxxx0d -> 0d
     706          49 :     switch (profile_idc) {
     707          49 :     case FF_PROFILE_H264_BASELINE:
     708             :         // check constraint_set_1_flag
     709          49 :         if ((profile_iop & 0x40) >> 6)
     710           0 :             ctx->profile |= FF_PROFILE_H264_CONSTRAINED;
     711          49 :         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             :     }
     720          49 :     JAMI_DBG("Using profile %s (%x) and level %d",
     721             :              avcodec_profile_name(AV_CODEC_ID_H264, ctx->profile),
     722             :              ctx->profile,
     723             :              ctx->level);
     724          49 : }
     725             : 
     726             : #ifdef ENABLE_HWACCEL
     727             : void
     728         149 : MediaEncoder::enableAccel(bool enableAccel)
     729             : {
     730         149 :     enableAccel_ = enableAccel;
     731         149 :     emitSignal<libjami::ConfigurationSignal::HardwareEncodingChanged>(enableAccel_);
     732         149 :     if (!enableAccel_) {
     733         149 :         accel_.reset();
     734         149 :         for (auto enc : encoders_)
     735           0 :             enc->opaque = nullptr;
     736             :     }
     737         149 : }
     738             : #endif
     739             : 
     740             : unsigned
     741           2 : MediaEncoder::getStreamCount() const
     742             : {
     743           2 :     return (audioOpts_.isValid() + videoOpts_.isValid());
     744             : }
     745             : 
     746             : MediaStream
     747           0 : MediaEncoder::getStream(const std::string& name, int streamIdx) const
     748             : {
     749             :     // if streamIdx is negative, use currentStreamIdx_
     750           0 :     if (streamIdx < 0)
     751           0 :         streamIdx = currentStreamIdx_;
     752             :     // make sure streamIdx is valid
     753           0 :     if (getStreamCount() <= 0 || streamIdx < 0 || encoders_.size() < (unsigned) (streamIdx + 1))
     754           0 :         return {};
     755           0 :     auto enc = encoders_[streamIdx];
     756             :     // TODO set firstTimestamp
     757           0 :     auto ms = MediaStream(name, enc);
     758             : #ifdef ENABLE_HWACCEL
     759           0 :     if (accel_)
     760           0 :         ms.format = accel_->getSoftwareFormat();
     761             : #endif
     762           0 :     return ms;
     763           0 : }
     764             : 
     765             : int
     766           1 : MediaEncoder::getCurrentAudioAVCtxFrameSize()
     767             : {
     768           1 :     if (auto* ctx = getCurrentAudioAVCtx()) {
     769           1 :         return ctx->frame_size;
     770             :     }
     771           0 :     return 0;
     772             : }
     773             : 
     774             : AVCodecContext*
     775         233 : MediaEncoder::initCodec(AVMediaType mediaType, AVCodecID avcodecId, uint64_t br)
     776             : {
     777         233 :     outputCodec_ = nullptr;
     778             : #ifdef ENABLE_HWACCEL
     779         233 :     if (mediaType == AVMEDIA_TYPE_VIDEO) {
     780          50 :         if (enableAccel_) {
     781           0 :             if (accel_) {
     782           0 :                 outputCodec_ = avcodec_find_encoder_by_name(accel_->getCodecName().c_str());
     783             :             }
     784             :         } else {
     785          50 :             JAMI_WARN() << "Hardware encoding disabled";
     786             :         }
     787             :     }
     788             : #endif
     789             : 
     790         233 :     if (!outputCodec_) {
     791             :         /* find the video encoder */
     792         233 :         if (avcodecId == AV_CODEC_ID_H263)
     793             :             // For H263 encoding, we force the use of AV_CODEC_ID_H263P (H263-1998)
     794             :             // H263-1998 can manage all frame sizes while H263 don't
     795             :             // AV_CODEC_ID_H263 decoder will be used for decoding
     796           0 :             outputCodec_ = avcodec_find_encoder(AV_CODEC_ID_H263P);
     797             :         else
     798         233 :             outputCodec_ = avcodec_find_encoder(static_cast<AVCodecID>(avcodecId));
     799         233 :         if (!outputCodec_) {
     800           0 :             throw MediaEncoderException("No output encoder");
     801             :         }
     802             :     }
     803             : 
     804         233 :     AVCodecContext* encoderCtx = prepareEncoderContext(outputCodec_, mediaType == AVMEDIA_TYPE_VIDEO);
     805             : 
     806             :     // Only clamp video bitrate
     807         233 :     if (mediaType == AVMEDIA_TYPE_VIDEO && br > 0) {
     808          50 :         if (br < SystemCodecInfo::DEFAULT_MIN_BITRATE) {
     809           4 :             JAMI_WARNING("Requested bitrate {:d} too low, setting to {:d}", br, SystemCodecInfo::DEFAULT_MIN_BITRATE);
     810           1 :             br = SystemCodecInfo::DEFAULT_MIN_BITRATE;
     811          49 :         } else if (br > SystemCodecInfo::DEFAULT_MAX_BITRATE) {
     812           0 :             JAMI_WARNING("Requested bitrate {:d} too high, setting to {:d}", br, SystemCodecInfo::DEFAULT_MAX_BITRATE);
     813           0 :             br = SystemCodecInfo::DEFAULT_MAX_BITRATE;
     814             :         }
     815             :     }
     816             : 
     817             :     /* Enable libopus FEC encoding support */
     818         233 :     if (mediaType == AVMEDIA_TYPE_AUDIO) {
     819         183 :         if (avcodecId == AV_CODEC_ID_OPUS) {
     820         183 :             initOpus(encoderCtx);
     821             :         }
     822             :     }
     823             : 
     824             :     /* let x264 preset override our encoder settings */
     825         233 :     if (avcodecId == AV_CODEC_ID_H264) {
     826          49 :         auto profileLevelId = libav_utils::getDictValue(options_, "parameters");
     827          49 :         extractProfileLevelID(profileLevelId, encoderCtx);
     828          49 :         forcePresetX2645(encoderCtx);
     829          49 :         encoderCtx->flags2 |= AV_CODEC_FLAG2_LOCAL_HEADER;
     830          49 :         initH264(encoderCtx, br);
     831         184 :     } else if (avcodecId == AV_CODEC_ID_HEVC) {
     832           0 :         encoderCtx->profile = FF_PROFILE_HEVC_MAIN;
     833           0 :         forcePresetX2645(encoderCtx);
     834           0 :         initH265(encoderCtx, br);
     835           0 :         av_opt_set_int(encoderCtx, "b_ref_mode", 0, AV_OPT_SEARCH_CHILDREN);
     836         184 :     } else if (avcodecId == AV_CODEC_ID_VP8) {
     837           1 :         initVP8(encoderCtx, br);
     838         183 :     } else if (avcodecId == AV_CODEC_ID_MPEG4) {
     839           0 :         initMPEG4(encoderCtx, br);
     840         183 :     } else if (avcodecId == AV_CODEC_ID_H263) {
     841           0 :         initH263(encoderCtx, br);
     842             :     }
     843         233 :     initAccel(encoderCtx, br);
     844         233 :     return encoderCtx;
     845             : }
     846             : 
     847             : int
     848          37 : MediaEncoder::setBitrate(uint64_t br)
     849             : {
     850          37 :     std::lock_guard lk(encMutex_);
     851          37 :     AVCodecContext* encoderCtx = getCurrentVideoAVCtx();
     852          37 :     if (not encoderCtx)
     853           0 :         return -1; // NOK
     854             : 
     855          37 :     AVCodecID codecId = encoderCtx->codec_id;
     856             : 
     857          37 :     if (not isDynBitrateSupported(codecId))
     858           0 :         return 0; // Restart needed
     859             : 
     860             :     // No need to restart encoder for h264, h263 and MPEG4
     861             :     // Change parameters on the fly
     862          37 :     if (codecId == AV_CODEC_ID_H264)
     863          37 :         initH264(encoderCtx, br);
     864          37 :     if (codecId == AV_CODEC_ID_HEVC)
     865           0 :         initH265(encoderCtx, br);
     866          37 :     else if (codecId == AV_CODEC_ID_H263P)
     867           0 :         initH263(encoderCtx, br);
     868          37 :     else if (codecId == AV_CODEC_ID_MPEG4)
     869           0 :         initMPEG4(encoderCtx, br);
     870             :     else {
     871             :         // restart encoder on runtime doesn't work for VP8
     872             :         // stopEncoder();
     873             :         // encoderCtx = initCodec(codecType, codecId, br);
     874             :         // if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0)
     875             :         //     throw MediaEncoderException("Unable to open encoder");
     876             :     }
     877          37 :     initAccel(encoderCtx, br);
     878          37 :     return 1; // OK
     879          37 : }
     880             : 
     881             : int
     882           0 : MediaEncoder::setPacketLoss(uint64_t pl)
     883             : {
     884           0 :     std::lock_guard lk(encMutex_);
     885           0 :     AVCodecContext* encoderCtx = getCurrentAudioAVCtx();
     886           0 :     if (not encoderCtx)
     887           0 :         return -1; // NOK
     888             : 
     889           0 :     AVCodecID codecId = encoderCtx->codec_id;
     890             : 
     891           0 :     if (not isDynPacketLossSupported(codecId))
     892           0 :         return 0; // Restart needed
     893             : 
     894             :     // Cap between 0 and 100
     895           0 :     pl = std::clamp((int) pl, 0, 100);
     896             : 
     897             :     // Change parameters on the fly
     898           0 :     if (codecId == AV_CODEC_ID_OPUS)
     899           0 :         av_opt_set_int(encoderCtx, "packet_loss", (int64_t) pl, AV_OPT_SEARCH_CHILDREN);
     900           0 :     return 1; // OK
     901           0 : }
     902             : 
     903             : void
     904          86 : MediaEncoder::initH264(AVCodecContext* encoderCtx, uint64_t br)
     905             : {
     906          86 :     uint64_t maxBitrate = 1000 * br;
     907             :     // 200 Kbit/s    -> CRF40
     908             :     // 6 Mbit/s      -> CRF23
     909          86 :     uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + LOGREG_PARAM_B * std::log(maxBitrate));
     910             :     // bufsize parameter impact the variation of the bitrate, reduce to half the maxrate to limit
     911             :     // peak and congestion
     912             :     // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate
     913          86 :     uint64_t bufSize = maxBitrate / 2;
     914             : 
     915             :     // If auto quality disabled use CRF mode
     916          86 :     if (mode_ == RateMode::CRF_CONSTRAINED) {
     917          86 :         av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN);
     918          86 :         av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
     919          86 :         av_opt_set_int(encoderCtx, "bufsize", bufSize, AV_OPT_SEARCH_CHILDREN);
     920         344 :         JAMI_DEBUG("H264 encoder setup: crf={:d}, maxrate={:d} kbit/s, bufsize={:d} kbit",
     921             :                    crf,
     922             :                    maxBitrate / 1000,
     923             :                    bufSize / 1000);
     924           0 :     } else if (mode_ == RateMode::CBR) {
     925           0 :         av_opt_set_int(encoderCtx, "b", maxBitrate, AV_OPT_SEARCH_CHILDREN);
     926           0 :         av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
     927           0 :         av_opt_set_int(encoderCtx, "minrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
     928           0 :         av_opt_set_int(encoderCtx, "bufsize", bufSize, AV_OPT_SEARCH_CHILDREN);
     929           0 :         av_opt_set_int(encoderCtx, "crf", -1, AV_OPT_SEARCH_CHILDREN);
     930             : 
     931           0 :         JAMI_DEBUG("H264 encoder setup cbr: bitrate={:d} kbit/s", br);
     932             :     }
     933          86 : }
     934             : 
     935             : void
     936           0 : MediaEncoder::initH265(AVCodecContext* encoderCtx, uint64_t br)
     937             : {
     938             :     // If auto quality disabled use CRF mode
     939           0 :     if (mode_ == RateMode::CRF_CONSTRAINED) {
     940           0 :         uint64_t maxBitrate = 1000 * br;
     941             :         // H265 use 50% less bitrate compared to H264 (half bitrate is equivalent to a change 6 for
     942             :         // CRF) https://slhck.info/video/2017/02/24/crf-guide.html
     943             :         // 200 Kbit/s    -> CRF35
     944             :         // 6 Mbit/s      -> CRF18
     945           0 :         uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A_HEVC + LOGREG_PARAM_B_HEVC * std::log(maxBitrate));
     946           0 :         uint64_t bufSize = maxBitrate / 2;
     947           0 :         av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN);
     948           0 :         av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
     949           0 :         av_opt_set_int(encoderCtx, "bufsize", bufSize, AV_OPT_SEARCH_CHILDREN);
     950           0 :         JAMI_DEBUG("H265 encoder setup: crf={:d}, maxrate={:d} kbit/s, bufsize={:d} kbit",
     951             :                    crf,
     952             :                    maxBitrate / 1000,
     953             :                    bufSize / 1000);
     954           0 :     } else if (mode_ == RateMode::CBR) {
     955           0 :         av_opt_set_int(encoderCtx, "b", br * 1000, AV_OPT_SEARCH_CHILDREN);
     956           0 :         av_opt_set_int(encoderCtx, "maxrate", br * 1000, AV_OPT_SEARCH_CHILDREN);
     957           0 :         av_opt_set_int(encoderCtx, "minrate", br * 1000, AV_OPT_SEARCH_CHILDREN);
     958           0 :         av_opt_set_int(encoderCtx, "bufsize", br * 500, AV_OPT_SEARCH_CHILDREN);
     959           0 :         av_opt_set_int(encoderCtx, "crf", -1, AV_OPT_SEARCH_CHILDREN);
     960           0 :         JAMI_DEBUG("H265 encoder setup cbr: bitrate={:d} kbit/s", br);
     961             :     }
     962           0 : }
     963             : 
     964             : void
     965           1 : MediaEncoder::initVP8(AVCodecContext* encoderCtx, uint64_t br)
     966             : {
     967           1 :     if (mode_ == RateMode::CQ) {
     968           0 :         av_opt_set_int(encoderCtx, "g", 120, AV_OPT_SEARCH_CHILDREN);
     969           0 :         av_opt_set_int(encoderCtx, "lag-in-frames", 0, AV_OPT_SEARCH_CHILDREN);
     970           0 :         av_opt_set(encoderCtx, "deadline", "good", AV_OPT_SEARCH_CHILDREN);
     971           0 :         av_opt_set_int(encoderCtx, "cpu-used", 0, AV_OPT_SEARCH_CHILDREN);
     972           0 :         av_opt_set_int(encoderCtx, "vprofile", 0, AV_OPT_SEARCH_CHILDREN);
     973           0 :         av_opt_set_int(encoderCtx, "qmax", 23, AV_OPT_SEARCH_CHILDREN);
     974           0 :         av_opt_set_int(encoderCtx, "qmin", 0, AV_OPT_SEARCH_CHILDREN);
     975           0 :         av_opt_set_int(encoderCtx, "slices", 4, AV_OPT_SEARCH_CHILDREN);
     976           0 :         av_opt_set_int(encoderCtx, "crf", 18, AV_OPT_SEARCH_CHILDREN);
     977           0 :         JAMI_DEBUG("VP8 encoder setup: crf=18");
     978             :     } else {
     979             :         // 1- if quality is set use it
     980             :         // bitrate need to be set. The target bitrate becomes the maximum allowed bitrate
     981             :         // 2- otherwise set rc_max_rate and rc_buffer_size
     982             :         // Using information given on this page:
     983             :         // http://www.webmproject.org/docs/encoder-parameters/
     984           1 :         uint64_t maxBitrate = 1000 * br;
     985             :         // 200 Kbit/s    -> CRF40
     986             :         // 6 Mbit/s      -> CRF23
     987           1 :         uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + LOGREG_PARAM_B * std::log(maxBitrate));
     988           1 :         uint64_t bufSize = maxBitrate / 2;
     989             : 
     990           1 :         av_opt_set(encoderCtx, "quality", "realtime", AV_OPT_SEARCH_CHILDREN);
     991           1 :         av_opt_set_int(encoderCtx, "error-resilient", 1, AV_OPT_SEARCH_CHILDREN);
     992           1 :         av_opt_set_int(encoderCtx, "cpu-used", 7,
     993             :                        AV_OPT_SEARCH_CHILDREN); // value obtained from testing
     994           1 :         av_opt_set_int(encoderCtx, "lag-in-frames", 0, AV_OPT_SEARCH_CHILDREN);
     995             :         // allow encoder to drop frames if buffers are full and
     996             :         // to undershoot target bitrate to lessen strain on resources
     997           1 :         av_opt_set_int(encoderCtx, "drop-frame", 25, AV_OPT_SEARCH_CHILDREN);
     998           1 :         av_opt_set_int(encoderCtx, "undershoot-pct", 95, AV_OPT_SEARCH_CHILDREN);
     999             :         // don't set encoderCtx->gop_size: let libvpx decide when to insert a keyframe
    1000           1 :         av_opt_set_int(encoderCtx, "slices", 2, AV_OPT_SEARCH_CHILDREN); // VP8E_SET_TOKEN_PARTITIONS
    1001           1 :         av_opt_set_int(encoderCtx, "qmax", 56, AV_OPT_SEARCH_CHILDREN);
    1002           1 :         av_opt_set_int(encoderCtx, "qmin", 4, AV_OPT_SEARCH_CHILDREN);
    1003           1 :         crf = std::clamp((int) crf, 4, 56);
    1004           1 :         av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN);
    1005           1 :         av_opt_set_int(encoderCtx, "b", maxBitrate, AV_OPT_SEARCH_CHILDREN);
    1006           1 :         av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
    1007           1 :         av_opt_set_int(encoderCtx, "bufsize", bufSize, AV_OPT_SEARCH_CHILDREN);
    1008           4 :         JAMI_DEBUG("VP8 encoder setup: crf={:d}, maxrate={:d}, bufsize={:d}", crf, maxBitrate / 1000, bufSize / 1000);
    1009             :     }
    1010           1 : }
    1011             : 
    1012             : void
    1013           0 : MediaEncoder::initMPEG4(AVCodecContext* encoderCtx, uint64_t br)
    1014             : {
    1015           0 :     uint64_t maxBitrate = 1000 * br;
    1016           0 :     uint64_t bufSize = maxBitrate / 2;
    1017             : 
    1018             :     // Use CBR (set bitrate)
    1019           0 :     encoderCtx->rc_buffer_size = bufSize;
    1020           0 :     encoderCtx->bit_rate = encoderCtx->rc_min_rate = encoderCtx->rc_max_rate = maxBitrate;
    1021           0 :     JAMI_DEBUG("MPEG4 encoder setup: maxrate={:d}, bufsize={:d}", maxBitrate, bufSize);
    1022           0 : }
    1023             : 
    1024             : void
    1025           0 : MediaEncoder::initH263(AVCodecContext* encoderCtx, uint64_t br)
    1026             : {
    1027           0 :     uint64_t maxBitrate = 1000 * br;
    1028           0 :     uint64_t bufSize = maxBitrate / 2;
    1029             : 
    1030             :     // Use CBR (set bitrate)
    1031           0 :     encoderCtx->rc_buffer_size = bufSize;
    1032           0 :     encoderCtx->bit_rate = encoderCtx->rc_min_rate = encoderCtx->rc_max_rate = maxBitrate;
    1033           0 :     JAMI_DEBUG("H263 encoder setup: maxrate={:d}, bufsize={:d}", maxBitrate, bufSize);
    1034           0 : }
    1035             : 
    1036             : void
    1037         183 : MediaEncoder::initOpus(AVCodecContext* encoderCtx)
    1038             : {
    1039             :     // Enable FEC support by default with 10% packet loss
    1040         183 :     av_opt_set_int(encoderCtx, "fec", fecEnabled_ ? 1 : 0, AV_OPT_SEARCH_CHILDREN);
    1041         183 :     av_opt_set_int(encoderCtx, "packet_loss", 10, AV_OPT_SEARCH_CHILDREN);
    1042         183 : }
    1043             : 
    1044             : void
    1045         270 : MediaEncoder::initAccel(AVCodecContext* encoderCtx, uint64_t br)
    1046             : {
    1047             : #ifdef ENABLE_HWACCEL
    1048         270 :     if (not accel_)
    1049         270 :         return;
    1050           0 :     if (accel_->getName() == "nvenc"sv) {
    1051             :         // Use same parameters as software
    1052           0 :     } else if (accel_->getName() == "vaapi"sv) {
    1053             :         // Use VBR encoding with bitrate target set to 80% of the maxrate
    1054           0 :         av_opt_set_int(encoderCtx, "crf", -1, AV_OPT_SEARCH_CHILDREN);
    1055           0 :         av_opt_set_int(encoderCtx, "b", br * 1000 * 0.8f, AV_OPT_SEARCH_CHILDREN);
    1056           0 :     } else if (accel_->getName() == "videotoolbox"sv) {
    1057           0 :         av_opt_set_int(encoderCtx, "b", br * 1000 * 0.8f, AV_OPT_SEARCH_CHILDREN);
    1058           0 :     } else if (accel_->getName() == "qsv"sv) {
    1059             :         // Use Video Conferencing Mode
    1060           0 :         av_opt_set_int(encoderCtx, "vcm", 1, AV_OPT_SEARCH_CHILDREN);
    1061           0 :         av_opt_set_int(encoderCtx, "b", br * 1000 * 0.8f, AV_OPT_SEARCH_CHILDREN);
    1062             :     }
    1063             : #endif
    1064             : }
    1065             : 
    1066             : AVCodecContext*
    1067          37 : MediaEncoder::getCurrentVideoAVCtx()
    1068             : {
    1069          37 :     for (auto it : encoders_) {
    1070          37 :         if (it->codec_type == AVMEDIA_TYPE_VIDEO)
    1071          37 :             return it;
    1072             :     }
    1073           0 :     return nullptr;
    1074             : }
    1075             : 
    1076             : AVCodecContext*
    1077           1 : MediaEncoder::getCurrentAudioAVCtx()
    1078             : {
    1079           1 :     for (auto it : encoders_) {
    1080           1 :         if (it->codec_type == AVMEDIA_TYPE_AUDIO)
    1081           1 :             return it;
    1082             :     }
    1083           0 :     return nullptr;
    1084             : }
    1085             : 
    1086             : void
    1087           0 : MediaEncoder::stopEncoder()
    1088             : {
    1089           0 :     flush();
    1090           0 :     for (auto it = encoders_.begin(); it != encoders_.end(); it++) {
    1091           0 :         if ((*it)->codec_type == AVMEDIA_TYPE_VIDEO) {
    1092           0 :             encoders_.erase(it);
    1093           0 :             break;
    1094             :         }
    1095             :     }
    1096           0 :     AVCodecContext* encoderCtx = getCurrentVideoAVCtx();
    1097           0 :     avcodec_close(encoderCtx);
    1098           0 :     avcodec_free_context(&encoderCtx);
    1099           0 :     av_free(encoderCtx);
    1100           0 : }
    1101             : 
    1102             : bool
    1103          37 : MediaEncoder::isDynBitrateSupported(AVCodecID codecid)
    1104             : {
    1105             : #ifdef ENABLE_HWACCEL
    1106          37 :     if (accel_) {
    1107           0 :         return accel_->dynBitrate();
    1108             :     }
    1109             : #endif
    1110          37 :     if (codecid != AV_CODEC_ID_VP8)
    1111          37 :         return true;
    1112             : 
    1113           0 :     return false;
    1114             : }
    1115             : 
    1116             : bool
    1117           0 : MediaEncoder::isDynPacketLossSupported(AVCodecID codecid)
    1118             : {
    1119           0 :     if (codecid == AV_CODEC_ID_OPUS)
    1120           0 :         return true;
    1121             : 
    1122           0 :     return false;
    1123             : }
    1124             : 
    1125             : void
    1126         233 : MediaEncoder::readConfig(AVCodecContext* encoderCtx)
    1127             : {
    1128         233 :     auto path = fileutils::get_config_dir() / "encoder.json";
    1129         233 :     std::string name = encoderCtx->codec->name;
    1130         233 :     std::error_code ec;
    1131         233 :     if (std::filesystem::is_regular_file(path, ec)) {
    1132           0 :         JAMI_WARN("encoder.json file found, default settings will be erased");
    1133             :         try {
    1134           0 :             Json::Value root;
    1135           0 :             std::ifstream file(path);
    1136           0 :             file >> root;
    1137           0 :             if (!root.isObject()) {
    1138           0 :                 JAMI_ERR() << "Invalid encoder configuration: root is not an object";
    1139           0 :                 return;
    1140             :             }
    1141           0 :             const auto& config = root[name];
    1142           0 :             if (config.isNull()) {
    1143           0 :                 JAMI_WARN() << "Encoder '" << name << "' not found in configuration file";
    1144           0 :                 return;
    1145             :             }
    1146           0 :             if (!config.isObject()) {
    1147           0 :                 JAMI_ERR() << "Invalid encoder configuration: '" << name << "' is not an object";
    1148           0 :                 return;
    1149             :             }
    1150           0 :             for (Json::Value::const_iterator it = config.begin(); it != config.end(); ++it) {
    1151           0 :                 Json::Value v = *it;
    1152           0 :                 if (!it.key().isConvertibleTo(Json::ValueType::stringValue)
    1153           0 :                     || !v.isConvertibleTo(Json::ValueType::stringValue)) {
    1154           0 :                     JAMI_ERR() << "Invalid configuration for '" << name << "'";
    1155           0 :                     return;
    1156             :                 }
    1157           0 :                 const auto& key = it.key().asString();
    1158           0 :                 const auto& value = v.asString();
    1159           0 :                 int ret = av_opt_set(reinterpret_cast<void*>(encoderCtx),
    1160             :                                      key.c_str(),
    1161             :                                      value.c_str(),
    1162             :                                      AV_OPT_SEARCH_CHILDREN);
    1163           0 :                 if (ret < 0) {
    1164           0 :                     JAMI_ERR() << "Failed to set option " << key << " in " << name
    1165           0 :                                << " context: " << libav_utils::getError(ret) << "\n";
    1166             :                 }
    1167           0 :             }
    1168           0 :         } catch (const Json::Exception& e) {
    1169           0 :             JAMI_ERR() << "Failed to load encoder configuration file: " << e.what();
    1170           0 :         }
    1171             :     }
    1172         233 : }
    1173             : 
    1174             : std::string
    1175         269 : MediaEncoder::testH265Accel()
    1176             : {
    1177             : #ifdef ENABLE_HWACCEL
    1178         269 :     if (jami::Manager::instance().videoPreferences.getEncodingAccelerated()) {
    1179             :         // Get compatible list of Hardware API
    1180           0 :         auto APIs = video::HardwareAccel::getCompatibleAccel(AV_CODEC_ID_H265, 1280, 720, CODEC_ENCODER);
    1181             : 
    1182           0 :         std::unique_ptr<video::HardwareAccel> accel;
    1183             : 
    1184           0 :         for (const auto& it : APIs) {
    1185           0 :             accel = std::make_unique<video::HardwareAccel>(it); // save accel
    1186             :             // Init codec need accel to init encoderCtx accelerated
    1187           0 :             auto outputCodec = avcodec_find_encoder_by_name(accel->getCodecName().c_str());
    1188             : 
    1189           0 :             AVCodecContext* encoderCtx = avcodec_alloc_context3(outputCodec);
    1190           0 :             encoderCtx->thread_count = std::min(std::thread::hardware_concurrency(), 16u);
    1191           0 :             encoderCtx->width = 1280;
    1192           0 :             encoderCtx->height = 720;
    1193             :             AVRational framerate;
    1194           0 :             framerate.num = 30;
    1195           0 :             framerate.den = 1;
    1196           0 :             encoderCtx->time_base = av_inv_q(framerate);
    1197           0 :             encoderCtx->pix_fmt = accel->getFormat();
    1198           0 :             encoderCtx->profile = FF_PROFILE_HEVC_MAIN;
    1199           0 :             encoderCtx->opaque = accel.get();
    1200             : 
    1201           0 :             auto br = SystemCodecInfo::DEFAULT_VIDEO_BITRATE;
    1202           0 :             av_opt_set_int(encoderCtx, "b", br * 1000, AV_OPT_SEARCH_CHILDREN);
    1203           0 :             av_opt_set_int(encoderCtx, "maxrate", br * 1000, AV_OPT_SEARCH_CHILDREN);
    1204           0 :             av_opt_set_int(encoderCtx, "minrate", br * 1000, AV_OPT_SEARCH_CHILDREN);
    1205           0 :             av_opt_set_int(encoderCtx, "bufsize", br * 500, AV_OPT_SEARCH_CHILDREN);
    1206           0 :             av_opt_set_int(encoderCtx, "crf", -1, AV_OPT_SEARCH_CHILDREN);
    1207             : 
    1208           0 :             auto ret = accel->initAPI(false, nullptr);
    1209           0 :             if (ret < 0) {
    1210           0 :                 accel.reset();
    1211             :                 ;
    1212           0 :                 encoderCtx = nullptr;
    1213           0 :                 continue;
    1214             :             }
    1215           0 :             accel->setDetails(encoderCtx);
    1216           0 :             if (avcodec_open2(encoderCtx, outputCodec, nullptr) < 0) {
    1217             :                 // Failed to open codec
    1218           0 :                 JAMI_WARN("Fail to open hardware encoder H265 with %s ", it.getName().c_str());
    1219           0 :                 avcodec_free_context(&encoderCtx);
    1220           0 :                 encoderCtx = nullptr;
    1221           0 :                 accel = nullptr;
    1222           0 :                 continue;
    1223             :             } else {
    1224             :                 // Succeed to open codec
    1225           0 :                 avcodec_free_context(&encoderCtx);
    1226           0 :                 encoderCtx = nullptr;
    1227           0 :                 accel = nullptr;
    1228           0 :                 return it.getName();
    1229             :             }
    1230             :         }
    1231           0 :     }
    1232             : #endif
    1233         269 :     return "";
    1234             : }
    1235             : 
    1236             : #ifdef ENABLE_VIDEO
    1237             : int
    1238        5751 : MediaEncoder::getHWFrame(const std::shared_ptr<VideoFrame>& input, std::shared_ptr<VideoFrame>& output)
    1239             : {
    1240             :     try {
    1241             : #if defined(TARGET_OS_IOS) && TARGET_OS_IOS
    1242             :         // iOS
    1243             :         if (accel_) {
    1244             :             auto pix = accel_->getSoftwareFormat();
    1245             :             if (input->format() != pix) {
    1246             :                 output = scaler_.convertFormat(*input.get(), pix);
    1247             :             } else {
    1248             :                 // Fully accelerated pipeline, skip main memory
    1249             :                 output = input;
    1250             :             }
    1251             :         } else {
    1252             :             output = getScaledSWFrame(*input.get());
    1253             :         }
    1254             : #elif !defined(__APPLE__) && defined(ENABLE_HWACCEL)
    1255             :         // Other Platforms
    1256        5751 :         auto desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format()));
    1257        5751 :         bool isHardware = desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL);
    1258        5751 :         if (accel_ && accel_->isLinked() && isHardware) {
    1259             :             // Fully accelerated pipeline, skip main memory
    1260           0 :             output = input;
    1261        5751 :         } else if (isHardware) {
    1262             :             // Hardware decoded frame, transfer back to main memory
    1263             :             // Transfer to GPU if we have a hardware encoder
    1264             :             // Hardware decoders decode to NV12, but Jami's supported software encoders want YUV420P
    1265           0 :             output = getUnlinkedHWFrame(*input.get());
    1266        5751 :         } else if (accel_) {
    1267             :             // Software decoded frame with a hardware encoder, convert to accepted format first
    1268           0 :             output = getHWFrameFromSWFrame(*input.get());
    1269             :         } else {
    1270        5751 :             output = getScaledSWFrame(*input.get());
    1271             :         }
    1272             : #else
    1273             :         // macOS
    1274             :         output = getScaledSWFrame(*input.get());
    1275             : #endif
    1276           0 :     } catch (const std::runtime_error& e) {
    1277           0 :         JAMI_ERR("Accel failure: %s", e.what());
    1278           0 :         return -1;
    1279           0 :     }
    1280             : 
    1281        5751 :     return 0;
    1282             : }
    1283             : 
    1284             : #ifdef ENABLE_HWACCEL
    1285             : std::shared_ptr<VideoFrame>
    1286           0 : MediaEncoder::getUnlinkedHWFrame(const VideoFrame& input)
    1287             : {
    1288           0 :     AVPixelFormat pix = (accel_ ? accel_->getSoftwareFormat() : AV_PIX_FMT_NV12);
    1289           0 :     std::shared_ptr<VideoFrame> framePtr = video::HardwareAccel::transferToMainMemory(input, pix);
    1290           0 :     if (!accel_) {
    1291           0 :         framePtr = scaler_.convertFormat(*framePtr, AV_PIX_FMT_YUV420P);
    1292             :     } else {
    1293           0 :         framePtr = accel_->transfer(*framePtr);
    1294             :     }
    1295           0 :     return framePtr;
    1296           0 : }
    1297             : 
    1298             : std::shared_ptr<VideoFrame>
    1299           0 : MediaEncoder::getHWFrameFromSWFrame(const VideoFrame& input)
    1300             : {
    1301           0 :     std::shared_ptr<VideoFrame> framePtr;
    1302           0 :     auto pix = accel_->getSoftwareFormat();
    1303           0 :     if (input.format() != pix) {
    1304           0 :         framePtr = scaler_.convertFormat(input, pix);
    1305           0 :         framePtr = accel_->transfer(*framePtr);
    1306             :     } else {
    1307           0 :         framePtr = accel_->transfer(input);
    1308             :     }
    1309           0 :     return framePtr;
    1310           0 : }
    1311             : #endif
    1312             : 
    1313             : std::shared_ptr<VideoFrame>
    1314        5751 : MediaEncoder::getScaledSWFrame(const VideoFrame& input)
    1315             : {
    1316        5751 :     libav_utils::fillWithBlack(scaledFrame_->pointer());
    1317        5751 :     scaler_.scale_with_aspect(input, *scaledFrame_);
    1318        5751 :     return scaledFrame_;
    1319             : }
    1320             : #endif
    1321             : 
    1322             : void
    1323           0 : MediaEncoder::resetStreams(int width, int height)
    1324             : {
    1325           0 :     videoOpts_.width = width;
    1326           0 :     videoOpts_.height = height;
    1327             : 
    1328             :     try {
    1329           0 :         flush();
    1330           0 :         initialized_ = false;
    1331           0 :         if (outputCtx_) {
    1332           0 :             for (auto encoderCtx : encoders_) {
    1333           0 :                 if (encoderCtx) {
    1334             : #ifndef _MSC_VER
    1335           0 :                     avcodec_free_context(&encoderCtx);
    1336             : #else
    1337             :                     avcodec_close(encoderCtx);
    1338             : #endif
    1339             :                 }
    1340             :             }
    1341           0 :             encoders_.clear();
    1342             :         }
    1343           0 :     } catch (...) {
    1344           0 :     }
    1345           0 : }
    1346             : 
    1347             : } // namespace jami

Generated by: LCOV version 1.14