LCOV - code coverage report
Current view: top level - src/media - media_encoder.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 51.9 % 725 376
Test Date: 2026-06-13 09:18:46 Functions: 42.1 % 152 64

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

Generated by: LCOV version 2.0-1