Line data Source code
1 : /* 2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc. 3 : * 4 : * This program is free software: you can redistribute it and/or modify 5 : * it under the terms of the GNU General Public License as published by 6 : * the Free Software Foundation, either version 3 of the License, or 7 : * (at your option) any later version. 8 : * 9 : * This program is distributed in the hope that it will be useful, 10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 : * GNU General Public License for more details. 13 : * 14 : * You should have received a copy of the GNU General Public License 15 : * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 : */ 17 : 18 : #include <cassert> 19 : 20 : #pragma GCC diagnostic push 21 : #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 22 : #include <yaml-cpp/yaml.h> 23 : #pragma GCC diagnostic pop 24 : 25 : #include "manager.h" 26 : #include "media_const.h" 27 : #include "client/jami_signal.h" 28 : #include "logger.h" 29 : #include "video_device_monitor.h" 30 : 31 : namespace jami { 32 : namespace video { 33 : 34 : constexpr const char* const VideoDeviceMonitor::CONFIG_LABEL; 35 : 36 : using std::map; 37 : using std::string; 38 : using std::vector; 39 : 40 : vector<string> 41 0 : VideoDeviceMonitor::getDeviceList() const 42 : { 43 0 : std::lock_guard l(lock_); 44 0 : vector<string> ids; 45 0 : ids.reserve(devices_.size()); 46 0 : for (const auto& dev : devices_) { 47 0 : if (dev.name != DEVICE_DESKTOP) 48 0 : ids.emplace_back(dev.getDeviceId()); 49 : } 50 0 : return ids; 51 0 : } 52 : 53 : libjami::VideoCapabilities 54 0 : VideoDeviceMonitor::getCapabilities(const string& id) const 55 : { 56 0 : std::lock_guard l(lock_); 57 0 : const auto iter = findDeviceById(id); 58 0 : if (iter == devices_.end()) 59 0 : return libjami::VideoCapabilities(); 60 : 61 0 : return iter->getCapabilities(); 62 0 : } 63 : 64 : VideoSettings 65 0 : VideoDeviceMonitor::getSettings(const string& id) 66 : { 67 0 : std::lock_guard l(lock_); 68 : 69 0 : const auto prefIter = findPreferencesById(id); 70 0 : if (prefIter == preferences_.end()) 71 0 : return VideoSettings(); 72 : 73 0 : return *prefIter; 74 0 : } 75 : 76 : void 77 0 : VideoDeviceMonitor::applySettings(const string& id, const VideoSettings& settings) 78 : { 79 0 : std::lock_guard l(lock_); 80 0 : const auto iter = findDeviceById(id); 81 : 82 0 : if (iter == devices_.end()) 83 0 : return; 84 : 85 0 : iter->applySettings(settings); 86 0 : auto it = findPreferencesById(settings.unique_id); 87 0 : if (it != preferences_.end()) 88 0 : (*it) = settings; 89 0 : } 90 : 91 : string 92 111 : VideoDeviceMonitor::getDefaultDevice() const 93 : { 94 111 : std::lock_guard l(lock_); 95 111 : const auto it = findDeviceById(defaultDevice_); 96 111 : if (it == std::end(devices_) || it->getDeviceId() == DEVICE_DESKTOP) 97 111 : return {}; 98 0 : return it->getDeviceId(); 99 111 : } 100 : 101 : std::string 102 129 : VideoDeviceMonitor::getMRLForDefaultDevice() const 103 : { 104 129 : std::lock_guard l(lock_); 105 129 : const auto it = findDeviceById(defaultDevice_); 106 129 : if (it == std::end(devices_) || it->getDeviceId() == DEVICE_DESKTOP) 107 129 : return {}; 108 0 : static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR; 109 0 : return libjami::Media::VideoProtocolPrefix::CAMERA + sep + it->getDeviceId(); 110 129 : } 111 : 112 : bool 113 0 : VideoDeviceMonitor::setDefaultDevice(const std::string& id) 114 : { 115 0 : std::lock_guard l(lock_); 116 0 : const auto itDev = findDeviceById(id); 117 0 : if (itDev != devices_.end()) { 118 0 : if (defaultDevice_ == itDev->getDeviceId()) 119 0 : return false; 120 0 : defaultDevice_ = itDev->getDeviceId(); 121 : 122 : // place it at the begining of the prefs 123 0 : auto itPref = findPreferencesById(itDev->getDeviceId()); 124 0 : if (itPref != preferences_.end()) { 125 0 : std::ranges::rotate(preferences_.begin(), itPref, itPref+1); 126 : } else { 127 0 : preferences_.insert(preferences_.begin(), itDev->getSettings()); 128 : } 129 0 : return true; 130 : } 131 0 : return false; 132 0 : } 133 : 134 : void 135 0 : VideoDeviceMonitor::setDeviceOrientation(const std::string& id, int angle) 136 : { 137 0 : std::lock_guard l(lock_); 138 0 : const auto itd = findDeviceById(id); 139 0 : if (itd != devices_.cend()) { 140 0 : itd->setOrientation(angle); 141 : } else { 142 0 : JAMI_WARN("Unable to find device %s to set orientation %d", id.c_str(), angle); 143 : } 144 0 : } 145 : 146 : DeviceParams 147 111 : VideoDeviceMonitor::getDeviceParams(const std::string& id) const 148 : { 149 111 : std::lock_guard l(lock_); 150 111 : const auto itd = findDeviceById(id); 151 111 : if (itd == devices_.cend()) 152 0 : return DeviceParams(); 153 111 : return itd->getDeviceParams(); 154 111 : } 155 : 156 : static void 157 31 : giveUniqueName(VideoDevice& dev, const vector<VideoDevice>& devices) 158 : { 159 31 : std::string suffix; 160 31 : uint64_t number = 2; 161 31 : auto unique = true; 162 0 : for (;; unique = static_cast<bool>(++number)) { 163 31 : for (const auto& s : devices) 164 0 : unique &= static_cast<bool>(s.name.compare(dev.name + suffix)); 165 31 : if (unique) 166 62 : return (void) (dev.name += suffix); 167 0 : suffix = " (" + std::to_string(number) + ")"; 168 0 : } 169 31 : } 170 : 171 : static void 172 31 : notify() 173 : { 174 31 : if (Manager::initialized) { 175 31 : emitSignal<libjami::VideoSignal::DeviceEvent>(); 176 : } 177 31 : } 178 : 179 : bool 180 31 : VideoDeviceMonitor::addDevice(const string& id, const std::vector<std::map<std::string, std::string>>& devInfo) 181 : { 182 : try { 183 31 : std::lock_guard l(lock_); 184 31 : if (findDeviceById(id) != devices_.end()) 185 0 : return false; 186 : 187 : // instantiate a new unique device 188 31 : VideoDevice dev {id, devInfo}; 189 : 190 31 : if (dev.getChannelList().empty()) 191 0 : return false; 192 : 193 31 : giveUniqueName(dev, devices_); 194 : 195 : // restore its preferences if any, or store the defaults 196 31 : auto it = findPreferencesById(id); 197 31 : if (it != preferences_.end()) { 198 0 : dev.applySettings(*it); 199 : } else { 200 31 : dev.applySettings(dev.getDefaultSettings()); 201 31 : preferences_.emplace_back(dev.getSettings()); 202 : } 203 : 204 : // in case there is no default device on a fresh run 205 31 : if (defaultDevice_.empty() && id != DEVICE_DESKTOP) 206 0 : defaultDevice_ = dev.getDeviceId(); 207 : 208 31 : devices_.emplace_back(std::move(dev)); 209 31 : } catch (const std::exception& e) { 210 0 : JAMI_ERR("Failed to add device %s: %s", id.c_str(), e.what()); 211 0 : return false; 212 0 : } 213 31 : notify(); 214 31 : return true; 215 : } 216 : 217 : void 218 0 : VideoDeviceMonitor::removeDevice(const string& id) 219 : { 220 : { 221 0 : std::lock_guard l(lock_); 222 0 : const auto it = findDeviceById(id); 223 0 : if (it == devices_.end()) 224 0 : return; 225 : 226 0 : devices_.erase(it); 227 0 : if (defaultDevice_.find(id) != std::string::npos) { 228 0 : defaultDevice_.clear(); 229 0 : for (const auto& dev : devices_) 230 0 : if (dev.name != DEVICE_DESKTOP) { 231 0 : defaultDevice_ = dev.getDeviceId(); 232 0 : break; 233 : } 234 : } 235 0 : } 236 0 : notify(); 237 : } 238 : 239 : vector<VideoDevice>::iterator 240 87 : VideoDeviceMonitor::findDeviceById(const string& id) 241 : { 242 87 : for (auto it = devices_.begin(); it != devices_.end(); ++it) 243 56 : if (it->getDeviceId().find(id) != std::string::npos) 244 56 : return it; 245 31 : return devices_.end(); 246 : } 247 : 248 : vector<VideoDevice>::const_iterator 249 351 : VideoDeviceMonitor::findDeviceById(const string& id) const 250 : { 251 351 : for (auto it = devices_.cbegin(); it != devices_.cend(); ++it) 252 351 : if (it->getDeviceId().find(id) != std::string::npos) 253 351 : return it; 254 0 : return devices_.end(); 255 : } 256 : 257 : vector<VideoSettings>::iterator 258 59 : VideoDeviceMonitor::findPreferencesById(const string& id) 259 : { 260 59 : for (auto it = preferences_.begin(); it != preferences_.end(); ++it) 261 28 : if (it->unique_id.find(id) != std::string::npos) 262 28 : return it; 263 31 : return preferences_.end(); 264 : } 265 : 266 : void 267 28 : VideoDeviceMonitor::overwritePreferences(const VideoSettings& settings) 268 : { 269 28 : auto it = findPreferencesById(settings.unique_id); 270 28 : if (it != preferences_.end()) 271 28 : preferences_.erase(it); 272 28 : preferences_.emplace_back(settings); 273 28 : } 274 : 275 : void 276 2166 : VideoDeviceMonitor::serialize(YAML::Emitter& out) const 277 : { 278 2166 : std::lock_guard l(lock_); 279 2166 : out << YAML::Key << "devices" << YAML::Value << preferences_; 280 2166 : } 281 : 282 : void 283 28 : VideoDeviceMonitor::unserialize(const YAML::Node& in) 284 : { 285 28 : std::lock_guard l(lock_); 286 28 : const auto& node = in[CONFIG_LABEL]; 287 : 288 : /* load the device list from the "video" YAML section */ 289 28 : const auto& devices = node["devices"]; 290 56 : for (const auto& dev : devices) { 291 28 : VideoSettings pref = dev.as<VideoSettings>(); 292 28 : if (pref.unique_id.empty()) 293 0 : continue; // discard malformed section 294 28 : overwritePreferences(pref); 295 28 : auto itd = findDeviceById(pref.unique_id); 296 28 : if (itd != devices_.end()) 297 28 : itd->applySettings(pref); 298 56 : } 299 : 300 : // Restore the default device if present, or select the first one 301 28 : const string prefId = preferences_.empty() ? "" : preferences_[0].unique_id; 302 28 : const auto devIter = findDeviceById(prefId); 303 28 : if (devIter != devices_.end() && prefId != DEVICE_DESKTOP) { 304 0 : defaultDevice_ = devIter->getDeviceId(); 305 : } else { 306 28 : defaultDevice_.clear(); 307 56 : for (const auto& dev : devices_) 308 28 : if (dev.name != DEVICE_DESKTOP) { 309 0 : defaultDevice_ = dev.getDeviceId(); 310 0 : break; 311 : } 312 : } 313 28 : } 314 : 315 : } // namespace video 316 : } // namespace jami