diff options
Diffstat (limited to 'talk/media/devices')
35 files changed, 5565 insertions, 0 deletions
diff --git a/talk/media/devices/carbonvideorenderer.cc b/talk/media/devices/carbonvideorenderer.cc new file mode 100644 index 0000000000..846135d925 --- /dev/null +++ b/talk/media/devices/carbonvideorenderer.cc @@ -0,0 +1,190 @@ +/* + * libjingle + * Copyright 2011 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Implementation of CarbonVideoRenderer + +#include "talk/media/devices/carbonvideorenderer.h" + +#include "talk/media/base/videocommon.h" +#include "talk/media/base/videoframe.h" +#include "webrtc/base/logging.h" + +namespace cricket { + +CarbonVideoRenderer::CarbonVideoRenderer(int x, int y) + : image_width_(0), + image_height_(0), + x_(x), + y_(y), + image_ref_(NULL), + window_ref_(NULL) { +} + +CarbonVideoRenderer::~CarbonVideoRenderer() { + if (window_ref_) { + DisposeWindow(window_ref_); + } +} + +// Called from the main event loop. All renderering needs to happen on +// the main thread. +OSStatus CarbonVideoRenderer::DrawEventHandler(EventHandlerCallRef handler, + EventRef event, + void* data) { + OSStatus status = noErr; + CarbonVideoRenderer* renderer = static_cast<CarbonVideoRenderer*>(data); + if (renderer != NULL) { + if (!renderer->DrawFrame()) { + LOG(LS_ERROR) << "Failed to draw frame."; + } + } + return status; +} + +bool CarbonVideoRenderer::DrawFrame() { + // Grab the image lock to make sure it is not changed why we'll draw it. + rtc::CritScope cs(&image_crit_); + + if (image_.get() == NULL) { + // Nothing to draw, just return. + return true; + } + int width = image_width_; + int height = image_height_; + CGDataProviderRef provider = + CGDataProviderCreateWithData(NULL, image_.get(), width * height * 4, + NULL); + CGColorSpaceRef color_space_ref = CGColorSpaceCreateDeviceRGB(); + CGBitmapInfo bitmap_info = kCGBitmapByteOrderDefault; + CGColorRenderingIntent rendering_intent = kCGRenderingIntentDefault; + CGImageRef image_ref = CGImageCreate(width, height, 8, 32, width * 4, + color_space_ref, bitmap_info, provider, + NULL, false, rendering_intent); + CGDataProviderRelease(provider); + + if (image_ref == NULL) { + return false; + } + CGContextRef context; + SetPortWindowPort(window_ref_); + if (QDBeginCGContext(GetWindowPort(window_ref_), &context) != noErr) { + CGImageRelease(image_ref); + return false; + } + Rect window_bounds; + GetWindowPortBounds(window_ref_, &window_bounds); + + // Anchor the image to the top left corner. + int x = 0; + int y = window_bounds.bottom - CGImageGetHeight(image_ref); + CGRect dst_rect = CGRectMake(x, y, CGImageGetWidth(image_ref), + CGImageGetHeight(image_ref)); + CGContextDrawImage(context, dst_rect, image_ref); + CGContextFlush(context); + QDEndCGContext(GetWindowPort(window_ref_), &context); + CGImageRelease(image_ref); + return true; +} + +bool CarbonVideoRenderer::SetSize(int width, int height, int reserved) { + if (width != image_width_ || height != image_height_) { + // Grab the image lock while changing its size. + rtc::CritScope cs(&image_crit_); + image_width_ = width; + image_height_ = height; + image_.reset(new uint8_t[width * height * 4]); + memset(image_.get(), 255, width * height * 4); + } + return true; +} + +bool CarbonVideoRenderer::RenderFrame(const VideoFrame* video_frame) { + if (!video_frame) { + return false; + } + { + const VideoFrame* frame = video_frame->GetCopyWithRotationApplied(); + + if (!SetSize(frame->GetWidth(), frame->GetHeight(), 0)) { + return false; + } + + // Grab the image lock so we are not trashing up the image being drawn. + rtc::CritScope cs(&image_crit_); + frame->ConvertToRgbBuffer(cricket::FOURCC_ABGR, + image_.get(), + frame->GetWidth() * frame->GetHeight() * 4, + frame->GetWidth() * 4); + } + + // Trigger a repaint event for the whole window. + Rect bounds; + InvalWindowRect(window_ref_, GetWindowPortBounds(window_ref_, &bounds)); + return true; +} + +bool CarbonVideoRenderer::Initialize() { + OSStatus err; + WindowAttributes attributes = + kWindowStandardDocumentAttributes | + kWindowLiveResizeAttribute | + kWindowFrameworkScaledAttribute | + kWindowStandardHandlerAttribute; + + struct Rect bounds; + bounds.top = y_; + bounds.bottom = 480; + bounds.left = x_; + bounds.right = 640; + err = CreateNewWindow(kDocumentWindowClass, attributes, + &bounds, &window_ref_); + if (!window_ref_ || err != noErr) { + LOG(LS_ERROR) << "CreateNewWindow failed, error code: " << err; + return false; + } + static const EventTypeSpec event_spec = { + kEventClassWindow, + kEventWindowDrawContent + }; + + err = InstallWindowEventHandler( + window_ref_, + NewEventHandlerUPP(CarbonVideoRenderer::DrawEventHandler), + GetEventTypeCount(event_spec), + &event_spec, + this, + NULL); + if (err != noErr) { + LOG(LS_ERROR) << "Failed to install event handler, error code: " << err; + return false; + } + SelectWindow(window_ref_); + ShowWindow(window_ref_); + return true; +} + +} // namespace cricket diff --git a/talk/media/devices/carbonvideorenderer.h b/talk/media/devices/carbonvideorenderer.h new file mode 100644 index 0000000000..52c974060c --- /dev/null +++ b/talk/media/devices/carbonvideorenderer.h @@ -0,0 +1,74 @@ +/* + * libjingle + * Copyright 2011 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Definition of class CarbonVideoRenderer that implements the abstract class +// cricket::VideoRenderer via Carbon. + +#ifndef TALK_MEDIA_DEVICES_CARBONVIDEORENDERER_H_ +#define TALK_MEDIA_DEVICES_CARBONVIDEORENDERER_H_ + +#include <Carbon/Carbon.h> + +#include "talk/media/base/videorenderer.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/scoped_ptr.h" + +namespace cricket { + +class CarbonVideoRenderer : public VideoRenderer { + public: + CarbonVideoRenderer(int x, int y); + virtual ~CarbonVideoRenderer(); + + // Implementation of pure virtual methods of VideoRenderer. + // These two methods may be executed in different threads. + // SetSize is called before RenderFrame. + virtual bool SetSize(int width, int height, int reserved); + virtual bool RenderFrame(const VideoFrame* frame); + + // Needs to be called on the main thread. + bool Initialize(); + + private: + bool DrawFrame(); + + static OSStatus DrawEventHandler(EventHandlerCallRef handler, + EventRef event, + void* data); + rtc::scoped_ptr<uint8_t[]> image_; + rtc::CriticalSection image_crit_; + int image_width_; + int image_height_; + int x_; + int y_; + CGImageRef image_ref_; + WindowRef window_ref_; +}; + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_CARBONVIDEORENDERER_H_ diff --git a/talk/media/devices/deviceinfo.h b/talk/media/devices/deviceinfo.h new file mode 100644 index 0000000000..86382f6145 --- /dev/null +++ b/talk/media/devices/deviceinfo.h @@ -0,0 +1,42 @@ +/* + * libjingle + * Copyright 2012 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_DEVICES_DEVICEINFO_H_ +#define TALK_MEDIA_DEVICES_DEVICEINFO_H_ + +#include <string> + +#include "talk/media/devices/devicemanager.h" + +namespace cricket { + +bool GetUsbId(const Device& device, std::string* usb_id); +bool GetUsbVersion(const Device& device, std::string* usb_version); + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_DEVICEINFO_H_ diff --git a/talk/media/devices/devicemanager.cc b/talk/media/devices/devicemanager.cc new file mode 100644 index 0000000000..1d7ac5baf1 --- /dev/null +++ b/talk/media/devices/devicemanager.cc @@ -0,0 +1,371 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/devicemanager.h" + +#include "talk/media/base/mediacommon.h" +#include "talk/media/base/videocapturerfactory.h" +#include "talk/media/devices/deviceinfo.h" +#include "talk/media/devices/filevideocapturer.h" +#include "talk/media/devices/yuvframescapturer.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/windowpicker.h" +#include "webrtc/base/windowpickerfactory.h" + +#ifdef HAVE_WEBRTC_VIDEO +#include "talk/media/webrtc/webrtcvideocapturerfactory.h" +#endif // HAVE_WEBRTC_VIDEO + +namespace { + +bool StringMatchWithWildcard( + const std::pair<const std::basic_string<char>, cricket::VideoFormat> key, + const std::string& val) { + return rtc::string_match(val.c_str(), key.first.c_str()); +} + +} // namespace + +namespace cricket { + +// Initialize to empty string. +const char DeviceManagerInterface::kDefaultDeviceName[] = ""; + +DeviceManager::DeviceManager() + : initialized_(false), + window_picker_(rtc::WindowPickerFactory::CreateWindowPicker()) { +#ifdef HAVE_WEBRTC_VIDEO + SetVideoDeviceCapturerFactory(new WebRtcVideoDeviceCapturerFactory()); +#endif // HAVE_WEBRTC_VIDEO +} + +DeviceManager::~DeviceManager() { + if (initialized()) { + Terminate(); + } +} + +bool DeviceManager::Init() { + if (!initialized()) { + if (!watcher()->Start()) { + return false; + } + set_initialized(true); + } + return true; +} + +void DeviceManager::Terminate() { + if (initialized()) { + watcher()->Stop(); + set_initialized(false); + } +} + +int DeviceManager::GetCapabilities() { + std::vector<Device> devices; + int caps = VIDEO_RECV; + if (GetAudioInputDevices(&devices) && !devices.empty()) { + caps |= AUDIO_SEND; + } + if (GetAudioOutputDevices(&devices) && !devices.empty()) { + caps |= AUDIO_RECV; + } + if (GetVideoCaptureDevices(&devices) && !devices.empty()) { + caps |= VIDEO_SEND; + } + return caps; +} + +bool DeviceManager::GetAudioInputDevices(std::vector<Device>* devices) { + return GetAudioDevices(true, devices); +} + +bool DeviceManager::GetAudioOutputDevices(std::vector<Device>* devices) { + return GetAudioDevices(false, devices); +} + +bool DeviceManager::GetAudioInputDevice(const std::string& name, Device* out) { + return GetAudioDevice(true, name, out); +} + +bool DeviceManager::GetAudioOutputDevice(const std::string& name, Device* out) { + return GetAudioDevice(false, name, out); +} + +bool DeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) { + devices->clear(); +#if defined(ANDROID) || defined(IOS) + // On Android and iOS, we treat the camera(s) as a single device. Even if + // there are multiple cameras, that's abstracted away at a higher level. + Device dev("camera", "1"); // name and ID + devices->push_back(dev); + return true; +#else + return false; +#endif +} + +bool DeviceManager::GetVideoCaptureDevice(const std::string& name, + Device* out) { + // If the name is empty, return the default device. + if (name.empty() || name == kDefaultDeviceName) { + return GetDefaultVideoCaptureDevice(out); + } + + std::vector<Device> devices; + if (!GetVideoCaptureDevices(&devices)) { + return false; + } + + for (std::vector<Device>::const_iterator it = devices.begin(); + it != devices.end(); ++it) { + if (name == it->name) { + *out = *it; + return true; + } + } + + // If |name| is a valid name for a file or yuvframedevice, + // return a fake video capturer device. + if (GetFakeVideoCaptureDevice(name, out)) { + return true; + } + + return false; +} + +bool DeviceManager::GetFakeVideoCaptureDevice(const std::string& name, + Device* out) const { + if (rtc::Filesystem::IsFile(name)) { + *out = FileVideoCapturer::CreateFileVideoCapturerDevice(name); + return true; + } + + if (name == YuvFramesCapturer::kYuvFrameDeviceName) { + *out = YuvFramesCapturer::CreateYuvFramesCapturerDevice(); + return true; + } + + return false; +} + +void DeviceManager::SetVideoCaptureDeviceMaxFormat( + const std::string& usb_id, + const VideoFormat& max_format) { + max_formats_[usb_id] = max_format; +} + +void DeviceManager::ClearVideoCaptureDeviceMaxFormat( + const std::string& usb_id) { + max_formats_.erase(usb_id); +} + +VideoCapturer* DeviceManager::CreateVideoCapturer(const Device& device) const { + VideoCapturer* capturer = MaybeConstructFakeVideoCapturer(device); + if (capturer) { + return capturer; + } + + if (!video_device_capturer_factory_) { + LOG(LS_ERROR) << "No video capturer factory for devices."; + return NULL; + } + capturer = video_device_capturer_factory_->Create(device); + if (!capturer) { + return NULL; + } + LOG(LS_INFO) << "Created VideoCapturer for " << device.name; + VideoFormat video_format; + bool has_max = GetMaxFormat(device, &video_format); + capturer->set_enable_camera_list(has_max); + if (has_max) { + capturer->ConstrainSupportedFormats(video_format); + } + return capturer; +} + +VideoCapturer* DeviceManager::MaybeConstructFakeVideoCapturer( + const Device& device) const { + // TODO(hellner): Throw out the creation of a file video capturer once the + // refactoring is completed. + if (FileVideoCapturer::IsFileVideoCapturerDevice(device)) { + FileVideoCapturer* capturer = new FileVideoCapturer; + if (!capturer->Init(device)) { + delete capturer; + return NULL; + } + LOG(LS_INFO) << "Created file video capturer " << device.name; + capturer->set_repeat(FileVideoCapturer::kForever); + return capturer; + } + + if (YuvFramesCapturer::IsYuvFramesCapturerDevice(device)) { + YuvFramesCapturer* capturer = new YuvFramesCapturer(); + capturer->Init(); + return capturer; + } + return NULL; +} + +bool DeviceManager::GetWindows( + std::vector<rtc::WindowDescription>* descriptions) { + if (!window_picker_) { + return false; + } + return window_picker_->GetWindowList(descriptions); +} + +bool DeviceManager::GetDesktops( + std::vector<rtc::DesktopDescription>* descriptions) { + if (!window_picker_) { + return false; + } + return window_picker_->GetDesktopList(descriptions); +} + +VideoCapturer* DeviceManager::CreateScreenCapturer( + const ScreencastId& screenid) const { + if (!screen_capturer_factory_) { + LOG(LS_ERROR) << "No video capturer factory for screens."; + return NULL; + } + return screen_capturer_factory_->Create(screenid); +} + +bool DeviceManager::GetAudioDevices(bool input, + std::vector<Device>* devs) { + devs->clear(); +#if defined(ANDROID) + // Under Android, 0 is always required for the playout device and 0 is the + // default for the recording device. + devs->push_back(Device("default-device", 0)); + return true; +#else + // Other platforms either have their own derived class implementation + // (desktop) or don't use device manager for audio devices (iOS). + return false; +#endif +} + +bool DeviceManager::GetAudioDevice(bool is_input, const std::string& name, + Device* out) { + // If the name is empty, return the default device id. + if (name.empty() || name == kDefaultDeviceName) { + *out = Device(name, -1); + return true; + } + + std::vector<Device> devices; + bool ret = is_input ? GetAudioInputDevices(&devices) : + GetAudioOutputDevices(&devices); + if (ret) { + ret = false; + for (size_t i = 0; i < devices.size(); ++i) { + if (devices[i].name == name) { + *out = devices[i]; + ret = true; + break; + } + } + } + return ret; +} + +bool DeviceManager::GetDefaultVideoCaptureDevice(Device* device) { + bool ret = false; + // We just return the first device. + std::vector<Device> devices; + ret = (GetVideoCaptureDevices(&devices) && !devices.empty()); + if (ret) { + *device = devices[0]; + } + return ret; +} + +bool DeviceManager::IsInWhitelist(const std::string& key, + VideoFormat* video_format) const { + std::map<std::string, VideoFormat>::const_iterator found = + std::search_n(max_formats_.begin(), max_formats_.end(), 1, key, + StringMatchWithWildcard); + if (found == max_formats_.end()) { + return false; + } + *video_format = found->second; + return true; +} + +bool DeviceManager::GetMaxFormat(const Device& device, + VideoFormat* video_format) const { + // Match USB ID if available. Failing that, match device name. + std::string usb_id; + if (GetUsbId(device, &usb_id) && IsInWhitelist(usb_id, video_format)) { + return true; + } + return IsInWhitelist(device.name, video_format); +} + +bool DeviceManager::ShouldDeviceBeIgnored(const std::string& device_name, + const char* const exclusion_list[]) { + // If exclusion_list is empty return directly. + if (!exclusion_list) + return false; + + int i = 0; + while (exclusion_list[i]) { + if (strnicmp(device_name.c_str(), exclusion_list[i], + strlen(exclusion_list[i])) == 0) { + LOG(LS_INFO) << "Ignoring device " << device_name; + return true; + } + ++i; + } + return false; +} + +bool DeviceManager::FilterDevices(std::vector<Device>* devices, + const char* const exclusion_list[]) { + if (!devices) { + return false; + } + + for (std::vector<Device>::iterator it = devices->begin(); + it != devices->end(); ) { + if (ShouldDeviceBeIgnored(it->name, exclusion_list)) { + it = devices->erase(it); + } else { + ++it; + } + } + return true; +} + +} // namespace cricket diff --git a/talk/media/devices/devicemanager.h b/talk/media/devices/devicemanager.h new file mode 100644 index 0000000000..982c679531 --- /dev/null +++ b/talk/media/devices/devicemanager.h @@ -0,0 +1,211 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_DEVICES_DEVICEMANAGER_H_ +#define TALK_MEDIA_DEVICES_DEVICEMANAGER_H_ + +#include <map> +#include <string> +#include <vector> + +#include "talk/media/base/device.h" +#include "talk/media/base/screencastid.h" +#include "talk/media/base/videocapturerfactory.h" +#include "talk/media/base/videocommon.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/window.h" + +namespace rtc { + +class DesktopDescription; +class WindowDescription; +class WindowPicker; + +} +namespace cricket { + +class VideoCapturer; + +// DeviceManagerInterface - interface to manage the audio and +// video devices on the system. +class DeviceManagerInterface { + public: + virtual ~DeviceManagerInterface() { } + + // Initialization + virtual bool Init() = 0; + virtual void Terminate() = 0; + + // Capabilities + virtual int GetCapabilities() = 0; + + // Device enumeration + virtual bool GetAudioInputDevices(std::vector<Device>* devices) = 0; + virtual bool GetAudioOutputDevices(std::vector<Device>* devices) = 0; + + virtual bool GetAudioInputDevice(const std::string& name, Device* out) = 0; + virtual bool GetAudioOutputDevice(const std::string& name, Device* out) = 0; + + virtual bool GetVideoCaptureDevices(std::vector<Device>* devs) = 0; + virtual bool GetVideoCaptureDevice(const std::string& name, Device* out) = 0; + + // If the device manager needs to create video capturers, here is + // how to control which video capturers are created. These take + // ownership of the factories. + virtual void SetVideoDeviceCapturerFactory( + VideoDeviceCapturerFactory* video_device_capturer_factory) = 0; + virtual void SetScreenCapturerFactory( + ScreenCapturerFactory* screen_capturer_factory) = 0; + + // Caps the capture format according to max format for capturers created + // by CreateVideoCapturer(). See ConstrainSupportedFormats() in + // videocapturer.h for more detail. + // Note that once a VideoCapturer has been created, calling this API will + // not affect it. + virtual void SetVideoCaptureDeviceMaxFormat( + const std::string& usb_id, + const VideoFormat& max_format) = 0; + virtual void ClearVideoCaptureDeviceMaxFormat(const std::string& usb_id) = 0; + + // Device creation + virtual VideoCapturer* CreateVideoCapturer(const Device& device) const = 0; + + virtual bool GetWindows( + std::vector<rtc::WindowDescription>* descriptions) = 0; + virtual bool GetDesktops( + std::vector<rtc::DesktopDescription>* descriptions) = 0; + virtual VideoCapturer* CreateScreenCapturer( + const ScreencastId& screenid) const = 0; + + sigslot::signal0<> SignalDevicesChange; + + static const char kDefaultDeviceName[]; +}; + +class DeviceWatcher { + public: + explicit DeviceWatcher(DeviceManagerInterface* dm) {} + virtual ~DeviceWatcher() {} + virtual bool Start() { return true; } + virtual void Stop() {} +}; + +class DeviceManagerFactory { + public: + static DeviceManagerInterface* Create(); + + private: + DeviceManagerFactory() {} +}; + +class DeviceManager : public DeviceManagerInterface { + public: + DeviceManager(); + virtual ~DeviceManager(); + + // Initialization + virtual bool Init(); + virtual void Terminate(); + + // Capabilities + virtual int GetCapabilities(); + + // Device enumeration + virtual bool GetAudioInputDevices(std::vector<Device>* devices); + virtual bool GetAudioOutputDevices(std::vector<Device>* devices); + + virtual bool GetAudioInputDevice(const std::string& name, Device* out); + virtual bool GetAudioOutputDevice(const std::string& name, Device* out); + + virtual bool GetVideoCaptureDevices(std::vector<Device>* devs); + virtual bool GetVideoCaptureDevice(const std::string& name, Device* out); + + virtual void SetVideoDeviceCapturerFactory( + VideoDeviceCapturerFactory* video_device_capturer_factory) { + video_device_capturer_factory_.reset(video_device_capturer_factory); + } + virtual void SetScreenCapturerFactory( + ScreenCapturerFactory* screen_capturer_factory) { + screen_capturer_factory_.reset(screen_capturer_factory); + } + + + virtual void SetVideoCaptureDeviceMaxFormat(const std::string& usb_id, + const VideoFormat& max_format); + virtual void ClearVideoCaptureDeviceMaxFormat(const std::string& usb_id); + + // TODO(pthatcher): Rename to CreateVideoDeviceCapturer. + virtual VideoCapturer* CreateVideoCapturer(const Device& device) const; + + virtual bool GetWindows( + std::vector<rtc::WindowDescription>* descriptions); + virtual bool GetDesktops( + std::vector<rtc::DesktopDescription>* descriptions); + virtual VideoCapturer* CreateScreenCapturer( + const ScreencastId& screenid) const; + + // The exclusion_list MUST be a NULL terminated list. + static bool FilterDevices(std::vector<Device>* devices, + const char* const exclusion_list[]); + bool initialized() const { return initialized_; } + + protected: + virtual bool GetAudioDevices(bool input, std::vector<Device>* devs); + virtual bool GetAudioDevice(bool is_input, const std::string& name, + Device* out); + virtual bool GetDefaultVideoCaptureDevice(Device* device); + bool IsInWhitelist(const std::string& key, VideoFormat* video_format) const; + virtual bool GetMaxFormat(const Device& device, + VideoFormat* video_format) const; + + void set_initialized(bool initialized) { initialized_ = initialized; } + + void set_watcher(DeviceWatcher* watcher) { watcher_.reset(watcher); } + DeviceWatcher* watcher() { return watcher_.get(); } + + private: + // The exclusion_list MUST be a NULL terminated list. + static bool ShouldDeviceBeIgnored(const std::string& device_name, + const char* const exclusion_list[]); + bool GetFakeVideoCaptureDevice(const std::string& name, Device* out) const; + VideoCapturer* MaybeConstructFakeVideoCapturer(const Device& device) const; + + bool initialized_; + rtc::scoped_ptr< + VideoDeviceCapturerFactory> video_device_capturer_factory_; + rtc::scoped_ptr< + ScreenCapturerFactory> screen_capturer_factory_; + std::map<std::string, VideoFormat> max_formats_; + rtc::scoped_ptr<DeviceWatcher> watcher_; + rtc::scoped_ptr<rtc::WindowPicker> window_picker_; +}; + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_DEVICEMANAGER_H_ diff --git a/talk/media/devices/devicemanager_unittest.cc b/talk/media/devices/devicemanager_unittest.cc new file mode 100644 index 0000000000..f259c7d0d3 --- /dev/null +++ b/talk/media/devices/devicemanager_unittest.cc @@ -0,0 +1,470 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/devicemanager.h" + +#ifdef WIN32 +#include <objbase.h> +#include "webrtc/base/win32.h" +#endif +#include <string> + +#include "talk/media/base/fakevideocapturer.h" +#include "talk/media/base/screencastid.h" +#include "talk/media/base/testutils.h" +#include "talk/media/base/videocapturerfactory.h" +#include "talk/media/devices/filevideocapturer.h" +#include "talk/media/devices/v4llookup.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/windowpickerfactory.h" + +#ifdef LINUX +// TODO(juberti): Figure out why this doesn't compile on Windows. +#include "webrtc/base/fileutils_mock.h" +#endif // LINUX + +using rtc::Pathname; +using rtc::FileTimeType; +using rtc::scoped_ptr; +using cricket::Device; +using cricket::DeviceManager; +using cricket::DeviceManagerFactory; +using cricket::DeviceManagerInterface; + +const cricket::VideoFormat kVgaFormat(640, 480, + cricket::VideoFormat::FpsToInterval(30), + cricket::FOURCC_I420); +const cricket::VideoFormat kHdFormat(1280, 720, + cricket::VideoFormat::FpsToInterval(30), + cricket::FOURCC_I420); + +class FakeVideoDeviceCapturerFactory : + public cricket::VideoDeviceCapturerFactory { + public: + FakeVideoDeviceCapturerFactory() {} + virtual ~FakeVideoDeviceCapturerFactory() {} + + virtual cricket::VideoCapturer* Create(const cricket::Device& device) { + return new cricket::FakeVideoCapturer; + } +}; + +class FakeScreenCapturerFactory : public cricket::ScreenCapturerFactory { + public: + FakeScreenCapturerFactory() {} + virtual ~FakeScreenCapturerFactory() {} + + virtual cricket::VideoCapturer* Create( + const cricket::ScreencastId& screenid) { + return new cricket::FakeVideoCapturer; + } +}; + +class DeviceManagerTestFake : public testing::Test { + public: + virtual void SetUp() { + dm_.reset(DeviceManagerFactory::Create()); + EXPECT_TRUE(dm_->Init()); + DeviceManager* device_manager = static_cast<DeviceManager*>(dm_.get()); + device_manager->SetVideoDeviceCapturerFactory( + new FakeVideoDeviceCapturerFactory()); + device_manager->SetScreenCapturerFactory( + new FakeScreenCapturerFactory()); + } + + virtual void TearDown() { + dm_->Terminate(); + } + + protected: + scoped_ptr<DeviceManagerInterface> dm_; +}; + + +// Test that we startup/shutdown properly. +TEST(DeviceManagerTest, StartupShutdown) { + scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create()); + EXPECT_TRUE(dm->Init()); + dm->Terminate(); +} + +// Test CoInitEx behavior +#ifdef WIN32 +TEST(DeviceManagerTest, CoInitialize) { + scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create()); + std::vector<Device> devices; + // Ensure that calls to video device work if COM is not yet initialized. + EXPECT_TRUE(dm->Init()); + EXPECT_TRUE(dm->GetVideoCaptureDevices(&devices)); + dm->Terminate(); + // Ensure that the ref count is correct. + EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED)); + CoUninitialize(); + // Ensure that Init works in COINIT_APARTMENTTHREADED setting. + EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)); + EXPECT_TRUE(dm->Init()); + dm->Terminate(); + CoUninitialize(); + // Ensure that the ref count is correct. + EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)); + CoUninitialize(); + // Ensure that Init works in COINIT_MULTITHREADED setting. + EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED)); + EXPECT_TRUE(dm->Init()); + dm->Terminate(); + CoUninitialize(); + // Ensure that the ref count is correct. + EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED)); + CoUninitialize(); +} +#endif + +// Test enumerating devices (although we may not find any). +TEST(DeviceManagerTest, GetDevices) { + scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create()); + std::vector<Device> audio_ins, audio_outs, video_ins; + std::vector<cricket::Device> video_in_devs; + cricket::Device def_video; + EXPECT_TRUE(dm->Init()); + EXPECT_TRUE(dm->GetAudioInputDevices(&audio_ins)); + EXPECT_TRUE(dm->GetAudioOutputDevices(&audio_outs)); + EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins)); + EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_in_devs)); + EXPECT_EQ(video_ins.size(), video_in_devs.size()); + // If we have any video devices, we should be able to pick a default. + EXPECT_TRUE(dm->GetVideoCaptureDevice( + cricket::DeviceManagerInterface::kDefaultDeviceName, &def_video) + != video_ins.empty()); +} + +// Test that we return correct ids for default and bogus devices. +TEST(DeviceManagerTest, GetAudioDeviceIds) { + scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create()); + Device device; + EXPECT_TRUE(dm->Init()); + EXPECT_TRUE(dm->GetAudioInputDevice( + cricket::DeviceManagerInterface::kDefaultDeviceName, &device)); + EXPECT_EQ("-1", device.id); + EXPECT_TRUE(dm->GetAudioOutputDevice( + cricket::DeviceManagerInterface::kDefaultDeviceName, &device)); + EXPECT_EQ("-1", device.id); + EXPECT_FALSE(dm->GetAudioInputDevice("_NOT A REAL DEVICE_", &device)); + EXPECT_FALSE(dm->GetAudioOutputDevice("_NOT A REAL DEVICE_", &device)); +} + +// Test that we get the video capture device by name properly. +TEST(DeviceManagerTest, GetVideoDeviceIds) { + scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create()); + Device device; + EXPECT_TRUE(dm->Init()); + EXPECT_FALSE(dm->GetVideoCaptureDevice("_NOT A REAL DEVICE_", &device)); + std::vector<Device> video_ins; + EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins)); + if (!video_ins.empty()) { + // Get the default device with the parameter kDefaultDeviceName. + EXPECT_TRUE(dm->GetVideoCaptureDevice( + cricket::DeviceManagerInterface::kDefaultDeviceName, &device)); + + // Get the first device with the parameter video_ins[0].name. + EXPECT_TRUE(dm->GetVideoCaptureDevice(video_ins[0].name, &device)); + EXPECT_EQ(device.name, video_ins[0].name); + EXPECT_EQ(device.id, video_ins[0].id); + } +} + +TEST(DeviceManagerTest, GetVideoDeviceIds_File) { + scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create()); + EXPECT_TRUE(dm->Init()); + Device device; + const std::string test_file = + cricket::GetTestFilePath("captured-320x240-2s-48.frames"); + EXPECT_TRUE(dm->GetVideoCaptureDevice(test_file, &device)); + EXPECT_TRUE(cricket::FileVideoCapturer::IsFileVideoCapturerDevice(device)); +} + +TEST(DeviceManagerTest, VerifyDevicesListsAreCleared) { + const std::string imaginary("_NOT A REAL DEVICE_"); + scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create()); + std::vector<Device> audio_ins, audio_outs, video_ins; + audio_ins.push_back(Device(imaginary, imaginary)); + audio_outs.push_back(Device(imaginary, imaginary)); + video_ins.push_back(Device(imaginary, imaginary)); + EXPECT_TRUE(dm->Init()); + EXPECT_TRUE(dm->GetAudioInputDevices(&audio_ins)); + EXPECT_TRUE(dm->GetAudioOutputDevices(&audio_outs)); + EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins)); + for (size_t i = 0; i < audio_ins.size(); ++i) { + EXPECT_NE(imaginary, audio_ins[i].name); + } + for (size_t i = 0; i < audio_outs.size(); ++i) { + EXPECT_NE(imaginary, audio_outs[i].name); + } + for (size_t i = 0; i < video_ins.size(); ++i) { + EXPECT_NE(imaginary, video_ins[i].name); + } +} + +static bool CompareDeviceList(std::vector<Device>& devices, + const char* const device_list[], int list_size) { + if (list_size != static_cast<int>(devices.size())) { + return false; + } + for (int i = 0; i < list_size; ++i) { + if (devices[i].name.compare(device_list[i]) != 0) { + return false; + } + } + return true; +} + +TEST(DeviceManagerTest, VerifyFilterDevices) { + static const char* const kTotalDevicesName[] = { + "Google Camera Adapters are tons of fun.", + "device1", + "device2", + "device3", + "device4", + "device5", + "Google Camera Adapter 0", + "Google Camera Adapter 1", + }; + static const char* const kFilteredDevicesName[] = { + "device2", + "device4", + "Google Camera Adapter", + NULL, + }; + static const char* const kDevicesName[] = { + "device1", + "device3", + "device5", + }; + std::vector<Device> devices; + for (int i = 0; i < ARRAY_SIZE(kTotalDevicesName); ++i) { + devices.push_back(Device(kTotalDevicesName[i], i)); + } + EXPECT_TRUE(CompareDeviceList(devices, kTotalDevicesName, + ARRAY_SIZE(kTotalDevicesName))); + // Return false if given NULL as the exclusion list. + EXPECT_TRUE(DeviceManager::FilterDevices(&devices, NULL)); + // The devices should not change. + EXPECT_TRUE(CompareDeviceList(devices, kTotalDevicesName, + ARRAY_SIZE(kTotalDevicesName))); + EXPECT_TRUE(DeviceManager::FilterDevices(&devices, kFilteredDevicesName)); + EXPECT_TRUE(CompareDeviceList(devices, kDevicesName, + ARRAY_SIZE(kDevicesName))); +} + +#ifdef LINUX +class FakeV4LLookup : public cricket::V4LLookup { + public: + explicit FakeV4LLookup(std::vector<std::string> device_paths) + : device_paths_(device_paths) {} + + protected: + bool CheckIsV4L2Device(const std::string& device) { + return std::find(device_paths_.begin(), device_paths_.end(), device) + != device_paths_.end(); + } + + private: + std::vector<std::string> device_paths_; +}; + +TEST(DeviceManagerTest, GetVideoCaptureDevices_K2_6) { + std::vector<std::string> devices; + devices.push_back("/dev/video0"); + devices.push_back("/dev/video5"); + cricket::V4LLookup::SetV4LLookup(new FakeV4LLookup(devices)); + + std::vector<rtc::FakeFileSystem::File> files; + files.push_back(rtc::FakeFileSystem::File("/dev/video0", "")); + files.push_back(rtc::FakeFileSystem::File("/dev/video5", "")); + files.push_back(rtc::FakeFileSystem::File( + "/sys/class/video4linux/video0/name", "Video Device 1")); + files.push_back(rtc::FakeFileSystem::File( + "/sys/class/video4linux/video1/model", "Bad Device")); + files.push_back( + rtc::FakeFileSystem::File("/sys/class/video4linux/video5/model", + "Video Device 2")); + rtc::FilesystemScope fs(new rtc::FakeFileSystem(files)); + + scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create()); + std::vector<Device> video_ins; + EXPECT_TRUE(dm->Init()); + EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins)); + EXPECT_EQ(2u, video_ins.size()); + EXPECT_EQ("Video Device 1", video_ins.at(0).name); + EXPECT_EQ("Video Device 2", video_ins.at(1).name); +} + +TEST(DeviceManagerTest, GetVideoCaptureDevices_K2_4) { + std::vector<std::string> devices; + devices.push_back("/dev/video0"); + devices.push_back("/dev/video5"); + cricket::V4LLookup::SetV4LLookup(new FakeV4LLookup(devices)); + + std::vector<rtc::FakeFileSystem::File> files; + files.push_back(rtc::FakeFileSystem::File("/dev/video0", "")); + files.push_back(rtc::FakeFileSystem::File("/dev/video5", "")); + files.push_back(rtc::FakeFileSystem::File( + "/proc/video/dev/video0", + "param1: value1\nname: Video Device 1\n param2: value2\n")); + files.push_back(rtc::FakeFileSystem::File( + "/proc/video/dev/video1", + "param1: value1\nname: Bad Device\n param2: value2\n")); + files.push_back(rtc::FakeFileSystem::File( + "/proc/video/dev/video5", + "param1: value1\nname: Video Device 2\n param2: value2\n")); + rtc::FilesystemScope fs(new rtc::FakeFileSystem(files)); + + scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create()); + std::vector<Device> video_ins; + EXPECT_TRUE(dm->Init()); + EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins)); + EXPECT_EQ(2u, video_ins.size()); + EXPECT_EQ("Video Device 1", video_ins.at(0).name); + EXPECT_EQ("Video Device 2", video_ins.at(1).name); +} + +TEST(DeviceManagerTest, GetVideoCaptureDevices_KUnknown) { + std::vector<std::string> devices; + devices.push_back("/dev/video0"); + devices.push_back("/dev/video5"); + cricket::V4LLookup::SetV4LLookup(new FakeV4LLookup(devices)); + + std::vector<rtc::FakeFileSystem::File> files; + files.push_back(rtc::FakeFileSystem::File("/dev/video0", "")); + files.push_back(rtc::FakeFileSystem::File("/dev/video1", "")); + files.push_back(rtc::FakeFileSystem::File("/dev/video5", "")); + rtc::FilesystemScope fs(new rtc::FakeFileSystem(files)); + + scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create()); + std::vector<Device> video_ins; + EXPECT_TRUE(dm->Init()); + EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins)); + EXPECT_EQ(2u, video_ins.size()); + EXPECT_EQ("/dev/video0", video_ins.at(0).name); + EXPECT_EQ("/dev/video5", video_ins.at(1).name); +} +#endif // LINUX + +// TODO(noahric): These are flaky on windows on headless machines. +#ifndef WIN32 +TEST(DeviceManagerTest, GetWindows) { + if (!rtc::WindowPickerFactory::IsSupported()) { + LOG(LS_INFO) << "skipping test: window capturing is not supported with " + << "current configuration."; + return; + } + scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create()); + dm->SetScreenCapturerFactory(new FakeScreenCapturerFactory()); + std::vector<rtc::WindowDescription> descriptions; + EXPECT_TRUE(dm->Init()); + if (!dm->GetWindows(&descriptions) || descriptions.empty()) { + LOG(LS_INFO) << "skipping test: window capturing. Does not have any " + << "windows to capture."; + return; + } + scoped_ptr<cricket::VideoCapturer> capturer(dm->CreateScreenCapturer( + cricket::ScreencastId(descriptions.front().id()))); + EXPECT_FALSE(capturer.get() == NULL); + // TODO(hellner): creating a window capturer and immediately deleting it + // results in "Continuous Build and Test Mainline - Mac opt" failure (crash). + // Remove the following line as soon as this has been resolved. + rtc::Thread::Current()->ProcessMessages(1); +} + +TEST(DeviceManagerTest, GetDesktops) { + if (!rtc::WindowPickerFactory::IsSupported()) { + LOG(LS_INFO) << "skipping test: desktop capturing is not supported with " + << "current configuration."; + return; + } + scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create()); + dm->SetScreenCapturerFactory(new FakeScreenCapturerFactory()); + std::vector<rtc::DesktopDescription> descriptions; + EXPECT_TRUE(dm->Init()); + if (!dm->GetDesktops(&descriptions) || descriptions.empty()) { + LOG(LS_INFO) << "skipping test: desktop capturing. Does not have any " + << "desktops to capture."; + return; + } + scoped_ptr<cricket::VideoCapturer> capturer(dm->CreateScreenCapturer( + cricket::ScreencastId(descriptions.front().id()))); + EXPECT_FALSE(capturer.get() == NULL); +} +#endif // !WIN32 + +TEST_F(DeviceManagerTestFake, CaptureConstraintsWhitelisted) { + const Device device("white", "white_id"); + dm_->SetVideoCaptureDeviceMaxFormat(device.name, kHdFormat); + scoped_ptr<cricket::VideoCapturer> capturer( + dm_->CreateVideoCapturer(device)); + cricket::VideoFormat best_format; + capturer->set_enable_camera_list(true); + EXPECT_TRUE(capturer->GetBestCaptureFormat(kHdFormat, &best_format)); + EXPECT_EQ(kHdFormat, best_format); +} + +TEST_F(DeviceManagerTestFake, CaptureConstraintsNotWhitelisted) { + const Device device("regular", "regular_id"); + scoped_ptr<cricket::VideoCapturer> capturer( + dm_->CreateVideoCapturer(device)); + cricket::VideoFormat best_format; + capturer->set_enable_camera_list(true); + EXPECT_TRUE(capturer->GetBestCaptureFormat(kHdFormat, &best_format)); + EXPECT_EQ(kHdFormat, best_format); +} + +TEST_F(DeviceManagerTestFake, CaptureConstraintsUnWhitelisted) { + const Device device("un_white", "un_white_id"); + dm_->SetVideoCaptureDeviceMaxFormat(device.name, kHdFormat); + dm_->ClearVideoCaptureDeviceMaxFormat(device.name); + scoped_ptr<cricket::VideoCapturer> capturer( + dm_->CreateVideoCapturer(device)); + cricket::VideoFormat best_format; + capturer->set_enable_camera_list(true); + EXPECT_TRUE(capturer->GetBestCaptureFormat(kHdFormat, &best_format)); + EXPECT_EQ(kHdFormat, best_format); +} + +TEST_F(DeviceManagerTestFake, CaptureConstraintsWildcard) { + const Device device("any_device", "any_device"); + dm_->SetVideoCaptureDeviceMaxFormat("*", kHdFormat); + scoped_ptr<cricket::VideoCapturer> capturer( + dm_->CreateVideoCapturer(device)); + cricket::VideoFormat best_format; + capturer->set_enable_camera_list(true); + EXPECT_TRUE(capturer->GetBestCaptureFormat(kHdFormat, &best_format)); + EXPECT_EQ(kHdFormat, best_format); +} diff --git a/talk/media/devices/dummydevicemanager.cc b/talk/media/devices/dummydevicemanager.cc new file mode 100644 index 0000000000..736258b752 --- /dev/null +++ b/talk/media/devices/dummydevicemanager.cc @@ -0,0 +1,38 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/dummydevicemanager.h" + +namespace cricket { + +const char DeviceManagerInterface::kDefaultDeviceName[] = ""; + +DeviceManagerInterface* DeviceManagerFactory::Create() { + return new DummyDeviceManager(); +} + +}; // namespace cricket diff --git a/talk/media/devices/dummydevicemanager.h b/talk/media/devices/dummydevicemanager.h new file mode 100644 index 0000000000..7da81853c7 --- /dev/null +++ b/talk/media/devices/dummydevicemanager.h @@ -0,0 +1,51 @@ +/* + * libjingle + * Copyright 2011 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_DEVICES_DUMMYDEVICEMANAGER_H_ +#define TALK_MEDIA_DEVICES_DUMMYDEVICEMANAGER_H_ + +#include <vector> + +#include "talk/media/base/mediacommon.h" +#include "talk/media/devices/fakedevicemanager.h" + +namespace cricket { + +class DummyDeviceManager : public FakeDeviceManager { + public: + DummyDeviceManager() { + std::vector<std::string> devices; + devices.push_back(DeviceManagerInterface::kDefaultDeviceName); + SetAudioInputDevices(devices); + SetAudioOutputDevices(devices); + SetVideoCaptureDevices(devices); + } +}; + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_DUMMYDEVICEMANAGER_H_ diff --git a/talk/media/devices/dummydevicemanager_unittest.cc b/talk/media/devices/dummydevicemanager_unittest.cc new file mode 100644 index 0000000000..9f4eb41b53 --- /dev/null +++ b/talk/media/devices/dummydevicemanager_unittest.cc @@ -0,0 +1,104 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/dummydevicemanager.h" +#include "webrtc/base/gunit.h" + +using cricket::Device; +using cricket::DummyDeviceManager; + +// Test that we startup/shutdown properly. +TEST(DummyDeviceManagerTest, StartupShutdown) { + DummyDeviceManager dm; + EXPECT_TRUE(dm.Init()); + dm.Terminate(); +} + +// Test enumerating capabilities. +TEST(DummyDeviceManagerTest, GetCapabilities) { + DummyDeviceManager dm; + int capabilities = dm.GetCapabilities(); + EXPECT_EQ((cricket::AUDIO_SEND | cricket::AUDIO_RECV | + cricket::VIDEO_SEND | cricket::VIDEO_RECV), capabilities); +} + +// Test enumerating devices. +TEST(DummyDeviceManagerTest, GetDevices) { + DummyDeviceManager dm; + EXPECT_TRUE(dm.Init()); + std::vector<Device> audio_ins, audio_outs, video_ins; + EXPECT_TRUE(dm.GetAudioInputDevices(&audio_ins)); + EXPECT_TRUE(dm.GetAudioOutputDevices(&audio_outs)); + EXPECT_TRUE(dm.GetVideoCaptureDevices(&video_ins)); +} + +// Test that we return correct ids for default and bogus devices. +TEST(DummyDeviceManagerTest, GetAudioDeviceIds) { + DummyDeviceManager dm; + Device device; + EXPECT_TRUE(dm.Init()); + EXPECT_TRUE(dm.GetAudioInputDevice( + cricket::DeviceManagerInterface::kDefaultDeviceName, &device)); + EXPECT_EQ("-1", device.id); + EXPECT_TRUE(dm.GetAudioOutputDevice( + cricket::DeviceManagerInterface::kDefaultDeviceName, &device)); + EXPECT_EQ("-1", device.id); + EXPECT_FALSE(dm.GetAudioInputDevice("_NOT A REAL DEVICE_", &device)); + EXPECT_FALSE(dm.GetAudioOutputDevice("_NOT A REAL DEVICE_", &device)); +} + +// Test that we get the video capture device by name properly. +TEST(DummyDeviceManagerTest, GetVideoDeviceIds) { + DummyDeviceManager dm; + Device device; + EXPECT_TRUE(dm.Init()); + EXPECT_FALSE(dm.GetVideoCaptureDevice("_NOT A REAL DEVICE_", &device)); + EXPECT_TRUE(dm.GetVideoCaptureDevice( + cricket::DeviceManagerInterface::kDefaultDeviceName, &device)); +} + +TEST(DummyDeviceManagerTest, VerifyDevicesListsAreCleared) { + const std::string imaginary("_NOT A REAL DEVICE_"); + DummyDeviceManager dm; + std::vector<Device> audio_ins, audio_outs, video_ins; + audio_ins.push_back(Device(imaginary, imaginary)); + audio_outs.push_back(Device(imaginary, imaginary)); + video_ins.push_back(Device(imaginary, imaginary)); + EXPECT_TRUE(dm.Init()); + EXPECT_TRUE(dm.GetAudioInputDevices(&audio_ins)); + EXPECT_TRUE(dm.GetAudioOutputDevices(&audio_outs)); + EXPECT_TRUE(dm.GetVideoCaptureDevices(&video_ins)); + for (size_t i = 0; i < audio_ins.size(); ++i) { + EXPECT_NE(imaginary, audio_ins[i].name); + } + for (size_t i = 0; i < audio_outs.size(); ++i) { + EXPECT_NE(imaginary, audio_outs[i].name); + } + for (size_t i = 0; i < video_ins.size(); ++i) { + EXPECT_NE(imaginary, video_ins[i].name); + } +} diff --git a/talk/media/devices/fakedevicemanager.h b/talk/media/devices/fakedevicemanager.h new file mode 100644 index 0000000000..a4b2b86e44 --- /dev/null +++ b/talk/media/devices/fakedevicemanager.h @@ -0,0 +1,239 @@ +/* + * libjingle + * Copyright 2008 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_DEVICES_FAKEDEVICEMANAGER_H_ +#define TALK_MEDIA_DEVICES_FAKEDEVICEMANAGER_H_ + +#include <string> +#include <vector> + +#include "talk/media/base/fakevideocapturer.h" +#include "talk/media/base/mediacommon.h" +#include "talk/media/devices/devicemanager.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/window.h" +#include "webrtc/base/windowpicker.h" + +namespace cricket { + +class FakeDeviceManager : public DeviceManagerInterface { + public: + FakeDeviceManager() {} + virtual bool Init() { + return true; + } + virtual void Terminate() { + } + virtual int GetCapabilities() { + std::vector<Device> devices; + int caps = VIDEO_RECV; + if (!input_devices_.empty()) { + caps |= AUDIO_SEND; + } + if (!output_devices_.empty()) { + caps |= AUDIO_RECV; + } + if (!vidcap_devices_.empty()) { + caps |= VIDEO_SEND; + } + return caps; + } + virtual bool GetAudioInputDevices(std::vector<Device>* devs) { + *devs = input_devices_; + return true; + } + virtual bool GetAudioOutputDevices(std::vector<Device>* devs) { + *devs = output_devices_; + return true; + } + virtual bool GetAudioInputDevice(const std::string& name, Device* out) { + return GetAudioDevice(true, name, out); + } + virtual bool GetAudioOutputDevice(const std::string& name, Device* out) { + return GetAudioDevice(false, name, out); + } + virtual bool GetVideoCaptureDevices(std::vector<Device>* devs) { + *devs = vidcap_devices_; + return true; + } + virtual void SetVideoDeviceCapturerFactory( + VideoDeviceCapturerFactory* video_device_capturer_factory) { + } + virtual void SetScreenCapturerFactory( + ScreenCapturerFactory* screen_capturer_factory) { + screen_capturer_factory_.reset(screen_capturer_factory); + } + virtual void SetVideoCaptureDeviceMaxFormat(const std::string& usb_id, + const VideoFormat& max_format) { + max_formats_[usb_id] = max_format; + } + bool IsMaxFormatForDevice(const std::string& usb_id, + const VideoFormat& max_format) const { + std::map<std::string, VideoFormat>::const_iterator found = + max_formats_.find(usb_id); + return (found != max_formats_.end()) ? + max_format == found->second : + false; + } + virtual void ClearVideoCaptureDeviceMaxFormat(const std::string& usb_id) { + max_formats_.erase(usb_id); + } + virtual VideoCapturer* CreateVideoCapturer(const Device& device) const { + return new FakeVideoCapturer(); + } + virtual VideoCapturer* CreateScreenCapturer( + const ScreencastId& screenid) const { + if (!screen_capturer_factory_) { + return new FakeVideoCapturer(); + } + return screen_capturer_factory_->Create(screenid); + } + virtual bool GetWindows( + std::vector<rtc::WindowDescription>* descriptions) { + descriptions->clear(); + const uint32_t id = 1u; // Note that 0 is not a valid ID. + const rtc::WindowId window_id = + rtc::WindowId::Cast(id); + std::string title = "FakeWindow"; + rtc::WindowDescription window_description(window_id, title); + descriptions->push_back(window_description); + return true; + } + virtual VideoCapturer* CreateWindowCapturer(rtc::WindowId window) { + if (!window.IsValid()) { + return NULL; + } + return new FakeVideoCapturer; + } + virtual bool GetDesktops( + std::vector<rtc::DesktopDescription>* descriptions) { + descriptions->clear(); + const int id = 0; + const int valid_index = 0; + const rtc::DesktopId desktop_id = + rtc::DesktopId::Cast(id, valid_index); + std::string title = "FakeDesktop"; + rtc::DesktopDescription desktop_description(desktop_id, title); + descriptions->push_back(desktop_description); + return true; + } + virtual VideoCapturer* CreateDesktopCapturer(rtc::DesktopId desktop) { + if (!desktop.IsValid()) { + return NULL; + } + return new FakeVideoCapturer; + } + + virtual bool GetDefaultVideoCaptureDevice(Device* device) { + if (vidcap_devices_.empty()) { + return false; + } + *device = vidcap_devices_[0]; + return true; + } + +#ifdef OSX + bool QtKitToSgDevice(const std::string& qtkit_name, Device* out) { + out->name = qtkit_name; + out->id = "sg:" + qtkit_name; + return true; + } +#endif + + void SetAudioInputDevices(const std::vector<std::string>& devices) { + input_devices_.clear(); + for (size_t i = 0; i < devices.size(); ++i) { + input_devices_.push_back(Device(devices[i], + static_cast<int>(i))); + } + SignalDevicesChange(); + } + void SetAudioOutputDevices(const std::vector<std::string>& devices) { + output_devices_.clear(); + for (size_t i = 0; i < devices.size(); ++i) { + output_devices_.push_back(Device(devices[i], + static_cast<int>(i))); + } + SignalDevicesChange(); + } + void SetVideoCaptureDevices(const std::vector<std::string>& devices) { + vidcap_devices_.clear(); + for (size_t i = 0; i < devices.size(); ++i) { + vidcap_devices_.push_back(Device(devices[i], + static_cast<int>(i))); + } + SignalDevicesChange(); + } + virtual bool GetVideoCaptureDevice(const std::string& name, + Device* out) { + if (vidcap_devices_.empty()) + return false; + + // If the name is empty, return the default device. + if (name.empty() || name == kDefaultDeviceName) { + *out = vidcap_devices_[0]; + return true; + } + + return FindDeviceByName(vidcap_devices_, name, out); + } + bool GetAudioDevice(bool is_input, const std::string& name, + Device* out) { + // If the name is empty, return the default device. + if (name.empty() || name == kDefaultDeviceName) { + *out = Device(name, -1); + return true; + } + + return FindDeviceByName((is_input ? input_devices_ : output_devices_), + name, out); + } + static bool FindDeviceByName(const std::vector<Device>& devices, + const std::string& name, + Device* out) { + for (std::vector<Device>::const_iterator it = devices.begin(); + it != devices.end(); ++it) { + if (name == it->name) { + *out = *it; + return true; + } + } + return false; + } + + private: + std::vector<Device> input_devices_; + std::vector<Device> output_devices_; + std::vector<Device> vidcap_devices_; + std::map<std::string, VideoFormat> max_formats_; + rtc::scoped_ptr< + ScreenCapturerFactory> screen_capturer_factory_; +}; + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_FAKEDEVICEMANAGER_H_ diff --git a/talk/media/devices/filevideocapturer.cc b/talk/media/devices/filevideocapturer.cc new file mode 100644 index 0000000000..72398e0b88 --- /dev/null +++ b/talk/media/devices/filevideocapturer.cc @@ -0,0 +1,385 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Implementation of VideoRecorder and FileVideoCapturer. + +#include "talk/media/devices/filevideocapturer.h" + +#include "webrtc/base/bytebuffer.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +///////////////////////////////////////////////////////////////////// +// Implementation of class VideoRecorder +///////////////////////////////////////////////////////////////////// +bool VideoRecorder::Start(const std::string& filename, bool write_header) { + Stop(); + write_header_ = write_header; + int err; + if (!video_file_.Open(filename, "wb", &err)) { + LOG(LS_ERROR) << "Unable to open file " << filename << " err=" << err; + return false; + } + return true; +} + +void VideoRecorder::Stop() { + video_file_.Close(); +} + +bool VideoRecorder::RecordFrame(const CapturedFrame& frame) { + if (rtc::SS_CLOSED == video_file_.GetState()) { + LOG(LS_ERROR) << "File not opened yet"; + return false; + } + + uint32_t size = 0; + if (!frame.GetDataSize(&size)) { + LOG(LS_ERROR) << "Unable to calculate the data size of the frame"; + return false; + } + + if (write_header_) { + // Convert the frame header to bytebuffer. + rtc::ByteBuffer buffer; + buffer.WriteUInt32(frame.width); + buffer.WriteUInt32(frame.height); + buffer.WriteUInt32(frame.fourcc); + buffer.WriteUInt32(frame.pixel_width); + buffer.WriteUInt32(frame.pixel_height); + // Elapsed time is deprecated. + const uint64_t dummy_elapsed_time = 0; + buffer.WriteUInt64(dummy_elapsed_time); + buffer.WriteUInt64(frame.time_stamp); + buffer.WriteUInt32(size); + + // Write the bytebuffer to file. + if (rtc::SR_SUCCESS != video_file_.Write(buffer.Data(), + buffer.Length(), + NULL, + NULL)) { + LOG(LS_ERROR) << "Failed to write frame header"; + return false; + } + } + // Write the frame data to file. + if (rtc::SR_SUCCESS != video_file_.Write(frame.data, + size, + NULL, + NULL)) { + LOG(LS_ERROR) << "Failed to write frame data"; + return false; + } + + return true; +} + +/////////////////////////////////////////////////////////////////////// +// Definition of private class FileReadThread that periodically reads +// frames from a file. +/////////////////////////////////////////////////////////////////////// +class FileVideoCapturer::FileReadThread + : public rtc::Thread, public rtc::MessageHandler { + public: + explicit FileReadThread(FileVideoCapturer* capturer) + : capturer_(capturer), + finished_(false) { + } + + virtual ~FileReadThread() { + Stop(); + } + + // Override virtual method of parent Thread. Context: Worker Thread. + virtual void Run() { + // Read the first frame and start the message pump. The pump runs until + // Stop() is called externally or Quit() is called by OnMessage(). + int waiting_time_ms = 0; + if (capturer_ && capturer_->ReadFrame(true, &waiting_time_ms)) { + PostDelayed(waiting_time_ms, this); + Thread::Run(); + } + + rtc::CritScope cs(&crit_); + finished_ = true; + } + + // Override virtual method of parent MessageHandler. Context: Worker Thread. + virtual void OnMessage(rtc::Message* /*pmsg*/) { + int waiting_time_ms = 0; + if (capturer_ && capturer_->ReadFrame(false, &waiting_time_ms)) { + PostDelayed(waiting_time_ms, this); + } else { + Quit(); + } + } + + // Check if Run() is finished. + bool Finished() const { + rtc::CritScope cs(&crit_); + return finished_; + } + + private: + FileVideoCapturer* capturer_; + mutable rtc::CriticalSection crit_; + bool finished_; + + RTC_DISALLOW_COPY_AND_ASSIGN(FileReadThread); +}; + +///////////////////////////////////////////////////////////////////// +// Implementation of class FileVideoCapturer +///////////////////////////////////////////////////////////////////// +static const int64_t kNumNanoSecsPerMilliSec = 1000000; +const char* FileVideoCapturer::kVideoFileDevicePrefix = "video-file:"; + +FileVideoCapturer::FileVideoCapturer() + : frame_buffer_size_(0), + file_read_thread_(NULL), + repeat_(0), + last_frame_timestamp_ns_(0), + ignore_framerate_(false) { +} + +FileVideoCapturer::~FileVideoCapturer() { + Stop(); + delete[] static_cast<char*>(captured_frame_.data); +} + +bool FileVideoCapturer::Init(const Device& device) { + if (!FileVideoCapturer::IsFileVideoCapturerDevice(device)) { + return false; + } + std::string filename(device.name); + if (IsRunning()) { + LOG(LS_ERROR) << "The file video capturer is already running"; + return false; + } + // Open the file. + int err; + if (!video_file_.Open(filename, "rb", &err)) { + LOG(LS_ERROR) << "Unable to open the file " << filename << " err=" << err; + return false; + } + // Read the first frame's header to determine the supported format. + CapturedFrame frame; + if (rtc::SR_SUCCESS != ReadFrameHeader(&frame)) { + LOG(LS_ERROR) << "Failed to read the first frame header"; + video_file_.Close(); + return false; + } + // Seek back to the start of the file. + if (!video_file_.SetPosition(0)) { + LOG(LS_ERROR) << "Failed to seek back to beginning of the file"; + video_file_.Close(); + return false; + } + + // Enumerate the supported formats. We have only one supported format. We set + // the frame interval to kMinimumInterval here. In Start(), if the capture + // format's interval is greater than kMinimumInterval, we use the interval; + // otherwise, we use the timestamp in the file to control the interval. + VideoFormat format(frame.width, frame.height, VideoFormat::kMinimumInterval, + frame.fourcc); + std::vector<VideoFormat> supported; + supported.push_back(format); + + // TODO(thorcarpenter): Report the actual file video format as the supported + // format. Do not use kMinimumInterval as it conflicts with video adaptation. + SetId(device.id); + SetSupportedFormats(supported); + + // TODO(wuwang): Design an E2E integration test for video adaptation, + // then remove the below call to disable the video adapter. + set_enable_video_adapter(false); + return true; +} + +bool FileVideoCapturer::Init(const std::string& filename) { + return Init(FileVideoCapturer::CreateFileVideoCapturerDevice(filename)); +} + +CaptureState FileVideoCapturer::Start(const VideoFormat& capture_format) { + if (IsRunning()) { + LOG(LS_ERROR) << "The file video capturer is already running"; + return CS_FAILED; + } + + if (rtc::SS_CLOSED == video_file_.GetState()) { + LOG(LS_ERROR) << "File not opened yet"; + return CS_NO_DEVICE; + } else if (!video_file_.SetPosition(0)) { + LOG(LS_ERROR) << "Failed to seek back to beginning of the file"; + return CS_FAILED; + } + + SetCaptureFormat(&capture_format); + // Create a thread to read the file. + file_read_thread_ = new FileReadThread(this); + bool ret = file_read_thread_->Start(); + if (ret) { + LOG(LS_INFO) << "File video capturer '" << GetId() << "' started"; + return CS_RUNNING; + } else { + LOG(LS_ERROR) << "File video capturer '" << GetId() << "' failed to start"; + return CS_FAILED; + } +} + +bool FileVideoCapturer::IsRunning() { + return file_read_thread_ && !file_read_thread_->Finished(); +} + +void FileVideoCapturer::Stop() { + if (file_read_thread_) { + file_read_thread_->Stop(); + file_read_thread_ = NULL; + LOG(LS_INFO) << "File video capturer '" << GetId() << "' stopped"; + } + SetCaptureFormat(NULL); +} + +bool FileVideoCapturer::GetPreferredFourccs(std::vector<uint32_t>* fourccs) { + if (!fourccs) { + return false; + } + + fourccs->push_back(GetSupportedFormats()->at(0).fourcc); + return true; +} + +rtc::StreamResult FileVideoCapturer::ReadFrameHeader( + CapturedFrame* frame) { + // We first read kFrameHeaderSize bytes from the file stream to a memory + // buffer, then construct a bytebuffer from the memory buffer, and finally + // read the frame header from the bytebuffer. + char header[CapturedFrame::kFrameHeaderSize]; + rtc::StreamResult sr; + size_t bytes_read; + int error; + sr = video_file_.Read(header, + CapturedFrame::kFrameHeaderSize, + &bytes_read, + &error); + LOG(LS_VERBOSE) << "Read frame header: stream_result = " << sr + << ", bytes read = " << bytes_read << ", error = " << error; + if (rtc::SR_SUCCESS == sr) { + if (CapturedFrame::kFrameHeaderSize != bytes_read) { + return rtc::SR_EOS; + } + rtc::ByteBuffer buffer(header, CapturedFrame::kFrameHeaderSize); + buffer.ReadUInt32(reinterpret_cast<uint32_t*>(&frame->width)); + buffer.ReadUInt32(reinterpret_cast<uint32_t*>(&frame->height)); + buffer.ReadUInt32(&frame->fourcc); + buffer.ReadUInt32(&frame->pixel_width); + buffer.ReadUInt32(&frame->pixel_height); + // Elapsed time is deprecated. + uint64_t dummy_elapsed_time; + buffer.ReadUInt64(&dummy_elapsed_time); + buffer.ReadUInt64(reinterpret_cast<uint64_t*>(&frame->time_stamp)); + buffer.ReadUInt32(&frame->data_size); + } + + return sr; +} + +// Executed in the context of FileReadThread. +bool FileVideoCapturer::ReadFrame(bool first_frame, int* wait_time_ms) { + uint32_t start_read_time_ms = rtc::Time(); + + // 1. Signal the previously read frame to downstream. + if (!first_frame) { + captured_frame_.time_stamp = + kNumNanoSecsPerMilliSec * static_cast<int64_t>(start_read_time_ms); + SignalFrameCaptured(this, &captured_frame_); + } + + // 2. Read the next frame. + if (rtc::SS_CLOSED == video_file_.GetState()) { + LOG(LS_ERROR) << "File not opened yet"; + return false; + } + // 2.1 Read the frame header. + rtc::StreamResult result = ReadFrameHeader(&captured_frame_); + if (rtc::SR_EOS == result) { // Loop back if repeat. + if (repeat_ != kForever) { + if (repeat_ > 0) { + --repeat_; + } else { + return false; + } + } + + if (video_file_.SetPosition(0)) { + result = ReadFrameHeader(&captured_frame_); + } + } + if (rtc::SR_SUCCESS != result) { + LOG(LS_ERROR) << "Failed to read the frame header"; + return false; + } + // 2.2 Reallocate memory for the frame data if necessary. + if (frame_buffer_size_ < captured_frame_.data_size) { + frame_buffer_size_ = captured_frame_.data_size; + delete[] static_cast<char*>(captured_frame_.data); + captured_frame_.data = new char[frame_buffer_size_]; + } + // 2.3 Read the frame adata. + if (rtc::SR_SUCCESS != video_file_.Read(captured_frame_.data, + captured_frame_.data_size, + NULL, NULL)) { + LOG(LS_ERROR) << "Failed to read frame data"; + return false; + } + + // 3. Decide how long to wait for the next frame. + *wait_time_ms = 0; + + // If the capture format's interval is not kMinimumInterval, we use it to + // control the rate; otherwise, we use the timestamp in the file to control + // the rate. + if (!first_frame && !ignore_framerate_) { + int64_t interval_ns = + GetCaptureFormat()->interval > VideoFormat::kMinimumInterval + ? GetCaptureFormat()->interval + : captured_frame_.time_stamp - last_frame_timestamp_ns_; + int interval_ms = static_cast<int>(interval_ns / kNumNanoSecsPerMilliSec); + interval_ms -= rtc::Time() - start_read_time_ms; + if (interval_ms > 0) { + *wait_time_ms = interval_ms; + } + } + // Keep the original timestamp read from the file. + last_frame_timestamp_ns_ = captured_frame_.time_stamp; + return true; +} + +} // namespace cricket diff --git a/talk/media/devices/filevideocapturer.h b/talk/media/devices/filevideocapturer.h new file mode 100644 index 0000000000..cc41c39b5d --- /dev/null +++ b/talk/media/devices/filevideocapturer.h @@ -0,0 +1,160 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// +// This file contains two classes, VideoRecorder and FileVideoCapturer. +// VideoRecorder records the captured frames into a file. The file stores a +// sequence of captured frames; each frame has a header defined in struct +// CapturedFrame, followed by the frame data. +// +// FileVideoCapturer, a subclass of VideoCapturer, is a simulated video capturer +// that periodically reads images from a previously recorded file. + +#ifndef TALK_MEDIA_DEVICES_FILEVIDEOCAPTURER_H_ +#define TALK_MEDIA_DEVICES_FILEVIDEOCAPTURER_H_ + +#include <string> +#include <vector> + +#include "talk/media/base/videocapturer.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringutils.h" + +namespace rtc { +class FileStream; +} + +namespace cricket { + +// Utility class to record the frames captured by a video capturer into a file. +class VideoRecorder { + public: + VideoRecorder() {} + ~VideoRecorder() { Stop(); } + + // Start the recorder by opening the specified file. Return true if the file + // is opened successfully. write_header should normally be true; false means + // write raw frame pixel data to file without any headers. + bool Start(const std::string& filename, bool write_header); + // Stop the recorder by closing the file. + void Stop(); + // Record a video frame to the file. Return true if the frame is written to + // the file successfully. This method needs to be called after Start() and + // before Stop(). + bool RecordFrame(const CapturedFrame& frame); + + private: + rtc::FileStream video_file_; + bool write_header_; + + RTC_DISALLOW_COPY_AND_ASSIGN(VideoRecorder); +}; + +// Simulated video capturer that periodically reads frames from a file. +class FileVideoCapturer : public VideoCapturer { + public: + static const int kForever = -1; + + FileVideoCapturer(); + virtual ~FileVideoCapturer(); + + // Determines if the given device is actually a video file, to be captured + // with a FileVideoCapturer. + static bool IsFileVideoCapturerDevice(const Device& device) { + return rtc::starts_with(device.id.c_str(), kVideoFileDevicePrefix); + } + + // Creates a fake device for the given filename. + static Device CreateFileVideoCapturerDevice(const std::string& filename) { + std::stringstream id; + id << kVideoFileDevicePrefix << filename; + return Device(filename, id.str()); + } + + // Set how many times to repeat reading the file. Repeat forever if the + // parameter is kForever; no repeat if the parameter is 0 or + // less than -1. + void set_repeat(int repeat) { repeat_ = repeat; } + + // If ignore_framerate is true, file is read as quickly as possible. If + // false, read rate is controlled by the timestamps in the video file + // (thus simulating camera capture). Default value set to false. + void set_ignore_framerate(bool ignore_framerate) { + ignore_framerate_ = ignore_framerate; + } + + // Initializes the capturer with the given file. + bool Init(const std::string& filename); + + // Initializes the capturer with the given device. This should only be used + // if IsFileVideoCapturerDevice returned true for the given device. + bool Init(const Device& device); + + // Override virtual methods of parent class VideoCapturer. + virtual CaptureState Start(const VideoFormat& capture_format); + virtual void Stop(); + virtual bool IsRunning(); + virtual bool IsScreencast() const { return false; } + + protected: + // Override virtual methods of parent class VideoCapturer. + virtual bool GetPreferredFourccs(std::vector<uint32_t>* fourccs); + + // Read the frame header from the file stream, video_file_. + rtc::StreamResult ReadFrameHeader(CapturedFrame* frame); + + // Read a frame and determine how long to wait for the next frame. If the + // frame is read successfully, Set the output parameter, wait_time_ms and + // return true. Otherwise, do not change wait_time_ms and return false. + bool ReadFrame(bool first_frame, int* wait_time_ms); + + // Return the CapturedFrame - useful for extracting contents after reading + // a frame. Should be used only while still reading a file (i.e. only while + // the CapturedFrame object still exists). + const CapturedFrame* frame() const { + return &captured_frame_; + } + + private: + class FileReadThread; // Forward declaration, defined in .cc. + + static const char* kVideoFileDevicePrefix; + rtc::FileStream video_file_; + CapturedFrame captured_frame_; + // The number of bytes allocated buffer for captured_frame_.data. + uint32_t frame_buffer_size_; + FileReadThread* file_read_thread_; + int repeat_; // How many times to repeat the file. + int64_t last_frame_timestamp_ns_; // Timestamp of last read frame. + bool ignore_framerate_; + + RTC_DISALLOW_COPY_AND_ASSIGN(FileVideoCapturer); +}; + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_FILEVIDEOCAPTURER_H_ diff --git a/talk/media/devices/filevideocapturer_unittest.cc b/talk/media/devices/filevideocapturer_unittest.cc new file mode 100644 index 0000000000..ccd2407214 --- /dev/null +++ b/talk/media/devices/filevideocapturer_unittest.cc @@ -0,0 +1,204 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> + +#include <string> +#include <vector> + +#include "talk/media/base/testutils.h" +#include "talk/media/devices/filevideocapturer.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" + +namespace { + +class FileVideoCapturerTest : public testing::Test { + public: + virtual void SetUp() { + capturer_.reset(new cricket::FileVideoCapturer); + } + + bool OpenFile(const std::string& filename) { + return capturer_->Init(cricket::GetTestFilePath(filename)); + } + + protected: + class VideoCapturerListener : public sigslot::has_slots<> { + public: + VideoCapturerListener() + : frame_count_(0), + frame_width_(0), + frame_height_(0), + resolution_changed_(false) { + } + + void OnFrameCaptured(cricket::VideoCapturer* capturer, + const cricket::CapturedFrame* frame) { + ++frame_count_; + if (1 == frame_count_) { + frame_width_ = frame->width; + frame_height_ = frame->height; + } else if (frame_width_ != frame->width || + frame_height_ != frame->height) { + resolution_changed_ = true; + } + } + + int frame_count() const { return frame_count_; } + int frame_width() const { return frame_width_; } + int frame_height() const { return frame_height_; } + bool resolution_changed() const { return resolution_changed_; } + + private: + int frame_count_; + int frame_width_; + int frame_height_; + bool resolution_changed_; + }; + + rtc::scoped_ptr<cricket::FileVideoCapturer> capturer_; + cricket::VideoFormat capture_format_; +}; + +TEST_F(FileVideoCapturerTest, TestNotOpened) { + EXPECT_EQ("", capturer_->GetId()); + EXPECT_TRUE(capturer_->GetSupportedFormats()->empty()); + EXPECT_EQ(NULL, capturer_->GetCaptureFormat()); + EXPECT_FALSE(capturer_->IsRunning()); +} + +TEST_F(FileVideoCapturerTest, TestInvalidOpen) { + EXPECT_FALSE(OpenFile("NotmeNotme")); +} + +TEST_F(FileVideoCapturerTest, TestOpen) { + EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames")); + EXPECT_NE("", capturer_->GetId()); + EXPECT_TRUE(NULL != capturer_->GetSupportedFormats()); + EXPECT_EQ(1U, capturer_->GetSupportedFormats()->size()); + EXPECT_EQ(NULL, capturer_->GetCaptureFormat()); // not started yet + EXPECT_FALSE(capturer_->IsRunning()); +} + +TEST_F(FileVideoCapturerTest, TestLargeSmallDesiredFormat) { + EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames")); + // desired format with large resolution. + cricket::VideoFormat desired( + 3200, 2400, cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_ANY); + EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_)); + EXPECT_EQ(320, capture_format_.width); + EXPECT_EQ(240, capture_format_.height); + + // Desired format with small resolution. + desired.width = 0; + desired.height = 0; + EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_)); + EXPECT_EQ(320, capture_format_.width); + EXPECT_EQ(240, capture_format_.height); +} + +TEST_F(FileVideoCapturerTest, TestSupportedAsDesiredFormat) { + EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames")); + // desired format same as the capture format supported by the file + cricket::VideoFormat desired = capturer_->GetSupportedFormats()->at(0); + EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_)); + EXPECT_TRUE(desired == capture_format_); + + // desired format same as the supported capture format except the fourcc + desired.fourcc = cricket::FOURCC_ANY; + EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_)); + EXPECT_NE(capture_format_.fourcc, desired.fourcc); + + // desired format with minimum interval + desired.interval = cricket::VideoFormat::kMinimumInterval; + EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_)); +} + +TEST_F(FileVideoCapturerTest, TestNoRepeat) { + EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames")); + VideoCapturerListener listener; + capturer_->SignalFrameCaptured.connect( + &listener, &VideoCapturerListener::OnFrameCaptured); + capturer_->set_repeat(0); + capture_format_ = capturer_->GetSupportedFormats()->at(0); + EXPECT_EQ(cricket::CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE_WAIT(!capturer_->IsRunning(), 20000); + EXPECT_EQ(48, listener.frame_count()); +} + +TEST_F(FileVideoCapturerTest, TestRepeatForever) { + // Start the capturer_ with 50 fps and read no less than 150 frames. + EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames")); + VideoCapturerListener listener; + capturer_->SignalFrameCaptured.connect( + &listener, &VideoCapturerListener::OnFrameCaptured); + capturer_->set_repeat(cricket::FileVideoCapturer::kForever); + capture_format_ = capturer_->GetSupportedFormats()->at(0); + capture_format_.interval = cricket::VideoFormat::FpsToInterval(50); + EXPECT_EQ(cricket::CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE(NULL != capturer_->GetCaptureFormat()); + EXPECT_TRUE(capture_format_ == *capturer_->GetCaptureFormat()); + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener.frame_count() >= 150, 20000); + capturer_->Stop(); + EXPECT_FALSE(capturer_->IsRunning()); + EXPECT_GE(listener.frame_count(), 150); + EXPECT_FALSE(listener.resolution_changed()); + EXPECT_EQ(listener.frame_width(), capture_format_.width); + EXPECT_EQ(listener.frame_height(), capture_format_.height); +} + +// See: https://code.google.com/p/webrtc/issues/detail?id=2409 +TEST_F(FileVideoCapturerTest, DISABLED_TestPartialFrameHeader) { + EXPECT_TRUE(OpenFile("1.frame_plus_1.byte")); + VideoCapturerListener listener; + capturer_->SignalFrameCaptured.connect( + &listener, &VideoCapturerListener::OnFrameCaptured); + capturer_->set_repeat(0); + capture_format_ = capturer_->GetSupportedFormats()->at(0); + EXPECT_EQ(cricket::CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE_WAIT(!capturer_->IsRunning(), 1000); + EXPECT_EQ(1, listener.frame_count()); +} + +TEST_F(FileVideoCapturerTest, TestFileDevices) { + cricket::Device not_a_file("I'm a camera", "with an id"); + EXPECT_FALSE( + cricket::FileVideoCapturer::IsFileVideoCapturerDevice(not_a_file)); + const std::string test_file = + cricket::GetTestFilePath("captured-320x240-2s-48.frames"); + cricket::Device file_device = + cricket::FileVideoCapturer::CreateFileVideoCapturerDevice(test_file); + EXPECT_TRUE( + cricket::FileVideoCapturer::IsFileVideoCapturerDevice(file_device)); + EXPECT_TRUE(capturer_->Init(file_device)); + EXPECT_EQ(file_device.id, capturer_->GetId()); +} + +} // unnamed namespace diff --git a/talk/media/devices/gdivideorenderer.cc b/talk/media/devices/gdivideorenderer.cc new file mode 100644 index 0000000000..69453067e2 --- /dev/null +++ b/talk/media/devices/gdivideorenderer.cc @@ -0,0 +1,279 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Implementation of GdiVideoRenderer on Windows + +#ifdef WIN32 + +#include "talk/media/devices/gdivideorenderer.h" + +#include "talk/media/base/videocommon.h" +#include "talk/media/base/videoframe.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/win32window.h" + +namespace cricket { + +///////////////////////////////////////////////////////////////////////////// +// Definition of private class VideoWindow. We use a worker thread to manage +// the window. +///////////////////////////////////////////////////////////////////////////// +class GdiVideoRenderer::VideoWindow : public rtc::Win32Window { + public: + VideoWindow(int x, int y, int width, int height); + virtual ~VideoWindow(); + + // Called when the video size changes. If it is called the first time, we + // create and start the thread. Otherwise, we send kSetSizeMsg to the thread. + // Context: non-worker thread. + bool SetSize(int width, int height); + + // Called when a new frame is available. Upon this call, we send + // kRenderFrameMsg to the window thread. Context: non-worker thread. It may be + // better to pass RGB bytes to VideoWindow. However, we pass VideoFrame to put + // all the thread synchronization within VideoWindow. + bool RenderFrame(const VideoFrame* frame); + + protected: + // Override virtual method of rtc::Win32Window. Context: worker Thread. + virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, + LRESULT& result); + + private: + enum { kSetSizeMsg = WM_USER, kRenderFrameMsg}; + + class WindowThread : public rtc::Thread { + public: + explicit WindowThread(VideoWindow* window) : window_(window) {} + + virtual ~WindowThread() { + Stop(); + } + + // Override virtual method of rtc::Thread. Context: worker Thread. + virtual void Run() { + // Initialize the window + if (!window_ || !window_->Initialize()) { + return; + } + // Run the message loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + private: + VideoWindow* window_; + }; + + // Context: worker Thread. + bool Initialize(); + void OnPaint(); + void OnSize(int width, int height, bool frame_changed); + void OnRenderFrame(const VideoFrame* frame); + + BITMAPINFO bmi_; + rtc::scoped_ptr<uint8_t[]> image_; + rtc::scoped_ptr<WindowThread> window_thread_; + // The initial position of the window. + int initial_x_; + int initial_y_; +}; + +///////////////////////////////////////////////////////////////////////////// +// Implementation of class VideoWindow +///////////////////////////////////////////////////////////////////////////// +GdiVideoRenderer::VideoWindow::VideoWindow( + int x, int y, int width, int height) + : initial_x_(x), + initial_y_(y) { + memset(&bmi_.bmiHeader, 0, sizeof(bmi_.bmiHeader)); + bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi_.bmiHeader.biPlanes = 1; + bmi_.bmiHeader.biBitCount = 32; + bmi_.bmiHeader.biCompression = BI_RGB; + bmi_.bmiHeader.biWidth = width; + bmi_.bmiHeader.biHeight = -height; + bmi_.bmiHeader.biSizeImage = width * height * 4; + + image_.reset(new uint8_t[bmi_.bmiHeader.biSizeImage]); +} + +GdiVideoRenderer::VideoWindow::~VideoWindow() { + // Context: caller Thread. We cannot call Destroy() since the window was + // created by another thread. Instead, we send WM_CLOSE message. + if (handle()) { + SendMessage(handle(), WM_CLOSE, 0, 0); + } +} + +bool GdiVideoRenderer::VideoWindow::SetSize(int width, int height) { + if (!window_thread_.get()) { + // Create and start the window thread. + window_thread_.reset(new WindowThread(this)); + return window_thread_->Start(); + } else if (width != bmi_.bmiHeader.biWidth || + height != -bmi_.bmiHeader.biHeight) { + SendMessage(handle(), kSetSizeMsg, 0, MAKELPARAM(width, height)); + } + return true; +} + +bool GdiVideoRenderer::VideoWindow::RenderFrame(const VideoFrame* video_frame) { + if (!handle()) { + return false; + } + + const VideoFrame* frame = video_frame->GetCopyWithRotationApplied(); + + if (!SetSize(static_cast<int>(frame->GetWidth()), + static_cast<int>(frame->GetHeight()))) { + return false; + } + + SendMessage(handle(), kRenderFrameMsg, reinterpret_cast<WPARAM>(frame), 0); + return true; +} + +bool GdiVideoRenderer::VideoWindow::OnMessage(UINT uMsg, WPARAM wParam, + LPARAM lParam, LRESULT& result) { + switch (uMsg) { + case WM_PAINT: + OnPaint(); + return true; + + case WM_DESTROY: + PostQuitMessage(0); // post WM_QUIT to end the message loop in Run() + return false; + + case WM_SIZE: // The window UI was resized. + OnSize(LOWORD(lParam), HIWORD(lParam), false); + return true; + + case kSetSizeMsg: // The video resolution changed. + OnSize(LOWORD(lParam), HIWORD(lParam), true); + return true; + + case kRenderFrameMsg: + OnRenderFrame(reinterpret_cast<const VideoFrame*>(wParam)); + return true; + } + return false; +} + +bool GdiVideoRenderer::VideoWindow::Initialize() { + if (!rtc::Win32Window::Create( + NULL, L"Video Renderer", + WS_OVERLAPPEDWINDOW | WS_SIZEBOX, + WS_EX_APPWINDOW, + initial_x_, initial_y_, + bmi_.bmiHeader.biWidth, -bmi_.bmiHeader.biHeight)) { + return false; + } + OnSize(bmi_.bmiHeader.biWidth, -bmi_.bmiHeader.biHeight, false); + return true; +} + +void GdiVideoRenderer::VideoWindow::OnPaint() { + RECT rcClient; + GetClientRect(handle(), &rcClient); + PAINTSTRUCT ps; + HDC hdc = BeginPaint(handle(), &ps); + StretchDIBits(hdc, + 0, 0, rcClient.right, rcClient.bottom, // destination rect + 0, 0, bmi_.bmiHeader.biWidth, -bmi_.bmiHeader.biHeight, // source rect + image_.get(), &bmi_, DIB_RGB_COLORS, SRCCOPY); + EndPaint(handle(), &ps); +} + +void GdiVideoRenderer::VideoWindow::OnSize(int width, int height, + bool frame_changed) { + // Get window and client sizes + RECT rcClient, rcWindow; + GetClientRect(handle(), &rcClient); + GetWindowRect(handle(), &rcWindow); + + // Find offset between window size and client size + POINT ptDiff; + ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; + ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; + + // Resize client + MoveWindow(handle(), rcWindow.left, rcWindow.top, + width + ptDiff.x, height + ptDiff.y, false); + UpdateWindow(handle()); + ShowWindow(handle(), SW_SHOW); + + if (frame_changed && (width != bmi_.bmiHeader.biWidth || + height != -bmi_.bmiHeader.biHeight)) { + // Update the bmi and image buffer + bmi_.bmiHeader.biWidth = width; + bmi_.bmiHeader.biHeight = -height; + bmi_.bmiHeader.biSizeImage = width * height * 4; + image_.reset(new uint8_t[bmi_.bmiHeader.biSizeImage]); + } +} + +void GdiVideoRenderer::VideoWindow::OnRenderFrame(const VideoFrame* frame) { + if (!frame) { + return; + } + // Convert frame to ARGB format, which is accepted by GDI + frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB, image_.get(), + bmi_.bmiHeader.biSizeImage, + bmi_.bmiHeader.biWidth * 4); + InvalidateRect(handle(), 0, 0); +} + +///////////////////////////////////////////////////////////////////////////// +// Implementation of class GdiVideoRenderer +///////////////////////////////////////////////////////////////////////////// +GdiVideoRenderer::GdiVideoRenderer(int x, int y) + : initial_x_(x), + initial_y_(y) { +} +GdiVideoRenderer::~GdiVideoRenderer() {} + +bool GdiVideoRenderer::SetSize(int width, int height, int reserved) { + if (!window_.get()) { // Create the window for the first frame + window_.reset(new VideoWindow(initial_x_, initial_y_, width, height)); + } + return window_->SetSize(width, height); +} + +bool GdiVideoRenderer::RenderFrame(const VideoFrame* frame) { + if (!frame || !window_.get()) { + return false; + } + return window_->RenderFrame(frame); +} + +} // namespace cricket +#endif // WIN32 diff --git a/talk/media/devices/gdivideorenderer.h b/talk/media/devices/gdivideorenderer.h new file mode 100755 index 0000000000..0e4f6cf7fe --- /dev/null +++ b/talk/media/devices/gdivideorenderer.h @@ -0,0 +1,63 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// +// Definition of class GdiVideoRenderer that implements the abstract class +// cricket::VideoRenderer via GDI on Windows. + +#ifndef TALK_MEDIA_DEVICES_GDIVIDEORENDERER_H_ +#define TALK_MEDIA_DEVICES_GDIVIDEORENDERER_H_ + +#ifdef WIN32 +#include "talk/media/base/videorenderer.h" +#include "webrtc/base/scoped_ptr.h" + +namespace cricket { + +class GdiVideoRenderer : public VideoRenderer { + public: + GdiVideoRenderer(int x, int y); + virtual ~GdiVideoRenderer(); + + // Implementation of pure virtual methods of VideoRenderer. + // These two methods may be executed in different threads. + // SetSize is called before RenderFrame. + virtual bool SetSize(int width, int height, int reserved); + virtual bool RenderFrame(const VideoFrame* frame); + + private: + class VideoWindow; // forward declaration, defined in the .cc file + rtc::scoped_ptr<VideoWindow> window_; + // The initial position of the window. + int initial_x_; + int initial_y_; +}; + +} // namespace cricket + +#endif // WIN32 +#endif // TALK_MEDIA_DEVICES_GDIVIDEORENDERER_H_ diff --git a/talk/media/devices/gtkvideorenderer.cc b/talk/media/devices/gtkvideorenderer.cc new file mode 100755 index 0000000000..d389960e3d --- /dev/null +++ b/talk/media/devices/gtkvideorenderer.cc @@ -0,0 +1,179 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Implementation of GtkVideoRenderer + +#include "talk/media/devices/gtkvideorenderer.h" + +#include <gdk/gdk.h> +#include <glib.h> +#include <gtk/gtk.h> + +#include "talk/media/base/videocommon.h" +#include "talk/media/base/videoframe.h" + +namespace cricket { + +class ScopedGdkLock { + public: + ScopedGdkLock() { + gdk_threads_enter(); + } + + ~ScopedGdkLock() { + gdk_threads_leave(); + } +}; + +GtkVideoRenderer::GtkVideoRenderer(int x, int y) + : window_(NULL), + draw_area_(NULL), + initial_x_(x), + initial_y_(y), + width_(0), + height_(0) { + g_type_init(); + // g_thread_init API is deprecated since glib 2.31.0, see release note: + // http://mail.gnome.org/archives/gnome-announce-list/2011-October/msg00041.html +#if !GLIB_CHECK_VERSION(2, 31, 0) + g_thread_init(NULL); +#endif + gdk_threads_init(); +} + +GtkVideoRenderer::~GtkVideoRenderer() { + if (window_) { + ScopedGdkLock lock; + gtk_widget_destroy(window_); + // Run the Gtk main loop to tear down the window. + Pump(); + } + // Don't need to destroy draw_area_ because it is not top-level, so it is + // implicitly destroyed by the above. +} + +bool GtkVideoRenderer::SetSize(int width, int height, int reserved) { + ScopedGdkLock lock; + + // If the dimension is the same, no-op. + if (width_ == width && height_ == height) { + return true; + } + + // For the first frame, initialize the GTK window + if ((!window_ && !Initialize(width, height)) || IsClosed()) { + return false; + } + + image_.reset(new uint8_t[width * height * 4]); + gtk_widget_set_size_request(draw_area_, width, height); + + width_ = width; + height_ = height; + return true; +} + +bool GtkVideoRenderer::RenderFrame(const VideoFrame* video_frame) { + if (!video_frame) { + return false; + } + + const VideoFrame* frame = video_frame->GetCopyWithRotationApplied(); + + // Need to set size as the frame might be rotated. + if (!SetSize(frame->GetWidth(), frame->GetHeight(), 0)) { + return false; + } + + // convert I420 frame to ABGR format, which is accepted by GTK + frame->ConvertToRgbBuffer(cricket::FOURCC_ABGR, + image_.get(), + frame->GetWidth() * frame->GetHeight() * 4, + frame->GetWidth() * 4); + + ScopedGdkLock lock; + + if (IsClosed()) { + return false; + } + + // draw the ABGR image + gdk_draw_rgb_32_image(draw_area_->window, + draw_area_->style->fg_gc[GTK_STATE_NORMAL], + 0, + 0, + frame->GetWidth(), + frame->GetHeight(), + GDK_RGB_DITHER_MAX, + image_.get(), + frame->GetWidth() * 4); + + // Run the Gtk main loop to refresh the window. + Pump(); + return true; +} + +bool GtkVideoRenderer::Initialize(int width, int height) { + gtk_init(NULL, NULL); + window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); + draw_area_ = gtk_drawing_area_new(); + if (!window_ || !draw_area_) { + return false; + } + + gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER); + gtk_window_set_title(GTK_WINDOW(window_), "Video Renderer"); + gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); + gtk_widget_set_size_request(draw_area_, width, height); + gtk_container_add(GTK_CONTAINER(window_), draw_area_); + gtk_widget_show_all(window_); + gtk_window_move(GTK_WINDOW(window_), initial_x_, initial_y_); + + image_.reset(new uint8_t[width * height * 4]); + return true; +} + +void GtkVideoRenderer::Pump() { + while (gtk_events_pending()) { + gtk_main_iteration(); + } +} + +bool GtkVideoRenderer::IsClosed() const { + if (!window_) { + // Not initialized yet, so hasn't been closed. + return false; + } + + if (!GTK_IS_WINDOW(window_) || !GTK_IS_DRAWING_AREA(draw_area_)) { + return true; + } + + return false; +} + +} // namespace cricket diff --git a/talk/media/devices/gtkvideorenderer.h b/talk/media/devices/gtkvideorenderer.h new file mode 100755 index 0000000000..0270a7a2c9 --- /dev/null +++ b/talk/media/devices/gtkvideorenderer.h @@ -0,0 +1,74 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Definition of class GtkVideoRenderer that implements the abstract class +// cricket::VideoRenderer via GTK. + +#ifndef TALK_MEDIA_DEVICES_GTKVIDEORENDERER_H_ +#define TALK_MEDIA_DEVICES_GTKVIDEORENDERER_H_ + +#include "talk/media/base/videorenderer.h" +#include "webrtc/base/basictypes.h" +#include "webrtc/base/scoped_ptr.h" + +typedef struct _GtkWidget GtkWidget; // forward declaration, defined in gtk.h + +namespace cricket { + +class GtkVideoRenderer : public VideoRenderer { + public: + GtkVideoRenderer(int x, int y); + virtual ~GtkVideoRenderer(); + + // Implementation of pure virtual methods of VideoRenderer. + // These two methods may be executed in different threads. + // SetSize is called before RenderFrame. + virtual bool SetSize(int width, int height, int reserved); + virtual bool RenderFrame(const VideoFrame* frame); + + private: + // Initialize the attributes when the first frame arrives. + bool Initialize(int width, int height); + // Pump the Gtk event loop until there are no events left. + void Pump(); + // Check if the window has been closed. + bool IsClosed() const; + + rtc::scoped_ptr<uint8_t[]> image_; + GtkWidget* window_; + GtkWidget* draw_area_; + // The initial position of the window. + int initial_x_; + int initial_y_; + + int width_; + int height_; +}; + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_GTKVIDEORENDERER_H_ diff --git a/talk/media/devices/libudevsymboltable.cc b/talk/media/devices/libudevsymboltable.cc new file mode 100644 index 0000000000..351a1e7f5e --- /dev/null +++ b/talk/media/devices/libudevsymboltable.cc @@ -0,0 +1,72 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/libudevsymboltable.h" + +#include <dlfcn.h> + +#include "webrtc/base/logging.h" + +namespace cricket { + +#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBUDEV_SYMBOLS_CLASS_NAME +#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBUDEV_SYMBOLS_LIST +#define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libudev.so.0" +#include "webrtc/base/latebindingsymboltable.cc.def" +#undef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#undef LATE_BINDING_SYMBOL_TABLE_DLL_NAME + +bool IsWrongLibUDevAbiVersion(rtc::DllHandle libudev_0) { + rtc::DllHandle libudev_1 = dlopen("libudev.so.1", + RTLD_NOW|RTLD_LOCAL|RTLD_NOLOAD); + bool unsafe_symlink = (libudev_0 == libudev_1); + if (unsafe_symlink) { + // .0 and .1 are distinct ABIs, so if they point to the same thing then one + // of them must be wrong. Probably the old has been symlinked to the new in + // a misguided attempt at backwards compatibility. + LOG(LS_ERROR) << "libudev.so.0 and libudev.so.1 unsafely point to the" + " same thing; not using libudev"; + } else if (libudev_1) { + // If libudev.so.1 is resident but distinct from libudev.so.0, then some + // system library loaded the new ABI separately. This is not a problem for + // LateBindingSymbolTable because its symbol look-ups are restricted to its + // DllHandle, but having libudev.so.0 resident may cause problems for that + // system library because symbol names are not namespaced by DLL. (Although + // our use of RTLD_LOCAL should avoid most problems.) + LOG(LS_WARNING) + << "libudev.so.1 is resident but distinct from libudev.so.0"; + } + if (libudev_1) { + // Release the refcount that we acquired above. (Does not unload the DLL; + // whoever loaded it still needs it.) + dlclose(libudev_1); + } + return unsafe_symlink; +} + +} // namespace cricket diff --git a/talk/media/devices/libudevsymboltable.h b/talk/media/devices/libudevsymboltable.h new file mode 100644 index 0000000000..f764cd263d --- /dev/null +++ b/talk/media/devices/libudevsymboltable.h @@ -0,0 +1,79 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_DEVICES_LIBUDEVSYMBOLTABLE_H_ +#define TALK_MEDIA_DEVICES_LIBUDEVSYMBOLTABLE_H_ + +#include <libudev.h> + +#include "webrtc/base/latebindingsymboltable.h" + +namespace cricket { + +#define LIBUDEV_SYMBOLS_CLASS_NAME LibUDevSymbolTable +// The libudev symbols we need, as an X-Macro list. +// This list must contain precisely every libudev function that is used in +// linuxdevicemanager.cc. +#define LIBUDEV_SYMBOLS_LIST \ + X(udev_device_get_devnode) \ + X(udev_device_get_parent_with_subsystem_devtype) \ + X(udev_device_get_sysattr_value) \ + X(udev_device_new_from_syspath) \ + X(udev_device_unref) \ + X(udev_enumerate_add_match_subsystem) \ + X(udev_enumerate_get_list_entry) \ + X(udev_enumerate_new) \ + X(udev_enumerate_scan_devices) \ + X(udev_enumerate_unref) \ + X(udev_list_entry_get_name) \ + X(udev_list_entry_get_next) \ + X(udev_monitor_enable_receiving) \ + X(udev_monitor_filter_add_match_subsystem_devtype) \ + X(udev_monitor_get_fd) \ + X(udev_monitor_new_from_netlink) \ + X(udev_monitor_receive_device) \ + X(udev_monitor_unref) \ + X(udev_new) \ + X(udev_unref) + +#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBUDEV_SYMBOLS_CLASS_NAME +#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBUDEV_SYMBOLS_LIST +#include "webrtc/base/latebindingsymboltable.h.def" +#undef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST + +// libudev has changed ABIs to libudev.so.1 in recent distros and lots of users +// and/or software (including Google Chrome) are symlinking the old to the new. +// The entire point of ABI versions is that you can't safely do that, and +// it has caused crashes in the wild. This function checks if the DllHandle that +// we got back for libudev.so.0 is actually for libudev.so.1. If so, the library +// cannot safely be used. +bool IsWrongLibUDevAbiVersion(rtc::DllHandle libudev_0); + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_LIBUDEVSYMBOLTABLE_H_ diff --git a/talk/media/devices/linuxdeviceinfo.cc b/talk/media/devices/linuxdeviceinfo.cc new file mode 100644 index 0000000000..0b22e15a99 --- /dev/null +++ b/talk/media/devices/linuxdeviceinfo.cc @@ -0,0 +1,174 @@ +/* + * libjingle + * Copyright 2012 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/deviceinfo.h" + +#include "talk/media/devices/libudevsymboltable.h" +#include "webrtc/base/common.h" // for ASSERT + +namespace cricket { + +class ScopedLibUdev { + public: + static ScopedLibUdev* Create() { + ScopedLibUdev* ret_val = new ScopedLibUdev(); + if (!ret_val->Init()) { + delete ret_val; + return NULL; + } + return ret_val; + } + ~ScopedLibUdev() { + libudev_.Unload(); + } + + LibUDevSymbolTable* instance() { return &libudev_; } + + private: + ScopedLibUdev() {} + + bool Init() { + return libudev_.Load() && + !IsWrongLibUDevAbiVersion(libudev_.GetDllHandle()); + } + + LibUDevSymbolTable libudev_; +}; + +class ScopedUdev { + public: + explicit ScopedUdev(LibUDevSymbolTable* libudev) : libudev_(libudev) { + udev_ = libudev_->udev_new()(); + } + ~ScopedUdev() { + if (udev_) libudev_->udev_unref()(udev_); + } + + udev* instance() { return udev_; } + + private: + LibUDevSymbolTable* libudev_; + udev* udev_; +}; + +class ScopedUdevEnumerate { + public: + ScopedUdevEnumerate(LibUDevSymbolTable* libudev, udev* udev) + : libudev_(libudev) { + enumerate_ = libudev_->udev_enumerate_new()(udev); + } + ~ScopedUdevEnumerate() { + if (enumerate_) libudev_->udev_enumerate_unref()(enumerate_); + } + + udev_enumerate* instance() { return enumerate_; } + + private: + LibUDevSymbolTable* libudev_; + udev_enumerate* enumerate_; +}; + +bool GetUsbProperty(const Device& device, const char* property_name, + std::string* property) { + rtc::scoped_ptr<ScopedLibUdev> libudev_context(ScopedLibUdev::Create()); + if (!libudev_context) { + return false; + } + ScopedUdev udev_context(libudev_context->instance()); + if (!udev_context.instance()) { + return false; + } + ScopedUdevEnumerate enumerate_context(libudev_context->instance(), + udev_context.instance()); + if (!enumerate_context.instance()) { + return false; + } + libudev_context->instance()->udev_enumerate_add_match_subsystem()( + enumerate_context.instance(), "video4linux"); + libudev_context->instance()->udev_enumerate_scan_devices()( + enumerate_context.instance()); + udev_list_entry* devices = + libudev_context->instance()->udev_enumerate_get_list_entry()( + enumerate_context.instance()); + if (!devices) { + return false; + } + udev_list_entry* dev_list_entry = NULL; + const char* property_value = NULL; + // Macro that expands to a for-loop over the devices. + for (dev_list_entry = devices; dev_list_entry != NULL; + dev_list_entry = libudev_context->instance()-> + udev_list_entry_get_next()(dev_list_entry)) { + const char* path = libudev_context->instance()->udev_list_entry_get_name()( + dev_list_entry); + if (!path) continue; + udev_device* dev = + libudev_context->instance()->udev_device_new_from_syspath()( + udev_context.instance(), path); + if (!dev) continue; + const char* device_node = + libudev_context->instance()->udev_device_get_devnode()(dev); + if (!device_node || device.id.compare(device_node) != 0) { + continue; + } + dev = libudev_context->instance()-> + udev_device_get_parent_with_subsystem_devtype()( + dev, "usb", "usb_device"); + if (!dev) continue; + property_value = libudev_context->instance()-> + udev_device_get_sysattr_value()( + dev, property_name); + break; + } + if (!property_value) { + return false; + } + property->assign(property_value); + return true; +} + +bool GetUsbId(const Device& device, std::string* usb_id) { + std::string id_vendor; + std::string id_product; + if (!GetUsbProperty(device, "idVendor", &id_vendor)) { + return false; + } + if (!GetUsbProperty(device, "idProduct", &id_product)) { + return false; + } + usb_id->clear(); + usb_id->append(id_vendor); + usb_id->append(":"); + usb_id->append(id_product); + return true; +} + +bool GetUsbVersion(const Device& device, std::string* usb_version) { + return GetUsbProperty(device, "version", usb_version); +} + +} // namespace cricket diff --git a/talk/media/devices/linuxdevicemanager.cc b/talk/media/devices/linuxdevicemanager.cc new file mode 100644 index 0000000000..25be321c5e --- /dev/null +++ b/talk/media/devices/linuxdevicemanager.cc @@ -0,0 +1,410 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/linuxdevicemanager.h" + +#include <unistd.h> +#include "talk/media/base/mediacommon.h" +#include "talk/media/devices/libudevsymboltable.h" +#include "talk/media/devices/v4llookup.h" +#include "webrtc/sound/platformsoundsystem.h" +#include "webrtc/sound/platformsoundsystemfactory.h" +#include "webrtc/sound/sounddevicelocator.h" +#include "webrtc/sound/soundsysteminterface.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/linux.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +DeviceManagerInterface* DeviceManagerFactory::Create() { + return new LinuxDeviceManager(); +} + +class LinuxDeviceWatcher + : public DeviceWatcher, + private rtc::Dispatcher { + public: + explicit LinuxDeviceWatcher(DeviceManagerInterface* dm); + virtual ~LinuxDeviceWatcher(); + virtual bool Start(); + virtual void Stop(); + + private: + virtual uint32_t GetRequestedEvents(); + virtual void OnPreEvent(uint32_t ff); + virtual void OnEvent(uint32_t ff, int err); + virtual int GetDescriptor(); + virtual bool IsDescriptorClosed(); + + DeviceManagerInterface* manager_; + LibUDevSymbolTable libudev_; + struct udev* udev_; + struct udev_monitor* udev_monitor_; + bool registered_; +}; + +static const char* const kFilteredAudioDevicesName[] = { +#if defined(CHROMEOS) + "surround40:", + "surround41:", + "surround50:", + "surround51:", + "surround71:", + "iec958:", // S/PDIF +#endif + NULL, +}; +static const char* kFilteredVideoDevicesName[] = { + NULL, +}; + +LinuxDeviceManager::LinuxDeviceManager() + : sound_system_(new rtc::PlatformSoundSystemFactory()) { + set_watcher(new LinuxDeviceWatcher(this)); +} + +LinuxDeviceManager::~LinuxDeviceManager() { +} + +bool LinuxDeviceManager::GetAudioDevices(bool input, + std::vector<Device>* devs) { + devs->clear(); + if (!sound_system_.get()) { + return false; + } + rtc::SoundSystemInterface::SoundDeviceLocatorList list; + bool success; + if (input) { + success = sound_system_->EnumerateCaptureDevices(&list); + } else { + success = sound_system_->EnumeratePlaybackDevices(&list); + } + if (!success) { + LOG(LS_ERROR) << "Can't enumerate devices"; + sound_system_.release(); + return false; + } + // We have to start the index at 1 because webrtc VoiceEngine puts the default + // device at index 0, but Enumerate(Capture|Playback)Devices does not include + // a locator for the default device. + int index = 1; + for (rtc::SoundSystemInterface::SoundDeviceLocatorList::iterator i = list.begin(); + i != list.end(); + ++i, ++index) { + devs->push_back(Device((*i)->name(), index)); + } + rtc::SoundSystemInterface::ClearSoundDeviceLocatorList(&list); + sound_system_.release(); + return FilterDevices(devs, kFilteredAudioDevicesName); +} + +static const std::string kVideoMetaPathK2_4("/proc/video/dev/"); +static const std::string kVideoMetaPathK2_6("/sys/class/video4linux/"); + +enum MetaType { M2_4, M2_6, NONE }; + +static void ScanDeviceDirectory(const std::string& devdir, + std::vector<Device>* devices) { + rtc::scoped_ptr<rtc::DirectoryIterator> directoryIterator( + rtc::Filesystem::IterateDirectory()); + + if (directoryIterator->Iterate(rtc::Pathname(devdir))) { + do { + std::string filename = directoryIterator->Name(); + std::string device_name = devdir + filename; + if (!directoryIterator->IsDots()) { + if (filename.find("video") == 0 && + V4LLookup::IsV4L2Device(device_name)) { + devices->push_back(Device(device_name, device_name)); + } + } + } while (directoryIterator->Next()); + } +} + +static std::string GetVideoDeviceNameK2_6(const std::string& device_meta_path) { + std::string device_name; + + rtc::scoped_ptr<rtc::FileStream> device_meta_stream( + rtc::Filesystem::OpenFile(device_meta_path, "r")); + + if (device_meta_stream) { + if (device_meta_stream->ReadLine(&device_name) != rtc::SR_SUCCESS) { + LOG(LS_ERROR) << "Failed to read V4L2 device meta " << device_meta_path; + } + device_meta_stream->Close(); + } + + return device_name; +} + +static std::string Trim(const std::string& s, const std::string& drop = " \t") { + std::string::size_type first = s.find_first_not_of(drop); + std::string::size_type last = s.find_last_not_of(drop); + + if (first == std::string::npos || last == std::string::npos) + return std::string(""); + + return s.substr(first, last - first + 1); +} + +static std::string GetVideoDeviceNameK2_4(const std::string& device_meta_path) { + rtc::ConfigParser::MapVector all_values; + + rtc::ConfigParser config_parser; + rtc::FileStream* file_stream = + rtc::Filesystem::OpenFile(device_meta_path, "r"); + + if (file_stream == NULL) return ""; + + config_parser.Attach(file_stream); + config_parser.Parse(&all_values); + + for (rtc::ConfigParser::MapVector::iterator i = all_values.begin(); + i != all_values.end(); ++i) { + rtc::ConfigParser::SimpleMap::iterator device_name_i = + i->find("name"); + + if (device_name_i != i->end()) { + return device_name_i->second; + } + } + + return ""; +} + +static std::string GetVideoDeviceName(MetaType meta, + const std::string& device_file_name) { + std::string device_meta_path; + std::string device_name; + std::string meta_file_path; + + if (meta == M2_6) { + meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/name"; + + LOG(LS_INFO) << "Trying " + meta_file_path; + device_name = GetVideoDeviceNameK2_6(meta_file_path); + + if (device_name.empty()) { + meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/model"; + + LOG(LS_INFO) << "Trying " << meta_file_path; + device_name = GetVideoDeviceNameK2_6(meta_file_path); + } + } else { + meta_file_path = kVideoMetaPathK2_4 + device_file_name; + LOG(LS_INFO) << "Trying " << meta_file_path; + device_name = GetVideoDeviceNameK2_4(meta_file_path); + } + + if (device_name.empty()) { + device_name = "/dev/" + device_file_name; + LOG(LS_ERROR) + << "Device name not found, defaulting to device path " << device_name; + } + + LOG(LS_INFO) << "Name for " << device_file_name << " is " << device_name; + + return Trim(device_name); +} + +static void ScanV4L2Devices(std::vector<Device>* devices) { + LOG(LS_INFO) << ("Enumerating V4L2 devices"); + + MetaType meta; + std::string metadata_dir; + + rtc::scoped_ptr<rtc::DirectoryIterator> directoryIterator( + rtc::Filesystem::IterateDirectory()); + + // Try and guess kernel version + if (directoryIterator->Iterate(kVideoMetaPathK2_6)) { + meta = M2_6; + metadata_dir = kVideoMetaPathK2_6; + } else if (directoryIterator->Iterate(kVideoMetaPathK2_4)) { + meta = M2_4; + metadata_dir = kVideoMetaPathK2_4; + } else { + meta = NONE; + } + + if (meta != NONE) { + LOG(LS_INFO) << "V4L2 device metadata found at " << metadata_dir; + + do { + std::string filename = directoryIterator->Name(); + + if (filename.find("video") == 0) { + std::string device_path = "/dev/" + filename; + + if (V4LLookup::IsV4L2Device(device_path)) { + devices->push_back( + Device(GetVideoDeviceName(meta, filename), device_path)); + } + } + } while (directoryIterator->Next()); + } else { + LOG(LS_ERROR) << "Unable to detect v4l2 metadata directory"; + } + + if (devices->size() == 0) { + LOG(LS_INFO) << "Plan B. Scanning all video devices in /dev directory"; + ScanDeviceDirectory("/dev/", devices); + } + + LOG(LS_INFO) << "Total V4L2 devices found : " << devices->size(); +} + +bool LinuxDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) { + devices->clear(); + ScanV4L2Devices(devices); + return FilterDevices(devices, kFilteredVideoDevicesName); +} + +LinuxDeviceWatcher::LinuxDeviceWatcher(DeviceManagerInterface* dm) + : DeviceWatcher(dm), + manager_(dm), + udev_(NULL), + udev_monitor_(NULL), + registered_(false) { +} + +LinuxDeviceWatcher::~LinuxDeviceWatcher() { +} + +static rtc::PhysicalSocketServer* CurrentSocketServer() { + rtc::SocketServer* ss = + rtc::ThreadManager::Instance()->WrapCurrentThread()->socketserver(); + return reinterpret_cast<rtc::PhysicalSocketServer*>(ss); +} + +bool LinuxDeviceWatcher::Start() { + // We deliberately return true in the failure paths here because libudev is + // not a critical component of a Linux system so it may not be present/usable, + // and we don't want to halt LinuxDeviceManager initialization in such a case. + if (!libudev_.Load() || IsWrongLibUDevAbiVersion(libudev_.GetDllHandle())) { + LOG(LS_WARNING) + << "libudev not present/usable; LinuxDeviceWatcher disabled"; + return true; + } + udev_ = libudev_.udev_new()(); + if (!udev_) { + LOG_ERR(LS_ERROR) << "udev_new()"; + return true; + } + // The second argument here is the event source. It can be either "kernel" or + // "udev", but "udev" is the only correct choice. Apps listen on udev and the + // udev daemon in turn listens on the kernel. + udev_monitor_ = libudev_.udev_monitor_new_from_netlink()(udev_, "udev"); + if (!udev_monitor_) { + LOG_ERR(LS_ERROR) << "udev_monitor_new_from_netlink()"; + return true; + } + // We only listen for changes in the video devices. Audio devices are more or + // less unimportant because receiving device change notifications really only + // matters for broadcasting updated send/recv capabilities based on whether + // there is at least one device available, and almost all computers have at + // least one audio device. Also, PulseAudio device notifications don't come + // from the udev daemon, they come from the PulseAudio daemon, so we'd only + // want to listen for audio device changes from udev if using ALSA. For + // simplicity, we don't bother with any audio stuff at all. + if (libudev_.udev_monitor_filter_add_match_subsystem_devtype()( + udev_monitor_, "video4linux", NULL) < 0) { + LOG_ERR(LS_ERROR) << "udev_monitor_filter_add_match_subsystem_devtype()"; + return true; + } + if (libudev_.udev_monitor_enable_receiving()(udev_monitor_) < 0) { + LOG_ERR(LS_ERROR) << "udev_monitor_enable_receiving()"; + return true; + } + CurrentSocketServer()->Add(this); + registered_ = true; + return true; +} + +void LinuxDeviceWatcher::Stop() { + if (registered_) { + CurrentSocketServer()->Remove(this); + registered_ = false; + } + if (udev_monitor_) { + libudev_.udev_monitor_unref()(udev_monitor_); + udev_monitor_ = NULL; + } + if (udev_) { + libudev_.udev_unref()(udev_); + udev_ = NULL; + } + libudev_.Unload(); +} + +uint32_t LinuxDeviceWatcher::GetRequestedEvents() { + return rtc::DE_READ; +} + +void LinuxDeviceWatcher::OnPreEvent(uint32_t ff) { + // Nothing to do. +} + +void LinuxDeviceWatcher::OnEvent(uint32_t ff, int err) { + udev_device* device = libudev_.udev_monitor_receive_device()(udev_monitor_); + if (!device) { + // Probably the socket connection to the udev daemon was terminated (perhaps + // the daemon crashed or is being restarted?). + LOG_ERR(LS_WARNING) << "udev_monitor_receive_device()"; + // Stop listening to avoid potential livelock (an fd with EOF in it is + // always considered readable). + CurrentSocketServer()->Remove(this); + registered_ = false; + return; + } + // Else we read the device successfully. + + // Since we already have our own filesystem-based device enumeration code, we + // simply re-enumerate rather than inspecting the device event. + libudev_.udev_device_unref()(device); + manager_->SignalDevicesChange(); +} + +int LinuxDeviceWatcher::GetDescriptor() { + return libudev_.udev_monitor_get_fd()(udev_monitor_); +} + +bool LinuxDeviceWatcher::IsDescriptorClosed() { + // If it is closed then we will just get an error in + // udev_monitor_receive_device and unregister, so we don't need to check for + // it separately. + return false; +} + +}; // namespace cricket diff --git a/talk/media/devices/linuxdevicemanager.h b/talk/media/devices/linuxdevicemanager.h new file mode 100644 index 0000000000..1eb648f395 --- /dev/null +++ b/talk/media/devices/linuxdevicemanager.h @@ -0,0 +1,55 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_DEVICES_LINUXDEVICEMANAGER_H_ +#define TALK_MEDIA_DEVICES_LINUXDEVICEMANAGER_H_ + +#include <string> +#include <vector> + +#include "talk/media/devices/devicemanager.h" +#include "webrtc/sound/soundsystemfactory.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/stringencode.h" + +namespace cricket { + +class LinuxDeviceManager : public DeviceManager { + public: + LinuxDeviceManager(); + virtual ~LinuxDeviceManager(); + + virtual bool GetVideoCaptureDevices(std::vector<Device>* devs); + + private: + virtual bool GetAudioDevices(bool input, std::vector<Device>* devs); + rtc::SoundSystemHandle sound_system_; +}; + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_LINUXDEVICEMANAGER_H_ diff --git a/talk/media/devices/macdeviceinfo.cc b/talk/media/devices/macdeviceinfo.cc new file mode 100644 index 0000000000..f34932d0e2 --- /dev/null +++ b/talk/media/devices/macdeviceinfo.cc @@ -0,0 +1,56 @@ +/* + * libjingle + * Copyright 2012 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/deviceinfo.h" + +namespace cricket { + +bool GetUsbId(const Device& device, std::string* usb_id) { + // Both PID and VID are 4 characters. + const int id_size = 4; + if (device.id.size() < 2 * id_size) { + return false; + } + + // The last characters of device id is a concatenation of VID and then PID. + const size_t vid_location = device.id.size() - 2 * id_size; + std::string id_vendor = device.id.substr(vid_location, id_size); + const size_t pid_location = device.id.size() - id_size; + std::string id_product = device.id.substr(pid_location, id_size); + + usb_id->clear(); + usb_id->append(id_vendor); + usb_id->append(":"); + usb_id->append(id_product); + return true; +} + +bool GetUsbVersion(const Device& device, std::string* usb_version) { + return false; +} + +} // namespace cricket diff --git a/talk/media/devices/macdevicemanager.cc b/talk/media/devices/macdevicemanager.cc new file mode 100644 index 0000000000..8f777b42da --- /dev/null +++ b/talk/media/devices/macdevicemanager.cc @@ -0,0 +1,196 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/macdevicemanager.h" + +#include <CoreAudio/CoreAudio.h> +#include <QuickTime/QuickTime.h> + +#include "talk/media/base/mediacommon.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" + +class DeviceWatcherImpl; + +namespace cricket { + +DeviceManagerInterface* DeviceManagerFactory::Create() { + return new MacDeviceManager(); +} + +class MacDeviceWatcher : public DeviceWatcher { + public: + explicit MacDeviceWatcher(DeviceManagerInterface* dm); + virtual ~MacDeviceWatcher(); + virtual bool Start(); + virtual void Stop(); + + private: + DeviceManagerInterface* manager_; + DeviceWatcherImpl* impl_; +}; + +static const char* kFilteredAudioDevicesName[] = { + NULL, +}; +// TODO(tommyw): Try to get hold of a copy of Final Cut to understand why we +// crash while scanning their components on OS X. +static const char* const kFilteredVideoDevicesName[] = { + "DVCPRO HD", // Final cut + "Sonix SN9C201p", // Crashes in OpenAComponent and CloseComponent + NULL, +}; +static const UInt32 kAudioDeviceNameLength = 64; +// Obj-C functions defined in macdevicemanagermm.mm +// TODO(ronghuawu): have a shared header for these function defines. +extern DeviceWatcherImpl* CreateDeviceWatcherCallback( + DeviceManagerInterface* dm); +extern void ReleaseDeviceWatcherCallback(DeviceWatcherImpl* impl); +extern bool GetAVFoundationVideoDevices(std::vector<Device>* out); +static bool GetAudioDeviceIDs(bool inputs, std::vector<AudioDeviceID>* out); +static bool GetAudioDeviceName(AudioDeviceID id, bool input, std::string* out); + +MacDeviceManager::MacDeviceManager() { + set_watcher(new MacDeviceWatcher(this)); +} + +MacDeviceManager::~MacDeviceManager() { +} + +bool MacDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) { + devices->clear(); + if (!GetAVFoundationVideoDevices(devices)) { + return false; + } + return FilterDevices(devices, kFilteredVideoDevicesName); +} + +bool MacDeviceManager::GetAudioDevices(bool input, + std::vector<Device>* devs) { + devs->clear(); + std::vector<AudioDeviceID> dev_ids; + bool ret = GetAudioDeviceIDs(input, &dev_ids); + if (!ret) { + return false; + } + for (size_t i = 0; i < dev_ids.size(); ++i) { + std::string name; + if (GetAudioDeviceName(dev_ids[i], input, &name)) { + devs->push_back(Device(name, dev_ids[i])); + } + } + return FilterDevices(devs, kFilteredAudioDevicesName); +} + +static bool GetAudioDeviceIDs(bool input, + std::vector<AudioDeviceID>* out_dev_ids) { + UInt32 propsize; + OSErr err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, + &propsize, NULL); + if (0 != err) { + LOG(LS_ERROR) << "Couldn't get information about property, " + << "so no device list acquired."; + return false; + } + + size_t num_devices = propsize / sizeof(AudioDeviceID); + rtc::scoped_ptr<AudioDeviceID[]> device_ids( + new AudioDeviceID[num_devices]); + + err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, + &propsize, device_ids.get()); + if (0 != err) { + LOG(LS_ERROR) << "Failed to get device ids, " + << "so no device listing acquired."; + return false; + } + + for (size_t i = 0; i < num_devices; ++i) { + AudioDeviceID an_id = device_ids[i]; + // find out the number of channels for this direction + // (input/output) on this device - + // we'll ignore anything with no channels. + err = AudioDeviceGetPropertyInfo(an_id, 0, input, + kAudioDevicePropertyStreams, + &propsize, NULL); + if (0 == err) { + unsigned num_channels = propsize / sizeof(AudioStreamID); + if (0 < num_channels) { + out_dev_ids->push_back(an_id); + } + } else { + LOG(LS_ERROR) << "No property info for stream property for device id " + << an_id << "(is_input == " << input + << "), so not including it in the list."; + } + } + + return true; +} + +static bool GetAudioDeviceName(AudioDeviceID id, + bool input, + std::string* out_name) { + UInt32 nameLength = kAudioDeviceNameLength; + char name[kAudioDeviceNameLength + 1]; + OSErr err = AudioDeviceGetProperty(id, 0, input, + kAudioDevicePropertyDeviceName, + &nameLength, name); + if (0 != err) { + LOG(LS_ERROR) << "No name acquired for device id " << id; + return false; + } + + *out_name = name; + return true; +} + +MacDeviceWatcher::MacDeviceWatcher(DeviceManagerInterface* manager) + : DeviceWatcher(manager), + manager_(manager), + impl_(NULL) { +} + +MacDeviceWatcher::~MacDeviceWatcher() { +} + +bool MacDeviceWatcher::Start() { + if (!impl_) { + impl_ = CreateDeviceWatcherCallback(manager_); + } + return impl_ != NULL; +} + +void MacDeviceWatcher::Stop() { + if (impl_) { + ReleaseDeviceWatcherCallback(impl_); + impl_ = NULL; + } +} + +}; // namespace cricket diff --git a/talk/media/devices/macdevicemanager.h b/talk/media/devices/macdevicemanager.h new file mode 100644 index 0000000000..82e62f9698 --- /dev/null +++ b/talk/media/devices/macdevicemanager.h @@ -0,0 +1,56 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_DEVICES_MACDEVICEMANAGER_H_ +#define TALK_MEDIA_DEVICES_MACDEVICEMANAGER_H_ + +#include <string> +#include <vector> + +#include "talk/media/devices/devicemanager.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/stringencode.h" + +namespace cricket { + +class DeviceWatcher; + +class MacDeviceManager : public DeviceManager { + public: + MacDeviceManager(); + virtual ~MacDeviceManager(); + + virtual bool GetVideoCaptureDevices(std::vector<Device>* devs); + + private: + virtual bool GetAudioDevices(bool input, std::vector<Device>* devs); + bool FilterDevice(const Device& d); +}; + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_MACDEVICEMANAGER_H_ diff --git a/talk/media/devices/macdevicemanagermm.mm b/talk/media/devices/macdevicemanagermm.mm new file mode 100644 index 0000000000..ed5934d57c --- /dev/null +++ b/talk/media/devices/macdevicemanagermm.mm @@ -0,0 +1,192 @@ +/* + * libjingle + * Copyright 2010 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// support GCC compiler +#ifndef __has_feature +#define __has_feature(x) 0 +#endif + +#include "talk/media/devices/devicemanager.h" + +#import <assert.h> +#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + #import <AVFoundation/AVFoundation.h> +#endif +#endif +#import <QTKit/QTKit.h> + +#include "webrtc/base/logging.h" + +@interface DeviceWatcherImpl : NSObject { + @private + cricket::DeviceManagerInterface* manager_; +} +- (id)init:(cricket::DeviceManagerInterface*)manager; +- (void)onDevicesChanged:(NSNotification*)notification; +@end + +@implementation DeviceWatcherImpl +- (id)init:(cricket::DeviceManagerInterface*)manager { + if ((self = [super init])) { + assert(manager != NULL); + manager_ = manager; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(onDevicesChanged:) + name:QTCaptureDeviceWasConnectedNotification + object:nil]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(onDevicesChanged:) + name:QTCaptureDeviceWasDisconnectedNotification + object:nil]; + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +#if !__has_feature(objc_arc) + [super dealloc]; +#endif +} +- (void)onDevicesChanged:(NSNotification*)notification { + manager_->SignalDevicesChange(); +} +@end + +namespace cricket { + +DeviceWatcherImpl* CreateDeviceWatcherCallback( + DeviceManagerInterface* manager) { + DeviceWatcherImpl* impl; +#if !__has_feature(objc_arc) + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; +#else + @autoreleasepool +#endif + { impl = [[DeviceWatcherImpl alloc] init:manager]; } +#if !__has_feature(objc_arc) + [pool drain]; +#endif + return impl; +} + +void ReleaseDeviceWatcherCallback(DeviceWatcherImpl* watcher) { +#if !__has_feature(objc_arc) + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + [watcher release]; + [pool drain]; +#endif +} + +bool GetQTKitVideoDevices(std::vector<Device>* devices) { +#if !__has_feature(objc_arc) + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; +#else + @autoreleasepool +#endif + { + NSArray* qt_capture_devices = + [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo]; + NSUInteger count = [qt_capture_devices count]; + LOG(LS_INFO) << count << " capture device(s) found:"; + for (QTCaptureDevice* qt_capture_device in qt_capture_devices) { + static NSString* const kFormat = @"localizedDisplayName: \"%@\", " + @"modelUniqueID: \"%@\", uniqueID \"%@\", isConnected: %d, " + @"isOpen: %d, isInUseByAnotherApplication: %d"; + NSString* info = [NSString + stringWithFormat:kFormat, + [qt_capture_device localizedDisplayName], + [qt_capture_device modelUniqueID], + [qt_capture_device uniqueID], + [qt_capture_device isConnected], + [qt_capture_device isOpen], + [qt_capture_device isInUseByAnotherApplication]]; + LOG(LS_INFO) << [info UTF8String]; + + std::string name([[qt_capture_device localizedDisplayName] UTF8String]); + devices->push_back( + Device(name, [[qt_capture_device uniqueID] UTF8String])); + } + } +#if !__has_feature(objc_arc) + [pool drain]; +#endif + return true; +} + +bool GetAVFoundationVideoDevices(std::vector<Device>* devices) { +#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED +#if __MAC_OS_X_VERSION_MAX_ALLOWED >=1070 + if (![AVCaptureDevice class]) { + // Fallback to using QTKit if AVFoundation is not available + return GetQTKitVideoDevices(devices); + } +#if !__has_feature(objc_arc) + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; +#else + @autoreleasepool +#endif + { + NSArray* capture_devices = [AVCaptureDevice devices]; + LOG(LS_INFO) << [capture_devices count] << " capture device(s) found:"; + for (AVCaptureDevice* capture_device in capture_devices) { + if ([capture_device hasMediaType:AVMediaTypeVideo] || + [capture_device hasMediaType:AVMediaTypeMuxed]) { + static NSString* const kFormat = @"localizedName: \"%@\", " + @"modelID: \"%@\", uniqueID \"%@\", isConnected: %d, " + @"isInUseByAnotherApplication: %d"; + NSString* info = [NSString + stringWithFormat:kFormat, + [capture_device localizedName], + [capture_device modelID], + [capture_device uniqueID], + [capture_device isConnected], + [capture_device isInUseByAnotherApplication]]; + LOG(LS_INFO) << [info UTF8String]; + + std::string name([[capture_device localizedName] UTF8String]); + devices->push_back( + Device(name, [[capture_device uniqueID] UTF8String])); + } + } + } +#if !__has_feature(objc_arc) + [pool drain]; +#endif + return true; +#else // __MAC_OS_X_VERSION_MAX_ALLOWED >=1070 + return GetQTKitVideoDevices(devices); +#endif // __MAC_OS_X_VERSION_MAX_ALLOWED >=1070 +#else // __MAC_OS_X_VERSION_MAX_ALLOWED + return GetQTKitVideoDevices(devices); +#endif // __MAC_OS_X_VERSION_MAX_ALLOWED +} + +} // namespace cricket diff --git a/talk/media/devices/mobiledevicemanager.cc b/talk/media/devices/mobiledevicemanager.cc new file mode 100644 index 0000000000..2a886a36d4 --- /dev/null +++ b/talk/media/devices/mobiledevicemanager.cc @@ -0,0 +1,77 @@ +/* + * libjingle + * Copyright 2013 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/devicemanager.h" +#include "webrtc/base/arraysize.h" +#include "webrtc/modules/video_capture/include/video_capture_factory.h" + +namespace cricket { + +class MobileDeviceManager : public DeviceManager { + public: + MobileDeviceManager(); + virtual ~MobileDeviceManager(); + virtual bool GetVideoCaptureDevices(std::vector<Device>* devs); +}; + +MobileDeviceManager::MobileDeviceManager() { + // We don't expect available devices to change on Android/iOS, so use a + // do-nothing watcher. + set_watcher(new DeviceWatcher(this)); +} + +MobileDeviceManager::~MobileDeviceManager() {} + +bool MobileDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devs) { + devs->clear(); + rtc::scoped_ptr<webrtc::VideoCaptureModule::DeviceInfo> info( + webrtc::VideoCaptureFactory::CreateDeviceInfo(0)); + if (!info) + return false; + + uint32_t num_cams = info->NumberOfDevices(); + char id[256]; + char name[256]; + for (uint32_t i = 0; i < num_cams; ++i) { + if (info->GetDeviceName(i, name, arraysize(name), id, arraysize(id))) + continue; + devs->push_back(Device(name, id)); + } + return true; +} + +DeviceManagerInterface* DeviceManagerFactory::Create() { + return new MobileDeviceManager(); +} + +bool GetUsbId(const Device& device, std::string* usb_id) { return false; } + +bool GetUsbVersion(const Device& device, std::string* usb_version) { + return false; +} + +} // namespace cricket diff --git a/talk/media/devices/v4llookup.cc b/talk/media/devices/v4llookup.cc new file mode 100644 index 0000000000..20b534c82d --- /dev/null +++ b/talk/media/devices/v4llookup.cc @@ -0,0 +1,92 @@ +/* + * libjingle + * Copyright 2009 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Author: lexnikitin@google.com (Alexey Nikitin) + * + * V4LLookup provides basic functionality to work with V2L2 devices in Linux + * The functionality is implemented as a class with virtual methods for + * the purpose of unit testing. + */ +#include "talk/media/devices/v4llookup.h" + +#include <errno.h> +#include <fcntl.h> +#include <linux/types.h> +#include <linux/videodev2.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "webrtc/base/logging.h" + +namespace cricket { + +V4LLookup *V4LLookup::v4l_lookup_ = NULL; + +bool V4LLookup::CheckIsV4L2Device(const std::string& device_path) { + // check device major/minor numbers are in the range for video devices. + struct stat s; + + if (lstat(device_path.c_str(), &s) != 0 || !S_ISCHR(s.st_mode)) return false; + + int video_fd = -1; + bool is_v4l2 = false; + + // check major/minur device numbers are in range for video device + if (major(s.st_rdev) == 81) { + dev_t num = minor(s.st_rdev); + if (num <= 63) { + video_fd = ::open(device_path.c_str(), O_RDONLY | O_NONBLOCK); + if ((video_fd >= 0) || (errno == EBUSY)) { + ::v4l2_capability video_caps; + memset(&video_caps, 0, sizeof(video_caps)); + + if ((errno == EBUSY) || + (::ioctl(video_fd, VIDIOC_QUERYCAP, &video_caps) >= 0 && + (video_caps.capabilities & V4L2_CAP_VIDEO_CAPTURE))) { + LOG(LS_INFO) << "Found V4L2 capture device " << device_path; + + is_v4l2 = true; + } else { + LOG_ERRNO(LS_ERROR) << "VIDIOC_QUERYCAP failed for " << device_path; + } + } else { + LOG_ERRNO(LS_ERROR) << "Failed to open " << device_path; + } + } + } + + if (video_fd >= 0) + ::close(video_fd); + + return is_v4l2; +} + +}; // namespace cricket diff --git a/talk/media/devices/v4llookup.h b/talk/media/devices/v4llookup.h new file mode 100644 index 0000000000..1bed90b650 --- /dev/null +++ b/talk/media/devices/v4llookup.h @@ -0,0 +1,70 @@ +/* + * libjingle + * Copyright 2009 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Author: lexnikitin@google.com (Alexey Nikitin) + * + * V4LLookup provides basic functionality to work with V2L2 devices in Linux + * The functionality is implemented as a class with virtual methods for + * the purpose of unit testing. + */ +#ifndef TALK_MEDIA_DEVICES_V4LLOOKUP_H_ +#define TALK_MEDIA_DEVICES_V4LLOOKUP_H_ + +#include <string> + +#ifdef LINUX +namespace cricket { +class V4LLookup { + public: + virtual ~V4LLookup() {} + + static bool IsV4L2Device(const std::string& device_path) { + return GetV4LLookup()->CheckIsV4L2Device(device_path); + } + + static void SetV4LLookup(V4LLookup* v4l_lookup) { + v4l_lookup_ = v4l_lookup; + } + + static V4LLookup* GetV4LLookup() { + if (!v4l_lookup_) { + v4l_lookup_ = new V4LLookup(); + } + return v4l_lookup_; + } + + protected: + static V4LLookup* v4l_lookup_; + // Making virtual so it is easier to mock + virtual bool CheckIsV4L2Device(const std::string& device_path); +}; + +} // namespace cricket + +#endif // LINUX +#endif // TALK_MEDIA_DEVICES_V4LLOOKUP_H_ diff --git a/talk/media/devices/videorendererfactory.h b/talk/media/devices/videorendererfactory.h new file mode 100644 index 0000000000..416f05b297 --- /dev/null +++ b/talk/media/devices/videorendererfactory.h @@ -0,0 +1,69 @@ +/* + * libjingle + * Copyright 2010 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// +// A factory to create a GUI video renderer. + +#ifndef TALK_MEDIA_DEVICES_VIDEORENDERERFACTORY_H_ +#define TALK_MEDIA_DEVICES_VIDEORENDERERFACTORY_H_ + +#include "talk/media/base/videorenderer.h" +#if defined(LINUX) && defined(HAVE_GTK) +#include "talk/media/devices/gtkvideorenderer.h" +#elif defined(OSX) && !defined(CARBON_DEPRECATED) +#include "talk/media/devices/carbonvideorenderer.h" +#elif defined(WIN32) +#include "talk/media/devices/gdivideorenderer.h" +#endif + +namespace cricket { + +class VideoRendererFactory { + public: + static VideoRenderer* CreateGuiVideoRenderer(int x, int y) { + #if defined(LINUX) && defined(HAVE_GTK) + return new GtkVideoRenderer(x, y); + #elif defined(OSX) && !defined(CARBON_DEPRECATED) + CarbonVideoRenderer* renderer = new CarbonVideoRenderer(x, y); + // Needs to be initialized on the main thread. + if (renderer->Initialize()) { + return renderer; + } else { + delete renderer; + return NULL; + } + #elif defined(WIN32) + return new GdiVideoRenderer(x, y); + #else + return NULL; + #endif + } +}; + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_VIDEORENDERERFACTORY_H_ diff --git a/talk/media/devices/win32deviceinfo.cc b/talk/media/devices/win32deviceinfo.cc new file mode 100644 index 0000000000..61a7759036 --- /dev/null +++ b/talk/media/devices/win32deviceinfo.cc @@ -0,0 +1,62 @@ +/* + * libjingle + * Copyright 2012 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/deviceinfo.h" + +namespace cricket { + +bool GetUsbId(const Device& device, std::string* usb_id) { + // Both PID and VID are 4 characters. + const int id_size = 4; + const char vid[] = "vid_"; // Also contains '\0'. + const size_t vid_location = device.id.find(vid); + if (vid_location == std::string::npos || + vid_location + sizeof(vid) - 1 + id_size > device.id.size()) { + return false; + } + const char pid[] = "pid_"; + const size_t pid_location = device.id.find(pid); + if (pid_location == std::string::npos || + pid_location + sizeof(pid) - 1 + id_size > device.id.size()) { + return false; + } + std::string id_vendor = device.id.substr(vid_location + sizeof(vid) - 1, + id_size); + std::string id_product = device.id.substr(pid_location + sizeof(pid) -1, + id_size); + usb_id->clear(); + usb_id->append(id_vendor); + usb_id->append(":"); + usb_id->append(id_product); + return true; +} + +bool GetUsbVersion(const Device& device, std::string* usb_version) { + return false; +} + +} // namespace cricket diff --git a/talk/media/devices/win32devicemanager.cc b/talk/media/devices/win32devicemanager.cc new file mode 100644 index 0000000000..1b9e9d86f6 --- /dev/null +++ b/talk/media/devices/win32devicemanager.cc @@ -0,0 +1,414 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/win32devicemanager.h" + +#include <atlbase.h> +#include <dbt.h> +#include <strmif.h> // must come before ks.h +#include <ks.h> +#include <ksmedia.h> +#include <mmdeviceapi.h> +#include <mmsystem.h> +#include <functiondiscoverykeys_devpkey.h> +#include <uuids.h> + +// PKEY_AudioEndpoint_GUID isn't included in uuid.lib and we don't want +// to define INITGUID in order to define all the uuids in this object file +// as it will conflict with uuid.lib (multiply defined symbols). +// So our workaround is to define this one missing symbol here manually. +// See: https://code.google.com/p/webrtc/issues/detail?id=3996 +EXTERN_C const PROPERTYKEY PKEY_AudioEndpoint_GUID = { { + 0x1da5d803, 0xd492, 0x4edd, { + 0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e + } }, 4 +}; + +#include "webrtc/base/logging.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/win32.h" // ToUtf8 +#include "webrtc/base/win32window.h" +#include "talk/media/base/mediacommon.h" +#ifdef HAVE_LOGITECH_HEADERS +#include "third_party/logitech/files/logitechquickcam.h" +#endif + +namespace cricket { + +DeviceManagerInterface* DeviceManagerFactory::Create() { + return new Win32DeviceManager(); +} + +class Win32DeviceWatcher + : public DeviceWatcher, + public rtc::Win32Window { + public: + explicit Win32DeviceWatcher(Win32DeviceManager* dm); + virtual ~Win32DeviceWatcher(); + virtual bool Start(); + virtual void Stop(); + + private: + HDEVNOTIFY Register(REFGUID guid); + void Unregister(HDEVNOTIFY notify); + virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result); + + Win32DeviceManager* manager_; + HDEVNOTIFY audio_notify_; + HDEVNOTIFY video_notify_; +}; + +static const char* kFilteredAudioDevicesName[] = { + NULL, +}; +static const char* const kFilteredVideoDevicesName[] = { + "Asus virtual Camera", // Bad Asus desktop virtual cam + "Bluetooth Video", // Bad Sony viao bluetooth sharing driver + NULL, +}; +static const wchar_t kFriendlyName[] = L"FriendlyName"; +static const wchar_t kDevicePath[] = L"DevicePath"; +static const char kUsbDevicePathPrefix[] = "\\\\?\\usb"; +static bool GetDevices(const CLSID& catid, std::vector<Device>* out); +static bool GetCoreAudioDevices(bool input, std::vector<Device>* devs); +static bool GetWaveDevices(bool input, std::vector<Device>* devs); + +Win32DeviceManager::Win32DeviceManager() + : need_couninitialize_(false) { + set_watcher(new Win32DeviceWatcher(this)); +} + +Win32DeviceManager::~Win32DeviceManager() { + if (initialized()) { + Terminate(); + } +} + +bool Win32DeviceManager::Init() { + if (!initialized()) { + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + need_couninitialize_ = SUCCEEDED(hr); + if (FAILED(hr)) { + LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr; + if (hr != RPC_E_CHANGED_MODE) { + return false; + } + } + if (!watcher()->Start()) { + return false; + } + set_initialized(true); + } + return true; +} + +void Win32DeviceManager::Terminate() { + if (initialized()) { + watcher()->Stop(); + if (need_couninitialize_) { + CoUninitialize(); + need_couninitialize_ = false; + } + set_initialized(false); + } +} + +bool Win32DeviceManager::GetDefaultVideoCaptureDevice(Device* device) { + bool ret = false; + // If there are multiple capture devices, we want the first USB one. + // This avoids issues with defaulting to virtual cameras or grabber cards. + std::vector<Device> devices; + ret = (GetVideoCaptureDevices(&devices) && !devices.empty()); + if (ret) { + *device = devices[0]; + for (size_t i = 0; i < devices.size(); ++i) { + if (strnicmp(devices[i].id.c_str(), kUsbDevicePathPrefix, + ARRAY_SIZE(kUsbDevicePathPrefix) - 1) == 0) { + *device = devices[i]; + break; + } + } + } + return ret; +} + +bool Win32DeviceManager::GetAudioDevices(bool input, + std::vector<Device>* devs) { + devs->clear(); + + if (rtc::IsWindowsVistaOrLater()) { + if (!GetCoreAudioDevices(input, devs)) + return false; + } else { + if (!GetWaveDevices(input, devs)) + return false; + } + return FilterDevices(devs, kFilteredAudioDevicesName); +} + +bool Win32DeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) { + devices->clear(); + if (!GetDevices(CLSID_VideoInputDeviceCategory, devices)) { + return false; + } + return FilterDevices(devices, kFilteredVideoDevicesName); +} + +bool GetDevices(const CLSID& catid, std::vector<Device>* devices) { + HRESULT hr; + + // CComPtr is a scoped pointer that will be auto released when going + // out of scope. CoUninitialize must not be called before the + // release. + CComPtr<ICreateDevEnum> sys_dev_enum; + CComPtr<IEnumMoniker> cam_enum; + if (FAILED(hr = sys_dev_enum.CoCreateInstance(CLSID_SystemDeviceEnum)) || + FAILED(hr = sys_dev_enum->CreateClassEnumerator(catid, &cam_enum, 0))) { + LOG(LS_ERROR) << "Failed to create device enumerator, hr=" << hr; + return false; + } + + // Only enum devices if CreateClassEnumerator returns S_OK. If there are no + // devices available, S_FALSE will be returned, but enumMk will be NULL. + if (hr == S_OK) { + CComPtr<IMoniker> mk; + while (cam_enum->Next(1, &mk, NULL) == S_OK) { +#ifdef HAVE_LOGITECH_HEADERS + // Initialize Logitech device if applicable + MaybeLogitechDeviceReset(mk); +#endif + CComPtr<IPropertyBag> bag; + if (SUCCEEDED(mk->BindToStorage(NULL, NULL, + __uuidof(bag), reinterpret_cast<void**>(&bag)))) { + CComVariant name, path; + std::string name_str, path_str; + if (SUCCEEDED(bag->Read(kFriendlyName, &name, 0)) && + name.vt == VT_BSTR) { + name_str = rtc::ToUtf8(name.bstrVal); + // Get the device id if one exists. + if (SUCCEEDED(bag->Read(kDevicePath, &path, 0)) && + path.vt == VT_BSTR) { + path_str = rtc::ToUtf8(path.bstrVal); + } + + devices->push_back(Device(name_str, path_str)); + } + } + mk = NULL; + } + } + + return true; +} + +HRESULT GetStringProp(IPropertyStore* bag, PROPERTYKEY key, std::string* out) { + out->clear(); + PROPVARIANT var; + PropVariantInit(&var); + + HRESULT hr = bag->GetValue(key, &var); + if (SUCCEEDED(hr)) { + if (var.pwszVal) + *out = rtc::ToUtf8(var.pwszVal); + else + hr = E_FAIL; + } + + PropVariantClear(&var); + return hr; +} + +// Adapted from http://msdn.microsoft.com/en-us/library/dd370812(v=VS.85).aspx +HRESULT CricketDeviceFromImmDevice(IMMDevice* device, Device* out) { + CComPtr<IPropertyStore> props; + + HRESULT hr = device->OpenPropertyStore(STGM_READ, &props); + if (FAILED(hr)) { + return hr; + } + + // Get the endpoint's name and id. + std::string name, guid; + hr = GetStringProp(props, PKEY_Device_FriendlyName, &name); + if (SUCCEEDED(hr)) { + hr = GetStringProp(props, PKEY_AudioEndpoint_GUID, &guid); + + if (SUCCEEDED(hr)) { + out->name = name; + out->id = guid; + } + } + return hr; +} + +bool GetCoreAudioDevices( + bool input, std::vector<Device>* devs) { + HRESULT hr = S_OK; + CComPtr<IMMDeviceEnumerator> enumerator; + + hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, + __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&enumerator)); + if (SUCCEEDED(hr)) { + CComPtr<IMMDeviceCollection> devices; + hr = enumerator->EnumAudioEndpoints((input ? eCapture : eRender), + DEVICE_STATE_ACTIVE, &devices); + if (SUCCEEDED(hr)) { + unsigned int count; + hr = devices->GetCount(&count); + + if (SUCCEEDED(hr)) { + for (unsigned int i = 0; i < count; i++) { + CComPtr<IMMDevice> device; + + // Get pointer to endpoint number i. + hr = devices->Item(i, &device); + if (FAILED(hr)) { + break; + } + + Device dev; + hr = CricketDeviceFromImmDevice(device, &dev); + if (SUCCEEDED(hr)) { + devs->push_back(dev); + } else { + LOG(LS_WARNING) << "Unable to query IMM Device, skipping. HR=" + << hr; + hr = S_FALSE; + } + } + } + } + } + + if (FAILED(hr)) { + LOG(LS_WARNING) << "GetCoreAudioDevices failed with hr " << hr; + return false; + } + return true; +} + +bool GetWaveDevices(bool input, std::vector<Device>* devs) { + // Note, we don't use the System Device Enumerator interface here since it + // adds lots of pseudo-devices to the list, such as DirectSound and Wave + // variants of the same device. + if (input) { + int num_devs = waveInGetNumDevs(); + for (int i = 0; i < num_devs; ++i) { + WAVEINCAPS caps; + if (waveInGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR && + caps.wChannels > 0) { + devs->push_back(Device(rtc::ToUtf8(caps.szPname), + rtc::ToString(i))); + } + } + } else { + int num_devs = waveOutGetNumDevs(); + for (int i = 0; i < num_devs; ++i) { + WAVEOUTCAPS caps; + if (waveOutGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR && + caps.wChannels > 0) { + devs->push_back(Device(rtc::ToUtf8(caps.szPname), i)); + } + } + } + return true; +} + +Win32DeviceWatcher::Win32DeviceWatcher(Win32DeviceManager* manager) + : DeviceWatcher(manager), + manager_(manager), + audio_notify_(NULL), + video_notify_(NULL) { +} + +Win32DeviceWatcher::~Win32DeviceWatcher() { +} + +bool Win32DeviceWatcher::Start() { + if (!Create(NULL, _T("libjingle Win32DeviceWatcher Window"), + 0, 0, 0, 0, 0, 0)) { + return false; + } + + audio_notify_ = Register(KSCATEGORY_AUDIO); + if (!audio_notify_) { + Stop(); + return false; + } + + video_notify_ = Register(KSCATEGORY_VIDEO); + if (!video_notify_) { + Stop(); + return false; + } + + return true; +} + +void Win32DeviceWatcher::Stop() { + UnregisterDeviceNotification(video_notify_); + video_notify_ = NULL; + UnregisterDeviceNotification(audio_notify_); + audio_notify_ = NULL; + Destroy(); +} + +HDEVNOTIFY Win32DeviceWatcher::Register(REFGUID guid) { + DEV_BROADCAST_DEVICEINTERFACE dbdi; + dbdi.dbcc_size = sizeof(dbdi); + dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + dbdi.dbcc_classguid = guid; + dbdi.dbcc_name[0] = '\0'; + return RegisterDeviceNotification(handle(), &dbdi, + DEVICE_NOTIFY_WINDOW_HANDLE); +} + +void Win32DeviceWatcher::Unregister(HDEVNOTIFY handle) { + UnregisterDeviceNotification(handle); +} + +bool Win32DeviceWatcher::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, + LRESULT& result) { + if (uMsg == WM_DEVICECHANGE) { + if (wParam == DBT_DEVICEARRIVAL || + wParam == DBT_DEVICEREMOVECOMPLETE) { + DEV_BROADCAST_DEVICEINTERFACE* dbdi = + reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(lParam); + if (dbdi->dbcc_classguid == KSCATEGORY_AUDIO || + dbdi->dbcc_classguid == KSCATEGORY_VIDEO) { + manager_->SignalDevicesChange(); + } + } + result = 0; + return true; + } + + return false; +} + +}; // namespace cricket diff --git a/talk/media/devices/win32devicemanager.h b/talk/media/devices/win32devicemanager.h new file mode 100644 index 0000000000..5f8ba8333a --- /dev/null +++ b/talk/media/devices/win32devicemanager.h @@ -0,0 +1,60 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_DEVICES_WIN32DEVICEMANAGER_H_ +#define TALK_MEDIA_DEVICES_WIN32DEVICEMANAGER_H_ + +#include <string> +#include <vector> + +#include "talk/media/devices/devicemanager.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/stringencode.h" + +namespace cricket { + +class Win32DeviceManager : public DeviceManager { + public: + Win32DeviceManager(); + virtual ~Win32DeviceManager(); + + // Initialization + virtual bool Init(); + virtual void Terminate(); + + virtual bool GetVideoCaptureDevices(std::vector<Device>* devs); + + private: + virtual bool GetAudioDevices(bool input, std::vector<Device>* devs); + virtual bool GetDefaultVideoCaptureDevice(Device* device); + + bool need_couninitialize_; +}; + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_WIN32DEVICEMANAGER_H_ diff --git a/talk/media/devices/yuvframescapturer.cc b/talk/media/devices/yuvframescapturer.cc new file mode 100644 index 0000000000..a60ad0270a --- /dev/null +++ b/talk/media/devices/yuvframescapturer.cc @@ -0,0 +1,199 @@ +/* + * libjingle + * Copyright 2004--2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/devices/yuvframescapturer.h" + +#include "webrtc/base/bytebuffer.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" + +#include "webrtc/system_wrappers/include/clock.h" + +namespace cricket { +/////////////////////////////////////////////////////////////////////// +// Definition of private class YuvFramesThread that periodically generates +// frames. +/////////////////////////////////////////////////////////////////////// +class YuvFramesCapturer::YuvFramesThread + : public rtc::Thread, public rtc::MessageHandler { + public: + explicit YuvFramesThread(YuvFramesCapturer* capturer) + : capturer_(capturer), + finished_(false) { + } + + virtual ~YuvFramesThread() { + Stop(); + } + + // Override virtual method of parent Thread. Context: Worker Thread. + virtual void Run() { + // Read the first frame and start the message pump. The pump runs until + // Stop() is called externally or Quit() is called by OnMessage(). + int waiting_time_ms = 0; + if (capturer_) { + capturer_->ReadFrame(true); + PostDelayed(waiting_time_ms, this); + Thread::Run(); + } + + rtc::CritScope cs(&crit_); + finished_ = true; + } + + // Override virtual method of parent MessageHandler. Context: Worker Thread. + virtual void OnMessage(rtc::Message* /*pmsg*/) { + int waiting_time_ms = 0; + if (capturer_) { + capturer_->ReadFrame(false); + PostDelayed(waiting_time_ms, this); + } else { + Quit(); + } + } + + // Check if Run() is finished. + bool Finished() const { + rtc::CritScope cs(&crit_); + return finished_; + } + + private: + YuvFramesCapturer* capturer_; + mutable rtc::CriticalSection crit_; + bool finished_; + + RTC_DISALLOW_COPY_AND_ASSIGN(YuvFramesThread); +}; + +///////////////////////////////////////////////////////////////////// +// Implementation of class YuvFramesCapturer. +///////////////////////////////////////////////////////////////////// + +const char* YuvFramesCapturer::kYuvFrameDeviceName = "YuvFramesGenerator"; + +// TODO(shaowei): allow width_ and height_ to be configurable. +YuvFramesCapturer::YuvFramesCapturer() + : frames_generator_thread(NULL), + width_(640), + height_(480), + frame_index_(0), + barcode_interval_(1) { +} + +YuvFramesCapturer::~YuvFramesCapturer() { + Stop(); + delete[] static_cast<char*>(captured_frame_.data); +} + +void YuvFramesCapturer::Init() { + int size = width_ * height_; + int qsize = size / 4; + frame_generator_ = new YuvFrameGenerator(width_, height_, true); + frame_data_size_ = size + 2 * qsize; + captured_frame_.data = new char[frame_data_size_]; + captured_frame_.fourcc = FOURCC_IYUV; + captured_frame_.pixel_height = 1; + captured_frame_.pixel_width = 1; + captured_frame_.width = width_; + captured_frame_.height = height_; + captured_frame_.data_size = frame_data_size_; + + // Enumerate the supported formats. We have only one supported format. + VideoFormat format(width_, height_, VideoFormat::kMinimumInterval, + FOURCC_IYUV); + std::vector<VideoFormat> supported; + supported.push_back(format); + SetSupportedFormats(supported); +} + +CaptureState YuvFramesCapturer::Start(const VideoFormat& capture_format) { + if (IsRunning()) { + LOG(LS_ERROR) << "Yuv Frame Generator is already running"; + return CS_FAILED; + } + SetCaptureFormat(&capture_format); + + barcode_reference_timestamp_millis_ = + static_cast<int64_t>(rtc::Time()) * 1000; + // Create a thread to generate frames. + frames_generator_thread = new YuvFramesThread(this); + bool ret = frames_generator_thread->Start(); + if (ret) { + LOG(LS_INFO) << "Yuv Frame Generator started"; + return CS_RUNNING; + } else { + LOG(LS_ERROR) << "Yuv Frame Generator failed to start"; + return CS_FAILED; + } +} + +bool YuvFramesCapturer::IsRunning() { + return frames_generator_thread && !frames_generator_thread->Finished(); +} + +void YuvFramesCapturer::Stop() { + if (frames_generator_thread) { + frames_generator_thread->Stop(); + frames_generator_thread = NULL; + LOG(LS_INFO) << "Yuv Frame Generator stopped"; + } + SetCaptureFormat(NULL); +} + +bool YuvFramesCapturer::GetPreferredFourccs(std::vector<uint32_t>* fourccs) { + if (!fourccs) { + return false; + } + fourccs->push_back(GetSupportedFormats()->at(0).fourcc); + return true; +} + +// Executed in the context of YuvFramesThread. +void YuvFramesCapturer::ReadFrame(bool first_frame) { + // 1. Signal the previously read frame to downstream. + if (!first_frame) { + SignalFrameCaptured(this, &captured_frame_); + } + uint8_t* buffer = new uint8_t[frame_data_size_]; + frame_generator_->GenerateNextFrame(buffer, GetBarcodeValue()); + frame_index_++; + memmove(captured_frame_.data, buffer, frame_data_size_); + delete[] buffer; +} + +int32_t YuvFramesCapturer::GetBarcodeValue() { + if (barcode_reference_timestamp_millis_ == -1 || + frame_index_ % barcode_interval_ != 0) { + return -1; + } + int64_t now_millis = static_cast<int64_t>(rtc::Time()) * 1000; + return static_cast<int32_t>(now_millis - barcode_reference_timestamp_millis_); +} + +} // namespace cricket diff --git a/talk/media/devices/yuvframescapturer.h b/talk/media/devices/yuvframescapturer.h new file mode 100644 index 0000000000..850a8dfb1d --- /dev/null +++ b/talk/media/devices/yuvframescapturer.h @@ -0,0 +1,98 @@ +/* + * libjingle + * Copyright 2010 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_DEVICES_YUVFRAMESCAPTURER_H_ +#define TALK_MEDIA_DEVICES_YUVFRAMESCAPTURER_H_ + +#include <string> +#include <vector> + +#include "talk/media/base/videocapturer.h" +#include "talk/media/base/yuvframegenerator.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringutils.h" + + +namespace rtc { +class FileStream; +} + +namespace cricket { + + +// Simulated video capturer that periodically reads frames from a file. +class YuvFramesCapturer : public VideoCapturer { + public: + YuvFramesCapturer(); + YuvFramesCapturer(int width, int height); + virtual ~YuvFramesCapturer(); + + static const char* kYuvFrameDeviceName; + static Device CreateYuvFramesCapturerDevice() { + std::stringstream id; + id << kYuvFrameDeviceName; + return Device(id.str(), id.str()); + } + static bool IsYuvFramesCapturerDevice(const Device& device) { + return rtc::starts_with(device.id.c_str(), kYuvFrameDeviceName); + } + + void Init(); + // Override virtual methods of parent class VideoCapturer. + virtual CaptureState Start(const VideoFormat& capture_format); + virtual void Stop(); + virtual bool IsRunning(); + virtual bool IsScreencast() const { return false; } + + protected: + // Override virtual methods of parent class VideoCapturer. + virtual bool GetPreferredFourccs(std::vector<uint32_t>* fourccs); + + // Read a frame and determine how long to wait for the next frame. + void ReadFrame(bool first_frame); + + private: + class YuvFramesThread; // Forward declaration, defined in .cc. + + YuvFrameGenerator* frame_generator_; + CapturedFrame captured_frame_; + YuvFramesThread* frames_generator_thread; + int width_; + int height_; + uint32_t frame_data_size_; + uint32_t frame_index_; + + int64_t barcode_reference_timestamp_millis_; + int32_t barcode_interval_; + int32_t GetBarcodeValue(); + + RTC_DISALLOW_COPY_AND_ASSIGN(YuvFramesCapturer); +}; + +} // namespace cricket + +#endif // TALK_MEDIA_DEVICES_YUVFRAMESCAPTURER_H_ |