Dela via


Bearbeta medieramar med MediaFrameReader

Den här artikeln visar hur du använder en MediaFrameReader med MediaCapture för att hämta mediaramar från en eller flera tillgängliga källor, inklusive färg-, djup- och infraröda kameror, ljudenheter eller till och med anpassade källor som producerar skelettspårningsramar. Den här funktionen är utformad för att användas av appar som utför realtidsbearbetning av medieramar, till exempel förhöjd verklighet och djupmedvetna kameraappar.

Om du är intresserad av att helt enkelt fånga video eller foton, till exempel en typisk fotoapp, vill du förmodligen använda någon av de andra inspelningsteknikerna som stöds av MediaCapture. En lista över tillgängliga tekniker och artiklar för medieinsamling som visar hur du använder dem finns i Kamera.

Anmärkning

De funktioner som beskrivs i den här artikeln är endast tillgängliga från och med Windows 10 version 1607.

Anmärkning

Det finns ett Universal Windows-appexempel som visar hur du använder MediaFrameReader för att visa ramar från olika ramkällor, inklusive färg, djup och infraröda kameror. Mer information finns i Exempel på kameraramar.

Anmärkning

En ny uppsättning API:er för att använda MediaFrameReader med ljuddata introducerades i Windows 10 version 1803. Mer information finns i Bearbeta ljudramar med MediaFrameReader.

Välj ramkällor och ramkällgrupper

Många appar som bearbetar medieramar måste hämta ramar från flera källor samtidigt, till exempel en enhets färg- och djupkameror. MediaFrameSourceGroup-objektet representerar en uppsättning medieramkällor som kan användas samtidigt. Anropa den statiska metoden MediaFrameSourceGroup.FindAllAsync för att hämta en lista över alla grupper av ramkällor som stöds av den aktuella enheten.

var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();

Du kan också skapa en DeviceWatcher- med DeviceInformation.CreateWatcher och värdet som returneras från MediaFrameSourceGroup.GetDeviceSelector för att ta emot meddelanden när de tillgängliga grupperna av ramkällor på enheten ändras, till exempel när en extern kamera ansluts. Mer information finns i Räkna upp enheter.

En MediaFrameSourceGroup har en samling MediaFrameSourceInfo-objekt som beskriver de ramkällor som ingår i gruppen. När du har hämtat de tillgängliga ramkällgrupperna på enheten kan du välja den grupp som exponerar de ramkällor som du är intresserad av.

I följande exempel visas det enklaste sättet att välja en ramkällgrupp. Den här koden loopar helt enkelt över alla tillgängliga grupper och loopar sedan över varje objekt i SourceInfos-samlingen . Varje MediaFrameSourceInfo kontrolleras för att se om det stöder de funktioner vi söker. I det här fallet kontrolleras egenskapen MediaStreamType efter värdet VideoPreview, vilket innebär att enheten tillhandahåller en videoförhandsgranskningsström, och egenskapen SourceKind kontrolleras efter värdet Färg, vilket anger att källan innehåller färgramar.

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

Den här metoden för att identifiera önskad ramkällgrupp och ramkällor fungerar för enkla fall, men om du vill välja ramkällor baserat på mer komplexa kriterier kan det snabbt bli besvärligt. En annan metod är att använda Linq-syntax och anonyma objekt för att göra markeringen. I följande exempel används metoden Välj tillägg för att omvandla MediaFrameSourceGroup-objekten i listan frameSourceGroups till ett anonymt objekt med två fält: sourceGroup, som representerar själva gruppen och colorSourceInfo, som representerar färgramkällan i gruppen. Fältet colorSourceInfo är inställt på resultatet av FirstOrDefault, som väljer det första objektet för vilket det angivna predikatet utvärderar till sant. I det här fallet är predikatet sant om strömtypen är VideoPreview, källtypen är Color och om kameran finns på enhetens frontpanel.

I listan över anonyma objekt som returneras från frågan som beskrivs ovan används metoden Where extension för att endast välja de objekt där fältet colorSourceInfo inte är null. Slutligen anropas FirstOrDefault för att välja det första objektet i listan.

Nu kan du använda fälten i det markerade objektet för att hämta referenser till det valda MediaFrameSourceGroup - och MediaFrameSourceInfo-objektet som representerar färgkameran. Dessa används senare för att initiera MediaCapture-objektet och skapa en MediaFrameReader för den valda källan. Slutligen bör du testa för att se om källgruppen är null, vilket innebär att den aktuella enheten inte har dina begärda avbildningskällor.

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

I följande exempel används en liknande teknik enligt beskrivningen ovan för att välja en källgrupp som innehåller färg, djup och infraröda kameror.

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

Anmärkning

Från och med Windows 10 version 1803 kan du använda klassen MediaCaptureVideoProfile för att välja en medieramkälla med en uppsättning önskade funktioner. Mer information finns i avsnittet Använda videoprofiler för att välja en ramkälla senare i den här artikeln.

Initiera MediaCapture-objektet för att använda den valda ramkällgruppen

Nästa steg är att initiera MediaCapture-objektet så att det använder den ramkällgrupp som du valde i föregående steg. Skapa en instans av MediaCapture-objektet genom att anropa konstruktorn. Skapa sedan ett MediaCaptureInitializationSettings-objekt som ska användas för att initiera MediaCapture-objektet . I det här exemplet används följande inställningar:

  • SourceGroup – detta talar om för systemet vilken källgrupp du ska använda för att hämta ramar. Kom ihåg att källgruppen definierar en uppsättning medieramkällor som kan användas samtidigt.
  • SharingMode – Detta talar om för systemet om du behöver exklusiv kontroll över inspelningskällorna. Om du ställer in detta på ExclusiveControl innebär det att du kan ändra inställningarna för avbildningsenheten, till exempel formatet på de ramar som skapas, men det innebär att om en annan app redan har exklusiv kontroll misslyckas appen när den försöker initiera medieinsamlingsenheten. Om du ställer in detta på SharedReadOnly kan du ta emot bildrutor från ramkällorna även om de används av en annan app, men du kan inte ändra inställningarna för enheterna.
  • MemoryPreference – Om du anger CPU, använder systemet CPU-minne som garanterar att när bilder tas emot, blir de tillgängliga som SoftwareBitmap-objekt. Om du anger Auto väljer systemet dynamiskt den optimala minnesplatsen för att lagra ramar. Om systemet väljer att använda GPU-minne, ankommer medieramarna i form av ett IDirect3DSurface--objekt och inte som ett SoftwareBitmap-.
  • StreamingCaptureMode – Ställ in detta på Video för att indikera att ljudet inte behöver strömmas.

Anropa InitializeAsync för att initiera MediaCapture med önskade inställningar. Var noga med att anropa detta inom en try-block om initieringen misslyckas.

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

Ange önskat format för ramkällan

Om du vill ange önskat format för en ramkälla måste du hämta ett MediaFrameSource-objekt som representerar källan. Du hämtar det här objektet genom att komma åt ordlistan Bildrutor för det initierade MediaCapture-objektet och ange identifieraren för den ramkälla som du vill använda. Därför sparade vi MediaFrameSourceInfo-objektet när vi valde en ramkällgrupp.

Egenskapen MediaFrameSource.SupportedFormats innehåller en lista över MediaFrameFormat-objekt som beskriver format som stöds för ramkällan. I det här exemplet väljs ett format som har en bredd på 1 080 bildpunkter och kan ange bildrutor i 32-bitars RGB-format. Utökningsmetoden FirstOrDefault väljer den första posten i listan. Om det valda formatet är null stöds inte det begärda formatet av ramkällan. Om formatet stöds kan du begära att källan använder det här formatet genom att anropa 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);

Skapa en ramläsare för ramkällan

Om du vill ta emot ramar för en medieramkälla använder du en MediaFrameReader-.

MediaFrameReader m_mediaFrameReader;

Instansiera ramläsaren genom att anropa CreateFrameReaderAsync på ditt initierade MediaCapture objekt. Det första argumentet för den här metoden är den ramkälla som du vill ta emot bildrutor från. Du kan skapa en separat ramläsare för varje ramkälla som du vill använda. Det andra argumentet talar om för systemet vilket utdataformat du vill att bildrutor ska tas emot i. Detta kan spara dig från att behöva göra dina egna konverteringar till ramar när de anländer. Observera att om du anger ett format som inte stöds av ramkällan genereras ett undantag, så se till att det här värdet finns i samlingen SupportedFormats .

När du har skapat ramläsaren registrerar du en hanterare för händelsen FrameArrived som aktiveras när en ny ram är tillgänglig från källan.

Be systemet att börja läsa bildrutor från källan genom att anropa StartAsync.

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

Hantera händelsen för den ankomna ramen

Händelsen MediaFrameReader.FrameArrived genereras när en ny bildruta är tillgänglig. Du kan välja att bearbeta varje ram som tas emot eller endast använda ramar när du behöver dem. Eftersom ramläsaren genererar händelsen i en egen tråd kan du behöva implementera viss synkroniseringslogik för att se till att du inte försöker komma åt samma data från flera trådar. Det här avsnittet visar hur du synkroniserar ritningsfärgramar till en bildkontroll på en XAML-sida. Det här scenariot åtgärdar den ytterligare synkroniseringsbegränsning som kräver att alla uppdateringar av XAML-kontroller utförs i användargränssnittstråden.

Det första steget i att visa ramar i XAML är att skapa en bildkontroll.

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

Deklarera i din kod bakom sida en klassmedlemsvariabel av typen SoftwareBitmap som ska användas som en bakåtbuffert som alla inkommande bilder kopieras till. Observera att själva bilddata inte kopieras, bara objektreferenserna. Deklarera också ett booleskt värde för att spåra om vår användargränssnittsåtgärd körs för närvarande.

private SoftwareBitmap backBuffer;
private bool taskRunning = false;

Eftersom ramarna tas emot som SoftwareBitmap-objekt måste du skapa ett SoftwareBitmapSource-objekt så att du kan använda en SoftwareBitmap som källa för en XAML Control. Du bör ange bildkällan någonstans i koden innan du startar bildruteläsaren.

iFrameReaderImageControl.Source = new SoftwareBitmapSource();

Nu är det dags att implementera händelsehanteraren för FrameArrived. När hanteraren anropas innehåller avsändarparametern en referens till MediaFrameReader-objektet som skapade händelsen. Anropa TryAcquireLatestFrame på det här objektet för att försöka hämta den senaste ramen. Som namnet antyder kanske TryAcquireLatestFrame- inte lyckas returnera en bildruta. När du kommer åt egenskaperna VideoMediaFrame och sedan SoftwareBitmap måste du därför testa null. I det här exemplet är den villkorsstyrda operatorn null ? används för att komma åt SoftwareBitmap och sedan kontrolleras det hämtade objektet för null.

Kontrollen Image kan bara visa bilder i BRGA8-format med antingen förmultipliserad eller ingen alfa. Om den ankommande ramen inte är i det formatet används den statiska metoden Convert för att konvertera programvarans bitmapp till rätt format.

Sedan används metoden Interlocked.Exchange för att byta ut referensen för den inkommande bitmappen mot backbuffer bitmappen. Den här metoden växlar dessa referenser i en atomisk åtgärd som är trådsäker. Efter bytet slängs den gamla backbuffer-avbildningen, som nu finns i softwareBitmap-variabeln, för att rensa dess resurser.

Sedan används CoreDispatcher som är associerad med avbildningselementet för att skapa en uppgift som ska köras i användargränssnittstråden genom att anropa RunAsync. Eftersom asynkrona uppgifter utförs i aktiviteten deklareras lambda-uttrycket som skickas till RunAsync med nyckelordet async .

I aktiviteten kontrolleras variabeln _taskRunning för att se till att endast en instans av aktiviteten körs i taget. Om aktiviteten inte redan körs är _taskRunning inställd på true för att förhindra att aktiviteten körs igen. I en medan-loop anropas Interlocked.Exchange för att kopiera från den bakre buffern till en tillfällig SoftwareBitmap tills bilden i den bakre buffern är null. För varje gång den tillfälliga bitmappen fylls i skickas källegenskapen för avbildningen till en SoftwareBitmapSource och sedan anropas SetBitmapAsync för att ange källan till avbildningen.

Slutligen ställs variabeln _taskRunning tillbaka till false så att uppgiften kan köras igen nästa gång hanteraren anropas.

Anmärkning

Om du kommer åt SoftwareBitmap eller Direct3DSurface-objekt som tillhandahålls av VideoMediaFrame-egenskapen för en MediaFrameReference, skapar systemet en stark referens till dessa objekt, vilket innebär att de inte tas bort när du anropar Dispose på den innehållande MediaFrameReference. Du måste uttryckligen anropa metoden Dispose för SoftwareBitmap eller Direct3DSurface direkt för att objekten ska tas bort omedelbart. Annars frigör skräpinsamlaren så småningom minnet för dessa objekt, men du kan inte veta när detta inträffar, och om antalet allokerade bitmappar eller ytor överskrider den maximala mängd som tillåts av systemet stoppas flödet av nya ramar. Du kan till exempel kopiera hämtade ramar med hjälp av metoden SoftwareBitmap.Copy och sedan släppa de ursprungliga ramarna för att övervinna den här begränsningen. Om du skapar MediaFrameReader med hjälp av överlagringen CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) eller CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype), de ramar som returneras är kopior av de ursprungliga ramdata och gör därför inte att ramförvärvet stoppas när de behålls.

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

Rensa resurser

När du är klar med att läsa bildrutor måste du stoppa medieramläsaren genom att anropa StopAsync, avregistrera FrameArrived-hanteraren och ta bort MediaCapture-objektet .

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

Mer information om hur du rensar mediefångstobjekt när programmet pausas finns i Visa kameraförhandsgranskningen i en WinUI 3-app.

Hjälpklassen FrameRenderer

Det här avsnittet innehåller en fullständig kodlista för en hjälpklass som gör det enkelt att visa bildrutorna från färg-, infraröd- och djupkällor i din app. Vanligtvis vill du göra något mer med djup och infraröda data än att bara visa dem på skärmen, men den här hjälpklassen är ett användbart verktyg för att demonstrera funktionen för bildläsare och för att felsöka din egen implementering av ramläsare. Koden i det här avsnittet är anpassad från exemplet kamerabildrutor.

Hjälpklassen FrameRenderer implementerar följande metoder.

  • FrameRenderer konstruktor – Konstruktorn initierar hjälpklassen för att använda elementet XAML Image som du anger för att visa medierutor.
  • ProcessFrame – Den här metoden visar en medieram som representeras av en MediaFrameReference-i elementet Image som du skickade till konstruktorn. Du bör vanligtvis anropa den här metoden från din FrameArrived händelsehanterare och skicka in den ram som returneras av TryAcquireLatestFrame.
  • ConvertToDisplayableImage – Den här metoden kontrollerar formatet på medieramen och konverterar den vid behov till ett visningsbart format. För färgbilder innebär det att se till att färgformatet är BGRA8 och att bitmappens alfaläge är förmultipliserat. För djup- eller infraröda bilder bearbetas varje skanningslinje för att konvertera djup- eller infraröda värden till en pseudofärg-toning med hjälp av PsuedoColorHelper-klassen, som också ingår i det angivna exemplet och listas nedan.

Anmärkning

För att kunna göra pixelmanipulering på SoftwareBitmap-bilder måste du komma åt en intern minnesbuffert. För att göra detta måste du använda com-gränssnittet IMemoryBufferByteAccess som ingår i kodlistan nedan och du måste uppdatera projektegenskaperna för att tillåta kompilering av osäker kod. Mer information finns i Skapa, redigera och spara bitmappsbilder.

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

Använda MultiSourceMediaFrameReader för att hämta tidskorrelerade ramar från flera källor

Från och med Windows 10 version 1607 kan du använda MultiSourceMediaFrameReader för att ta emot tidskorrelerade bildrutor från flera källor. Det här API:et gör det enklare att utföra bearbetning som kräver ramar från flera källor som har tagits i nära tidsmässig närhet, till exempel med klassen DepthCorrelatedCoordinateMapper . En begränsning med att använda den här nya metoden är att frame-arrived-händelser endast höjs med den långsammaste avbildningskällan. Extra bildrutor från snabbare källor tas bort. Eftersom systemet förväntar sig att ramar kommer från olika källor vid olika hastigheter, upptäcker det inte automatiskt om en källa har slutat generera ramar helt och hållet. Exempelkoden i det här avsnittet visar hur du använder en händelse för att skapa en egen timeoutlogik som anropas om korrelerade ramar inte tas emot inom en appdefinierad tidsgräns.

Stegen för att använda MultiSourceMediaFrameReader liknar stegen för att använda MediaFrameReader som beskrivs tidigare i den här artikeln. I det här exemplet används en färgkälla och en djupkälla. Deklarera vissa strängvariabler för att lagra käll-ID:t för medieramen som ska användas för att välja bildrutor från varje källa. Deklarera sedan en ManualResetEventSlim, en CancellationTokenSource och en EventHandler som ska användas för att implementera timeout-logik för exemplet.

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;

Med hjälp av de tekniker som beskrivs tidigare i den här artikeln frågar du efter en MediaFrameSourceGroup som innehåller de färg- och djupkällor som krävs för det här exempelscenariot. När du har valt önskad ramkällgrupp hämtar du MediaFrameSourceInfo- för varje ramkälla.

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

Skapa och initiera ett MediaCapture-objekt och överför den valda bildkällgruppen i initieringsinställningarna.

m_mediaCapture = new MediaCapture();

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

await m_mediaCapture.InitializeAsync(settings);

När du har initierat MediaCapture-objektet hämtar du MediaFrameSource-objekt för färg- och djupkamerorna. Lagra ID för varje källa så att du kan välja den ankommande bildrutan för motsvarande källa.

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;

Skapa och initiera MultiSourceMediaFrameReader genom att anropa CreateMultiSourceFrameReaderAsync och skicka en matris med ramkällor som läsaren ska använda. Registrera en händelsehanterare för FrameArrived-händelsen . I det här exemplet skapas en instans av hjälpklassen FrameRenderer , som beskrivs tidigare i den här artikeln, för att återge ramar till en bildkontroll . Starta bildruteläsaren genom att anropa StartAsync.

Registrera en händelsehanterare för händelsen CorrelationFailed, som deklarerades tidigare i exemplet. Vi kommer att signalera den här händelsen om någon av de medieramkällor som används slutar producera bildrutor. Anropa slutligen Task.Run för att anropa timeout-hjälpmetoden NotifyAboutCorrelationFailure i en separat tråd. Implementeringen av den här metoden visas senare i den här artikeln.

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

Händelsen FrameArrived utlöses när en ny ram är tillgänglig från alla medieramkällor som hanteras av MultiSourceMediaFrameReader. Det innebär att händelsen aktiveras i takt med den långsammaste mediekällan. Om en källa skapar flera bildrutor under den tid som en långsammare källa skapar en bildruta, kommer de extra bildrutorna från den snabba källan att tas bort.

Hämta MultiSourceMediaFrameReference- som är associerad med händelsen genom att anropa TryAcquireLatestFrame. Hämta MediaFrameReference- associerad med varje medieramkälla genom att anropa TryGetFrameReferenceBySourceIdoch skicka in ID-strängarna som lagrades när bildruteläsaren initierades.

Anropa metoden Set för ManualResetEventSlim-objektet för att signalera att ramar har anlänt. Vi kontrollerar den här händelsen i metoden NotifyCorrelationFailure som körs i en separat tråd.

Utför slutligen all bearbetning på de tidskorrelerade medieramarna. Det här exemplet visar ramen från djupkällan på ett enkelt sätt.

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

Hjälpmetoden NotifyCorrelationFailure kördes på en separat tråd efter att ramläsaren hade startats. I den här metoden kontrollerar du om den mottagna ramhändelsen har signalerats. Kom ihåg att i FrameArrived-hanteraren anger vi den här händelsen när en uppsättning korrelerade ramar anländer. Om händelsen inte har signalerats under någon appdefinierad tidsperiod – 5 sekunder är ett rimligt värde – och aktiviteten inte avbröts med hjälp av CancellationToken är det troligt att en av medieramkällorna har slutat läsa bildrutor. I det här fallet vill du vanligtvis stänga av bildruteläsaren, så utlöser du den appdefinierade CorrelationFailed-händelsen. I hanteraren för den här händelsen kan du stoppa bildruteläsaren och rensa dess associerade resurser, som visats tidigare i den här artikeln.

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

Använd hämtningsläge för buffrad bildruta för att bevara sekvensen med anskaffade bildrutor

Från och med Windows 10 version 1709 kan du ange egenskapen AcquisitionMode för en MediaFrameReader- eller MultiSourceMediaFrameReader till buffrad för att bevara sekvensen med bildrutor som skickas till din app från ramkällan.

m_mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;

I standardläget för inhämtning Realtid, om flera bildrutor hämtas från källan medan appen fortfarande hanterar FrameArrived händelse för en tidigare bildruta, skickar systemet den senast inhämtade bildrutan till din app och släpper ytterligare bildrutor som väntar i bufferten. Detta ger din app den senaste tillgängliga ramen hela tiden. Detta är vanligtvis det mest användbara läget för realtidsapplikationer för datorsyn.

I Buffrad förvärvsläge behåller systemet alla bilder i bufferten och skickar dem till din app i den ordning de tas emot via händelsen FrameArrived. Observera att när systemets buffert för ramar är fylld i det här läget slutar systemet att hämta nya ramar tills appen har slutfört FrameArrived-händelsen för tidigare bildrutor, vilket frigör mer utrymme i bufferten.

Använda MediaSource för att visa ramar i ett MediaPlayerElement

Från och med Windows version 1709 kan du visa bildrutor som hämtats från en MediaFrameReader direkt i en MediaPlayerElement-kontroll på din XAML-sida. Detta uppnås genom att använda MediaSource.CreateFromMediaFrameSource för att skapa MediaSource-objekt som kan användas direkt av en MediaPlayer som är associerad med ett MediaPlayerElement. Detaljerad information om hur du arbetar med MediaPlayer och MediaPlayerElement finns i Spela upp ljud och video med MediaPlayer.

I följande kodexempel visas en enkel implementering som visar bildrutorna från en framåtriktad och bakåtriktad kamera samtidigt på en XAML-sida.

Lägg först till två MediaPlayerElement-kontroller på XAML-sidan.

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

Med hjälp av de tekniker som visas i föregående avsnitt i den här artikeln väljer du sedan en MediaFrameSourceGroup som innehåller MediaFrameSourceInfo-objekt för färgkameror på frontpanelen och på baksidan. Observera att MediaPlayer inte automatiskt konverterar bildrutor från icke-färgformat, till exempel djup eller infraröda data, till färgdata. Användning av andra sensortyper kan ge oväntade resultat.

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

Initiera objektet MediaCapture för att använda den valda MediaFrameSourceGroup.

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

Anropa slutligen MediaSource.CreateFromMediaFrameSource för att skapa en MediaSource för varje ramkälla med hjälp av ID-egenskapen för det associerade MediaFrameSourceInfo-objektet för att välja en av ramkällorna i MediaCapture-objektetsFrameSources-samling . Initiera ett nytt MediaPlayer-objekt och tilldela det till ett MediaPlayerElement genom att anropa SetMediaPlayer. Ange sedan egenskapen Source till det nyligen skapade MediaSource-objektet .

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;

Använda videoprofiler för att välja en ramkälla

En kameraprofil, som representeras av ett MediaCaptureVideoProfile-objekt , representerar en uppsättning funktioner som en viss inspelningsenhet tillhandahåller, till exempel bildfrekvenser, upplösningar eller avancerade funktioner som HDR-avbildning. En avbildningsenhet kan ha stöd för flera profiler, så att du kan välja den som är optimerad för ditt avbildningsscenario. Från och med Windows 10 version 1803 kan du använda MediaCaptureVideoProfile för att välja en medieramkälla med särskilda funktioner innan du initierar MediaCapture-objektet . Följande exempelkod letar efter en videoprofil som stöder HDR med Wide Color Gamut (WCG) och returnerar ett MediaCaptureInitializationSettings-objekt som kan användas för att initiera MediaCapture för att använda den valda enheten och profilen.

Anropa först MediaFrameSourceGroup.FindAllAsync för att hämta en lista över alla medieramkällgrupper som är tillgängliga på den aktuella enheten. Gå igenom varje källgrupp och anropa MediaCapture.FindKnownVideoProfiles för att hämta en lista över alla videoprofiler för den aktuella källgruppen som stöder den angivna profilen, i det här fallet HDR med WCG-foto. Om en profil som uppfyller kriterierna hittas skapar du ett nytt MediaCaptureInitializationSettings-objekt och anger VideoProfile till den valda profilen och VideoDeviceId till egenskapen Id för den aktuella mediekällgruppens ramverk.

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

Mer information om hur du använder kameraprofiler finns i Kameraprofiler.