1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/capture/video/win/video_capture_device_utils_win.h"
6
7 #include <cmath>
8 #include <iostream>
9
10 #include "base/win/win_util.h"
11 #include "base/win/windows_version.h"
12
13 namespace media {
14
15 namespace {
16
17 const int kDegreesToArcSeconds = 3600;
18 const int kSecondsTo100MicroSeconds = 10000;
19
20 // Determines if camera is mounted on a device with naturally portrait display.
IsPortraitDevice(DWORD display_height,DWORD display_width,DWORD display_orientation)21 bool IsPortraitDevice(DWORD display_height,
22 DWORD display_width,
23 DWORD display_orientation) {
24 if (display_orientation == DMDO_DEFAULT || display_orientation == DMDO_180)
25 return display_height >= display_width;
26 else
27 return display_height < display_width;
28 }
29
30 } // namespace
31
32 // Windows platform stores pan and tilt (min, max, step and current) in
33 // degrees. Spec expects them in arc seconds.
34 // https://docs.microsoft.com/en-us/windows/win32/api/strmif/ne-strmif-cameracontrolproperty
35 // spec: https://w3c.github.io/mediacapture-image/#pan
CaptureAngleToPlatformValue(double arc_seconds)36 long CaptureAngleToPlatformValue(double arc_seconds) {
37 return std::round(arc_seconds / kDegreesToArcSeconds);
38 }
39
PlatformAngleToCaptureValue(long degrees)40 double PlatformAngleToCaptureValue(long degrees) {
41 return 1.0 * degrees * kDegreesToArcSeconds;
42 }
43
PlatformAngleToCaptureStep(long step,double min,double max)44 double PlatformAngleToCaptureStep(long step, double min, double max) {
45 return PlatformAngleToCaptureValue(step);
46 }
47
48 // Windows platform stores exposure time (min, max and current) in log base 2
49 // seconds. If value is n, exposure time is 2^n seconds. Spec expects exposure
50 // times in 100 micro seconds.
51 // https://docs.microsoft.com/en-us/windows/win32/api/strmif/ne-strmif-cameracontrolproperty
52 // spec: https://w3c.github.io/mediacapture-image/#exposure-time
CaptureExposureTimeToPlatformValue(double hundreds_of_microseconds)53 long CaptureExposureTimeToPlatformValue(double hundreds_of_microseconds) {
54 return std::log2(hundreds_of_microseconds / kSecondsTo100MicroSeconds);
55 }
56
PlatformExposureTimeToCaptureValue(long log_seconds)57 double PlatformExposureTimeToCaptureValue(long log_seconds) {
58 return std::exp2(log_seconds) * kSecondsTo100MicroSeconds;
59 }
60
PlatformExposureTimeToCaptureStep(long log_step,double min,double max)61 double PlatformExposureTimeToCaptureStep(long log_step,
62 double min,
63 double max) {
64 // The smallest possible value is
65 // |exp2(min_log_seconds) * kSecondsTo100MicroSeconds|.
66 // That value can be computed by PlatformExposureTimeToCaptureValue and is
67 // passed to this function as |min| thus there is not need to recompute it
68 // here.
69 // The second smallest possible value is
70 // |exp2(min_log_seconds + log_step) * kSecondsTo100MicroSeconds| which equals
71 // to |exp2(log_step) * min|.
72 // While the relative step or ratio between consecutive values is always the
73 // same (|std::exp2(log_step)|), the smallest absolute step is between the
74 // smallest and the second smallest possible values i.e. between |min| and
75 // |exp2(log_step) * min|.
76 return (std::exp2(log_step) - 1) * min;
77 }
78
79 // Note: Because we can't find a solid way to detect camera location (front/back
80 // or external USB camera) with Win32 APIs, assume it's always front camera when
81 // auto rotation is enabled for now.
GetCameraRotation(VideoFacingMode facing)82 int GetCameraRotation(VideoFacingMode facing) {
83 int rotation = 0;
84
85 // Before Win10, we can't distinguish if the selected camera is an internal or
86 // external one. So we assume it's internal and do the frame rotation if the
87 // auto rotation is enabled to cover most user cases.
88 if (!IsInternalCamera(facing)) {
89 return rotation;
90 }
91
92 // When display is only on external monitors, the auto-rotation state still
93 // may be ENALBED on the target device. In that case, we shouldn't query the
94 // display orientation and the built-in camera will be treated as an external
95 // one.
96 DISPLAY_DEVICE internal_display_device;
97 if (!HasActiveInternalDisplayDevice(&internal_display_device)) {
98 return rotation;
99 }
100
101 if (facing == VideoFacingMode::MEDIA_VIDEO_FACING_NONE) {
102 // We set camera facing using Win10 only DeviceInformation API. So pre-Win10
103 // cameras always have a facing of VideoFacingMode::MEDIA_VIDEO_FACING_NONE.
104 // Win10 cameras with VideoFacingMode::MEDIA_VIDEO_FACING_NONE should early
105 // exit as part of the IsInternalCamera(facing) check above.
106 DCHECK(base::win::GetVersion() < base::win::Version::WIN10);
107 }
108
109 DEVMODE mode;
110 ::ZeroMemory(&mode, sizeof(mode));
111 mode.dmSize = sizeof(mode);
112 mode.dmDriverExtra = 0;
113 if (::EnumDisplaySettings(internal_display_device.DeviceName,
114 ENUM_CURRENT_SETTINGS, &mode)) {
115 int camera_offset = 0; // Measured in degrees, clockwise.
116 bool portrait_device = IsPortraitDevice(mode.dmPelsHeight, mode.dmPelsWidth,
117 mode.dmDisplayOrientation);
118 switch (mode.dmDisplayOrientation) {
119 case DMDO_DEFAULT:
120 if (portrait_device &&
121 facing == VideoFacingMode::MEDIA_VIDEO_FACING_ENVIRONMENT) {
122 camera_offset = 270; // Adjust portrait device rear camera by 180.
123 } else if (portrait_device) {
124 camera_offset = 90; // Portrait device front camera is offset by 90.
125 } else {
126 camera_offset = 0;
127 }
128 break;
129 case DMDO_90:
130 if (portrait_device)
131 camera_offset = 180;
132 else if (facing == VideoFacingMode::MEDIA_VIDEO_FACING_ENVIRONMENT)
133 camera_offset = 270; // Adjust landscape device rear camera by 180.
134 else
135 camera_offset = 90;
136 break;
137 case DMDO_180:
138 if (portrait_device &&
139 facing == VideoFacingMode::MEDIA_VIDEO_FACING_ENVIRONMENT) {
140 camera_offset = 90; // Adjust portrait device rear camera by 180.
141 } else if (portrait_device) {
142 camera_offset = 270;
143 } else {
144 camera_offset = 180;
145 }
146 break;
147 case DMDO_270:
148 if (portrait_device)
149 camera_offset = 0;
150 else if (facing == VideoFacingMode::MEDIA_VIDEO_FACING_ENVIRONMENT)
151 camera_offset = 90; // Adjust landscape device rear camera by 180.
152 else
153 camera_offset = 270;
154 break;
155 }
156 rotation = (360 - camera_offset) % 360;
157 }
158
159 return rotation;
160 }
161
IsAutoRotationEnabled()162 bool IsAutoRotationEnabled() {
163 typedef BOOL(WINAPI * GetAutoRotationState)(PAR_STATE state);
164 static const auto get_rotation_state = reinterpret_cast<GetAutoRotationState>(
165 base::win::GetUser32FunctionPointer("GetAutoRotationState"));
166
167 if (get_rotation_state) {
168 AR_STATE auto_rotation_state;
169 ::ZeroMemory(&auto_rotation_state, sizeof(AR_STATE));
170
171 if (get_rotation_state(&auto_rotation_state)) {
172 // AR_ENABLED is defined as '0x0', while AR_STATE enumeration is defined
173 // as bitwise. See the example codes in
174 // https://msdn.microsoft.com/en-us/library/windows/desktop/dn629263(v=vs.85).aspx.
175 if (auto_rotation_state == AR_ENABLED) {
176 return true;
177 }
178 }
179 }
180
181 return false;
182 }
183
IsInternalCamera(VideoFacingMode facing)184 bool IsInternalCamera(VideoFacingMode facing) {
185 if (base::win::GetVersion() < base::win::Version::WIN10) {
186 return true;
187 }
188
189 if (facing == MEDIA_VIDEO_FACING_USER ||
190 facing == MEDIA_VIDEO_FACING_ENVIRONMENT) {
191 return true;
192 }
193
194 return false;
195 }
196
HasActiveInternalDisplayDevice(DISPLAY_DEVICE * internal_display_device)197 bool HasActiveInternalDisplayDevice(DISPLAY_DEVICE* internal_display_device) {
198 DISPLAY_DEVICE display_device;
199 display_device.cb = sizeof(display_device);
200
201 for (int device_index = 0;; ++device_index) {
202 BOOL enum_result =
203 ::EnumDisplayDevices(NULL, device_index, &display_device, 0);
204 if (!enum_result)
205 break;
206 if (!(display_device.StateFlags & DISPLAY_DEVICE_ACTIVE))
207 continue;
208
209 HRESULT hr = CheckPathInfoForInternal(display_device.DeviceName);
210 if (SUCCEEDED(hr)) {
211 *internal_display_device = display_device;
212 return true;
213 }
214 }
215 return false;
216 }
217
CheckPathInfoForInternal(const PCWSTR device_name)218 HRESULT CheckPathInfoForInternal(const PCWSTR device_name) {
219 HRESULT hr = S_OK;
220 UINT32 path_info_array_size = 0;
221 UINT32 mode_info_array_size = 0;
222 DISPLAYCONFIG_PATH_INFO* path_info_array = nullptr;
223 DISPLAYCONFIG_MODE_INFO* mode_info_array = nullptr;
224
225 do {
226 // In case this isn't the first time through the loop, delete the buffers
227 // allocated.
228 delete[] path_info_array;
229 path_info_array = nullptr;
230
231 delete[] mode_info_array;
232 mode_info_array = nullptr;
233
234 hr = HRESULT_FROM_WIN32(::GetDisplayConfigBufferSizes(
235 QDC_ONLY_ACTIVE_PATHS, &path_info_array_size, &mode_info_array_size));
236 if (FAILED(hr)) {
237 break;
238 }
239
240 path_info_array =
241 new (std::nothrow) DISPLAYCONFIG_PATH_INFO[path_info_array_size];
242 if (path_info_array == nullptr) {
243 hr = E_OUTOFMEMORY;
244 break;
245 }
246
247 mode_info_array =
248 new (std::nothrow) DISPLAYCONFIG_MODE_INFO[mode_info_array_size];
249 if (mode_info_array == nullptr) {
250 hr = E_OUTOFMEMORY;
251 break;
252 }
253
254 hr = HRESULT_FROM_WIN32(::QueryDisplayConfig(
255 QDC_ONLY_ACTIVE_PATHS, &path_info_array_size, path_info_array,
256 &mode_info_array_size, mode_info_array, nullptr));
257 } while (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
258
259 int desired_path_index = -1;
260 if (SUCCEEDED(hr)) {
261 // Loop through all sources until the one which matches the |device_name|
262 // is found.
263 for (UINT32 path_index = 0; path_index < path_info_array_size;
264 ++path_index) {
265 DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name = {};
266 source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
267 source_name.header.size = sizeof(source_name);
268 source_name.header.adapterId =
269 path_info_array[path_index].sourceInfo.adapterId;
270 source_name.header.id = path_info_array[path_index].sourceInfo.id;
271
272 hr =
273 HRESULT_FROM_WIN32(::DisplayConfigGetDeviceInfo(&source_name.header));
274 if (SUCCEEDED(hr)) {
275 if (wcscmp(device_name, source_name.viewGdiDeviceName) == 0 &&
276 IsInternalVideoOutput(
277 path_info_array[path_index].targetInfo.outputTechnology)) {
278 desired_path_index = path_index;
279 break;
280 }
281 }
282 }
283 }
284
285 if (desired_path_index == -1) {
286 hr = E_INVALIDARG;
287 }
288
289 delete[] path_info_array;
290 path_info_array = nullptr;
291
292 delete[] mode_info_array;
293 mode_info_array = nullptr;
294
295 return hr;
296 }
297
IsInternalVideoOutput(const DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY video_output_tech_type)298 bool IsInternalVideoOutput(
299 const DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY video_output_tech_type) {
300 switch (video_output_tech_type) {
301 case DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL:
302 case DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED:
303 case DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED:
304 return TRUE;
305
306 default:
307 return FALSE;
308 }
309 }
310
311 } // namespace media
312