Фонарик камеры в моем универсальном приложении для Windows работает только один раз

Я работаю над универсальным приложением Windows на платформе Windows 10. Приложение должно использовать камеру для захвата штрих-кода и сделать что-то полезное со штрих-кодом. Пока что он отлично работает с захватом и переводом штрих-кода (с использованием библиотеки ZXing). Я могу использовать его для захвата штрих-кода один за другим, нажав кнопку один раз для каждого штрих-кода.

Но мне это нужно для работы в условиях низкой освещенности. Я хочу попросить камеру автоматически включить фонарик (или фонарик) в условиях низкой освещенности. Я обнаружил, что камера может автоматически включить фонарик (или фонарик) в условиях низкой освещенности, прежде чем я сделаю первый снимок. Каким-то образом фонарик выключается после первой фотографии. Я хочу, чтобы он оставался включенным до тех пор, пока пользователь остается на той же странице моего приложения (и до тех пор, пока среда тусклая). Пожалуйста, помогите мне решить эту проблему.

На данный момент я могу определить, что MediaCapture.CapturePhotoToStorageFileAsync() - это команда, которая выключает фонарик.

Ниже приведена рабочая тестовая программа, которая устраняет эту проблему.

Это файл программы MainPage.xaml в тестовом приложении:

<Page
    x:Class="TestApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:TestApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
      <CaptureElement Name="captureElement"
                      Stretch="UniformToFill"
                      Margin="32,-93,34.5,181.5"
                      d:LayoutOverrides="LeftPosition, RightPosition, TopPosition, BottomPosition" RenderTransformOrigin="0.5,0.5" UseLayoutRounding="False" d:LayoutRounding="Auto" >
         <CaptureElement.RenderTransform>
            <CompositeTransform Rotation="90"/>
         </CaptureElement.RenderTransform>
      </CaptureElement>
      <Button x:Name="btnCapture" Content="Capture Barcode" HorizontalAlignment="Left" Margin="10,0,0,203" VerticalAlignment="Bottom" Height="64" BorderThickness="2,2,4,4" Background="#33FFFFFF" BorderBrush="Black" FontSize="20" FontWeight="Bold" Click="btnCapture_OnClick" Width="340"/>
      <Button x:Name="btnTerminateApp" Content="Terminate This App" HorizontalAlignment="Stretch" Height="66" Margin="10,0,10,42" VerticalAlignment="Bottom" Background="#33FFFFFF" BorderBrush="Black" BorderThickness="2,2,4,4" FontWeight="Bold" d:LayoutOverrides="LeftPosition, RightPosition" Click="btnTerminateApp_OnClick" FontSize="20"/>

    </Grid>
</Page>

Это файл программы MainPage.xaml.cs в тестовом приложении:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Graphics.Imaging;        // For BitmapDecoder.
using Windows.Media.Capture;           // For MediaCapture.
using Windows.Media.Devices;           // For FocusSettings, FocusMode, AutoFocusRange.
using Windows.Media.MediaProperties;   // For ImageEncodingProperties.
using Windows.Media.Playback;          // For MediaPlayer.Volume.
using Windows.Storage;                 // For StorageFile.
using Windows.Storage.Streams;         // For IRandomAccessStream.
using Windows.UI.Popups;               // For MessageDialog().
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;   // For WriteableBitmap.
using Windows.UI.Xaml.Navigation;
//using ZXing;                           // For BarcodeFormat.

// The Blank Page item template is documented
// at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace TestApp
   {
   /// <summary>
   /// An empty page that can be used on its own or navigated to within a Frame.
   /// </summary>
   public sealed partial class MainPage : Page
      {
      private MediaCapture captureMgr { get; set; }

      public MainPage()
         {
         this.InitializeComponent();
         this.InitCapture();
         }

      private void btnTerminateApp_OnClick(object sender, RoutedEventArgs e)
         {
         this.ReleaseCapture();
         Application.Current.Exit();
         }

      private async void btnCapture_OnClick( object sender, RoutedEventArgs e )
         // Capture the barcode photo and translate it into a barcode number. And then
         // use the barcode number to mark the piece as checked out. 
         {
         // Capture the barcode and translate it into a barcode number.

         //....Capture the barcode photo from the camera to a storage-file.
         ImageEncodingProperties fmtImage = ImageEncodingProperties.CreateJpeg();
         StorageFile storefile = await ApplicationData.Current.LocalFolder.CreateFileAsync
            (
            "BarcodePhoto.jpg",
            CreationCollisionOption.GenerateUniqueName
            );
         await this.captureMgr.CapturePhotoToStorageFileAsync( fmtImage, storefile );

         //....Convert the barcode photo in the storage file into a writeable-bitmap.
         IRandomAccessStream stream = await storefile.OpenAsync( FileAccessMode.Read );
         BitmapDecoder decoderBmp = await BitmapDecoder.CreateAsync( stream );
         WriteableBitmap bmp = new WriteableBitmap( (int)decoderBmp.PixelWidth,
                                                    (int)decoderBmp.PixelHeight );
         bmp.SetSource(stream);

         //....We are done with the temporary barcode image file. Delete it.
         await storefile.DeleteAsync();

         ////....Translate the barcode photo from the writeable-bitmap into a barcode number.
         //
         //ZXing.BarcodeReader bcodeReader = new ZXing.BarcodeReader();
         //
         //BarcodeFormat[] aAllowedFormat = new BarcodeFormat[] { BarcodeFormat.CODE_39 };
         //bcodeReader.Options.PossibleFormats = aAllowedFormat;
         //   // We only want it to deal with one barcode format. Hopefully this will reduce the
         //   // chance of reading the barcode number wrong, or speed up the decoding process.
         //   // Note that this option only works if we includes "Microphone" as a required
         //   // DeviceCapability of this app in Package.appmanifest. If we don't include
         //   // "Microphone", we will get an unhandled exception here.
         //
         //bcodeReader.Options.TryHarder = true;  // Try this option to see if we can reduce the
         //                                       //    chance of failing to translate the
         //                                       //    barcode into a number. So far no problem
         //                                       //    as of 11/21/2016.
         //
         //var result = bcodeReader.Decode( bmp );
         //if ( result == null )
         //   return;
         }

      private async void InitCapture()
         // Initialize everything about MediaCapture.
         {
         this.captureMgr = new MediaCapture();
         await this.captureMgr.InitializeAsync();

         // Skip the steps to set the photo resolution to the second lowest in order
         // not to make this test program too big.

         // Start the camera preview.
         captureElement.Source = this.captureMgr;
         await this.captureMgr.StartPreviewAsync();

         // Set the camera to auto-focus.
         var settings = new FocusSettings { Mode           = FocusMode.Continuous,
                                            AutoFocusRange = AutoFocusRange.FullRange };
         await this.captureMgr.VideoDeviceController.FocusControl.UnlockAsync();
         this.captureMgr.VideoDeviceController.FocusControl.Configure( settings );
         await this.captureMgr.VideoDeviceController.FocusControl.FocusAsync();

         // Turn on the flashlight in case the lighting is dim. Without enough
         // lighting, the auto-focus feature of the camera cannot work.
         var cameraFlashLight = this.captureMgr.VideoDeviceController.FlashControl;
         if ( cameraFlashLight.Supported )
            {
            if (cameraFlashLight.PowerSupported)
               cameraFlashLight.PowerPercent = 100;
            cameraFlashLight.Enabled = true;
            }
         // //////////////////////////
         // Tried replacing flashlight with torch. But get the same problem.
         // //////////////////////////
         //var cameraTorch = this.captureMgr.VideoDeviceController.TorchControl;
         //if ( cameraTorch.Supported )
         //   {
         //   if ( cameraTorch.PowerSupported )
         //      cameraTorch.PowerPercent = 100;
         //   cameraTorch.Enabled = true;
         //   }
         // //////////////////////////
         }

      private async void ReleaseCapture()
         {
         captureElement.Source = null;
         await this.captureMgr.StopPreviewAsync();
         this.captureMgr.Dispose();
         }
      }
   }

Чтобы не заставлять людей устанавливать ZXing Library только для того, чтобы попробовать тестовое приложение выше, я прокомментировал все, что связано с ZXing Library в тестовом приложении.

Пользователь может обойти проблему, покинув эту страницу (и вернувшись в главное меню), а затем вернувшись обратно на ту же страницу. Это сбрасывает что-то в программе и заставляет функцию авто-фонарика работать снова. Очевидно, что это не хороший обходной путь, потому что пользователь должен делать это при каждом получении штрих-кода. Обратите внимание, что показанное выше тестовое приложение имеет только одну страницу и не имеет главного меню. Поэтому вы не сможете увидеть этот обходной путь с помощью тестового приложения выше.

Я попытался обойти это путем сброса MediaCapture после съемки каждой фотографии. Это делается путем вызова ReleaseCapture() и InitCapture() в моем тестовом приложении, показанном выше. К сожалению, это не только замедляет каждый захват штрих-кода, но также вызывает System.ObjectDisposedException об объекте не инициализируется или что-то в этом роде. В любом случае, я предпочитаю исправить исходную проблему, а не использовать обходной путь.

Кстати, на моем компьютере для разработки есть Windows 10 Professional и Visual Studio 2015 Professional. Windows Phone, который я использую, - это Microsoft Lumia 640 LTE с Windows 10 Moblile версии 1511, встроенная в ОС 10.0.10586.107.

Пожалуйста, помогите мне решить эту проблему. Также приветствуются любые предложения.

Заранее спасибо.

Джей Чан

1 ответ

Оказывается, решение довольно простое. Все, что мне нужно сделать, это сбросить CaptureElement, сначала освободив его, а затем повторно инициализировав, например так:

this.ReleaseCapture();
this.InitCapture();

Сложная часть ReleaseCapture() заключается в том, что я не могу избавиться от MediaCapture. В противном случае произойдет сбой программы, когда я попытаюсь повторно инициализировать CaptureElement. Следовательно, ReleaseCapture() теперь выглядит следующим образом:

private async void ReleaseCapture()
   // Release the resources used for capturing photo.
   {
   try
      {
      captureElement.Source = null;
      await this.captureMgr.StopPreviewAsync();

      //////////////
      // Don't dispose it. Otherwise, when we re-initialize it right after we have released it, the program will
      // crash. We are better off don't do this here. When we are leaving the page, the page will release it
      // anyway.
      //////////////
      //this.captureMgr.Dispose();
      /////////////////////////////
      }
   catch( Exception ex )
      {
      String sErrMsg = String.Concat( "Fail to release resources related to the ",
                                      "use of the camera. The error message is: ",
                                      ex.Message );
      await new MessageDialog( sErrMsg, "Error" ).ShowAsync();
      }
   }

Другое дело, что мне нужно заменить использование вспышки с факелом, когда я инициализирую CaptureElement. Причина в том, что я предпочитаю использовать факел, чтобы иметь непрерывное освещение. По общему признанию это не имеет никакого отношения к этой проблеме. Но я упоминаю об этом только для того, чтобы объяснить, почему эта версия InitCapture() отличается от моей оригинальной версии:

private async void InitCapture()
   // Initialize everything about MediaCapture.
   {
   this.captureMgr = new MediaCapture();
   await this.captureMgr.InitializeAsync();

   // Skip the steps to set the photo resolution to simplify
   // the sample program.

   // Start the camera preview.
   captureElement.Source = this.captureMgr;
   await this.captureMgr.StartPreviewAsync();

   // Ask the camera to auto-focus now.
   var focusControl = this.captureMgr.VideoDeviceController.FocusControl;
   var settings = new FocusSettings { Mode           = FocusMode.Continuous,
                                      AutoFocusRange = AutoFocusRange.FullRange };
   focusControl.Configure( settings );
   await focusControl.FocusAsync();       // Wait for the camera to focus

   // Turn on the torch in case the lighting is dim. Without enough
   // lighting, the auto-focus feature of the camera cannot work.
   var cameraTorch = this.captureMgr.VideoDeviceController.TorchControl;
   if ( cameraTorch.Supported )
      {
      if ( cameraTorch.PowerSupported )
         cameraTorch.PowerPercent = 100;
      cameraTorch.Enabled = true;
      }

   #region Error handling
   MediaCaptureFailedEventHandler handler = (sender, e) =>
      {
       System.Threading.Tasks.Task task = System.Threading.Tasks.Task.Run(async () =>
         {
         await new MessageDialog( "There was an error capturing the video from camera.", "Error" ).ShowAsync();
         });
      };

   this.captureMgr.Failed += handler;
   #endregion
   }

Я пробовал это в трех разных телефонах Windows Mobile 10. Они все работают.

Надеюсь, это кому-нибудь поможет.

Джей Чан

Другие вопросы по тегам