Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
In dit artikel leest u hoe u een MediaFrameReader met MediaCapture gebruikt om mediaframes op te halen uit een of meer beschikbare bronnen, waaronder kleur-, diepte- en infraroodcamera's, audioapparaten of zelfs aangepaste framebronnen, zoals frames die skeletale traceringsframes produceren. Deze functie is ontworpen voor gebruik door apps die realtime verwerking van mediaframes uitvoeren, zoals augmented reality en dieptebewuste camera-apps.
Als u alleen video's of foto's wilt vastleggen, zoals een typische fotografie-app, wilt u waarschijnlijk een van de andere opnametechnieken gebruiken die worden ondersteund door MediaCapture. Zie Camera voor een lijst met beschikbare technieken voor het vastleggen van media en artikelen waarin wordt getoond hoe u deze kunt gebruiken.
Opmerking
De functies die in dit artikel worden besproken, zijn alleen beschikbaar vanaf Windows 10 versie 1607.
Opmerking
Er is een universeel Windows-app-voorbeeld dat laat zien hoe u MediaFrameReader gebruikt om frames uit verschillende framebronnen weer te geven, waaronder kleur-, diepte- en infraroodcamera's. Zie Het voorbeeld van cameraframes voor meer informatie.
Opmerking
Er is een nieuwe set API's voor het gebruik van MediaFrameReader met audiogegevens geïntroduceerd in Windows 10 versie 1803. Zie Audioframes verwerken met MediaFrameReader voor meer informatie.
Framebronnen en framebrongroepen selecteren
Veel apps die mediaframes verwerken, moeten frames uit meerdere bronnen tegelijk ophalen, zoals de kleur- en dieptecamera's van een apparaat. Het MediaFrameSourceGroup-object vertegenwoordigt een set mediaframebronnen die tegelijkertijd kunnen worden gebruikt. Roep de statische methode MediaFrameSourceGroup.FindAllAsync aan om een lijst op te halen van alle groepen framebronnen die door het huidige apparaat worden ondersteund.
var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();
U kunt ook een DeviceWatcher maken met DeviceInformation.CreateWatcher en de waarde die wordt geretourneerd door MediaFrameSourceGroup.GetDeviceSelector om meldingen te ontvangen wanneer de beschikbare framebrongroepen op het apparaat worden gewijzigd, bijvoorbeeld wanneer een externe camera is aangesloten. Voor meer informatie, zie Apparaten opsommen.
Een MediaFrameSourceGroup heeft een verzameling MediaFrameSourceInfo-objecten waarmee de framebronnen in de groep worden beschreven. Nadat u de framebrongroepen hebt opgehaald die beschikbaar zijn op het apparaat, kunt u de groep selecteren waarin de framebronnen worden weergegeven waarin u geïnteresseerd bent.
In het volgende voorbeeld ziet u de eenvoudigste manier om een framebrongroep te selecteren. De code doorloopt eenvoudig alle beschikbare groepen en vervolgens worden met deze code ook elk item in de SourceInfos collectie doorlopen. Elke MediaFrameSourceInfo wordt gecontroleerd om te zien of deze ondersteuning biedt voor de functies die we zoeken. In dit geval wordt de eigenschap MediaStreamType gecontroleerd op de waarde VideoPreview, wat betekent dat het apparaat een videovoorbeeldstream biedt en de eigenschap SourceKind wordt gecontroleerd op de waardeKleur, waarmee wordt aangegeven dat de bron kleurframes levert.
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;
}
}
Deze methode voor het identificeren van de gewenste framebrongroep en framebronnen werkt voor eenvoudige gevallen, maar als u framebronnen wilt selecteren op basis van complexere criteria, kan het snel omslachtig worden. Een andere methode is het gebruik van Linq-syntaxis en anonieme objecten om de selectie te maken. In het volgende voorbeeld wordt de methode Select extension gebruikt om de MediaFrameSourceGroup-objecten in de lijst frameSourceGroups te transformeren naar een anoniem object met twee velden: sourceGroup, die de groep zelf vertegenwoordigt en colorSourceInfo, die de bron van het kleurenframe in de groep vertegenwoordigt. Het veld colorSourceInfo is ingesteld op het resultaat van FirstOrDefault, waarmee het eerste object wordt geselecteerd waarvoor het opgegeven predicaat als waar wordt bevonden. In dit geval is het predicaat waar als het stroomtype VideoPreview is, het brontype Kleur is en als de camera zich op het voorpaneel van het apparaat bevindt.
In de lijst met anonieme objecten die zijn geretourneerd uit de hierboven beschreven query, wordt de Where-extensiemethode gebruikt om alleen die objecten te selecteren waarvoor het colorSourceInfo-veld niet null is. Ten slotte wordt FirstOrDefault aangeroepen om het eerste item in de lijst te selecteren.
U kunt nu de velden van het geselecteerde object gebruiken om verwijzingen op te halen naar de geselecteerde MediaFrameSourceGroup en het MediaFrameSourceInfo-object dat de kleurencamera vertegenwoordigt. Deze worden later gebruikt om het MediaCapture-object te initialiseren en een MediaFrameReader te maken voor de geselecteerde bron. Ten slotte moet u testen of de brongroep null is, wat betekent dat het huidige apparaat niet beschikt over de aangevraagde opnamebronnen.
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;
}
In het volgende voorbeeld wordt een vergelijkbare techniek gebruikt zoals hierboven beschreven om een brongroep te selecteren die kleur-, diepte- en infraroodcamera's bevat.
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];
Opmerking
Vanaf Windows 10 versie 1803 kunt u de klasse MediaCaptureVideoProfile gebruiken om een mediaframebron te selecteren met een set gewenste mogelijkheden. Zie de sectie Videoprofielen gebruiken om later in dit artikel een framebron te selecteren voor meer informatie.
Het MediaCapture-object initialiseren om de geselecteerde framebrongroep te gebruiken
De volgende stap is het initialiseren van het MediaCapture-object om de framebrongroep te gebruiken die u in de vorige stap hebt geselecteerd. Maak een exemplaar van het MediaCapture-object door de constructor aan te roepen. Maak vervolgens een MediaCaptureInitializationSettings-object dat wordt gebruikt om het MediaCapture-object te initialiseren. In dit voorbeeld worden de volgende instellingen gebruikt:
- SourceGroup - Dit geeft aan welke bron groep je zult gebruiken om frames op te halen. Houd er rekening mee dat de brongroep een set mediaframebronnen definieert die tegelijkertijd kunnen worden gebruikt.
- SharingMode : hiermee wordt aan het systeem aangegeven of u exclusieve controle nodig hebt over de opnamebronapparaten. Als u dit instelt op ExclusiveControl, betekent dit dat u de instellingen van het opnameapparaat kunt wijzigen, zoals de indeling van de frames die het produceert, maar dit betekent dat als een andere app al exclusief beheer heeft, uw app mislukt wanneer het apparaat voor het vastleggen van media wordt geïnitialiseerd. Als u dit instelt op SharedReadOnly, kunt u frames ontvangen van de framebronnen, zelfs als ze worden gebruikt door een andere app, maar u kunt de instellingen voor de apparaten niet wijzigen.
- MemoryPreference : als u CPU opgeeft, gebruikt het systeem CPU-geheugen dat garandeert dat wanneer frames binnenkomen, ze beschikbaar zijn als SoftwareBitmap-objecten . Als u Auto opgeeft, kiest het systeem dynamisch de optimale geheugenlocatie om frames op te slaan. Als het systeem ervoor kiest gpu-geheugen te gebruiken, komen de mediaframes binnen als een IDirect3DSurface-object en niet als een SoftwareBitmap.
- StreamingCaptureMode - Stel deze in op Video om aan te geven dat audio niet hoeft te worden gestreamd.
Roep InitializeAsync aan om mediacapture te initialiseren met de gewenste instellingen. Zorg ervoor dat u dit aanroept binnen een try- blok voor het geval de initialisatie mislukt.
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;
}
De voorkeursindeling voor de framebron instellen
Als u de voorkeursindeling voor een framebron wilt instellen, moet u een MediaFrameSource--object ophalen dat de bron vertegenwoordigt. U krijgt dit object door toegang te krijgen tot de frameswoordenlijst van het geïnitialiseerde MediaCapture-object, waarbij u de id opgeeft van de framebron die u wilt gebruiken. Daarom hebben we het MediaFrameSourceInfo-object opgeslagen toen we een framebrongroep selecteerden.
De eigenschap MediaFrameSource.SupportedFormats bevat een lijst met MediaFrameFormat-objecten die de ondersteunde indelingen voor de framebron beschrijven. In dit voorbeeld wordt een indeling geselecteerd die een breedte van 1080 pixels heeft en frames in 32-bits RGB-indeling kan leveren. Met de extensiemethode FirstOrDefault wordt het eerste item in de lijst geselecteerd. Als de geselecteerde indeling null is, wordt de aangevraagde indeling niet ondersteund door de framebron. Als de indeling wordt ondersteund, kunt u aanvragen dat de bron deze indeling gebruikt door SetFormatAsync aan te roepen.
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);
Een framelezer maken voor de framebron
Gebruik een MediaFrameReader-om frames voor een mediaframebron te ontvangen.
MediaFrameReader m_mediaFrameReader;
Instantieer de framelezer door CreateFrameReaderAsync aan te roepen op uw geïnitialiseerde MediaCapture-object . Het eerste argument voor deze methode is de framebron waaruit u frames wilt ontvangen. U kunt een afzonderlijke framelezer maken voor elke framebron die u wilt gebruiken. Het tweede argument vertelt het systeem de uitvoerindeling waarin u frames wilt ontvangen. Hierdoor kunt u voorkomen dat u uw eigen conversies naar frames moet uitvoeren wanneer ze binnenkomen. Als u een indeling opgeeft die niet wordt ondersteund door de framebron, wordt er een uitzondering gegenereerd, dus zorg ervoor dat deze waarde zich in de verzameling SupportedFormats bevindt.
Nadat u de framelezer hebt gemaakt, registreert u een handler voor de FrameArrived-gebeurtenis die wordt gegenereerd wanneer een nieuw frame beschikbaar is vanuit de bron.
Laat het systeem frames van de bron lezen door StartAsync-aan te roepen.
m_mediaFrameReader = await m_mediaCapture.CreateFrameReaderAsync(colorFrameSource, MediaEncodingSubtypes.Argb32);
m_mediaFrameReader.FrameArrived += ColorFrameReader_FrameArrived;
await m_mediaFrameReader.StartAsync();
Het evenement 'Frame aangekomen' afhandelen
De gebeurtenis MediaFrameReader.FrameArrived wordt gegenereerd wanneer er een nieuw frame beschikbaar is. U kunt ervoor kiezen om elk frame te verwerken dat binnenkomt of alleen frames gebruikt wanneer u ze nodig hebt. Omdat de framelezer de gebeurtenis op een eigen thread genereert, moet u mogelijk bepaalde synchronisatielogica implementeren om ervoor te zorgen dat u niet probeert toegang te krijgen tot dezelfde gegevens uit meerdere threads. In deze sectie ziet u hoe u tekenkleurframes kunt synchroniseren met een afbeeldingsbeheer op een XAML-pagina. In dit scenario wordt de extra synchronisatiebeperking opgelost waarvoor alle updates voor XAML-besturingselementen moeten worden uitgevoerd op de UI-thread.
De eerste stap bij het weergeven van frames in XAML is het maken van een besturingselement voor afbeeldingen.
<Image x:Name="iFrameReaderImageControl" MaxWidth="300" MaxHeight="200"/>
Declareer op de code-behind-pagina een klasseledenvariabele van het type SoftwareBitmap die wordt gebruikt als een backbuffer waarnaar alle binnenkomende afbeeldingen worden gekopieerd. Houd er rekening mee dat de afbeeldingsgegevens zelf niet worden gekopieerd, alleen de objectverwijzingen. Declareer ook een Booleaanse waarde om bij te houden of onze UI-bewerking momenteel wordt uitgevoerd.
private SoftwareBitmap backBuffer;
private bool taskRunning = false;
Omdat de frames binnenkomen als SoftwareBitmap-objecten, moet u een SoftwareBitmapSource-object maken waarmee u een SoftwareBitmap kunt gebruiken als bron voor een XAML-besturingselement. U moet de afbeeldingsbron ergens in uw code instellen voordat u de framelezer start.
iFrameReaderImageControl.Source = new SoftwareBitmapSource();
Nu is het tijd om de FrameArrived eventhandler te implementeren. Wanneer de handler wordt aangeroepen, bevat de afzenderparameter een verwijzing naar het MediaFrameReader-object dat de gebeurtenis heeft gegenereerd. Roep TryAcquireLatestFrame op dit object aan om het nieuwste frame te verkrijgen. Zoals de naam al aangeeft, slaagt TryAcquireLatestFrame mogelijk niet in het retourneren van een frame. Dus, wanneer u toegang krijgt tot het VideoMediaFrame en vervolgens de SoftwareBitmap-eigenschappen, moet u testen op null. In dit voorbeeld is de voorwaardelijke operator null? wordt gebruikt voor toegang tot de SoftwareBitmap en vervolgens wordt het opgehaalde object gecontroleerd op null.
Het controle-element Afbeelding kan alleen afbeeldingen weergeven in BRGA8-formaat met vooraf vermenigvuldigde alfa of geen alfa. Als het binnenkomende frame niet in die indeling staat, wordt de statische methode Convert gebruikt om de software bitmap te converteren naar de juiste indeling.
Vervolgens wordt de methode Interlocked.Exchange gebruikt om de referentie van de inkomende bitmap te wisselen met die van de backbuffer-bitmap. Met deze methode worden deze verwijzingen in een atomische bewerking gewisseld die draadveilig is. Na het wisselen wordt de oude backbuffer-afbeelding, die zich nu in de softwareBitmap variabele bevindt, verwijderd om de resources op te schonen.
Vervolgens wordt de CoreDispatcher die is gekoppeld aan het element Image gebruikt om een taak te maken die wordt uitgevoerd op de UI-thread door RunAsync aan te roepen. Omdat de asynchrone taken binnen de taak worden uitgevoerd, wordt de lambda-expressie die aan RunAsync wordt doorgegeven, gedeclareerd met het asynchrone trefwoord.
Binnen de taak wordt de variabele _taskRunning gecontroleerd om ervoor te zorgen dat slechts één exemplaar van de taak tegelijk wordt uitgevoerd. Als de taak nog niet wordt uitgevoerd, _taskRunning wordt ingesteld op true om te voorkomen dat de taak opnieuw wordt gestart. In een terwijl lusstructuur, wordt Interlocked.Exchange aangeroepen om van de backbuffer naar een tijdelijke SoftwareBitmap te kopiëren totdat de backbufferafbeelding null is. Telkens wanneer de tijdelijke bitmap wordt ingevuld, wordt de broneigenschap van de afbeelding naar een SoftwareBitmapSource gecast en wordt SetBitmapAsync aangeroepen om de bron van de afbeelding in te stellen.
Ten slotte wordt de _taskRunning variabele weer ingesteld op false, zodat de taak opnieuw kan worden uitgevoerd wanneer de handler de volgende keer wordt aangeroepen.
Opmerking
Als u toegang krijgt tot de SoftwareBitmap of Direct3DSurface objecten die worden geleverd door de VideoMediaFrame eigenschap van een MediaFrameReference, maakt het systeem een sterke verwijzing naar deze objecten, wat betekent dat ze niet worden verwijderd wanneer u Dispose aanroept op de bijbehorende MediaFrameReference. U moet de verwijderingsmethode van de SoftwareBitmap of Direct3DSurface expliciet aanroepen om de objecten onmiddellijk te kunnen verwijderen. Anders zal de garbagecollector uiteindelijk het geheugen voor deze objecten vrij maken, maar u kunt niet weten wanneer dit gebeurt en als het aantal toegewezen bitmaps of oppervlakken de maximale hoeveelheid overschrijdt die door het systeem is toegestaan, stopt de stroom van nieuwe frames. U kunt opgehaalde frames bijvoorbeeld kopiëren met behulp van de methode SoftwareBitmap.Copy en vervolgens de oorspronkelijke frames vrijgeven om deze beperking te overwinnen. Als u ook de MediaFrameReader maakt door middel van de overload CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) of CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype), dan zijn de geretourneerde frames kopieën van de oorspronkelijke framegegevens, en zorgen ze er dus niet voor dat de framerverwerving stopt wanneer ze worden vastgehouden.
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();
}
}
Middelen opschonen
Wanneer u klaar bent met het lezen van frames, moet u de mediaframelezer stoppen door StopAsync aan te roepen, de registratie van de FrameArrived handler ongedaan te maken en het MediaCapture-object te verwijderen.
await m_mediaFrameReader.StopAsync();
m_mediaFrameReader.FrameArrived -= ColorFrameReader_FrameArrived;
m_mediaCapture.Dispose();
m_mediaCapture = null;
Zie Het cameravoorbeeld weergeven in een WinUI 3-app voor meer informatie over het opschonen van media-opnameobjecten wanneer uw toepassing wordt onderbroken.
De FrameRenderer-hulpmodule
In deze sectie vindt u de volledige codevermelding voor een helperklasse waarmee u de frames eenvoudig kunt weergeven op basis van kleur-, infrarood- en dieptebronnen in uw app. Normaal gesproken wilt u iets meer doen met diepte- en infraroodgegevens dan alleen op het scherm, maar deze helperklasse is een handig hulpmiddel voor het demonstreren van de functie framelezer en voor het opsporen van fouten in uw eigen framelezerimplementatie. De code in deze sectie is aangepast aan het voorbeeld van cameraframes.
De helperklasse FrameRenderer implementeert de volgende methoden.
- FrameRenderer-constructor : de constructor initialiseert de helperklasse voor het gebruik van het XAML-afbeeldingselement dat u doorgeeft voor het weergeven van mediaframes.
- ProcessFrame : met deze methode wordt een mediaframe weergegeven, vertegenwoordigd door een MediaFrameReference, in het element Afbeelding dat u hebt doorgegeven aan de constructor. Je moet deze methode meestal aanroepen vanuit je FrameArrived gebeurtenishandler, waarbij het frame wordt doorgegeven dat wordt geretourneerd door TryAcquireLatestFrame.
- ConvertToDisplayableImage - Met deze methoden wordt de indeling van het mediaframe gecontroleerd en, indien nodig, geconverteerd naar een weergavebare indeling. Voor kleurenafbeeldingen betekent dit dat de kleurnotatie BGRA8 moet zijn en dat de bitmap alfamodus voorgemultipliceerd is. Voor diepte- of infraroodframes wordt elke scanlijn verwerkt om de diepte- of infraroodwaarden te converteren naar een kleurovergang van psuedocolor, met behulp van de PsuedoColorHelper klasse die ook is opgenomen in de steekproef en hieronder wordt vermeld.
Opmerking
Als u pixelbewerkingen wilt uitvoeren op SoftwareBitmap-afbeeldingen , moet u toegang krijgen tot een systeemeigen geheugenbuffer. Hiervoor moet u de IMemoryBufferByteAccess COM-interface gebruiken die is opgenomen in de onderstaande codevermelding en moet u uw projecteigenschappen bijwerken om compilatie van onveilige code mogelijk te maken. Zie Bitmapafbeeldingen maken, bewerken en opslaan voor meer informatie.
[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;
});
}
}
}
Gebruik MultiSourceMediaFrameReader om tijd-gecorreleerde frames uit meerdere bronnen op te halen.
Vanaf Windows 10, versie 1607, kunt u MultiSourceMediaFrameReader gebruiken om frames met tijdcorreleatie van meerdere bronnen te ontvangen. Deze API maakt het eenvoudiger om verwerking uit te voeren waarvoor frames nodig zijn van meerdere bronnen die nabij elkaar in tijd zijn opgenomen, zoals bij gebruik van de klasse DepthCorrelatedCoördinaatMapper. Een beperking van het gebruik van deze nieuwe methode is dat frame-aangekomen gebeurtenissen alleen worden verhoogd met de snelheid van de traagste capture-bron. Extra frames van snellere bronnen worden verwijderd. Omdat het systeem verwacht dat frames van verschillende bronnen met verschillende snelheden binnenkomen, herkent het niet automatisch als een bron volledig is gestopt met het genereren van frames. De voorbeeldcode in deze sectie laat zien hoe u een gebeurtenis gebruikt om uw eigen time-outlogica te maken die wordt aangeroepen als gecorreleerde frames niet binnen een door de app gedefinieerde tijdslimiet binnenkomen.
De stappen voor het gebruik van MultiSourceMediaFrameReader zijn vergelijkbaar met de stappen voor het gebruik van MediaFrameReader die eerder in dit artikel zijn beschreven. In dit voorbeeld wordt een kleurbron en een dieptebron gebruikt. Declareer enkele tekenreeksvariabelen om de bron-id's van het mediaframe op te slaan die worden gebruikt om frames uit elke bron te selecteren. Declareer vervolgens een ManualResetEventSlim, een CancellationTokenSource en een EventHandler die wordt gebruikt voor het implementeren van time-outlogica voor het voorbeeld.
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;
Met behulp van de technieken die eerder in dit artikel zijn beschreven, voert u een query uit voor een MediaFrameSourceGroup die de kleur- en dieptebronnen bevat die vereist zijn voor dit voorbeeldscenario. Nadat u de gewenste framebrongroep hebt geselecteerd, haalt u de MediaFrameSourceInfo voor elke framebron op.
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];
Maak en initialiseer een MediaCapture-object , waarbij de geselecteerde framebrongroep wordt doorgegeven in de initialisatie-instellingen.
m_mediaCapture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings()
{
SourceGroup = selectedGroup,
SharingMode = MediaCaptureSharingMode.ExclusiveControl,
MemoryPreference = MediaCaptureMemoryPreference.Cpu,
StreamingCaptureMode = StreamingCaptureMode.Video
};
await m_mediaCapture.InitializeAsync(settings);
Nadat u het MediaCapture-object hebt geïnitialiseerd, haalt u MediaFrameSource-objecten op voor de kleur- en dieptecamera's. Sla de id voor elke bron op, zodat u het binnenkomende frame voor de bijbehorende bron kunt selecteren.
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;
Maak en initialiseer MultiSourceMediaFrameReader door CreateMultiSourceFrameReaderAsync aan te roepen en een matrix met framebronnen door te geven die de lezer gaat gebruiken. Registreer een event handler voor de FrameArrived gebeurtenis. In dit voorbeeld wordt een exemplaar gemaakt van de helperklasse FrameRenderer, zoals eerder in dit artikel is beschreven, om frames weer te geven aan een Afbeelding besturingselement. Start de framelezer door StartAsync aan te roepen.
Registreer een event handler voor het CorellationFailed event dat eerder in het voorbeeld is gedeclareerd. We geven deze gebeurtenis aan als een van de mediaframebronnen die worden gebruikt, stopt met het produceren van frames. Roep ten slotte Task.Run aan om de time-outhulpmethode, NotifyAboutCorrelationFailure, aan te roepen op een afzonderlijke thread. De implementatie van deze methode wordt verderop in dit artikel weergegeven.
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));
De gebeurtenis FrameArrived wordt gegenereerd wanneer een nieuw frame beschikbaar is vanuit alle mediaframebronnen die worden beheerd door de MultiSourceMediaFrameReader-. Dit betekent dat de gebeurtenis wordt gegenereerd op de frequentie van de langzaamste mediabron. Als één bron meerdere frames produceert in de tijd dat een tragere bron één frame produceert, worden de extra frames van de snelle bron verwijderd.
Haal de MultiSourceMediaFrameReference- op die is gekoppeld aan de gebeurtenis door TryAcquireLatestFrameaan te roepen. Haal de MediaFrameReference- dat is gekoppeld aan elke mediaframebron op door TryGetFrameReferenceBySourceIdaan te roepen, waarbij je de ID-tekenreeksen doorgeeft die zijn opgeslagen toen de framelezer werd geïnitialiseerd.
Roep de setmethode van het object ManualResetEventSlim aan om aan te geven dat frames zijn aangekomen. Deze gebeurtenis wordt gecontroleerd in de methode NotifyCorrelationFailure die wordt uitgevoerd in een afzonderlijke thread.
Voer ten slotte alle verwerkingen uit op de tijdcorrelerende mediaframes. In dit voorbeeld wordt simpelweg het frame uit de dieptebron weergegeven.
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);
}
}
De helpermethode NotifyCorrelationFailure is uitgevoerd op een afzonderlijke thread nadat de framelezer is gestart. In deze methode controleert u of het ontvangen frame-evenement is gesignaleerd. Onthoud dat we in de FrameArrived-handler deze gebeurtenis instellen wanneer een set gecorreleerde frames binnenkomt. Als de gebeurtenis niet is gesignaleerd voor een bepaalde door de app gedefinieerde periode - 5 seconden is een redelijke waarde - en de taak niet is geannuleerd met behulp van het CancellationToken, is het waarschijnlijk dat een van de mediaframebronnen het lezen van frames heeft gestopt. In dit geval wilt u doorgaans de framelezer afsluiten, dus trigger de app-gedefinieerde CorrelationFailed gebeurtenis. In de handler voor deze gebeurtenis kunt u de framelezer stoppen en de bijbehorende resources opschonen, zoals eerder in dit artikel is weergegeven.
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;
}
Gebruik de modus voor het verkrijgen van gebufferde frames om de volgorde van verkregen frames te behouden
Vanaf Windows 10 versie 1709 kunt u de eigenschap AcquisitionMode instellen van een MediaFrameReader- of MultiSourceMediaFrameReader op Gebufferd om de volgorde van frames die vanuit de framebron naar uw app worden doorgegeven, te behouden.
m_mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;
In de standaardovernamemodus , Realtime, als er meerdere frames worden verkregen uit de bron terwijl uw app nog steeds de FrameArrived-gebeurtenis voor een vorig frame verwerkt, verzendt het systeem uw app het laatst verkregen frame en zet extra frames neer die in de buffer wachten. Dit biedt uw app altijd het meest recente beschikbare frame. Dit is doorgaans de handigste modus voor realtime computer vision-toepassingen.
In gebufferde acquisitiemodus houdt het systeem alle frames in de buffer en verstrekt deze aan uw app via de FrameArrived event in de volgorde waarin ze zijn ontvangen. Wanneer in deze modus de buffer van het systeem voor frames is gevuld, stopt het systeem met het verkrijgen van nieuwe frames totdat de app de FrameArrived-gebeurtenis voor vorige frames voltooit, waardoor er meer ruimte in de buffer vrijkomt.
MediaSource gebruiken om frames weer te geven in een MediaPlayerElement
Vanaf Windows, versie 1709, kunt u frames weergeven die zijn verkregen uit een MediaFrameReader rechtstreeks in een MediaPlayerElement besturingselement op uw XAML-pagina. Dit wordt bereikt door mediasource.CreateFromMediaFrameSource te gebruiken om een MediaSource-object te maken dat rechtstreeks kan worden gebruikt door een MediaPlayer die is gekoppeld aan een MediaPlayerElement. Zie Audio en video afspelen met MediaPlayer voor gedetailleerde informatie over het werken met MediaPlayer en MediaPlayerElement.
In de volgende codevoorbeelden ziet u een eenvoudige implementatie waarmee de frames van een frontgerichte en back-gerichte camera tegelijkertijd op een XAML-pagina worden weergegeven.
Voeg eerst twee MediaPlayerElement-besturingselementen toe aan uw XAML-pagina.
<MediaPlayerElement x:Name="mediaPlayerElement1" Width="320" Height="240"/>
<MediaPlayerElement x:Name="mediaPlayerElement2" Width="320" Height="240"/>
Selecteer vervolgens door gebruik te maken van de technieken in de vorige secties van dit artikel een MediaFrameSourceGroup met MediaFrameSourceInfo objecten voor kleurencamera's op het voorpaneel en het achterpaneel. Houd er rekening mee dat de MediaPlayer niet automatisch frames converteert van niet-kleurindelingen, zoals diepte- of infraroodgegevens, naar kleurgegevens. Het gebruik van andere sensortypen kan onverwachte resultaten opleveren.
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];
Initialiseer het MediaCapture-object om de geselecteerde MediaFrameSourceGroup te gebruiken.
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;
}
Roep tot slot MediaSource.CreateFromMediaFrameSource aan om een MediaSource- te maken voor elke framebron met behulp van de eigenschap Id van de gekoppelde MediaFrameSourceInfo object om een van de framebronnen te selecteren in de MediaCapture object Verzameling FrameSources. Initialiseer een nieuw MediaPlayer-object en wijs het toe aan een MediaPlayerElement door SetMediaPlayer aan te roepen. Stel vervolgens de eigenschap Bron in op het zojuist gemaakte MediaSource-object .
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;
Videoprofielen gebruiken om een framebron te selecteren
Een cameraprofiel, vertegenwoordigd door een MediaCaptureVideoProfile-object , vertegenwoordigt een set mogelijkheden die een bepaald opnameapparaat biedt, zoals framesnelheden, resoluties of geavanceerde functies zoals HDR-opname. Een opnameapparaat ondersteunt mogelijk meerdere profielen, zodat u het profiel kunt selecteren dat is geoptimaliseerd voor uw opnamescenario. Vanaf Windows 10 versie 1803 kunt u MediaCaptureVideoProfile gebruiken om een mediaframebron met bepaalde mogelijkheden te selecteren voordat u het MediaCapture-object initialiseert. De volgende voorbeeldcode zoekt naar een videoprofiel dat HDR ondersteunt met WCG (Wide Color Gamut) en retourneert een MediaCaptureInitializationSettings-object dat kan worden gebruikt om de MediaCapture te initialiseren om het geselecteerde apparaat en profiel te gebruiken.
Roep eerst MediaFrameSourceGroup.FindAllAsync aan om een lijst op te halen met alle mediaframebrongroepen die beschikbaar zijn op het huidige apparaat. Loop door elke brongroep en roep MediaCapture.FindKnownVideoProfiles aan om een lijst op te halen met alle videoprofielen voor de huidige brongroep die ondersteuning bieden voor het opgegeven profiel, in dit geval HDR met WCG-foto. Als er een profiel wordt gevonden dat aan de criteria voldoet, maakt u een nieuw MediaCaptureInitializationSettings-object en stelt u het VideoProfile in op het geselecteerde profiel en de VideoDeviceIdop de id-eigenschap van de huidige mediaframebrongroep.
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;
}
}
Zie Cameraprofielen voor meer informatie over het gebruik van cameraprofielen.
Verwante onderwerpen
Windows developer