Просмотр исходного кода

Update config modified callbacks with disable/autoselection semantics

Autoselection is only implemented for frame rate as it doesn't make
sense to autoselect the video format (Any fills the role of library
supplied autoselection) or the resolution (it may make sense to
implement resolution matching based on the current transformation
settings in the future though)
Palana 11 лет назад
Родитель
Сommit
2bf0163aa5
1 измененных файлов с 286 добавлено и 102 удалено
  1. 286 102
      plugins/win-dshow/win-dshow.cpp

+ 286 - 102
plugins/win-dshow/win-dshow.cpp

@@ -9,6 +9,7 @@
 
 #include <algorithm>
 #include <limits>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -32,8 +33,6 @@ using namespace DShow;
 #define VIDEO_FORMAT      "video_format"
 #define LAST_VIDEO_DEV_ID "last_video_device_id"
 #define LAST_RESOLUTION   "last_resolution"
-#define LAST_RES_TYPE     "last_res_type"
-#define LAST_INTERVAL     "last_interval"
 
 enum ResType {
 	ResType_Preferred,
@@ -530,82 +529,58 @@ static const FPSFormat validFPSFormats[] = {
 	{"1",          MAKE_DSHOW_FPS(1)},
 };
 
-static bool AddFPSRate(obs_property_t p, const FPSFormat &format,
-		const VideoInfo &cap)
-{
-	long long interval = format.interval;
+static bool DeviceIntervalChanged(obs_properties_t props, obs_property_t p,
+		obs_data_t settings);
 
-	if (!FrameRateAvailable(format, cap))
+static bool TryResolution(VideoDevice &dev, string res)
+{
+	int cx, cy;
+	if (!ConvertRes(cx, cy, res.c_str()))
 		return false;
 
-	obs_property_list_add_int(p, format.text, interval);
-	return true;
+	return ResolutionAvailable(dev, cx, cy);
 }
 
-static inline bool AddFPSRates(obs_property_t p, const VideoDevice &device,
-		int cx, int cy, long long &interval)
+static bool SetResolution(obs_properties_t props, obs_data_t settings,
+		string res, bool autoselect=false)
 {
-	long long bestInterval = numeric_limits<long long>::max();
-	bool intervalFound = false;
-
-	for (const FPSFormat &format : validFPSFormats) {
-		for (const VideoInfo &cap : device.caps) {
-			if (ResolutionAvailable(cap, cx, cy)) {
-				if (!intervalFound) {
-					if (FrameRateAvailable(cap, interval))
-						intervalFound = true;
-					else if (cap.minInterval < bestInterval)
-						bestInterval = cap.minInterval;
-				}
-
-				if (AddFPSRate(p, format, cap))
-					break;
-			}
-		}
-	}
+	if (autoselect)
+		obs_data_set_autoselect_string(settings, RESOLUTION,
+				res.c_str());
+	else
+		obs_data_unset_autoselect_value(settings, RESOLUTION);
 
-	if (!intervalFound) {
-		interval = bestInterval;
-		return false;
-	}
+	DeviceIntervalChanged(props, obs_properties_get(props, FRAME_INTERVAL),
+			settings);
 
+	if (!autoselect)
+		obs_data_setstring(settings, LAST_RESOLUTION, res.c_str());
 	return true;
 }
 
-static bool DeviceIntervalChanged(obs_properties_t props, obs_property_t p,
-		obs_data_t settings);
-
 static bool DeviceResolutionChanged(obs_properties_t props, obs_property_t p,
 		obs_data_t settings)
 {
+	UNUSED_PARAMETER(p);
+
 	PropertiesData *data = (PropertiesData*)obs_properties_get_param(props);
-	const char *res, *last_res, *id;
-	long long interval;
+	const char *id;
 	VideoDevice device;
 
 	id       = obs_data_getstring(settings, VIDEO_DEVICE_ID);
-	res      = obs_data_getstring(settings, RESOLUTION);
-	last_res = obs_data_getstring(settings, LAST_RESOLUTION);
-	interval = obs_data_getint   (settings, FRAME_INTERVAL);
+	string res = obs_data_getstring(settings, RESOLUTION);
+	string last_res = obs_data_getstring(settings, LAST_RESOLUTION);
 
 	if (!data->GetDevice(device, id))
-		return true;
-
-	int cx, cy;
-	if (!ConvertRes(cx, cy, res))
-		return true;
-
-	p = obs_properties_get(props, FRAME_INTERVAL);
+		return false;
 
-	obs_property_list_clear(p);
-	AddFPSRates(p, device, cx, cy, interval);
+	if (TryResolution(device, res))
+		return SetResolution(props, settings, res);
 
-	if (res && last_res && strcmp(res, last_res) != 0) {
-		DeviceIntervalChanged(props, p, settings);
-		obs_data_setstring(settings, LAST_RESOLUTION, res);
-	}
+	if (TryResolution(device, last_res))
+		return SetResolution(props, settings, last_res, true);
 
-	return true;
+	return false;
 }
 
 struct VideoFormatName {
@@ -634,47 +609,66 @@ static const VideoFormatName videoFormatNames[] = {
 	{VideoFormat::H264,  "H264"}
 };
 
-static bool UpdateVideoFormats(obs_properties_t props, VideoDevice &device,
-		long long interval, int cx, int cy, VideoFormat format)
+static bool ResTypeChanged(obs_properties_t props, obs_property_t p,
+		obs_data_t settings);
+
+static size_t AddDevice(obs_property_t device_list, const string &id)
 {
-	bool foundFormat = false;
-	obs_property_t p = obs_properties_get(props, VIDEO_FORMAT);
+	DStr name, path;
+	if (!DecodeDeviceId(name, path, id.c_str()))
+		return numeric_limits<size_t>::max();
 
-	obs_property_list_clear(p);
-	obs_property_list_add_int(p, "Any", (int)VideoFormat::Any);
+	return obs_property_list_add_string(device_list, name, id.c_str());
+}
+
+static bool UpdateDeviceList(obs_property_t list, const string &id)
+{
+	size_t size = obs_property_list_item_count(list);
+	bool found = false;
+	bool disabled_unknown_found = false;
 
-	for (const VideoFormatName &name : videoFormatNames) {
-		for (const VideoInfo &cap : device.caps) {
+	for (size_t i = 0; i < size; i++) {
+		if (obs_property_list_item_string(list, i) == id) {
+			found = true;
+			continue;
+		}
+		if (obs_property_list_item_disabled(list, i))
+			disabled_unknown_found = true;
+	}
 
-			if (FrameRateAvailable(cap, interval) &&
-			    ResolutionAvailable(cap, cx, cy) &&
-			    FormatMatches(cap.format, name.format)) {
+	if (!found && !disabled_unknown_found) {
+		size_t idx = AddDevice(list, id);
+		obs_property_list_item_disable(list, idx, true);
+		return true;
+	}
 
-				if (format == cap.format)
-					foundFormat = true;
+	if (found && !disabled_unknown_found)
+		return false;
 
-				obs_property_list_add_int(p, name.name,
-						(int)name.format);
-				break;
-			}
+	for (size_t i = 0; i < size;) {
+		if (obs_property_list_item_disabled(list, i)) {
+			obs_property_list_item_remove(list, i);
+			continue;
 		}
+		i += 1;
 	}
 
-	return foundFormat;
+	return true;
 }
 
 static bool DeviceSelectionChanged(obs_properties_t props, obs_property_t p,
 		obs_data_t settings)
 {
 	PropertiesData *data = (PropertiesData*)obs_properties_get_param(props);
-	const char *id, *old_id;
 	VideoDevice device;
 
-	id     = obs_data_getstring(settings, VIDEO_DEVICE_ID);
-	old_id = obs_data_getstring(settings, LAST_VIDEO_DEV_ID);
+	string id     = obs_data_getstring(settings, VIDEO_DEVICE_ID);
+	string old_id = obs_data_getstring(settings, LAST_VIDEO_DEV_ID);
 
-	if (!data->GetDevice(device, id))
-		return false;
+	bool device_list_updated = UpdateDeviceList(p, id);
+
+	if (!data->GetDevice(device, id.c_str()))
+		return !device_list_updated;
 
 	vector<Resolution> resolutions;
 	for (const VideoInfo &cap : device.caps)
@@ -694,10 +688,11 @@ static bool DeviceSelectionChanged(obs_properties_t props, obs_property_t p,
 		obs_property_list_add_string(p, strRes.c_str(), strRes.c_str());
 	}
 
-	/* only reset resolution if device legitimately changed */
-	if (old_id && id && strcmp(id, old_id) != 0) {
-		DeviceResolutionChanged(props, p, settings);
-		obs_data_setstring(settings, LAST_VIDEO_DEV_ID, id);
+	/* only refresh properties if device legitimately changed */
+	if (!id.size() || !old_id.size() || id != old_id) {
+		p = obs_properties_get(props, RES_TYPE);
+		ResTypeChanged(props, p, settings);
+		obs_data_setstring(settings, LAST_VIDEO_DEV_ID, id.c_str());
 	}
 
 	return true;
@@ -775,7 +770,6 @@ static bool ResTypeChanged(obs_properties_t props, obs_property_t p,
 		obs_data_t settings)
 {
 	int  val     = (int)obs_data_getint(settings, RES_TYPE);
-	int  lastVal = (int)obs_data_getint(settings, LAST_RES_TYPE);
 	bool enabled = (val != ResType_Preferred);
 
 	p = obs_properties_get(props, RESOLUTION);
@@ -787,9 +781,11 @@ static bool ResTypeChanged(obs_properties_t props, obs_property_t p,
 	p = obs_properties_get(props, VIDEO_FORMAT);
 	obs_property_set_enabled(p, enabled);
 
-	if (val == ResType_Custom && lastVal != val) {
-		SetClosestResFPS(props, settings);
-		obs_data_setint(settings, LAST_RES_TYPE, val);
+	if (val == ResType_Custom) {
+		p = obs_properties_get(props, RESOLUTION);
+		DeviceResolutionChanged(props, p, settings);
+	} else {
+		obs_data_unset_autoselect_value(settings, FRAME_INTERVAL);
 	}
 
 	return true;
@@ -811,6 +807,42 @@ static DStr GetFPSName(long long interval)
 	return name;
 }
 
+static void UpdateFPS(VideoDevice &device, VideoFormat format,
+		long long interval, int cx, int cy, obs_properties_t props)
+{
+	obs_property_t list = obs_properties_get(props, FRAME_INTERVAL);
+
+	obs_property_list_clear(list);
+
+	bool interval_added = false;
+	for (const FPSFormat &fps_format : validFPSFormats) {
+		bool video_format_match = false;
+		long long format_interval = fps_format.interval;
+
+		bool available = CapsMatch(device,
+				ResolutionMatcher(cx, cy),
+				VideoFormatMatcher(format, video_format_match),
+				FrameRateMatcher(format_interval));
+
+		if (!available && interval != fps_format.interval)
+			continue;
+
+		if (interval == fps_format.interval)
+			interval_added = true;
+
+		size_t idx = obs_property_list_add_int(list, fps_format.text,
+				fps_format.interval);
+		obs_property_list_item_disable(list, idx, !available);
+	}
+
+	if (interval_added)
+		return;
+
+	size_t idx = obs_property_list_add_int(list, GetFPSName(interval),
+			interval);
+	obs_property_list_item_disable(list, idx, true);
+}
+
 static DStr GetVideoFormatName(VideoFormat format)
 {
 	DStr name;
@@ -825,42 +857,192 @@ static DStr GetVideoFormatName(VideoFormat format)
 	return name;
 }
 
+static void UpdateVideoFormats(VideoDevice &device, VideoFormat format_,
+		int cx, int cy, long long interval, obs_properties_t props)
+{
+	set<VideoFormat> formats = { VideoFormat::Any };
+	auto format_gatherer = [&formats](const VideoInfo &info) mutable -> bool
+	{
+		formats.insert(info.format);
+		return false;
+	};
+
+	CapsMatch(device,
+			ResolutionMatcher(cx, cy),
+			FrameRateMatcher(interval),
+			format_gatherer);
+
+	obs_property_t list = obs_properties_get(props, VIDEO_FORMAT);
+	obs_property_list_clear(list);
+
+	bool format_added = false;
+	for (const VideoFormatName &format : videoFormatNames) {
+		bool available = formats.find(format.format) != end(formats);
+
+		if (!available && format.format != format_)
+			continue;
+
+		if (format.format == format_)
+			format_added = true;
+
+		size_t idx = obs_property_list_add_int(list, format.name,
+				(long long)format.format);
+		obs_property_list_item_disable(list, idx, !available);
+	}
+
+	if (format_added)
+		return;
+
+	size_t idx = obs_property_list_add_int(list,
+			GetVideoFormatName(format_), (long long)format_);
+	obs_property_list_item_disable(list, idx, true);
+}
+
+static bool UpdateFPS(long long interval, obs_property_t list)
+{
+	size_t size = obs_property_list_item_count(list);
+	bool fps_found = false;
+	DStr name;
+
+	for (size_t i = 0; i < size; i++) {
+		if (obs_property_list_item_int(list, i) != interval)
+			continue;
+
+		obs_property_list_item_disable(list, i, true);
+		if (size == 1)
+			return false;
+
+		dstr_cat(name, obs_property_list_item_name(list, i));
+		fps_found = true;
+		break;
+	}
+
+	obs_property_list_clear(list);
+
+	if (!name->len)
+		name = GetFPSName(interval);
+
+	obs_property_list_add_int(list, name, interval);
+	obs_property_list_item_disable(list, 0, true);
+
+	return true;
+}
+
 static bool DeviceIntervalChanged(obs_properties_t props, obs_property_t p,
 		obs_data_t settings)
 {
-	int  val     = (int)obs_data_getint(settings, FRAME_INTERVAL);
-	int  lastVal = (int)obs_data_getint(settings, LAST_INTERVAL);
+	long long val = obs_data_getint(settings, FRAME_INTERVAL);
 
 	PropertiesData *data = (PropertiesData*)obs_properties_get_param(props);
 	const char *id = obs_data_getstring(settings, VIDEO_DEVICE_ID);
 	VideoDevice device;
 
 	if (!data->GetDevice(device, id))
-		return false;
+		return UpdateFPS(val, p);
 
-	int cx, cy;
-	const char *res = obs_data_getstring(settings, RESOLUTION);
-	if (!ConvertRes(cx, cy, res))
+	int cx = 0, cy = 0;
+	if (!DetermineResolution(cx, cy, settings, device)) {
+		UpdateVideoFormats(device, VideoFormat::Any, 0, 0, 0, props);
+		UpdateFPS(device, VideoFormat::Any, 0, 0, 0, props);
 		return true;
+	}
 
-	VideoFormat curFormat =
-		(VideoFormat)obs_data_getint(settings, VIDEO_FORMAT);
+	int resType = (int)obs_data_getint(settings, RES_TYPE);
+	if (resType != ResType_Custom)
+		return true;
 
-	bool foundFormat =
-		UpdateVideoFormats(props, device, val, cx, cy, curFormat);
+	VideoFormat format = (VideoFormat)obs_data_getint(settings,
+							VIDEO_FORMAT);
+
+	bool video_format_matches = false;
+	long long best_interval = numeric_limits<long long>::max();
+	bool frameRateSupported = CapsMatch(device,
+			ResolutionMatcher(cx, cy),
+			VideoFormatMatcher(format, video_format_matches),
+			ClosestFrameRateSelector(val, best_interval),
+			FrameRateMatcher(val));
+
+	if (video_format_matches &&
+			!frameRateSupported &&
+			best_interval != val) {
+		long long listed_val = 0;
+		for (const FPSFormat &format : validFPSFormats) {
+			long long diff = llabs(format.interval - best_interval);
+			if (diff < DEVICE_INTERVAL_DIFF_LIMIT) {
+				listed_val = format.interval;
+				break;
+			}
+		}
 
-	if (val != lastVal) {
-		if (!foundFormat)
-			obs_data_setint(settings, VIDEO_FORMAT,
-					(int)VideoFormat::Any);
+		if (listed_val != val)
+			obs_data_set_autoselect_int(settings, FRAME_INTERVAL,
+					listed_val);
 
-		obs_data_setint(settings, LAST_INTERVAL, val);
+	} else {
+		obs_data_unset_autoselect_value(settings, FRAME_INTERVAL);
 	}
 
+	UpdateVideoFormats(device, format, cx, cy, val, props);
+	UpdateFPS(device, format, val, cx, cy, props);
+
 	UNUSED_PARAMETER(p);
 	return true;
 }
 
+static bool UpdateVideoFormats(VideoFormat format, obs_property_t list)
+{
+	size_t size = obs_property_list_item_count(list);
+	DStr name;
+
+	for (size_t i = 0; i < size; i++) {
+		if ((VideoFormat)obs_property_list_item_int(list, i) != format)
+			continue;
+
+		if (size == 1)
+			return false;
+
+		dstr_cat(name, obs_property_list_item_name(list, i));
+		break;
+	}
+
+	obs_property_list_clear(list);
+
+	if (!name->len)
+		name = GetVideoFormatName(format);
+
+	obs_property_list_add_int(list, name, (long long)format);
+	obs_property_list_item_disable(list, 0, true);
+
+	return true;
+}
+
+static bool VideoFormatChanged(obs_properties_t props, obs_property_t p,
+		obs_data_t settings)
+{
+	PropertiesData *data = (PropertiesData*)obs_properties_get_param(props);
+	const char *id = obs_data_getstring(settings, VIDEO_DEVICE_ID);
+	VideoDevice device;
+
+	VideoFormat curFormat =
+		(VideoFormat)obs_data_getint(settings, VIDEO_FORMAT);
+
+	if (!data->GetDevice(device, id))
+		return UpdateVideoFormats(curFormat, p);
+
+	int cx, cy;
+	if (!DetermineResolution(cx, cy, settings, device)) {
+		UpdateVideoFormats(device, VideoFormat::Any, cx, cy, 0, props);
+		UpdateFPS(device, VideoFormat::Any, 0, 0, 0, props);
+		return true;
+	}
+
+	long long interval = obs_data_getint(settings, FRAME_INTERVAL);
+
+	UpdateVideoFormats(device, curFormat, cx, cy, interval, props);
+	UpdateFPS(device, curFormat, interval, cx, cy, props);
+	return true;
+}
+
 static obs_properties_t GetDShowProperties(void)
 {
 	obs_properties_t ppts = obs_properties_create();
@@ -904,9 +1086,11 @@ static obs_properties_t GetDShowProperties(void)
 
 	obs_property_set_modified_callback(p, DeviceIntervalChanged);
 
-	obs_properties_add_list(ppts, VIDEO_FORMAT, "Video Format",
+	p = obs_properties_add_list(ppts, VIDEO_FORMAT, "Video Format",
 			OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
 
+	obs_property_set_modified_callback(p, VideoFormatChanged);
+
 	return ppts;
 }