1 // FFMPEG Video Encoder Integration for OBS Studio
2 // Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com>
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in all
12 // copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 // SOFTWARE.
21
22 #include "encoder-ffmpeg.hpp"
23 #include "strings.hpp"
24 #include <sstream>
25 #include "codecs/hevc.hpp"
26 #include "ffmpeg/tools.hpp"
27 #include "handlers/debug_handler.hpp"
28 #include "handlers/nvenc_h264_handler.hpp"
29 #include "handlers/nvenc_hevc_handler.hpp"
30 #include "handlers/prores_aw_handler.hpp"
31 #include "obs/gs/gs-helper.hpp"
32 #include "plugin.hpp"
33
34 extern "C" {
35 #pragma warning(push)
36 #pragma warning(disable : 4244)
37 #include <obs-avc.h>
38 #include <libavcodec/avcodec.h>
39 #include <libavutil/dict.h>
40 #include <libavutil/frame.h>
41 #include <libavutil/opt.h>
42 #include <libavutil/pixdesc.h>
43 #pragma warning(pop)
44 }
45
46 #ifdef WIN32
47 #include "ffmpeg/hwapi/d3d11.hpp"
48 #endif
49
50 // FFmpeg
51 #define ST_FFMPEG "FFmpegEncoder"
52 #define ST_FFMPEG_SUFFIX ST_FFMPEG ".Suffix"
53 #define ST_FFMPEG_CUSTOMSETTINGS "FFmpegEncoder.CustomSettings"
54 #define KEY_FFMPEG_CUSTOMSETTINGS "FFmpeg.CustomSettings"
55 #define ST_FFMPEG_THREADS "FFmpegEncoder.Threads"
56 #define KEY_FFMPEG_THREADS "FFmpeg.Threads"
57 #define ST_FFMPEG_COLORFORMAT "FFmpegEncoder.ColorFormat"
58 #define KEY_FFMPEG_COLORFORMAT "FFmpeg.ColorFormat"
59 #define ST_FFMPEG_STANDARDCOMPLIANCE "FFmpegEncoder.StandardCompliance"
60 #define KEY_FFMPEG_STANDARDCOMPLIANCE "FFmpeg.StandardCompliance"
61 #define ST_FFMPEG_GPU "FFmpegEncoder.GPU"
62 #define KEY_FFMPEG_GPU "FFmpeg.GPU"
63
64 #define ST_KEYFRAMES "FFmpegEncoder.KeyFrames"
65 #define ST_KEYFRAMES_INTERVALTYPE "FFmpegEncoder.KeyFrames.IntervalType"
66 #define ST_KEYFRAMES_INTERVALTYPE_(x) "FFmpegEncoder.KeyFrames.IntervalType." D_VSTR(x)
67 #define KEY_KEYFRAMES_INTERVALTYPE "KeyFrames.IntervalType"
68 #define ST_KEYFRAMES_INTERVAL "FFmpegEncoder.KeyFrames.Interval"
69 #define ST_KEYFRAMES_INTERVAL_SECONDS "FFmpegEncoder.KeyFrames.Interval.Seconds"
70 #define KEY_KEYFRAMES_INTERVAL_SECONDS "KeyFrames.Interval.Seconds"
71 #define ST_KEYFRAMES_INTERVAL_FRAMES "FFmpegEncoder.KeyFrames.Interval.Frames"
72 #define KEY_KEYFRAMES_INTERVAL_FRAMES "KeyFrames.Interval.Frames"
73
74 using namespace streamfx::encoder::ffmpeg;
75 using namespace streamfx::encoder::codec;
76
77 enum class keyframe_type { SECONDS, FRAMES };
78
ffmpeg_instance(obs_data_t * settings,obs_encoder_t * self,bool is_hw)79 ffmpeg_instance::ffmpeg_instance(obs_data_t* settings, obs_encoder_t* self, bool is_hw)
80 : encoder_instance(settings, self, is_hw),
81
82 _factory(reinterpret_cast<ffmpeg_factory*>(obs_encoder_get_type_data(self))),
83
84 _codec(_factory->get_avcodec()), _context(nullptr), _handler(ffmpeg_manager::get()->get_handler(_codec->name)),
85
86 _scaler(), _packet(),
87
88 _hwapi(), _hwinst(),
89
90 _lag_in_frames(0), _sent_frames(0), _have_first_frame(false), _extra_data(), _sei_data(),
91
92 _free_frames(), _used_frames(), _free_frames_last_used()
93 {
94 // Initialize GPU Stuff
95 if (is_hw) {
96 // Abort if user specified manual override.
97 if ((static_cast<AVPixelFormat>(obs_data_get_int(settings, KEY_FFMPEG_COLORFORMAT)) != AV_PIX_FMT_NONE)
98 || (obs_data_get_int(settings, KEY_FFMPEG_GPU) != -1) || (obs_encoder_scaling_enabled(_self))
99 || (video_output_get_info(obs_encoder_video(_self))->format != VIDEO_FORMAT_NV12)) {
100 throw std::runtime_error(
101 "Selected settings prevent the use of hardware encoding, falling back to software.");
102 }
103
104 #ifdef WIN32
105 auto gctx = gs::context();
106 if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) {
107 _hwapi = std::make_shared<::ffmpeg::hwapi::d3d11>();
108 }
109 #endif
110 if (!_hwapi) {
111 throw std::runtime_error("Failed to create acceleration context.");
112 }
113
114 _hwinst = _hwapi->create_from_obs();
115 }
116
117 // Initialize context.
118 _context = avcodec_alloc_context3(_codec);
119 if (!_context) {
120 DLOG_ERROR("Failed to create context for encoder '%s'.", _codec->name);
121 throw std::runtime_error("Failed to create encoder context.");
122 }
123
124 // Create 8MB of precached Packet data for use later on.
125 av_init_packet(&_packet);
126 av_new_packet(&_packet, 8 * 1024 * 1024); // 8 MB precached Packet size.
127
128 // Initialize
129 if (is_hw) {
130 initialize_hw(settings);
131 } else {
132 initialize_sw(settings);
133 }
134
135 // Update settings
136 update(settings);
137
138 // Initialize Encoder
139 auto gctx = gs::context();
140 int res = avcodec_open2(_context, _codec, NULL);
141 if (res < 0) {
142 throw std::runtime_error(::ffmpeg::tools::get_error_description(res));
143 }
144 }
145
~ffmpeg_instance()146 ffmpeg_instance::~ffmpeg_instance()
147 {
148 auto gctx = gs::context();
149 if (_context) {
150 // Flush encoders that require it.
151 if ((_codec->capabilities & AV_CODEC_CAP_DELAY) != 0) {
152 avcodec_send_frame(_context, nullptr);
153 while (avcodec_receive_packet(_context, &_packet) >= 0) {
154 avcodec_send_frame(_context, nullptr);
155 std::this_thread::sleep_for(std::chrono::milliseconds(1));
156 }
157 }
158
159 // Close and free context.
160 avcodec_close(_context);
161 avcodec_free_context(&_context);
162 }
163
164 av_packet_unref(&_packet);
165
166 _scaler.finalize();
167 }
168
get_properties(obs_properties_t * props)169 void ffmpeg_instance::get_properties(obs_properties_t* props)
170 {
171 if (_handler)
172 _handler->get_properties(props, _codec, _context, _handler->is_hardware_encoder(_factory));
173
174 obs_property_set_enabled(obs_properties_get(props, KEY_KEYFRAMES_INTERVALTYPE), false);
175 obs_property_set_enabled(obs_properties_get(props, KEY_KEYFRAMES_INTERVAL_SECONDS), false);
176 obs_property_set_enabled(obs_properties_get(props, KEY_KEYFRAMES_INTERVAL_FRAMES), false);
177
178 obs_property_set_enabled(obs_properties_get(props, KEY_FFMPEG_COLORFORMAT), false);
179 obs_property_set_enabled(obs_properties_get(props, KEY_FFMPEG_THREADS), false);
180 obs_property_set_enabled(obs_properties_get(props, KEY_FFMPEG_STANDARDCOMPLIANCE), false);
181 obs_property_set_enabled(obs_properties_get(props, KEY_FFMPEG_GPU), false);
182 }
183
migrate(obs_data_t * settings,uint64_t version)184 void ffmpeg_instance::migrate(obs_data_t* settings, uint64_t version)
185 {
186 if (_handler)
187 _handler->migrate(settings, version, _codec, _context);
188 }
189
update(obs_data_t * settings)190 bool ffmpeg_instance::update(obs_data_t* settings)
191 {
192 // FFmpeg Options
193 _context->debug = 0;
194 _context->strict_std_compliance = static_cast<int>(obs_data_get_int(settings, KEY_FFMPEG_STANDARDCOMPLIANCE));
195
196 /// Threading
197 if (!_hwinst) {
198 _context->thread_type = 0;
199 if (_codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
200 _context->thread_type |= FF_THREAD_FRAME;
201 }
202 if (_codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
203 _context->thread_type |= FF_THREAD_SLICE;
204 }
205 if (_context->thread_type != 0) {
206 int64_t threads = obs_data_get_int(settings, ST_FFMPEG_THREADS);
207 if (threads > 0) {
208 _context->thread_count = static_cast<int>(threads);
209 } else {
210 _context->thread_count = static_cast<int>(std::thread::hardware_concurrency());
211 }
212 } else {
213 _context->thread_count = 1;
214 }
215 // Frame Delay (Lag In Frames)
216 _context->delay = _context->thread_count;
217 } else {
218 _context->delay = 0;
219 }
220
221 // Apply GPU Selection
222 if (!_hwinst && ::ffmpeg::tools::can_hardware_encode(_codec)) {
223 av_opt_set_int(_context, "gpu", (int)obs_data_get_int(settings, KEY_FFMPEG_GPU), AV_OPT_SEARCH_CHILDREN);
224 }
225
226 // Keyframes
227 if (_handler && _handler->has_keyframe_support(_factory)) {
228 // Key-Frame Options
229 obs_video_info ovi;
230 if (!obs_get_video_info(&ovi)) {
231 throw std::runtime_error("obs_get_video_info failed, restart OBS Studio to fix it (hopefully).");
232 }
233
234 int64_t kf_type = obs_data_get_int(settings, KEY_KEYFRAMES_INTERVALTYPE);
235 bool is_seconds = (kf_type == 0);
236
237 if (is_seconds) {
238 _context->gop_size = static_cast<int>(obs_data_get_double(settings, KEY_KEYFRAMES_INTERVAL_SECONDS)
239 * (ovi.fps_num / ovi.fps_den));
240 } else {
241 _context->gop_size = static_cast<int>(obs_data_get_int(settings, KEY_KEYFRAMES_INTERVAL_FRAMES));
242 }
243 _context->keyint_min = _context->gop_size;
244 }
245
246 // Handler Options
247 if (_handler)
248 _handler->update(settings, _codec, _context);
249
250 { // FFmpeg Custom Options
251 const char* opts = obs_data_get_string(settings, KEY_FFMPEG_CUSTOMSETTINGS);
252 std::size_t opts_len = strnlen(opts, 65535);
253
254 parse_ffmpeg_commandline(std::string{opts, opts + opts_len});
255 }
256
257 // Handler Overrides
258 if (_handler)
259 _handler->override_update(this, settings);
260
261 // Handler Logging
262 if (_handler) {
263 DLOG_INFO("[%s] Initializing...", _codec->name);
264 DLOG_INFO("[%s] FFmpeg:", _codec->name);
265 DLOG_INFO("[%s] Custom Settings: %s", _codec->name,
266 obs_data_get_string(settings, KEY_FFMPEG_CUSTOMSETTINGS));
267 DLOG_INFO("[%s] Standard Compliance: %s", _codec->name,
268 ::ffmpeg::tools::get_std_compliance_name(_context->strict_std_compliance));
269 DLOG_INFO("[%s] Threading: %s (with %i threads)", _codec->name,
270 ::ffmpeg::tools::get_thread_type_name(_context->thread_type), _context->thread_count);
271
272 DLOG_INFO("[%s] Video:", _codec->name);
273 if (_hwinst) {
274 DLOG_INFO("[%s] Texture: %" PRId32 "x%" PRId32 " %s %s %s", _codec->name, _context->width,
275 _context->height, ::ffmpeg::tools::get_pixel_format_name(_context->sw_pix_fmt),
276 ::ffmpeg::tools::get_color_space_name(_context->colorspace),
277 av_color_range_name(_context->color_range));
278 } else {
279 DLOG_INFO("[%s] Input: %" PRId32 "x%" PRId32 " %s %s %s", _codec->name, _scaler.get_source_width(),
280 _scaler.get_source_height(), ::ffmpeg::tools::get_pixel_format_name(_scaler.get_source_format()),
281 ::ffmpeg::tools::get_color_space_name(_scaler.get_source_colorspace()),
282 _scaler.is_source_full_range() ? "Full" : "Partial");
283 DLOG_INFO("[%s] Output: %" PRId32 "x%" PRId32 " %s %s %s", _codec->name, _scaler.get_target_width(),
284 _scaler.get_target_height(), ::ffmpeg::tools::get_pixel_format_name(_scaler.get_target_format()),
285 ::ffmpeg::tools::get_color_space_name(_scaler.get_target_colorspace()),
286 _scaler.is_target_full_range() ? "Full" : "Partial");
287 if (!_hwinst)
288 DLOG_INFO("[%s] On GPU Index: %lli", _codec->name, obs_data_get_int(settings, KEY_FFMPEG_GPU));
289 }
290 DLOG_INFO("[%s] Framerate: %" PRId32 "/%" PRId32 " (%f FPS)", _codec->name, _context->time_base.den,
291 _context->time_base.num,
292 static_cast<double_t>(_context->time_base.den) / static_cast<double_t>(_context->time_base.num));
293
294 DLOG_INFO("[%s] Keyframes: ", _codec->name);
295 if (_context->keyint_min != _context->gop_size) {
296 DLOG_INFO("[%s] Minimum: %i frames", _codec->name, _context->keyint_min);
297 DLOG_INFO("[%s] Maximum: %i frames", _codec->name, _context->gop_size);
298 } else {
299 DLOG_INFO("[%s] Distance: %i frames", _codec->name, _context->gop_size);
300 }
301 _handler->log_options(settings, _codec, _context);
302 }
303
304 return true;
305 }
306
copy_data(encoder_frame * frame,AVFrame * vframe)307 static inline void copy_data(encoder_frame* frame, AVFrame* vframe)
308 {
309 int h_chroma_shift, v_chroma_shift;
310 av_pix_fmt_get_chroma_sub_sample(static_cast<AVPixelFormat>(vframe->format), &h_chroma_shift, &v_chroma_shift);
311
312 for (std::size_t idx = 0; idx < MAX_AV_PLANES; idx++) {
313 if (!frame->data[idx] || !vframe->data[idx])
314 continue;
315
316 std::size_t plane_height = static_cast<size_t>(vframe->height) >> (idx ? v_chroma_shift : 0);
317
318 if (static_cast<uint32_t>(vframe->linesize[idx]) == frame->linesize[idx]) {
319 std::memcpy(vframe->data[idx], frame->data[idx], frame->linesize[idx] * plane_height);
320 } else {
321 std::size_t ls_in = static_cast<size_t>(frame->linesize[idx]);
322 std::size_t ls_out = static_cast<size_t>(vframe->linesize[idx]);
323 std::size_t bytes = ls_in < ls_out ? ls_in : ls_out;
324
325 uint8_t* to = vframe->data[idx];
326 uint8_t* from = frame->data[idx];
327
328 for (std::size_t y = 0; y < plane_height; y++) {
329 std::memcpy(to, from, bytes);
330 to += ls_out;
331 from += ls_in;
332 }
333 }
334 }
335 }
336
encode_audio(struct encoder_frame * frame,struct encoder_packet * packet,bool * received_packet)337 bool ffmpeg_instance::encode_audio(struct encoder_frame* frame, struct encoder_packet* packet, bool* received_packet)
338 {
339 throw std::logic_error("The method or operation is not implemented.");
340 }
341
encode_video(struct encoder_frame * frame,struct encoder_packet * packet,bool * received_packet)342 bool ffmpeg_instance::encode_video(struct encoder_frame* frame, struct encoder_packet* packet, bool* received_packet)
343 {
344 std::shared_ptr<AVFrame> vframe = pop_free_frame(); // Retrieve an empty frame.
345
346 // Convert frame.
347 {
348 vframe->height = _context->height;
349 vframe->format = _context->pix_fmt;
350 vframe->color_range = _context->color_range;
351 vframe->colorspace = _context->colorspace;
352 vframe->color_primaries = _context->color_primaries;
353 vframe->color_trc = _context->color_trc;
354 vframe->pts = frame->pts;
355
356 if ((_scaler.is_source_full_range() == _scaler.is_target_full_range())
357 && (_scaler.get_source_colorspace() == _scaler.get_target_colorspace())
358 && (_scaler.get_source_format() == _scaler.get_target_format())) {
359 copy_data(frame, vframe.get());
360 } else {
361 int res = _scaler.convert(reinterpret_cast<uint8_t**>(frame->data), reinterpret_cast<int*>(frame->linesize),
362 0, _context->height, vframe->data, vframe->linesize);
363 if (res <= 0) {
364 DLOG_ERROR("Failed to convert frame: %s (%" PRId32 ").", ::ffmpeg::tools::get_error_description(res),
365 res);
366 return false;
367 }
368 }
369 }
370
371 if (!encode_avframe(vframe, packet, received_packet))
372 return false;
373
374 return true;
375 }
376
encode_video(uint32_t handle,int64_t pts,uint64_t lock_key,uint64_t * next_key,struct encoder_packet * packet,bool * received_packet)377 bool ffmpeg_instance::encode_video(uint32_t handle, int64_t pts, uint64_t lock_key, uint64_t* next_key,
378 struct encoder_packet* packet, bool* received_packet)
379 {
380 #ifdef D_PLATFORM_WINDOWS
381 if (handle == GS_INVALID_HANDLE) {
382 DLOG_ERROR("Received invalid handle.");
383 *next_key = lock_key;
384 return false;
385 }
386
387 std::shared_ptr<AVFrame> vframe = pop_free_frame();
388 _hwinst->copy_from_obs(_context->hw_frames_ctx, handle, lock_key, next_key, vframe);
389
390 vframe->color_range = _context->color_range;
391 vframe->colorspace = _context->colorspace;
392 vframe->color_primaries = _context->color_primaries;
393 vframe->color_trc = _context->color_trc;
394 vframe->pts = pts;
395
396 if (!encode_avframe(vframe, packet, received_packet))
397 return false;
398
399 *next_key = lock_key;
400
401 return true;
402 #else
403 return false;
404 #endif
405 }
406
initialize_sw(obs_data_t * settings)407 void ffmpeg_instance::initialize_sw(obs_data_t* settings)
408 {
409 if (_codec->type == AVMEDIA_TYPE_VIDEO) {
410 // Initialize Video Encoding
411 auto voi = video_output_get_info(obs_encoder_video(_self));
412
413 // Find a suitable Pixel Format.
414 AVPixelFormat _pixfmt_source = ::ffmpeg::tools::obs_videoformat_to_avpixelformat(voi->format);
415 AVPixelFormat _pixfmt_target = static_cast<AVPixelFormat>(obs_data_get_int(settings, KEY_FFMPEG_COLORFORMAT));
416 if (_pixfmt_target == AV_PIX_FMT_NONE) {
417 // Find the best conversion format.
418 if (_codec->pix_fmts) {
419 _pixfmt_target = ::ffmpeg::tools::get_least_lossy_format(_codec->pix_fmts, _pixfmt_source);
420 } else { // If there are no supported formats, just pass in the current one.
421 _pixfmt_target = _pixfmt_source;
422 }
423
424 if (_handler) // Allow Handler to override the automatic color format for sanity reasons.
425 _handler->override_colorformat(_pixfmt_target, settings, _codec, _context);
426 } else {
427 // Use user override, guaranteed to be supported.
428 bool is_format_supported = false;
429 for (auto ptr = _codec->pix_fmts; *ptr != AV_PIX_FMT_NONE; ptr++) {
430 if (*ptr == _pixfmt_target) {
431 is_format_supported = true;
432 }
433 }
434
435 if (!is_format_supported) {
436 std::stringstream sstr;
437 sstr << "Color Format '" << ::ffmpeg::tools::get_pixel_format_name(_pixfmt_target)
438 << "' is not supported by the encoder.";
439 throw std::runtime_error(sstr.str().c_str());
440 }
441 }
442
443 // Setup from OBS information.
444 ::ffmpeg::tools::context_setup_from_obs(voi, _context);
445
446 // Override with other information.
447 _context->width = static_cast<int>(obs_encoder_get_width(_self));
448 _context->height = static_cast<int>(obs_encoder_get_height(_self));
449 _context->pix_fmt = _pixfmt_target;
450
451 // Prevent pixelation by sampling "center" instead of corners. This creates
452 // a smoother look, which may not be H.264/AVC standard compliant, however it
453 // provides better support for scaling algorithms, such as Bicubic.
454 _context->chroma_sample_location = AVCHROMA_LOC_CENTER;
455
456 _scaler.set_source_size(static_cast<uint32_t>(_context->width), static_cast<uint32_t>(_context->height));
457 _scaler.set_source_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace);
458 _scaler.set_source_format(_pixfmt_source);
459
460 _scaler.set_target_size(static_cast<uint32_t>(_context->width), static_cast<uint32_t>(_context->height));
461 _scaler.set_target_color(_context->color_range == AVCOL_RANGE_JPEG, _context->colorspace);
462 _scaler.set_target_format(_pixfmt_target);
463
464 // Create Scaler
465 if (!_scaler.initialize(SWS_POINT)) {
466 std::stringstream sstr;
467 sstr << "Initializing scaler failed for conversion from '"
468 << ::ffmpeg::tools::get_pixel_format_name(_scaler.get_source_format()) << "' to '"
469 << ::ffmpeg::tools::get_pixel_format_name(_scaler.get_target_format()) << "' with color space '"
470 << ::ffmpeg::tools::get_color_space_name(_scaler.get_source_colorspace()) << "' and "
471 << (_scaler.is_source_full_range() ? "full" : "partial") << " range.";
472 throw std::runtime_error(sstr.str());
473 }
474 }
475 }
476
initialize_hw(obs_data_t *)477 void ffmpeg_instance::initialize_hw(obs_data_t*)
478 {
479 #ifndef D_PLATFORM_WINDOWS
480 throw std::runtime_error("OBS Studio currently does not support zero copy encoding for this platform.");
481 #else
482 // Initialize Video Encoding
483 const video_output_info* voi = video_output_get_info(obs_encoder_video(_self));
484
485 // Apply pixel format settings.
486 ::ffmpeg::tools::context_setup_from_obs(voi, _context);
487 _context->sw_pix_fmt = _context->pix_fmt;
488 _context->pix_fmt = AV_PIX_FMT_D3D11;
489
490 // Try to create a hardware context.
491 _context->hw_device_ctx = _hwinst->create_device_context();
492 _context->hw_frames_ctx = av_hwframe_ctx_alloc(_context->hw_device_ctx);
493 if (!_context->hw_frames_ctx) {
494 throw std::runtime_error("Creating hardware context failed.");
495 }
496
497 // Initialize Hardware Context
498 AVHWFramesContext* ctx = reinterpret_cast<AVHWFramesContext*>(_context->hw_frames_ctx->data);
499 ctx->width = _context->width;
500 ctx->height = _context->height;
501 ctx->format = _context->pix_fmt;
502 ctx->sw_format = _context->sw_pix_fmt;
503 if (int32_t res = av_hwframe_ctx_init(_context->hw_frames_ctx); res < 0) {
504 std::array<char, 2048> buffer;
505 size_t len = static_cast<size_t>(snprintf(buffer.data(), buffer.size(),
506 "Initializing hardware context failed with error: %s (%" PRIu32 ")",
507 ::ffmpeg::tools::get_error_description(res), res));
508 throw std::runtime_error(std::string(buffer.data(), buffer.data() + len));
509 }
510 #endif
511 }
512
push_free_frame(std::shared_ptr<AVFrame> frame)513 void ffmpeg_instance::push_free_frame(std::shared_ptr<AVFrame> frame)
514 {
515 auto now = std::chrono::high_resolution_clock::now();
516 if (_free_frames.size() > 0) {
517 if ((now - _free_frames_last_used) < std::chrono::seconds(1)) {
518 _free_frames.push(frame);
519 }
520 } else {
521 _free_frames.push(frame);
522 _free_frames_last_used = std::chrono::high_resolution_clock::now();
523 }
524 }
525
pop_free_frame()526 std::shared_ptr<AVFrame> ffmpeg_instance::pop_free_frame()
527 {
528 std::shared_ptr<AVFrame> frame;
529 if (_free_frames.size() > 0) {
530 // Re-use existing frames first.
531 frame = _free_frames.top();
532 _free_frames.pop();
533 } else {
534 if (_hwinst) {
535 frame = _hwinst->allocate_frame(_context->hw_frames_ctx);
536 } else {
537 frame = std::shared_ptr<AVFrame>(av_frame_alloc(), [](AVFrame* frame) {
538 av_frame_unref(frame);
539 av_frame_free(&frame);
540 });
541
542 frame->width = _context->width;
543 frame->height = _context->height;
544 frame->format = _context->pix_fmt;
545
546 int res = av_frame_get_buffer(frame.get(), 32);
547 if (res < 0) {
548 throw std::runtime_error(::ffmpeg::tools::get_error_description(res));
549 }
550 }
551 }
552
553 return frame;
554 }
555
push_used_frame(std::shared_ptr<AVFrame> frame)556 void ffmpeg_instance::push_used_frame(std::shared_ptr<AVFrame> frame)
557 {
558 _used_frames.push(frame);
559 }
560
pop_used_frame()561 std::shared_ptr<AVFrame> ffmpeg_instance::pop_used_frame()
562 {
563 auto frame = _used_frames.front();
564 _used_frames.pop();
565 return frame;
566 }
567
get_extra_data(uint8_t ** data,size_t * size)568 bool ffmpeg_instance::get_extra_data(uint8_t** data, size_t* size)
569 {
570 if (_extra_data.size() == 0)
571 return false;
572
573 *data = _extra_data.data();
574 *size = _extra_data.size();
575 return true;
576 }
577
get_sei_data(uint8_t ** data,size_t * size)578 bool ffmpeg_instance::get_sei_data(uint8_t** data, size_t* size)
579 {
580 if (_sei_data.size() == 0)
581 return false;
582
583 *data = _sei_data.data();
584 *size = _sei_data.size();
585 return true;
586 }
587
get_video_info(struct video_scale_info * info)588 void ffmpeg_instance::get_video_info(struct video_scale_info* info)
589 {
590 if (!is_hardware_encode()) {
591 // Override input with supported format if software encode.
592 info->format = ::ffmpeg::tools::avpixelformat_to_obs_videoformat(_scaler.get_source_format());
593 }
594 }
595
receive_packet(bool * received_packet,struct encoder_packet * packet)596 int ffmpeg_instance::receive_packet(bool* received_packet, struct encoder_packet* packet)
597 {
598 int res = 0;
599
600 av_packet_unref(&_packet);
601
602 {
603 auto gctx = gs::context();
604 res = avcodec_receive_packet(_context, &_packet);
605 }
606 if (res != 0) {
607 return res;
608 }
609
610 if (!_have_first_frame) {
611 if (_codec->id == AV_CODEC_ID_H264) {
612 uint8_t* tmp_packet;
613 uint8_t* tmp_header;
614 uint8_t* tmp_sei;
615 std::size_t sz_packet, sz_header, sz_sei;
616
617 obs_extract_avc_headers(_packet.data, static_cast<size_t>(_packet.size), &tmp_packet, &sz_packet,
618 &tmp_header, &sz_header, &tmp_sei, &sz_sei);
619
620 if (sz_header) {
621 _extra_data.resize(sz_header);
622 std::memcpy(_extra_data.data(), tmp_header, sz_header);
623 }
624
625 if (sz_sei) {
626 _sei_data.resize(sz_sei);
627 std::memcpy(_sei_data.data(), tmp_sei, sz_sei);
628 }
629
630 // Not required, we only need the Extra Data and SEI Data anyway.
631 //std::memcpy(_current_packet.data, tmp_packet, sz_packet);
632 //_current_packet.size = static_cast<int>(sz_packet);
633
634 bfree(tmp_packet);
635 bfree(tmp_header);
636 bfree(tmp_sei);
637 } else if (_codec->id == AV_CODEC_ID_HEVC) {
638 hevc::extract_header_sei(_packet.data, static_cast<size_t>(_packet.size), _extra_data, _sei_data);
639 } else if (_context->extradata != nullptr) {
640 _extra_data.resize(static_cast<size_t>(_context->extradata_size));
641 std::memcpy(_extra_data.data(), _context->extradata, static_cast<size_t>(_context->extradata_size));
642 }
643 _have_first_frame = true;
644 }
645
646 // Allow Handler Post-Processing
647 if (_handler)
648 _handler->process_avpacket(_packet, _codec, _context);
649
650 packet->type = OBS_ENCODER_VIDEO;
651 packet->pts = _packet.pts;
652 packet->dts = _packet.dts;
653 packet->data = _packet.data;
654 packet->size = static_cast<size_t>(_packet.size);
655 packet->keyframe = !!(_packet.flags & AV_PKT_FLAG_KEY);
656 packet->drop_priority = packet->keyframe ? 0 : 1;
657 *received_packet = true;
658
659 push_free_frame(pop_used_frame());
660
661 return res;
662 }
663
send_frame(std::shared_ptr<AVFrame> const frame)664 int ffmpeg_instance::send_frame(std::shared_ptr<AVFrame> const frame)
665 {
666 int res = 0;
667 {
668 auto gctx = gs::context();
669 res = avcodec_send_frame(_context, frame.get());
670 }
671 if (res == 0) {
672 push_used_frame(frame);
673 }
674
675 return res;
676 }
677
encode_avframe(std::shared_ptr<AVFrame> frame,encoder_packet * packet,bool * received_packet)678 bool ffmpeg_instance::encode_avframe(std::shared_ptr<AVFrame> frame, encoder_packet* packet, bool* received_packet)
679 {
680 bool sent_frame = false;
681 bool recv_packet = false;
682 bool should_lag = (_sent_frames >= _lag_in_frames);
683
684 auto loop_begin = std::chrono::high_resolution_clock::now();
685 auto loop_end = loop_begin + std::chrono::milliseconds(50);
686
687 while ((!sent_frame || (should_lag && !recv_packet)) && !(std::chrono::high_resolution_clock::now() > loop_end)) {
688 bool eagain_is_stupid = false;
689
690 if (!sent_frame) {
691 int res = send_frame(frame);
692 switch (res) {
693 case 0:
694 sent_frame = true;
695 frame = nullptr;
696 break;
697 case AVERROR(EAGAIN):
698 // This means we should call receive_packet again, but what do we do with that data?
699 // Why can't we queue on both? Do I really have to implement threading for this stuff?
700 if (*received_packet == true) {
701 DLOG_WARNING("Skipped frame due to EAGAIN when a packet was already returned.");
702 sent_frame = true;
703 }
704 eagain_is_stupid = true;
705 break;
706 case AVERROR(EOF):
707 DLOG_ERROR("Skipped frame due to end of stream.");
708 sent_frame = true;
709 break;
710 default:
711 DLOG_ERROR("Failed to encode frame: %s (%" PRId32 ").", ::ffmpeg::tools::get_error_description(res),
712 res);
713 return false;
714 }
715 }
716
717 if (!recv_packet) {
718 int res = receive_packet(received_packet, packet);
719 switch (res) {
720 case 0:
721 recv_packet = true;
722 break;
723 case AVERROR(EOF):
724 DLOG_ERROR("Received end of file.");
725 recv_packet = true;
726 break;
727 case AVERROR(EAGAIN):
728 if (sent_frame) {
729 recv_packet = true;
730 }
731 if (eagain_is_stupid) {
732 DLOG_ERROR("Both send and recieve returned EAGAIN, encoder is broken.");
733 return false;
734 }
735 break;
736 default:
737 DLOG_ERROR("Failed to receive packet: %s (%" PRId32 ").", ::ffmpeg::tools::get_error_description(res),
738 res);
739 return false;
740 }
741 }
742
743 if (!sent_frame || !recv_packet) {
744 std::this_thread::sleep_for(std::chrono::milliseconds(1));
745 }
746 }
747
748 if (!sent_frame)
749 push_free_frame(frame);
750
751 return true;
752 }
753
is_hardware_encode()754 bool ffmpeg_instance::is_hardware_encode()
755 {
756 return _hwinst != nullptr;
757 }
758
get_avcodec()759 const AVCodec* ffmpeg_instance::get_avcodec()
760 {
761 return _codec;
762 }
763
get_avcodeccontext()764 const AVCodecContext* ffmpeg_instance::get_avcodeccontext()
765 {
766 return _context;
767 }
768
parse_ffmpeg_commandline(std::string text)769 void ffmpeg_instance::parse_ffmpeg_commandline(std::string text)
770 {
771 // Steps to properly parse a command line:
772 // 1. Split by space and package by quotes.
773 // 2. Parse each resulting option individually.
774
775 // First, we split by space and of course respect quotes while doing so.
776 // That means that "-foo= bar" is stored as std::string("-foo= bar"),
777 // and things like -foo="bar" is stored as std::string("-foo=\"bar\"").
778 // However "-foo"=bar" -foo2=bar" is stored as std::string("-foo=bar -foo2=bar")
779 // because the quote was not escaped.
780 std::list<std::string> opts;
781 std::stringstream opt_stream{std::ios_base::in | std::ios_base::out | std::ios_base::binary};
782 std::stack<char> quote_stack;
783 for (std::size_t p = 0; p <= text.size(); p++) {
784 char here = p < text.size() ? text.at(p) : 0;
785
786 if (here == '\\') {
787 std::size_t p2 = p + 1;
788 if (p2 < text.size()) {
789 char here2 = text.at(p2);
790 if (isdigit(here2)) { // Octal
791 // Not supported yet.
792 p++;
793 } else if (here2 == 'x') { // Hexadecimal
794 // Not supported yet.
795 p += 3;
796 } else if (here2 == 'u') { // 4 or 8 wide Unicode.
797 // Not supported yet.
798 } else if (here2 == 'a') {
799 opt_stream << '\a';
800 p++;
801 } else if (here2 == 'b') {
802 opt_stream << '\b';
803 p++;
804 } else if (here2 == 'f') {
805 opt_stream << '\f';
806 p++;
807 } else if (here2 == 'n') {
808 opt_stream << '\n';
809 p++;
810 } else if (here2 == 'r') {
811 opt_stream << '\r';
812 p++;
813 } else if (here2 == 't') {
814 opt_stream << '\t';
815 p++;
816 } else if (here2 == 'v') {
817 opt_stream << '\v';
818 p++;
819 } else if (here2 == '\\') {
820 opt_stream << '\\';
821 p++;
822 } else if (here2 == '\'') {
823 opt_stream << '\'';
824 p++;
825 } else if (here2 == '"') {
826 opt_stream << '"';
827 p++;
828 } else if (here2 == '?') {
829 opt_stream << '\?';
830 p++;
831 }
832 }
833 } else if ((here == '\'') || (here == '"')) {
834 if (quote_stack.size() > 1) {
835 opt_stream << here;
836 }
837 if (quote_stack.size() == 0) {
838 quote_stack.push(here);
839 } else if (quote_stack.top() == here) {
840 quote_stack.pop();
841 } else {
842 quote_stack.push(here);
843 }
844 } else if ((here == 0) || ((here == ' ') && (quote_stack.size() == 0))) {
845 std::string ropt = opt_stream.str();
846 if (ropt.size() > 0) {
847 opts.push_back(ropt);
848 opt_stream.str(std::string());
849 opt_stream.clear();
850 }
851 } else {
852 opt_stream << here;
853 }
854 }
855
856 // Now that we have a list of parameters as neatly grouped strings, and
857 // have also dealt with escaping for the most part. We want to parse
858 // an FFmpeg commandline option set here, so the first character in
859 // the string must be a '-'.
860 for (std::string& opt : opts) {
861 // Skip empty options.
862 if (opt.size() == 0)
863 continue;
864
865 // Skip options that don't start with a '-'.
866 if (opt.at(0) != '-') {
867 DLOG_WARNING("Option '%s' is malformed, must start with a '-'.", opt.c_str());
868 continue;
869 }
870
871 // Skip options that don't contain a '='.
872 const char* cstr = opt.c_str();
873 const char* eq_at = strchr(cstr, '=');
874 if (eq_at == nullptr) {
875 DLOG_WARNING("Option '%s' is malformed, must contain a '='.", opt.c_str());
876 continue;
877 }
878
879 try {
880 std::string key = opt.substr(1, static_cast<size_t>((eq_at - cstr) - 1));
881 std::string value = opt.substr(static_cast<size_t>((eq_at - cstr) + 1));
882
883 int res = av_opt_set(_context, key.c_str(), value.c_str(), AV_OPT_SEARCH_CHILDREN);
884 if (res < 0) {
885 DLOG_WARNING("Option '%s' (key: '%s', value: '%s') encountered error: %s", opt.c_str(), key.c_str(),
886 value.c_str(), ::ffmpeg::tools::get_error_description(res));
887 }
888 } catch (const std::exception& ex) {
889 DLOG_ERROR("Option '%s' encountered exception: %s", opt.c_str(), ex.what());
890 }
891 }
892 }
893
ffmpeg_factory(const AVCodec * codec)894 ffmpeg_factory::ffmpeg_factory(const AVCodec* codec) : _avcodec(codec)
895 {
896 // Generate default identifier.
897 {
898 std::stringstream str;
899 str << PREFIX << _avcodec->name;
900 _id = str.str();
901 }
902
903 { // Generate default name.
904 std::stringstream str;
905 if (_avcodec->long_name) {
906 str << _avcodec->long_name;
907 str << " (" << _avcodec->name << ")";
908 } else {
909 str << _avcodec->name;
910 }
911 str << D_TRANSLATE(ST_FFMPEG_SUFFIX);
912 _name = str.str();
913 }
914
915 // Try and find a codec name that libOBS understands.
916 if (auto* desc = avcodec_descriptor_get(_avcodec->id); desc) {
917 _codec = desc->name;
918 } else {
919 // If FFmpeg doesn't know better, fall back to the name.
920 _codec = _avcodec->name;
921 }
922
923 // Find any available handlers for this codec.
924 if (_handler = ffmpeg_manager::get()->get_handler(_avcodec->name); _handler) {
925 // Override any found info with the one specified by the handler.
926 _handler->adjust_info(this, _avcodec, _id, _name, _codec);
927
928 // Add texture capability for hardware encoders.
929 if (_handler->is_hardware_encoder(this)) {
930 _info.caps |= OBS_ENCODER_CAP_PASS_TEXTURE;
931 }
932 } else {
933 // If there are no handlers, default to mark it deprecated.
934 _info.caps |= OBS_ENCODER_CAP_DEPRECATED;
935 }
936
937 { // Build Info structure.
938 _info.id = _id.c_str();
939 _info.codec = _codec.c_str();
940 if (_avcodec->type == AVMediaType::AVMEDIA_TYPE_VIDEO) {
941 _info.type = obs_encoder_type::OBS_ENCODER_VIDEO;
942 } else if (_avcodec->type == AVMediaType::AVMEDIA_TYPE_AUDIO) {
943 _info.type = obs_encoder_type::OBS_ENCODER_AUDIO;
944 }
945 }
946
947 // Register encoder and proxies.
948 finish_setup();
949 const std::string proxies[] = {
950 std::string("streamfx--") + _avcodec->name,
951 std::string("StreamFX-") + _avcodec->name,
952 std::string("obs-ffmpeg-encoder_") + _avcodec->name,
953 };
954 for (auto proxy_id : proxies) {
955 register_proxy(proxy_id);
956 if (_info.caps & OBS_ENCODER_CAP_PASS_TEXTURE) {
957 std::string proxy_fallback_id = proxy_id + "_sw";
958 register_proxy(proxy_fallback_id);
959 }
960 }
961 }
962
~ffmpeg_factory()963 ffmpeg_factory::~ffmpeg_factory() {}
964
get_name()965 const char* ffmpeg_factory::get_name()
966 {
967 return _name.c_str();
968 }
969
get_defaults2(obs_data_t * settings)970 void ffmpeg_factory::get_defaults2(obs_data_t* settings)
971 {
972 if (_handler)
973 _handler->get_defaults(settings, _avcodec, nullptr, _handler->is_hardware_encoder(this));
974
975 if ((_avcodec->capabilities & AV_CODEC_CAP_INTRA_ONLY) == 0) {
976 obs_data_set_default_int(settings, KEY_KEYFRAMES_INTERVALTYPE, 0);
977 obs_data_set_default_double(settings, KEY_KEYFRAMES_INTERVAL_SECONDS, 2.0);
978 obs_data_set_default_int(settings, KEY_KEYFRAMES_INTERVAL_FRAMES, 300);
979 }
980
981 { // Integrated Options
982 // FFmpeg
983 obs_data_set_default_string(settings, KEY_FFMPEG_CUSTOMSETTINGS, "");
984 obs_data_set_default_int(settings, KEY_FFMPEG_COLORFORMAT, static_cast<int64_t>(AV_PIX_FMT_NONE));
985 obs_data_set_default_int(settings, KEY_FFMPEG_THREADS, 0);
986 obs_data_set_default_int(settings, KEY_FFMPEG_GPU, -1);
987 obs_data_set_default_int(settings, KEY_FFMPEG_STANDARDCOMPLIANCE, FF_COMPLIANCE_STRICT);
988 }
989 }
990
modified_keyframes(obs_properties_t * props,obs_property_t *,obs_data_t * settings)991 static bool modified_keyframes(obs_properties_t* props, obs_property_t*, obs_data_t* settings) noexcept
992 try {
993 bool is_seconds = obs_data_get_int(settings, KEY_KEYFRAMES_INTERVALTYPE) == 0;
994 obs_property_set_visible(obs_properties_get(props, KEY_KEYFRAMES_INTERVAL_FRAMES), !is_seconds);
995 obs_property_set_visible(obs_properties_get(props, KEY_KEYFRAMES_INTERVAL_SECONDS), is_seconds);
996 return true;
997 } catch (const std::exception& ex) {
998 DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
999 return false;
1000 } catch (...) {
1001 DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
1002 return false;
1003 }
1004
get_properties2(instance_t * data)1005 obs_properties_t* ffmpeg_factory::get_properties2(instance_t* data)
1006 {
1007 obs_properties_t* props = obs_properties_create();
1008
1009 if (data) {
1010 data->get_properties(props);
1011 }
1012
1013 if (_handler)
1014 _handler->get_properties(props, _avcodec, nullptr, _handler->is_hardware_encoder(this));
1015
1016 if (_handler && _handler->has_keyframe_support(this)) {
1017 // Key-Frame Options
1018 obs_properties_t* grp = props;
1019 if (!util::are_property_groups_broken()) {
1020 grp = obs_properties_create();
1021 obs_properties_add_group(props, ST_KEYFRAMES, D_TRANSLATE(ST_KEYFRAMES), OBS_GROUP_NORMAL, grp);
1022 }
1023
1024 { // Key-Frame Interval Type
1025 auto p = obs_properties_add_list(grp, KEY_KEYFRAMES_INTERVALTYPE, D_TRANSLATE(ST_KEYFRAMES_INTERVALTYPE),
1026 OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
1027 obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_KEYFRAMES_INTERVALTYPE)));
1028 obs_property_set_modified_callback(p, modified_keyframes);
1029 obs_property_list_add_int(p, D_TRANSLATE(ST_KEYFRAMES_INTERVALTYPE_(Seconds)), 0);
1030 obs_property_list_add_int(p, D_TRANSLATE(ST_KEYFRAMES_INTERVALTYPE_(Frames)), 1);
1031 }
1032 { // Key-Frame Interval Seconds
1033 auto p = obs_properties_add_float(grp, KEY_KEYFRAMES_INTERVAL_SECONDS, D_TRANSLATE(ST_KEYFRAMES_INTERVAL),
1034 0.00, std::numeric_limits<int16_t>::max(), 0.01);
1035 obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_KEYFRAMES_INTERVAL)));
1036 obs_property_float_set_suffix(p, " seconds");
1037 }
1038 { // Key-Frame Interval Frames
1039 auto p = obs_properties_add_int(grp, KEY_KEYFRAMES_INTERVAL_FRAMES, D_TRANSLATE(ST_KEYFRAMES_INTERVAL), 0,
1040 std::numeric_limits<int32_t>::max(), 1);
1041 obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_KEYFRAMES_INTERVAL)));
1042 obs_property_int_set_suffix(p, " frames");
1043 }
1044 }
1045
1046 {
1047 obs_properties_t* grp = props;
1048 if (!util::are_property_groups_broken()) {
1049 auto prs = obs_properties_create();
1050 obs_properties_add_group(props, ST_FFMPEG, D_TRANSLATE(ST_FFMPEG), OBS_GROUP_NORMAL, prs);
1051 grp = prs;
1052 }
1053
1054 { // Custom Settings
1055 auto p = obs_properties_add_text(grp, KEY_FFMPEG_CUSTOMSETTINGS, D_TRANSLATE(ST_FFMPEG_CUSTOMSETTINGS),
1056 obs_text_type::OBS_TEXT_DEFAULT);
1057 obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_FFMPEG_CUSTOMSETTINGS)));
1058 }
1059
1060 if (_handler && _handler->is_hardware_encoder(this)) {
1061 auto p = obs_properties_add_int(grp, KEY_FFMPEG_GPU, D_TRANSLATE(ST_FFMPEG_GPU), -1,
1062 std::numeric_limits<uint8_t>::max(), 1);
1063 obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_FFMPEG_GPU)));
1064 }
1065
1066 if (_handler && _handler->has_threading_support(this)) {
1067 auto p = obs_properties_add_int_slider(grp, KEY_FFMPEG_THREADS, D_TRANSLATE(ST_FFMPEG_THREADS), 0,
1068 static_cast<int64_t>(std::thread::hardware_concurrency() * 2), 1);
1069 obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_FFMPEG_THREADS)));
1070 }
1071
1072 if (_handler && _handler->has_pixel_format_support(this)) {
1073 auto p = obs_properties_add_list(grp, KEY_FFMPEG_COLORFORMAT, D_TRANSLATE(ST_FFMPEG_COLORFORMAT),
1074 OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
1075 obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_FFMPEG_COLORFORMAT)));
1076 obs_property_list_add_int(p, D_TRANSLATE(S_STATE_AUTOMATIC), static_cast<int64_t>(AV_PIX_FMT_NONE));
1077 for (auto ptr = _avcodec->pix_fmts; *ptr != AV_PIX_FMT_NONE; ptr++) {
1078 obs_property_list_add_int(p, ::ffmpeg::tools::get_pixel_format_name(*ptr), static_cast<int64_t>(*ptr));
1079 }
1080 }
1081
1082 {
1083 auto p =
1084 obs_properties_add_list(grp, KEY_FFMPEG_STANDARDCOMPLIANCE, D_TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE),
1085 OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
1086 obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_FFMPEG_STANDARDCOMPLIANCE)));
1087 obs_property_list_add_int(p, D_TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE ".VeryStrict"),
1088 FF_COMPLIANCE_VERY_STRICT);
1089 obs_property_list_add_int(p, D_TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE ".Strict"), FF_COMPLIANCE_STRICT);
1090 obs_property_list_add_int(p, D_TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE ".Normal"), FF_COMPLIANCE_NORMAL);
1091 obs_property_list_add_int(p, D_TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE ".Unofficial"),
1092 FF_COMPLIANCE_UNOFFICIAL);
1093 obs_property_list_add_int(p, D_TRANSLATE(ST_FFMPEG_STANDARDCOMPLIANCE ".Experimental"),
1094 FF_COMPLIANCE_EXPERIMENTAL);
1095 }
1096 };
1097
1098 return props;
1099 }
1100
get_avcodec()1101 const AVCodec* ffmpeg_factory::get_avcodec()
1102 {
1103 return _avcodec;
1104 }
1105
get_info()1106 obs_encoder_info* streamfx::encoder::ffmpeg::ffmpeg_factory::get_info()
1107 {
1108 return &_info;
1109 }
1110
ffmpeg_manager()1111 ffmpeg_manager::ffmpeg_manager() : _factories(), _handlers(), _debug_handler()
1112 {
1113 // Handlers
1114 _debug_handler = ::std::make_shared<handler::debug_handler>();
1115 register_handler("prores_aw", ::std::make_shared<handler::prores_aw_handler>());
1116 register_handler("h264_nvenc", ::std::make_shared<handler::nvenc_h264_handler>());
1117 register_handler("hevc_nvenc", ::std::make_shared<handler::nvenc_hevc_handler>());
1118 }
1119
~ffmpeg_manager()1120 ffmpeg_manager::~ffmpeg_manager()
1121 {
1122 _factories.clear();
1123 }
1124
register_encoders()1125 void ffmpeg_manager::register_encoders()
1126 {
1127 // Encoders
1128 #if FF_API_NEXT
1129 void* iterator = nullptr;
1130 for (const AVCodec* codec = av_codec_iterate(&iterator); codec != nullptr; codec = av_codec_iterate(&iterator)) {
1131 // Only register encoders.
1132 if (!av_codec_is_encoder(codec))
1133 continue;
1134
1135 if ((codec->type == AVMediaType::AVMEDIA_TYPE_AUDIO) || (codec->type == AVMediaType::AVMEDIA_TYPE_VIDEO)) {
1136 try {
1137 _factories.emplace(codec, std::make_shared<ffmpeg_factory>(codec));
1138 } catch (const std::exception& ex) {
1139 DLOG_ERROR("Failed to register encoder '%s': %s", codec->name, ex.what());
1140 }
1141 }
1142 }
1143 #else
1144 AVCodec* codec = nullptr;
1145 for (codec = av_codec_next(codec); codec != nullptr; codec = av_codec_next(codec)) {
1146 // Only register encoders.
1147 if (!av_codec_is_encoder(codec))
1148 continue;
1149
1150 if ((codec->type == AVMediaType::AVMEDIA_TYPE_AUDIO) || (codec->type == AVMediaType::AVMEDIA_TYPE_VIDEO)) {
1151 try {
1152 _factories.emplace(codec, std::make_shared<ffmpeg_factory>(codec));
1153 } catch (const std::exception& ex) {
1154 DLOG_ERROR("Failed to register encoder '%s': %s", codec->name, ex.what());
1155 }
1156 }
1157 }
1158 #endif
1159 }
1160
register_handler(std::string codec,std::shared_ptr<handler::handler> handler)1161 void ffmpeg_manager::register_handler(std::string codec, std::shared_ptr<handler::handler> handler)
1162 {
1163 _handlers.emplace(codec, handler);
1164 }
1165
get_handler(std::string codec)1166 std::shared_ptr<handler::handler> ffmpeg_manager::get_handler(std::string codec)
1167 {
1168 auto fnd = _handlers.find(codec);
1169 if (fnd != _handlers.end())
1170 return fnd->second;
1171 #ifdef _DEBUG
1172 return _debug_handler;
1173 #else
1174 return nullptr;
1175 #endif
1176 }
1177
has_handler(std::string codec)1178 bool ffmpeg_manager::has_handler(std::string codec)
1179 {
1180 return (_handlers.find(codec) != _handlers.end());
1181 }
1182
1183 std::shared_ptr<ffmpeg_manager> _ffmepg_encoder_factory_instance = nullptr;
1184
initialize()1185 void ffmpeg_manager::initialize()
1186 {
1187 if (!_ffmepg_encoder_factory_instance) {
1188 _ffmepg_encoder_factory_instance = std::make_shared<ffmpeg_manager>();
1189 _ffmepg_encoder_factory_instance->register_encoders();
1190 }
1191 }
1192
finalize()1193 void ffmpeg_manager::finalize()
1194 {
1195 _ffmepg_encoder_factory_instance.reset();
1196 }
1197
get()1198 std::shared_ptr<ffmpeg_manager> ffmpeg_manager::get()
1199 {
1200 return _ffmepg_encoder_factory_instance;
1201 }
1202