Neue Benutzeroberflächentechnologien

Videofeeds auf Windows Phone 7

Charles Petzold

Charles PetzoldDas moderne Smartphone ist voll gepackt mit elektronischen Sensoren, mit denen es Informationen von der Welt draußen empfangen kann. Dazu gehören der Mobiltelefonsender selbst, Wi-Fi, GPS, ein Touchscreen, Bewegungssensoren und mehr.

Für den Anwendungsprogrammierer sind diese Sensoren nur so nützlich wie die mit diesen verknüpften APIs. Wenn die APIs fehlerhaft sind, sind die Hardwarefunktionen weniger nützlich oder sogar völlig nutzlos.

Aus der Sicht des Anwendungsprogrammierers war eine gute Kamerafunktion eine der wichtigsten Funktionen, die in der ursprünglichen Version von Windows Phone fehlten. Die Kamerafunktion war zwar stets in Windows Phone vorhanden. Camera­CaptureTask war jedoch die einzige API, die in der ursprünglichen Version verfügbar war. Diese Klasse generiert im Wesentlichen einen untergeordneten Prozess, mit dem der Benutzer ein Bild aufnehmen kann. Anschließend wird dieses Bild an die Anwendung zurückgegeben. Das war’s. Die Anwendung kann keinen Teil dieses Prozesses steuern. Sie kann auch den Livevideofeed nicht empfangen, der von der Linse kommt.

Dieser Mangel wurde nun mithilfe von zwei Sätzen von Programmierschnittstellen behoben.

Ein API-Satz behandelt die Klassen Camera und PhotoCamera. Diese Klassen ermöglichen der Anwendung die Assemblierung einer vollständigen Benutzeroberfläche für die Aufnahme von Bildern, einschließlich Blitzlichtoptionen, einer Livevorschau des Videofeeds, Blendentasten sowie Halbblendentasten und Fokussierung. Ich hoffe, diese Schnittstelle in einem zukünftigen Beitrag behandeln zu können.

Die APIs, die ich in diesem Beitrag besprechen werde, werden von der Silverlight 4-Webcamschnittstelle ererbt. Sie ermöglichen der Anwendung den Empfang von Livevideo- und -audiofeeds von der Kamera und vom Mikrofon des Telefons. Diese Feeds können dem Benutzer angezeigt, in eine Datei gespeichert oder – und hier wird es interessanter – bearbeitet oder interpretiert werden.

Geräte und Quellen

Die Webcamschnittstelle von Silverlight 4 wurde für Windows Phone 7 nur ein wenig optimiert und besteht aus ungefähr einem Dutzend Klassen, die im Namespace System.Windows.Media definiert sind. Sie beginnen stets mit der statischen Klasse CaptureDeviceConfiguration. Wenn das Telefon mehrere Kameras oder Mikrofone unterstützt, stehen Ihnen diese mit den Methoden Get­AvailableVideoCaptureDevices und GetAvailableAudioCapture­Devices zur Verfügung. Sie sollten diese dem Benutzer in einer Auswahlliste anzeigen. Alternativ können Sie einfach die Methoden GetDefaultVideoCaptureDevice und GetDefaultAudioCaptureDevice aufrufen.

Die Dokumentation sagt, dass diese Methoden möglicherweise Null zurückgeben. Dies zeigt wahrscheinlich an, dass das Telefon keine Kamera enthält. Dies ist zwar unwahrscheinlich. Es ist jedoch eine gute Idee, auf jeden Fall auf Null zu prüfen.

Diese CaptureDeviceConfiguration-Methoden geben Instanzen von VideoCaptureDevice und AudioCaptureDevice oder Auflistungen von Instanzen dieser beiden Klassen zurück. Diese Klassen stellen einen Anzeigenamen für das Gerät, die Auflistung SupportedFormats und die Eigenschaft DesiredFormat bereit. Für Videoaufnahmen enthalten die Formate die Pixelabmessungen der einzelnen Videoframes, das Farbformat und die Framerate pro Sekunde. Für Audioaufnahmen gibt das Format die Anzahl der Kanäle, die Bits pro Sample und das Waveformat an. Dieses ist stets Pulse Code Modulation (PCM).

Eine Silverlight 4-Anwendung muss die Methode CaptureDevice­Configuration.RequestDeviceAccess aufrufen, um die Genehmigung des Benutzers für den Zugriff auf die Webcam zu erhalten. Dieser Aufruf muss auf eine Benutzereingabe reagieren, wie z. B. einen Tastendruck. Wenn die Eigenschaft CaptureDevice­Configur­­ation.AllowedDeviceAccess "true" ist, dann hat der Benutzer bereits die Genehmigung für den Zugriff erteilt. Das Programm muss in diesem Fall RequestDeviceAccess nicht erneut aufrufen.

Offensichtlich dient die Methode RequestDeviceAccess dem Schutz der Privatsphäre des Benutzers. Das webbasierte Silverlight und Silverlight for Windows Phone 7 scheinen sich in dieser Hinsicht jedoch etwas zu unterscheiden. Die Vorstellung, dass eine Website heimlich auf Ihre Webcam zugreift, ist mit Sicherheit unangenehm. Dies gilt für ein Telefonprogramm jedoch nicht in gleichem Maß. Nach meiner Erfahrung gibt AllowedDeviceAccess für eine Windows Phone-Anwendung stets "true" zurück. Ich habe dennoch in allen in diesem Beitrag beschriebenen Programmen die Benutzeroberfläche so definiert, dass RequestDeviceAccess aufgerufen wird.

Die Anwendung muss außerdem das Objekt CaptureSource erstellen, das ein Video- und ein Audiogerät zu einem einzelnen Livestream für Video und Audio kombiniert. CaptureSource hat zwei Eigenschaften namens VideoCaptureDevice und AudioCaptureDevice. Diese legen Sie als Instanzen von VideoCaptureDevice und AudioCaptureDevice fest, die von CaptureDeviceConfiguration empfangen werden. Sie müssen nicht beide Eigenschaften festlegen, wenn Sie nur an Video oder nur an Audio interessiert sind. In den Beispielprogrammen für diesen Beitrag konzentriere ich mich ausschließlich auf Video.

Nach der Erstellung des Objekts CaptureSource können Sie die Methoden Start und Stop des Objekts aufrufen. In einem Programm, das für den Empfang von Video- oder Audiofeeds vorgesehen ist, sollten Sie wahrscheinlich Start im Override OnNavigated­To und Stop im Override OnNavigatedFrom aufrufen.

Zusätzlich können Sie die CaptureImageAsync-Methode von CaptureSource aufrufen, um einzelne Videoframes als WriteableBitmap-Objekte zu empfangen. Ich werde diese Funktion hier nicht zeigen.

Nach der Erstellung des Objekts CaptureSource können Sie eine von zwei Möglichkeiten wählen: Sie können einen VideoBrush erstellen, um den Livevideofeed anzuzeigen, oder Sie können CaptureSource mit einem "Sink"-Objekt verbinden, um Zugriff auf die Rohdaten zu erhalten oder um die Inhalte in eine Datei an einem isolierten Speicherort zu speichern.

VideoBrush

Die mit Sicherheit einfachste CaptureSource-Option ist Videobrush. Silverlight 3 hat VideoBrush mit einer MediaElement-Quelle eingeführt. Silverlight 4 hat die CaptureSource-Alternative für VideoBrush hinzugefügt. Wie bei jedem Brush, können Sie dies für die Färbung von Elementhintergründen oder -vordergründen verwenden.

Im herunterladbaren Code für diesen Beitrag finden Sie ein Programm namens StraightVideo, das VideoCaptureDevice, CaptureSource und VideoBrush verwendet, um den Livevideofeed anzuzeigen, der von der voreingestellten Kameralinse empfangen wird. Abbildung 1 zeigt einen umfangreichen Abschnitt der Datei MainPage.xaml. Beachten Sie die Verwendung des Querformatmodus (den Sie für Videofeeds verwenden sollten), die Definition von VideoBrush auf der Eigenschaft Background des Inhaltsrasters und die Schaltfläche für die Abfrage der Genehmigung des Benutzers für den Zugriff auf die Kamera.

Abbildung 1 Die Datei MainPage.xaml von 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>

Abbildung 2 zeigt einen großen Teil der Codebehind-Datei. Das Objekt CaptureSource wird im Konstruktor der Seite erstellt, wird jedoch in den Navigationsoverrides gestartet und angehalten. Ich hielt es auch für notwendig, SetSource auf VideoBrush in OnNavigatedTo aufzurufen. Andernfalls würde das Bild nach einem vorangehenden Stop-Aufruf verloren gehen.

Abbildung 2 Die Datei MainPage.xaml.cs von 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;
    }
  }
}

Sie können dieses Programm im Windows Phone Emulator ausführen. Es ist jedoch auf einem echten Gerät wesentlich interessanter. Sie werden feststellen, dass das Rendern des Videofeeds sehr schnell reagiert. Offensichtlich wird der Videofeed direkt zur Videohardware geleitet. (Ein weiterer Beleg für diese Annahme ist die Tatsache, dass meine normale Methode für die Erstellung von Screenshots vom Telefon mittels des Renderns des Objekts PhoneApplicationFrame in eine WriteableBitmap für dieses Programm nicht funktioniert hat.) Sie werden außerdem feststellen, dass der Brush an die Abmessungen des Inhaltsrasters angepasst wurde und das Bild verzerrt ist, da das Video über einen Brush gerendert wird.

Eine der interessanten Eigenschaften von Brushes besteht darin, dass sie von mehreren Elementen verwendet werden können. Dies ist die Idee hinter FlipXYVideo. Dieses Programm erstellt dynamisch eine Reihe von ziegelartig angeordneten Rectangle-Objekten in einem Raster. Für jedes dieser Rechtecke wird der gleiche VideoBrush verwendet. Die Rechtecke sind jedoch abwechselnd vertikal oder horizontal gedreht, oder beide, wie in Abbildung 3 gezeigt. Sie können die Anzahl der Zeilen und Spalten über die ApplicationBar-Schaltflächen erhöhen oder erniedrigen.

Abbildung 3 Verwenden von VideoBrush-Objekten 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;
}

Dieses Programm ist unterhaltsam, jedoch nicht so unterhaltsam wie das Kaleidoskop-Programm, das ich in Kürze vorstellen werde.

Quelle und Sink

Die Alternative zur Verwendung eines VideoBrush stellt die Verbindung eines CaptureSource-Objekts mit einem AudioSink-, VideoSink- oder FileSink-Objekt dar. Das Wort "Sink" in diesen Klassennamen ist im Sinne von "Auffangbehälter" zu verstehen und der Verwendung dieses Worts in der Elektronik- oder Netzwerktheorie vergleichbar. (Sie können sich auch eine "Hitzequelle" und einen "Hitzeauffangbehälter" vorstellen.)

Die Klasse FileSink ist die bevorzugte Methode für das Speichern von Video- oder Audiostreams im isolierten Speicher der Anwendung, ohne dass Sie eingreifen müssen. Wenn Sie auf die tatsächlichen Video- oder Audiobits in Echtzeit zugreifen müssen, verwenden Sie VideoSink und AudioSink. Diese beiden Klassen sind abstrakt. Sie leiten eine Klasse von einer oder von beiden dieser abstrakten Klassen ab und setzen die Methoden OnCaptureStarted, OnCaptureStopped, OnFormatChange und OnSample außer Kraft.

Die Klasse, die Sie von VideoSink oder AudioSink ableiten, erhält vor dem ersten Aufruf für OnSample stets einen Aufruf für OnFormatChange. Die Informationen, die von OnFormatChange bereitgestellt werden, zeigen an, wie die Sampledaten interpretiert werden müssen. Der OnSample-Aufruf stellt sowohl für VideoSink als auch für AudioSink Zeitplanungsinformationen und ein Array von Bytes bereit. Im Fall von AudioSink stellen diese Bytes PCM-Daten dar. Im Fall von VideoSink handelt es sich bei diesen Bytes um Zeilen und Spalten von Pixeln für jeden einzelnen Videoframe. Diese Daten sind stets roh und unkomprimiert.

Sowohl OnFormatChange als auch OnSample werden in sekundären Ausführungsthreads aufgerufen. Daher benötigen Sie ein Dispatcher-Objekt für Aufgaben innerhalb dieser Methoden, die im Benutzeroberflächenthread ausgeführt werden müssen.

Das Programm StraightVideoSink ist StraightVideo vergleichbar. Die Videodaten werden jedoch von einer Klasse empfangen, die von VideoSink abgeleitet wurde. Diese abgeleitete Klasse (siehe Abbildung 4) wurde SimpleVideoSink genannt, da sie lediglich das OnSample-Bytearray empfängt und an eine WriteableBitmap weiterleitet.

Abbildung 4 Die Klasse SimpleVideoSink Class 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 verwendet diese WriteableBitmap mit einem Image-Element, um den resultierenden Videofeed anzuzeigen. (Alternativ könnte ein ImageBrush erstellt werden und auf den Hinter- oder Vordergrund eines Elements festgelegt werden.)

Der Haken ist folgender: Diese WriteableBitmap kann nicht erstellt werden, wenn die OnFormatChange-Methode nicht in der VideoSink-Ableitung aufgerufen wird, da dieser Aufruf die Größe des Videoframes angibt. (In der Regel sind dies auf meinem Telefon 640x480 Pixel. Dies muss jedoch nicht stets der Fall sein.) Obwohl die VideoSink-Ableitung die WriteableBitmap erstellt, muss MainPage auf diese zugreifen. Daher habe ich einen Konstruktor für SimpleVideoSink erstellt, der ein Action-Argument enthält, das bei der Erstellung der WriteableBitmap aufgerufen wird.

Beachten Sie, dass die WriteableBitmap im Benutzeroberflächenthread des Programms erstellt werden muss. Daher verwendet SimpleVideoSink ein Dispatcher-Objekt, um die Erstellung in die Warteschlagen für den Benutzeroberflächenthread einzureihen. Das bedeutet, dass die WriteableBitmap möglicherweise nicht vor dem ersten OnSample-Aufruf erstellt wird. Achten Sie darauf! Obwohl die OnSample-Methode auf den Pixelarray der WriteableBitmap in einem sekundären Thread zugreifen kann, muss der Aufruf für die Invalidierung der Bitmap im Benutzeroberflächenthread erfolgen, da dieser Aufruf letzten Endes Auswirkungen auf die Anzeige der Bitmap durch das Image-Element hat.

Die Klasse MainPage von StraightVideoSink enthält eine Application­Bar-Schaltfläche, um zwischen farbigen und schwarz-weißen Videofeeds umzuschalten. Dies sind die beiden einzigen Optionen. Sie können zwischen diesen wechseln, indem Sie die Eigenschaft DesiredFormat des Objekts VideoCaptureDevice festlegen. Ein Farbvideofeed hat 4 Bytes pro Pixel in der Reihenfolge Grün, Blau, Rot und Alpha (dies ist stets 255). Ein Schwarzweißvideofeed hat lediglich 1 Byte pro Pixel. In beiden Fällen hat eine WriteableBitmap stets 4 Bytes pro Pixel, wobei jedes Pixel von einer 32-Bit-Integerzahl dargestellt wird. Die höchsten 8 Bits stehen für den Alphakanal, gefolgt von Rot, Grün und Blau. Das CaptureSource-Objekt muss angehalten und neu gestartet werden, wenn das Format gewechselt wird.

Obwohl sowohl StraightVideo als auch StraightVideoSink den Livevideofeed anzeigen, werden Sie wahrscheinlich feststellen, dass StraightVideoSink wesentlich langsamer ist. Der Grund hierfür ist, dass das Programm den Videoframe an die WriteableBitmap weiterleitet.

Erstellen eines Kaleidoskops

Wenn Sie nur einen Echtzeitvideofeed mit gelegentlichen Frameerfassungen benötigen, können Sie die CaptureImageAsync-Methode von CaptureSource verwenden. Aufgrund des Leistungsoverheads werden Sie wahrscheinlich die Verwendung von VideoSink auf speziellere Anwendungen einschränken, die die Bearbeitung der Pixelbits beinhalten.

Erstellen wir nun ein solches "spezielleres" Programm, das den Videofeed in einem Kaleidoskopmuster anordnet. Vom Konzept her ist der Code ziemlich einfach: Die VideoSink-Ableitung erhält einen Videofeed, in dem jeder Frame wahrscheinlich 640x480 Pixel groß ist oder vielleicht eine andere Größe hat. Sie möchten auf ein gleichseitiges Dreieck von Imagedaten aus diesem Frame verweisen, wie in Abbildung 5 gezeigt.

Ich habe mich für ein Dreieck mit der Grundseite oben und der Spitze unten entschieden, um Gesichter besser erfassen zu können.

The Source Triangle for a Kaleidoscope
Abbildung 5 Das Quelldreieck für ein Kaleidoskop

Das Bild in diesem Dreieck wird anschließend mehrmals auf einer WriteableBitmap dupliziert, wobei es rotiert und gedreht wird, sodass die Bilder ohne Unterbrechungen in Sechsecken angeordnet und gruppiert werden, wie in Abbildung 6 gezeigt. Die Sechsecke sehen wie hübsche Blumen aus. Es handelt sich in Wirklichkeit jedoch einfach nur um zahlreiche Bilder meines Gesichts (vielleicht ein wenig zu viel davon).

The Destination Bitmap for a Kaleidoscope
Abbildung 6 Das Zieldreieck für ein Kaleidoskop

Das Wiederholungsmuster wird offensichtlicher, wenn die einzelnen Dreiecke voneinander abgegrenzt werden, wie in Abbildung 7 gezeigt. Beim Rendern auf dem Telefon ist die Höhe der vorgesehenen WriteableBitmap die gleiche wie die kleinere Abmessung des Telefons bzw. 480 Pixel. Jedes gleichseitige Dreieck hat daher eine Seite mit 120 Pixeln. Das bedeutet, dass die Höhe des Dreiecks das 120-Fache des Quadrats von 0,75 oder etwa 104 Pixel beträgt. Im Programm verwende ich für die Berechnung 104, jedoch 105 für die Größenanpassung der Bitmap, um die Schleifen zu vereinfachen. Das sich ergebende Gesamtbild ist 630 Pixel breit.

The Destination Bitmap Showing the Source Triangles
Abbildung 7 Die Zielbitmap mit den Quelldreiecken

Ich fand es äußerst nützlich, das Gesamtbild als drei identische vertikale Bänder mit einer Breite von 210 Pixeln zu behandeln. Jedes dieser vertikalen Bänder verfügt um den vertikalen Mittelpunkt herum über eine symmetrische Reflektion. Daher habe ich das Bild auf eine einzelne Bitmap mit 105x480 Pixeln reduziert, die sechsmal wiederholt wird. Die Hälfte verfügt über Reflektion. Das Band besteht aus nur sieben vollständigen Dreiecken und zwei Teildreiecken.

Trotzdem war ich hinsichtlich der Berechnungen nervös, die für die Assemblierung dieses Bilds erforderlich waren. Dann erkannte ich, dass diese Berechnungen nicht mit der Aktualisierungsrate für das Video von 30 Aktualisierungen pro Sekunde durchgeführt werden müssen. Sie müssen nur einmal durchgeführt werden, wenn die Größe des Videobilds im OnFormatChange-Override verfügbar wird.

Das Programm hat den Namen Kaleidovideo erhalten. (Dieser Name wird von Traditionalisten sicherlich als etymologische Abscheulichkeit betrachtet, da "kaleido" aus dem Griechischen stammt und "schöne Gestalt" bedeutet, während Video aus dem Lateinischen stammt, und Sie die beiden Sprachen nicht vermischen sollten, wenn Sie neue Begriffe prägen.)

Die Klasse KaleidoscopeVideoSink setzt VideoSink außer Kraft. Die Methode OnFormatChange ist für die Berechnung der Mitglieder eines Arrays namens indexMap verantwortlich. Dieses Array hat genauso viele Mitglieder, wie es in der WriteableBitmap Pixel gibt (105 multipliziert mit 480 bzw. 50.400) und speichert einen Index in den rechteckigen Bereich des Videobilds. Mittels des Arrays indexMap ist die Weiterleitung der Pixel vom Videobild zur WriteableBitmap in der OnSample-Methode so schnell, wie man sich nur vorstellen kann.

Dieses Programm ist äußerst unterhaltsam. Alles sieht mit Kaleidovideo einfach schöner aus. Abbildung 8 zeigt beispielsweise eines meiner Bücherregale. Sie können mit Kaleidovideo sogar fernsehen.

A Typical Kaleidovideo Screen
Abbildung 8 Ein typischer Kaleidovideo-Bildschirm

Für den Fall, dass Sie auf dem Bildschirm etwas sehen, was Sie speichern möchten, habe ich der ApplicationBar eine Schaltfläche für Screenshots hinzugefügt. Die Bilder werden im Ordner für gespeicherte Bilder in der Fotobibliothek des Telefons gespeichert.           

Charles Petzold schreibt seit langem redaktionelle Beiträge für das MSDN Magazin. Sein letztes Buch mit dem Titel "Programming in Windows Phone 7" (Microsoft Press 2010) ist als kostenloser Download unter bit.ly/cpebookpdf verfügbar.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Mark Hopkins und Matt Stroshane