Partager via


Traiter des trames multimédias avec MediaFrameReader

Cet article vous montre comment utiliser un MediaFrameReader avec MediaCapture pour obtenir des images multimédias à partir d’une ou plusieurs sources disponibles, notamment des caméras couleur, profondeur et infrarouge, des appareils audio ou même des sources d’images personnalisées telles que celles qui produisent des images de suivi squelette. Cette fonctionnalité est conçue pour être utilisée par les applications qui effectuent un traitement en temps réel d’images multimédias, telles que la réalité augmentée et les applications de caméra prenant en charge la profondeur.

Si vous souhaitez simplement capturer des vidéos ou des photos, comme une application de photographie classique, vous souhaitez probablement utiliser l’une des autres techniques de capture prises en charge par MediaCapture. Pour obtenir la liste des techniques de capture multimédia disponibles et des articles montrant comment les utiliser, consultez Caméra.

Remarque

Les fonctionnalités décrites dans cet article sont disponibles uniquement à partir de Windows 10 version 1607.

Remarque

Il existe un exemple d’application Windows universelle qui illustre l’utilisation de MediaFrameReader pour afficher des images provenant de différentes sources d’images, notamment des caméras couleur, profondeur et infrarouge. Pour plus d'informations, consultez l'exemple de trames de caméra .

Remarque

Un nouvel ensemble d’API permettant d’utiliser MediaFrameReader avec des données audio ont été introduites dans Windows 10, version 1803. Pour plus d’informations, consultez Traiter des trames audio avec MediaFrameReader.

Sélectionner des sources d’images et des groupes de sources d’images

De nombreuses applications qui traitent les images multimédias doivent obtenir des images à partir de plusieurs sources à la fois, telles que les caméras de couleur et de profondeur d’un appareil. L’objet MediaFrameSourceGroup représente un ensemble de sources de trames multimédias qui peuvent être utilisées simultanément. Appelez la méthode statique MediaFrameSourceGroup.FindAllAsync pour obtenir la liste de tous les groupes de sources d’images prises en charge par l’appareil actuel.

var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();

Vous pouvez également créer un DeviceWatcher à l’aide de DeviceInformation.CreateWatcher et la valeur retournée par MediaFrameSourceGroup.GetDeviceSelector pour recevoir des notifications lorsque les groupes sources d’images disponibles sur l’appareil changent, par exemple lorsqu’une caméra externe est branchée. Pour plus d’informations, consultez Énumération des appareils.

Un MediaFrameSourceGroup possède une collection d’objets MediaFrameSourceInfo qui décrivent les sources d’images contenues dans le groupe. Après avoir récupéré les groupes de sources d’images disponibles sur l’appareil, vous pouvez sélectionner le groupe qui expose les sources d’images qui vous intéressent.

L’exemple suivant montre la façon la plus simple de sélectionner un groupe source d’images. Ce code effectue simplement une boucle sur tous les groupes disponibles, puis effectue une boucle sur chaque élément de la collection SourceInfos . Chaque MediaFrameSourceInfo est vérifié pour voir s’il prend en charge les fonctionnalités que nous recherchons. Dans ce cas, la propriété MediaStreamType est vérifiée pour la valeur VideoPreview, ce qui signifie que l’appareil fournit un flux d’aperçu vidéo et que la propriété SourceKind est vérifiée pour la valeur Color, indiquant que la source fournit des images de couleur.

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;
    }
}

Cette méthode d’identification du groupe source d’images souhaité et des sources d’images fonctionne pour des cas simples, mais si vous souhaitez sélectionner des sources d’images basées sur des critères plus complexes, elle peut rapidement devenir fastidieuse. Une autre méthode consiste à utiliser la syntaxe Linq et les objets anonymes pour effectuer la sélection. L’exemple suivant utilise la méthode Select extension pour transformer les objets MediaFrameSourceGroup dans la liste frameSourceGroups en un objet anonyme avec deux champs : sourceGroup, représentant le groupe lui-même et colorSourceInfo, qui représente la source du cadre de couleur dans le groupe. Le champ colorSourceInfo est défini sur le résultat de FirstOrDefault, qui sélectionne le premier objet pour lequel le prédicat fourni s'évalue à vrai. Dans ce cas, le prédicat est vrai si le type de flux est VideoPreview, le type source est Color et si l’appareil photo se trouve sur le panneau frontal de l’appareil.

Dans la liste des objets anonymes retournés par la requête décrite ci-dessus, la méthode Where extension est utilisée pour sélectionner uniquement les objets où le champ colorSourceInfo n’est pas null. Enfin, FirstOrDefault est appelé pour sélectionner le premier élément de la liste.

Vous pouvez maintenant utiliser les champs de l’objet sélectionné pour obtenir des références au MediaFrameSourceGroup sélectionné et à l’objet MediaFrameSourceInfo représentant l’appareil photo couleur. Celles-ci seront utilisées ultérieurement pour initialiser l’objet MediaCapture et créer un MediaFrameReader pour la source sélectionnée. Enfin, vous devez tester si le groupe source est null, ce qui signifie que l’appareil actuel n’a pas vos sources de capture demandées.

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;
}

L’exemple suivant utilise une technique similaire comme décrit ci-dessus pour sélectionner un groupe source qui contient des caméras couleur, profondeur et infrarouge.

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];

Remarque

À compter de Windows 10, version 1803, vous pouvez utiliser la classe MediaCaptureVideoProfile pour sélectionner une source de trame multimédia avec un ensemble de fonctionnalités souhaitées. Pour plus d’informations, consultez la section Utiliser des profils vidéo pour sélectionner une source de trame plus loin dans cet article.

Initialiser l’objet MediaCapture pour utiliser le groupe source d’images sélectionné

L’étape suivante consiste à initialiser l’objet MediaCapture pour utiliser le groupe source d’images que vous avez sélectionné à l’étape précédente. Créez une instance de l’objet MediaCapture en appelant le constructeur. Ensuite, créez un objet MediaCaptureInitializationSettings qui sera utilisé pour initialiser l’objet MediaCapture . Dans cet exemple, les paramètres suivants sont utilisés :

  • SourceGroup : indique au système quel groupe source vous utiliserez pour obtenir des images. N’oubliez pas que le groupe source définit un ensemble de sources d’images multimédias qui peuvent être utilisées simultanément.
  • SharingMode : indique au système si vous avez besoin d’un contrôle exclusif sur les appareils sources de capture. Si vous définissez ce paramètre sur ExclusiveControl, cela signifie que vous pouvez modifier les paramètres de l’appareil de capture, tels que le format des images qu’il produit, mais cela signifie que si une autre application a déjà un contrôle exclusif, votre application échoue lorsqu’elle tente d’initialiser l’appareil de capture multimédia. Si vous définissez cette valeur sur SharedReadOnly, vous pouvez recevoir des images à partir des sources d’images même si elles sont utilisées par une autre application, mais vous ne pouvez pas modifier les paramètres des appareils.
  • MemoryPreference - Si vous spécifiez processeur, le système utilise la mémoire processeur qui garantit que lorsque des images arrivent, elles seront disponibles en tant qu’objets SoftwareBitmap. Si vous spécifiez Auto, le système choisit dynamiquement l’emplacement de mémoire optimal pour stocker les trames. Si le système choisit d’utiliser la mémoire GPU, les trames multimédias arrivent en tant qu’objet IDirect3DSurface et non en tant qu’objet SoftwareBitmap.
  • StreamingCaptureMode : définissez cette valeur sur vidéo pour indiquer que l’audio n’a pas besoin d’être diffusé en continu.

Appelez InitializeAsync pour initialiser MediaCapture avec vos paramètres souhaités. Veillez à l’appeler dans un bloc try au cas où l’initialisation échouerait.

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;
}

Définir le format préféré pour la source de trame

Pour définir le format préféré d’une source de trame, vous devez obtenir un objet MediaFrameSource représentant la source. Vous obtenez cet objet en accédant au dictionnaire Frames de l’objet MediaCapture initialisé, en spécifiant l’identificateur de la source de trame que vous souhaitez utiliser. C’est pourquoi nous avons enregistré l’objet MediaFrameSourceInfo lors de la sélection d’un groupe source d’images.

La propriété MediaFrameSource.SupportedFormats contient une liste d’objets MediaFrameFormat décrivant les formats pris en charge pour la source d’images. Dans cet exemple, un format est sélectionné avec une largeur de 1 080 pixels et peut fournir des images au format RVB 32 bits. La méthode d’extension FirstOrDefault sélectionne la première entrée dans la liste. Si le format sélectionné est null, le format demandé n’est pas pris en charge par la source de trame. Si le format est pris en charge, vous pouvez demander que la source utilise ce format en appelant SetFormatAsync.

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);

Créer un lecteur d’images pour la source d’images

Pour recevoir des images pour une source d’images multimédias, utilisez un MediaFrameReader.

MediaFrameReader m_mediaFrameReader;

Instanciez le lecteur d’images en appelant CreateFrameReaderAsync sur votre objet MediaCapture initialisé. Le premier argument de cette méthode est la source d’images à partir de laquelle vous souhaitez recevoir des images. Vous pouvez créer un lecteur d’images distinct pour chaque source d’images que vous souhaitez utiliser. Le deuxième argument indique au système le format de sortie dans lequel vous souhaitez que les images arrivent. Cela peut vous éviter d’avoir à effectuer vos propres conversions en images à mesure qu’elles arrivent. Notez que si vous spécifiez un format qui n'est pas pris en charge par la source de cadre, une exception sera levée, assurez-vous donc que cette valeur se trouve dans la collection SupportedFormats.

Après avoir créé le lecteur d’images, inscrivez un gestionnaire pour l’événement FrameArrived qui est déclenché chaque fois qu’une nouvelle image est disponible à partir de la source.

Indiquez au système de commencer à lire des images à partir de la source en appelant StartAsync.

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

Gérer l’événement frame arrivé

L’événement MediaFrameReader.FrameArrived est déclenché chaque fois qu’une nouvelle image est disponible. Vous pouvez choisir de traiter chaque image qui arrive ou utilise uniquement des images quand vous en avez besoin. Étant donné que le lecteur d’images déclenche l’événement sur son propre thread, vous devrez peut-être implémenter une logique de synchronisation pour vous assurer que vous ne tentez pas d’accéder aux mêmes données à partir de plusieurs threads. Cette section vous montre comment synchroniser des cadres de couleur de dessin avec un contrôle d’image dans une page XAML. Ce scénario traite de la contrainte de synchronisation supplémentaire qui nécessite que toutes les mises à jour des contrôles XAML soient effectuées sur le thread d’interface utilisateur.

La première étape de l’affichage des cadres en XAML consiste à créer un contrôle Image.

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

Dans votre page code-behind, déclarez une variable membre de classe de type SoftwareBitmap qui sera utilisée comme mémoire tampon de retour vers laquelle toutes les images entrantes seront copiées. Notez que les données d’image proprement dites ne sont pas copiées, mais uniquement les références d’objet. De plus, déclarez une valeur booléenne pour suivre si notre opération d’interface utilisateur est en cours d’exécution.

private SoftwareBitmap backBuffer;
private bool taskRunning = false;

Étant donné que les images arrivent en tant qu’objets SoftwareBitmap, vous devez créer un objet SoftwareBitmapSource qui vous permet d’utiliser un SoftwareBitmap comme source pour un contrôle XAML . Vous devez définir la source d’image quelque part dans votre code avant de démarrer le lecteur d’images.

iFrameReaderImageControl.Source = new SoftwareBitmapSource();

Il est maintenant temps d’implémenter le gestionnaire d’événements FrameArrived . Lorsque le gestionnaire est appelé, le paramètre expéditeur contient une référence à l’objet MediaFrameReader qui a déclenché l’événement. Appelez TryAcquireLatestFrame sur cet objet pour tenter d’obtenir la dernière image. Comme le nom l’indique, TryAcquireLatestFrame peut ne pas réussir à renvoyer une trame. Par conséquent, lorsque vous accédez à VideoMediaFrame, puis aux propriétés SoftwareBitmap, veillez à tester la valeur Null. Dans cet exemple, l’opérateur conditionnel Null ? est utilisé pour accéder au SoftwareBitmap , puis l’objet récupéré est vérifié pour null.

Le contrôle Image ne peut afficher que des images au format BRGA8 avec une valeur alpha prédéfinie ou non. Si le cadre arrivant n’est pas dans ce format, la méthode statique Convert est utilisée pour convertir la bitmap logicielle au format correct.

Ensuite, la méthode Interlocked.Exchange est utilisée pour échanger la référence du bitmap d'arrivée avec le bitmap de backbuffer. Cette méthode échange ces références dans une opération atomique qui est sécurisée pour les threads. Après l’échange, l’ancienne image backbuffer, désormais dans la variable softwareBitmap est supprimée pour nettoyer ses ressources.

Ensuite, le CoreDispatcher associé à l’élément Image est utilisé pour créer une tâche qui sera exécutée sur le thread de l’interface utilisateur en appelant RunAsync. Étant donné que les tâches asynchrones sont effectuées dans la tâche, l’expression lambda passée à RunAsync est déclarée avec le mot clé async.

Dans la tâche, la variable _taskRunning est vérifiée pour vous assurer qu’une seule instance de la tâche est en cours d’exécution à la fois. Si la tâche n’est pas déjà en cours d’exécution, _taskRunning a la valeur true pour empêcher l’exécution de la tâche. Dans un pendant que boucle, interlocked.Exchange est appelée pour copier à partir du backbuffer dans un SoftwareBitmap temporaire jusqu’à ce que l’image backbuffer soit null. Chaque fois que la bitmap temporaire est remplie, la propriété Source de la Image est convertie en SoftwareBitmapSource, puis SetBitmapAsync est appelée pour définir la source de l’image.

Enfin, la variable _taskRunning est définie sur false afin que la tâche puisse être réexécuter la prochaine fois que le gestionnaire est appelé.

Remarque

Si vous accédez aux objets SoftwareBitmap ou Direct3DSurface fournis par la propriété VideoMediaFrame d’un MediaFrameReference, le système crée une référence forte à ces objets, ce qui signifie qu’ils ne seront pas supprimés lorsque vous appelez Dispose sur l'MediaFrameReference conteneur. Vous devez appeler explicitement la méthode Dispose de SoftwareBitmap ou Direct3DSurface directement pour que les objets soient immédiatement supprimés. Sinon, le ramasse-miettes finira par libérer la mémoire de ces objets, mais il est impossible de savoir quand cela se produira, et si le nombre de bitmaps ou de surfaces allouées dépasse le nombre maximal autorisé par le système, le flux de nouvelles images s’arrêtera. Vous pouvez copier des images récupérées à l’aide de la méthode SoftwareBitmap.Copy , par exemple, puis libérer les images d’origine pour surmonter cette limitation. En outre, si vous créez le MediaFrameReader à l’aide de la surcharge CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) ou CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype), les images retournées sont des copies des données d’images d’origine et ne provoquent donc pas l’arrêt de l’acquisition d’images lorsqu’elles sont conservées.

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();
    }
}

Nettoyer les ressources

Lorsque vous avez terminé de lire des images, veillez à arrêter le lecteur d’images multimédia en appelant StopAsync, en annulant l’inscription du gestionnaire FrameArrived et en supprimant l’objet MediaCapture.

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

Pour plus d’informations sur le nettoyage des objets de capture multimédia lorsque votre application est suspendue, consultez Afficher l’aperçu de l’appareil photo dans une application WinUI 3.

Classe utilitaire FrameRenderer

Cette section fournit la liste complète du code d’une classe d’assistance qui facilite l’affichage des images à partir de sources de couleur, d’infrarouge et de profondeur dans votre application. En règle générale, vous voudrez faire quelque chose de plus avec les données de profondeur et infrarouge que de l’afficher à l’écran, mais cette classe d’assistance est un outil utile pour démontrer la fonctionnalité lecteur d’images et pour déboguer votre propre implémentation de lecteur d’images. Le code de cette section est adapté à partir de l’exemple d’images caméra.

La classe d’assistance FrameRenderer implémente les méthodes suivantes.

  • constructeur FrameRenderer : le constructeur initialise la classe d’assistance pour utiliser l’élément XAML Image que vous transmettez pour afficher des cadres multimédias.
  • ProcessFrame : cette méthode affiche une trame multimédia, représentée par un MediaFrameReference, dans l’élément Image que vous avez passé dans le constructeur. Vous devriez généralement appeler cette méthode depuis votre gestionnaire d'événements FrameArrived, en transmettant la trame retournée par TryAcquireLatestFrame.
  • ConvertToDisplayableImage : cette méthode vérifie le format du cadre multimédia et, si nécessaire, la convertit en format affichable. Pour les images de couleur, cela signifie que le format de couleur est BGRA8 et que le mode alpha bitmap est prémultiplié. Pour les images de profondeur ou infrarouge, chaque ligne de balayage est traitée pour convertir les valeurs de profondeur ou d’infrarouge en un dégradé pseudocolor, à l’aide de la classe PsuedoColorHelper, qui est également incluse dans l’exemple et répertoriée ci-dessous.

Remarque

Pour effectuer une manipulation de pixels sur images SoftwareBitmap, vous devez accéder à une mémoire tampon mémoire native. Pour ce faire, vous devez utiliser l’interface COM IMemoryBufferByteAccess incluse dans la liste de codes ci-dessous et vous devez mettre à jour vos propriétés de projet pour autoriser la compilation de code non sécurisé. Pour plus d’informations, consultez Créer, modifier et enregistrer des images bitmap.

[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;
                });
        }
    }
}

Utiliser MultiSourceMediaFrameReader pour obtenir des images corrélées dans le temps à partir de plusieurs sources

À compter de Windows 10, version 1607, vous pouvez utiliser MultiSourceMediaFrameReader pour recevoir des images en corrélation temporelle à partir de plusieurs sources. Cette API facilite le traitement qui nécessite des images provenant de plusieurs sources qui ont été prises à proximité temporelle proche, comme l’utilisation de la classe DepthCorrelatedCoordinateMapper . L’une des limitations de l’utilisation de cette nouvelle méthode est que les événements arrivés aux images ne sont déclenchés qu’à la vitesse de la source de capture la plus lente. Des images supplémentaires provenant de sources plus rapides seront supprimées. En outre, étant donné que le système s’attend à ce que les images arrivent de différentes sources à différents taux, elle ne reconnaît pas automatiquement si une source a cessé de générer des images complètement. L’exemple de code de cette section montre comment utiliser un événement pour créer votre propre logique de délai d’expiration qui est appelée si les images corrélées ne arrivent pas dans une limite de temps définie par l’application.

Les étapes d’utilisation de MultiSourceMediaFrameReader sont similaires aux étapes d’utilisation de MediaFrameReader décrites précédemment dans cet article. Cet exemple utilise une source de couleur et une source de profondeur. Déclarez des variables de type chaîne pour stocker les identifiants de source des cadres multimédias qui seront utilisés pour sélectionner des cadres à partir de chaque source. Ensuite, déclarez un ManualResetEventSlim, un CancellationTokenSource et un EventHandler qui sera utilisé pour implémenter la logique de délai d’expiration pour l’exemple.

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;

À l’aide des techniques décrites précédemment dans cet article, interrogez un MediaFrameSourceGroup qui inclut les sources de couleur et de profondeur requises pour cet exemple de scénario. Après avoir sélectionné le groupe source d’images souhaité, obtenez l'information MediaFrameSourceInfo pour chaque source de cadre.

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];

Créez et initialisez un objet MediaCapture , en passant le groupe source d’images sélectionné dans les paramètres d’initialisation.

m_mediaCapture = new MediaCapture();

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

await m_mediaCapture.InitializeAsync(settings);

Après avoir initialisé l’objet MediaCapture, récupérez les objets MediaFrameSource pour les caméras de couleur et de profondeur. Stockez l’ID pour chaque source afin de pouvoir sélectionner le cadre arrivant pour la source correspondante.

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;

Créez et initialisez MultiSourceMediaFrameReader en appelant CreateMultiSourceFrameReaderAsync et en transmettant un tableau de sources d’images que le lecteur utilisera. Enregistrez un gestionnaire d’événements pour l’événement FrameArrived. Cet exemple crée une instance de la classe d’assistance FrameRenderer , décrite précédemment dans cet article, pour afficher des images dans un contrôle Image . Démarrez le lecteur d’images en appelant StartAsync.

Inscrivez un gestionnaire d’événements pour l’événement CorellationFailed déclaré précédemment dans l’exemple. Nous signalons cet événement si l’une des sources d’images multimédias utilisées cesse de produire des images. Enfin, appelez Task.Run pour invoquer la méthode utilitaire de délai d'attente, NotifyAboutCorrelationFailure, dans un thread distinct. L’implémentation de cette méthode est présentée plus loin dans cet article.

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));

L'événement FrameArrived est déclenché chaque fois qu'un nouveau cadre est disponible à partir de toutes les sources de cadres multimédias gérées par le MultiSourceMediaFrameReader. Cela signifie que l’événement sera déclenché à la cadence de la source multimédia la plus lente. Si une source produit plusieurs images dans le temps où une source plus lente produit une image, les images supplémentaires de la source rapide sont supprimées.

Obtenez le MultiSourceMediaFrameReference associé à l’événement en appelant TryAcquireLatestFrame. Obtenez le MediaFrameReference associé à chaque source de trame multimédia en appelant TryGetFrameReferenceBySourceId, en passant les chaînes d’ID stockées lors de l’initialisation du lecteur d’images.

Appelez la méthode Set de l’objet ManualResetEventSlim pour signaler que les images sont arrivées. Nous allons vérifier cet événement dans la méthode NotifyCorrelationFailure qui s’exécute sur un thread distinct.

Enfin, effectuez tout traitement sur les trames multimédia corrélées dans le temps. Cet exemple affiche simplement le cadre de la source de profondeur.

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);
    }
}

La méthode d’assistance NotifyCorrelationFailure a été exécutée sur un thread distinct après le démarrage du lecteur d’images. Dans cette méthode, vérifiez si l’événement reçu de la trame a été signalé. N’oubliez pas que dans le gestionnaire FrameArrived , nous définissons cet événement chaque fois qu’un ensemble d’images corrélées arrive. Si l’événement n’a pas été signalé pendant une période définie par l’application - 5 secondes est une valeur raisonnable - et que la tâche n’a pas été annulée à l’aide de CancellationToken, il est probable que l’une des sources d’images multimédias ait arrêté la lecture des images. Dans ce cas, vous souhaitez généralement arrêter le lecteur d’images, alors déclenchez l’événement CorrelationFailed défini par l’application. Dans le gestionnaire de cet événement, vous pouvez arrêter le lecteur d’images et nettoyer ses ressources associées, comme indiqué précédemment dans cet article.

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;
}

Utiliser le mode d’acquisition d’images mis en mémoire tampon pour conserver la séquence d’images acquises

À partir de Windows 10, version 1709, vous pouvez définir la propriété AcquisitionMode d’un MediaFrameReader ou MultiSourceMediaFrameReader sur Buffered pour préserver la séquence des images transférées à votre application depuis la source d'images.

m_mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;

En mode d’acquisition par défaut, en temps réel, si plusieurs images sont acquises à partir de la source pendant que votre application gère toujours l’événement FrameArrived pour une image précédente, le système envoie à votre application le cadre le plus récemment acquis et supprime les images supplémentaires en attente dans la mémoire tampon. Cela fournit à votre application le cadre disponible le plus récent à tout moment. Il s’agit généralement du mode le plus utile pour les applications de vision par ordinateur en temps réel.

Dans le mode d’acquisition mis en mémoire tampon, le système conserve toutes les trames dans la mémoire tampon et les fournit à votre application dans l’ordre de réception via l’événement FrameArrived. Notez que dans ce mode, lorsque la mémoire tampon du système pour les images est remplie, le système cesse d’acquérir de nouvelles images jusqu’à ce que votre application termine l’événement FrameArrived pour les images précédentes, ce qui libère davantage d’espace dans la mémoire tampon.

Utiliser MediaSource pour afficher des cadres dans un MediaPlayerElement

À compter de Windows, version 1709, vous pouvez afficher des images acquises à partir d’un MediaFrameReader directement dans un contrôle MediaPlayerElement dans votre page XAML. Pour ce faire , utilisez MediaSource.CreateFromMediaFrameSource pour créer un objet MediaSource qui peut être utilisé directement par un MediaPlayer associé à un MediaPlayerElement. Pour plus d’informations sur l’utilisation de MediaPlayer et de MediaPlayerElement, consultez Lire l’audio et la vidéo avec MediaPlayer.

Les exemples de code suivants vous montrent une implémentation simple qui affiche les images à partir d’une caméra frontale et arrière simultanément dans une page XAML.

Tout d’abord, ajoutez deux contrôles MediaPlayerElement à votre page XAML.

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

Ensuite, à l’aide des techniques présentées dans les sections précédentes de cet article, sélectionnez un MediaFrameSourceGroup qui contient objets MediaFrameSourceInfo pour les caméras couleur sur les panneaux avant et arrière. Notez que MediaPlayer ne convertit pas automatiquement les images à partir de formats non coloris, tels qu’une profondeur ou des données infrarouges, en données de couleur. L’utilisation d’autres types de capteurs peut produire des résultats inattendus.

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];

Initialisez l'objet MediaCapture pour utiliser le MediaFrameSourceGroup sélectionné.

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;
}

Enfin, appelez MediaSource.CreateFromMediaFrameSource pour créer une MediaSource pour chaque source d’images à l’aide de la propriété Id de l’objet MediaFrameSourceInfo associé pour sélectionner l’une des sources d’images dans la collection MediaCapture objet FrameSources. Initialisez un nouvel objet MediaPlayer et affectez-le à un MediaPlayerElement en appelant SetMediaPlayer. Définissez ensuite la propriété Source sur l’objet MediaSource nouvellement créé.

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;

Utiliser des profils vidéo pour sélectionner une source de trame

Un profil d’appareil photo, représenté par un objet MediaCaptureVideoProfile , représente un ensemble de fonctionnalités qu’un appareil de capture particulier fournit, comme les fréquences d’images, les résolutions ou les fonctionnalités avancées telles que la capture HDR. Un appareil de capture peut prendre en charge plusieurs profils, ce qui vous permet de sélectionner celui qui est optimisé pour votre scénario de capture. À compter de Windows 10, version 1803, vous pouvez utiliser MediaCaptureVideoProfile pour sélectionner une source de trame multimédia avec des fonctionnalités particulières avant d’initialiser l’objet MediaCapture . L’exemple de code suivant recherche un profil vidéo qui prend en charge HDR avec Wide Color Gamut (WCG) et retourne un objet MediaCaptureInitializationSettings qui peut être utilisé pour initialiser MediaCapture afin d’utiliser l’appareil et le profil sélectionnés.

Tout d’abord, appelez MediaFrameSourceGroup.FindAllAsync pour obtenir la liste de tous les groupes de sources d’images multimédias disponibles sur l’appareil actuel. Parcourez chaque groupe source et appelez MediaCapture.FindKnownVideoProfiles pour obtenir la liste de tous les profils vidéo du groupe source actuel qui prennent en charge le profil spécifié, dans ce cas HDR avec la photo WCG. Si un profil répondant aux critères est trouvé, créez un nouvel objet MediaCaptureInitializationSettings et définissez le VideoProfile sur le profil vidéo sélectionné et le VideoDeviceId sur la propriété Id du groupe de sources de trames multimédias actuel.

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;
    }
}

Pour plus d’informations sur l’utilisation des profils d’appareil photo, consultez Profils de caméra.