Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #include "libav_deps.h" // MUST BE INCLUDED FIRST
18 : #include "media_codec.h"
19 : #include "media_encoder.h"
20 : #include "media_buffer.h"
21 :
22 : #include "client/jami_signal.h"
23 : #include "fileutils.h"
24 : #include "logger.h"
25 : #include "manager.h"
26 : #include "string_utils.h"
27 : #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 317 : MediaEncoder::MediaEncoder()
60 317 : : outputCtx_(avformat_alloc_context())
61 : {
62 317 : JAMI_DBG("[%p] New instance created", this);
63 317 : }
64 :
65 317 : MediaEncoder::~MediaEncoder()
66 : {
67 317 : if (outputCtx_) {
68 317 : if (outputCtx_->priv_data && outputCtx_->pb)
69 48 : av_write_trailer(outputCtx_);
70 317 : if (fileIO_) {
71 1 : avio_close(outputCtx_->pb);
72 : }
73 543 : for (auto* encoderCtx : encoders_) {
74 226 : if (encoderCtx) {
75 : #ifndef _MSC_VER
76 226 : avcodec_free_context(&encoderCtx);
77 : #else
78 : avcodec_close(encoderCtx);
79 : #endif
80 : }
81 : }
82 317 : avformat_free_context(outputCtx_);
83 : }
84 317 : av_dict_free(&options_);
85 :
86 317 : JAMI_DBG("[%p] Instance destroyed", this);
87 317 : }
88 :
89 : void
90 320 : MediaEncoder::setOptions(const MediaStream& opts)
91 : {
92 320 : if (!opts.isValid()) {
93 0 : JAMI_ERR() << "Invalid options";
94 0 : return;
95 : }
96 :
97 320 : if (opts.isVideo) {
98 142 : 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 142 : videoOpts_.width = ((videoOpts_.width >> 3) << 3);
103 142 : videoOpts_.height = ((videoOpts_.height >> 3) << 3);
104 142 : if (!videoOpts_.frameRate)
105 92 : videoOpts_.frameRate = 30;
106 142 : if (!videoOpts_.bitrate) {
107 49 : videoOpts_.bitrate = SystemCodecInfo::DEFAULT_VIDEO_BITRATE;
108 : }
109 : } else {
110 178 : audioOpts_ = opts;
111 : }
112 : }
113 :
114 : void
115 316 : MediaEncoder::setOptions(const MediaDescription& args)
116 : {
117 : int ret;
118 632 : if (args.payload_type
119 630 : and (ret = av_opt_set_int(reinterpret_cast<void*>(outputCtx_),
120 : "payload_type",
121 314 : args.payload_type,
122 : AV_OPT_SEARCH_CHILDREN)
123 314 : < 0))
124 0 : JAMI_ERR() << "Failed to set payload type: " << libav_utils::getError(ret);
125 :
126 316 : if (not args.parameters.empty())
127 139 : libav_utils::setDictValue(&options_, "parameters", args.parameters);
128 :
129 316 : mode_ = args.mode;
130 316 : linkableHW_ = args.linkableHW;
131 316 : fecEnabled_ = args.fecEnabled;
132 316 : }
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 314 : MediaEncoder::setInitSeqVal(uint16_t seqVal)
145 : {
146 : // only set not default value (!=0)
147 314 : if (seqVal != 0)
148 139 : av_opt_set_int(outputCtx_, "seq", seqVal, AV_OPT_SEARCH_CHILDREN);
149 314 : }
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 317 : MediaEncoder::openOutput(const std::string& filename, const std::string& format)
163 : {
164 317 : avformat_free_context(outputCtx_);
165 948 : int result = avformat_alloc_output_context2(&outputCtx_,
166 : nullptr,
167 631 : format.empty() ? nullptr : format.c_str(),
168 : filename.c_str());
169 317 : if (result < 0)
170 0 : JAMI_ERR() << "Unable to open " << filename << ": " << libav_utils::getError(-result);
171 317 : }
172 :
173 : int
174 320 : MediaEncoder::addStream(const SystemCodecInfo& systemCodecInfo)
175 : {
176 320 : if (systemCodecInfo.mediaType == MEDIA_AUDIO) {
177 178 : audioCodec_ = systemCodecInfo.name;
178 : } else {
179 142 : videoCodec_ = systemCodecInfo.name;
180 : }
181 :
182 320 : auto* stream = avformat_new_stream(outputCtx_, outputCodec_);
183 :
184 320 : 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 320 : 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 320 : if (systemCodecInfo.mediaType == MEDIA_AUDIO) {
193 178 : 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 142 : if (audioOpts_.isValid())
201 3 : return 1;
202 : else
203 139 : return 0;
204 : }
205 :
206 : int
207 48 : MediaEncoder::initStream(const std::string& codecName, AVBufferRef* framesCtx)
208 : {
209 48 : const auto codecInfo = getSystemCodecContainer()->searchCodecByName(codecName, MEDIA_ALL);
210 48 : if (codecInfo)
211 48 : return initStream(*codecInfo, framesCtx);
212 : else
213 0 : return -1;
214 48 : }
215 :
216 : int
217 226 : MediaEncoder::initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* framesCtx)
218 : {
219 226 : 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 226 : std::lock_guard lk(encMutex_);
226 :
227 226 : if (!outputCtx_)
228 0 : throw MediaEncoderException("Unable to allocate stream");
229 :
230 : // Must already have codec instance(s)
231 226 : 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 226 : AVCodecContext* encoderCtx = nullptr;
237 : AVMediaType mediaType;
238 :
239 226 : if (systemCodecInfo.mediaType == MEDIA_VIDEO)
240 48 : mediaType = AVMEDIA_TYPE_VIDEO;
241 178 : else if (systemCodecInfo.mediaType == MEDIA_AUDIO)
242 178 : mediaType = AVMEDIA_TYPE_AUDIO;
243 : else
244 0 : throw MediaEncoderException("Unsuported media type");
245 :
246 226 : AVStream* stream {nullptr};
247 :
248 : // Only supports one audio and one video streams at most per instance.
249 453 : for (unsigned i = 0; i < outputCtx_->nb_streams; i++) {
250 227 : stream = outputCtx_->streams[i];
251 227 : 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 226 : 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 226 : currentStreamIdx_ = stream->index;
268 : #ifdef ENABLE_HWACCEL
269 : // Get compatible list of Hardware API
270 226 : 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 226 : if (!encoderCtx) {
317 226 : JAMI_WARN("Not using hardware encoding for %s",
318 : avcodec_get_name(static_cast<AVCodecID>(systemCodecInfo.avcodecId)));
319 226 : encoderCtx = initCodec(mediaType, static_cast<AVCodecID>(systemCodecInfo.avcodecId), videoOpts_.bitrate);
320 226 : readConfig(encoderCtx);
321 226 : encoders_.emplace_back(encoderCtx);
322 226 : if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0)
323 0 : throw MediaEncoderException("Unable to open encoder");
324 : }
325 :
326 226 : avcodec_parameters_from_context(stream->codecpar, encoderCtx);
327 :
328 : // framerate is not copied from encoderCtx to stream
329 226 : stream->avg_frame_rate = encoderCtx->framerate;
330 : #ifdef ENABLE_VIDEO
331 226 : if (systemCodecInfo.mediaType == MEDIA_VIDEO) {
332 : // allocate buffers for both scaled (pre-encoder) and encoded frames
333 48 : const int width = encoderCtx->width;
334 48 : const int height = encoderCtx->height;
335 48 : int format = encoderCtx->pix_fmt;
336 : #ifdef ENABLE_HWACCEL
337 48 : if (accel_) {
338 : // hardware encoders require a specific pixel format
339 0 : const 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 48 : scaledFrameBufferSize_ = videoFrameSize(format, width, height);
345 48 : if (scaledFrameBufferSize_ < 0)
346 0 : throw MediaEncoderException(
347 0 : ("Unable to compute buffer size: " + libav_utils::getError(scaledFrameBufferSize_)).c_str());
348 48 : else if (scaledFrameBufferSize_ <= AV_INPUT_BUFFER_MIN_SIZE)
349 0 : throw MediaEncoderException("buffer too small");
350 :
351 48 : scaledFrameBuffer_.resize(scaledFrameBufferSize_);
352 48 : scaledFrame_ = std::make_shared<VideoFrame>();
353 48 : scaledFrame_->setFromMemory(scaledFrameBuffer_.data(), format, width, height);
354 : }
355 : #endif // ENABLE_VIDEO
356 :
357 226 : return stream->index;
358 226 : }
359 :
360 : void
361 48 : MediaEncoder::openIOContext()
362 : {
363 48 : if (ioCtx_) {
364 47 : outputCtx_->pb = ioCtx_;
365 47 : 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 48 : }
382 :
383 : void
384 48 : MediaEncoder::startIO()
385 : {
386 48 : if (!outputCtx_->pb)
387 48 : openIOContext();
388 48 : 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 48 : av_dump_format(outputCtx_, 0, outputCtx_->url, 1);
395 : #else
396 : av_dump_format(outputCtx_, 0, outputCtx_->filename, 1);
397 : #endif
398 48 : initialized_ = true;
399 48 : }
400 :
401 : #ifdef ENABLE_VIDEO
402 : int
403 5104 : MediaEncoder::encode(const std::shared_ptr<VideoFrame>& input, bool is_keyframe, int64_t frame_number)
404 : {
405 5104 : auto width = (input->width() >> 3) << 3;
406 5104 : auto height = (input->height() >> 3) << 3;
407 5104 : if (initialized_ && (getWidth() != width || getHeight() != height)) {
408 0 : resetStreams(width, height);
409 0 : is_keyframe = true;
410 : }
411 :
412 5104 : if (!initialized_) {
413 47 : initStream(videoCodec_, input->pointer()->hw_frames_ctx);
414 47 : startIO();
415 : }
416 :
417 5104 : std::shared_ptr<VideoFrame> output;
418 : #ifdef ENABLE_HWACCEL
419 5104 : 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 5104 : if (!output) {
428 0 : JAMI_ERR("Fail to get frame");
429 0 : return -1;
430 : }
431 5104 : auto* avframe = output->pointer();
432 :
433 5104 : AVCodecContext* enc = encoders_[currentStreamIdx_];
434 5104 : avframe->pts = frame_number;
435 5104 : 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 5104 : if (is_keyframe) {
439 64 : avframe->pict_type = AV_PICTURE_TYPE_I;
440 64 : avframe->key_frame = 1;
441 : } else {
442 5040 : avframe->pict_type = AV_PICTURE_TYPE_NONE;
443 5040 : avframe->key_frame = 0;
444 : }
445 :
446 5104 : return encode(avframe, currentStreamIdx_);
447 5104 : }
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 5160 : MediaEncoder::encode(AVFrame* frame, int streamIdx)
468 : {
469 5160 : 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 5159 : int ret = 0;
484 5159 : if (static_cast<size_t>(streamIdx) >= encoders_.size())
485 2 : return -1;
486 5157 : AVCodecContext* encoderCtx = encoders_[streamIdx];
487 : AVPacket pkt;
488 5157 : av_init_packet(&pkt);
489 5157 : pkt.data = nullptr; // packet data will be allocated by the encoder
490 5157 : pkt.size = 0;
491 :
492 5157 : if (!encoderCtx)
493 0 : return -1;
494 :
495 5157 : ret = avcodec_send_frame(encoderCtx, frame);
496 5157 : if (ret < 0)
497 0 : return -1;
498 :
499 5160 : while (ret >= 0) {
500 5157 : ret = avcodec_receive_packet(encoderCtx, &pkt);
501 5157 : if (ret == AVERROR(EAGAIN))
502 0 : break;
503 5157 : 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 5157 : if (pkt.size) {
509 5154 : if (send(pkt, streamIdx))
510 5154 : break;
511 : }
512 : }
513 :
514 5157 : av_packet_unref(&pkt);
515 5157 : return 0;
516 : }
517 :
518 : bool
519 5154 : MediaEncoder::send(AVPacket& pkt, int streamIdx)
520 : {
521 5154 : if (!initialized_) {
522 0 : streamIdx = initStream(videoCodec_);
523 0 : startIO();
524 : }
525 5154 : if (streamIdx < 0)
526 0 : streamIdx = currentStreamIdx_;
527 5154 : if (streamIdx >= 0 and static_cast<size_t>(streamIdx) < encoders_.size()
528 10308 : and static_cast<unsigned int>(streamIdx) < outputCtx_->nb_streams) {
529 5154 : auto* encoderCtx = encoders_[streamIdx];
530 5154 : pkt.stream_index = streamIdx;
531 5154 : if (pkt.pts != AV_NOPTS_VALUE)
532 5154 : pkt.pts = av_rescale_q(pkt.pts, encoderCtx->time_base, outputCtx_->streams[streamIdx]->time_base);
533 5154 : if (pkt.dts != AV_NOPTS_VALUE)
534 5154 : pkt.dts = av_rescale_q(pkt.dts, encoderCtx->time_base, outputCtx_->streams[streamIdx]->time_base);
535 : }
536 : // write the compressed frame
537 5154 : auto ret = av_write_frame(outputCtx_, &pkt);
538 5154 : if (ret < 0) {
539 0 : JAMI_ERR() << "av_write_frame failed: " << libav_utils::getError(ret);
540 : }
541 5154 : 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, static_cast<int>(i)) < 0) {
550 2 : JAMI_ERR() << "Unable to flush stream #" << i;
551 2 : ret |= 1 << 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 226 : MediaEncoder::prepareEncoderContext(const AVCodec* outputCodec, bool is_video)
582 : {
583 226 : AVCodecContext* encoderCtx = avcodec_alloc_context3(outputCodec);
584 :
585 226 : const auto* encoderName = outputCodec->name; // guaranteed to be non null if AVCodec is not null
586 :
587 226 : encoderCtx->thread_count = std::min(static_cast<int>(std::thread::hardware_concurrency()), is_video ? 16 : 4);
588 226 : JAMI_DBG("[%s] Using %d threads", encoderName, encoderCtx->thread_count);
589 :
590 226 : if (is_video) {
591 : // resolution must be a multiple of two
592 48 : encoderCtx->width = videoOpts_.width;
593 48 : encoderCtx->height = videoOpts_.height;
594 :
595 : // satisfy ffmpeg: denominator must be 16bit or less value
596 : // time base = 1/FPS
597 48 : av_reduce(&encoderCtx->framerate.num,
598 : &encoderCtx->framerate.den,
599 48 : videoOpts_.frameRate.numerator(),
600 48 : videoOpts_.frameRate.denominator(),
601 : (1U << 16) - 1);
602 48 : encoderCtx->time_base = av_inv_q(encoderCtx->framerate);
603 :
604 : // emit one intra frame every gop_size frames
605 48 : encoderCtx->max_b_frames = 0;
606 48 : 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 48 : 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 712 : JAMI_WARNING("Codec format: {} {} {} {}",
625 : encoderName,
626 : audioOpts_.format,
627 : audioOpts_.sampleRate,
628 : audioOpts_.nbChannels);
629 178 : encoderCtx->sample_fmt = (AVSampleFormat) audioOpts_.format;
630 178 : encoderCtx->sample_rate = std::max(8000, audioOpts_.sampleRate);
631 178 : encoderCtx->time_base = AVRational {1, encoderCtx->sample_rate};
632 178 : 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 178 : av_channel_layout_default(&encoderCtx->ch_layout, audioOpts_.nbChannels);
637 178 : if (audioOpts_.frameSize) {
638 176 : encoderCtx->frame_size = audioOpts_.frameSize;
639 176 : JAMI_DBG() << "[" << encoderName << "] Frame size " << encoderCtx->frame_size;
640 : } else {
641 2 : JAMI_WARN() << "[" << encoderName << "] Frame size not set";
642 : }
643 : }
644 :
645 226 : return encoderCtx;
646 : }
647 :
648 : void
649 47 : MediaEncoder::forcePresetX2645(AVCodecContext* encoderCtx)
650 : {
651 : #ifdef ENABLE_HWACCEL
652 47 : 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 47 : const char* speedPreset = "veryfast";
666 : #endif
667 47 : if (av_opt_set(encoderCtx, "preset", speedPreset, AV_OPT_SEARCH_CHILDREN))
668 0 : JAMI_WARN("Failed to set preset '%s'", speedPreset);
669 47 : const char* tune = "zerolatency";
670 47 : if (av_opt_set(encoderCtx, "tune", tune, AV_OPT_SEARCH_CHILDREN))
671 0 : JAMI_WARN("Failed to set tune '%s'", tune);
672 : }
673 47 : }
674 :
675 : void
676 47 : 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 47 : ctx->profile = FF_PROFILE_H264_CONSTRAINED_BASELINE;
682 47 : ctx->level = 0x0d;
683 : // ctx->level = 0x0d; // => 13 aka 1.3
684 47 : if (parameters.empty())
685 0 : return;
686 :
687 47 : const std::string target("profile-level-id=");
688 47 : size_t needle = parameters.find(target);
689 47 : if (needle == std::string::npos)
690 0 : return;
691 :
692 47 : needle += target.length();
693 47 : const size_t id_length = 6; /* digits */
694 47 : const std::string profileLevelID(parameters.substr(needle, id_length));
695 47 : if (profileLevelID.length() != id_length)
696 0 : return;
697 :
698 : int result;
699 47 : std::stringstream ss;
700 47 : ss << profileLevelID;
701 47 : ss >> std::hex >> result;
702 : // profile-level id consists of three bytes
703 47 : const unsigned char profile_idc = result >> 16; // 42xxxx -> 42
704 47 : const unsigned char profile_iop = ((result >> 8) & 0xff); // xx80xx -> 80
705 47 : ctx->level = result & 0xff; // xxxx0d -> 0d
706 47 : switch (profile_idc) {
707 47 : case FF_PROFILE_H264_BASELINE:
708 : // check constraint_set_1_flag
709 47 : if ((profile_iop & 0x40) >> 6)
710 0 : ctx->profile |= FF_PROFILE_H264_CONSTRAINED;
711 47 : 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 0 : default:
720 0 : JAMI_DBG("Unrecognized H264 profile byte");
721 : }
722 47 : JAMI_DBG("Using profile %s (%x) and level %d",
723 : avcodec_profile_name(AV_CODEC_ID_H264, ctx->profile),
724 : ctx->profile,
725 : ctx->level);
726 47 : }
727 :
728 : #ifdef ENABLE_HWACCEL
729 : void
730 141 : MediaEncoder::enableAccel(bool enableAccel)
731 : {
732 141 : enableAccel_ = enableAccel;
733 141 : emitSignal<libjami::ConfigurationSignal::HardwareEncodingChanged>(enableAccel_);
734 141 : if (!enableAccel_) {
735 141 : accel_.reset();
736 141 : for (auto* enc : encoders_)
737 0 : enc->opaque = nullptr;
738 : }
739 141 : }
740 : #endif
741 :
742 : unsigned
743 2 : MediaEncoder::getStreamCount() const
744 : {
745 2 : return (audioOpts_.isValid() + videoOpts_.isValid());
746 : }
747 :
748 : MediaStream
749 0 : MediaEncoder::getStream(const std::string& name, int streamIdx) const
750 : {
751 : // if streamIdx is negative, use currentStreamIdx_
752 0 : if (streamIdx < 0)
753 0 : streamIdx = currentStreamIdx_;
754 : // make sure streamIdx is valid
755 0 : if (getStreamCount() <= 0 || streamIdx < 0 || encoders_.size() < (unsigned) (streamIdx + 1))
756 0 : return {};
757 0 : auto* enc = encoders_[streamIdx];
758 : // TODO set firstTimestamp
759 0 : auto ms = MediaStream(name, enc);
760 : #ifdef ENABLE_HWACCEL
761 0 : if (accel_)
762 0 : ms.format = accel_->getSoftwareFormat();
763 : #endif
764 0 : return ms;
765 0 : }
766 :
767 : int
768 1 : MediaEncoder::getCurrentAudioAVCtxFrameSize()
769 : {
770 1 : if (auto* ctx = getCurrentAudioAVCtx()) {
771 1 : return ctx->frame_size;
772 : }
773 0 : return 0;
774 : }
775 :
776 : AVCodecContext*
777 226 : MediaEncoder::initCodec(AVMediaType mediaType, AVCodecID avcodecId, uint64_t br)
778 : {
779 226 : outputCodec_ = nullptr;
780 : #ifdef ENABLE_HWACCEL
781 226 : if (mediaType == AVMEDIA_TYPE_VIDEO) {
782 48 : if (enableAccel_) {
783 0 : if (accel_) {
784 0 : outputCodec_ = avcodec_find_encoder_by_name(accel_->getCodecName().c_str());
785 : }
786 : } else {
787 48 : JAMI_WARN() << "Hardware encoding disabled";
788 : }
789 : }
790 : #endif
791 :
792 226 : if (!outputCodec_) {
793 : /* find the video encoder */
794 226 : if (avcodecId == AV_CODEC_ID_H263)
795 : // For H263 encoding, we force the use of AV_CODEC_ID_H263P (H263-1998)
796 : // H263-1998 can manage all frame sizes while H263 don't
797 : // AV_CODEC_ID_H263 decoder will be used for decoding
798 0 : outputCodec_ = avcodec_find_encoder(AV_CODEC_ID_H263P);
799 : else
800 226 : outputCodec_ = avcodec_find_encoder(static_cast<AVCodecID>(avcodecId));
801 226 : if (!outputCodec_) {
802 0 : throw MediaEncoderException("No output encoder");
803 : }
804 : }
805 :
806 226 : AVCodecContext* encoderCtx = prepareEncoderContext(outputCodec_, mediaType == AVMEDIA_TYPE_VIDEO);
807 :
808 : // Only clamp video bitrate
809 226 : if (mediaType == AVMEDIA_TYPE_VIDEO && br > 0) {
810 48 : if (br < SystemCodecInfo::DEFAULT_MIN_BITRATE) {
811 4 : JAMI_WARNING("Requested bitrate {:d} too low, setting to {:d}", br, SystemCodecInfo::DEFAULT_MIN_BITRATE);
812 1 : br = SystemCodecInfo::DEFAULT_MIN_BITRATE;
813 47 : } else if (br > SystemCodecInfo::DEFAULT_MAX_BITRATE) {
814 0 : JAMI_WARNING("Requested bitrate {:d} too high, setting to {:d}", br, SystemCodecInfo::DEFAULT_MAX_BITRATE);
815 0 : br = SystemCodecInfo::DEFAULT_MAX_BITRATE;
816 : }
817 : }
818 :
819 : /* Enable libopus FEC encoding support */
820 226 : if (mediaType == AVMEDIA_TYPE_AUDIO) {
821 178 : if (avcodecId == AV_CODEC_ID_OPUS) {
822 178 : initOpus(encoderCtx);
823 : }
824 : }
825 :
826 : /* let x264 preset override our encoder settings */
827 226 : if (avcodecId == AV_CODEC_ID_H264) {
828 47 : const auto* profileLevelId = libav_utils::getDictValue(options_, "parameters");
829 47 : extractProfileLevelID(profileLevelId, encoderCtx);
830 47 : forcePresetX2645(encoderCtx);
831 47 : encoderCtx->flags2 |= AV_CODEC_FLAG2_LOCAL_HEADER;
832 47 : initH264(encoderCtx, br);
833 179 : } else if (avcodecId == AV_CODEC_ID_HEVC) {
834 0 : encoderCtx->profile = FF_PROFILE_HEVC_MAIN;
835 0 : forcePresetX2645(encoderCtx);
836 0 : initH265(encoderCtx, br);
837 0 : av_opt_set_int(encoderCtx, "b_ref_mode", 0, AV_OPT_SEARCH_CHILDREN);
838 179 : } else if (avcodecId == AV_CODEC_ID_VP8) {
839 1 : initVP8(encoderCtx, br);
840 178 : } else if (avcodecId == AV_CODEC_ID_MPEG4) {
841 0 : initMPEG4(encoderCtx, br);
842 178 : } else if (avcodecId == AV_CODEC_ID_H263) {
843 0 : initH263(encoderCtx, br);
844 : }
845 226 : initAccel(encoderCtx, br);
846 226 : return encoderCtx;
847 : }
848 :
849 : int
850 31 : MediaEncoder::setBitrate(uint64_t br)
851 : {
852 31 : std::lock_guard lk(encMutex_);
853 31 : AVCodecContext* encoderCtx = getCurrentVideoAVCtx();
854 31 : if (not encoderCtx)
855 0 : return -1; // NOK
856 :
857 31 : AVCodecID codecId = encoderCtx->codec_id;
858 :
859 31 : if (not isDynBitrateSupported(codecId))
860 0 : return 0; // Restart needed
861 :
862 : // No need to restart encoder for h264, h263 and MPEG4
863 : // Change parameters on the fly
864 31 : if (codecId == AV_CODEC_ID_H264)
865 31 : initH264(encoderCtx, br);
866 31 : if (codecId == AV_CODEC_ID_HEVC)
867 0 : initH265(encoderCtx, br);
868 31 : else if (codecId == AV_CODEC_ID_H263P)
869 0 : initH263(encoderCtx, br);
870 31 : else if (codecId == AV_CODEC_ID_MPEG4)
871 0 : initMPEG4(encoderCtx, br);
872 : else {
873 : // restart encoder on runtime doesn't work for VP8
874 : // stopEncoder();
875 : // encoderCtx = initCodec(codecType, codecId, br);
876 : // if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0)
877 : // throw MediaEncoderException("Unable to open encoder");
878 : }
879 31 : initAccel(encoderCtx, br);
880 31 : return 1; // OK
881 31 : }
882 :
883 : int
884 0 : MediaEncoder::setPacketLoss(uint64_t pl)
885 : {
886 0 : std::lock_guard lk(encMutex_);
887 0 : AVCodecContext* encoderCtx = getCurrentAudioAVCtx();
888 0 : if (not encoderCtx)
889 0 : return -1; // NOK
890 :
891 0 : AVCodecID codecId = encoderCtx->codec_id;
892 :
893 0 : if (not isDynPacketLossSupported(codecId))
894 0 : return 0; // Restart needed
895 :
896 : // Cap between 0 and 100
897 0 : pl = std::clamp((int) pl, 0, 100);
898 :
899 : // Change parameters on the fly
900 0 : if (codecId == AV_CODEC_ID_OPUS)
901 0 : av_opt_set_int(encoderCtx, "packet_loss", (int64_t) pl, AV_OPT_SEARCH_CHILDREN);
902 0 : return 1; // OK
903 0 : }
904 :
905 : void
906 78 : MediaEncoder::initH264(AVCodecContext* encoderCtx, uint64_t br)
907 : {
908 78 : int64_t maxBitrate = static_cast<int64_t>(1000 * br);
909 : // 200 Kbit/s -> CRF40
910 : // 6 Mbit/s -> CRF23
911 78 : uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + LOGREG_PARAM_B * std::log(maxBitrate));
912 : // bufsize parameter impact the variation of the bitrate, reduce to half the maxrate to limit
913 : // peak and congestion
914 : // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate
915 78 : int64_t bufSize = maxBitrate / 2;
916 :
917 : // If auto quality disabled use CRF mode
918 78 : if (mode_ == RateMode::CRF_CONSTRAINED) {
919 78 : av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN);
920 78 : av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
921 78 : av_opt_set_int(encoderCtx, "bufsize", bufSize, AV_OPT_SEARCH_CHILDREN);
922 312 : JAMI_DEBUG("H264 encoder setup: crf={:d}, maxrate={:d} kbit/s, bufsize={:d} kbit",
923 : crf,
924 : maxBitrate / 1000,
925 : bufSize / 1000);
926 0 : } else if (mode_ == RateMode::CBR) {
927 0 : av_opt_set_int(encoderCtx, "b", maxBitrate, AV_OPT_SEARCH_CHILDREN);
928 0 : av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
929 0 : av_opt_set_int(encoderCtx, "minrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
930 0 : av_opt_set_int(encoderCtx, "bufsize", bufSize, AV_OPT_SEARCH_CHILDREN);
931 0 : av_opt_set_int(encoderCtx, "crf", -1, AV_OPT_SEARCH_CHILDREN);
932 :
933 0 : JAMI_DEBUG("H264 encoder setup cbr: bitrate={:d} kbit/s", br);
934 : }
935 78 : }
936 :
937 : void
938 0 : MediaEncoder::initH265(AVCodecContext* encoderCtx, uint64_t br)
939 : {
940 : // If auto quality disabled use CRF mode
941 0 : if (mode_ == RateMode::CRF_CONSTRAINED) {
942 0 : int64_t maxBitrate = static_cast<int64_t>(1000 * br);
943 : // H265 use 50% less bitrate compared to H264 (half bitrate is equivalent to a change 6 for
944 : // CRF) https://slhck.info/video/2017/02/24/crf-guide.html
945 : // 200 Kbit/s -> CRF35
946 : // 6 Mbit/s -> CRF18
947 0 : uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A_HEVC + LOGREG_PARAM_B_HEVC * std::log(maxBitrate));
948 0 : int64_t bufSize = maxBitrate / 2;
949 0 : av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN);
950 0 : av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
951 0 : av_opt_set_int(encoderCtx, "bufsize", bufSize, AV_OPT_SEARCH_CHILDREN);
952 0 : JAMI_DEBUG("H265 encoder setup: crf={:d}, maxrate={:d} kbit/s, bufsize={:d} kbit",
953 : crf,
954 : maxBitrate / 1000,
955 : bufSize / 1000);
956 0 : } else if (mode_ == RateMode::CBR) {
957 0 : int64_t signedBr = static_cast<int64_t>(br);
958 0 : av_opt_set_int(encoderCtx, "b", signedBr * 1000, AV_OPT_SEARCH_CHILDREN);
959 0 : av_opt_set_int(encoderCtx, "maxrate", signedBr * 1000, AV_OPT_SEARCH_CHILDREN);
960 0 : av_opt_set_int(encoderCtx, "minrate", signedBr * 1000, AV_OPT_SEARCH_CHILDREN);
961 0 : av_opt_set_int(encoderCtx, "bufsize", signedBr * 500, AV_OPT_SEARCH_CHILDREN);
962 0 : av_opt_set_int(encoderCtx, "crf", -1, AV_OPT_SEARCH_CHILDREN);
963 0 : JAMI_DEBUG("H265 encoder setup cbr: bitrate={:d} kbit/s", br);
964 : }
965 0 : }
966 :
967 : void
968 1 : MediaEncoder::initVP8(AVCodecContext* encoderCtx, uint64_t br)
969 : {
970 1 : if (mode_ == RateMode::CQ) {
971 0 : av_opt_set_int(encoderCtx, "g", 120, AV_OPT_SEARCH_CHILDREN);
972 0 : av_opt_set_int(encoderCtx, "lag-in-frames", 0, AV_OPT_SEARCH_CHILDREN);
973 0 : av_opt_set(encoderCtx, "deadline", "good", AV_OPT_SEARCH_CHILDREN);
974 0 : av_opt_set_int(encoderCtx, "cpu-used", 0, AV_OPT_SEARCH_CHILDREN);
975 0 : av_opt_set_int(encoderCtx, "vprofile", 0, AV_OPT_SEARCH_CHILDREN);
976 0 : av_opt_set_int(encoderCtx, "qmax", 23, AV_OPT_SEARCH_CHILDREN);
977 0 : av_opt_set_int(encoderCtx, "qmin", 0, AV_OPT_SEARCH_CHILDREN);
978 0 : av_opt_set_int(encoderCtx, "slices", 4, AV_OPT_SEARCH_CHILDREN);
979 0 : av_opt_set_int(encoderCtx, "crf", 18, AV_OPT_SEARCH_CHILDREN);
980 0 : JAMI_DEBUG("VP8 encoder setup: crf=18");
981 : } else {
982 : // 1- if quality is set use it
983 : // bitrate need to be set. The target bitrate becomes the maximum allowed bitrate
984 : // 2- otherwise set rc_max_rate and rc_buffer_size
985 : // Using information given on this page:
986 : // http://www.webmproject.org/docs/encoder-parameters/
987 1 : int64_t maxBitrate = static_cast<int64_t>(1000 * br);
988 : // 200 Kbit/s -> CRF40
989 : // 6 Mbit/s -> CRF23
990 1 : uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + LOGREG_PARAM_B * std::log(maxBitrate));
991 1 : int64_t bufSize = maxBitrate / 2;
992 :
993 1 : av_opt_set(encoderCtx, "quality", "realtime", AV_OPT_SEARCH_CHILDREN);
994 1 : av_opt_set_int(encoderCtx, "error-resilient", 1, AV_OPT_SEARCH_CHILDREN);
995 1 : av_opt_set_int(encoderCtx, "cpu-used", 7,
996 : AV_OPT_SEARCH_CHILDREN); // value obtained from testing
997 1 : av_opt_set_int(encoderCtx, "lag-in-frames", 0, AV_OPT_SEARCH_CHILDREN);
998 : // allow encoder to drop frames if buffers are full and
999 : // to undershoot target bitrate to lessen strain on resources
1000 1 : av_opt_set_int(encoderCtx, "drop-frame", 25, AV_OPT_SEARCH_CHILDREN);
1001 1 : av_opt_set_int(encoderCtx, "undershoot-pct", 95, AV_OPT_SEARCH_CHILDREN);
1002 : // don't set encoderCtx->gop_size: let libvpx decide when to insert a keyframe
1003 1 : av_opt_set_int(encoderCtx, "slices", 2, AV_OPT_SEARCH_CHILDREN); // VP8E_SET_TOKEN_PARTITIONS
1004 1 : av_opt_set_int(encoderCtx, "qmax", 56, AV_OPT_SEARCH_CHILDREN);
1005 1 : av_opt_set_int(encoderCtx, "qmin", 4, AV_OPT_SEARCH_CHILDREN);
1006 1 : crf = std::clamp((int) crf, 4, 56);
1007 1 : av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN);
1008 1 : av_opt_set_int(encoderCtx, "b", maxBitrate, AV_OPT_SEARCH_CHILDREN);
1009 1 : av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
1010 1 : av_opt_set_int(encoderCtx, "bufsize", bufSize, AV_OPT_SEARCH_CHILDREN);
1011 4 : JAMI_DEBUG("VP8 encoder setup: crf={:d}, maxrate={:d}, bufsize={:d}", crf, maxBitrate / 1000, bufSize / 1000);
1012 : }
1013 1 : }
1014 :
1015 : void
1016 0 : MediaEncoder::initMPEG4(AVCodecContext* encoderCtx, uint64_t br)
1017 : {
1018 0 : int64_t maxBitrate = static_cast<int64_t>(1000 * br);
1019 0 : int bufSize = static_cast<int>(maxBitrate / 2);
1020 :
1021 : // Use CBR (set bitrate)
1022 0 : encoderCtx->rc_buffer_size = bufSize;
1023 0 : encoderCtx->bit_rate = encoderCtx->rc_min_rate = encoderCtx->rc_max_rate = maxBitrate;
1024 0 : JAMI_DEBUG("MPEG4 encoder setup: maxrate={:d}, bufsize={:d}", maxBitrate, bufSize);
1025 0 : }
1026 :
1027 : void
1028 0 : MediaEncoder::initH263(AVCodecContext* encoderCtx, uint64_t br)
1029 : {
1030 0 : int64_t maxBitrate = static_cast<int64_t>(1000 * br);
1031 0 : int bufSize = static_cast<int>(maxBitrate) / 2;
1032 :
1033 : // Use CBR (set bitrate)
1034 0 : encoderCtx->rc_buffer_size = bufSize;
1035 0 : encoderCtx->bit_rate = encoderCtx->rc_min_rate = encoderCtx->rc_max_rate = maxBitrate;
1036 0 : JAMI_DEBUG("H263 encoder setup: maxrate={:d}, bufsize={:d}", maxBitrate, bufSize);
1037 0 : }
1038 :
1039 : void
1040 178 : MediaEncoder::initOpus(AVCodecContext* encoderCtx)
1041 : {
1042 : // Enable FEC support by default with 10% packet loss
1043 178 : av_opt_set_int(encoderCtx, "fec", fecEnabled_ ? 1 : 0, AV_OPT_SEARCH_CHILDREN);
1044 178 : av_opt_set_int(encoderCtx, "packet_loss", 10, AV_OPT_SEARCH_CHILDREN);
1045 178 : }
1046 :
1047 : void
1048 257 : MediaEncoder::initAccel(AVCodecContext* encoderCtx, uint64_t br)
1049 : {
1050 : #ifdef ENABLE_HWACCEL
1051 257 : float val = static_cast<float>(br) * 1000.0f * 0.8f;
1052 257 : if (not accel_)
1053 257 : return;
1054 0 : if (accel_->getName() == "nvenc"sv) {
1055 : // Use same parameters as software
1056 0 : } else if (accel_->getName() == "vaapi"sv) {
1057 : // Use VBR encoding with bitrate target set to 80% of the maxrate
1058 0 : av_opt_set_int(encoderCtx, "crf", -1, AV_OPT_SEARCH_CHILDREN);
1059 0 : av_opt_set_int(encoderCtx, "b", static_cast<int64_t>(val), AV_OPT_SEARCH_CHILDREN);
1060 0 : } else if (accel_->getName() == "videotoolbox"sv) {
1061 0 : av_opt_set_int(encoderCtx, "b", static_cast<int64_t>(val), AV_OPT_SEARCH_CHILDREN);
1062 0 : } else if (accel_->getName() == "qsv"sv) {
1063 : // Use Video Conferencing Mode
1064 0 : av_opt_set_int(encoderCtx, "vcm", 1, AV_OPT_SEARCH_CHILDREN);
1065 0 : av_opt_set_int(encoderCtx, "b", static_cast<int64_t>(val), AV_OPT_SEARCH_CHILDREN);
1066 : }
1067 : #endif
1068 : }
1069 :
1070 : AVCodecContext*
1071 31 : MediaEncoder::getCurrentVideoAVCtx()
1072 : {
1073 31 : for (auto* it : encoders_) {
1074 31 : if (it->codec_type == AVMEDIA_TYPE_VIDEO)
1075 31 : return it;
1076 : }
1077 0 : return nullptr;
1078 : }
1079 :
1080 : AVCodecContext*
1081 1 : MediaEncoder::getCurrentAudioAVCtx()
1082 : {
1083 1 : for (auto* it : encoders_) {
1084 1 : if (it->codec_type == AVMEDIA_TYPE_AUDIO)
1085 1 : return it;
1086 : }
1087 0 : return nullptr;
1088 : }
1089 :
1090 : void
1091 0 : MediaEncoder::stopEncoder()
1092 : {
1093 0 : flush();
1094 0 : for (auto it = encoders_.begin(); it != encoders_.end(); it++) {
1095 0 : if ((*it)->codec_type == AVMEDIA_TYPE_VIDEO) {
1096 0 : encoders_.erase(it);
1097 0 : break;
1098 : }
1099 : }
1100 0 : AVCodecContext* encoderCtx = getCurrentVideoAVCtx();
1101 0 : avcodec_close(encoderCtx);
1102 0 : avcodec_free_context(&encoderCtx);
1103 0 : av_free(encoderCtx);
1104 0 : }
1105 :
1106 : bool
1107 31 : MediaEncoder::isDynBitrateSupported(AVCodecID codecid)
1108 : {
1109 : #ifdef ENABLE_HWACCEL
1110 31 : if (accel_) {
1111 0 : return accel_->dynBitrate();
1112 : }
1113 : #endif
1114 31 : if (codecid != AV_CODEC_ID_VP8)
1115 31 : return true;
1116 :
1117 0 : return false;
1118 : }
1119 :
1120 : bool
1121 0 : MediaEncoder::isDynPacketLossSupported(AVCodecID codecid)
1122 : {
1123 0 : if (codecid == AV_CODEC_ID_OPUS)
1124 0 : return true;
1125 :
1126 0 : return false;
1127 : }
1128 :
1129 : void
1130 226 : MediaEncoder::readConfig(AVCodecContext* encoderCtx)
1131 : {
1132 226 : auto path = fileutils::get_config_dir() / "encoder.json";
1133 226 : std::string name = encoderCtx->codec->name;
1134 226 : std::error_code ec;
1135 226 : if (std::filesystem::is_regular_file(path, ec)) {
1136 0 : JAMI_WARN("encoder.json file found, default settings will be erased");
1137 : try {
1138 0 : Json::Value root;
1139 0 : std::ifstream file(path);
1140 0 : file >> root;
1141 0 : if (!root.isObject()) {
1142 0 : JAMI_ERR() << "Invalid encoder configuration: root is not an object";
1143 0 : return;
1144 : }
1145 0 : const auto& config = root[name];
1146 0 : if (config.isNull()) {
1147 0 : JAMI_WARN() << "Encoder '" << name << "' not found in configuration file";
1148 0 : return;
1149 : }
1150 0 : if (!config.isObject()) {
1151 0 : JAMI_ERR() << "Invalid encoder configuration: '" << name << "' is not an object";
1152 0 : return;
1153 : }
1154 0 : for (Json::Value::const_iterator it = config.begin(); it != config.end(); ++it) {
1155 0 : const Json::Value& v = *it;
1156 0 : if (!it.key().isConvertibleTo(Json::ValueType::stringValue)
1157 0 : || !v.isConvertibleTo(Json::ValueType::stringValue)) {
1158 0 : JAMI_ERR() << "Invalid configuration for '" << name << "'";
1159 0 : return;
1160 : }
1161 0 : const auto& key = it.key().asString();
1162 0 : const auto& value = v.asString();
1163 0 : int ret = av_opt_set(reinterpret_cast<void*>(encoderCtx),
1164 : key.c_str(),
1165 : value.c_str(),
1166 : AV_OPT_SEARCH_CHILDREN);
1167 0 : if (ret < 0) {
1168 0 : JAMI_ERR() << "Failed to set option " << key << " in " << name
1169 0 : << " context: " << libav_utils::getError(ret) << "\n";
1170 : }
1171 0 : }
1172 0 : } catch (const Json::Exception& e) {
1173 0 : JAMI_ERR() << "Failed to load encoder configuration file: " << e.what();
1174 0 : }
1175 : }
1176 226 : }
1177 :
1178 : std::string
1179 268 : MediaEncoder::testH265Accel()
1180 : {
1181 : #ifdef ENABLE_HWACCEL
1182 268 : if (jami::Manager::instance().videoPreferences.getEncodingAccelerated()) {
1183 : // Get compatible list of Hardware API
1184 0 : auto APIs = video::HardwareAccel::getCompatibleAccel(AV_CODEC_ID_H265, 1280, 720, CODEC_ENCODER);
1185 :
1186 0 : std::unique_ptr<video::HardwareAccel> accel;
1187 :
1188 0 : for (const auto& it : APIs) {
1189 0 : accel = std::make_unique<video::HardwareAccel>(it); // save accel
1190 : // Init codec need accel to init encoderCtx accelerated
1191 0 : const auto* outputCodec = avcodec_find_encoder_by_name(accel->getCodecName().c_str());
1192 :
1193 0 : AVCodecContext* encoderCtx = avcodec_alloc_context3(outputCodec);
1194 0 : encoderCtx->thread_count = static_cast<int>(std::min(std::thread::hardware_concurrency(), 16u));
1195 0 : encoderCtx->width = 1280;
1196 0 : encoderCtx->height = 720;
1197 : AVRational framerate;
1198 0 : framerate.num = 30;
1199 0 : framerate.den = 1;
1200 0 : encoderCtx->time_base = av_inv_q(framerate);
1201 0 : encoderCtx->pix_fmt = accel->getFormat();
1202 0 : encoderCtx->profile = FF_PROFILE_HEVC_MAIN;
1203 0 : encoderCtx->opaque = accel.get();
1204 :
1205 0 : auto br = static_cast<int64_t>(SystemCodecInfo::DEFAULT_VIDEO_BITRATE);
1206 0 : av_opt_set_int(encoderCtx, "b", br * 1000, AV_OPT_SEARCH_CHILDREN);
1207 0 : av_opt_set_int(encoderCtx, "maxrate", br * 1000, AV_OPT_SEARCH_CHILDREN);
1208 0 : av_opt_set_int(encoderCtx, "minrate", br * 1000, AV_OPT_SEARCH_CHILDREN);
1209 0 : av_opt_set_int(encoderCtx, "bufsize", br * 500, AV_OPT_SEARCH_CHILDREN);
1210 0 : av_opt_set_int(encoderCtx, "crf", -1, AV_OPT_SEARCH_CHILDREN);
1211 :
1212 0 : auto ret = accel->initAPI(false, nullptr);
1213 0 : if (ret < 0) {
1214 0 : accel.reset();
1215 : ;
1216 0 : encoderCtx = nullptr;
1217 0 : continue;
1218 : }
1219 0 : accel->setDetails(encoderCtx);
1220 0 : if (avcodec_open2(encoderCtx, outputCodec, nullptr) < 0) {
1221 : // Failed to open codec
1222 0 : JAMI_WARN("Fail to open hardware encoder H265 with %s ", it.getName().c_str());
1223 0 : avcodec_free_context(&encoderCtx);
1224 0 : encoderCtx = nullptr;
1225 0 : accel = nullptr;
1226 0 : continue;
1227 : } else {
1228 : // Succeed to open codec
1229 0 : avcodec_free_context(&encoderCtx);
1230 0 : encoderCtx = nullptr;
1231 0 : accel = nullptr;
1232 0 : return it.getName();
1233 : }
1234 : }
1235 0 : }
1236 : #endif
1237 268 : return "";
1238 : }
1239 :
1240 : #ifdef ENABLE_VIDEO
1241 : int
1242 5104 : MediaEncoder::getHWFrame(const std::shared_ptr<VideoFrame>& input, std::shared_ptr<VideoFrame>& output)
1243 : {
1244 : try {
1245 : #if defined(TARGET_OS_IOS) && TARGET_OS_IOS
1246 : // iOS
1247 : if (accel_) {
1248 : auto pix = accel_->getSoftwareFormat();
1249 : if (input->format() != pix) {
1250 : output = scaler_.convertFormat(*input.get(), pix);
1251 : } else {
1252 : // Fully accelerated pipeline, skip main memory
1253 : output = input;
1254 : }
1255 : } else {
1256 : output = getScaledSWFrame(*input.get());
1257 : }
1258 : #elif !defined(__APPLE__) && defined(ENABLE_HWACCEL)
1259 : // Other Platforms
1260 5104 : const auto* desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format()));
1261 5104 : bool isHardware = desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL);
1262 5104 : if (accel_ && accel_->isLinked() && isHardware) {
1263 : // Fully accelerated pipeline, skip main memory
1264 0 : output = input;
1265 5104 : } else if (isHardware) {
1266 : // Hardware decoded frame, transfer back to main memory
1267 : // Transfer to GPU if we have a hardware encoder
1268 : // Hardware decoders decode to NV12, but Jami's supported software encoders want YUV420P
1269 0 : output = getUnlinkedHWFrame(*input.get());
1270 5104 : } else if (accel_) {
1271 : // Software decoded frame with a hardware encoder, convert to accepted format first
1272 0 : output = getHWFrameFromSWFrame(*input.get());
1273 : } else {
1274 5104 : output = getScaledSWFrame(*input.get());
1275 : }
1276 : #else
1277 : // macOS
1278 : output = getScaledSWFrame(*input.get());
1279 : #endif
1280 0 : } catch (const std::runtime_error& e) {
1281 0 : JAMI_ERR("Accel failure: %s", e.what());
1282 0 : return -1;
1283 0 : }
1284 :
1285 5104 : return 0;
1286 : }
1287 :
1288 : #ifdef ENABLE_HWACCEL
1289 : std::shared_ptr<VideoFrame>
1290 0 : MediaEncoder::getUnlinkedHWFrame(const VideoFrame& input)
1291 : {
1292 0 : AVPixelFormat pix = (accel_ ? accel_->getSoftwareFormat() : AV_PIX_FMT_NV12);
1293 0 : std::shared_ptr<VideoFrame> framePtr = video::HardwareAccel::transferToMainMemory(input, pix);
1294 0 : if (!accel_) {
1295 0 : framePtr = scaler_.convertFormat(*framePtr, AV_PIX_FMT_YUV420P);
1296 : } else {
1297 0 : framePtr = accel_->transfer(*framePtr);
1298 : }
1299 0 : return framePtr;
1300 0 : }
1301 :
1302 : std::shared_ptr<VideoFrame>
1303 0 : MediaEncoder::getHWFrameFromSWFrame(const VideoFrame& input)
1304 : {
1305 0 : std::shared_ptr<VideoFrame> framePtr;
1306 0 : auto pix = accel_->getSoftwareFormat();
1307 0 : if (input.format() != pix) {
1308 0 : framePtr = scaler_.convertFormat(input, pix);
1309 0 : framePtr = accel_->transfer(*framePtr);
1310 : } else {
1311 0 : framePtr = accel_->transfer(input);
1312 : }
1313 0 : return framePtr;
1314 0 : }
1315 : #endif
1316 :
1317 : std::shared_ptr<VideoFrame>
1318 5104 : MediaEncoder::getScaledSWFrame(const VideoFrame& input)
1319 : {
1320 5104 : libav_utils::fillWithBlack(scaledFrame_->pointer());
1321 5104 : scaler_.scale_with_aspect(input, *scaledFrame_);
1322 5104 : return scaledFrame_;
1323 : }
1324 : #endif
1325 :
1326 : void
1327 0 : MediaEncoder::resetStreams(int width, int height)
1328 : {
1329 0 : videoOpts_.width = width;
1330 0 : videoOpts_.height = height;
1331 :
1332 : try {
1333 0 : flush();
1334 0 : initialized_ = false;
1335 0 : if (outputCtx_) {
1336 0 : for (auto* encoderCtx : encoders_) {
1337 0 : if (encoderCtx) {
1338 : #ifndef _MSC_VER
1339 0 : avcodec_free_context(&encoderCtx);
1340 : #else
1341 : avcodec_close(encoderCtx);
1342 : #endif
1343 : }
1344 : }
1345 0 : encoders_.clear();
1346 : }
1347 0 : } catch (const std::exception& e) {
1348 0 : JAMI_WARNING("Couldn't reset streams: {}", e.what());
1349 0 : }
1350 0 : }
1351 :
1352 : } // namespace jami
|