Ring Daemon
Loading...
Searching...
No Matches
media_encoder.cpp
Go to the documentation of this file.
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"
28
29#ifdef ENABLE_HWACCEL
30#include "video/accel.h"
31#endif
32
33extern "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
49using namespace std::literals;
50
51namespace jami {
52
53constexpr double LOGREG_PARAM_A {101};
54constexpr double LOGREG_PARAM_B {-5.};
55
56constexpr double LOGREG_PARAM_A_HEVC {96};
57constexpr double LOGREG_PARAM_B_HEVC {-5.};
58
60 : outputCtx_(avformat_alloc_context())
61{
62 JAMI_DBG("[%p] New instance created", this);
63}
64
66{
67 if (outputCtx_) {
68 if (outputCtx_->priv_data && outputCtx_->pb)
69 av_write_trailer(outputCtx_);
70 if (fileIO_) {
71 avio_close(outputCtx_->pb);
72 }
73 for (auto* encoderCtx : encoders_) {
74 if (encoderCtx) {
75#ifndef _MSC_VER
77#else
79#endif
80 }
81 }
82 avformat_free_context(outputCtx_);
83 }
85
86 JAMI_DBG("[%p] Instance destroyed", this);
87}
88
89void
91{
92 if (!opts.isValid()) {
93 JAMI_ERR() << "Invalid options";
94 return;
95 }
96
97 if (opts.isVideo) {
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 videoOpts_.width = ((videoOpts_.width >> 3) << 3);
103 videoOpts_.height = ((videoOpts_.height >> 3) << 3);
106 if (!videoOpts_.bitrate) {
108 }
109 } else {
111 }
112}
113
114void
116{
117 int ret;
118 if (args.payload_type
119 and (ret = av_opt_set_int(reinterpret_cast<void*>(outputCtx_),
120 "payload_type",
121 args.payload_type,
123 < 0))
124 JAMI_ERR() << "Failed to set payload type: " << libav_utils::getError(ret);
125
126 if (not args.parameters.empty())
127 libav_utils::setDictValue(&options_, "parameters", args.parameters);
128
129 mode_ = args.mode;
130 linkableHW_ = args.linkableHW;
131 fecEnabled_ = args.fecEnabled;
132}
133
134void
135MediaEncoder::setMetadata(const std::string& title, const std::string& description)
136{
137 if (not title.empty())
138 libav_utils::setDictValue(&outputCtx_->metadata, "title", title);
139 if (not description.empty())
140 libav_utils::setDictValue(&outputCtx_->metadata, "description", description);
141}
142
143void
145{
146 // only set not default value (!=0)
147 if (seqVal != 0)
148 av_opt_set_int(outputCtx_, "seq", seqVal, AV_OPT_SEARCH_CHILDREN);
149}
150
153{
155 if (av_opt_get_int(outputCtx_, "seq", AV_OPT_SEARCH_CHILDREN, &retVal) >= 0)
156 return (uint16_t) retVal;
157 else
158 return 0;
159}
160
161void
162MediaEncoder::openOutput(const std::string& filename, const std::string& format)
163{
164 avformat_free_context(outputCtx_);
165 int result = avformat_alloc_output_context2(&outputCtx_,
166 nullptr,
167 format.empty() ? nullptr : format.c_str(),
168 filename.c_str());
169 if (result < 0)
170 JAMI_ERR() << "Unable to open " << filename << ": " << libav_utils::getError(-result);
171}
172
173int
175{
176 if (systemCodecInfo.mediaType == MEDIA_AUDIO) {
178 } else {
180 }
181
182 auto* stream = avformat_new_stream(outputCtx_, outputCodec_);
183
184 if (stream == nullptr) {
185 JAMI_ERR("[%p] Failed to create coding instance for %s", this, systemCodecInfo.name.c_str());
186 return -1;
187 }
188
189 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 if (systemCodecInfo.mediaType == MEDIA_AUDIO) {
193 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 if (audioOpts_.isValid())
201 return 1;
202 else
203 return 0;
204}
205
206int
207MediaEncoder::initStream(const std::string& codecName, AVBufferRef* framesCtx)
208{
209 const auto codecInfo = getSystemCodecContainer()->searchCodecByName(codecName, MEDIA_ALL);
210 if (codecInfo)
211 return initStream(*codecInfo, framesCtx);
212 else
213 return -1;
214}
215
216int
217MediaEncoder::initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* framesCtx)
218{
219 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 std::lock_guard lk(encMutex_);
226
227 if (!outputCtx_)
228 throw MediaEncoderException("Unable to allocate stream");
229
230 // Must already have codec instance(s)
231 if (outputCtx_->nb_streams == 0) {
232 JAMI_ERR("[%p] Unable to init, output context has no coding sessions!", this);
233 throw MediaEncoderException("Unable to init, output context has no coding sessions!");
234 }
235
236 AVCodecContext* encoderCtx = nullptr;
237 AVMediaType mediaType;
238
239 if (systemCodecInfo.mediaType == MEDIA_VIDEO)
240 mediaType = AVMEDIA_TYPE_VIDEO;
241 else if (systemCodecInfo.mediaType == MEDIA_AUDIO)
242 mediaType = AVMEDIA_TYPE_AUDIO;
243 else
244 throw MediaEncoderException("Unsuported media type");
245
246 AVStream* stream {nullptr};
247
248 // Only supports one audio and one video streams at most per instance.
249 for (unsigned i = 0; i < outputCtx_->nb_streams; i++) {
250 stream = outputCtx_->streams[i];
251 if (stream->codecpar->codec_type == mediaType) {
252 if (mediaType == AVMEDIA_TYPE_VIDEO) {
253 stream->codecpar->width = videoOpts_.width;
254 stream->codecpar->height = videoOpts_.height;
255 }
256 break;
257 }
258 }
259
260 if (stream == nullptr) {
261 JAMI_ERR("[%p] Unable to init, output context has no coding sessions for %s",
262 this,
263 systemCodecInfo.name.c_str());
264 throw MediaEncoderException("Unable to allocate stream");
265 }
266
267 currentStreamIdx_ = stream->index;
268#ifdef ENABLE_HWACCEL
269 // Get compatible list of Hardware API
270 if (enableAccel_ && mediaType == AVMEDIA_TYPE_VIDEO) {
275 for (const auto& it : APIs) {
276 accel_ = std::make_unique<video::HardwareAccel>(it); // save accel
277 // Init codec need accel_ to init encoderCtx accelerated
278 encoderCtx = initCodec(mediaType, static_cast<AVCodecID>(systemCodecInfo.avcodecId), videoOpts_.bitrate);
279 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 if (framesCtx) {
284 auto* hw = reinterpret_cast<AVHWFramesContext*>(framesCtx->data);
285 if (encoderCtx->pix_fmt != hw->format)
286 linkableHW_ = false;
287 }
288 auto ret = accel_->initAPI(linkableHW_, framesCtx);
289 if (ret < 0) {
290 accel_.reset();
291 encoderCtx = nullptr;
292 continue;
293 }
294 accel_->setDetails(encoderCtx);
295 if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0) {
296 // Failed to open codec
297 JAMI_WARN("Fail to open hardware encoder %s with %s ",
298 avcodec_get_name(static_cast<AVCodecID>(systemCodecInfo.avcodecId)),
299 it.getName().c_str());
301 encoderCtx = nullptr;
302 accel_ = nullptr;
303 continue;
304 } else {
305 // Succeed to open codec
306 JAMI_WARN("Using hardware encoding for %s with %s ",
307 avcodec_get_name(static_cast<AVCodecID>(systemCodecInfo.avcodecId)),
308 it.getName().c_str());
309 encoders_.emplace_back(encoderCtx);
310 break;
311 }
312 }
313 }
314#endif
315
316 if (!encoderCtx) {
317 JAMI_WARN("Not using hardware encoding for %s",
318 avcodec_get_name(static_cast<AVCodecID>(systemCodecInfo.avcodecId)));
319 encoderCtx = initCodec(mediaType, static_cast<AVCodecID>(systemCodecInfo.avcodecId), videoOpts_.bitrate);
321 encoders_.emplace_back(encoderCtx);
322 if (avcodec_open2(encoderCtx, outputCodec_, &options_) < 0)
323 throw MediaEncoderException("Unable to open encoder");
324 }
325
327
328 // framerate is not copied from encoderCtx to stream
329 stream->avg_frame_rate = encoderCtx->framerate;
330#ifdef ENABLE_VIDEO
331 if (systemCodecInfo.mediaType == MEDIA_VIDEO) {
332 // allocate buffers for both scaled (pre-encoder) and encoded frames
333 const int width = encoderCtx->width;
334 const int height = encoderCtx->height;
335 int format = encoderCtx->pix_fmt;
336#ifdef ENABLE_HWACCEL
337 if (accel_) {
338 // hardware encoders require a specific pixel format
339 const auto* desc = av_pix_fmt_desc_get(encoderCtx->pix_fmt);
340 if (desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL))
341 format = accel_->getSoftwareFormat();
342 }
343#endif
344 scaledFrameBufferSize_ = videoFrameSize(format, width, height);
345 if (scaledFrameBufferSize_ < 0)
346 throw MediaEncoderException(
347 ("Unable to compute buffer size: " + libav_utils::getError(scaledFrameBufferSize_)).c_str());
348 else if (scaledFrameBufferSize_ <= AV_INPUT_BUFFER_MIN_SIZE)
349 throw MediaEncoderException("buffer too small");
350
351 scaledFrameBuffer_.resize(scaledFrameBufferSize_);
352 scaledFrame_ = std::make_shared<VideoFrame>();
353 scaledFrame_->setFromMemory(scaledFrameBuffer_.data(), format, width, height);
354 }
355#endif // ENABLE_VIDEO
356
357 return stream->index;
358}
359
360void
361MediaEncoder::openIOContext()
362{
363 if (ioCtx_) {
364 outputCtx_->pb = ioCtx_;
365 outputCtx_->packet_size = outputCtx_->pb->buffer_size;
366 } else {
367 int ret = 0;
368#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
369 const char* filename = outputCtx_->url;
370#else
371 const char* filename = outputCtx_->filename;
372#endif
373 if (!(outputCtx_->oformat->flags & AVFMT_NOFILE)) {
374 fileIO_ = true;
375 if ((ret = avio_open(&outputCtx_->pb, filename, AVIO_FLAG_WRITE)) < 0) {
376 throw MediaEncoderException(
377 fmt::format("Unable to open IO context for '{}': {}", filename, libav_utils::getError(ret)));
378 }
379 }
380 }
381}
382
383void
384MediaEncoder::startIO()
385{
386 if (!outputCtx_->pb)
387 openIOContext();
388 if (avformat_write_header(outputCtx_, options_ ? &options_ : nullptr)) {
389 JAMI_ERR("Unable to write header for output file... check codec parameters");
390 throw MediaEncoderException("Failed to write output file header");
391 }
392
393#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
394 av_dump_format(outputCtx_, 0, outputCtx_->url, 1);
395#else
396 av_dump_format(outputCtx_, 0, outputCtx_->filename, 1);
397#endif
398 initialized_ = true;
399}
400
401#ifdef ENABLE_VIDEO
402int
403MediaEncoder::encode(const std::shared_ptr<VideoFrame>& input, bool is_keyframe, int64_t frame_number)
404{
405 auto width = (input->width() >> 3) << 3;
406 auto height = (input->height() >> 3) << 3;
407 if (initialized_ && (getWidth() != width || getHeight() != height)) {
408 resetStreams(width, height);
409 is_keyframe = true;
410 }
411
412 if (!initialized_) {
413 initStream(videoCodec_, input->pointer()->hw_frames_ctx);
414 startIO();
415 }
416
417 std::shared_ptr<VideoFrame> output;
418#ifdef ENABLE_HWACCEL
419 if (getHWFrame(input, output) < 0) {
420 JAMI_ERR("Fail to get hardware frame");
421 return -1;
422 }
423#else
424 output = getScaledSWFrame(*input.get());
425#endif // ENABLE_HWACCEL
426
427 if (!output) {
428 JAMI_ERR("Fail to get frame");
429 return -1;
430 }
431 auto* avframe = output->pointer();
432
433 AVCodecContext* enc = encoders_[currentStreamIdx_];
434 avframe->pts = frame_number;
435 if (enc->framerate.num != enc->time_base.den || enc->framerate.den != enc->time_base.num)
436 avframe->pts /= (rational<int64_t>(enc->framerate) * rational<int64_t>(enc->time_base)).real<int64_t>();
437
438 if (is_keyframe) {
439 avframe->pict_type = AV_PICTURE_TYPE_I;
440 avframe->key_frame = 1;
441 } else {
442 avframe->pict_type = AV_PICTURE_TYPE_NONE;
443 avframe->key_frame = 0;
444 }
445
446 return encode(avframe, currentStreamIdx_);
447}
448#endif // ENABLE_VIDEO
449
450int
452{
453 if (!initialized_) {
454 // Initialize on first video frame, or first audio frame if no video stream
455 if (not videoOpts_.isValid())
456 startIO();
457 else
458 return 0;
459 }
460 frame.pointer()->pts = sent_samples;
461 sent_samples += frame.pointer()->nb_samples;
462 encode(frame.pointer(), currentStreamIdx_);
463 return 0;
464}
465
466int
468{
469 if (!initialized_ && frame) {
470 // Initialize on first video frame, or first audio frame if no video stream
471 bool isVideo = (frame->width > 0 && frame->height > 0);
472 if (isVideo and videoOpts_.isValid()) {
473 // Has video stream, so init with video frame
474 streamIdx = initStream(videoCodec_, frame->hw_frames_ctx);
475 startIO();
476 } else if (!isVideo and !videoOpts_.isValid()) {
477 // Only audio, for MediaRecorder, which doesn't use encodeAudio
478 startIO();
479 } else {
480 return 0;
481 }
482 }
483 int ret = 0;
484 if (static_cast<size_t>(streamIdx) >= encoders_.size())
485 return -1;
486 AVCodecContext* encoderCtx = encoders_[streamIdx];
489 pkt.data = nullptr; // packet data will be allocated by the encoder
490 pkt.size = 0;
491
492 if (!encoderCtx)
493 return -1;
494
496 if (ret < 0)
497 return -1;
498
499 while (ret >= 0) {
501 if (ret == AVERROR(EAGAIN))
502 break;
503 if (ret < 0 && ret != AVERROR_EOF) { // we still want to write our frame on EOF
504 JAMI_ERR() << "Failed to encode frame: " << libav_utils::getError(ret);
505 return ret;
506 }
507
508 if (pkt.size) {
509 if (send(pkt, streamIdx))
510 break;
511 }
512 }
513
515 return 0;
516}
517
518bool
520{
521 if (!initialized_) {
522 streamIdx = initStream(videoCodec_);
523 startIO();
524 }
525 if (streamIdx < 0)
526 streamIdx = currentStreamIdx_;
527 if (streamIdx >= 0 and static_cast<size_t>(streamIdx) < encoders_.size()
528 and static_cast<unsigned int>(streamIdx) < outputCtx_->nb_streams) {
529 auto* encoderCtx = encoders_[streamIdx];
530 pkt.stream_index = streamIdx;
531 if (pkt.pts != AV_NOPTS_VALUE)
532 pkt.pts = av_rescale_q(pkt.pts, encoderCtx->time_base, outputCtx_->streams[streamIdx]->time_base);
533 if (pkt.dts != AV_NOPTS_VALUE)
534 pkt.dts = av_rescale_q(pkt.dts, encoderCtx->time_base, outputCtx_->streams[streamIdx]->time_base);
535 }
536 // write the compressed frame
537 auto ret = av_write_frame(outputCtx_, &pkt);
538 if (ret < 0) {
539 JAMI_ERR() << "av_write_frame failed: " << libav_utils::getError(ret);
540 }
541 return ret >= 0;
542}
543
544int
546{
547 int ret = 0;
548 for (size_t i = 0; i < outputCtx_->nb_streams; ++i) {
549 if (encode(nullptr, static_cast<int>(i)) < 0) {
550 JAMI_ERR() << "Unable to flush stream #" << i;
551 ret |= 1 << i; // provide a way for caller to know which streams failed
552 }
553 }
554 return -ret;
555}
556
557std::string
559{
560 /* theora sdp can be huge */
561 const auto sdp_size = outputCtx_->streams[currentStreamIdx_]->codecpar->extradata_size + 2048;
562 std::string sdp(sdp_size, '\0');
563 av_sdp_create(&outputCtx_, 1, &(*sdp.begin()), sdp_size);
564
565 std::string result;
566 result.reserve(sdp_size);
567
568 std::string_view steam(sdp), line;
569 while (jami::getline(steam, line)) {
570 /* strip windows line ending */
571 result += line.substr(0, line.length() - 1);
572 result += "\n"sv;
573 }
574#ifdef DEBUG_SDP
575 JAMI_DBG("Sending SDP:\n%s", result.c_str());
576#endif
577 return result;
578}
579
581MediaEncoder::prepareEncoderContext(const AVCodec* outputCodec, bool is_video)
582{
584
585 const auto* encoderName = outputCodec->name; // guaranteed to be non null if AVCodec is not null
586
587 encoderCtx->thread_count = std::min(static_cast<int>(std::thread::hardware_concurrency()), is_video ? 16 : 4);
588 JAMI_DBG("[%s] Using %d threads", encoderName, encoderCtx->thread_count);
589
590 if (is_video) {
591 // resolution must be a multiple of two
592 encoderCtx->width = videoOpts_.width;
593 encoderCtx->height = videoOpts_.height;
594
595 // satisfy ffmpeg: denominator must be 16bit or less value
596 // time base = 1/FPS
597 av_reduce(&encoderCtx->framerate.num,
598 &encoderCtx->framerate.den,
601 (1U << 16) - 1);
602 encoderCtx->time_base = av_inv_q(encoderCtx->framerate);
603
604 // emit one intra frame every gop_size frames
605 encoderCtx->max_b_frames = 0;
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 if (accel_)
614 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 JAMI_WARNING("Codec format: {} {} {} {}",
630 encoderCtx->sample_rate = std::max(8000, audioOpts_.sampleRate);
631 encoderCtx->time_base = AVRational {1, encoderCtx->sample_rate};
633 audioOpts_.nbChannels = std::clamp(audioOpts_.nbChannels, 1, 2);
634 JAMI_ERR() << "[" << encoderName << "] Clamping invalid channel count: " << audioOpts_.nbChannels;
635 }
637 if (audioOpts_.frameSize) {
638 encoderCtx->frame_size = audioOpts_.frameSize;
639 JAMI_DBG() << "[" << encoderName << "] Frame size " << encoderCtx->frame_size;
640 } else {
641 JAMI_WARN() << "[" << encoderName << "] Frame size not set";
642 }
643 }
644
645 return encoderCtx;
646}
647
648void
649MediaEncoder::forcePresetX2645(AVCodecContext* encoderCtx)
650{
651#ifdef ENABLE_HWACCEL
652 if (accel_ && accel_->getName() == "nvenc") {
653 if (av_opt_set(encoderCtx, "preset", "fast", AV_OPT_SEARCH_CHILDREN))
654 JAMI_WARN("Failed to set preset to 'fast'");
655 if (av_opt_set(encoderCtx, "level", "auto", AV_OPT_SEARCH_CHILDREN))
656 JAMI_WARN("Failed to set level to 'auto'");
657 if (av_opt_set_int(encoderCtx, "zerolatency", 1, AV_OPT_SEARCH_CHILDREN))
658 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 const char* speedPreset = "veryfast";
666#endif
668 JAMI_WARN("Failed to set preset '%s'", speedPreset);
669 const char* tune = "zerolatency";
671 JAMI_WARN("Failed to set tune '%s'", tune);
672 }
673}
674
675void
676MediaEncoder::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.
682 ctx->level = 0x0d;
683 // ctx->level = 0x0d; // => 13 aka 1.3
684 if (parameters.empty())
685 return;
686
687 const std::string target("profile-level-id=");
688 size_t needle = parameters.find(target);
689 if (needle == std::string::npos)
690 return;
691
692 needle += target.length();
693 const size_t id_length = 6; /* digits */
694 const std::string profileLevelID(parameters.substr(needle, id_length));
695 if (profileLevelID.length() != id_length)
696 return;
697
698 int result;
699 std::stringstream ss;
701 ss >> std::hex >> result;
702 // profile-level id consists of three bytes
703 const unsigned char profile_idc = result >> 16; // 42xxxx -> 42
704 const unsigned char profile_iop = ((result >> 8) & 0xff); // xx80xx -> 80
705 ctx->level = result & 0xff; // xxxx0d -> 0d
706 switch (profile_idc) {
708 // check constraint_set_1_flag
709 if ((profile_iop & 0x40) >> 6)
710 ctx->profile |= FF_PROFILE_H264_CONSTRAINED;
711 break;
715 // check constraint_set_3_flag
716 if ((profile_iop & 0x10) >> 4)
717 ctx->profile |= FF_PROFILE_H264_INTRA;
718 break;
719 default:
720 JAMI_DBG("Unrecognized H264 profile byte");
721 }
722 JAMI_DBG("Using profile %s (%x) and level %d",
724 ctx->profile,
725 ctx->level);
726}
727
728#ifdef ENABLE_HWACCEL
729void
730MediaEncoder::enableAccel(bool enableAccel)
731{
734 if (!enableAccel_) {
735 accel_.reset();
736 for (auto* enc : encoders_)
737 enc->opaque = nullptr;
738 }
739}
740#endif
741
742unsigned
747
749MediaEncoder::getStream(const std::string& name, int streamIdx) const
750{
751 // if streamIdx is negative, use currentStreamIdx_
752 if (streamIdx < 0)
753 streamIdx = currentStreamIdx_;
754 // make sure streamIdx is valid
755 if (getStreamCount() <= 0 || streamIdx < 0 || encoders_.size() < (unsigned) (streamIdx + 1))
756 return {};
757 auto* enc = encoders_[streamIdx];
758 // TODO set firstTimestamp
759 auto ms = MediaStream(name, enc);
760#ifdef ENABLE_HWACCEL
761 if (accel_)
762 ms.format = accel_->getSoftwareFormat();
763#endif
764 return ms;
765}
766
767int
769{
770 if (auto* ctx = getCurrentAudioAVCtx()) {
771 return ctx->frame_size;
772 }
773 return 0;
774}
775
777MediaEncoder::initCodec(AVMediaType mediaType, AVCodecID avcodecId, uint64_t br)
778{
779 outputCodec_ = nullptr;
780#ifdef ENABLE_HWACCEL
781 if (mediaType == AVMEDIA_TYPE_VIDEO) {
782 if (enableAccel_) {
783 if (accel_) {
784 outputCodec_ = avcodec_find_encoder_by_name(accel_->getCodecName().c_str());
785 }
786 } else {
787 JAMI_WARN() << "Hardware encoding disabled";
788 }
789 }
790#endif
791
792 if (!outputCodec_) {
793 /* find the video encoder */
794 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
799 else
800 outputCodec_ = avcodec_find_encoder(static_cast<AVCodecID>(avcodecId));
801 if (!outputCodec_) {
802 throw MediaEncoderException("No output encoder");
803 }
804 }
805
806 AVCodecContext* encoderCtx = prepareEncoderContext(outputCodec_, mediaType == AVMEDIA_TYPE_VIDEO);
807
808 // Only clamp video bitrate
809 if (mediaType == AVMEDIA_TYPE_VIDEO && br > 0) {
811 JAMI_WARNING("Requested bitrate {:d} too low, setting to {:d}", br, SystemCodecInfo::DEFAULT_MIN_BITRATE);
814 JAMI_WARNING("Requested bitrate {:d} too high, setting to {:d}", br, SystemCodecInfo::DEFAULT_MAX_BITRATE);
816 }
817 }
818
819 /* Enable libopus FEC encoding support */
820 if (mediaType == AVMEDIA_TYPE_AUDIO) {
821 if (avcodecId == AV_CODEC_ID_OPUS) {
822 initOpus(encoderCtx);
823 }
824 }
825
826 /* let x264 preset override our encoder settings */
827 if (avcodecId == AV_CODEC_ID_H264) {
828 const auto* profileLevelId = libav_utils::getDictValue(options_, "parameters");
829 extractProfileLevelID(profileLevelId, encoderCtx);
830 forcePresetX2645(encoderCtx);
832 initH264(encoderCtx, br);
833 } else if (avcodecId == AV_CODEC_ID_HEVC) {
835 forcePresetX2645(encoderCtx);
836 initH265(encoderCtx, br);
838 } else if (avcodecId == AV_CODEC_ID_VP8) {
839 initVP8(encoderCtx, br);
840 } else if (avcodecId == AV_CODEC_ID_MPEG4) {
841 initMPEG4(encoderCtx, br);
842 } else if (avcodecId == AV_CODEC_ID_H263) {
843 initH263(encoderCtx, br);
844 }
845 initAccel(encoderCtx, br);
846 return encoderCtx;
847}
848
849int
851{
852 std::lock_guard lk(encMutex_);
853 AVCodecContext* encoderCtx = getCurrentVideoAVCtx();
854 if (not encoderCtx)
855 return -1; // NOK
856
857 AVCodecID codecId = encoderCtx->codec_id;
858
859 if (not isDynBitrateSupported(codecId))
860 return 0; // Restart needed
861
862 // No need to restart encoder for h264, h263 and MPEG4
863 // Change parameters on the fly
865 initH264(encoderCtx, br);
867 initH265(encoderCtx, br);
868 else if (codecId == AV_CODEC_ID_H263P)
869 initH263(encoderCtx, br);
870 else if (codecId == AV_CODEC_ID_MPEG4)
871 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 initAccel(encoderCtx, br);
880 return 1; // OK
881}
882
883int
885{
886 std::lock_guard lk(encMutex_);
887 AVCodecContext* encoderCtx = getCurrentAudioAVCtx();
888 if (not encoderCtx)
889 return -1; // NOK
890
891 AVCodecID codecId = encoderCtx->codec_id;
892
893 if (not isDynPacketLossSupported(codecId))
894 return 0; // Restart needed
895
896 // Cap between 0 and 100
897 pl = std::clamp((int) pl, 0, 100);
898
899 // Change parameters on the fly
902 return 1; // OK
903}
904
905void
906MediaEncoder::initH264(AVCodecContext* encoderCtx, uint64_t br)
907{
908 int64_t maxBitrate = static_cast<int64_t>(1000 * br);
909 // 200 Kbit/s -> CRF40
910 // 6 Mbit/s -> CRF23
911 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 int64_t bufSize = maxBitrate / 2;
916
917 // If auto quality disabled use CRF mode
918 if (mode_ == RateMode::CRF_CONSTRAINED) {
920 av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
922 JAMI_DEBUG("H264 encoder setup: crf={:d}, maxrate={:d} kbit/s, bufsize={:d} kbit",
923 crf,
924 maxBitrate / 1000,
925 bufSize / 1000);
926 } else if (mode_ == RateMode::CBR) {
928 av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
929 av_opt_set_int(encoderCtx, "minrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
932
933 JAMI_DEBUG("H264 encoder setup cbr: bitrate={:d} kbit/s", br);
934 }
935}
936
937void
938MediaEncoder::initH265(AVCodecContext* encoderCtx, uint64_t br)
939{
940 // If auto quality disabled use CRF mode
941 if (mode_ == RateMode::CRF_CONSTRAINED) {
942 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 uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A_HEVC + LOGREG_PARAM_B_HEVC * std::log(maxBitrate));
948 int64_t bufSize = maxBitrate / 2;
950 av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
952 JAMI_DEBUG("H265 encoder setup: crf={:d}, maxrate={:d} kbit/s, bufsize={:d} kbit",
953 crf,
954 maxBitrate / 1000,
955 bufSize / 1000);
956 } else if (mode_ == RateMode::CBR) {
957 int64_t signedBr = static_cast<int64_t>(br);
963 JAMI_DEBUG("H265 encoder setup cbr: bitrate={:d} kbit/s", br);
964 }
965}
966
967void
968MediaEncoder::initVP8(AVCodecContext* encoderCtx, uint64_t br)
969{
970 if (mode_ == RateMode::CQ) {
973 av_opt_set(encoderCtx, "deadline", "good", AV_OPT_SEARCH_CHILDREN);
980 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 int64_t maxBitrate = static_cast<int64_t>(1000 * br);
988 // 200 Kbit/s -> CRF40
989 // 6 Mbit/s -> CRF23
990 uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + LOGREG_PARAM_B * std::log(maxBitrate));
991 int64_t bufSize = maxBitrate / 2;
992
993 av_opt_set(encoderCtx, "quality", "realtime", AV_OPT_SEARCH_CHILDREN);
994 av_opt_set_int(encoderCtx, "error-resilient", 1, AV_OPT_SEARCH_CHILDREN);
995 av_opt_set_int(encoderCtx, "cpu-used", 7,
996 AV_OPT_SEARCH_CHILDREN); // value obtained from testing
998 // allow encoder to drop frames if buffers are full and
999 // to undershoot target bitrate to lessen strain on resources
1001 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 av_opt_set_int(encoderCtx, "slices", 2, AV_OPT_SEARCH_CHILDREN); // VP8E_SET_TOKEN_PARTITIONS
1006 crf = std::clamp((int) crf, 4, 56);
1009 av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
1011 JAMI_DEBUG("VP8 encoder setup: crf={:d}, maxrate={:d}, bufsize={:d}", crf, maxBitrate / 1000, bufSize / 1000);
1012 }
1013}
1014
1015void
1016MediaEncoder::initMPEG4(AVCodecContext* encoderCtx, uint64_t br)
1017{
1018 int64_t maxBitrate = static_cast<int64_t>(1000 * br);
1019 int bufSize = static_cast<int>(maxBitrate / 2);
1020
1021 // Use CBR (set bitrate)
1022 encoderCtx->rc_buffer_size = bufSize;
1023 encoderCtx->bit_rate = encoderCtx->rc_min_rate = encoderCtx->rc_max_rate = maxBitrate;
1024 JAMI_DEBUG("MPEG4 encoder setup: maxrate={:d}, bufsize={:d}", maxBitrate, bufSize);
1025}
1026
1027void
1028MediaEncoder::initH263(AVCodecContext* encoderCtx, uint64_t br)
1029{
1030 int64_t maxBitrate = static_cast<int64_t>(1000 * br);
1031 int bufSize = static_cast<int>(maxBitrate) / 2;
1032
1033 // Use CBR (set bitrate)
1034 encoderCtx->rc_buffer_size = bufSize;
1035 encoderCtx->bit_rate = encoderCtx->rc_min_rate = encoderCtx->rc_max_rate = maxBitrate;
1036 JAMI_DEBUG("H263 encoder setup: maxrate={:d}, bufsize={:d}", maxBitrate, bufSize);
1037}
1038
1039void
1040MediaEncoder::initOpus(AVCodecContext* encoderCtx)
1041{
1042 // Enable FEC support by default with 10% packet loss
1043 av_opt_set_int(encoderCtx, "fec", fecEnabled_ ? 1 : 0, AV_OPT_SEARCH_CHILDREN);
1045}
1046
1047void
1048MediaEncoder::initAccel(AVCodecContext* encoderCtx, uint64_t br)
1049{
1050#ifdef ENABLE_HWACCEL
1051 float val = static_cast<float>(br) * 1000.0f * 0.8f;
1052 if (not accel_)
1053 return;
1054 if (accel_->getName() == "nvenc"sv) {
1055 // Use same parameters as software
1056 } else if (accel_->getName() == "vaapi"sv) {
1057 // Use VBR encoding with bitrate target set to 80% of the maxrate
1060 } else if (accel_->getName() == "videotoolbox"sv) {
1062 } else if (accel_->getName() == "qsv"sv) {
1063 // Use Video Conferencing Mode
1066 }
1067#endif
1068}
1069
1071MediaEncoder::getCurrentVideoAVCtx()
1072{
1073 for (auto* it : encoders_) {
1074 if (it->codec_type == AVMEDIA_TYPE_VIDEO)
1075 return it;
1076 }
1077 return nullptr;
1078}
1079
1081MediaEncoder::getCurrentAudioAVCtx()
1082{
1083 for (auto* it : encoders_) {
1084 if (it->codec_type == AVMEDIA_TYPE_AUDIO)
1085 return it;
1086 }
1087 return nullptr;
1088}
1089
1090void
1091MediaEncoder::stopEncoder()
1092{
1093 flush();
1094 for (auto it = encoders_.begin(); it != encoders_.end(); it++) {
1095 if ((*it)->codec_type == AVMEDIA_TYPE_VIDEO) {
1096 encoders_.erase(it);
1097 break;
1098 }
1099 }
1100 AVCodecContext* encoderCtx = getCurrentVideoAVCtx();
1104}
1105
1106bool
1107MediaEncoder::isDynBitrateSupported(AVCodecID codecid)
1108{
1109#ifdef ENABLE_HWACCEL
1110 if (accel_) {
1111 return accel_->dynBitrate();
1112 }
1113#endif
1114 if (codecid != AV_CODEC_ID_VP8)
1115 return true;
1116
1117 return false;
1118}
1119
1120bool
1121MediaEncoder::isDynPacketLossSupported(AVCodecID codecid)
1122{
1124 return true;
1125
1126 return false;
1127}
1128
1129void
1131{
1132 auto path = fileutils::get_config_dir() / "encoder.json";
1133 std::string name = encoderCtx->codec->name;
1134 std::error_code ec;
1135 if (std::filesystem::is_regular_file(path, ec)) {
1136 JAMI_WARN("encoder.json file found, default settings will be erased");
1137 try {
1138 Json::Value root;
1139 std::ifstream file(path);
1140 file >> root;
1141 if (!root.isObject()) {
1142 JAMI_ERR() << "Invalid encoder configuration: root is not an object";
1143 return;
1144 }
1145 const auto& config = root[name];
1146 if (config.isNull()) {
1147 JAMI_WARN() << "Encoder '" << name << "' not found in configuration file";
1148 return;
1149 }
1150 if (!config.isObject()) {
1151 JAMI_ERR() << "Invalid encoder configuration: '" << name << "' is not an object";
1152 return;
1153 }
1154 for (Json::Value::const_iterator it = config.begin(); it != config.end(); ++it) {
1155 const Json::Value& v = *it;
1156 if (!it.key().isConvertibleTo(Json::ValueType::stringValue)
1157 || !v.isConvertibleTo(Json::ValueType::stringValue)) {
1158 JAMI_ERR() << "Invalid configuration for '" << name << "'";
1159 return;
1160 }
1161 const auto& key = it.key().asString();
1162 const auto& value = v.asString();
1163 int ret = av_opt_set(reinterpret_cast<void*>(encoderCtx),
1164 key.c_str(),
1165 value.c_str(),
1167 if (ret < 0) {
1168 JAMI_ERR() << "Failed to set option " << key << " in " << name
1169 << " context: " << libav_utils::getError(ret) << "\n";
1170 }
1171 }
1172 } catch (const Json::Exception& e) {
1173 JAMI_ERR() << "Failed to load encoder configuration file: " << e.what();
1174 }
1175 }
1176}
1177
1178std::string
1180{
1181#ifdef ENABLE_HWACCEL
1182 if (jami::Manager::instance().videoPreferences.getEncodingAccelerated()) {
1183 // Get compatible list of Hardware API
1185
1186 std::unique_ptr<video::HardwareAccel> accel;
1187
1188 for (const auto& it : APIs) {
1189 accel = std::make_unique<video::HardwareAccel>(it); // save accel
1190 // Init codec need accel to init encoderCtx accelerated
1191 const auto* outputCodec = avcodec_find_encoder_by_name(accel->getCodecName().c_str());
1192
1194 encoderCtx->thread_count = static_cast<int>(std::min(std::thread::hardware_concurrency(), 16u));
1195 encoderCtx->width = 1280;
1196 encoderCtx->height = 720;
1197 AVRational framerate;
1198 framerate.num = 30;
1199 framerate.den = 1;
1200 encoderCtx->time_base = av_inv_q(framerate);
1201 encoderCtx->pix_fmt = accel->getFormat();
1203 encoderCtx->opaque = accel.get();
1204
1211
1212 auto ret = accel->initAPI(false, nullptr);
1213 if (ret < 0) {
1214 accel.reset();
1215 ;
1216 encoderCtx = nullptr;
1217 continue;
1218 }
1219 accel->setDetails(encoderCtx);
1220 if (avcodec_open2(encoderCtx, outputCodec, nullptr) < 0) {
1221 // Failed to open codec
1222 JAMI_WARN("Fail to open hardware encoder H265 with %s ", it.getName().c_str());
1224 encoderCtx = nullptr;
1225 accel = nullptr;
1226 continue;
1227 } else {
1228 // Succeed to open codec
1230 encoderCtx = nullptr;
1231 accel = nullptr;
1232 return it.getName();
1233 }
1234 }
1235 }
1236#endif
1237 return "";
1238}
1239
1240#ifdef ENABLE_VIDEO
1241int
1242MediaEncoder::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 const auto* desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format()));
1261 bool isHardware = desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL);
1262 if (accel_ && accel_->isLinked() && isHardware) {
1263 // Fully accelerated pipeline, skip main memory
1264 output = input;
1265 } 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 output = getUnlinkedHWFrame(*input.get());
1270 } else if (accel_) {
1271 // Software decoded frame with a hardware encoder, convert to accepted format first
1272 output = getHWFrameFromSWFrame(*input.get());
1273 } else {
1274 output = getScaledSWFrame(*input.get());
1275 }
1276#else
1277 // macOS
1278 output = getScaledSWFrame(*input.get());
1279#endif
1280 } catch (const std::runtime_error& e) {
1281 JAMI_ERR("Accel failure: %s", e.what());
1282 return -1;
1283 }
1284
1285 return 0;
1286}
1287
1288#ifdef ENABLE_HWACCEL
1289std::shared_ptr<VideoFrame>
1290MediaEncoder::getUnlinkedHWFrame(const VideoFrame& input)
1291{
1292 AVPixelFormat pix = (accel_ ? accel_->getSoftwareFormat() : AV_PIX_FMT_NV12);
1293 std::shared_ptr<VideoFrame> framePtr = video::HardwareAccel::transferToMainMemory(input, pix);
1294 if (!accel_) {
1295 framePtr = scaler_.convertFormat(*framePtr, AV_PIX_FMT_YUV420P);
1296 } else {
1297 framePtr = accel_->transfer(*framePtr);
1298 }
1299 return framePtr;
1300}
1301
1302std::shared_ptr<VideoFrame>
1303MediaEncoder::getHWFrameFromSWFrame(const VideoFrame& input)
1304{
1305 std::shared_ptr<VideoFrame> framePtr;
1306 auto pix = accel_->getSoftwareFormat();
1307 if (input.format() != pix) {
1308 framePtr = scaler_.convertFormat(input, pix);
1309 framePtr = accel_->transfer(*framePtr);
1310 } else {
1311 framePtr = accel_->transfer(input);
1312 }
1313 return framePtr;
1314}
1315#endif
1316
1317std::shared_ptr<VideoFrame>
1318MediaEncoder::getScaledSWFrame(const VideoFrame& input)
1319{
1321 scaler_.scale_with_aspect(input, *scaledFrame_);
1322 return scaledFrame_;
1323}
1324#endif
1325
1326void
1327MediaEncoder::resetStreams(int width, int height)
1328{
1329 videoOpts_.width = width;
1330 videoOpts_.height = height;
1331
1332 try {
1333 flush();
1334 initialized_ = false;
1335 if (outputCtx_) {
1336 for (auto* encoderCtx : encoders_) {
1337 if (encoderCtx) {
1338#ifndef _MSC_VER
1340#else
1342#endif
1343 }
1344 }
1345 encoders_.clear();
1346 }
1347 } catch (const std::exception& e) {
1348 JAMI_WARNING("Couldn't reset streams: {}", e.what());
1349 }
1350}
1351
1352} // namespace jami
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:694
std::string videoCodec_
unsigned getStreamCount() const
MediaStream audioOpts_
void setInitSeqVal(uint16_t seqVal)
int getWidth() const
int encodeAudio(AudioFrame &frame)
void readConfig(AVCodecContext *encoderCtx)
void resetStreams(int width, int height)
int addStream(const SystemCodecInfo &codec)
static std::string testH265Accel()
MediaStream getStream(const std::string &name, int streamIdx=-1) const
int encode(AVFrame *frame, int streamIdx)
AVDictionary * options_
int setPacketLoss(uint64_t pl)
void setMetadata(const std::string &title, const std::string &description)
int getHeight() const
MediaStream videoOpts_
std::string audioCodec_
void openOutput(const std::string &filename, const std::string &format="")
std::string print_sdp()
int setBitrate(uint64_t br)
void setOptions(const MediaStream &opts)
bool send(AVPacket &packet, int streamIdx=-1)
constexpr I denominator() const
Definition rational.h:86
constexpr I numerator() const
Definition rational.h:85
static std::list< HardwareAccel > getCompatibleAccel(AVCodecID id, int width, int height, CodecType type)
Definition accel.cpp:417
static std::unique_ptr< VideoFrame > transferToMainMemory(const VideoFrame &frame, AVPixelFormat desiredFormat)
Transfers hardware frame to main memory.
Definition accel.cpp:367
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_DEBUG(formatstr,...)
Definition logger.h:238
#define JAMI_WARN(...)
Definition logger.h:229
#define JAMI_WARNING(formatstr,...)
Definition logger.h:242
const std::filesystem::path & get_config_dir()
void fillWithBlack(AVFrame *frame)
const char * getDictValue(const AVDictionary *d, const std::string &key, int flags)
void setDictValue(AVDictionary **d, const std::string &key, const std::string &value, int flags)
std::string getError(int err)
constexpr double LOGREG_PARAM_A
decltype(getGlobalInstance< SystemCodecContainer >) & getSystemCodecContainer
constexpr double LOGREG_PARAM_B
void emitSignal(Args... args)
Definition jami_signal.h:64
libjami::VideoFrame VideoFrame
Definition video_base.h:49
bool getline(std::string_view &str, std::string_view &line, char delim='\n')
Similar to @getline_full but skips empty results.
@ CODEC_ENCODER
Definition media_codec.h:39
constexpr double LOGREG_PARAM_A_HEVC
constexpr double LOGREG_PARAM_B_HEVC
@ MEDIA_AUDIO
Definition media_codec.h:46
@ MEDIA_ALL
Definition media_codec.h:48
@ MEDIA_VIDEO
Definition media_codec.h:47
MediaDescription Negotiated RTP media slot.
bool isValid() const
rational< int > frameRate
static constexpr unsigned DEFAULT_MAX_BITRATE
Definition media_codec.h:72
static constexpr unsigned DEFAULT_MIN_BITRATE
Definition media_codec.h:71
static constexpr unsigned DEFAULT_VIDEO_BITRATE
Definition media_codec.h:73