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