How To Scan QRCode For UWP (2)
这篇随笔主要介绍照相预览功能,重要使用的是MediaCapture对象,MediaCapture对象还可以用来处理录音和录制视频,本文只讨论照相功能。
1:查找摄像头
后置摄像头优先,找不到后置摄像头就返回找到的可用的摄像头列表的第一个。
1 /// <summary> 2 /// Queries the available video capture devices to try and find one mounted on the desired panel. 3 /// </summary> 4 /// <param name="desiredPanel">The panel on the device that the desired camera is mounted on.</param> 5 /// <returns>A DeviceInformation instance with a reference to the camera mounted on the desired panel if available, 6 /// any other camera if not, or null if no camera is available.</returns> 7 private static async Task<DeviceInformation> FindCameraDeviceByPanelAsync(Windows.Devices.Enumeration.Panel desiredPanel) 8 { 9 // Get available devices for capturing pictures. 10 var allVideoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture); 11 12 // Get the desired camera by panel. 13 DeviceInformation desiredDevice = allVideoDevices.FirstOrDefault(x => x.EnclosureLocation != null && x.EnclosureLocation.Panel == desiredPanel); 14 15 // If there is no device mounted on the desired panel, return the first device found. 16 return desiredDevice ?? allVideoDevices.FirstOrDefault(); 17 }
2:初始Camera
首先需要创建一个MediaCapture实例,MediaCapture类位于Windows.Media.Capture命名空间。然后需要允许App访问Camera,需要修改Package.appxmanifest文件,选择Capabilities Tab 勾选中 Microphone & Webcam。 调用MediaCapture.InitializeAsync方法并指定要初始化上一步找到的Camera。
如果MediaCapture使用的过程中抛出了Error,此时就需要释放MediaCapture资源,可以在MediaCapture的Failed事件来处理。
此外还使用了isInitialized 标志位来判断是否已经成功初始化了Camera。
3:预览
需要调用MediaCapture.StartPreviewAsync(),在预览过程中使用了DisplayRequest对象RequestActive方法来防止设备屏幕休眠。
同样使用了isPreviewing来判断Camera是否处于预览状态。
经过三个步骤后,就可以正常预览Camera了。
MediaCapture对象的StopPreviewAsync方法用来停止预览,停止预览后,设备就可以允许睡眠了,需要调用DisplayRequest对象RequestRelease。
4:其他
当App转换为Suspending状态时,先使用SuspendingOperation.GetDeferral()请求延迟挂起操作,释放完MediaCapture资源,最后调用SuspendingDeferral.Complete() 通知操作系统App已经准好被挂起了。当App转换为Resuming状态时,需要再次开启Camera。
可以分别处理Application.Current.Suspending跟Application.Current.Resuming事件。
附上代码:
1 using System; 2 using System.Linq; 3 using System.Threading.Tasks; 4 using Windows.ApplicationModel; 5 using Windows.Devices.Enumeration; 6 using Windows.Media.Capture; 7 using Windows.System.Display; 8 using Windows.UI.Core; 9 using Windows.UI.Popups; 10 using Windows.UI.Xaml; 11 using Windows.UI.Xaml.Controls; 12 using Windows.UI.Xaml.Navigation; 13 14 // The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 15 16 namespace ScanQRCode 17 { 18 /// <summary> 19 /// An empty page that can be used on its own or navigated to within a Frame. 20 /// </summary> 21 public sealed partial class MainPage : Page 22 { 23 const int borderThickness = 5; 24 25 private MediaCapture mediaCapture; 26 private bool isInitialized = false; 27 private bool isPreviewing = false; 28 29 // Prevent the screen from sleeping while the camera is running. 30 private readonly DisplayRequest displayRequest = new DisplayRequest(); 31 32 public MainPage() 33 { 34 this.InitializeComponent(); 35 36 // Useful to know when to initialize/clean up the camera 37 Application.Current.Suspending += Application_Suspending; 38 Application.Current.Resuming += Application_Resuming; 39 } 40 41 /// <summary> 42 /// Occures on app suspending. Stops camera if initialized. 43 /// </summary> 44 /// <param name="sender"></param> 45 /// <param name="e"></param> 46 private async void Application_Suspending(object sender, SuspendingEventArgs e) 47 { 48 // Handle global application events only if this page is active. 49 if (Frame.CurrentSourcePageType == typeof(MainPage)) 50 { 51 var deferral = e.SuspendingOperation.GetDeferral(); 52 53 await CleanupCameraAsync(); 54 55 deferral.Complete(); 56 } 57 } 58 59 /// <summary> 60 /// Occures on app resuming. Initializes camera if available. 61 /// </summary> 62 /// <param name="sender"></param> 63 /// <param name="o"></param> 64 private async void Application_Resuming(object sender, object o) 65 { 66 // Handle global application events only if this page is active 67 if (Frame.CurrentSourcePageType == typeof(MainPage)) 68 { 69 await StartCameraAsync(); 70 } 71 } 72 73 private void InitFocusRec() 74 { 75 leftTopBorder.BorderThickness = new Thickness(borderThickness, borderThickness, 0, 0); 76 rightTopBorder.BorderThickness = new Thickness(0, borderThickness, borderThickness, 0); 77 leftBottomBorder.BorderThickness = new Thickness(borderThickness, 0, 0, borderThickness); 78 rightBottomBorder.BorderThickness = new Thickness(0, 0, borderThickness, borderThickness); 79 80 var borderLength = 20; 81 leftTopBorder.Width = leftTopBorder.Height = borderLength; 82 rightTopBorder.Width = rightTopBorder.Height = borderLength; 83 leftBottomBorder.Width = leftBottomBorder.Height = borderLength; 84 rightBottomBorder.Width = rightBottomBorder.Height = borderLength; 85 86 var focusRecLength = Math.Min(ActualWidth / 2, ActualHeight / 2); 87 scanGrid.Width = scanGrid.Height = focusRecLength; 88 scanCavas.Width = scanCavas.Height = focusRecLength; 89 90 scanStoryboard.Stop(); 91 scanLine.X2 = scanCavas.Width - 20; 92 scanAnimation.To = scanCavas.Height; 93 94 scanStoryboard.Begin(); 95 } 96 97 private void Page_SizeChanged(object sender, SizeChangedEventArgs e) 98 { 99 InitFocusRec(); 100 } 101 102 protected override async void OnNavigatedTo(NavigationEventArgs e) 103 { 104 await StartCameraAsync(); 105 } 106 107 private async Task StartCameraAsync() 108 { 109 if (!isInitialized) 110 { 111 await InitializeCameraAsync(); 112 } 113 114 if (isInitialized) 115 { 116 PreviewControl.Visibility = Visibility.Visible; 117 } 118 } 119 120 /// <summary> 121 /// Queries the available video capture devices to try and find one mounted on the desired panel. 122 /// </summary> 123 /// <param name="desiredPanel">The panel on the device that the desired camera is mounted on.</param> 124 /// <returns>A DeviceInformation instance with a reference to the camera mounted on the desired panel if available, 125 /// any other camera if not, or null if no camera is available.</returns> 126 private static async Task<DeviceInformation> FindCameraDeviceByPanelAsync(Windows.Devices.Enumeration.Panel desiredPanel) 127 { 128 // Get available devices for capturing pictures. 129 var allVideoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture); 130 131 // Get the desired camera by panel. 132 DeviceInformation desiredDevice = allVideoDevices.FirstOrDefault(x => x.EnclosureLocation != null && x.EnclosureLocation.Panel == desiredPanel); 133 134 // If there is no device mounted on the desired panel, return the first device found. 135 return desiredDevice ?? allVideoDevices.FirstOrDefault(); 136 } 137 138 /// Initializes the MediaCapture 139 /// </summary> 140 private async Task InitializeCameraAsync() 141 { 142 if (mediaCapture == null) 143 { 144 // Attempt to get the back camera if one is available, but use any camera device if not. 145 var cameraDevice = await FindCameraDeviceByPanelAsync(Windows.Devices.Enumeration.Panel.Back); 146 147 if (cameraDevice == null) 148 { 149 //No camera device! 150 return; 151 } 152 153 // Create MediaCapture and its settings. 154 mediaCapture = new MediaCapture(); 155 156 // Register for a notification when something goes wrong 157 mediaCapture.Failed += MediaCapture_Failed; 158 159 var settings = new MediaCaptureInitializationSettings { VideoDeviceId = cameraDevice.Id }; 160 161 // Initialize MediaCapture 162 try 163 { 164 await mediaCapture.InitializeAsync(settings); 165 isInitialized = true; 166 } 167 catch (UnauthorizedAccessException) 168 { 169 await ShowMessage("Denied access to the camera."); 170 } 171 catch (Exception ex) 172 { 173 await ShowMessage("Exception when init MediaCapture. " + ex.Message); 174 } 175 176 // If initialization succeeded, start the preview. 177 if (isInitialized) 178 { 179 await StartPreviewAsync(); 180 } 181 } 182 } 183 184 /// <summary> 185 /// Starts the preview after making a request to keep the screen on and unlocks the UI. 186 /// </summary> 187 private async Task StartPreviewAsync() 188 { 189 // Prevent the device from sleeping while the preview is running. 190 displayRequest.RequestActive(); 191 192 // Set the preview source in the UI. 193 PreviewControl.Source = mediaCapture; 194 // Start the preview. 195 try 196 { 197 await mediaCapture.StartPreviewAsync(); 198 isPreviewing = true; 199 } 200 catch (Exception ex) 201 { 202 await ShowMessage("Exception starting preview." + ex.Message); 203 } 204 } 205 206 207 /// <summary> 208 /// Handles MediaCapture failures. Cleans up the camera resources. 209 /// </summary> 210 /// <param name="sender"></param> 211 /// <param name="errorEventArgs"></param> 212 private async void MediaCapture_Failed(MediaCapture sender, MediaCaptureFailedEventArgs errorEventArgs) 213 { 214 await CleanupCameraAsync(); 215 } 216 217 218 /// <summary> 219 /// Cleans up the camera resources (after stopping the preview if necessary) and unregisters from MediaCapture events. 220 /// </summary> 221 private async Task CleanupCameraAsync() 222 { 223 if (isInitialized) 224 { 225 if (isPreviewing) 226 { 227 // The call to stop the preview is included here for completeness, but can be 228 // safely removed if a call to MediaCapture.Dispose() is being made later, 229 // as the preview will be automatically stopped at that point 230 await StopPreviewAsync(); 231 } 232 233 isInitialized = false; 234 } 235 236 if (mediaCapture != null) 237 { 238 mediaCapture.Failed -= MediaCapture_Failed; 239 mediaCapture.Dispose(); 240 mediaCapture = null; 241 } 242 } 243 244 /// <summary> 245 /// Stops the preview and deactivates a display request, to allow the screen to go into power saving modes, and locks the UI 246 /// </summary> 247 /// <returns></returns> 248 private async Task StopPreviewAsync() 249 { 250 try 251 { 252 isPreviewing = false; 253 await mediaCapture.StopPreviewAsync(); 254 } 255 catch (Exception ex) 256 { 257 // Use the dispatcher because this method is sometimes called from non-UI threads. 258 await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => 259 { 260 await ShowMessage("Exception stopping preview. " + ex.Message); 261 }); 262 } 263 264 // Use the dispatcher because this method is sometimes called from non-UI threads. 265 await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => 266 { 267 PreviewControl.Source = null; 268 269 // Allow the device to sleep now that the preview is stopped. 270 displayRequest.RequestRelease(); 271 }); 272 } 273 274 private async Task ShowMessage(string message) 275 { 276 var messageDialog = new MessageDialog(message); 277 await messageDialog.ShowAsync(); 278 } 279 } 280 }