LCOV - code coverage report
Current view: top level - src/media - media_encoder.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 375 722 51.9 %
Date: 2024-12-21 08:56:24 Functions: 41 69 59.4 %

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

Generated by: LCOV version 1.14