Il presente articolo è stato tradotto automaticamente.

Frontiere dell'interfaccia utente

Feed video in Windows Phone

Charles Petzold

Scaricare il codice di esempio

Charles PetzoldLo smartphone moderno è ricco di organi sensoriali elettronici attraverso il quale si possono ottenere informazioni sul mondo esterno.Questi includono la radio telefono cellulare stesso, Wi-Fi, GPS, un touchscreen, sensori di movimento e di più.

Al programmatore di applicazione, questi organi sensoriali sono solo come utili come le API ad essi associati.Se le API sono carenti, le caratteristiche hardware diventano molto meno preziosi o addirittura inutile.

Dal punto di vista di un programmatore di applicazione, una delle caratteristiche che manca dalla versione iniziale di Windows Phone è stato un buon bulbo oculare.Anche se la fotocamera è sempre stato in Windows Phone, l'unica API disponibile nella versione originale era fotocamera­CaptureTask.Questa classe genera essenzialmente un processo figlio che consente all'utente di scattare una foto e quindi restituisce quell'immagine all'applicazione.Questo è tutto.L'applicazione non può controllare qualsiasi parte di questo processo, né essa può ottenere il feed video dal vivo, venendo attraverso la lente.

Quel deficit ora è stato corretto con due serie di interfacce di programmazione.

Un set di API riguarda le classi fotocamera e fotocamera.Queste classi consentono un'applicazione assemblare un intero foto presa UI, tra cui flash opzioni; video anteprima dal vivo di alimentazione; otturatore dei tasti e metà-presse; e la rilevazione di messa a fuoco.Spero di discutere questa interfaccia in un articolo futuro.

Le API che sarà discusso in questa colonna sono state ereditate dall'interfaccia webcam Silverlight 4.Hanno lasciato un'applicazione ottenere feed audio e video dal vivo da fotocamera e il microfono del telefono.Questi feed possono essere presentati all'utente, salvata in un file o — e qui si fa più interessante — manipolati o interpretato in qualche modo.

Dispositivi e fonti

L'interfaccia di webcam di Silverlight 4 è stato migliorato appena un po' per Windows Phone 7 ed è costituito da circa una dozzina di classi definite nello spazio dei nomi System.Windows.Media.Iniziare sempre con la classe statica CaptureDeviceConfiguration.Se il telefono supporta più telecamere o microfoni, questi sono disponibili da ottenere­AvailableVideoCaptureDevices e GetAvailableAudioCapture­metodi di dispositivi.Si potrebbe voler presentare questi per l'utente in un elenco di selezione.In alternativa, è possibile semplicemente chiamare i metodi GetDefaultVideoCaptureDevice e GetDefaultAudioCaptureDevice.

Le menzioni di documentazione questi metodi potrebbero restituire null, probabilmente che indica il telefono non contiene una telecamera.Questo è improbabile, ma è una buona idea controllare per null comunque.

Questi metodi CaptureDeviceConfiguration restituiscono istanze di VideoCaptureDevice e AudioCaptureDevice o raccolte di istanze di queste due classi.Queste classi forniscono un nome descrittivo per il dispositivo, una collezione di SupportedFormats e una proprietà DesiredFormat.Per il video, i formati di coinvolgono le dimensioni in pixel di ogni frame video, il formato di colore e fotogrammi al secondo.Per l'audio, il formato specifica il numero di canali, i bit per campione e il formato di onda, che è sempre Pulse Code Modulation (PCM).

Un'applicazione Silverlight 4 deve chiamare il CaptureDevice­Configuration.RequestDeviceAccess metodo per ottenere il permesso da parte dell'utente di accedere alla webcam.Questa chiamata deve essere in risposta all'input dell'utente, ad esempio clic di un pulsante.Se il CaptureDevice­sezione­­ation.Proprietà AllowedDeviceAccess è vero, tuttavia, allora l'utente ha già dato il permesso per l'accesso e il programma non deve chiamare RequestDeviceAccess nuovamente.

Ovviamente, il metodo RequestDeviceAccess serve a proteggere la privacy dell'utente.Ma Silverlight basati sul Web e Silverlight per Windows Phone 7 sembra essere un po' diverso al riguardo.L'idea di un sito Web surrettiziamente l'accesso la tua webcam è decisamente inquietante, ma molto meno così per un programma di telefono.È la mia esperienza che per un'applicazione di Windows Phone, AllowedDeviceAccess restituisce sempre true.Tuttavia, in tutti i programmi descritti in questo articolo, ho definito un'interfaccia utente per chiamare RequestDeviceAccess.

L'applicazione inoltre necessario creare un oggetto CaptureSource, che associa un dispositivo video e un dispositivo audio in un unico flusso di audio e video dal vivo.CaptureSource ha due proprietà, denominata VideoCaptureDevice e AudioCaptureDevice, che si imposta su istanze di VideoCaptureDevice e AudioCaptureDevice ottenuto da CaptureDeviceConfiguration.Se siete interessati a solo video o solo audio, è non devono impostare entrambe le proprietà.I programmi di esempio in questa colonna, io ho concentrata interamente sul video.

Dopo aver creato un oggetto CaptureSource, è possibile chiamare l'oggetto avviare e arrestare i metodi.In un programma dedicato a ottenere il feed video o audio, probabilmente vorrete chiamare Start nella OnNavigated­per eseguire l'override e arrestare nell'override OnNavigatedFrom.

Inoltre, è possibile utilizzare il metodo CaptureImageAsync di CaptureSource per ottenere singoli fotogrammi video sotto forma di oggetti WriteableBitmap.Non dimostrando che la funzione.

Una volta che hai un oggetto CaptureSource, si può andare in una delle due direzioni: È possibile creare un oggetto VideoBrush per visualizzare il video live feed, oppure è possibile connettersi a CaptureSource a un oggetto "sink" per ottenere l'accesso a dati grezzi o per salvare in un file nell'archiviazione isolata.

VideoBrush

Sicuramente l'opzione più semplice di CaptureSource è VideoBrush.Silverlight 3 introdotto VideoBrush con una fonte di MediaElement e Silverlight 4 aggiunto l'alternativa di CaptureSource per VideoBrush.Come con qualsiasi pennello, è possibile utilizzare esso per sfondi di elemento di colore o di primo piano.

Nel codice scaricabile per questa colonna è un programma chiamato StraightVideo che utilizza VideoCaptureDevice, CaptureSource e VideoBrush per visualizzare il feed video dal vivo che arrivano attraverso l'obiettivo della fotocamera predefinito.Figura 1 mostra un buon pezzo di file MainPage.Si noti l'uso di modalità orizzontale (che vorrete per feed video), la definizione di VideoBrush sulla proprietà Background del contenuto griglia e il pulsante per ottenere l'autorizzazione utente per accedere la fotocamera.

Figura 1 il File MainPage da StraightVideo

<phone:PhoneApplicationPage
  x:Class="StraightVideo.MainPage"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
  xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
  ...
  SupportedOrientations="Landscape" Orientation="LandscapeLeft"
  shell:SystemTray.IsVisible="True">
  <Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
      <TextBlock x:Name="ApplicationTitle" Text="STRAIGHT VIDEO"
        Style="{StaticResource PhoneTextNormalStyle}"/>
    </StackPanel>
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
      <Grid.Background>
        <VideoBrush x:Name="videoBrush" />
      </Grid.Background>
      <Button Name="startButton"
        Content="start"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Click="OnStartButtonClick" />
    </Grid>
  </Grid>
</phone:PhoneApplicationPage>

Figura 2 mostra gran parte del file code-behind. L'oggetto CaptureSource viene creato nel costruttore della pagina, ma ha iniziato e si fermò nelle sostituzioni di navigazione. Ho anche trovato necessario chiamare SetSource su VideoBrush in OnNavigatedTo; in caso contrario l'immagine è stato perso dopo una precedente chiamata di Stop.

Nella figura 2 il File MainPage.xaml.cs dal StraightVideo

public partial class MainPage : PhoneApplicationPage
{
  CaptureSource captureSource;
  public MainPage()
  {
    InitializeComponent();
    captureSource = new CaptureSource
    {
      VideoCaptureDevice =
        CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice()
    };
  }
  protected override void OnNavigatedTo(NavigationEventArgs args)
  {
    if (captureSource !=
      null && CaptureDeviceConfiguration.AllowedDeviceAccess)
    {
      videoBrush.SetSource(captureSource);
      captureSource.Start();
      startButton.Visibility = Visibility.Collapsed;
    }
    base.OnNavigatedTo(args);
  }
  protected override void OnNavigatedFrom(NavigationEventArgs args)
  {
    if (captureSource != null && captureSource.State == CaptureState.Started)
    {
      captureSource.Stop();
      startButton.Visibility = Visibility.Visible;
    }
    base.OnNavigatedFrom(args);
  }
  void OnStartButtonClick(object sender, RoutedEventArgs args)
  {
    if (captureSource != null &&
        (CaptureDeviceConfiguration.AllowedDeviceAccess ||
        CaptureDeviceConfiguration.RequestDeviceAccess())
    {
      videoBrush.SetSource(captureSource);
      captureSource.Start();
      startButton.Visibility = Visibility.Collapsed;
    }
  }
}

È possibile eseguire questo programma sull'emulatore Windows Phone, ma è molto più interessante su un dispositivo reale. Si noterà che il rendering del video feed è molto reattivo. Evidentemente il feed video sta andando direttamente all'hardware video. (Ulteriori elementi di prova per questa supposizione è che il mio consueto metodo di ottenimento screenshot dal telefono eseguendo il rendering dell'oggetto PhoneApplicationFrame per un WriteableBitmap non ha funzionato con questo programma). Avrete anche notato che, perché il video viene eseguito il rendering tramite un pennello, il pennello è allungato per le dimensioni della griglia contenuta e l'immagine è distorta.

Una delle caratteristiche di pennelli belle è che possono essere condivise tra più elementi. Questa è l'idea dietro FlipXYVideo. Questo programma crea dinamicamente un mucchio di oggetti Rectangle piastrellati in una griglia. VideoBrush stesso viene utilizzato per ciascuno, salvo ogni altro rettangolo è capovolto verticalmente o orizzontalmente o entrambi, come mostrato nella Figura 3. Si possono aumentare o diminuire il numero di righe e colonne da ApplicationBar pulsanti.

Figura 3 condivisione oggetti VideoBrush in FlipXYVideo

void CreateRowsAndColumns()
{
  videoPanel.Children.Clear();
  videoPanel.RowDefinitions.Clear();
  videoPanel.ColumnDefinitions.Clear();
  for (int row = 0; row < numRowsCols; row++)
    videoPanel.RowDefinitions.Add(new RowDefinition
    {
      Height = new GridLength(1, GridUnitType.Star)
    });
  for (int col = 0; col < numRowsCols; col++)
    videoPanel.ColumnDefinitions.Add(new ColumnDefinition
  {
    Width = new GridLength(1, GridUnitType.Star)
  });
  for (int row = 0; row < numRowsCols; row++)
    for (int col = 0; col < numRowsCols; col++)
    {
      Rectangle rect = new Rectangle
      {
        Fill = videoBrush,
        RenderTransformOrigin = new Point(0.5, 0.5),
        RenderTransform = new ScaleTransform
        {
          ScaleX = 1 - 2 * (col % 2),
          ScaleY = 1 - 2 * (row % 2),
        },
      };
      Grid.SetRow(rect, row);
      Grid.SetColumn(rect, col);
      videoPanel.Children.Add(rect);
    }
  fewerButton.IsEnabled = numRowsCols > 1;
}

Questo programma è divertente da giocare con, ma non così divertente come il programma CALEIDOSCOPIO, che tratterò poco.

Fonte e lavandino

L'alternativa all'utilizzo di un oggetto VideoBrush si connette un oggetto CaptureSource a un oggetto AudioSink, VideoSink o FileSink. L'uso della parola "sink" in questi nomi di classe è nel senso di "contenitore" ed è simile all'uso della parola nella teoria elettronica o rete. (O pensare a una "fonte di calore" e "un dissipatore di calore.")

La classe FileSink è il metodo preferito per il salvataggio dei flussi video o audio all'archiviazione isolata dell'applicazione senza alcun intervento da parte vostra. Se è necessario accedere ai bit effettivi di video o audio in tempo reale, utilizzerai VideoSink e AudioSink. Queste due classi sono astratte. Si derivare una classe da una o entrambe queste classi astratte ed eseguire l'override dei metodi OnCaptureStarted, OnCaptureStopped, OnFormatChange e OnSample.

La classe che si deriva da VideoSink o AudioSink otterrà sempre una chiamata a OnFormatChange prima la prima chiamata a OnSample. Le informazioni fornite con OnFormatChange indicano come i dati di esempio sono da interpretare. Per VideoSink e AudioSink, la chiamata di OnSample fornisce informazioni di temporizzazione e una matrice di byte. Per AudioSink, questi byte rappresentano dati PCM. Per VideoSink, questi byte sono righe e colonne di pixel per ogni fotogramma del video. Questi dati sono sempre crudo e non compresso.

Sia OnFormatChange e OnSample sono chiamati in secondaria thread di esecuzione, quindi avrete bisogno di utilizzare un oggetto Dispatcher per attività all'interno di questi metodi che devono essere eseguiti nel thread dell'interfaccia utente.

Il programma StraightVideoSink è simile a StraightVideo, salvo i dati video passa attraverso una classe derivata da VideoSink. Questa classe derivata (mostrato in Figura 4) è chiamato SimpleVideoSink, perché semplicemente prende la matrice di byte OnSample e si trasferisce a un WriteableBitmap.

Figura 4 la classe SimpleVideoSink utilizzata in StraightVideoSink

public class SimpleVideoSink : VideoSink
{
  VideoFormat videoFormat;
  WriteableBitmap writeableBitmap;
  Action<WriteableBitmap> action;
  public SimpleVideoSink(Action<WriteableBitmap> action)
  {
    this.action = action;
  }
  protected override void OnCaptureStarted() { }
  protected override void OnCaptureStopped() { }
  protected override void OnFormatChange(VideoFormat videoFormat)
  {
    this.videoFormat = videoFormat;
    System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
      writeableBitmap = new WriteableBitmap(videoFormat.PixelWidth,
        videoFormat.PixelHeight);
      action(writeableBitmap);
    });
  }
  protected override void OnSample(long sampleTimeInHundredNanoseconds,
    long frameDurationInHundredNanoseconds, byte[] sampleData)
  {
    if (writeableBitmap == null)
      return;
    int baseIndex = 0;
    for (int row = 0; row < writeableBitmap.PixelHeight; row++)
    {
      for (int col = 0; col < writeableBitmap.PixelWidth; col++)
      {
        int pixel = 0;
        if (videoFormat.PixelFormat == PixelFormatType.Format8bppGrayscale)
        {
          byte grayShade = sampleData[baseIndex + col];
          pixel = (int)grayShade | (grayShade << 8) |
            (grayShade << 16) | (0xFF << 24);
        }
        else
        {
          int index = baseIndex + 4 * col;
          pixel = (int)sampleData[index + 0] | (sampleData[index + 1] << 8) |
            (sampleData[index + 2] << 16) | (sampleData[index + 3] << 24);
        }
        writeableBitmap.Pixels[row * writeableBitmap.PixelWidth + col] = pixel;
      }
      baseIndex += videoFormat.Stride;
    }
    writeableBitmap.Dispatcher.BeginInvoke(() =>
      {
        writeableBitmap.Invalidate();
      });
  }
}

MainPage utilizza tale WriteableBitmap con un elemento di immagine per visualizzare il feed video risulta. (In alternativa, potrebbe creare ImageBrush e che impostare lo sfondo o il primo piano di un elemento.)

Ecco la cattura: Quel WriteableBitmap non è possibile creare fino a quando non viene chiamato il metodo di OnFormatChange nella derivata di VideoSink, perché quella chiamata indica le dimensioni del fotogramma video. (Di solito è 640 x 480 pixel sul mio telefono ma plausibilmente potrebbe essere qualcos'altro). Anche se la derivata di VideoSink crea WriteableBitmap, MainPage deve accedervi. Ecco perché ho definito un costruttore per SimpleVideoSink che contiene un argomento di azione da chiamare quando viene creato l'oggetto WriteableBitmap.

Si noti che WriteableBitmap deve essere creato nel thread dell'interfaccia utente del programma, così SimpleVideoSink utilizza un oggetto Dispatcher per la creazione di thread dell'interfaccia utente in fila. Questo significa che WriteableBitmap non potrebbe essere creato prima di chiamare il primo OnSample. Guardare fuori per questo! Anche se il metodo OnSample possibile accedere alla matrice di pixel di WriteableBitmap in un thread secondario, la chiamata a Invalidate bitmap deve verificarsi nel thread dell'interfaccia utente perché quella chiamata colpisce in definitiva la visualizzazione dell'immagine bitmap dall'elemento Image.

La classe MainPage di StraightVideoSink include un'applicazione­Bar pulsante per passare tra colore e feed video grigio sfumato. Questi sono solo due opzioni, e si può passare a uno o l'altro impostando la proprietà DesiredFormat dell'oggetto VideoCaptureDevice. Un feed di colore ha 4 byte per pixel nell'ordine verde, blu, rosso e alfa (che sarà sempre 255). Un feed di tonalità di grigio ha solo 1 byte per pixel. In entrambi i casi un WriteableBitmap ha sempre 4 byte per pixel dove ogni pixel è rappresentato da un valore integer a 32-bit con il più alto 8 bit per il canale alfa, seguita da rosso, verde e blu. L'oggetto CaptureSource deve essere arrestato e riavviato quando si passa formati.

Anche se StraightVideo e StraightVideoSink visualizzare il video live feed, probabilmente noterete che StraightVideoSink è notevolmente più lenta come risultato di lavoro che il programma sta facendo per trasferire il fotogramma video a WriteableBitmap.

Facendo un caleidoscopio

Se hai solo bisogno di un feed video in tempo reale con telaio occasionali cattura, è possibile utilizzare il metodo CaptureImageAsync di cattura­Source. Causa dell'overhead delle prestazioni, avrete probabilmente limitare l'uso di VideoSink alle applicazioni più specializzate che coinvolgono la manipolazione dei bit di pixel.

Scriviamo un programma "specializzato" che organizza il video feed in un caleidoscopico pattern. Concettualmente, questo è abbastanza semplice: La derivata di VideoSink ottiene un video feed dove ogni frame è probabilmente il 640 x 480 pixel nella dimensione o forse qualcos'altro. Si desidera fare riferimento un triangolo equilatero di dati immagine da quella cornice, come mostrato nella Figura 5.

Ho deciso su un triangolo con la sua base sulla parte superiore e il suo apice nella parte inferiore di meglio catturare facce.

The Source Triangle for a Kaleidoscope
Nella figura 5 il triangolo di origine per un caleidoscopio

L'immagine in quel triangolo è poi duplicato su un WriteableBitmap più volte con qualche rotazione e lanciando così le immagini sono piastrellate e raggruppate in esagoni senza alcuna discontinuità, come mostrato nella Figura 6. So che gli esagoni assomigliare abbastanza fiori, ma sono davvero solo molte immagini del mio viso (forse troppe immagini del mio viso).

The Destination Bitmap for a Kaleidoscope
Nella figura 6 la Bitmap di destinazione per un caleidoscopio

Il modello di ripetizione diventa più evidente quando i singoli triangoli sono delimitati, come mostrato nella Figura 7. Quando il rendering sul telefono cellulare, l'altezza del target WriteableBitmap sarà lo stesso, come dimensione più piccola del telefono cellulare, o 480 pixel. Ogni triangolo equilatero, quindi, ha un lato di 120 pixel. Questo significa che l'altezza del triangolo è 120 volte la radice quadrata di 0,75, o circa 104 pixel. Nel programma, io uso 104 per la matematica, ma 105 per il dimensionamento della bitmap per rendere più semplice il loop. L'intera immagine risulta è 630 pixel di larghezza.

The Destination Bitmap Showing the Source Triangles
Figura 7 la Bitmap di destinazione che mostra i triangoli di origine

Ho trovato più conveniente per trattare l'immagine totale come tre pixel identiche bande verticali 210. Ognuno di tali bande verticali ha simmetria di riflessione intorno il punto medio verticale, così ho ridotto l'immagine di una bitmap singolo pixel di 105 x 480 ripetuto sei volte, metà di quelli con la riflessione. Quella banda è costituito da soli sette triangoli pieni e due triangoli parziale.

Anche così, ero molto nervoso per i calcoli necessari per assemblare questa immagine. Poi ho capito che questi calcoli avrebbe dovuto essere eseguite alla frequenza di refresh video di 30 volte al secondo. Hanno bisogno di essere eseguite solo una volta, quando le dimensioni delle immagini video diventano disponibile nell'override di OnFormatChange.

Il programma risulta viene chiamato Kaleidovideo. (Quel nome sarebbe considerato un abominio etimologico dai tradizionalisti perché "kaleido" deriva da una radice greca che significa "bella forma," ma "video" ha una radice latina, e quando coniando nuove parole non siete supposto per mescolare i due.)

La classe KaleidoscopeVideoSink esegue l'override di VideoSink. Il metodo OnFormatChange è responsabile per il computing i membri di una matrice chiamato indexMap. Tale matrice ha come molti membri come il numero di pixel in WriteableBitmap (105 moltiplicato per 480, o 50.400) e memorizza un indice in area rettangolare dell'immagine video. Utilizzando questa matrice di indexMap, il trasferimento di pixel dall'immagine video a WriteableBitmap nel metodo OnSample è molto veloce come plausibilmente possibile.

Questo programma è un sacco di divertimento. Tutto nel mondo sembra piuttosto più bello Kaleidovideo. Figura 8 mostra uno dei miei scaffali, per esempio. Potete anche guardare la TV attraverso di essa.

A Typical Kaleidovideo Screen
Figura 8 tipico Kaleidovideo schermo

Nel caso in cui si vede qualcosa sullo schermo che si desidera salvare, ho aggiunto un pulsante "cattura" alla ApplicationBar. Troverete le immagini nella cartella immagini salvate della Fototeca del telefono.

Charles Petzold è un redattore lunga data di MSDN Magazine. Il suo libro recente, programmazione Windows Phone 7 (Microsoft Press, 2010), è disponibile come download gratuito presso bit.ly/cpebookpdf.

Grazie ai seguenti esperti tecnici per la revisione di questo articolo: Mark Hopkins e Matt Stroshane