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