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 1 : VideoDeviceMonitor::getDeviceList() const 42 : { 43 1 : std::lock_guard l(lock_); 44 1 : vector<string> ids; 45 1 : ids.reserve(devices_.size()); 46 2 : for (const auto& dev : devices_) { 47 1 : if (dev.name != DEVICE_DESKTOP) 48 0 : ids.emplace_back(dev.getDeviceId()); 49 : } 50 2 : return ids; 51 1 : } 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 297 : VideoDeviceMonitor::getDefaultDevice() const 93 : { 94 297 : std::lock_guard l(lock_); 95 297 : const auto it = findDeviceById(defaultDevice_); 96 297 : if (it == std::end(devices_) || it->getDeviceId() == DEVICE_DESKTOP) 97 297 : return {}; 98 0 : return it->getDeviceId(); 99 297 : } 100 : 101 : std::string 102 356 : VideoDeviceMonitor::getMRLForDefaultDevice() const 103 : { 104 356 : std::lock_guard l(lock_); 105 356 : const auto it = findDeviceById(defaultDevice_); 106 356 : if (it == std::end(devices_) || it->getDeviceId() == DEVICE_DESKTOP) 107 356 : return {}; 108 0 : static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR; 109 0 : return libjami::Media::VideoProtocolPrefix::CAMERA + sep + it->getDeviceId(); 110 356 : } 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 297 : VideoDeviceMonitor::getDeviceParams(const std::string& id) const 148 : { 149 297 : std::lock_guard l(lock_); 150 297 : const auto itd = findDeviceById(id); 151 297 : if (itd == devices_.cend()) 152 0 : return DeviceParams(); 153 297 : return itd->getDeviceParams(); 154 297 : } 155 : 156 : static void 157 32 : giveUniqueName(VideoDevice& dev, const vector<VideoDevice>& devices) 158 : { 159 32 : std::string suffix; 160 32 : uint64_t number = 2; 161 32 : auto unique = true; 162 0 : for (;; unique = static_cast<bool>(++number)) { 163 32 : for (const auto& s : devices) 164 0 : unique &= static_cast<bool>(s.name.compare(dev.name + suffix)); 165 32 : if (unique) 166 64 : return (void) (dev.name += suffix); 167 0 : suffix = " (" + std::to_string(number) + ")"; 168 0 : } 169 32 : } 170 : 171 : static void 172 32 : notify() 173 : { 174 32 : if (Manager::initialized) { 175 32 : emitSignal<libjami::VideoSignal::DeviceEvent>(); 176 : } 177 32 : } 178 : 179 : bool 180 32 : VideoDeviceMonitor::addDevice(const string& id, const std::vector<std::map<std::string, std::string>>& devInfo) 181 : { 182 : try { 183 32 : std::lock_guard l(lock_); 184 32 : if (findDeviceById(id) != devices_.end()) 185 0 : return false; 186 : 187 : // instantiate a new unique device 188 32 : VideoDevice dev {id, devInfo}; 189 : 190 32 : if (dev.getChannelList().empty()) 191 0 : return false; 192 : 193 32 : giveUniqueName(dev, devices_); 194 : 195 : // restore its preferences if any, or store the defaults 196 32 : auto it = findPreferencesById(id); 197 32 : if (it != preferences_.end()) { 198 0 : dev.applySettings(*it); 199 : } else { 200 32 : dev.applySettings(dev.getDefaultSettings()); 201 32 : preferences_.emplace_back(dev.getSettings()); 202 : } 203 : 204 : // in case there is no default device on a fresh run 205 32 : if (defaultDevice_.empty() && id != DEVICE_DESKTOP) 206 0 : defaultDevice_ = dev.getDeviceId(); 207 : 208 32 : devices_.emplace_back(std::move(dev)); 209 32 : } 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 32 : notify(); 214 32 : 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 90 : VideoDeviceMonitor::findDeviceById(const string& id) 241 : { 242 90 : for (auto it = devices_.begin(); it != devices_.end(); ++it) 243 58 : if (it->getDeviceId().find(id) != std::string::npos) 244 58 : return it; 245 32 : return devices_.end(); 246 : } 247 : 248 : vector<VideoDevice>::const_iterator 249 950 : VideoDeviceMonitor::findDeviceById(const string& id) const 250 : { 251 950 : for (auto it = devices_.cbegin(); it != devices_.cend(); ++it) 252 950 : if (it->getDeviceId().find(id) != std::string::npos) 253 950 : return it; 254 0 : return devices_.end(); 255 : } 256 : 257 : vector<VideoSettings>::iterator 258 61 : VideoDeviceMonitor::findPreferencesById(const string& id) 259 : { 260 61 : for (auto it = preferences_.begin(); it != preferences_.end(); ++it) 261 29 : if (it->unique_id.find(id) != std::string::npos) 262 29 : return it; 263 32 : return preferences_.end(); 264 : } 265 : 266 : void 267 29 : VideoDeviceMonitor::overwritePreferences(const VideoSettings& settings) 268 : { 269 29 : auto it = findPreferencesById(settings.unique_id); 270 29 : if (it != preferences_.end()) 271 29 : preferences_.erase(it); 272 29 : preferences_.emplace_back(settings); 273 29 : } 274 : 275 : void 276 2443 : VideoDeviceMonitor::serialize(YAML::Emitter& out) const 277 : { 278 2443 : std::lock_guard l(lock_); 279 2443 : out << YAML::Key << "devices" << YAML::Value << preferences_; 280 2443 : } 281 : 282 : void 283 29 : VideoDeviceMonitor::unserialize(const YAML::Node& in) 284 : { 285 29 : std::lock_guard l(lock_); 286 29 : const auto& node = in[CONFIG_LABEL]; 287 : 288 : /* load the device list from the "video" YAML section */ 289 29 : const auto& devices = node["devices"]; 290 58 : for (const auto& dev : devices) { 291 29 : VideoSettings pref = dev.as<VideoSettings>(); 292 29 : if (pref.unique_id.empty()) 293 0 : continue; // discard malformed section 294 29 : overwritePreferences(pref); 295 29 : auto itd = findDeviceById(pref.unique_id); 296 29 : if (itd != devices_.end()) 297 29 : itd->applySettings(pref); 298 58 : } 299 : 300 : // Restore the default device if present, or select the first one 301 29 : const string prefId = preferences_.empty() ? "" : preferences_[0].unique_id; 302 29 : const auto devIter = findDeviceById(prefId); 303 29 : if (devIter != devices_.end() && prefId != DEVICE_DESKTOP) { 304 0 : defaultDevice_ = devIter->getDeviceId(); 305 : } else { 306 29 : defaultDevice_.clear(); 307 58 : for (const auto& dev : devices_) 308 29 : if (dev.name != DEVICE_DESKTOP) { 309 0 : defaultDevice_ = dev.getDeviceId(); 310 0 : break; 311 : } 312 : } 313 29 : } 314 : 315 : } // namespace video 316 : } // namespace jami