Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tento článek ukazuje, jak pomocí MediaFrameReader s MediaCapture získat multimediální snímky z jednoho nebo více dostupných zdrojů, včetně barev, hloubky a infračervených kamer, zvukových zařízení nebo dokonce vlastních zdrojů snímků, jako jsou zdroje, které vytvářejí snímky sledování skeletalu. Tato funkce je navržená tak, aby ji používaly aplikace, které provádějí zpracování snímků médií v reálném čase, jako jsou rozšířené reality a aplikace fotoaparátu s hloubkou.
Pokud chcete jednoduše zachytit video nebo fotky, jako je typická aplikace pro fotografie, pravděpodobně budete chtít použít jednu z dalších technik zachycení, které podporuje MediaCapture. Seznam dostupných technik zachycení médií a článků, které ukazují, jak je používat, najdete v tématu Kamera.
Poznámka:
Funkce probírané v tomto článku jsou dostupné jenom od Windows 10 verze 1607.
Poznámka:
Existuje ukázka univerzální aplikace pro Windows, která demonstruje použití MediaFrameReader k zobrazení snímků z různých zdrojů snímků, včetně barev, hloubky a infračervených fotoaparátů. Další informace naleznete v ukázce rámečků fotoaparátu .
Poznámka:
Ve Windows 10 verze 1803 byla zavedena nová sada rozhraní API pro použití MediaFrameReader se zvukovými daty. Další informace naleznete v tématu Zpracování zvukových snímků pomocí MediaFrameReader.
Výběr zdrojů rámců a skupin zdrojů rámců
Mnoho aplikací, které zpracovávají multimediální snímky, musí získat snímky z více zdrojů najednou, například barevné a hloubkové kamery zařízení. MediaFrameSourceGroup objekt představuje sadu zdrojů rámců médií, které lze použít současně. Zavolejte statickou metodu MediaFrameSourceGroup.FindAllAsync, abyste získali seznam všech skupin zdrojů rámce podporovaných aktuálním zařízením.
var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();
Můžete také vytvořit DeviceWatcher pomocí DeviceInformation.CreateWatcher a hodnoty vrácené z MediaFrameSourceGroup.GetDeviceSelector k přijímání oznámení, když se na zařízení změní dostupné skupiny zdrojů snímků, například když je externí fotoaparát připojen. Pro další informace viz Výčet zařízení.
MediaFrameSourceGroup má kolekci MediaFrameSourceInfo objektů, které popisují zdroje rámců zahrnuté ve skupině. Po načtení zdrojových skupin snímků dostupných v zařízení můžete vybrat skupinu, která zveřejňuje zdroje snímků, které vás zajímají.
Následující příklad ukazuje nejjednodušší způsob, jak vybrat zdrojovou skupinu snímků. Tento kód jednoduše prochází cyklem všechny dostupné skupiny a následně každou položku v kolekci SourceInfos. Každá MediaFrameSourceInfo je ověřena, aby se zjistilo, zda podporuje funkce, které potřebujeme. V tomto případě je vlastnost MediaStreamType kontrolována pro hodnotu VideoPreview, což znamená, že zařízení poskytuje stream náhledu videa a sourceKind vlastnost je zkontrolována pro hodnotu Color, což znamená, že zdroj poskytuje barevné rámečky.
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;
}
}
Tato metoda identifikace požadované skupiny zdrojů rámce a zdrojů snímků dobře funguje pro jednoduché případy, ale pokud chcete vybírat zdroje snímků na základě složitějších kritérií, může se rychle stát složitou. Další metodou je použití syntaxe Linq a anonymních objektů k výběru. Následující příklad používá rozšiřující metodu Select k transformaci objektů MediaFrameSourceGroup v seznamu frameSourceGroups do anonymního objektu se dvěma poli: sourceGroup, což představuje samotnou skupinu, a colorSourceInfo, které představuje zdroj barevného rámce ve skupině. Pole colorSourceInfo je nastaveno na výsledek FirstOrDefault, který vybere první objekt, pro který zadaný predikát vyhodnotí jako pravdivý. V tomto případě je predikát pravdivý, pokud je typ datového proudu VideoPreview, druh zdroje je Barva a pokud je kamera na předním panelu zařízení.
Ze seznamu anonymních objektů vrácených z dotazu popsaného výše se rozšiřující metoda Where používá k výběru pouze těch objektů, ve kterých hodnota pole colorSourceInfo není null. Nakonec je volán FirstOrDefault k výběru první položky v seznamu.
Nyní můžete pomocí polí vybraného objektu získat odkazy na vybranou MediaFrameSourceGroup a MediaFrameSourceInfo objekt představující barevnou kameru. Budou použity později k inicializaci MediaCapture objektu a vytvoření MediaFrameReader pro vybraný zdroj. Nakonec byste měli otestovat, jestli je zdrojová skupina null, což znamená, že aktuální zařízení nemá požadované zdroje zachycení.
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;
}
Následující příklad používá podobnou techniku, jak je popsáno výše, k výběru zdrojové skupiny, která obsahuje barvu, hloubku a infračervené kamery.
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];
Poznámka:
Počínaje Windows 10 verze 1803 můžete pomocí třídy MediaCaptureVideoProfile vybrat zdroj snímků médií se sadou požadovaných funkcí. Další informace najdete v části Použití profilů videa k výběru zdroje snímků dále v tomto článku.
Inicializace objektu MediaCapture pro použití vybrané skupiny zdrojů snímků
Dalším krokem je inicializace objektu MediaCapture pro použití zdrojové skupiny snímků, kterou jste vybrali v předchozím kroku. Vytvořte instanci objektu MediaCapture voláním konstruktoru. Dále vytvořte MediaCaptureInitializationSettings objekt, který se použije k inicializaci MediaCapture objektu. V tomto příkladu se používají následující nastavení:
- SourceGroup – To říká systému, kterou zdrojovou skupinu použijete k získání rámců. Pamatujte, že skupina zdrojů definuje soubor zdrojů mediálních snímků, které lze použít současně.
- SharingMode – Říká systému, jestli potřebujete výhradní kontrolu nad zdrojovými zařízeními pro zachytávání. Pokud tuto možnost nastavíte na ExclusiveControl, znamená to, že můžete změnit nastavení zařízení pro zachytávání, například formát snímků, které vytváří, ale to znamená, že pokud už má jiná aplikace výhradní kontrolu, vaše aplikace selže, když se pokusí inicializovat zařízení pro zachytávání médií. Pokud tuto možnost nastavíte na SharedReadOnly, můžete přijímat rámce ze zdrojů snímků, i když jsou používány jinou aplikací, ale nemůžete změnit nastavení pro zařízení.
- MemoryPreference – Pokud zadáte CPU, systém použije paměť CPU, která zaručuje, že snímky budou při doručení dostupné jako objekty SoftwareBitmap. Pokud zadáte možnost Automaticky, systém dynamicky zvolí optimální umístění paměti pro ukládání snímků. Pokud se systém rozhodne použít paměť GPU, mediální snímky dorazí jako IDirect3DSurface objekt, a ne jako SoftwareBitmap.
- StreamingCaptureMode – Nastavte tuto možnost na Video , aby bylo možné označit, že zvuk není potřeba streamovat.
Zavolejte InitializeAsync pro inicializaci MediaCapture s požadovaným nastavením. Nezapomeňte to volat v rámci try bloku v případě selhání inicializace.
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;
}
Nastavení upřednostňovaného formátu pro zdroj rámce
Pokud chcete nastavit upřednostňovaný formát pro zdroj rámce, potřebujete získat objekt MediaFrameSource představující zdroj. Tento objekt získáte tak, že přistupujete ke slovníku Frames objektu inicializovaného MediaCapture a zadáním identifikátoru zdroje rámce, který chcete použít. Proto jsme při výběru zdrojové skupiny rámců uložili objekt MediaFrameSourceInfo .
MediaFrameSource.SupportedFormats vlastnost obsahuje seznam MediaFrameFormat objekty popisující podporované formáty pro zdroj rámce. V tomto příkladu je vybraný formát, který má šířku 1080 pixelů a může poskytovat snímky ve 32bitovém formátu RGB. Metoda rozšíření FirstOrDefault vybere první položku v seznamu. Pokud je vybraný formát null, zdroj rámce požadovaný formát nepodporuje. Pokud je formát podporovaný, můžete požádat, aby zdroj používal tento formát voláním 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);
Vytvoření čtečky snímků pro zdroj snímků
Chcete-li přijímat rámce pro zdroj médií, použijte MediaFrameReader.
MediaFrameReader m_mediaFrameReader;
Inicializujte čtečku snímků voláním CreateFrameReaderAsync na vašem inicializovaném objektu MediaCapture. Prvním argumentem této metody je zdroj rámce, ze kterého chcete přijímat snímky. Pro každý zdroj snímků, který chcete použít, můžete vytvořit samostatnou čtečku snímků. Druhý argument říká systému výstupní formát, ve kterém mají být snímky doručeny. Může vás to ušetřit od nutnosti provádět vlastní převody na snímky při jejich doručení. Všimněte si, že pokud zadáte formát, který zdroj rámce nepodporuje, vyvolá se výjimka, takže se ujistěte, že tato hodnota je v kolekci SupportedFormats .
Po vytvoření čtečky snímků zaregistrujte obslužnou rutinu pro událost FrameArrived, která se vyvolá pokaždé, když je ze zdroje zpřístupněn nový snímek.
Řekněte systému, aby začal číst rámce ze zdroje voláním StartAsync.
m_mediaFrameReader = await m_mediaCapture.CreateFrameReaderAsync(colorFrameSource, MediaEncodingSubtypes.Argb32);
m_mediaFrameReader.FrameArrived += ColorFrameReader_FrameArrived;
await m_mediaFrameReader.StartAsync();
Zpracování události doručené rámce
Událost MediaFrameReader.FrameArrived je vyvolána vždy, když je k dispozici nový snímek. Můžete se rozhodnout zpracovat každý snímek, který dorazí, nebo použít snímky jen tehdy, když je potřebujete. Vzhledem k tomu, že čtečka rámců vyvolá událost ve vlastním vlákně, možná budete muset implementovat nějakou synchronizační logiku, abyste se ujistili, že se nepokoušíte získat přístup ke stejným datům z více vláken. V této části se dozvíte, jak synchronizovat barevné rámečky výkresu s ovládacím prvku obrázek na stránce XAML. Tento scénář řeší další omezení synchronizace, které vyžaduje, aby se všechny aktualizace ovládacích prvků XAML prováděly ve vlákně uživatelského rozhraní.
Prvním krokem při zobrazení rámců v XAML je vytvoření ovládacího prvku Image.
<Image x:Name="iFrameReaderImageControl" MaxWidth="300" MaxHeight="200"/>
Na stránce kódu na pozadí deklarujte proměnnou člena třídy typu SoftwareBitmap, která se použije jako záložní vyrovnávací paměť, do níž budou zkopírovány všechny příchozí obrázky. Všimněte si, že samotná data obrázku se nekopírují, pouze odkazy na objekty. Deklarujte také logickou hodnotu, která sleduje, jestli je naše operace uživatelského rozhraní aktuálně spuštěná.
private SoftwareBitmap backBuffer;
private bool taskRunning = false;
Vzhledem k tomu, že rámce budou doručeny jako SoftwareBitmap objekty, je třeba vytvořit SoftwareBitmapSource objekt, který umožňuje použití SoftwareBitmap jako zdroj pro XAML ovládací prvek. Než začnete čtečku snímků, měli byste zdroj obrázku nastavit někde ve svém kódu.
iFrameReaderImageControl.Source = new SoftwareBitmapSource();
Teď je čas implementovat obslužnou rutinu události FrameArrived. Při zavolání obslužné rutiny obsahuje sender parametr odkaz na objekt MediaFrameReader, který vyvolal událost. Zavolejte TryAcquireLatestFrame na tomto objektu, abyste se pokusili získat nejnovější snímek. Jak už název napovídá, TryAcquireLatestFrame se nemusí podařit vrátit rámec. Pokud tedy přistupujete k objektu VideoMediaFrame a potom vlastnosti SoftwareBitmap, nezapomeňte otestovat hodnotu null. V tomto příkladu je použit podmínkový operátor null. Pro přístup k SoftwareBitmap se používá a pak se zkontroluje, zda načtený objekt není nulový.
Ovládací prvek Obrázek může zobrazit pouze obrázky ve formátu BRGA8 s předem vynásobeným nebo bez alfa. Pokud dorazivší rámec není v daném formátu, použije se statická metoda Convert k převodu bitmapy softwaru do správného formátu.
Dále se metoda Interlocked.Exchange používá k výměně reference příchozí bitmapy s backbufferovou bitmapou. Tato metoda vymění tyto odkazy v atomické operaci, která je bezpečná pro přístup více vláken. Po prohození se stará image backbufferu, která je teď v softwareBitmap proměnná, zbaví k vyčištění svých prostředků.
Dále se CoreDispatcher, který je přidružen k elementu Image, používá k vytvoření úlohy, která se spustí ve vlákně uživatelského rozhraní pomocí volání RunAsync. Vzhledem k tomu, že asynchronní úlohy budou provedeny v rámci úlohy, výraz lambda předaný runAsync je deklarován pomocí asynchronního klíčového slova.
V rámci úlohy je zaškrtnutá proměnná _taskRunning , aby se zajistilo, že najednou běží jenom jedna instance úlohy. Pokud úloha ještě není spuštěná, _taskRunning je nastavená na hodnotu True, aby se zabránilo opětovnému spuštění úlohy. Při smyčky se volá Interlocked.Exchange ke kopírování z backbufferu do dočasné SoftwareBitmap, dokud nebude image backbuffer null. Při každém naplnění dočasného rastrového obrázku se vlastnost
Nakonec se proměnná _taskRunning nastaví zpět na false, aby bylo možné úlohu spustit znovu při příštím zavolání obslužné rutiny.
Poznámka:
Pokud přistupujete k objektům SoftwareBitmap nebo Direct3DSurface poskytovaným vlastností VideoMediaFrame z MediaFrameReference, systém vytvoří silný odkaz na tyto objekty, což znamená, že nebudou uvolněny při volání Dispose na obsahující MediaFrameReference. Je nutné explicitně volat metodu Dispose pro objekty SoftwareBitmap nebo Direct3DSurface, aby byly ihned odstraněny. Jinak garbage collector nakonec uvolní paměť pro tyto objekty, ale nemůžete vědět, kdy k tomu dojde, a pokud počet přidělených bitmap nebo povrchů překročí maximální povolené množství systému, zastaví se tok nových snímků. Načtené snímky můžete kopírovat například pomocí metody SoftwareBitmap.Copy a poté uvolnit původní snímky, abyste překonali toto omezení. Také pokud vytvoříte MediaFrameReader pomocí přetížení CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) nebo CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype), vrácené snímky jsou kopiemi původních snímkových dat a proto nezpůsobí zastavení pořízení snímku, pokud jsou uchovány.
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();
}
}
Úklid zdrojů
Až dokončíte čtení snímků, nezapomeňte zastavit čtečku snímků médií voláním StopAsync, odregistrujte obslužnou rutinu FrameArrived a uvolněte objekt MediaCapture.
await m_mediaFrameReader.StopAsync();
m_mediaFrameReader.FrameArrived -= ColorFrameReader_FrameArrived;
m_mediaCapture.Dispose();
m_mediaCapture = null;
Další informace o čištění objektů zachytávání médií při pozastavení aplikace naleznete v tématu Zobrazení náhledu kamery v aplikaci WinUI.
Pomocná třída FrameRenderer
Tato část obsahuje úplný výpis kódu pro pomocnou třídu, která usnadňuje zobrazení snímků z barev, infračervených a hloubkových zdrojů ve vaší aplikaci. Obvykle budete chtít udělat něco s hloubkovými a infračervenými daty, než jen zobrazit na obrazovce, ale tato pomocná třída je užitečný nástroj pro předvedení funkce čtečky snímků a ladění vlastní implementace čtečky snímků. Kód v této části je upraven z ukázky snímků fotoaparátu.
Pomocné třídy FrameRenderer implementuje následující metody.
- FrameRenderer konstruktor – konstruktor inicializuje pomocnou třídu pro použití XAML Image elementu, který předáte pro zobrazení mediálních snímků.
- ProcessFrame – Tato metoda zobrazí multimediální rámec reprezentovaný MediaFrameReference v elementu Image , který jste předali do konstruktoru. Obvykle byste měli tuto metodu volat z obslužné rutiny události FrameArrived a jako argument použít rámeček vrácený metodou TryAcquireLatestFrame.
- ConvertToDisplayableImage - Tato metoda zkontroluje formát rámečku média a v případě potřeby jej převede na zobrazitelný formát. Pro barevné obrázky to znamená, že formát barev je BGRA8 a že režim průhlednosti bitmapy je přednásobený. Pro hloubkové nebo infračervené snímky se každý řádek skenování zpracovává a převádí hloubkové nebo infračervené hodnoty na gradient pseudobarev pomocí třídy PsuedoColorHelper, která je také zahrnuta v ukázce a uvedená níže.
Poznámka:
Chcete-li provést manipulaci s pixely na obrazech SoftwareBitmap, musíte přistupovat k nativní vyrovnávací paměti. K tomu musíte použít rozhraní COM IMemoryBufferByteAccess, které je součástí níže uvedeného výpisu kódu, a musíte aktualizovat vlastnosti projektu, aby byla možná kompilace nezabezpečeného kódu. Další informace naleznete v tématu Vytváření, úpravy a ukládání rastrových obrázků.
[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;
});
}
}
}
Použijte MultiSourceMediaFrameReader k získání časově korelovaných snímků z více zdrojů
Počínaje Windows 10 verze 1607 můžete použít MultiSourceMediaFrameReader k příjmu časových korelací snímků z více zdrojů. Toto rozhraní API usnadňuje zpracování, které vyžaduje snímky z více zdrojů, které byly pořízeny v těsné časové blízkosti, jako je použití DepthCorrelatedCoordinateMapper třídy. Jedním z omezení použití této nové metody je to, že události příjezdu snímku jsou vyvolány jen rychlostí nejpomalejšího zdroje zachycení. Nadbytečné snímky z rychlejších zdrojů se zahodí. Vzhledem k tomu, že systém očekává, že snímky přijdou z různých zdrojů různými rychlostmi, nerozpozná se automaticky, pokud zdroj úplně přestal generovat snímky. Ukázkový kód v této části ukazuje, jak pomocí události vytvořit vlastní logiku časového limitu, která se vyvolá, pokud korelační rámce nedorazí do časového limitu definovaného aplikací.
Kroky pro použití MultiSourceMediaFrameReader jsou podobné krokům použití MediaFrameReader popsané výše v tomto článku. Tento příklad použije zdroj barev a hloubkový zdroj. Deklarujte některé řetězcové proměnné pro uložení zdrojových ID snímků médií, které se použijí k výběru rámců z každého zdroje. Dále deklarujte ManualResetEventSlim, CancellationTokenSource a EventHandler , která se použije k implementaci logiky časového limitu pro příklad.
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;
Pomocí technik popsaných výše v tomto článku zadejte dotaz na skupinu MediaFrameSourceGroup , která obsahuje zdroje barev a hloubky vyžadované pro tento ukázkový scénář. Po výběru požadované skupiny zdrojů rámce získejte MediaFrameSourceInfo pro každý zdroj.
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];
Vytvořte a inicializujte MediaCapture objekt, přičemž v nastavení inicializace předáte vybranou skupinu zdrojů rámce.
m_mediaCapture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings()
{
SourceGroup = selectedGroup,
SharingMode = MediaCaptureSharingMode.ExclusiveControl,
MemoryPreference = MediaCaptureMemoryPreference.Cpu,
StreamingCaptureMode = StreamingCaptureMode.Video
};
await m_mediaCapture.InitializeAsync(settings);
Po inicializaci objektu MediaCapture načtěte objekty MediaFrameSource pro barevné a hloubkové kamery. Uložte ID pro každý zdroj, abyste mohli vybrat přicházející rámec pro odpovídající zdroj.
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;
Vytvořte a inicializujte MultiSourceMediaFrameReader voláním CreateMultiSourceFrameReaderAsync a předáním pole zdrojů rámců, které bude čtenář používat. Zaregistrujte obslužnou rutinu pro událost FrameArrived. Tento příklad vytvoří instanci pomocné třídy FrameRenderer, která je popsána výše v tomto článku, pro vykreslení rámů v ovládacím prvku Image. Spusťte čtečku snímků voláním metody StartAsync.
Zaregistrujte obslužnou rutinu události pro událost CorrelationFailed, která byla deklarována dříve v příkladu. Tuto událost signalizujeme, pokud některý ze zdrojů mediálních rámců, které se používají, přestane produkovat video snímky. Nakonec spusťte Task.Run, abyste spustili pomocnou metodu časového limitu, NotifyAboutCorrelationFailure, na samostatném vlákně. Implementace této metody je uvedena dále v tomto článku.
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));
Událost FrameArrived je vyvolána vždy, když je nový rámec k dispozici ze všech zdrojů médií, které spravuje MultiSourceMediaFrameReader. To znamená, že událost bude vyvolána podle kadence nejpomalejšího mediálního zdroje. Pokud jeden zdroj vytvoří více snímků v době, kdy pomalejší zdroj vytvoří jeden rámec, nadbytečné snímky z rychlého zdroje se zahodí.
Získejte MultiSourceMediaFrameReference přidružené k události voláním TryAcquireLatestFrame. Získejte MediaFrameReference přidružené ke každému zdroji médií voláním TryGetFrameReferenceBySourceId, předáním řetězců ID uložených při inicializaci čtečky snímků.
Pro volejte metodu Set objektu ManualResetEventSlim, abyste signalizovali, že rámce dorazily. Tuto událost zkontrolujeme v metodě NotifyCorrelationFailure, která běží v samostatném vlákně.
Nakonec proveďte jakékoli zpracování s multimediálními snímky časově korelovanými. Tento příklad jednoduše zobrazí snímek ze zdroje hloubky.
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);
}
}
Pomocná metoda NotifyCorrelationFailure byla spuštěna na samostatném vlákně po zahájení činnosti čtečky snímků. Zkontrolujte v této metodě, zda byla událost přijatého rámce signalizována. Nezapomeňte, že v obslužné funkci FrameArrived nastavíme tuto událost vždy, když dorazí sada souvislých snímků. Pokud událost nebyla signalizována po určitou dobu definovanou aplikací – 5 sekund je přiměřenou hodnotou – a úkol nebyl zrušen pomocí CancellationTokenu, je pravděpodobné, že některý ze zdrojů snímků médií přestal číst snímky. V takovém případě obvykle chcete vypnout čtečku snímků, takže vyvolejte událost definovanou aplikací CorrelationFailed. V obslužné rutině pro tuto událost můžete zastavit čtečku snímků a vyčistit její přidružené prostředky, jak bylo dříve uvedeno v tomto článku.
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;
}
Použití režimu pořízení rámce v vyrovnávací paměti k zachování posloupnosti získaných snímků
Počínaje Windows 10, verzí 1709, můžete nastavit vlastnost AcquisitionMode čtečky médií MediaFrameReader nebo MultiSourceMediaFrameReader na hodnotu Buffered, abyste zachovali posloupnost snímků předaných vaší aplikaci ze zdroje snímků.
m_mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;
Ve výchozím režimu pořízení Realtime, pokud se ze zdroje získá více snímků, když vaše aplikace stále zpracovává událost FrameArrived pro předchozí snímek, systém odešle vaší aplikaci naposledy získaný snímek a vypustí další snímky čekající ve vyrovnávací paměti. To vaší aplikaci poskytuje vždy nejnovější dostupný snímek. To je obvykle nejužitečnější režim pro aplikace počítačového zpracování obrazu v reálném čase.
Ve vyrovnávacím režimu získávání systém zachová všechny snímky ve vyrovnávací paměti a poskytne je vaší aplikaci prostřednictvím události FrameArrived v přijatém pořadí. Všimněte si, že když je v tomto režimu zaplněna vyrovnávací paměť systému pro snímky, systém přestane získávat nové snímky, dokud vaše aplikace nedokončí událost FrameArrived pro předchozí snímky, což uvolní více místa v vyrovnávací paměti.
Použití MediaSource k zobrazení snímků v MediaPlayerElement
Od Windows verze 1709 můžete zobrazit snímky získané z MediaFrameReader přímo v ovládacím prvku MediaPlayerElement na stránce XAML. Toho lze dosáhnout pomocí MediaSource.CreateFromMediaFrameSource k vytvoření objektu MediaSource, který lze přímo použít v MediaPlayer přidruženému k MediaPlayerElement. Podrobné informace o práci s MediaPlayer a MediaPlayerElement naleznete v tématu Přehrávání zvuku a videa s MediaPlayer.
Následující příklady kódu ukazují jednoduchou implementaci, která zobrazuje snímky z přední a zadní kamery současně na stránce XAML.
Nejprve na stránku XAML přidejte dva Ovládací prvky MediaPlayerElement.
<MediaPlayerElement x:Name="mediaPlayerElement1" Width="320" Height="240"/>
<MediaPlayerElement x:Name="mediaPlayerElement2" Width="320" Height="240"/>
Dále pomocí technik zobrazených v předchozích částech tohoto článku vyberte MediaFrameSourceGroup, které obsahuje MediaFrameSourceInfo objekty pro barevné kamery na předním a zadním panelu. Všimněte si, že MediaPlayer nepřevádí automaticky rámce z nebarevných formátů, jako je hloubka nebo infračervená data, na barevná data. Použití jiných typů senzorů může vést k neočekávaným výsledkům.
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];
Inicializujte objekt MediaCapture pro použití vybrané skupiny 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;
}
Nakonec zavolejte MediaSource.CreateFromMediaFrameSource k vytvoření MediaSource pro každý zdroj snímku pomocí vlastnosti Id přidruženého objektu MediaFrameSourceInfo k vybrání jednoho ze zdrojů snímků v kolekci MediaCapture objektu FrameSources. Inicializujte nový objekt MediaPlayer a přiřaďte ho k MediaPlayerElement voláním SetMediaPlayer. Potom nastavte vlastnost Source na nově vytvořený objekt MediaSource .
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;
Použití profilů videa k výběru zdroje snímků
Profil kamery reprezentovaný objektem MediaCaptureVideoProfile představuje sadu funkcí, které konkrétní zachytácí zařízení poskytuje, například snímkové frekvence, rozlišení nebo pokročilé funkce, jako je HDR capture. Zachytácí zařízení může podporovat více profilů, takže můžete vybrat ten, který je optimalizovaný pro váš scénář zachycení. Počínaje Windows 10, verzí 1803, můžete použít MediaCaptureVideoProfile k výběru zdroje snímku média s konkrétními schopnostmi před inicializací objektu MediaCapture. Následující příklad kódu hledá profil videa, který podporuje HDR s wide Color Gamut (WCG) a vrátí MediaCaptureInitializationSettings objekt, který lze použít k inicializaci MediaCapture pro použití vybraného zařízení a profilu.
Nejprve zavolejte MediaFrameSourceGroup.FindAllAsync, abyste získali seznam všech zdrojových skupin snímků médií dostupných na aktuálním zařízení. Projděte každou zdrojovou skupinu a zavolejte MediaCapture.FindKnownVideoProfiles, abyste získali seznam všech profilů videa pro aktuální zdrojovou skupinu, které podporují zadaný profil, v tomto případě HDR s WCG fotografií. Pokud je nalezen profil, který splňuje kritéria, vytvořte nový MediaCaptureInitializationSettings objekt a nastavte VideoProfile na vybraný profil a VideoDeviceId na Id aktuální skupiny snímků médií.
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;
}
}
Další informace o používání profilů fotoaparátu najdete v tématu Profily fotoaparátu.
Související témata
- Fotoaparát
- Základní pořizování fotografií, videa a zvukových záznamů s MediaCapture
- rámy fotoaparátu vzorové
Windows developer