mac-avcapture: Add manual configuration

Currently supported settings:
- Resolution
- Frame rate
- Input format
This commit is contained in:
Palana 2015-08-18 20:00:55 +02:00
parent 520f300f0e
commit 3d558d65ba
4 changed files with 949 additions and 4 deletions

View File

@ -15,6 +15,7 @@ include_directories(${AVFOUNDATION}
${COCOA})
set(mac-avcapture_HEADERS
scope-guard.hpp
)
set(mac-avcapture_SOURCES

View File

@ -6,14 +6,52 @@
#include <obs-module.h>
#include <media-io/video-io.h>
#include <util/dstr.hpp>
#include <algorithm>
#include <initializer_list>
#include <cinttypes>
#include <limits>
#include <memory>
#include <vector>
#include "scope-guard.hpp"
#define NBSP "\xC2\xA0"
using namespace std;
namespace std {
template <>
struct default_delete<obs_data_t> {
void operator()(obs_data_t *data)
{
obs_data_release(data);
}
};
template <>
struct default_delete<obs_data_item_t> {
void operator()(obs_data_item_t *item)
{
obs_data_item_release(&item);
}
};
}
#define TEXT_AVCAPTURE obs_module_text("AVCapture")
#define TEXT_DEVICE obs_module_text("Device")
#define TEXT_USE_PRESET obs_module_text("UsePreset")
#define TEXT_PRESET obs_module_text("Preset")
#define TEXT_RESOLUTION obs_module_text("Resolution")
#define TEXT_FRAME_RATE obs_module_text("FrameRate")
#define TEXT_MATCH_OBS obs_module_text("MatchOBS")
#define TEXT_INPUT_FORMAT obs_module_text("InputFormat")
#define TEXT_AUTO obs_module_text("Auto")
static const FourCharCode INPUT_FORMAT_AUTO = -1;
#define MILLI_TIMESCALE 1000
#define MICRO_TIMESCALE (MILLI_TIMESCALE * 1000)
@ -73,6 +111,8 @@ struct av_capture {
dispatch_queue_t queue;
bool has_clock;
bool device_locked;
AVCaptureVideoDataOutput *out;
AVCaptureDevice *device;
AVCaptureDeviceInput *device_input;
@ -103,6 +143,115 @@ static AVCaptureDevice *get_device(obs_data_t *settings)
return [AVCaptureDevice deviceWithUniqueID:uid];
}
template <typename T, typename U>
static void clamp(T& store, U val, T low = numeric_limits<T>::min(),
T high = numeric_limits<T>::max())
{
store = static_cast<intmax_t>(val) < static_cast<intmax_t>(low) ? low :
(static_cast<intmax_t>(val) > static_cast<intmax_t>(high) ?
high : static_cast<T>(val));
}
static bool get_resolution(obs_data_t *settings, CMVideoDimensions &dims)
{
using item_ptr = unique_ptr<obs_data_item_t>;
item_ptr item{obs_data_item_byname(settings, "resolution")};
if (!item)
return false;
auto res_str = obs_data_item_get_string(item.get());
unique_ptr<obs_data_t> res{obs_data_create_from_json(res_str)};
if (!res)
return false;
item_ptr width{obs_data_item_byname(res.get(), "width")};
item_ptr height{obs_data_item_byname(res.get(), "height")};
if (!width || !height)
return false;
clamp(dims.width, obs_data_item_get_int(width.get()), 0);
clamp(dims.height, obs_data_item_get_int(height.get()), 0);
if (!dims.width || !dims.height)
return false;
return true;
}
static bool get_input_format(obs_data_t *settings, FourCharCode &fourcc)
{
auto item = unique_ptr<obs_data_item_t>{
obs_data_item_byname(settings, "input_format")};
if (!item)
return false;
fourcc = obs_data_item_get_int(item.get());
return true;
}
namespace {
struct config_helper {
obs_data_t *settings = nullptr;
AVCaptureDevice *dev_ = nullptr;
bool dims_valid : 1;
bool fr_valid : 1;
bool fps_valid : 1;
bool if_valid : 1;
CMVideoDimensions dims_{};
const char *frame_rate_ = nullptr;
media_frames_per_second fps_{};
FourCharCode input_format_ = INPUT_FORMAT_AUTO;
explicit config_helper(obs_data_t *settings)
: settings(settings)
{
dev_ = get_device(settings);
dims_valid = get_resolution(settings, dims_);
fr_valid = obs_data_get_frames_per_second(settings,
"frame_rate", nullptr, &frame_rate_);
fps_valid = obs_data_get_frames_per_second(settings,
"frame_rate", &fps_, nullptr);
if_valid = get_input_format(settings, input_format_);
}
AVCaptureDevice *dev() const
{
return dev_;
}
const CMVideoDimensions *dims() const
{
return dims_valid ? &dims_ : nullptr;
}
const char *frame_rate() const
{
return fr_valid ? frame_rate_ : nullptr;
}
const media_frames_per_second *fps() const
{
return fps_valid ? &fps_ : nullptr;
}
const FourCharCode *input_format() const
{
return if_valid ? &input_format_ : nullptr;
}
};
}
static inline video_format format_from_subtype(FourCharCode subtype)
{
//TODO: uncomment VIDEO_FORMAT_NV12 and VIDEO_FORMAT_ARGB once libobs
@ -124,6 +273,85 @@ static inline video_format format_from_subtype(FourCharCode subtype)
}
}
static const char *fourcc_subtype_name(FourCharCode fourcc);
static const char *format_description_subtype_name(CMFormatDescriptionRef desc,
FourCharCode *fourcc_=nullptr)
{
FourCharCode fourcc = CMFormatDescriptionGetMediaSubType(desc);
if (fourcc_)
*fourcc_ = fourcc;
return fourcc_subtype_name(fourcc);
}
static const char *fourcc_subtype_name(FourCharCode fourcc)
{
switch (fourcc) {
case kCVPixelFormatType_422YpCbCr8:
return "UYVY - 422YpCbCr8"; //VIDEO_FORMAT_UYVY;
case kCVPixelFormatType_422YpCbCr8_yuvs:
return "YUY2 - 422YpCbCr8_yuvs"; //VIDEO_FORMAT_YUY2;
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
return "NV12 - 420YpCbCr8BiPlanar"; //VIDEO_FORMAT_NV12;
case kCVPixelFormatType_32ARGB:
return "ARGB - 32ARGB"; //VIDEO_FORMAT_ARGB;*/
case kCVPixelFormatType_32BGRA:
return "BGRA - 32BGRA"; //VIDEO_FORMAT_BGRA;
case kCMVideoCodecType_Animation: return "Apple Animation";
case kCMVideoCodecType_Cinepak: return "Cinepak";
case kCMVideoCodecType_JPEG: return "JPEG";
case kCMVideoCodecType_JPEG_OpenDML: return "MJPEG - JPEG OpenDML";
case kCMVideoCodecType_SorensonVideo: return "Sorenson Video";
case kCMVideoCodecType_SorensonVideo3: return "Sorenson Video 3";
case kCMVideoCodecType_H263: return "H.263";
case kCMVideoCodecType_H264: return "H.264";
case kCMVideoCodecType_MPEG4Video: return "MPEG-4";
case kCMVideoCodecType_MPEG2Video: return "MPEG-2";
case kCMVideoCodecType_MPEG1Video: return "MPEG-1";
case kCMVideoCodecType_DVCNTSC:
return "DV NTSC";
case kCMVideoCodecType_DVCPAL:
return "DV PAL";
case kCMVideoCodecType_DVCProPAL:
return "Panasonic DVCPro PAL";
case kCMVideoCodecType_DVCPro50NTSC:
return "Panasonic DVCPro-50 NTSC";
case kCMVideoCodecType_DVCPro50PAL:
return "Panasonic DVCPro-50 PAL";
case kCMVideoCodecType_DVCPROHD720p60:
return "Panasonic DVCPro-HD 720p60";
case kCMVideoCodecType_DVCPROHD720p50:
return "Panasonic DVCPro-HD 720p50";
case kCMVideoCodecType_DVCPROHD1080i60:
return "Panasonic DVCPro-HD 1080i60";
case kCMVideoCodecType_DVCPROHD1080i50:
return "Panasonic DVCPro-HD 1080i50";
case kCMVideoCodecType_DVCPROHD1080p30:
return "Panasonic DVCPro-HD 1080p30";
case kCMVideoCodecType_DVCPROHD1080p25:
return "Panasonic DVCPro-HD 1080p25";
case kCMVideoCodecType_AppleProRes4444:
return "Apple ProRes 4444";
case kCMVideoCodecType_AppleProRes422HQ:
return "Apple ProRes 422 HQ";
case kCMVideoCodecType_AppleProRes422:
return "Apple ProRes 422";
case kCMVideoCodecType_AppleProRes422LT:
return "Apple ProRes 422 LT";
case kCMVideoCodecType_AppleProRes422Proxy:
return "Apple ProRes 422 Proxy";
default:
blog(LOG_INFO, "Unknown format %s", AV_FOURCC_STR(fourcc));
return "unknown";
}
}
static inline bool is_fullrange_yuv(FourCharCode pixel_format)
{
switch (pixel_format) {
@ -291,6 +519,17 @@ static const char *av_capture_getname(void*)
return TEXT_AVCAPTURE;
}
static void unlock_device(av_capture *capture, AVCaptureDevice *dev=nullptr)
{
if (!dev)
dev = capture->device;
if (dev && capture->device_locked)
[dev unlockForConfiguration];
capture->device_locked = false;
}
static void start_capture(av_capture *capture)
{
if (capture->session && !capture->session.running)
@ -311,6 +550,8 @@ static void remove_device(av_capture *capture)
[capture->session removeInput:capture->device_input];
unlock_device(capture);
capture->device_input = nullptr;
capture->device = nullptr;
}
@ -453,6 +694,8 @@ static bool init_preset(av_capture *capture, AVCaptureDevice *dev,
{
clear_capture(capture);
unlock_device(capture, dev);
NSString *preset = get_string(settings, "preset");
if (![dev supportsAVCaptureSessionPreset:preset]) {
AVLOG(LOG_WARNING, "Preset %s not available",
@ -473,6 +716,158 @@ static bool init_preset(av_capture *capture, AVCaptureDevice *dev,
return true;
}
static bool operator==(const CMVideoDimensions &a, const CMVideoDimensions &b);
static CMVideoDimensions get_dimensions(AVCaptureDeviceFormat *format);
static AVCaptureDeviceFormat *find_format(AVCaptureDevice *dev,
CMVideoDimensions dims)
{
for (AVCaptureDeviceFormat *format in dev.formats) {
if (get_dimensions(format) == dims)
return format;
}
return nullptr;
}
static CMTime convert(media_frames_per_second fps)
{
CMTime time{};
time.value = fps.denominator;
time.timescale = fps.numerator;
time.flags = 1;
return time;
}
static bool lock_device(av_capture *capture, AVCaptureDevice *dev)
{
if (!dev)
dev = capture->device;
NSError *err;
if (![dev lockForConfiguration:&err]) {
AVLOG(LOG_WARNING, "Could not lock device for configuration: "
"%s", err.localizedDescription.UTF8String);
return false;
}
capture->device_locked = true;
return true;
}
template <typename Func>
static void find_formats(media_frames_per_second fps, AVCaptureDevice *dev,
const CMVideoDimensions *dims, Func &&f)
{
auto time = convert(fps);
for (AVCaptureDeviceFormat *format in dev.formats) {
if (!(get_dimensions(format) == *dims))
continue;
for (AVFrameRateRange *range in
format.videoSupportedFrameRateRanges) {
if (CMTimeCompare(range.maxFrameDuration, time) >= 0 &&
CMTimeCompare(range.minFrameDuration,
time) <= 0)
if (f(format))
return;
}
}
}
static bool init_manual(av_capture *capture, AVCaptureDevice *dev,
obs_data_t *settings)
{
clear_capture(capture);
auto input_format = obs_data_get_int(settings, "input_format");
FourCharCode actual_format = input_format;
SCOPE_EXIT
{
bool refresh = false;
if (input_format != actual_format) {
refresh = obs_data_get_autoselect_int(settings,
"input_format") != actual_format;
obs_data_set_autoselect_int(settings, "input_format",
actual_format);
} else {
refresh = obs_data_has_autoselect_value(settings,
"input_format");
obs_data_unset_autoselect_value(settings,
"input_format");
}
if (refresh)
obs_source_update_properties(capture->source);
};
CMVideoDimensions dims{};
if (!get_resolution(settings, dims)) {
AVLOG(LOG_WARNING, "Could not load resolution");
return false;
}
media_frames_per_second fps{};
if (!obs_data_get_frames_per_second(settings, "frame_rate", &fps,
nullptr)) {
AVLOG(LOG_WARNING, "Could not load frame rate");
return false;
}
AVCaptureDeviceFormat *format = nullptr;
find_formats(fps, dev, &dims, [&](AVCaptureDeviceFormat *format_)
{
auto desc = format_.formatDescription;
auto fourcc = CMFormatDescriptionGetMediaSubType(desc);
if (input_format != INPUT_FORMAT_AUTO && fourcc != input_format)
return false;
actual_format = fourcc;
format = format_;
return true;
});
if (!format) {
AVLOG(LOG_WARNING, "Frame rate is not supported: %g FPS "
"(%u/%u)",
media_frames_per_second_to_fps(fps),
fps.numerator, fps.denominator);
return false;
}
if (!lock_device(capture, dev))
return false;
const char *if_name = input_format == INPUT_FORMAT_AUTO ?
"Auto" : fourcc_subtype_name(input_format);
#define IF_AUTO(x) (input_format != INPUT_FORMAT_AUTO ? "" : x)
AVLOG(LOG_INFO, "Capturing '%s' (%s):\n"
" Resolution: %ux%u\n"
" FPS: %g (%" PRIu32 "/%" PRIu32 ")\n"
" Frame interval: %g" NBSP "s\n"
" Input format: %s%s%s (%s)%s\n"
" Using format: %s",
dev.localizedName.UTF8String, dev.uniqueID.UTF8String,
dims.width, dims.height,
media_frames_per_second_to_fps(fps),
fps.numerator, fps.denominator,
media_frames_per_second_to_frame_interval(fps),
if_name, IF_AUTO(" (actual: "),
IF_AUTO(fourcc_subtype_name(actual_format)),
AV_FOURCC_STR(actual_format), IF_AUTO(")"),
format.description.UTF8String);
#undef IF_AUTO
dev.activeFormat = format;
dev.activeVideoMinFrameDuration = convert(fps);
dev.activeVideoMaxFrameDuration = convert(fps);
return true;
}
static void capture_device(av_capture *capture, AVCaptureDevice *dev,
obs_data_t *settings)
{
@ -484,6 +879,10 @@ static void capture_device(av_capture *capture, AVCaptureDevice *dev,
if (obs_data_get_bool(settings, "use_preset")) {
if (!init_preset(capture, dev, settings))
return;
} else {
if (!init_manual(capture, dev, settings))
return;
}
if (!init_device_input(capture, dev))
@ -662,8 +1061,11 @@ static void av_capture_defaults(obs_data_t *settings)
{
obs_data_set_default_string(settings, "uid", "");
obs_data_set_default_bool(settings, "use_preset", true);
obs_data_set_default_string(settings, "preset",
AVCaptureSessionPreset1280x720.UTF8String);
obs_data_set_default_int(settings, "input_format", INPUT_FORMAT_AUTO);
}
static bool update_device_list(obs_property_t *list,
@ -791,6 +1193,356 @@ static bool autoselect_preset(AVCaptureDevice *dev, obs_data_t *settings)
return false;
}
static CMVideoDimensions get_dimensions(AVCaptureDeviceFormat *format)
{
auto desc = format.formatDescription;
return CMVideoFormatDescriptionGetDimensions(desc);
}
using resolutions_t = vector<CMVideoDimensions>;
static resolutions_t enumerate_resolutions(AVCaptureDevice *dev)
{
resolutions_t res;
if (!dev)
return res;
res.reserve(dev.formats.count + 1);
for (AVCaptureDeviceFormat *format in dev.formats) {
auto dims = get_dimensions(format);
if (find(begin(res), end(res), dims) == end(res))
res.push_back(dims);
}
return res;
}
static void sort_resolutions(vector<CMVideoDimensions> &resolutions)
{
auto cmp = [](const CMVideoDimensions &a, const CMVideoDimensions &b)
{
return a.width * a.height > b.width * b.height;
};
sort(begin(resolutions), end(resolutions), cmp);
}
static void data_set_resolution(obs_data_t *data, const CMVideoDimensions &dims)
{
obs_data_set_int(data, "width", dims.width);
obs_data_set_int(data, "height", dims.height);
}
static void data_set_resolution(const unique_ptr<obs_data_t> &data,
const CMVideoDimensions &dims)
{
data_set_resolution(data.get(), dims);
}
static bool add_resolution_to_list(vector<CMVideoDimensions> &res,
const CMVideoDimensions &dims)
{
if (find(begin(res), end(res), dims) != end(res))
return false;
res.push_back(dims);
return true;
}
static const char *obs_data_get_json(const unique_ptr<obs_data_t> &data)
{
return obs_data_get_json(data.get());
}
static bool operator==(const CMVideoDimensions &a, const CMVideoDimensions &b)
{
return a.width == b.width && a.height == b.height;
}
static bool resolution_property_needs_update(obs_property_t *p,
const resolutions_t &resolutions)
{
vector<bool> res_found(resolutions.size());
auto num = obs_property_list_item_count(p);
for (size_t i = 1; i < num; i++) { // skip empty entry
const char *json = obs_property_list_item_string(p, i);
unique_ptr<obs_data_t> buffer{obs_data_create_from_json(json)};
CMVideoDimensions dims{};
if (!get_resolution(buffer.get(), dims))
return true;
auto pos = find(begin(resolutions), end(resolutions), dims);
if (pos == end(resolutions))
return true;
res_found[pos - begin(resolutions)] = true;
}
return any_of(begin(res_found), end(res_found),
[](bool b) { return !b; });
}
static bool update_resolution_property(obs_properties_t *props,
const config_helper &conf, obs_property_t *p=nullptr)
{
if (!p)
p = obs_properties_get(props, "resolution");
if (!p)
return false;
auto valid_dims = conf.dims();
auto resolutions = enumerate_resolutions(conf.dev());
bool unsupported = true;
if (valid_dims)
unsupported = add_resolution_to_list(resolutions, *valid_dims);
bool was_enabled = obs_property_enabled(p);
obs_property_set_enabled(p, !!conf.dev());
if (!resolution_property_needs_update(p, resolutions))
return was_enabled != obs_property_enabled(p);
sort_resolutions(resolutions);
obs_property_list_clear(p);
obs_property_list_add_string(p, "", "{}");
DStr name;
unique_ptr<obs_data_t> buffer{obs_data_create()};
for (const CMVideoDimensions &dims : resolutions) {
data_set_resolution(buffer, dims);
auto json = obs_data_get_json(buffer);
dstr_printf(name, "%dx%d", dims.width, dims.height);
size_t idx = obs_property_list_add_string(p, name->array, json);
if (unsupported && valid_dims && dims == *valid_dims)
obs_property_list_item_disable(p, idx, true);
}
return true;
}
static media_frames_per_second convert(CMTime time_)
{
media_frames_per_second res{};
clamp(res.numerator, time_.timescale);
clamp(res.denominator, time_.value);
return res;
}
using frame_rates_t = vector<pair<media_frames_per_second,
media_frames_per_second>>;
static frame_rates_t enumerate_frame_rates(AVCaptureDevice *dev,
const CMVideoDimensions *dims = nullptr)
{
frame_rates_t res;
if (!dev || !dims)
return res;
auto add_unique_frame_rate_range = [&](AVFrameRateRange *range)
{
auto min = convert(range.maxFrameDuration);
auto max = convert(range.minFrameDuration);
auto pair = make_pair(min, max);
if (find(begin(res), end(res), pair) != end(res))
return;
res.push_back(pair);
};
for (AVCaptureDeviceFormat *format in dev.formats) {
if (!(get_dimensions(format) == *dims))
continue;
for (AVFrameRateRange *range in
format.videoSupportedFrameRateRanges) {
add_unique_frame_rate_range(range);
//FIXME remove debug true
if (true || CMTimeCompare(range.minFrameDuration,
range.maxFrameDuration) != 0) {
blog(LOG_WARNING, "Got actual frame rate range:"
" %g - %g "
"({%lld, %d} - {%lld, %d})",
range.minFrameRate,
range.maxFrameRate,
range.maxFrameDuration.value,
range.maxFrameDuration.timescale,
range.minFrameDuration.value,
range.minFrameDuration.timescale
);
}
}
}
return res;
}
static bool operator==(const media_frames_per_second &a,
const media_frames_per_second &b)
{
return a.numerator == b.numerator && a.denominator == b.denominator;
}
static bool operator!=(const media_frames_per_second &a,
const media_frames_per_second &b)
{
return !(a == b);
}
static bool frame_rate_property_needs_update(obs_property_t *p,
const frame_rates_t &frame_rates)
{
auto fps_num = frame_rates.size();
auto num = obs_property_frame_rate_fps_ranges_count(p);
if (fps_num != num)
return true;
vector<bool> fps_found(fps_num);
for (size_t i = 0; i < num; i++) {
auto min_ = obs_property_frame_rate_fps_range_min(p, i);
auto max_ = obs_property_frame_rate_fps_range_max(p, i);
auto it = find(begin(frame_rates), end(frame_rates),
make_pair(min_, max_));
if (it == end(frame_rates))
return true;
fps_found[it - begin(frame_rates)] = true;
}
return any_of(begin(fps_found), end(fps_found),
[](bool b) { return !b; });
}
static bool update_frame_rate_property(obs_properties_t *props,
const config_helper &conf, obs_property_t *p=nullptr)
{
if (!p)
p = obs_properties_get(props, "frame_rate");
if (!p)
return false;
auto valid_dims = conf.dims();
auto frame_rates = enumerate_frame_rates(conf.dev(), valid_dims);
bool was_enabled = obs_property_enabled(p);
obs_property_set_enabled(p, !frame_rates.empty());
if (!frame_rate_property_needs_update(p, frame_rates))
return was_enabled != obs_property_enabled(p);
obs_property_frame_rate_fps_ranges_clear(p);
for (auto &pair : frame_rates)
obs_property_frame_rate_fps_range_add(p,
pair.first, pair.second);
return true;
}
static vector<AVCaptureDeviceFormat*> enumerate_formats(AVCaptureDevice *dev,
const CMVideoDimensions &dims,
const media_frames_per_second &fps)
{
vector<AVCaptureDeviceFormat*> result;
find_formats(fps, dev, &dims, [&](AVCaptureDeviceFormat *format)
{
result.push_back(format);
return false;
});
return result;
}
static bool input_format_property_needs_update(obs_property_t *p,
const vector<AVCaptureDeviceFormat*> &formats,
const FourCharCode *fourcc_)
{
bool fourcc_found = !fourcc_;
vector<bool> if_found(formats.size());
auto num = obs_property_list_item_count(p);
for (size_t i = 1; i < num; i++) { // skip auto entry
FourCharCode fourcc = obs_property_list_item_int(p, i);
fourcc_found = fourcc_found || fourcc == *fourcc_;
auto pos = find_if(begin(formats), end(formats),
[&](AVCaptureDeviceFormat *format)
{
FourCharCode fourcc_ = 0;
format_description_subtype_name(
format.formatDescription, &fourcc_);
return fourcc_ == fourcc;
});
if (pos == end(formats))
return true;
if_found[pos - begin(formats)] = true;
}
return fourcc_found || any_of(begin(if_found), end(if_found),
[](bool b) { return !b; });
}
static bool update_input_format_property(obs_properties_t *props,
const config_helper &conf, obs_property_t *p=nullptr)
{
if (!p)
p = obs_properties_get(props, "input_format");
if (!p)
return false;
auto update_enabled = [&](bool enabled)
{
bool was_enabled = obs_property_enabled(p);
obs_property_set_enabled(p, enabled);
return was_enabled != enabled;
};
auto valid_dims = conf.dims();
auto valid_fps = conf.fps();
auto valid_if = conf.input_format();
if (!valid_dims || !valid_fps)
return update_enabled(false);
auto formats = enumerate_formats(conf.dev(), *valid_dims, *valid_fps);
if (!input_format_property_needs_update(p, formats, valid_if))
return update_enabled(!formats.empty());
while (obs_property_list_item_count(p) > 1)
obs_property_list_item_remove(p, 1);
bool fourcc_found = !valid_if || *valid_if == INPUT_FORMAT_AUTO;
for (auto &format : formats) {
FourCharCode fourcc = 0;
const char *name = format_description_subtype_name(
format.formatDescription, &fourcc);
obs_property_list_add_int(p, name, fourcc);
fourcc_found = fourcc_found || fourcc == *valid_if;
}
if (!fourcc_found) {
const char *name = fourcc_subtype_name(*valid_if);
obs_property_list_add_int(p, name, *valid_if);
}
return update_enabled(!formats.empty());
}
static bool properties_device_changed(obs_properties_t *props, obs_property_t *p,
obs_data_t *settings)
{
@ -805,7 +1557,45 @@ static bool properties_device_changed(obs_properties_t *props, obs_property_t *p
bool preset_list_changed = check_preset(dev, p, settings);
bool autoselect_changed = autoselect_preset(dev, settings);
return preset_list_changed || autoselect_changed || dev_list_updated;
config_helper conf{settings};
bool res_changed = update_resolution_property(props, conf);
bool fps_changed = update_frame_rate_property(props, conf);
bool if_changed = update_input_format_property(props, conf);
return preset_list_changed || autoselect_changed || dev_list_updated
|| res_changed || fps_changed || if_changed;
}
static bool properties_use_preset_changed(obs_properties_t *props,
obs_property_t *, obs_data_t *settings)
{
auto use_preset = obs_data_get_bool(settings, "use_preset");
config_helper conf{settings};
bool updated = false;
bool visible = false;
obs_property_t *p = nullptr;
auto noop = [](obs_properties_t *, const config_helper&,
obs_property_t *)
{
return false;
};
#define UPDATE_PROPERTY(prop, uses_preset, func) \
p = obs_properties_get(props, prop); \
visible = use_preset == uses_preset; \
updated = obs_property_visible(p) != visible || updated; \
obs_property_set_visible(p, visible);\
updated = func(props, conf, p) || updated;
UPDATE_PROPERTY("preset", true, noop);
UPDATE_PROPERTY("resolution", false, update_resolution_property);
UPDATE_PROPERTY("frame_rate", false, update_frame_rate_property);
UPDATE_PROPERTY("input_format", false, update_input_format_property);
return updated;
}
static bool properties_preset_changed(obs_properties_t *, obs_property_t *p,
@ -820,6 +1610,37 @@ static bool properties_preset_changed(obs_properties_t *, obs_property_t *p,
return preset_list_changed || autoselect_changed;
}
static bool properties_resolution_changed(obs_properties_t *props,
obs_property_t *p, obs_data_t *settings)
{
config_helper conf{settings};
bool res_updated = update_resolution_property(props, conf, p);
bool fps_updated = update_frame_rate_property(props, conf);
bool if_updated = update_input_format_property(props, conf);
return res_updated || fps_updated || if_updated;
}
static bool properties_frame_rate_changed(obs_properties_t *props,
obs_property_t *p, obs_data_t *settings)
{
config_helper conf{settings};
bool fps_updated = update_frame_rate_property(props, conf, p);
bool if_updated = update_input_format_property(props, conf);
return fps_updated || if_updated;
}
static bool properties_input_format_changed(obs_properties_t *props,
obs_property_t *p, obs_data_t *settings)
{
config_helper conf{settings};
return update_input_format_property(props, conf, p);
}
static void add_preset_properties(obs_properties_t *props)
{
obs_property_t *preset_list = obs_properties_add_list(props, "preset",
@ -834,6 +1655,33 @@ static void add_preset_properties(obs_properties_t *props)
properties_preset_changed);
}
static void add_manual_properties(obs_properties_t *props)
{
obs_property_t *resolutions = obs_properties_add_list(props,
"resolution", TEXT_RESOLUTION, OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
obs_property_set_enabled(resolutions, false);
obs_property_set_modified_callback(resolutions,
properties_resolution_changed);
obs_property_t *frame_rates = obs_properties_add_frame_rate(props,
"frame_rate", TEXT_FRAME_RATE);
/*obs_property_frame_rate_option_add(frame_rates, "match obs",
TEXT_MATCH_OBS);*/
obs_property_set_enabled(frame_rates, false);
obs_property_set_modified_callback(frame_rates,
properties_frame_rate_changed);
obs_property_t *input_format = obs_properties_add_list(props,
"input_format", TEXT_INPUT_FORMAT,
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(input_format, TEXT_AUTO,
INPUT_FORMAT_AUTO);
obs_property_set_enabled(input_format, false);
obs_property_set_modified_callback(input_format,
properties_input_format_changed);
}
static obs_properties_t *av_capture_properties(void*)
{
obs_properties_t *props = obs_properties_create();
@ -854,11 +1702,13 @@ static obs_properties_t *av_capture_properties(void*)
obs_property_t *use_preset = obs_properties_add_bool(props,
"use_preset", TEXT_USE_PRESET);
// TODO: implement manual configuration
obs_property_set_enabled(use_preset, false);
obs_property_set_modified_callback(use_preset,
properties_use_preset_changed);
add_preset_properties(props);
add_manual_properties(props);
obs_properties_add_bool(props, "buffering",
obs_module_text("Buffering"));
@ -893,6 +1743,8 @@ static void switch_device(av_capture *capture, NSString *uid,
static void update_preset(av_capture *capture, obs_data_t *settings)
{
unlock_device(capture);
NSString *preset = get_string(settings, "preset");
if (![capture->device supportsAVCaptureSessionPreset:preset]) {
AVLOG(LOG_WARNING, "Preset %s not available",
@ -906,6 +1758,12 @@ static void update_preset(av_capture *capture, obs_data_t *settings)
start_capture(capture);
}
static void update_manual(av_capture *capture, obs_data_t *settings)
{
if (init_manual(capture, capture->device, settings))
start_capture(capture);
}
static void av_capture_update(void *data, obs_data_t *settings)
{
auto capture = static_cast<av_capture*>(data);
@ -915,8 +1773,11 @@ static void av_capture_update(void *data, obs_data_t *settings)
if (!capture->device || ![capture->device.uniqueID isEqualToString:uid])
return switch_device(capture, uid, settings);
if (obs_data_get_bool(settings, "use_preset"))
if (obs_data_get_bool(settings, "use_preset")) {
update_preset(capture, settings);
} else {
update_manual(capture, settings);
}
av_capture_enable_buffering(capture,
obs_data_get_bool(settings, "buffering"));

View File

@ -3,3 +3,6 @@ Device="Device"
UsePreset="Use Preset"
Preset="Preset"
Buffering="Use Buffering"
FrameRate="Frame rate"
InputFormat="Input format"
Auto="Auto"

View File

@ -0,0 +1,80 @@
/*
* Based on Loki::ScopeGuard
*/
#pragma once
#include <utility>
namespace scope_guard_util {
template <typename FunctionType>
class ScopeGuard {
public:
void dismiss() noexcept
{
dismissed_ = true;
}
explicit ScopeGuard(const FunctionType &fn)
: function_(fn)
{}
explicit ScopeGuard(FunctionType &&fn)
: function_(std::move(fn))
{}
ScopeGuard(ScopeGuard &&other)
: dismissed_(other.dismissed_),
function_(std::move(other.function_))
{
other.dismissed_ = true;
}
~ScopeGuard() noexcept
{
if (!dismissed_)
execute();
}
private:
void* operator new(size_t) = delete;
void execute() noexcept
{
function_();
}
bool dismissed_ = false;
FunctionType function_;
};
template <typename FunctionType>
ScopeGuard<typename std::decay<FunctionType>::type>
make_guard(FunctionType &&fn)
{
return ScopeGuard<typename std::decay<FunctionType>::type>{
std::forward<FunctionType>(fn)};
}
namespace detail {
enum class ScopeGuardOnExit {};
template <typename FunctionType>
ScopeGuard<typename std::decay<FunctionType>::type>
operator+(detail::ScopeGuardOnExit, FunctionType &&fn) {
return ScopeGuard<typename std::decay<FunctionType>::type>(
std::forward<FunctionType>(fn));
}
}
} // namespace scope_guard_util
#define SCOPE_EXIT_CONCAT2(x, y) x ## y
#define SCOPE_EXIT_CONCAT(x, y) SCOPE_EXIT_CONCAT2(x, y)
#define SCOPE_EXIT \
auto SCOPE_EXIT_CONCAT(SCOPE_EXIT_STATE, __LINE__) = \
::scope_guard_util::detail::ScopeGuardOnExit() + [&]() noexcept