Freigeben über


Verarbeiten von Medienframes mit „MediaFrameReader“

Dieser Artikel zeigt Ihnen, wie Sie MediaFrameReader mit MediaCapture verwenden, um Medienframes aus einer oder mehreren verfügbaren Quellen abzurufen, einschließlich Farb-, Tiefen- und Infrarotkameras, Audiogeräte oder sogar benutzerdefinierte Framequellen wie solche, die Skelett-Tracking-Frames erzeugen. Diese Funktion ist für Apps gedacht, die Medienframes in Echtzeit verarbeiten, wie z. B. Apps für erweiterte Realität und tiefenwahrnehmungsfähige Kameras.

Wenn Sie nur Video oder Fotos aufnehmen möchten, z. B. eine typische Foto-App, sollten Sie wahrscheinlich eine der anderen Aufnahmetechniken verwenden, die von MediaCapture unterstützt werden. Für eine Liste der verfügbaren Medienaufnahmetechniken und Artikel, die zeigen, wie man diese verwendet, siehe Kamera.

Hinweis

Die in diesem Artikel beschriebenen Features sind nur ab Windows 10, Version 1607, verfügbar.

Hinweis

Es gibt ein Beispiel für universelle Windows-Apps, das die Verwendung von MediaFrameReader zum Anzeigen von Frames aus verschiedenen Framequellen veranschaulicht, einschließlich Farb-, Tiefen- und Infrarotkameras. Weitere Informationen finden Sie unter Beispiel für Kameraframes.

Hinweis

In Windows 10, Version 1803, wurde eine neue Gruppe von APIs für die Verwendung von MediaFrameReader mit Audiodaten eingeführt. Weitere Informationen finden Sie unter Verarbeiten von Audioframes mit MediaFrameReader.

Wählen Sie Rahmenquellen und Rahmenquellengruppen aus

Viele Apps, die Medienframes verarbeiten, müssen Frames aus mehreren Quellen gleichzeitig abrufen, z. B. die Farb- und Tiefenkameras eines Geräts. Das MediaFrameSourceGroup-Objekt repräsentiert eine Reihe von Framequellen, die gleichzeitig verwendet werden können. Rufen Sie die statische Methode MediaFrameSourceGroup.FindAllAsync auf, um eine Liste aller Gruppen von Framequellen abzurufen, die vom aktuellen Gerät unterstützt werden.

var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();

Sie können auch einen DeviceWatcher- erstellen, indem Sie DeviceInformation.CreateWatcher verwenden und den von MediaFrameSourceGroup.GetDeviceSelector zurückgegebenen Wert verwenden, um Benachrichtigungen zu empfangen, wenn sich die verfügbaren Quellgruppen für Frames auf dem Gerät verändern, z. B. wenn eine externe Kamera angeschlossen wird. Weitere Informationen finden Sie unter Geräte auflisten.

Ein MediaFrameSourceGroup- verfügt über eine Sammlung von MediaFrameSourceInfo- Objekten, die die in der Gruppe enthaltenen Framequellen beschreiben. Nachdem Sie die auf dem Gerät verfügbaren Framequellgruppen abgerufen haben, können Sie die Gruppe auswählen, die die Framequellen, an denen Sie interessiert sind, verfügbar macht.

Das folgende Beispiel zeigt die einfachste Möglichkeit zum Auswählen einer Framequellgruppe. Dieser Code durchläuft einfach alle verfügbaren Gruppen und durchläuft dann jedes Element in der SourceInfos Auflistung. Jede MediaFrameSourceInfo- wird überprüft, um festzustellen, ob sie die gesuchten Features unterstützt. In diesem Fall wird die MediaStreamType Eigenschaft auf den Wert VideoPreviewüberprüft, was bedeutet, dass das Gerät einen Videovorschaudatenstrom bereitstellt. Zudem wird die SourceKind Eigenschaft auf den Wert Colorüberprüft, was darauf hinweist, dass die Quelle Farbframes bereitstellt.

var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();

MediaFrameSourceGroup selectedGroup = null;
MediaFrameSourceInfo colorSourceInfo = null;

foreach (var sourceGroup in frameSourceGroups)
{
    foreach (var sourceInfo in sourceGroup.SourceInfos)
    {
        if (sourceInfo.MediaStreamType == MediaStreamType.VideoPreview
            && sourceInfo.SourceKind == MediaFrameSourceKind.Color)
        {
            colorSourceInfo = sourceInfo;
            break;
        }
    }
    if (colorSourceInfo != null)
    {
        selectedGroup = sourceGroup;
        break;
    }
}

Diese Methode zum Identifizieren der gewünschten Framequellgruppe und Framequellen funktioniert für einfache Fälle, aber wenn Sie Framequellen basierend auf komplexeren Kriterien auswählen möchten, kann es schnell umständlich werden. Eine weitere Methode besteht darin, linq-Syntax und anonyme Objekte zu verwenden, um die Auswahl zu treffen. Im folgenden Beispiel wird die Select Verlängerungsmethode verwendet, um die MediaFrameSourceGroup Objekte in der frameSourceGroups Liste in ein anonymes Objekt mit zwei Feldern zu transformieren: sourceGroup, das die Gruppe selbst darstellt, und colorSourceInfo, das die Farbrahmenquelle in der Gruppe darstellt. Das colorSourceInfo Feld wird auf das Ergebnis von FirstOrDefaultfestgelegt, das das erste Objekt auswählt, für das das gegebene Prädikat true ergibt. In diesem Fall ist das Prädikat "wahr", wenn der Datenstromtyp VideoPreviewist, die Quellart Colorist, und wenn sich die Kamera auf der Vorderseite des Geräts befindet.

Aus der Liste der anonymen Objekte, die aus der oben beschriebenen Abfrage zurückgegeben werden, wird die Where-Erweiterungsmethode verwendet, um nur die Objekte auszuwählen, bei denen das ColorSourceInfo-Feld nicht NULL ist. Schließlich wird FirstOrDefault aufgerufen, um das erste Element in der Liste auszuwählen.

Jetzt können Sie die Felder des ausgewählten Objekts verwenden, um Verweise auf die ausgewählte MediaFrameSourceGroup und das MediaFrameSourceInfo-Objekt abzurufen, welches die Farbkamera darstellt. Diese werden später verwendet, um das MediaCapture--Objekt zu initialisieren und einen MediaFrameReader- für die ausgewählte Quelle zu erstellen. Schließlich sollten Sie testen, ob die Quellgruppe null ist, was bedeutet, dass das aktuelle Gerät nicht über die angeforderten Aufnahmequellen verfügt.

var selectedGroupObjects = frameSourceGroups.Select(group =>
   new
   {
       sourceGroup = group,
       colorSourceInfo = group.SourceInfos.FirstOrDefault((sourceInfo) =>
       {
           // On Xbox/Kinect, omit the MediaStreamType and EnclosureLocation tests
           return sourceInfo.MediaStreamType == MediaStreamType.VideoPreview
           && sourceInfo.SourceKind == MediaFrameSourceKind.Color
           && sourceInfo.DeviceInformation?.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front;
       })

   }).Where(t => t.colorSourceInfo != null)
   .FirstOrDefault();

MediaFrameSourceGroup selectedGroup = selectedGroupObjects?.sourceGroup;
MediaFrameSourceInfo colorSourceInfo = selectedGroupObjects?.colorSourceInfo;

if (selectedGroup == null)
{
    return;
}

Im folgenden Beispiel wird eine ähnliche Technik verwendet, wie oben beschrieben, um eine Quellgruppe auszuwählen, die Farb-, Tiefen- und Infrarotkameras enthält.

var allGroups = await MediaFrameSourceGroup.FindAllAsync();
var eligibleGroups = allGroups.Select(g => new
{
    Group = g,

    // For each source kind, find the source which offers that kind of media frame,
    // or null if there is no such source.
    SourceInfos = new MediaFrameSourceInfo[]
    {
        g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Color),
        g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Depth),
        g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Infrared),
    }
}).Where(g => g.SourceInfos.Any(info => info != null)).ToList();

if (eligibleGroups.Count == 0)
{
    System.Diagnostics.Debug.WriteLine("No source group with color, depth or infrared found.");
    return;
}

var selectedGroupIndex = 0; // Select the first eligible group
MediaFrameSourceGroup selectedGroup = eligibleGroups[selectedGroupIndex].Group;
MediaFrameSourceInfo colorSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[0];
MediaFrameSourceInfo infraredSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[1];
MediaFrameSourceInfo depthSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[2];

Hinweis

Ab Windows 10, Version 1803, können Sie die MediaCaptureVideoProfile-Klasse verwenden, um eine Medienframequelle mit einer Reihe von gewünschten Funktionen auszuwählen. Weitere Informationen finden Sie im Abschnitt Verwendung von Videoprofilen zur Auswahl einer Framequelle weiter unten in diesem Artikel.

Initialisieren des MediaCapture-Objekts für die Verwendung der ausgewählten Framequellgruppe

Der nächste Schritt besteht darin, das MediaCapture-Objekt zu initialisieren, um die Framequellgruppe zu verwenden, die Sie im vorherigen Schritt ausgewählt haben. Erstellen Sie eine Instanz des MediaCapture-Objekts , indem Sie den Konstruktor aufrufen. Erstellen Sie als Nächstes ein MediaCaptureInitializationSettings Objekt, das zum Initialisieren des MediaCapture Objekts verwendet wird. In diesem Beispiel werden die folgenden Einstellungen verwendet:

  • SourceGroup- – Hiermit wird dem System mitgeteilt, welche Quellgruppe Sie verwenden, um Frames abzurufen. Denken Sie daran, dass die Quellgruppe einen Satz von Medienframequellen definiert, die gleichzeitig verwendet werden können.
  • SharingMode- – Damit wird dem System mitgeteilt, ob Sie exklusive Kontrolle über die Aufnahmequellgeräte benötigen. Wenn Sie dies auf ExclusiveControl-festlegen, bedeutet dies, dass Sie die Einstellungen des Aufnahmegeräts ändern können, z. B. das Format der von ihm erzeugten Frames. Allerdings wird Ihre App fehlschlagen, wenn bereits eine andere App über exklusive Kontrolle verfügt, wenn Ihre App versucht, das Medienaufnahmegerät zu initialisieren. Wenn Sie dies auf SharedReadOnly-festlegen, können Sie Frames aus den Framequellen empfangen, auch wenn sie von einer anderen App verwendet werden, aber Sie können die Einstellungen für die Geräte nicht ändern.
  • MemoryPreference- - Wenn Sie CPU-angeben, verwendet das System den CPU-Speicher, der gewährleistet, dass bei Eintreffen der Frames diese als SoftwareBitmap--Objekte verfügbar sind. Wenn Sie Autoangeben, wählt das System dynamisch den optimalen Speicherort für Frames aus. Wenn sich das System für die Verwendung des GPU-Speichers entscheidet, werden die Medienframes als IDirect3DSurface--Objekt und nicht als SoftwareBitmap-eintreffen.
  • StreamingCaptureMode – Legen Sie dies auf Video fest, um anzugeben, dass Audio nicht gestreamt werden muss.

Rufen Sie InitializeAsync- auf, um die MediaCapture- mit den gewünschten Einstellungen zu initialisieren. Achten Sie darauf, dies innerhalb eines try--Blocks aufzurufen, falls die Initialisierung fehlschlägt.

m_mediaCapture = new MediaCapture();

var settings = new MediaCaptureInitializationSettings()
{
    SourceGroup = selectedGroup,
    SharingMode = MediaCaptureSharingMode.ExclusiveControl,
    MemoryPreference = MediaCaptureMemoryPreference.Cpu,
    StreamingCaptureMode = StreamingCaptureMode.Video
};
try
{
    await m_mediaCapture.InitializeAsync(settings);
}
catch (Exception ex)
{
    System.Diagnostics.Debug.WriteLine("MediaCapture initialization failed: " + ex.Message);
    return;
}

Festlegen des bevorzugten Formats für die Framequelle

Um das bevorzugte Format für eine Framequelle festzulegen, müssen Sie ein MediaFrameSource-Objekt abrufen, das die Quelle darstellt. Sie erhalten dieses Objekt, indem Sie auf das Frames--Wörterbuch des initialisierten MediaCapture-Objekts zugreifen und den Bezeichner der Framequelle angeben, die Sie verwenden möchten. Deshalb haben wir das MediaFrameSourceInfo-Objekt gespeichert, als wir eine Framequellgruppe ausgewählt haben.

Die MediaFrameSource.SupportedFormats--Eigenschaft enthält eine Liste von MediaFrameFormat--Objekten, die die unterstützten Formate der Framequelle beschreiben. In diesem Beispiel wird ein Format ausgewählt, das eine Breite von 1080 Pixel aufweist und Frames im 32-Bit-RGB-Format bereitstellen kann. Die FirstOrDefault Erweiterungsmethode wählt den ersten Eintrag in der Liste aus. Wenn das ausgewählte Format NULL ist, wird das angeforderte Format von der Framequelle nicht unterstützt. Wenn das Format unterstützt wird, können Sie anfordern, dass die Quelle dieses Format verwendet, indem Sie SetFormatAsync aufrufen.

var colorFrameSource = m_mediaCapture.FrameSources[colorSourceInfo.Id];
var preferredFormat = colorFrameSource.SupportedFormats.Where(format =>
{
    return format.VideoFormat.Width >= 1080
    && format.Subtype == MediaEncodingSubtypes.Argb32;

}).FirstOrDefault();

if (preferredFormat == null)
{
    // Our desired format is not supported
    return;
}

await colorFrameSource.SetFormatAsync(preferredFormat);

Erstellen Sie einen Rahmenleser für die Rahmenquelle.

Verwenden Sie zum Empfangen von Frames für eine Medienrahmenquelle einen MediaFrameReader-.

MediaFrameReader m_mediaFrameReader;

Instanziieren Sie den Frame-Leser, indem Sie CreateFrameReaderAsync für das initialisierte MediaCapture-Objekt aufrufen. Das erste Argument für diese Methode ist die Rahmenquelle, von der Sie Rahmen empfangen möchten. Sie können für jede Framequelle, die Sie verwenden möchten, einen separaten Frameleser erstellen. Das zweite Argument gibt dem System an, in welchem Ausgabeformat die Frames ankommen sollen. Dies kann Ihnen ersparen, Ihre eigenen Umwandlungen in Frames vornehmen zu müssen, sobald sie ankommen. Beachten Sie, dass beim Angeben eines Formats, das von der Framequelle nicht unterstützt wird, eine Ausnahme ausgelöst wird. Stellen Sie daher sicher, dass dieser Wert in der SupportedFormats-Auflistung enthalten ist.

Registrieren Sie, nachdem Sie den Framereader erstellt haben, einen Handler für das FrameArrived--Ereignis, das ausgelöst wird, wenn ein neuer Frame aus der Quelle verfügbar ist.

Weisen Sie das System an, mit dem Lesen von Frames aus der Quelle zu beginnen, indem Sie StartAsyncaufrufen.

m_mediaFrameReader = await m_mediaCapture.CreateFrameReaderAsync(colorFrameSource, MediaEncodingSubtypes.Argb32);
m_mediaFrameReader.FrameArrived += ColorFrameReader_FrameArrived;
await m_mediaFrameReader.StartAsync();

Behandeln des eingetroffenen Frame-Ereignisses

Das MediaFrameReader.FrameArrived-Ereignis wird ausgelöst, wenn ein neuer Frame verfügbar ist. Sie können auswählen, ob jeder Frame verarbeitet wird, der eingeht, oder ob Sie Frames nur verwenden, wenn Sie sie benötigen. Da der Frameleser das Ereignis in einem eigenen Thread auslöst, müssen Sie möglicherweise eine Synchronisierungslogik implementieren, um sicherzustellen, dass Sie nicht versuchen, auf dieselben Daten aus mehreren Threads zuzugreifen. In diesem Abschnitt wird erklärt, wie Sie Farbrahmen von Zeichnungen mit einem Bildsteuerelement auf einer XAML-Seite synchronisieren. In diesem Szenario wird die zusätzliche Synchronisierungseinschränkung behandelt, die erfordert, dass alle Aktualisierungen von XAML-Steuerelementen im UI-Thread durchgeführt werden.

Der erste Schritt beim Anzeigen von Frames in XAML besteht darin, ein Bildsteuerelement zu erstellen.

<Image x:Name="iFrameReaderImageControl" MaxWidth="300" MaxHeight="200"/>

Deklarieren Sie auf der Code-Behind-Seite eine Klassenmitgliedsvariable vom Typ SoftwareBitmap, die als Hintergrundpuffer verwendet wird und in den alle eingehenden Bilder kopiert werden. Beachten Sie, dass die Bilddaten selbst nicht kopiert werden, nur die Objektverweise. Deklarieren Sie außerdem einen booleschen Wert, um nachzuverfolgen, ob der UI-Vorgang zurzeit ausgeführt wird.

private SoftwareBitmap backBuffer;
private bool taskRunning = false;

Da die Frames als SoftwareBitmap- Objekte ankommen, müssen Sie ein SoftwareBitmapSource--Objekt erstellen, das es Ihnen ermöglicht, ein SoftwareBitmap- als Quelle für ein XAML-Steuerelement-zu verwenden. Sie sollten die Bildquelle an einer beliebigen Stelle im Code festlegen, bevor Sie den Frameleser starten.

iFrameReaderImageControl.Source = new SoftwareBitmapSource();

Jetzt ist es an der Zeit, den FrameArrived Ereignishandler zu implementieren. Wenn der Handler aufgerufen wird, enthält der Absenderparameter einen Verweis auf das MediaFrameReader--Objekt, das das Ereignis ausgelöst hat. Rufen Sie die Methode TryAcquireLatestFrame auf diesem Objekt auf, um zu versuchen, den neuesten Frame abzurufen. Wie der Name schon sagt, kann TryAcquireLatestFrame- möglicherweise nicht erfolgreich ein Frame zurückgeben. Wenn Sie also auf die Eigenschaften "VideoMediaFrame" und "SoftwareBitmap" zugreifen, sollten Sie unbedingt auf NULL testen. In diesem Beispiel ist der bedingte Operator null ? wird verwendet, um auf die SoftwareBitmap- zuzugreifen, und dann wird das abgerufene Objekt auf NULL überprüft.

Das Bildsteuerungselement kann nur Bilder im BRGA8-Format mit entweder vorab multiplizierten oder ohne Alpha anzeigen. Wenn sich der eingehende Frame nicht in diesem Format befindet, wird die statische Methode Convert genutzt, um die (Software)-Bitmap in das richtige Format zu konvertieren.

Als Nächstes wird die Interlocked.Exchange-Methode verwendet, um den Verweis auf die eingehende Bitmap mit der Backbuffer-Bitmap zu tauschen. Mit dieser Methode werden diese Verweise in einem atombasierten Vorgang ausgetauscht, der threadsicher ist. Nach dem Austausch wird das alte Backbuffer-Bild, das sich nun in der softwareBitmap Variable befindet, entsorgt, um die Ressourcen freizugeben.

Als Nächstes wird der CoreDispatcher, der dem Image-Element zugeordnet ist, verwendet, um eine Aufgabe zu erstellen, die im UI-Thread ausgeführt wird, indem RunAsyncaufgerufen wird. Da die asynchronen Aufgaben innerhalb derselben Aufgabe ausgeführt werden, wird der an RunAsync übergebene Lambda-Ausdruck mit dem async Schlüsselwort deklariert.

Innerhalb der Aufgabe wird die _taskRunning Variable überprüft, um sicherzustellen, dass jeweils nur eine Instanz der Aufgabe ausgeführt wird. Wenn die Aufgabe noch nicht ausgeführt wird, wird _taskRunning auf "true" festgelegt, um zu verhindern, dass die Aufgabe erneut ausgeführt wird. In einer while--Schleife wird Interlocked.Exchange aufgerufen, um aus dem Backbuffer in eine temporäre SoftwareBitmap zu kopieren, bis das Backbuffer-Image null ist. Bei jedem Auffüllen der temporären Bitmap wird die Eigenschaft Source des Image- in eine SoftwareBitmapSource-konvertiert, und dann wird SetBitmapAsync- aufgerufen, damit die Quelle des Bildes festgelegt wird.

Schließlich wird die _taskRunning Variable auf "false" zurückgesetzt, sodass die Aufgabe erneut ausgeführt werden kann, wenn der Handler das nächste Mal aufgerufen wird.

Hinweis

Wenn Sie auf die SoftwareBitmap oder Direct3DSurface Objekte zugreifen, die von der VideoMediaFrame Eigenschaft eines MediaFrameReference-bereitgestellt werden, erstellt das System einen starken Verweis auf diese Objekte, was bedeutet, dass sie nicht verworfen werden, wenn Sie Dispose für die enthaltende MediaFrameReferenceaufrufen. Sie müssen explizit die Methode Dispose der SoftwareBitmap oder Direct3DSurface direkt aufrufen, sodass die Objekte sofort verworfen werden. Andernfalls gibt der Garbage Collector schließlich den Speicher für diese Objekte frei, aber Sie können nicht wissen, wann dies geschieht, und wenn die Anzahl der zugewiesenen Bitmaps oder Oberflächen den vom System zulässigen maximalen Wert überschreitet, wird der Fluss neuer Frames beendet. Sie können abgerufene Frames kopieren, z. B. mit der SoftwareBitmap.Copy-Methode , und dann die ursprünglichen Frames freigeben, um diese Einschränkung zu überwinden. Wenn Sie außerdem den MediaFrameReader mithilfe der Überladungsfunktion CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) oder CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype)erstellen, werden die zurückgegebenen Frames als Kopien der ursprünglichen Framedaten bereitgestellt und führen daher nicht dazu, dass die Frameerfassung angehalten wird, wenn sie aufbewahrt werden.

private void ColorFrameReader_FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
    var mediaFrameReference = sender.TryAcquireLatestFrame();
    var videoMediaFrame = mediaFrameReference?.VideoMediaFrame;
    var softwareBitmap = videoMediaFrame?.SoftwareBitmap;

    if (softwareBitmap != null)
    {
        if (softwareBitmap.BitmapPixelFormat != Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8 ||
            softwareBitmap.BitmapAlphaMode != Windows.Graphics.Imaging.BitmapAlphaMode.Premultiplied)
        {
            softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
        }

        // Swap the processed frame to _backBuffer and dispose of the unused image.
        softwareBitmap = Interlocked.Exchange(ref backBuffer, softwareBitmap);
        softwareBitmap?.Dispose();

        // Changes to XAML ImageElement must happen on UI thread through Dispatcher
        var task = iFrameReaderImageControl.DispatcherQueue.TryEnqueue(
            async () =>
            {
                // Don't let two copies of this task run at the same time.
                if (taskRunning)
                {
                    return;
                }
                taskRunning = true;

                // Keep draining frames from the backbuffer until the backbuffer is empty.
                SoftwareBitmap latestBitmap;
                while ((latestBitmap = Interlocked.Exchange(ref backBuffer, null)) != null)
                {
                    var imageSource = (SoftwareBitmapSource)iFrameReaderImageControl.Source;
                    await imageSource.SetBitmapAsync(latestBitmap);
                    latestBitmap.Dispose();
                }

                taskRunning = false;
            });
    }

    if (mediaFrameReference != null)
    {
        mediaFrameReference.Dispose();
    }
}

Bereinigen von Ressourcen

Wenn Sie mit dem Lesen von Frames fertig sind, stellen Sie sicher, dass Sie den Medienframeleser stoppen, indem Sie StopAsyncaufrufen, die Registrierung des FrameArrived--Handlers aufheben und das MediaCapture--Objekt entsorgen.

await m_mediaFrameReader.StopAsync();
m_mediaFrameReader.FrameArrived -= ColorFrameReader_FrameArrived;
m_mediaCapture.Dispose();
m_mediaCapture = null;

Weitere Informationen zum Bereinigen von Medienaufnahmeobjekten beim Anhalten der Anwendung finden Sie unter "Anzeigen der Kameravorschau" in einer WinUI 3-App.

Die FrameRenderer-Hilfsklasse

Dieser Abschnitt enthält die vollständige Codeauflistung für eine Hilfsklasse, die das Anzeigen der Frames aus Farb-, Infrarot- und Tiefenquellen in Ihrer App erleichtert. Normalerweise möchten Sie mehr mit Tiefen- und Infrarotdaten anstellen, als sie nur auf dem Bildschirm anzuzeigen, aber diese Hilfsklasse ist ein nützliches Werkzeug, um die Frame-Lesefunktion zu demonstrieren und Ihre eigene Frame-Reader-Implementierung zu debuggen. Der Code in diesem Abschnitt wird aus dem Beispiel für Kameraframes angepasst.

Die FrameRenderer-Hilfsklasse implementiert die folgenden Methoden.

  • FrameRenderer-Konstruktor – Der Konstruktor richtet die Hilfsklasse ein, um das XAML-Image--Element zu verwenden, das Sie zur Anzeige von Medienrahmen übergeben.
  • ProcessFrame- – Mit dieser Methode wird ein Medienframe angezeigt, dargestellt durch ein MediaFrameReference-im Image-Element, das Sie an den Konstruktor übergeben haben. Normalerweise sollten Sie diese Methode innerhalb Ihres FrameArrived-Ereignishandlers aufrufen und den Frame übergeben, der von TryAcquireLatestFramezurückgegeben wird.
  • ConvertToDisplayableImage- – Diese Methode überprüft das Format des Medienframes und konvertiert es bei Bedarf in ein anzeigbares Format. Bei Farbbildern bedeutet dies, dass das Farbformat BGRA8 ist und dass der Bitmap-Alphamodus prämultipliziert ist. Bei Tiefen- oder Infrarotframes wird jede Scanlinie verarbeitet, um die Tiefen- oder Infrarotwerte in einen Pseudocolor-Farbverlauf zu konvertieren. Dabei wird die Klasse PseudocolorHelper verwendet, die ebenfalls im Beispiel enthalten und unten aufgeführt ist.

Hinweis

Um die Pixelbearbeitung für SoftwareBitmap Bilder durchzuführen, müssen Sie auf einen systemeigenen Speicherpuffer zugreifen. Dazu müssen Sie die IMemoryBufferByteAccess-COM-Schnittstelle verwenden, die in der folgenden Codeauflistung enthalten ist, und Sie müssen die Projekteigenschaften aktualisieren, um die Kompilierung unsicherer Code zu ermöglichen. Weitere Informationen finden Sie unter Erstellen, Bearbeiten und Speichern von Bitmapbildern.

[GeneratedComInterface]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe partial interface IMemoryBufferByteAccess
{
    void GetBuffer(out byte* buffer, out uint capacity);
}
class FrameRenderer
{
    private Image m_imageElement;
    private SoftwareBitmap m_backBuffer;
    private bool _taskRunning = false;

    public FrameRenderer(Image imageElement)
    {
        m_imageElement = imageElement;
        m_imageElement.Source = new SoftwareBitmapSource();
    }

    // Processes a MediaFrameReference and displays it in a XAML image control
    public void ProcessFrame(MediaFrameReference frame)
    {
        var softwareBitmap = FrameRenderer.ConvertToDisplayableImage(frame?.VideoMediaFrame);
        if (softwareBitmap != null)
        {
            // Swap the processed frame to m_backBuffer and trigger UI thread to render it
            softwareBitmap = Interlocked.Exchange(ref m_backBuffer, softwareBitmap);

            // UI thread always reset m_backBuffer before using it.  Unused bitmap should be disposed.
            softwareBitmap?.Dispose();

            // Changes to xaml ImageElement must happen in UI thread through Dispatcher
            var task = m_imageElement.DispatcherQueue.TryEnqueue(
                async () =>
                {
                    // Don't let two copies of this task run at the same time.
                    if (_taskRunning)
                    {
                        return;
                    }
                    _taskRunning = true;

                    // Keep draining frames from the backbuffer until the backbuffer is empty.
                    SoftwareBitmap latestBitmap;
                    while ((latestBitmap = Interlocked.Exchange(ref m_backBuffer, null)) != null)
                    {
                        var imageSource = (SoftwareBitmapSource)m_imageElement.Source;
                        await imageSource.SetBitmapAsync(latestBitmap);
                        latestBitmap.Dispose();
                    }

                    _taskRunning = false;
                });
        }
    }



    // Function delegate that transforms a scanline from an input image to an output image.
    private unsafe delegate void TransformScanline(int pixelWidth, byte* inputRowBytes, byte* outputRowBytes);
    /// <summary>
    /// Determines the subtype to request from the MediaFrameReader that will result in
    /// a frame that can be rendered by ConvertToDisplayableImage.
    /// </summary>
    /// <returns>Subtype string to request, or null if subtype is not renderable.</returns>

    public static string GetSubtypeForFrameReader(MediaFrameSourceKind kind, MediaFrameFormat format)
    {
        // Note that media encoding subtypes may differ in case.
        // https://docs.microsoft.com/en-us/uwp/api/Windows.Media.MediaProperties.MediaEncodingSubtypes

        string subtype = format.Subtype;
        switch (kind)
        {
            // For color sources, we accept anything and request that it be converted to Bgra8.
            case MediaFrameSourceKind.Color:
                return Windows.Media.MediaProperties.MediaEncodingSubtypes.Bgra8;

            // The only depth format we can render is D16.
            case MediaFrameSourceKind.Depth:
                return String.Equals(subtype, Windows.Media.MediaProperties.MediaEncodingSubtypes.D16, StringComparison.OrdinalIgnoreCase) ? subtype : null;

            // The only infrared formats we can render are L8 and L16.
            case MediaFrameSourceKind.Infrared:
                return (String.Equals(subtype, Windows.Media.MediaProperties.MediaEncodingSubtypes.L8, StringComparison.OrdinalIgnoreCase) ||
                    String.Equals(subtype, Windows.Media.MediaProperties.MediaEncodingSubtypes.L16, StringComparison.OrdinalIgnoreCase)) ? subtype : null;

            // No other source kinds are supported by this class.
            default:
                return null;
        }
    }

    /// <summary>
    /// Converts a frame to a SoftwareBitmap of a valid format to display in an Image control.
    /// </summary>
    /// <param name="inputFrame">Frame to convert.</param>

    public static unsafe SoftwareBitmap ConvertToDisplayableImage(VideoMediaFrame inputFrame)
    {
        SoftwareBitmap result = null;
        using (var inputBitmap = inputFrame?.SoftwareBitmap)
        {
            if (inputBitmap != null)
            {
                switch (inputFrame.FrameReference.SourceKind)
                {
                    case MediaFrameSourceKind.Color:
                        // XAML requires Bgra8 with premultiplied alpha.
                        // We requested Bgra8 from the MediaFrameReader, so all that's
                        // left is fixing the alpha channel if necessary.
                        if (inputBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8)
                        {
                            System.Diagnostics.Debug.WriteLine("Color frame in unexpected format.");
                        }
                        else if (inputBitmap.BitmapAlphaMode == BitmapAlphaMode.Premultiplied)
                        {
                            // Already in the correct format.
                            result = SoftwareBitmap.Copy(inputBitmap);
                        }
                        else
                        {
                            // Convert to premultiplied alpha.
                            result = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
                        }
                        break;

                    case MediaFrameSourceKind.Depth:
                        // We requested D16 from the MediaFrameReader, so the frame should
                        // be in Gray16 format.
                        if (inputBitmap.BitmapPixelFormat == BitmapPixelFormat.Gray16)
                        {
                            // Use a special pseudo color to render 16 bits depth frame.
                            var depthScale = (float)inputFrame.DepthMediaFrame.DepthFormat.DepthScaleInMeters;
                            var minReliableDepth = inputFrame.DepthMediaFrame.MinReliableDepth;
                            var maxReliableDepth = inputFrame.DepthMediaFrame.MaxReliableDepth;
                            result = TransformBitmap(inputBitmap, (w, i, o) => PseudoColorHelper.PseudoColorForDepth(w, i, o, depthScale, minReliableDepth, maxReliableDepth));
                        }
                        else
                        {
                            System.Diagnostics.Debug.WriteLine("Depth frame in unexpected format.");
                        }
                        break;

                    case MediaFrameSourceKind.Infrared:
                        // We requested L8 or L16 from the MediaFrameReader, so the frame should
                        // be in Gray8 or Gray16 format. 
                        switch (inputBitmap.BitmapPixelFormat)
                        {
                            case BitmapPixelFormat.Gray16:
                                // Use pseudo color to render 16 bits frames.
                                result = TransformBitmap(inputBitmap, PseudoColorHelper.PseudoColorFor16BitInfrared);
                                break;

                            case BitmapPixelFormat.Gray8:
                                // Use pseudo color to render 8 bits frames.
                                result = TransformBitmap(inputBitmap, PseudoColorHelper.PseudoColorFor8BitInfrared);
                                break;
                            default:
                                System.Diagnostics.Debug.WriteLine("Infrared frame in unexpected format.");
                                break;
                        }
                        break;
                }
            }
        }

        return result;
    }



    /// <summary>
    /// Transform image into Bgra8 image using given transform method.
    /// </summary>
    /// <param name="softwareBitmap">Input image to transform.</param>
    /// <param name="transformScanline">Method to map pixels in a scanline.</param>

    private static unsafe SoftwareBitmap TransformBitmap(SoftwareBitmap softwareBitmap, TransformScanline transformScanline)
    {
        // XAML Image control only supports premultiplied Bgra8 format.
        var outputBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8,
            softwareBitmap.PixelWidth, softwareBitmap.PixelHeight, BitmapAlphaMode.Premultiplied);

        using (var input = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
        using (var output = outputBitmap.LockBuffer(BitmapBufferAccessMode.Write))
        {
            // Get stride values to calculate buffer position for a given pixel x and y position.
            int inputStride = input.GetPlaneDescription(0).Stride;
            int outputStride = output.GetPlaneDescription(0).Stride;
            int pixelWidth = softwareBitmap.PixelWidth;
            int pixelHeight = softwareBitmap.PixelHeight;

            using (var outputReference = output.CreateReference())
            using (var inputReference = input.CreateReference())
            {
                // Get input and output byte access buffers.
                byte* inputBytes;
                uint inputCapacity;
                ((IMemoryBufferByteAccess)inputReference).GetBuffer(out inputBytes, out inputCapacity);
                byte* outputBytes;
                uint outputCapacity;
                ((IMemoryBufferByteAccess)outputReference).GetBuffer(out outputBytes, out outputCapacity);

                // Iterate over all pixels and store converted value.
                for (int y = 0; y < pixelHeight; y++)
                {
                    byte* inputRowBytes = inputBytes + y * inputStride;
                    byte* outputRowBytes = outputBytes + y * outputStride;

                    transformScanline(pixelWidth, inputRowBytes, outputRowBytes);
                }
            }
        }

        return outputBitmap;
    }



    /// <summary>
    /// A helper class to manage look-up-table for pseudo-colors.
    /// </summary>

    private static class PseudoColorHelper
    {
        #region Constructor, private members and methods

        private const int TableSize = 1024;   // Look up table size
        private static readonly uint[] PseudoColorTable;
        private static readonly uint[] InfraredRampTable;

        // Color palette mapping value from 0 to 1 to blue to red colors.
        private static readonly Color[] ColorRamp =
        {
            Color.FromArgb(a:0xFF, r:0x7F, g:0x00, b:0x00),
            Color.FromArgb(a:0xFF, r:0xFF, g:0x00, b:0x00),
            Color.FromArgb(a:0xFF, r:0xFF, g:0x7F, b:0x00),
            Color.FromArgb(a:0xFF, r:0xFF, g:0xFF, b:0x00),
            Color.FromArgb(a:0xFF, r:0x7F, g:0xFF, b:0x7F),
            Color.FromArgb(a:0xFF, r:0x00, g:0xFF, b:0xFF),
            Color.FromArgb(a:0xFF, r:0x00, g:0x7F, b:0xFF),
            Color.FromArgb(a:0xFF, r:0x00, g:0x00, b:0xFF),
            Color.FromArgb(a:0xFF, r:0x00, g:0x00, b:0x7F),
        };

        static PseudoColorHelper()
        {
            PseudoColorTable = InitializePseudoColorLut();
            InfraredRampTable = InitializeInfraredRampLut();
        }

        /// <summary>
        /// Maps an input infrared value between [0, 1] to corrected value between [0, 1].
        /// </summary>
        /// <param name="value">Input value between [0, 1].</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]  // Tell the compiler to inline this method to improve performance

        private static uint InfraredColor(float value)
        {
            int index = (int)(value * TableSize);
            index = index < 0 ? 0 : index > TableSize - 1 ? TableSize - 1 : index;
            return InfraredRampTable[index];
        }

        /// <summary>
        /// Initializes the pseudo-color look up table for infrared pixels
        /// </summary>

        private static uint[] InitializeInfraredRampLut()
        {
            uint[] lut = new uint[TableSize];
            for (int i = 0; i < TableSize; i++)
            {
                var value = (float)i / TableSize;
                // Adjust to increase color change between lower values in infrared images

                var alpha = (float)Math.Pow(1 - value, 12);
                lut[i] = ColorRampInterpolation(alpha);
            }

            return lut;
        }



        /// <summary>
        /// Initializes pseudo-color look up table for depth pixels
        /// </summary>
        private static uint[] InitializePseudoColorLut()
        {
            uint[] lut = new uint[TableSize];
            for (int i = 0; i < TableSize; i++)
            {
                lut[i] = ColorRampInterpolation((float)i / TableSize);
            }

            return lut;
        }



        /// <summary>
        /// Maps a float value to a pseudo-color pixel
        /// </summary>
        private static uint ColorRampInterpolation(float value)
        {
            // Map value to surrounding indexes on the color ramp
            int rampSteps = ColorRamp.Length - 1;
            float scaled = value * rampSteps;
            int integer = (int)scaled;
            int index =
                integer < 0 ? 0 :
                integer >= rampSteps - 1 ? rampSteps - 1 :
                integer;

            Color prev = ColorRamp[index];
            Color next = ColorRamp[index + 1];

            // Set color based on ratio of closeness between the surrounding colors
            uint alpha = (uint)((scaled - integer) * 255);
            uint beta = 255 - alpha;
            return
                ((prev.A * beta + next.A * alpha) / 255) << 24 | // Alpha
                ((prev.R * beta + next.R * alpha) / 255) << 16 | // Red
                ((prev.G * beta + next.G * alpha) / 255) << 8 |  // Green
                ((prev.B * beta + next.B * alpha) / 255);        // Blue
        }


        /// <summary>
        /// Maps a value in [0, 1] to a pseudo RGBA color.
        /// </summary>
        /// <param name="value">Input value between [0, 1].</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]

        private static uint PseudoColor(float value)
        {
            int index = (int)(value * TableSize);
            index = index < 0 ? 0 : index > TableSize - 1 ? TableSize - 1 : index;
            return PseudoColorTable[index];
        }

        #endregion

        /// <summary>
        /// Maps each pixel in a scanline from a 16 bit depth value to a pseudo-color pixel.
        /// </summary>
        /// <param name="pixelWidth">Width of the input scanline, in pixels.</param>
        /// <param name="inputRowBytes">Pointer to the start of the input scanline.</param>
        /// <param name="outputRowBytes">Pointer to the start of the output scanline.</param>
        /// <param name="depthScale">Physical distance that corresponds to one unit in the input scanline.</param>
        /// <param name="minReliableDepth">Shortest distance at which the sensor can provide reliable measurements.</param>
        /// <param name="maxReliableDepth">Furthest distance at which the sensor can provide reliable measurements.</param>

        public static unsafe void PseudoColorForDepth(int pixelWidth, byte* inputRowBytes, byte* outputRowBytes, float depthScale, float minReliableDepth, float maxReliableDepth)
        {
            // Visualize space in front of your desktop.
            float minInMeters = minReliableDepth * depthScale;
            float maxInMeters = maxReliableDepth * depthScale;
            float one_min = 1.0f / minInMeters;
            float range = 1.0f / maxInMeters - one_min;

            ushort* inputRow = (ushort*)inputRowBytes;
            uint* outputRow = (uint*)outputRowBytes;

            for (int x = 0; x < pixelWidth; x++)
            {
                var depth = inputRow[x] * depthScale;

                if (depth == 0)
                {
                    // Map invalid depth values to transparent pixels.
                    // This happens when depth information cannot be calculated, e.g. when objects are too close.
                    outputRow[x] = 0;
                }
                else
                {
                    var alpha = (1.0f / depth - one_min) / range;
                    outputRow[x] = PseudoColor(alpha * alpha);
                }
            }
        }



        /// <summary>
        /// Maps each pixel in a scanline from a 8 bit infrared value to a pseudo-color pixel.
        /// </summary>
        /// /// <param name="pixelWidth">Width of the input scanline, in pixels.</param>
        /// <param name="inputRowBytes">Pointer to the start of the input scanline.</param>
        /// <param name="outputRowBytes">Pointer to the start of the output scanline.</param>

        public static unsafe void PseudoColorFor8BitInfrared(
            int pixelWidth, byte* inputRowBytes, byte* outputRowBytes)
        {
            byte* inputRow = inputRowBytes;
            uint* outputRow = (uint*)outputRowBytes;

            for (int x = 0; x < pixelWidth; x++)
            {
                outputRow[x] = InfraredColor(inputRow[x] / (float)Byte.MaxValue);
            }
        }

        /// <summary>
        /// Maps each pixel in a scanline from a 16 bit infrared value to a pseudo-color pixel.
        /// </summary>
        /// <param name="pixelWidth">Width of the input scanline.</param>
        /// <param name="inputRowBytes">Pointer to the start of the input scanline.</param>
        /// <param name="outputRowBytes">Pointer to the start of the output scanline.</param>

        public static unsafe void PseudoColorFor16BitInfrared(int pixelWidth, byte* inputRowBytes, byte* outputRowBytes)
        {
            ushort* inputRow = (ushort*)inputRowBytes;
            uint* outputRow = (uint*)outputRowBytes;

            for (int x = 0; x < pixelWidth; x++)
            {
                outputRow[x] = InfraredColor(inputRow[x] / (float)UInt16.MaxValue);
            }
        }
    }


    // Displays the provided softwareBitmap in a XAML image control.
    public void PresentSoftwareBitmap(SoftwareBitmap softwareBitmap)
    {
        if (softwareBitmap != null)
        {
            // Swap the processed frame to m_backBuffer and trigger UI thread to render it
            softwareBitmap = Interlocked.Exchange(ref m_backBuffer, softwareBitmap);

            // UI thread always reset m_backBuffer before using it.  Unused bitmap should be disposed.
            softwareBitmap?.Dispose();

            // Changes to xaml ImageElement must happen in UI thread through Dispatcher
            var task = m_imageElement.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                async () =>
                {
                    // Don't let two copies of this task run at the same time.
                    if (_taskRunning)
                    {
                        return;
                    }
                    _taskRunning = true;

                    // Keep draining frames from the backbuffer until the backbuffer is empty.
                    SoftwareBitmap latestBitmap;
                    while ((latestBitmap = Interlocked.Exchange(ref m_backBuffer, null)) != null)
                    {
                        var imageSource = (SoftwareBitmapSource)m_imageElement.Source;
                        await imageSource.SetBitmapAsync(latestBitmap);
                        latestBitmap.Dispose();
                    }

                    _taskRunning = false;
                });
        }
    }
}

Verwenden von MultiSourceMediaFrameReader zum Abrufen zeitkorrelierter Frames aus mehreren Quellen

Ab Windows 10, Version 1607, können Sie MultiSourceMediaFrameReader verwenden, um zeitkorrelierte Frames aus mehreren Quellen zu empfangen. Diese API erleichtert die Verarbeitung, die Frames aus mehreren Quellen erfordert, die in enger zeitlicher Nähe aufgenommen wurden, z. B. mit der DepthCorrelatedCoordinateMapper Klasse. Eine Einschränkung der Verwendung dieser neuen Methode besteht darin, dass Frame-eintreffende Ereignisse nur mit der Geschwindigkeit der langsamsten Aufnahmequelle ausgelöst werden. Zusätzliche Frames aus schnelleren Quellen werden gelöscht. Da das System davon ausgeht, dass Frames aus unterschiedlichen Quellen mit unterschiedlichen Geschwindigkeiten eingehen, erkennt es nicht automatisch, ob eine Quelle das Generieren von Frames vollständig gestoppt hat. Im Beispielcode in diesem Abschnitt wird gezeigt, wie Sie mithilfe eines Ereignisses eine eigene Timeout-Logik erstellen, die aufgerufen wird, wenn korrelierte Frames nicht innerhalb eines anwendungsspezifischen Zeitlimits eingehen.

Die Schritte zur Verwendung von MultiSourceMediaFrameReader ähneln den Schritten, die weiter oben in diesem Artikel zur Verwendung von MediaFrameReader beschrieben wurden. In diesem Beispiel wird eine Farbquelle und eine Tiefenquelle verwendet. Deklarieren Sie einige Zeichenfolgenvariablen, um die Medienframequellen-IDs zu speichern, die zum Auswählen von Frames aus jeder Quelle verwendet werden. Deklarieren Sie als Nächstes ein ManualResetEventSlim-, ein CancellationTokenSource-und ein EventHandler-, das zum Implementieren von Timeoutlogik für das Beispiel verwendet wird.

private MultiSourceMediaFrameReader m_multiFrameReader = null;
private string m_colorSourceId = null;
private string m_depthSourceId = null;


private readonly ManualResetEventSlim m_frameReceived = new ManualResetEventSlim(false);
private readonly CancellationTokenSource m_tokenSource = new CancellationTokenSource();
public event EventHandler CorrelationFailed;

Mit den in diesem Artikel beschriebenen Techniken können Sie nach einer MediaFrameSourceGroup abfragen, die die für dieses Beispielszenario erforderlichen Farb- und Tiefenquellen enthält. Rufen Sie nach dem Auswählen der gewünschten Framequellgruppe die MediaFrameSourceInfo- für jede einzelne Framequelle ab.

var allGroups = await MediaFrameSourceGroup.FindAllAsync();
var eligibleGroups = allGroups.Select(g => new
{
    Group = g,

    // For each source kind, find the source which offers that kind of media frame,
    // or null if there is no such source.
    SourceInfos = new MediaFrameSourceInfo[]
    {
        g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Color),
        g.SourceInfos.FirstOrDefault(info => info.SourceKind == MediaFrameSourceKind.Depth)
    }
}).Where(g => g.SourceInfos.Any(info => info != null)).ToList();

if (eligibleGroups.Count == 0)
{
    System.Diagnostics.Debug.WriteLine("No source group with color, depth or infrared found.");
    return;
}

var selectedGroupIndex = 0; // Select the first eligible group
MediaFrameSourceGroup selectedGroup = eligibleGroups[selectedGroupIndex].Group;
MediaFrameSourceInfo colorSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[0];
MediaFrameSourceInfo depthSourceInfo = eligibleGroups[selectedGroupIndex].SourceInfos[1];

Erstellen und initialisieren Sie ein MediaCapture--Objekt, und übergeben Sie die ausgewählte Framequellgruppe in den Initialisierungseinstellungen.

m_mediaCapture = new MediaCapture();

var settings = new MediaCaptureInitializationSettings()
{
    SourceGroup = selectedGroup,
    SharingMode = MediaCaptureSharingMode.ExclusiveControl,
    MemoryPreference = MediaCaptureMemoryPreference.Cpu,
    StreamingCaptureMode = StreamingCaptureMode.Video
};

await m_mediaCapture.InitializeAsync(settings);

Rufen Sie nach dem Initialisieren des MediaCapture--Objekts die MediaFrameSource---Objekte für die Farb- und Tiefenkameras ab. Speichern Sie die ID für jede Quelle, damit Sie den eingehenden Frame für die entsprechende Quelle auswählen können.

MediaFrameSource colorSource =
    m_mediaCapture.FrameSources.Values.FirstOrDefault(
        s => s.Info.SourceKind == MediaFrameSourceKind.Color);

MediaFrameSource depthSource =
    m_mediaCapture.FrameSources.Values.FirstOrDefault(
        s => s.Info.SourceKind == MediaFrameSourceKind.Depth);

if (colorSource == null || depthSource == null)
{
    System.Diagnostics.Debug.WriteLine("MediaCapture doesn't have the Color and Depth streams");
    return;
}

m_colorSourceId = colorSource.Info.Id;
m_depthSourceId = depthSource.Info.Id;

Erstellen und initialisieren Sie den MultiSourceMediaFrameReader, indem Sie CreateMultiSourceFrameReaderAsync aufrufen und ein Array von Rahmenquellen übergeben, die vom Leser verwendet werden. Registrieren Sie einen Ereignishandler für das FrameArrived--Ereignis. In diesem Beispiel wird eine Instanz der FrameRenderer Hilfsklasse erstellt, die zuvor in diesem Artikel beschrieben wurde, um Frames für ein Image--Steuerelement zu rendern. Starten Sie den Frame-Reader, indem Sie StartAsyncaufrufen.

Registrieren Sie einen Ereignishandler für das zuvor im Beispiel deklarierte CorellationFailed--Ereignis. Dieses Ereignis wird signalisiert, wenn eine der verwendeten Medienframequellen die Erstellung von Frames beendet. Rufen Sie schließlich Task.Run auf, um die Timeout-Hilfsmethode NotifyAboutCorrelationFailurein einem separaten Thread aufzurufen. Die Implementierung dieser Methode wird weiter unten in diesem Artikel gezeigt.

m_multiFrameReader = await m_mediaCapture.CreateMultiSourceFrameReaderAsync(
    new[] { colorSource, depthSource });

m_multiFrameReader.FrameArrived += MultiFrameReader_FrameArrived;

m_frameRenderer = new FrameRenderer(iFrameReaderImageControl);

MultiSourceMediaFrameReaderStartStatus startStatus =
    await m_multiFrameReader.StartAsync();

if (startStatus != MultiSourceMediaFrameReaderStartStatus.Success)
{
    throw new InvalidOperationException(
        "Unable to start reader: " + startStatus);
}

this.CorrelationFailed += MainWindow_CorrelationFailed;
Task.Run(() => NotifyAboutCorrelationFailure(m_tokenSource.Token));

Das FrameArrived--Ereignis wird ausgelöst, wann immer ein neuer Frame aus allen Medienrahmenquellen verfügbar ist, die vom MultiSourceMediaFrameReaderverwaltet werden. Dies bedeutet, dass das Ereignis im Takt der langsamsten Medienquelle ausgelöst wird. Wenn eine Quelle mehrere Frames in der Zeit erzeugt, in der eine langsamere Quelle einen Frame erzeugt, werden die zusätzlichen Frames aus der schnellen Quelle gelöscht.

Rufen Sie die MultiSourceMediaFrameReference- auf, das dem Ereignis zugeordnet ist, indem Sie TryAcquireLatestFrame-aufrufen. Rufen Sie die MediaFrameReference- ab, die jeder Medienframequelle zugeordnet ist, indem Sie TryGetFrameReferenceBySourceIdaufrufen und die ID-Zeichenfolgen übergeben, die beim Initialisieren des Framelesers gespeichert wurden.

Rufen Sie die Methode Set des Objekts ManualResetEventSlim auf, um zu signalisieren, dass die Frames eingetroffen sind. Wir werden dieses Ereignis in der Methode NotifyCorrelationFailure überprüfen, die in einem separaten Thread läuft.

Führen Sie schließlich alle Verarbeitungen für zeitkorrelierte Medienframes durch. In diesem Beispiel wird einfach der Frame aus der Tiefenquelle angezeigt.

private void MultiFrameReader_FrameArrived(MultiSourceMediaFrameReader sender, MultiSourceMediaFrameArrivedEventArgs args)
{
    using (MultiSourceMediaFrameReference muxedFrame =
        sender.TryAcquireLatestFrame())
    using (MediaFrameReference colorFrame =
        muxedFrame.TryGetFrameReferenceBySourceId(m_colorSourceId))
    using (MediaFrameReference depthFrame =
        muxedFrame.TryGetFrameReferenceBySourceId(m_depthSourceId))
    {
        // Notify the listener thread that the frame has been received.
        m_frameReceived.Set();
        m_frameRenderer.ProcessFrame(depthFrame);
    }
}

Die NotifyCorrelationFailure Hilfsmethode wurde auf einem separaten Thread ausgeführt, nachdem der Frame-Leser gestartet wurde. Überprüfen Sie in dieser Methode, ob das frame received-Ereignis signalisiert wurde. Denken Sie daran, dass im FrameArrived--Handler dieses Ereignis ausgelöst wird, wenn eine Reihe korrelierter Frames eintrifft. Wenn das Ereignis für einen bestimmten app-definierten Zeitraum nicht signalisiert wurde - 5 Sekunden ist ein angemessener Wert - und die Aufgabe wurde nicht mit dem CancellationTokenabgebrochen, dann ist es wahrscheinlich, dass eine der Medienframequellen das Lesen von Frames beendet hat. In diesem Fall möchten Sie in der Regel den Frame-Leser herunterfahren. Lösen Sie daher das app-definierte CorrelationFailed-Ereignis aus. Im Handler für dieses Ereignis können Sie den Framereader beenden und die zugeordneten Ressourcen bereinigen, wie zuvor in diesem Artikel gezeigt.

private void NotifyAboutCorrelationFailure(CancellationToken token)
{
    // If in 5 seconds the token is not cancelled and frame event is not signaled,
    // correlation is most likely failed.
    if (WaitHandle.WaitAny(new[] { token.WaitHandle, m_frameReceived.WaitHandle }, 5000)
            == WaitHandle.WaitTimeout)
    {
        CorrelationFailed?.Invoke(this, EventArgs.Empty);
    }
}
private async void MainWindow_CorrelationFailed(object sender, EventArgs e)
{
    await m_multiFrameReader.StopAsync();
    m_multiFrameReader.FrameArrived -= MultiFrameReader_FrameArrived;
    m_mediaCapture.Dispose();
    m_mediaCapture = null;
}

Verwenden Sie den gepufferten Frame-Erfassungsmodus zur Erhaltung der Sequenz der erfassten Frames.

Ab Windows 10, Version 1709, können Sie die Eigenschaft AcquisitionMode eines MediaFrameReader oder MultiSourceMediaFrameReader auf Gepuffert festlegen, um die von der Framequelle an Ihre App übergebene Sequenz von Frames beizubehalten.

m_mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;

Im Standard-Erfassungsmodus Realtime, wenn mehrere Frames aus der Quelle abgerufen werden, während Ihre App noch das FrameArrived Ereignis für einen vorherigen Frame verarbeitet, sendet das System Ihrer App den zuletzt erfassten Frame und verwirft zusätzliche Frames, die im Puffer warten. Dadurch erhält Ihre App jederzeit den aktuellsten verfügbaren Frame. Dies ist in der Regel der nützlichste Modus für Echtzeit-Computer-Vision-Anwendungen.

Im gepufferten Erfassungsmodus behält das System alle Frames im Puffer und stellt sie Ihrer App über das FrameArrived Ereignis in der empfangenen Reihenfolge bereit. Beachten Sie, dass in diesem Modus das System die Erfassung neuer Frames beendet, wenn der Puffer für Frames gefüllt ist. Es werden erst dann wieder neue Frames erfasst, wenn die App das FrameArrived- Ereignis für vorherige Frames abgeschlossen hat und dadurch mehr Speicherplatz im Puffer freigibt.

Verwenden Sie MediaSource, um Frames in einem MediaPlayerElement anzuzeigen

Ab Windows-Version 1709 können Sie Frames, die von einem MediaFrameReader bezogen wurden, direkt in einem MediaPlayerElement--Steuerelement auf Ihrer XAML-Seite anzeigen. Dies wird mithilfe der MediaSource.CreateFromMediaFrameSource erreicht, um MediaSource--Objekt zu erstellen, das direkt von einem MediaPlayer- verwendet werden kann, das einem MediaPlayerElement-zugeordnet ist. Ausführliche Informationen zum Arbeiten mit MediaPlayer und MediaPlayerElement finden Sie unter Wiedergeben von Audio und Video mit MediaPlayer.

Die folgenden Codebeispiele zeigen eine einfache Implementierung, die die Frames von einer Frontkamera und Rückkamera gleichzeitig auf einer XAML-Seite anzeigt.

Fügen Sie zunächst zwei Steuerelemente MediaPlayerElement zu Ihrer XAML-Seite hinzu.

<MediaPlayerElement x:Name="mediaPlayerElement1" Width="320" Height="240"/>
<MediaPlayerElement x:Name="mediaPlayerElement2" Width="320" Height="240"/>

Wählen Sie als Nächstes mithilfe der in den vorherigen Abschnitten dieses Artikels dargestellten Techniken eine MediaFrameSourceGroup aus, die MediaFrameSourceInfo Objekte für Farbkameras auf der Vorder- und Rückseite enthält. Beachten Sie, dass der MediaPlayer Frames aus nicht farbigen Formaten, wie z. B. Tiefen- oder Infrarotdaten, nicht automatisch in Farbdaten konvertiert. Die Verwendung anderer Sensortypen kann zu unerwarteten Ergebnissen führen.

var allGroups = await MediaFrameSourceGroup.FindAllAsync();
var eligibleGroups = allGroups.Select(g => new
{
    Group = g,

    // For each source kind, find the source which offers that kind of media frame,
    // or null if there is no such source.
    SourceInfos = new MediaFrameSourceInfo[]
    {
        g.SourceInfos.FirstOrDefault(info => info.DeviceInformation?.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front
            && info.SourceKind == MediaFrameSourceKind.Color),
        g.SourceInfos.FirstOrDefault(info => info.DeviceInformation?.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Back
            && info.SourceKind == MediaFrameSourceKind.Color)
    }
}).Where(g => g.SourceInfos.Any(info => info != null)).ToList();

if (eligibleGroups.Count == 0)
{
    System.Diagnostics.Debug.WriteLine("No source group with front and back-facing camera found.");
    return;
}

var selectedGroupIndex = 0; // Select the first eligible group
MediaFrameSourceGroup selectedGroup = eligibleGroups[selectedGroupIndex].Group;
MediaFrameSourceInfo frontSourceInfo = selectedGroup.SourceInfos[0];
MediaFrameSourceInfo backSourceInfo = selectedGroup.SourceInfos[1];

Initialisieren Sie das MediaCapture Objekt, um die ausgewählte MediaFrameSourceGroupzu verwenden.

m_mediaCapture = new MediaCapture();

var settings = new MediaCaptureInitializationSettings()
{
    SourceGroup = selectedGroup,
    SharingMode = MediaCaptureSharingMode.ExclusiveControl,
    MemoryPreference = MediaCaptureMemoryPreference.Cpu,
    StreamingCaptureMode = StreamingCaptureMode.Video
};
try
{
    await m_mediaCapture.InitializeAsync(settings);
}
catch (Exception ex)
{
    System.Diagnostics.Debug.WriteLine("MediaCapture initialization failed: " + ex.Message);
    return;
}

Rufen Sie schließlich MediaSource.CreateFromMediaFrameSource auf, um eine MediaSource- für jede Framequelle zu erstellen, indem Sie die Id-Eigenschaft des zugeordneten MediaFrameSourceInfo--Objekts verwenden, um eine der Framequellen in der MediaCapture FrameSources--Auflistung des objekts auszuwählen. Initialisieren Sie ein neues MediaPlayer--Objekt und weisen Sie es einem MediaPlayerElement- zu, indem Sie SetMediaPlayer-aufrufen. Legen Sie dann die eigenschaft Source auf das neu erstellte MediaSource-Objekt fest.

var frameMediaSource1 = MediaSource.CreateFromMediaFrameSource(m_mediaCapture.FrameSources[frontSourceInfo.Id]);
mediaPlayerElement1.SetMediaPlayer(new Windows.Media.Playback.MediaPlayer());
mediaPlayerElement1.MediaPlayer.Source = frameMediaSource1;
mediaPlayerElement1.AutoPlay = true;

var frameMediaSource2 = MediaSource.CreateFromMediaFrameSource(m_mediaCapture.FrameSources[backSourceInfo.Id]);
mediaPlayerElement2.SetMediaPlayer(new Windows.Media.Playback.MediaPlayer());
mediaPlayerElement2.MediaPlayer.Source = frameMediaSource2;
mediaPlayerElement2.AutoPlay = true;

Verwenden von Videoprofilen zum Auswählen einer Framequelle

Ein Kameraprofil, dargestellt durch ein MediaCaptureVideoProfile -Objekt, stellt eine Reihe von Funktionen dar, die ein bestimmtes Aufnahmegerät bereitstellt, z. B. Bildfrequenzen, Auflösungen oder erweiterte Features wie HDR-Aufnahme. Ein Aufnahmegerät unterstützt möglicherweise mehrere Profile, sodass Sie das für Ihr Aufnahmeszenario optimierte auswählen können. Ab Windows 10, Version 1803, können Sie MediaCaptureVideoProfile verwenden, um eine Medienframequelle mit bestimmten Funktionen auszuwählen, bevor Sie das MediaCapture-Objekt initialisieren. Der folgende Beispielcode sucht nach einem Videoprofil, das HDR mit Wide Color Gamut (WCG) unterstützt, und gibt ein MediaCaptureInitializationSettings -Objekt zurück, das zum Initialisieren der MediaCapture zum Verwenden des ausgewählten Geräts und Profils verwendet werden kann.

Rufen Sie zunächst MediaFrameSourceGroup.FindAllAsync auf, um eine Liste aller Medienquellgruppen zu erhalten, die auf dem aktuellen Gerät verfügbar sind. Durchlaufen Sie jede Quellgruppe, und rufen Sie MediaCapture.FindKnownVideoProfiles auf, um eine Liste aller Videoprofile für die aktuelle Quellgruppe abzurufen, die das angegebene Profil unterstützen, in diesem Fall HDR mit WCG-Foto. Wenn ein Profil gefunden wird, das den Kriterien entspricht, erstellen Sie ein neues MediaCaptureInitializationSettings Objekt und legen Sie das VideoProfile auf das ausgewählte Profil und die VideoDeviceId auf die ID-Eigenschaft der aktuellen Medien-Frame-Quellgruppe fest.

IReadOnlyList<MediaFrameSourceGroup> sourceGroups = await MediaFrameSourceGroup.FindAllAsync();
MediaCaptureInitializationSettings settings = null;

foreach (MediaFrameSourceGroup sourceGroup in sourceGroups)
{
    // Find a device that support AdvancedColorPhoto
    IReadOnlyList<MediaCaptureVideoProfile> profileList = MediaCapture.FindKnownVideoProfiles(
                                  sourceGroup.Id,
                                  KnownVideoProfile.HdrWithWcgPhoto);

    if (profileList.Count > 0)
    {
        settings = new MediaCaptureInitializationSettings();
        settings.VideoProfile = profileList[0];
        settings.VideoDeviceId = sourceGroup.Id;
        break;
    }
}

Weitere Informationen zur Verwendung von Kameraprofilen finden Sie unter Kameraprofile.