blob: ee58fbc383e9cacd837bbf42021fd3154c3e2a35 [file] [log] [blame]
// 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