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