| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/capture/video/win/video_capture_device_utils_win.h" |
| |
| #include <cmath> |
| #include <iostream> |
| |
| #include "base/win/win_util.h" |
| #include "base/win/windows_version.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| const int kDegreesToArcSeconds = 3600; |
| const int kSecondsTo100MicroSeconds = 10000; |
| |
| // Determines if camera is mounted on a device with naturally portrait display. |
| bool IsPortraitDevice(DWORD display_height, |
| DWORD display_width, |
| DWORD display_orientation) { |
| if (display_orientation == DMDO_DEFAULT || display_orientation == DMDO_180) |
| return display_height >= display_width; |
| else |
| return display_height < display_width; |
| } |
| |
| } // namespace |
| |
| // Windows platform stores pan and tilt (min, max, step and current) in |
| // degrees. Spec expects them in arc seconds. |
| // https://docs.microsoft.com/en-us/windows/win32/api/strmif/ne-strmif-cameracontrolproperty |
| // spec: https://w3c.github.io/mediacapture-image/#pan |
| long CaptureAngleToPlatformValue(double arc_seconds) { |
| return std::round(arc_seconds / kDegreesToArcSeconds); |
| } |
| |
| double PlatformAngleToCaptureValue(long degrees) { |
| return 1.0 * degrees * kDegreesToArcSeconds; |
| } |
| |
| double PlatformAngleToCaptureStep(long step, double min, double max) { |
| return PlatformAngleToCaptureValue(step); |
| } |
| |
| // Windows platform stores exposure time (min, max and current) in log base 2 |
| // seconds. If value is n, exposure time is 2^n seconds. Spec expects exposure |
| // times in 100 micro seconds. |
| // https://docs.microsoft.com/en-us/windows/win32/api/strmif/ne-strmif-cameracontrolproperty |
| // spec: https://w3c.github.io/mediacapture-image/#exposure-time |
| long CaptureExposureTimeToPlatformValue(double hundreds_of_microseconds) { |
| return std::log2(hundreds_of_microseconds / kSecondsTo100MicroSeconds); |
| } |
| |
| double PlatformExposureTimeToCaptureValue(long log_seconds) { |
| return std::exp2(log_seconds) * kSecondsTo100MicroSeconds; |
| } |
| |
| double PlatformExposureTimeToCaptureStep(long log_step, |
| double min, |
| double max) { |
| // The smallest possible value is |
| // |exp2(min_log_seconds) * kSecondsTo100MicroSeconds|. |
| // That value can be computed by PlatformExposureTimeToCaptureValue and is |
| // passed to this function as |min| thus there is not need to recompute it |
| // here. |
| // The second smallest possible value is |
| // |exp2(min_log_seconds + log_step) * kSecondsTo100MicroSeconds| which equals |
| // to |exp2(log_step) * min|. |
| // While the relative step or ratio between consecutive values is always the |
| // same (|std::exp2(log_step)|), the smallest absolute step is between the |
| // smallest and the second smallest possible values i.e. between |min| and |
| // |exp2(log_step) * min|. |
| return (std::exp2(log_step) - 1) * min; |
| } |
| |
| // Note: Because we can't find a solid way to detect camera location (front/back |
| // or external USB camera) with Win32 APIs, assume it's always front camera when |
| // auto rotation is enabled for now. |
| int GetCameraRotation(VideoFacingMode facing) { |
| int rotation = 0; |
| |
| // Before Win10, we can't distinguish if the selected camera is an internal or |
| // external one. So we assume it's internal and do the frame rotation if the |
| // auto rotation is enabled to cover most user cases. |
| if (!IsInternalCamera(facing)) { |
| return rotation; |
| } |
| |
| // When display is only on external monitors, the auto-rotation state still |
| // may be ENALBED on the target device. In that case, we shouldn't query the |
| // display orientation and the built-in camera will be treated as an external |
| // one. |
| DISPLAY_DEVICE internal_display_device; |
| if (!HasActiveInternalDisplayDevice(&internal_display_device)) { |
| return rotation; |
| } |
| |
| if (facing == VideoFacingMode::MEDIA_VIDEO_FACING_NONE) { |
| // We set camera facing using Win10 only DeviceInformation API. So pre-Win10 |
| // cameras always have a facing of VideoFacingMode::MEDIA_VIDEO_FACING_NONE. |
| // Win10 cameras with VideoFacingMode::MEDIA_VIDEO_FACING_NONE should early |
| // exit as part of the IsInternalCamera(facing) check above. |
| DCHECK(base::win::GetVersion() < base::win::Version::WIN10); |
| } |
| |
| DEVMODE mode; |
| ::ZeroMemory(&mode, sizeof(mode)); |
| mode.dmSize = sizeof(mode); |
| mode.dmDriverExtra = 0; |
| if (::EnumDisplaySettings(internal_display_device.DeviceName, |
| ENUM_CURRENT_SETTINGS, &mode)) { |
| int camera_offset = 0; // Measured in degrees, clockwise. |
| bool portrait_device = IsPortraitDevice(mode.dmPelsHeight, mode.dmPelsWidth, |
| mode.dmDisplayOrientation); |
| switch (mode.dmDisplayOrientation) { |
| case DMDO_DEFAULT: |
| if (portrait_device && |
| facing == VideoFacingMode::MEDIA_VIDEO_FACING_ENVIRONMENT) { |
| camera_offset = 270; // Adjust portrait device rear camera by 180. |
| } else if (portrait_device) { |
| camera_offset = 90; // Portrait device front camera is offset by 90. |
| } else { |
| camera_offset = 0; |
| } |
| break; |
| case DMDO_90: |
| if (portrait_device) |
| camera_offset = 180; |
| else if (facing == VideoFacingMode::MEDIA_VIDEO_FACING_ENVIRONMENT) |
| camera_offset = 270; // Adjust landscape device rear camera by 180. |
| else |
| camera_offset = 90; |
| break; |
| case DMDO_180: |
| if (portrait_device && |
| facing == VideoFacingMode::MEDIA_VIDEO_FACING_ENVIRONMENT) { |
| camera_offset = 90; // Adjust portrait device rear camera by 180. |
| } else if (portrait_device) { |
| camera_offset = 270; |
| } else { |
| camera_offset = 180; |
| } |
| break; |
| case DMDO_270: |
| if (portrait_device) |
| camera_offset = 0; |
| else if (facing == VideoFacingMode::MEDIA_VIDEO_FACING_ENVIRONMENT) |
| camera_offset = 90; // Adjust landscape device rear camera by 180. |
| else |
| camera_offset = 270; |
| break; |
| } |
| rotation = (360 - camera_offset) % 360; |
| } |
| |
| return rotation; |
| } |
| |
| bool IsAutoRotationEnabled() { |
| typedef BOOL(WINAPI * GetAutoRotationState)(PAR_STATE state); |
| static const auto get_rotation_state = reinterpret_cast<GetAutoRotationState>( |
| base::win::GetUser32FunctionPointer("GetAutoRotationState")); |
| |
| if (get_rotation_state) { |
| AR_STATE auto_rotation_state; |
| ::ZeroMemory(&auto_rotation_state, sizeof(AR_STATE)); |
| |
| if (get_rotation_state(&auto_rotation_state)) { |
| // AR_ENABLED is defined as '0x0', while AR_STATE enumeration is defined |
| // as bitwise. See the example codes in |
| // https://msdn.microsoft.com/en-us/library/windows/desktop/dn629263(v=vs.85).aspx. |
| if (auto_rotation_state == AR_ENABLED) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| bool IsInternalCamera(VideoFacingMode facing) { |
| if (base::win::GetVersion() < base::win::Version::WIN10) { |
| return true; |
| } |
| |
| if (facing == MEDIA_VIDEO_FACING_USER || |
| facing == MEDIA_VIDEO_FACING_ENVIRONMENT) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool HasActiveInternalDisplayDevice(DISPLAY_DEVICE* internal_display_device) { |
| DISPLAY_DEVICE display_device; |
| display_device.cb = sizeof(display_device); |
| |
| for (int device_index = 0;; ++device_index) { |
| BOOL enum_result = |
| ::EnumDisplayDevices(NULL, device_index, &display_device, 0); |
| if (!enum_result) |
| break; |
| if (!(display_device.StateFlags & DISPLAY_DEVICE_ACTIVE)) |
| continue; |
| |
| HRESULT hr = CheckPathInfoForInternal(display_device.DeviceName); |
| if (SUCCEEDED(hr)) { |
| *internal_display_device = display_device; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| HRESULT CheckPathInfoForInternal(const PCWSTR device_name) { |
| HRESULT hr = S_OK; |
| UINT32 path_info_array_size = 0; |
| UINT32 mode_info_array_size = 0; |
| DISPLAYCONFIG_PATH_INFO* path_info_array = nullptr; |
| DISPLAYCONFIG_MODE_INFO* mode_info_array = nullptr; |
| |
| do { |
| // In case this isn't the first time through the loop, delete the buffers |
| // allocated. |
| delete[] path_info_array; |
| path_info_array = nullptr; |
| |
| delete[] mode_info_array; |
| mode_info_array = nullptr; |
| |
| hr = HRESULT_FROM_WIN32(::GetDisplayConfigBufferSizes( |
| QDC_ONLY_ACTIVE_PATHS, &path_info_array_size, &mode_info_array_size)); |
| if (FAILED(hr)) { |
| break; |
| } |
| |
| path_info_array = |
| new (std::nothrow) DISPLAYCONFIG_PATH_INFO[path_info_array_size]; |
| if (path_info_array == nullptr) { |
| hr = E_OUTOFMEMORY; |
| break; |
| } |
| |
| mode_info_array = |
| new (std::nothrow) DISPLAYCONFIG_MODE_INFO[mode_info_array_size]; |
| if (mode_info_array == nullptr) { |
| hr = E_OUTOFMEMORY; |
| break; |
| } |
| |
| hr = HRESULT_FROM_WIN32(::QueryDisplayConfig( |
| QDC_ONLY_ACTIVE_PATHS, &path_info_array_size, path_info_array, |
| &mode_info_array_size, mode_info_array, nullptr)); |
| } while (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)); |
| |
| int desired_path_index = -1; |
| if (SUCCEEDED(hr)) { |
| // Loop through all sources until the one which matches the |device_name| |
| // is found. |
| for (UINT32 path_index = 0; path_index < path_info_array_size; |
| ++path_index) { |
| DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name = {}; |
| source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; |
| source_name.header.size = sizeof(source_name); |
| source_name.header.adapterId = |
| path_info_array[path_index].sourceInfo.adapterId; |
| source_name.header.id = path_info_array[path_index].sourceInfo.id; |
| |
| hr = |
| HRESULT_FROM_WIN32(::DisplayConfigGetDeviceInfo(&source_name.header)); |
| if (SUCCEEDED(hr)) { |
| if (wcscmp(device_name, source_name.viewGdiDeviceName) == 0 && |
| IsInternalVideoOutput( |
| path_info_array[path_index].targetInfo.outputTechnology)) { |
| desired_path_index = path_index; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (desired_path_index == -1) { |
| hr = E_INVALIDARG; |
| } |
| |
| delete[] path_info_array; |
| path_info_array = nullptr; |
| |
| delete[] mode_info_array; |
| mode_info_array = nullptr; |
| |
| return hr; |
| } |
| |
| bool IsInternalVideoOutput( |
| const DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY video_output_tech_type) { |
| switch (video_output_tech_type) { |
| case DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL: |
| case DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED: |
| case DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED: |
| return TRUE; |
| |
| default: |
| return FALSE; |
| } |
| } |
| |
| } // namespace media |