本文介绍如何将 MediaFrameReader 与 MediaCapture 配合使用,从一个或多个可用源获取媒体帧,这些源包括颜色、深度和红外相机、音频设备,甚至自定义帧源(例如生成骨架跟踪帧)。 此功能旨在供执行媒体帧实时处理的应用使用,例如增强现实和深度感知相机应用。
如果只想捕获视频或照片(如典型的摄影应用),则可能需要使用 MediaCapture支持的其他捕获技术之一。 有关可用媒体捕获技术和说明如何使用它们的文章的列表,请参阅 相机。
注释
本文中讨论的功能仅从 Windows 10 版本 1607 开始可用。
注释
有一个通用 Windows 应用示例,演示如何使用 MediaFrameReader 显示来自不同帧源的帧,包括颜色、深度和红外相机。 有关详细信息,请参阅 相机帧示例。
注释
Windows 10 版本 1803 中引入了一组用于将 MediaFrameReader 与音频数据配合使用的新 API。 有关详细信息,请参阅 使用 MediaFrameReader处理音频帧。
选择帧源和帧源组
处理媒体帧的许多应用需要同时从多个源获取帧,例如设备的颜色和深度相机。 MediaFrameSourceGroup 对象表示一组可同时使用的媒体帧源。 MediaFrameSourceGroup.FindAllAsync 调用静态方法,以获取当前设备支持的所有帧源组的列表。
var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();
还可以使用 DeviceInformation.CreateWatcher 和从 MediaFrameSourceGroup.GetDeviceSelector 返回的值来创建 DeviceWatcher,以在设备上可用的帧源组发生更改时(例如插入外部相机时)接收通知。 有关详细信息,请参阅 枚举设备。
MediaFrameSourceGroup 具有 MediaFrameSourceInfo 对象的集合,这些对象描述组中包含的帧源。 检索设备上可用的帧源组后,可以选择公开你感兴趣的帧源的组。
以下示例显示了选择帧源组的最简单方法。 此代码只需遍历所有可用组,然后循环访问 SourceInfos 集合中的每个项。 检查每个 MediaFrameSourceInfo,以查看它是否支持我们寻求的功能。 在这种情况下,将验证 MediaStreamType 属性的值是否为 VideoPreview,表明设备提供视频预览流;且验证 SourceKind 属性的值是否为 Color,意味着源提供颜色帧。
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;
}
}
用于识别所需帧源组和帧源的方法适用于简单情况,但如果要根据更复杂的条件选择帧源,则可能会很快变得繁琐。 另一种方法是使用 Linq 语法和匿名对象进行选择。 以下示例使用 Select 扩展方法将 MediaFrameSourceGroup 对象在 frameSourceGroups 列表中转换为具有两个字段的匿名对象:sourceGroup表示组本身, 和 colorSourceInfo表示组中的颜色帧源。 colorSourceInfo 字段设置为 FirstOrDefault的结果,该字段选择提供谓词解析为 true 的第一个对象。 在这种情况下,如果流类型 VideoPreview,则源类型 颜色,并且相机位于设备的前面板上,则谓词为 true。
从上述查询返回的匿名对象列表中,Where 扩展方法仅选择 colorSourceInfo 字段不为 null 的对象。 最后,调用 FirstOrDefault 以选择列表中的第一项。
现在,可以使用所选对象的字段来获取对所选 MediaFrameSourceGroup 和表示颜色相机的 MediaFrameSourceInfo 对象的引用。 稍后将使用这些对象初始化 MediaCapture 对象,并为所选源创建 MediaFrameReader。 最后,应测试源组是否为 null,这意味着当前设备没有请求的捕获源。
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;
}
以下示例使用上述类似的技术来选择包含颜色、深度和红外相机的源组。
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];
注释
从 Windows 10 版本 1803 开始,可以使用 MediaCaptureVideoProfile 类选择具有一组所需功能的媒体帧源。 有关详细信息,请参阅本文后面部分 使用视频配置文件选择帧源。
初始化 MediaCapture 对象以使用所选帧源组
下一步是初始化 MediaCapture 对象,以使用在上一步中选择的帧源组。 通过调用构造函数创建 MediaCapture 对象的实例。 接下来,创建 MediaCaptureInitializationSettings 对象,该对象将用于初始化 MediaCapture 对象。 在此示例中,使用以下设置:
- SourceGroup - 这告知系统将用于获取帧的源组。 请记住,源组定义了一组可同时使用的媒体帧源。
- SharingMode - 这告知系统是否需要对捕获源设备进行独占控制。 如果将其设置为 ExclusiveControl,则意味着你可以更改捕获设备的设置,例如它生成的帧的格式,但这意味着,如果另一个应用已经拥有独占控制,则应用在尝试初始化媒体捕获设备时会失败。 如果将其设置为 SharedReadOnly,则即使帧源正在被其他应用使用,您也可以接收来自这些帧源的帧,但无法更改设备的设置。
- 如果指定MemoryPreference ,系统将使用 CPU 内存,以确保帧到达时,它们将作为 softwareBitmap 对象CPU 可用。 如果指定 自动,系统将动态选择存储帧的最佳内存位置。 如果系统选择使用 GPU 内存,媒体帧将作为 IDirect3DSurface 对象到达,而不是 SoftwareBitmap。 - StreamingCaptureMode - 将其设置为 视频 以指示不需要流式传输音频。
请调用 InitializeAsync,以用您所需的设置初始化 MediaCapture。 请确保在 try 块中调用此函数,以防初始化失败。
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;
}
设置帧源的首选格式
若要设置帧源的首选格式,需要获取表示源的 MediaFrameSource 对象。 可以通过访问初始化 MediaCapture 对象的 Frames 字典来获取此对象,并指定要使用的帧源的标识符。 这就是为什么我们在选择帧源组时保存 MediaFrameSourceInfo 对象的原因。
MediaFrameSource.SupportedFormats 属性包含描述帧源支持格式的 MediaFrameFormat 对象的列表。 在此示例中,所选格式的宽度为 1080 像素,并且可以以 32 位 RGB 格式提供帧。 FirstOrDefault 扩展方法选择列表中的第一个条目。 如果所选格式为 null,则帧源不支持所请求的格式。 如果支持该格式,可以通过调用 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);
为帧源创建帧读取器
若要接收媒体帧源的帧,请使用 MediaFrameReader。
MediaFrameReader m_mediaFrameReader;
通过在初始化 MediaCapture 对象上调用 CreateFrameReaderAsync 来实例化帧读取器。 此方法的第一个参数是您希望接收帧的帧源。 可以为要使用的每个帧源创建单独的帧阅读器。 第二个参数告知系统希望帧以哪种输出格式到达。 这可以让你省去在帧到达时自行进行转换。 请注意,如果指定框架源不支持的格式,将引发异常,因此请确保此值位于 SupportedFormats 集合中。
创建帧读取器后,为 FrameArrived 事件注册一个处理程序,该事件会在源提供新帧时触发。
通过调用 StartAsync,告诉系统开始从源读取帧。
m_mediaFrameReader = await m_mediaCapture.CreateFrameReaderAsync(colorFrameSource, MediaEncodingSubtypes.Argb32);
m_mediaFrameReader.FrameArrived += ColorFrameReader_FrameArrived;
await m_mediaFrameReader.StartAsync();
处理帧到达事件
每当有新帧可用时,就会触发 MediaFrameReader.FrameArrived 事件。 可以选择处理每个到达的帧,或者仅在需要时使用帧。 由于帧读取器在其自己的线程上引发事件,因此可能需要实现一些同步逻辑,以确保你不会尝试从多个线程访问相同的数据。 本部分介绍如何将绘图颜色帧同步到 XAML 页面中的图像控件。 此方案解决了需要在 UI 线程上执行 XAML 控件的所有更新的其他同步约束。
在 XAML 中显示帧的第一步是创建图像控件。
<Image x:Name="iFrameReaderImageControl" MaxWidth="300" MaxHeight="200"/>
在代码后置页中,声明一个 SoftwareBitmap 类型的类成员变量,该变量将用作所有传入图像都将复制到其中的后台缓冲区。 请注意,不会复制图像数据本身,只复制对象引用。 此外,声明一个布尔值,用于跟踪 UI 操作当前是否正在运行。
private SoftwareBitmap backBuffer;
private bool taskRunning = false;
由于帧将作为 SoftwareBitmap 对象到达,因此需要创建一个 SoftwareBitmapSource 对象,这样就可以使用 SoftwareBitmap 作为 XAML Control的来源。 在开始帧读取器之前,应在代码中的某个位置设置图像源。
iFrameReaderImageControl.Source = new SoftwareBitmapSource();
现在,是时候实现 FrameArrived 事件处理程序了。 调用处理程序时,发送方 参数包含对引发事件的 MediaFrameReader 对象的引用。 在此对象上调用 TryAcquireLatestFrame,以尝试获取最新的帧。 顾名思义,TryAcquireLatestFrame 可能无法成功返回帧。 因此,在访问 VideoMediaFrame 和 SoftwareBitmap 属性时,请务必检查它们是否为 null。 在此示例中,空条件运算符(?) 用于访问 SoftwareBitmap,然后检查检索到的对象是否为 null。
Image 控件只能以 BRGA8 格式显示预乘或无 alpha 的图像。 如果到达帧不是该格式,则静态方法 Convert 用于将软件位图转换为正确的格式。
接下来,Interlocked.Exchange 方法用于将到达位图的引用与后缓冲区位图交换。 此方法通过线程安全的原子操作来交换这些引用。 交换后,旧的后备缓冲区图像(现在位于 softwareBitmap 变量中)将被释放以清理资源。
接下来,与 Image 元素关联的 CoreDispatcher 用于通过调用 RunAsync在 UI 线程上运行的任务。 由于异步任务将在任务中执行,因此传递给 RunAsync 的 lambda 表达式使用 async 关键字声明。
在任务执行过程中,检查 _taskRunning 变量,以确保同一时间只有一个任务实例在运行。 如果任务尚未运行,则 _taskRunning 设置为 true,以防止任务再次运行。 在 循环的
最后,将 _taskRunning 变量设置为 false,以便在下次调用处理程序时再次运行任务。
注释
如果访问 SoftwareBitmap 或 Direct3DSurface 由 MediaFrameReference的 VideoMediaFrame 属性提供的对象,则系统将创建对这些对象的强引用, 这意味着在对包含 MediaFrameReference调用 Dispose 时,它们不会被释放。 必须显式地直接调用 Dispose 方法以立即释放 SoftwareBitmap 或 Direct3DSurface 对象。 否则,垃圾回收器最终将释放这些对象的内存,但你不知道何时会发生,如果分配的位图或图面数超过系统允许的最大数量,新帧的流将停止。 例如,可以使用 SoftwareBitmap.Copy 方法复制检索的帧,然后释放原始帧以克服此限制。 此外,如果使用重载 CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype, Windows.Graphics.Imaging.BitmapSize outputSize) 或 CreateFrameReaderAsync(Windows.Media.Capture.Frames.MediaFrameSource inputSource, System.String outputSubtype)创建 MediaFrameReader,返回的帧是原始帧数据的副本,因此在保留帧时不会导致帧获取中断。
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();
}
}
清理资源
在读取帧完成后,请务必通过调用 StopAsync、注销 FrameArrived 处理程序以及处置 MediaCapture 对象来停止媒体帧读取器。
await m_mediaFrameReader.StopAsync();
m_mediaFrameReader.FrameArrived -= ColorFrameReader_FrameArrived;
m_mediaCapture.Dispose();
m_mediaCapture = null;
有关在应用程序暂停时清理媒体捕获对象的详细信息,请参阅 WinUI 3 应用中显示相机预览。
FrameRenderer 辅助类
本部分提供了一个帮助程序类的完整代码列表,使你可以轻松地在应用中显示来自颜色、红外和深度源的帧。 通常,你希望使用深度和红外数据执行更多操作,而不仅仅是将其显示到屏幕,但此帮助程序类是一个有用的工具,用于演示帧读取器功能和调试你自己的帧阅读器实现。 本节中的代码改编自 相机帧示例。
FrameRenderer 辅助类实现以下方法。
- FrameRenderer 构造函数 - 该构造函数初始化帮助类,以便使用您传递给它的 XAML Image 元素来显示媒体帧。
- ProcessFrame - 此方法在您传递给构造函数的 Image 元素中,显示由 MediaFrameReference表示的媒体帧。 通常应该在你的 FrameArrived 事件处理程序中调用此方法,并传入由 TryAcquireLatestFrame返回的帧。
- ConvertToDisplayableImage - 此方法检查媒体帧的格式,并在必要时将其转换为可显示的格式。 对于彩色图像,这意味着确保颜色格式为 BGRA8,并且位图的 alpha 模式是预乘过的。 对于深度或红外帧,处理每个扫描线,以使用示例中包含的 PseudoColorHelper 类将深度或红外值转换为伪彩色渐变。
注释
若要对 SoftwareBitmap 图像执行像素操作,必须访问本机内存缓冲区。 若要执行此操作,必须使用下面代码列表中包括的 IMemoryBufferByteAccess COM 接口,并且必须更新项目属性以允许编译不安全的代码。 有关详细信息,请参阅 创建、编辑和保存位图图像。
[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;
});
}
}
}
使用 MultiSourceMediaFrameReader 从多个源获取与时间相关的帧
从 Windows 10 版本 1607 开始,可以使用 MultiSourceMediaFrameReader 从多个源接收与时间相关的帧。 通过此 API,可以更容易进行需要来自多个源的帧且时间上接近的处理,例如使用 DepthCorrelatedCoordinateMapper 类。 使用此新方法的一个限制是,帧到达事件的触发频率取决于最慢捕获源的速度。 将删除来自更快源的额外帧。 此外,由于系统期望帧以不同的速率从不同的源到达,因此它不会自动识别源是否已完全停止生成帧。 本部分中的示例代码展示了如何使用事件来创建自定义的超时逻辑,如果在应用程序定义的时间限制内没有接收到相关帧,则会调用该逻辑。
使用 MultiSourceMediaFrameReader 的步骤类似于本文前面所述的使用 MediaFrameReader 的步骤。 此示例将使用颜色源和深度源。 声明一些字符串变量来存储将用于从每个源中选择帧的媒体帧源 ID。 接下来,您需要声明用于实现示例超时逻辑的 ManualResetEventSlim、CancellationTokenSource以及 EventHandler。
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;
使用本文前面所述的技术,查询包含本示例所需颜色和深度源的 MediaFrameSourceGroup。 在选择所需的帧源组后,为每个帧源获取 MediaFrameSourceInfo。
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];
创建和初始化 MediaCapture 对象,并在初始化设置中传递所选帧源组。
m_mediaCapture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings()
{
SourceGroup = selectedGroup,
SharingMode = MediaCaptureSharingMode.ExclusiveControl,
MemoryPreference = MediaCaptureMemoryPreference.Cpu,
StreamingCaptureMode = StreamingCaptureMode.Video
};
await m_mediaCapture.InitializeAsync(settings);
初始化 MediaCapture 对象后,检索颜色和深度相机的 MediaFrameSource 对象。 存储每个源的 ID,以便为相应的源选择到达帧。
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;
通过调用 CreateMultiSourceFrameReaderAsync 并传递读取器将使用的帧源数组来创建和初始化 MultiSourceMediaFrameReader。 为 FrameArrived 事件注册事件处理程序。 此示例创建一种在本文前面介绍的 FrameRenderer 辅助类的实例,以将帧渲染到 图像 控制控件。 通过调用 StartAsync来启动帧阅读器。
为示例中前面声明的 CorrelationFailed 事件注册一个事件处理程序。 如果使用的媒体帧源之一停止生成帧,我们将发出此事件信号。 最后,调用 Task.Run,以在单独的线程上调用超时助手方法 NotifyAboutCorrelationFailure。 本文稍后将介绍此方法的实现。
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));
每当从由 MultiSourceMediaFrameReader管理的所有媒体帧源获取新帧时,都会触发 FrameArrived 事件。 这意味着事件将按照最慢媒体源的速度引发。 如果一个源在较慢的源生成一帧的时间内生成多个帧,那么来自较快源的多余帧将被丢弃。
要获取与事件关联的 MultiSourceMediaFrameReference,请调用 TryAcquireLatestFrame。 通过调用 TryGetFrameReferenceBySourceId(传入初始化帧读取器时存储的 ID 字符串)获取与每个媒体帧源关联的 MediaFrameReference。
调用 ManualResetEventSlim 对象的 Set 方法,以指示帧已到达。 我们将在 NotifyCorrelationFailure 方法中检查此事件,该方法在单独的线程中运行。
最后,对时间相关的媒体帧执行任何处理。 此示例仅显示来自深度源的帧。
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);
}
}
启动帧阅读器后,NotifyCorrelationFailure 辅助方法在单独的线程上运行。 在此方法中,检查帧接收的事件是否已发出信号。 请记住,在 FrameArrived 处理程序中,每当一组相关帧到达时,我们设置此事件。 如果某个应用定义的时间段内(例如 5 秒)未发出事件信号,并且未使用 CancellationToken取消该任务,那么很可能其中一个媒体帧源已经停止了读取帧。 在这种情况下,您通常需要关闭帧读取器,因此触发应用程序定义的 CorrelationFailed 事件。 在此事件的处理程序中,可以停止帧读取器并清理其关联的资源,如本文前面所示。
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;
}
使用缓冲帧获取模式来保留获取的帧序列
从 Windows 10 版本 1709 开始,你可以将 MediaFrameReader 或 MultiSourceMediaFrameReader 的 AcquisitionMode 属性设置为 缓冲 ,以保留从帧源传递到应用的帧序列。
m_mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;
在默认的采集模式下,实时,如果应用在处理上一帧的 FrameArrived 事件期间从源中获取了多个帧,系统将把最近获取的帧发送给应用,并删除缓冲区中等待的其他帧。 这随时为应用提供最新的可用帧。 这通常是实时计算机视觉应用程序最有用的模式。
在 缓冲 获取模式中,系统将保留缓冲区中的所有帧,并按照收到的顺序,通过 FrameArrived 事件提供给您的应用。 请注意,在此模式下,当系统为帧填充缓冲区时,系统将停止获取新帧,直到应用完成之前帧的 FrameArrived 事件,从而释放缓冲区中的更多空间。
使用 MediaSource 在 MediaPlayerElement 中显示帧
从 Windows 版本 1709 开始,可以直接在 XAML 页面中的 MediaPlayerElement 控件中显示从 MediaFrameReader 获取的帧。 这是通过使用 MediaSource.CreateFromMediaFrameSource 创建 MediaSource 对象来实现的,该对象可以直接由与 MediaPlayerElement关联的 MediaPlayer 使用。 有关使用 MediaPlayer 和 MediaPlayerElement的详细信息,请参阅 使用 MediaPlayer播放音频和视频。
下面的代码示例演示了一个简单的实现,该实现在 XAML 页中同时显示来自正面和反向相机的帧。
首先,将两个 MediaPlayerElement 控件添加到 XAML 页面。
<MediaPlayerElement x:Name="mediaPlayerElement1" Width="320" Height="240"/>
<MediaPlayerElement x:Name="mediaPlayerElement2" Width="320" Height="240"/>
接下来,使用本文前面部分中所述技术,选择一个 MediaFrameSourceGroup,其中包含用于前面板和后面板彩色相机的 MediaFrameSourceInfo 对象。 请注意,MediaPlayer 不会自动将帧从非颜色格式(如深度或红外数据)转换为颜色数据。 使用其他传感器类型可能会产生意外的结果。
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];
初始化 MediaCapture 对象以使用所选的 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;
}
最后,调用 MediaSource.CreateFromMediaFrameSource,使用关联的 MediaFrameSourceInfo 对象的 Id 属性为每个帧源创建 MediaSource,以选择 MediaCapture 对象的 FrameSources 集合中的一个帧源。 初始化新的 MediaPlayer 对象,并通过调用 SetMediaPlayer将其分配给 MediaPlayerElement。 然后将 Source 属性设置为新创建的 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;
使用视频配置文件选择帧源
由 MediaCaptureVideoProfile 对象表示的相机配置文件表示特定捕获设备提供的一组功能,例如帧速率、分辨率或 HDR 捕获等高级功能。 捕获设备可能支持多个配置文件,允许你选择针对捕获方案优化的配置文件。 从 Windows 10 版本 1803 开始,可以使用 MediaCaptureVideoProfile 来选择具有特定功能的媒体帧源,然后再初始化 MediaCapture 对象。 以下示例代码查找支持具有 Wide Color Gamut (WCG) HDR 的视频配置文件,并返回一个 MediaCaptureInitializationSettings 对象,该对象可用于初始化 MediaCapture 以使用所选设备和配置文件。
首先,调用 MediaFrameSourceGroup.FindAllAsync 以获取当前设备上可用的所有媒体帧源组的列表。 循环遍历每个源组并调用 MediaCapture.FindKnownVideoProfiles,获取支持指定配置文件(在本例中,HDR 结合 WCG 照片)的当前源组的所有视频配置文件列表。 如果找到符合条件的配置文件,请创建一个新的 MediaCaptureInitializationSettings 对象,将 VideoProfile 设置为选定的配置文件,并将 VideoDeviceId 设置为当前媒体帧源组的 Id 属性。
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;
}
}
有关使用相机配置文件的详细信息,请参阅 相机配置文件。
相关主题