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