OLD | NEW |
| (Empty) |
1 /* | |
2 * libjingle | |
3 * Copyright 2004 Google Inc. | |
4 * | |
5 * Redistribution and use in source and binary forms, with or without | |
6 * modification, are permitted provided that the following conditions are met: | |
7 * | |
8 * 1. Redistributions of source code must retain the above copyright notice, | |
9 * this list of conditions and the following disclaimer. | |
10 * 2. Redistributions in binary form must reproduce the above copyright notice, | |
11 * this list of conditions and the following disclaimer in the documentation | |
12 * and/or other materials provided with the distribution. | |
13 * 3. The name of the author may not be used to endorse or promote products | |
14 * derived from this software without specific prior written permission. | |
15 * | |
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED | |
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO | |
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | |
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | |
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
26 */ | |
27 | |
28 #include "talk/media/devices/linuxdevicemanager.h" | |
29 | |
30 #include <unistd.h> | |
31 #include "talk/media/base/mediacommon.h" | |
32 #include "talk/media/devices/libudevsymboltable.h" | |
33 #include "talk/media/devices/v4llookup.h" | |
34 #include "webrtc/sound/platformsoundsystem.h" | |
35 #include "webrtc/sound/platformsoundsystemfactory.h" | |
36 #include "webrtc/sound/sounddevicelocator.h" | |
37 #include "webrtc/sound/soundsysteminterface.h" | |
38 #include "webrtc/base/fileutils.h" | |
39 #include "webrtc/base/linux.h" | |
40 #include "webrtc/base/logging.h" | |
41 #include "webrtc/base/pathutils.h" | |
42 #include "webrtc/base/physicalsocketserver.h" | |
43 #include "webrtc/base/stream.h" | |
44 #include "webrtc/base/stringutils.h" | |
45 #include "webrtc/base/thread.h" | |
46 | |
47 namespace cricket { | |
48 | |
49 DeviceManagerInterface* DeviceManagerFactory::Create() { | |
50 return new LinuxDeviceManager(); | |
51 } | |
52 | |
53 class LinuxDeviceWatcher | |
54 : public DeviceWatcher, | |
55 private rtc::Dispatcher { | |
56 public: | |
57 explicit LinuxDeviceWatcher(DeviceManagerInterface* dm); | |
58 virtual ~LinuxDeviceWatcher(); | |
59 virtual bool Start(); | |
60 virtual void Stop(); | |
61 | |
62 private: | |
63 virtual uint32_t GetRequestedEvents(); | |
64 virtual void OnPreEvent(uint32_t ff); | |
65 virtual void OnEvent(uint32_t ff, int err); | |
66 virtual int GetDescriptor(); | |
67 virtual bool IsDescriptorClosed(); | |
68 | |
69 DeviceManagerInterface* manager_; | |
70 LibUDevSymbolTable libudev_; | |
71 struct udev* udev_; | |
72 struct udev_monitor* udev_monitor_; | |
73 bool registered_; | |
74 }; | |
75 | |
76 static const char* const kFilteredAudioDevicesName[] = { | |
77 #if defined(CHROMEOS) | |
78 "surround40:", | |
79 "surround41:", | |
80 "surround50:", | |
81 "surround51:", | |
82 "surround71:", | |
83 "iec958:", // S/PDIF | |
84 #endif | |
85 NULL, | |
86 }; | |
87 static const char* kFilteredVideoDevicesName[] = { | |
88 NULL, | |
89 }; | |
90 | |
91 LinuxDeviceManager::LinuxDeviceManager() | |
92 : sound_system_(new rtc::PlatformSoundSystemFactory()) { | |
93 set_watcher(new LinuxDeviceWatcher(this)); | |
94 } | |
95 | |
96 LinuxDeviceManager::~LinuxDeviceManager() { | |
97 } | |
98 | |
99 bool LinuxDeviceManager::GetAudioDevices(bool input, | |
100 std::vector<Device>* devs) { | |
101 devs->clear(); | |
102 if (!sound_system_.get()) { | |
103 return false; | |
104 } | |
105 rtc::SoundSystemInterface::SoundDeviceLocatorList list; | |
106 bool success; | |
107 if (input) { | |
108 success = sound_system_->EnumerateCaptureDevices(&list); | |
109 } else { | |
110 success = sound_system_->EnumeratePlaybackDevices(&list); | |
111 } | |
112 if (!success) { | |
113 LOG(LS_ERROR) << "Can't enumerate devices"; | |
114 sound_system_.release(); | |
115 return false; | |
116 } | |
117 // We have to start the index at 1 because webrtc VoiceEngine puts the default | |
118 // device at index 0, but Enumerate(Capture|Playback)Devices does not include | |
119 // a locator for the default device. | |
120 int index = 1; | |
121 for (rtc::SoundSystemInterface::SoundDeviceLocatorList::iterator i = list.begi
n(); | |
122 i != list.end(); | |
123 ++i, ++index) { | |
124 devs->push_back(Device((*i)->name(), index)); | |
125 } | |
126 rtc::SoundSystemInterface::ClearSoundDeviceLocatorList(&list); | |
127 sound_system_.release(); | |
128 return FilterDevices(devs, kFilteredAudioDevicesName); | |
129 } | |
130 | |
131 static const std::string kVideoMetaPathK2_4("/proc/video/dev/"); | |
132 static const std::string kVideoMetaPathK2_6("/sys/class/video4linux/"); | |
133 | |
134 enum MetaType { M2_4, M2_6, NONE }; | |
135 | |
136 static void ScanDeviceDirectory(const std::string& devdir, | |
137 std::vector<Device>* devices) { | |
138 rtc::scoped_ptr<rtc::DirectoryIterator> directoryIterator( | |
139 rtc::Filesystem::IterateDirectory()); | |
140 | |
141 if (directoryIterator->Iterate(rtc::Pathname(devdir))) { | |
142 do { | |
143 std::string filename = directoryIterator->Name(); | |
144 std::string device_name = devdir + filename; | |
145 if (!directoryIterator->IsDots()) { | |
146 if (filename.find("video") == 0 && | |
147 V4LLookup::IsV4L2Device(device_name)) { | |
148 devices->push_back(Device(device_name, device_name)); | |
149 } | |
150 } | |
151 } while (directoryIterator->Next()); | |
152 } | |
153 } | |
154 | |
155 static std::string GetVideoDeviceNameK2_6(const std::string& device_meta_path) { | |
156 std::string device_name; | |
157 | |
158 rtc::scoped_ptr<rtc::FileStream> device_meta_stream( | |
159 rtc::Filesystem::OpenFile(device_meta_path, "r")); | |
160 | |
161 if (device_meta_stream) { | |
162 if (device_meta_stream->ReadLine(&device_name) != rtc::SR_SUCCESS) { | |
163 LOG(LS_ERROR) << "Failed to read V4L2 device meta " << device_meta_path; | |
164 } | |
165 device_meta_stream->Close(); | |
166 } | |
167 | |
168 return device_name; | |
169 } | |
170 | |
171 static std::string Trim(const std::string& s, const std::string& drop = " \t") { | |
172 std::string::size_type first = s.find_first_not_of(drop); | |
173 std::string::size_type last = s.find_last_not_of(drop); | |
174 | |
175 if (first == std::string::npos || last == std::string::npos) | |
176 return std::string(""); | |
177 | |
178 return s.substr(first, last - first + 1); | |
179 } | |
180 | |
181 static std::string GetVideoDeviceNameK2_4(const std::string& device_meta_path) { | |
182 rtc::ConfigParser::MapVector all_values; | |
183 | |
184 rtc::ConfigParser config_parser; | |
185 rtc::FileStream* file_stream = | |
186 rtc::Filesystem::OpenFile(device_meta_path, "r"); | |
187 | |
188 if (file_stream == NULL) return ""; | |
189 | |
190 config_parser.Attach(file_stream); | |
191 config_parser.Parse(&all_values); | |
192 | |
193 for (rtc::ConfigParser::MapVector::iterator i = all_values.begin(); | |
194 i != all_values.end(); ++i) { | |
195 rtc::ConfigParser::SimpleMap::iterator device_name_i = | |
196 i->find("name"); | |
197 | |
198 if (device_name_i != i->end()) { | |
199 return device_name_i->second; | |
200 } | |
201 } | |
202 | |
203 return ""; | |
204 } | |
205 | |
206 static std::string GetVideoDeviceName(MetaType meta, | |
207 const std::string& device_file_name) { | |
208 std::string device_meta_path; | |
209 std::string device_name; | |
210 std::string meta_file_path; | |
211 | |
212 if (meta == M2_6) { | |
213 meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/name"; | |
214 | |
215 LOG(LS_INFO) << "Trying " + meta_file_path; | |
216 device_name = GetVideoDeviceNameK2_6(meta_file_path); | |
217 | |
218 if (device_name.empty()) { | |
219 meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/model"; | |
220 | |
221 LOG(LS_INFO) << "Trying " << meta_file_path; | |
222 device_name = GetVideoDeviceNameK2_6(meta_file_path); | |
223 } | |
224 } else { | |
225 meta_file_path = kVideoMetaPathK2_4 + device_file_name; | |
226 LOG(LS_INFO) << "Trying " << meta_file_path; | |
227 device_name = GetVideoDeviceNameK2_4(meta_file_path); | |
228 } | |
229 | |
230 if (device_name.empty()) { | |
231 device_name = "/dev/" + device_file_name; | |
232 LOG(LS_ERROR) | |
233 << "Device name not found, defaulting to device path " << device_name; | |
234 } | |
235 | |
236 LOG(LS_INFO) << "Name for " << device_file_name << " is " << device_name; | |
237 | |
238 return Trim(device_name); | |
239 } | |
240 | |
241 static void ScanV4L2Devices(std::vector<Device>* devices) { | |
242 LOG(LS_INFO) << ("Enumerating V4L2 devices"); | |
243 | |
244 MetaType meta; | |
245 std::string metadata_dir; | |
246 | |
247 rtc::scoped_ptr<rtc::DirectoryIterator> directoryIterator( | |
248 rtc::Filesystem::IterateDirectory()); | |
249 | |
250 // Try and guess kernel version | |
251 if (directoryIterator->Iterate(kVideoMetaPathK2_6)) { | |
252 meta = M2_6; | |
253 metadata_dir = kVideoMetaPathK2_6; | |
254 } else if (directoryIterator->Iterate(kVideoMetaPathK2_4)) { | |
255 meta = M2_4; | |
256 metadata_dir = kVideoMetaPathK2_4; | |
257 } else { | |
258 meta = NONE; | |
259 } | |
260 | |
261 if (meta != NONE) { | |
262 LOG(LS_INFO) << "V4L2 device metadata found at " << metadata_dir; | |
263 | |
264 do { | |
265 std::string filename = directoryIterator->Name(); | |
266 | |
267 if (filename.find("video") == 0) { | |
268 std::string device_path = "/dev/" + filename; | |
269 | |
270 if (V4LLookup::IsV4L2Device(device_path)) { | |
271 devices->push_back( | |
272 Device(GetVideoDeviceName(meta, filename), device_path)); | |
273 } | |
274 } | |
275 } while (directoryIterator->Next()); | |
276 } else { | |
277 LOG(LS_ERROR) << "Unable to detect v4l2 metadata directory"; | |
278 } | |
279 | |
280 if (devices->size() == 0) { | |
281 LOG(LS_INFO) << "Plan B. Scanning all video devices in /dev directory"; | |
282 ScanDeviceDirectory("/dev/", devices); | |
283 } | |
284 | |
285 LOG(LS_INFO) << "Total V4L2 devices found : " << devices->size(); | |
286 } | |
287 | |
288 bool LinuxDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) { | |
289 devices->clear(); | |
290 ScanV4L2Devices(devices); | |
291 return FilterDevices(devices, kFilteredVideoDevicesName); | |
292 } | |
293 | |
294 LinuxDeviceWatcher::LinuxDeviceWatcher(DeviceManagerInterface* dm) | |
295 : DeviceWatcher(dm), | |
296 manager_(dm), | |
297 udev_(NULL), | |
298 udev_monitor_(NULL), | |
299 registered_(false) { | |
300 } | |
301 | |
302 LinuxDeviceWatcher::~LinuxDeviceWatcher() { | |
303 } | |
304 | |
305 static rtc::PhysicalSocketServer* CurrentSocketServer() { | |
306 rtc::SocketServer* ss = | |
307 rtc::ThreadManager::Instance()->WrapCurrentThread()->socketserver(); | |
308 return reinterpret_cast<rtc::PhysicalSocketServer*>(ss); | |
309 } | |
310 | |
311 bool LinuxDeviceWatcher::Start() { | |
312 // We deliberately return true in the failure paths here because libudev is | |
313 // not a critical component of a Linux system so it may not be present/usable, | |
314 // and we don't want to halt LinuxDeviceManager initialization in such a case. | |
315 if (!libudev_.Load() || IsWrongLibUDevAbiVersion(libudev_.GetDllHandle())) { | |
316 LOG(LS_WARNING) | |
317 << "libudev not present/usable; LinuxDeviceWatcher disabled"; | |
318 return true; | |
319 } | |
320 udev_ = libudev_.udev_new()(); | |
321 if (!udev_) { | |
322 LOG_ERR(LS_ERROR) << "udev_new()"; | |
323 return true; | |
324 } | |
325 // The second argument here is the event source. It can be either "kernel" or | |
326 // "udev", but "udev" is the only correct choice. Apps listen on udev and the | |
327 // udev daemon in turn listens on the kernel. | |
328 udev_monitor_ = libudev_.udev_monitor_new_from_netlink()(udev_, "udev"); | |
329 if (!udev_monitor_) { | |
330 LOG_ERR(LS_ERROR) << "udev_monitor_new_from_netlink()"; | |
331 return true; | |
332 } | |
333 // We only listen for changes in the video devices. Audio devices are more or | |
334 // less unimportant because receiving device change notifications really only | |
335 // matters for broadcasting updated send/recv capabilities based on whether | |
336 // there is at least one device available, and almost all computers have at | |
337 // least one audio device. Also, PulseAudio device notifications don't come | |
338 // from the udev daemon, they come from the PulseAudio daemon, so we'd only | |
339 // want to listen for audio device changes from udev if using ALSA. For | |
340 // simplicity, we don't bother with any audio stuff at all. | |
341 if (libudev_.udev_monitor_filter_add_match_subsystem_devtype()( | |
342 udev_monitor_, "video4linux", NULL) < 0) { | |
343 LOG_ERR(LS_ERROR) << "udev_monitor_filter_add_match_subsystem_devtype()"; | |
344 return true; | |
345 } | |
346 if (libudev_.udev_monitor_enable_receiving()(udev_monitor_) < 0) { | |
347 LOG_ERR(LS_ERROR) << "udev_monitor_enable_receiving()"; | |
348 return true; | |
349 } | |
350 CurrentSocketServer()->Add(this); | |
351 registered_ = true; | |
352 return true; | |
353 } | |
354 | |
355 void LinuxDeviceWatcher::Stop() { | |
356 if (registered_) { | |
357 CurrentSocketServer()->Remove(this); | |
358 registered_ = false; | |
359 } | |
360 if (udev_monitor_) { | |
361 libudev_.udev_monitor_unref()(udev_monitor_); | |
362 udev_monitor_ = NULL; | |
363 } | |
364 if (udev_) { | |
365 libudev_.udev_unref()(udev_); | |
366 udev_ = NULL; | |
367 } | |
368 libudev_.Unload(); | |
369 } | |
370 | |
371 uint32_t LinuxDeviceWatcher::GetRequestedEvents() { | |
372 return rtc::DE_READ; | |
373 } | |
374 | |
375 void LinuxDeviceWatcher::OnPreEvent(uint32_t ff) { | |
376 // Nothing to do. | |
377 } | |
378 | |
379 void LinuxDeviceWatcher::OnEvent(uint32_t ff, int err) { | |
380 udev_device* device = libudev_.udev_monitor_receive_device()(udev_monitor_); | |
381 if (!device) { | |
382 // Probably the socket connection to the udev daemon was terminated (perhaps | |
383 // the daemon crashed or is being restarted?). | |
384 LOG_ERR(LS_WARNING) << "udev_monitor_receive_device()"; | |
385 // Stop listening to avoid potential livelock (an fd with EOF in it is | |
386 // always considered readable). | |
387 CurrentSocketServer()->Remove(this); | |
388 registered_ = false; | |
389 return; | |
390 } | |
391 // Else we read the device successfully. | |
392 | |
393 // Since we already have our own filesystem-based device enumeration code, we | |
394 // simply re-enumerate rather than inspecting the device event. | |
395 libudev_.udev_device_unref()(device); | |
396 manager_->SignalDevicesChange(); | |
397 } | |
398 | |
399 int LinuxDeviceWatcher::GetDescriptor() { | |
400 return libudev_.udev_monitor_get_fd()(udev_monitor_); | |
401 } | |
402 | |
403 bool LinuxDeviceWatcher::IsDescriptorClosed() { | |
404 // If it is closed then we will just get an error in | |
405 // udev_monitor_receive_device and unregister, so we don't need to check for | |
406 // it separately. | |
407 return false; | |
408 } | |
409 | |
410 }; // namespace cricket | |
OLD | NEW |