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