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