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