diff --git a/cscore.gradle b/cscore.gradle index 1b79e2227f..56e4611bec 100644 --- a/cscore.gradle +++ b/cscore.gradle @@ -120,6 +120,29 @@ def cscoreSetupExamplesModel = { project -> } } } + + settings(NativeExecutableSpec) { + if (project.isArm) { + targetPlatform 'arm' + } else { + //targetPlatform 'x86' + targetPlatform 'x64' + } + setupDefines(project, binaries) + sources { + cpp { + source { + srcDir "${rootDir}/examples/settings" + include '**/*.cpp' + } + exportedHeaders { + srcDirs = ["${rootDir}/include", "${rootDir}/wpiutil/include", project.openCvInclude] + include '**/*.h' + } + lib library: 'cscore', linkage: 'static' + } + } + } } } } diff --git a/examples/settings/settings.cpp b/examples/settings/settings.cpp new file mode 100644 index 0000000000..ef3bc420f7 --- /dev/null +++ b/examples/settings/settings.cpp @@ -0,0 +1,95 @@ +#include +#include + +#include "cscore.h" + +#include "llvm/SmallString.h" +#include "llvm/raw_ostream.h" + +int main(int argc, char** argv) { + if (argc < 2) { + llvm::errs() << "Usage: settings camera [prop val] ... -- [prop val]...\n"; + llvm::errs() << " Example: settings 1 brightness 30 raw_contrast 10\n"; + return 1; + } + + int id; + if (llvm::StringRef{argv[1]}.getAsInteger(10, id)) { + llvm::errs() << "Expected number for camera\n"; + return 2; + } + + cs::UsbCamera camera{"usbcam", id}; + + // Set prior to connect + int arg = 2; + llvm::StringRef propName; + for (; arg < argc && llvm::StringRef{argv[arg]} != "--"; ++arg) { + if (propName.empty()) + propName = argv[arg]; + else { + llvm::StringRef propVal{argv[arg]}; + int intVal; + if (propVal.getAsInteger(10, intVal)) + camera.GetProperty(propName).SetString(propVal); + else + camera.GetProperty(propName).Set(intVal); + propName = llvm::StringRef{}; + } + } + if (llvm::StringRef{argv[arg]} == "--") ++arg; + + // Wait to connect + while (!camera.IsConnected()) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + // Set rest + propName = llvm::StringRef{}; + for (; arg < argc; ++arg) { + if (propName.empty()) + propName = argv[arg]; + else { + llvm::StringRef propVal{argv[arg]}; + int intVal; + if (propVal.getAsInteger(10, intVal)) + camera.GetProperty(propName).SetString(propVal); + else + camera.GetProperty(propName).Set(intVal); + propName = llvm::StringRef{}; + } + } + + // Print settings + llvm::SmallString<64> buf; + llvm::outs() << "Properties:\n"; + for (const auto& prop : camera.EnumerateProperties()) { + llvm::outs() << " " << prop.GetName(); + switch (prop.GetKind()) { + case cs::VideoProperty::kBoolean: + llvm::outs() << " (bool): " << "value=" << prop.Get() + << " default=" << prop.GetDefault(); + break; + case cs::VideoProperty::kInteger: + llvm::outs() << " (int): " + << "value=" << prop.Get() << " min=" << prop.GetMin() + << " max=" << prop.GetMax() << " step=" << prop.GetStep() + << " default=" << prop.GetDefault(); + break; + case cs::VideoProperty::kString: + llvm::outs() << " (string): " << prop.GetString(buf); + break; + case cs::VideoProperty::kEnum: { + llvm::outs() << " (enum): " << "value=" << prop.Get(); + auto choices = prop.GetChoices(); + for (size_t i = 0; i < choices.size(); ++i) { + if (choices[i].empty()) continue; + llvm::outs() << "\n " << i << ": " << choices[i]; + } + break; + } + default: + break; + } + llvm::outs() << '\n'; + } +} diff --git a/src/CvSourceImpl.cpp b/src/CvSourceImpl.cpp index d1911c37fc..0cf48f4ca4 100644 --- a/src/CvSourceImpl.cpp +++ b/src/CvSourceImpl.cpp @@ -30,6 +30,11 @@ CvSourceImpl::~CvSourceImpl() {} void CvSourceImpl::Start() {} +std::unique_ptr CvSourceImpl::CreateEmptyProperty( + llvm::StringRef name) const { + return llvm::make_unique(name); +} + bool CvSourceImpl::CacheProperties(CS_Status* status) const { // Doesn't need to do anything. m_properties_cached = true; @@ -43,15 +48,20 @@ void CvSourceImpl::SetProperty(int property, int value, CS_Status* status) { *status = CS_INVALID_PROPERTY; return; } + + // Guess it's integer if we've set before get + if (prop->propKind == CS_PROP_NONE) prop->propKind = CS_PROP_INTEGER; + if ((prop->propKind & (CS_PROP_BOOLEAN | CS_PROP_INTEGER | CS_PROP_ENUM)) == 0) { *status = CS_WRONG_PROPERTY_TYPE; return; } - prop->value = value; - Notifier::GetInstance().NotifySourceProperty( - *this, CS_SOURCE_PROPERTY_VALUE_UPDATED, property, prop->propKind, - prop->value, prop->valueStr); + prop->SetValue(value); + if (m_properties_cached) + Notifier::GetInstance().NotifySourceProperty( + *this, CS_SOURCE_PROPERTY_VALUE_UPDATED, property, prop->propKind, + prop->value, prop->valueStr); } void CvSourceImpl::SetStringProperty(int property, llvm::StringRef value, @@ -62,14 +72,19 @@ void CvSourceImpl::SetStringProperty(int property, llvm::StringRef value, *status = CS_INVALID_PROPERTY; return; } + + // Guess it's string if we've set before get + if (prop->propKind == CS_PROP_NONE) prop->propKind = CS_PROP_STRING; + if (prop->propKind != CS_PROP_STRING) { *status = CS_WRONG_PROPERTY_TYPE; return; } - prop->valueStr = value; - Notifier::GetInstance().NotifySourceProperty( - *this, CS_SOURCE_PROPERTY_VALUE_UPDATED, property, CS_PROP_STRING, - prop->value, prop->valueStr); + prop->SetValue(value); + if (m_properties_cached) + Notifier::GetInstance().NotifySourceProperty( + *this, CS_SOURCE_PROPERTY_VALUE_UPDATED, property, CS_PROP_STRING, + prop->value, prop->valueStr); } bool CvSourceImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) { diff --git a/src/CvSourceImpl.h b/src/CvSourceImpl.h index 166afa566e..d9e9535e46 100644 --- a/src/CvSourceImpl.h +++ b/src/CvSourceImpl.h @@ -48,16 +48,24 @@ class CvSourceImpl : public SourceImpl { class PropertyData : public PropertyBase { public: PropertyData() = default; + PropertyData(llvm::StringRef name_) : PropertyBase{name_} {} PropertyData(llvm::StringRef name_, CS_PropertyKind kind_, int minimum_, int maximum_, int step_, int defaultValue_, int value_) - : PropertyBase{name_, kind_, minimum_, maximum_, - step_, defaultValue_, value_} {} + : PropertyBase{name_, kind_, step_, defaultValue_, value_} { + hasMinimum = true; + minimum = minimum_; + hasMaximum = true; + maximum = maximum_; + } ~PropertyData() override = default; std::function onChange; }; protected: + std::unique_ptr CreateEmptyProperty( + llvm::StringRef name) const override; + bool CacheProperties(CS_Status* status) const override; private: diff --git a/src/SourceImpl.cpp b/src/SourceImpl.cpp index effabe4186..824ec0b2f1 100644 --- a/src/SourceImpl.cpp +++ b/src/SourceImpl.cpp @@ -90,7 +90,7 @@ int SourceImpl::GetPropertyIndex(llvm::StringRef name) const { if (ndx == 0) { // create a new index ndx = m_propertyData.size() + 1; - m_propertyData.emplace_back(); + m_propertyData.emplace_back(CreateEmptyProperty(name)); } return ndx; } diff --git a/src/SourceImpl.h b/src/SourceImpl.h index 0d0e87d2a5..4f8ff9e4b2 100644 --- a/src/SourceImpl.h +++ b/src/SourceImpl.h @@ -139,12 +139,11 @@ class SourceImpl { class PropertyBase { public: PropertyBase() = default; - PropertyBase(llvm::StringRef name_, CS_PropertyKind kind_, int minimum_, - int maximum_, int step_, int defaultValue_, int value_) + PropertyBase(llvm::StringRef name_) : name{name_} {} + PropertyBase(llvm::StringRef name_, CS_PropertyKind kind_, int step_, + int defaultValue_, int value_) : name{name_}, propKind{kind_}, - minimum{minimum_}, - maximum{maximum_}, step{step_}, defaultValue{defaultValue_}, value{value_} {} @@ -152,12 +151,38 @@ class SourceImpl { PropertyBase(const PropertyBase& oth) = delete; PropertyBase& operator=(const PropertyBase& oth) = delete; + void SetValue(int v) { + if (hasMinimum && v < minimum) + value = minimum; + else if (hasMaximum && v > maximum) + value = maximum; + else + value = v; + valueSet = true; + } + + void SetValue(llvm::StringRef v) { + valueStr = v; + valueSet = true; + } + + void SetDefaultValue(int v) { + if (hasMinimum && v < minimum) + defaultValue = minimum; + else if (hasMaximum && v > maximum) + defaultValue = maximum; + else + defaultValue = v; + } + std::string name; CS_PropertyKind propKind{CS_PROP_NONE}; - int minimum; - int maximum; - int step; - int defaultValue; + bool hasMinimum{false}; + bool hasMaximum{false}; + int minimum{0}; + int maximum{100}; + int step{1}; + int defaultValue{0}; int value{0}; std::string valueStr; std::vector enumChoices; @@ -176,6 +201,12 @@ class SourceImpl { return m_propertyData[property - 1].get(); } + // Create an "empty" property. This is called by GetPropertyIndex to create + // properties that don't exist (as GetPropertyIndex can't fail). + // Note: called with m_mutex held. + virtual std::unique_ptr CreateEmptyProperty( + llvm::StringRef name) const = 0; + // Cache properties. Implementations must return false and set status to // CS_SOURCE_IS_DISCONNECTED if not possible to cache. virtual bool CacheProperties(CS_Status* status) const = 0; diff --git a/src/UsbCameraImpl.cpp b/src/UsbCameraImpl.cpp index c34753f646..9149eef5a9 100644 --- a/src/UsbCameraImpl.cpp +++ b/src/UsbCameraImpl.cpp @@ -57,19 +57,37 @@ static inline struct v4l2_fract FPSToFract(int fps) { } // Conversion from v4l2_format pixelformat to VideoMode::PixelFormat -static VideoMode::PixelFormat ToPixelFormat(__u32 pixelformat) { - switch (pixelformat) { +static VideoMode::PixelFormat ToPixelFormat(__u32 pixelFormat) { + switch (pixelFormat) { case V4L2_PIX_FMT_MJPEG: return VideoMode::kMJPEG; case V4L2_PIX_FMT_YUYV: return VideoMode::kYUYV; case V4L2_PIX_FMT_RGB565: return VideoMode::kRGB565; + case V4L2_PIX_FMT_BGR24: + return VideoMode::kBGR; default: return VideoMode::kUnknown; } } +// Conversion from VideoMode::PixelFormat to v4l2_format pixelformat +static __u32 FromPixelFormat(VideoMode::PixelFormat pixelFormat) { + switch (pixelFormat) { + case VideoMode::kMJPEG: + return V4L2_PIX_FMT_MJPEG; + case VideoMode::kYUYV: + return V4L2_PIX_FMT_YUYV; + case VideoMode::kRGB565: + return V4L2_PIX_FMT_RGB565; + case VideoMode::kBGR: + return V4L2_PIX_FMT_BGR24; + default: + return 0; + } +} + // Removes non-alphanumeric characters and replaces spaces with underscores. // e.g. "Zoom, Absolute" -> "zoom_absolute", "Pan (Absolute)" -> "pan_absolute" static llvm::StringRef NormalizeName(llvm::StringRef name, @@ -86,13 +104,53 @@ static llvm::StringRef NormalizeName(llvm::StringRef name, return llvm::StringRef(buf.data(), buf.size()); } +static bool IsPercentageProperty(llvm::StringRef name) { + if (name.startswith("raw_")) name = name.substr(4); + return name == "brightness" || name == "contrast" || name == "saturation" || + name == "hue" || name == "sharpness" || name == "gain" || + name == "exposure_absolute"; +} + +int UsbCameraImpl::RawToPercentage(const PropertyData& rawProp, int rawValue) { + return 100.0 * (rawValue - rawProp.minimum) / + (rawProp.maximum - rawProp.minimum); +} + +int UsbCameraImpl::PercentageToRaw(const PropertyData& rawProp, + int percentValue) { + return rawProp.minimum + + (rawProp.maximum - rawProp.minimum) * (percentValue / 100.0); +} + +void UsbCameraImpl::UpdatePropertyValue(int property, bool setString, int value, + llvm::StringRef valueStr) { + auto prop = static_cast(GetProperty(property)); + if (!prop) return; + + if (setString) + prop->SetValue(valueStr); + else + prop->SetValue(value); + + // Only notify updates after we've notified created + if (m_properties_cached) + Notifier::GetInstance().NotifySourceProperty( + *this, CS_SOURCE_PROPERTY_VALUE_UPDATED, property, prop->propKind, + prop->value, prop->valueStr); +} + #ifdef VIDIOC_QUERY_EXT_CTRL UsbCameraImpl::PropertyData::PropertyData( const struct v4l2_query_ext_ctrl& ctrl) - : PropertyBase(llvm::StringRef{}, CS_PROP_NONE, ctrl.minimum, ctrl.maximum, - ctrl.step, ctrl.default_value, 0), + : PropertyBase(llvm::StringRef{}, CS_PROP_NONE, ctrl.step, + ctrl.default_value, 0), id(ctrl.id & V4L2_CTRL_ID_MASK), type(ctrl.type) { + hasMinimum = true; + minimum = ctrl.minimum; + hasMaximum = true; + maximum = ctrl.maximum; + // propKind switch (ctrl.type) { case V4L2_CTRL_TYPE_INTEGER: @@ -122,10 +180,15 @@ UsbCameraImpl::PropertyData::PropertyData( #endif UsbCameraImpl::PropertyData::PropertyData(const struct v4l2_queryctrl& ctrl) - : PropertyBase(llvm::StringRef{}, CS_PROP_NONE, ctrl.minimum, ctrl.maximum, - ctrl.step, ctrl.default_value, 0), + : PropertyBase(llvm::StringRef{}, CS_PROP_NONE, ctrl.step, + ctrl.default_value, 0), id(ctrl.id & V4L2_CTRL_ID_MASK), type(ctrl.type) { + hasMinimum = true; + minimum = ctrl.minimum; + hasMaximum = true; + maximum = ctrl.maximum; + // propKind switch (ctrl.type) { case V4L2_CTRL_TYPE_INTEGER: @@ -647,9 +710,10 @@ void UsbCameraImpl::DeviceConnect() { SDEBUG3("restoring settings"); std::unique_lock lock2(m_mutex); for (std::size_t i = 0; i < m_propertyData.size(); ++i) { - const auto& prop = m_propertyData[i]; - if (!prop || !prop->valueSet) continue; - if (!DeviceSetProperty(lock2, static_cast(*prop))) + const auto prop = + static_cast(m_propertyData[i].get()); + if (!prop || !prop->valueSet || prop->percentage) continue; + if (!DeviceSetProperty(lock2, *prop)) SWARNING("failed to set property " << prop->name); } } @@ -820,66 +884,17 @@ void UsbCameraImpl::DeviceProcessCommands() { } } else if (msg->kind == Message::kCmdSetProperty || msg->kind == Message::kCmdSetPropertyStr) { + bool setString = (msg->kind == Message::kCmdSetPropertyStr); int property = msg->data[0]; - - // Look up - auto prop = static_cast(GetProperty(property)); - if (!prop) { + int value = msg->data[1]; + CS_StatusValue status = + DeviceCmdSetProperty(lock, property, setString, value, msg->dataStr); + if (status == CS_OK) { + msg->kind = Message::kOk; + } else { msg->kind = Message::kError; - msg->data[0] = CS_INVALID_PROPERTY; - goto done; + msg->data[0] = status; } - - // Check kind match - if ((msg->kind == Message::kCmdSetPropertyStr && - prop->propKind != CS_PROP_STRING) || - (msg->kind == Message::kCmdSetProperty && - (prop->propKind & - (CS_PROP_BOOLEAN | CS_PROP_INTEGER | CS_PROP_ENUM)) == 0)) { - msg->kind = Message::kError; - msg->data[0] = CS_WRONG_PROPERTY_TYPE; - goto done; - } - - // If we're connected... - int fd = m_fd.load(); - if (fd >= 0) { - // Get into local variables before we release the lock - int rv; - unsigned id = prop->id; - int maximum = prop->maximum; - int type = prop->type; - - // Set the property value on the device - lock.unlock(); - if (msg->kind == Message::kCmdSetPropertyStr) - rv = SetStringCtrlIoctl(fd, id, maximum, msg->dataStr); - else - rv = - SetIntCtrlIoctl(fd, id, type, static_cast(msg->data[1])); - lock.lock(); - - if (rv < 0) { - msg->kind = Message::kError; - msg->data[0] = CS_PROPERTY_WRITE_FAILED; - goto done; - } - } - - // Cache the set value. We need to re-get the pointer due to - // releasing the lock. - prop = static_cast(GetProperty(property)); - if (msg->kind == Message::kCmdSetPropertyStr) - prop->valueStr = msg->dataStr; - else - prop->value = msg->data[1]; - prop->valueSet = true; - // Only notify updates after we've notified created - if (m_properties_cached) - Notifier::GetInstance().NotifySourceProperty( - *this, CS_SOURCE_PROPERTY_VALUE_UPDATED, property, prop->propKind, - prop->value, prop->valueStr); - msg->kind = Message::kOk; } else if (msg->kind == Message::kNumSinksChanged || msg->kind == Message::kNumSinksEnabledChanged) { // These are send-only messages, so recycle here. DestroyMessage needs @@ -893,7 +908,6 @@ void UsbCameraImpl::DeviceProcessCommands() { msg->kind = Message::kNone; } -done: if (msg) m_responses.emplace_back(std::move(msg)); } lock.unlock(); @@ -912,21 +926,12 @@ void UsbCameraImpl::DeviceSetMode() { : 0; #endif vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - switch (m_mode.pixelFormat) { - case VideoMode::kMJPEG: - vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; - break; - case VideoMode::kYUYV: - vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; - break; - case VideoMode::kRGB565: - vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565; - break; - default: - SWARNING("could not set format " << m_mode.pixelFormat - << ", defaulting to MJPEG"); - vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; - break; + vfmt.fmt.pix.pixelformat = + FromPixelFormat(static_cast(m_mode.pixelFormat)); + if (vfmt.fmt.pix.pixelformat == 0) { + SWARNING("could not set format " << m_mode.pixelFormat + << ", defaulting to MJPEG"); + vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; } vfmt.fmt.pix.width = m_mode.width; vfmt.fmt.pix.height = m_mode.height; @@ -977,21 +982,7 @@ void UsbCameraImpl::DeviceCacheMode() { m_mode = VideoMode{VideoMode::kMJPEG, 320, 240, 30}; return; } - VideoMode::PixelFormat pixelFormat; - switch (vfmt.fmt.pix.pixelformat) { - case V4L2_PIX_FMT_MJPEG: - pixelFormat = VideoMode::kMJPEG; - break; - case V4L2_PIX_FMT_YUYV: - pixelFormat = VideoMode::kYUYV; - break; - case V4L2_PIX_FMT_RGB565: - pixelFormat = VideoMode::kRGB565; - break; - default: - pixelFormat = VideoMode::kUnknown; - break; - } + VideoMode::PixelFormat pixelFormat = ToPixelFormat(vfmt.fmt.pix.pixelformat); int width = vfmt.fmt.pix.width; int height = vfmt.fmt.pix.height; @@ -1066,49 +1057,118 @@ void UsbCameraImpl::DeviceCacheMode() { Notifier::GetInstance().NotifySource(*this, CS_SOURCE_VIDEOMODE_CHANGED); } -void UsbCameraImpl::DeviceCacheProperty(std::unique_ptr prop) { - std::unique_lock lock(m_mutex); - int& ndx = m_properties[prop->name]; - if (ndx == 0) { - // get the value - lock.unlock(); - if (!DeviceGetProperty(prop.get())) - SWARNING("failed to get property " << prop->name); - lock.lock(); - // create a new index - ndx = m_propertyData.size() + 1; - m_propertyData.emplace_back(std::move(prop)); - } else { - // merge with existing settings - auto prop2 = static_cast(GetProperty(ndx)); - prop->valueSet = prop2->valueSet; - prop->value = prop2->value; - prop->valueStr = std::move(prop2->valueStr); - lock.unlock(); - if (prop->valueSet) { - // set the value if it was previously set - if (!DeviceSetProperty(lock, *prop)) - SWARNING("failed to set property " << prop->name); - } else { - // otherwise get the value - if (!DeviceGetProperty(prop.get())) - SWARNING("failed to get property " << prop->name); - } - lock.lock(); - m_propertyData[ndx - 1] = std::move(prop); - } - auto eventProp = static_cast(GetProperty(ndx)); +void UsbCameraImpl::NotifyPropertyCreated(int propIndex, PropertyData& prop) { auto& notifier = Notifier::GetInstance(); - notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, ndx, - eventProp->propKind, eventProp->value, - eventProp->valueStr); + notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, propIndex, + prop.propKind, prop.value, prop.valueStr); // also notify choices updated event for enum types - if (eventProp->propKind == CS_PROP_ENUM) + if (prop.propKind == CS_PROP_ENUM) notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED, - ndx, eventProp->propKind, eventProp->value, + propIndex, prop.propKind, prop.value, llvm::StringRef{}); } +void UsbCameraImpl::DeviceCacheProperty(std::unique_ptr rawProp) { + // For percentage properties, we want to cache both the raw and the + // percentage versions. This function is always called with prop being + // the raw property (as it's coming from the camera) so if required, we need + // to rename this one as well as create/cache the percentage version. + // + // This is complicated by the fact that either the percentage version or the + // the raw version may have been set previously. If both were previously set, + // the raw version wins. + std::unique_ptr perProp; + if (IsPercentageProperty(rawProp->name)) { + perProp = llvm::make_unique(rawProp->name, 0, *rawProp, 0, 0); + rawProp->name = "raw_" + perProp->name; + } + + std::unique_lock lock(m_mutex); + int* rawIndex = &m_properties[rawProp->name]; + bool newRaw = *rawIndex == 0; + PropertyData* oldRawProp = + newRaw ? nullptr : static_cast(GetProperty(*rawIndex)); + + int* perIndex = perProp ? &m_properties[perProp->name] : nullptr; + bool newPer = !perIndex || *perIndex == 0; + PropertyData* oldPerProp = + newPer ? nullptr : static_cast(GetProperty(*perIndex)); + + if (oldRawProp && oldRawProp->valueSet) { + // Merge existing raw setting and set percentage from it + rawProp->SetValue(oldRawProp->value); + rawProp->valueStr = std::move(oldRawProp->valueStr); + + if (perProp) { + perProp->SetValue(RawToPercentage(*rawProp, rawProp->value)); + perProp->valueStr = rawProp->valueStr; // copy + } + } else if (oldPerProp && oldPerProp->valueSet) { + // Merge existing percentage setting and set raw from it + perProp->SetValue(oldPerProp->value); + perProp->valueStr = std::move(oldPerProp->valueStr); + + rawProp->SetValue(PercentageToRaw(*rawProp, perProp->value)); + rawProp->valueStr = perProp->valueStr; // copy + } else { + // Read current raw value and set percentage from it + lock.unlock(); + if (!DeviceGetProperty(rawProp.get())) + SWARNING("failed to get property " << rawProp->name); + lock.lock(); + + if (perProp) { + perProp->SetValue(RawToPercentage(*rawProp, rawProp->value)); + perProp->valueStr = rawProp->valueStr; // copy + } + } + + // Set value on device if user-configured + if (rawProp->valueSet) { + if (!DeviceSetProperty(lock, *rawProp)) + SWARNING("failed to set property " << rawProp->name); + } + + // Update pointers since we released the lock + rawIndex = &m_properties[rawProp->name]; + perIndex = perProp ? &m_properties[perProp->name] : nullptr; + + // Get pointers before we move the std::unique_ptr values + auto rawPropPtr = rawProp.get(); + auto perPropPtr = perProp.get(); + + if (newRaw) { + // create a new index + *rawIndex = m_propertyData.size() + 1; + m_propertyData.emplace_back(std::move(rawProp)); + } else { + // update + m_propertyData[*rawIndex - 1] = std::move(rawProp); + } + + // Finish setting up percentage property + if (perProp) { + perProp->propPair = *rawIndex; + perProp->defaultValue = + RawToPercentage(*rawPropPtr, rawPropPtr->defaultValue); + + if (newPer) { + // create a new index + *perIndex = m_propertyData.size() + 1; + m_propertyData.emplace_back(std::move(perProp)); + } else if (perIndex) { + // update + m_propertyData[*perIndex - 1] = std::move(perProp); + } + + // Tell raw property where to find percentage property + rawPropPtr->propPair = *perIndex; + } + + NotifyPropertyCreated(*rawIndex, *rawPropPtr); + if (perPropPtr) NotifyPropertyCreated(*perIndex, *perPropPtr); +} + void UsbCameraImpl::DeviceCacheProperties() { int fd = m_fd.load(); if (fd < 0) return; @@ -1213,6 +1273,14 @@ bool UsbCameraImpl::DeviceGetProperty(PropertyData* prop) { bool UsbCameraImpl::DeviceSetProperty(std::unique_lock& lock, const PropertyData& prop) { + // Make a copy of the string as we're about to release the lock + llvm::SmallString<128> valueStr{prop.valueStr}; + return DeviceSetProperty(lock, prop, prop.value, valueStr); +} + +bool UsbCameraImpl::DeviceSetProperty(std::unique_lock& lock, + const PropertyData& prop, int value, + llvm::StringRef valueStr) { int fd = m_fd.load(); if (fd < 0) return true; unsigned id = prop.id; @@ -1224,7 +1292,6 @@ bool UsbCameraImpl::DeviceSetProperty(std::unique_lock& lock, case CS_PROP_ENUM: { int type = prop.type; - int value = prop.value; lock.unlock(); rv = SetIntCtrlIoctl(fd, id, type, value); lock.lock(); @@ -1233,7 +1300,6 @@ bool UsbCameraImpl::DeviceSetProperty(std::unique_lock& lock, case CS_PROP_STRING: { int maximum = prop.maximum; - llvm::SmallString<128> valueStr{prop.valueStr}; lock.unlock(); rv = SetStringCtrlIoctl(fd, id, maximum, valueStr); lock.lock(); @@ -1246,6 +1312,54 @@ bool UsbCameraImpl::DeviceSetProperty(std::unique_lock& lock, return rv >= 0; } +CS_StatusValue UsbCameraImpl::DeviceCmdSetProperty( + std::unique_lock& lock, int property, bool setString, int value, + llvm::StringRef valueStr) { + // Look up + auto prop = static_cast(GetProperty(property)); + if (!prop) return CS_INVALID_PROPERTY; + + // If setting before we get, guess initial type based on set + if (prop->propKind == CS_PROP_NONE) { + if (setString) + prop->propKind = CS_PROP_STRING; + else + prop->propKind = CS_PROP_INTEGER; + } + + // Check kind match + if ((setString && prop->propKind != CS_PROP_STRING) || + (!setString && + (prop->propKind & (CS_PROP_BOOLEAN | CS_PROP_INTEGER | CS_PROP_ENUM)) == + 0)) + return CS_WRONG_PROPERTY_TYPE; + + // Handle percentage property + int percentageProperty = prop->propPair; + int percentageValue = value; + if (percentageProperty != 0) { + if (prop->percentage) { + std::swap(percentageProperty, property); + prop = static_cast(GetProperty(property)); + value = PercentageToRaw(*prop, percentageValue); + } else { + percentageValue = RawToPercentage(*prop, value); + } + } + + // Actually set the new value on the device (if possible) + if (!DeviceSetProperty(lock, *prop, value, valueStr)) + return CS_PROPERTY_WRITE_FAILED; + + // Cache the set values + UpdatePropertyValue(property, setString, value, valueStr); + if (percentageProperty != 0) + UpdatePropertyValue(percentageProperty, setString, percentageValue, + valueStr); + + return CS_OK; +} + std::unique_ptr UsbCameraImpl::SendAndWait( std::unique_ptr msg) const { int fd = m_command_fd.load(); @@ -1303,6 +1417,11 @@ void UsbCameraImpl::Send(std::unique_ptr msg) const { eventfd_write(fd, 1); } +std::unique_ptr UsbCameraImpl::CreateEmptyProperty( + llvm::StringRef name) const { + return llvm::make_unique(name); +} + bool UsbCameraImpl::CacheProperties(CS_Status* status) const { // Wake up camera thread; this will try to reconnect auto msg = CreateMessage(Message::kNone); diff --git a/src/UsbCameraImpl.h b/src/UsbCameraImpl.h index da44196573..01dc3644eb 100644 --- a/src/UsbCameraImpl.h +++ b/src/UsbCameraImpl.h @@ -53,6 +53,22 @@ class UsbCameraImpl : public SourceImpl { class PropertyData : public PropertyBase { public: PropertyData() = default; + PropertyData(llvm::StringRef name_) : PropertyBase{name_} {} + + // Normalized property constructor + PropertyData(llvm::StringRef name_, int rawIndex_, + const PropertyData& rawProp, int defaultValue_, int value_) + : PropertyBase(name_, rawProp.propKind, 1, defaultValue_, value_), + percentage{true}, + propPair{rawIndex_}, + id{rawProp.id}, + type{rawProp.type} { + hasMinimum = true; + minimum = 0; + hasMaximum = true; + maximum = 100; + } + #ifdef __linux__ #ifdef VIDIOC_QUERY_EXT_CTRL PropertyData(const struct v4l2_query_ext_ctrl& ctrl); @@ -60,8 +76,14 @@ class UsbCameraImpl : public SourceImpl { PropertyData(const struct v4l2_queryctrl& ctrl); #endif - unsigned id; // implementation-level id - int type; // implementation type, not CS_PropertyKind! + // If this is a percentage (rather than raw) property + bool percentage{false}; + + // If not 0, index of corresponding raw/percentage property + int propPair{0}; + + unsigned id{0}; // implementation-level id + int type{0}; // implementation type, not CS_PropertyKind! }; // Messages passed to/from camera thread @@ -89,6 +111,9 @@ class UsbCameraImpl : public SourceImpl { }; protected: + std::unique_ptr CreateEmptyProperty( + llvm::StringRef name) const override; + // Cache properties. Immediately successful if properties are already cached. // If they are not, tries to connect to the camera to do so; returns false and // sets status to CS_SOURCE_IS_DISCONNECTED if that too fails. @@ -126,12 +151,27 @@ class UsbCameraImpl : public SourceImpl { void DeviceSetMode(); void DeviceSetFPS(); void DeviceCacheMode(); - void DeviceCacheProperty(std::unique_ptr prop); + void DeviceCacheProperty(std::unique_ptr rawProp); void DeviceCacheProperties(); void DeviceCacheVideoModes(); bool DeviceGetProperty(PropertyData* prop); bool DeviceSetProperty(std::unique_lock& lock, const PropertyData& prop); + bool DeviceSetProperty(std::unique_lock& lock, + const PropertyData& prop, int value, + llvm::StringRef valueStr); + + // Command helper functions + CS_StatusValue DeviceCmdSetProperty(std::unique_lock& lock, + int property, bool setString, int value, + llvm::StringRef valueStr); + + // Property helper functions + int RawToPercentage(const PropertyData& rawProp, int rawValue); + int PercentageToRaw(const PropertyData& rawProp, int percentValue); + void UpdatePropertyValue(int property, bool setString, int value, + llvm::StringRef valueStr); + void NotifyPropertyCreated(int propIndex, PropertyData& prop); // // Variables only used within camera thread