65.9K
CodeProject 正在变化。 阅读更多。
Home

CaptureManager SDK - 从网络摄像头捕获、录制和流式传输视频和音频

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (213投票s)

2015年8月11日

CPOL

64分钟阅读

viewsIcon

744530

downloadIcon

94230

本文介绍了我的项目,该项目通过 Microsoft Media Foundation 在 Windows 操作系统上捕获视频和音频源。

CaptureManager 项目的长期开发过程中,我取得了显著进展,并收到了许多关于将其实现到其他项目(包括开源项目)中的问题。经过一番思考,我决定在开源许可下发布 CaptureManager SDK 的源代码,供软件开发人员友好社区免费使用。CaptureManager SDK 的源代码可以通过此链接下载。

我很高兴收到您对其有用性的反馈,以及您关于改进 SDK 功能的意见和通过以下链接向其他开发人员提出的建议

最有趣的演示程序如下所示

  • WPFVirtualCamera - 用于将 CaptureManager SDK 集成到第三方网络摄像头应用程序(如 Skype 或 Zoom)中的 DirectShow 过滤器
  • WPFStreamer(在 Facebook(SSL 加密连接:Facebook 要求所有实时视频流都通过 RTMPS,或在 TLS/SSL 连接上的实时消息协议 (RTMP) - 这将有助于通过加密保护流媒体内容,从而确保更高的数据完整性)和 YouTube 网站上进行实时视频和音频流式传输)- 免费版。
  • WPFRTSPClient - 免费版
  • WPFRtspServer - 免费版

CaptureManager SDK 版本

CaptureManager 帮助文件

引言

本文介绍了我的项目,该项目通过 Microsoft Media Foundation 在 Windows 操作系统上捕获视频和音频源。

我花了很多时间解决视频和音频数据处理的不同任务,并研究了在 Windows 系列操作系统上捕获视频和音频的技术。DirectShow 长期以来一直是 Windows 系列操作系统上捕获实时视频的主要技术。然而,自 Vista 操作系统以来,微软引入了一个新的框架来处理视频和音频 - Microsoft Media Foundation,自 Windows7 操作系统以来,它具有通过 USB 视频类驱动程序和线路音频输入处理网络摄像头的特性。我将这项技术纳入了许多项目,并撰写了两篇关于它的文章

这些文章介绍了用于捕获网络摄像头和抓取图像帧的简单库,但 Microsoft Media Foundation 是一个功能更强大的框架。经过一番思考,我决定编写一个处理实时视频和音频的新解决方案,该解决方案比我之前的解决方案更灵活,并且可以发挥 Microsoft Media Foundation 更大的能力(或力量 :))。该解决方案可以与 Microsoft 解决方案竞争。该解决方案,可以被称为任何开发人员以前见过的最好的解决方案。

背景

当我遇到一个不寻常的任务时,我产生了基于 Microsoft Media Foundation 编写一个处理网络摄像头的新解决方案的想法。所以,这个任务没有解决,但我写了一些代码,并决定继续开发这个解决方案。

一开始,该解决方案只包含少数几个类,只允许执行少数几个功能,但在增加了一些对该解决方案的要求后,我决定编写一个简单的 SDK,允许轻松地为新任务进行捕获配置,并通过实现 Microsoft Media Foundation 和 CaptureManager 的接口,将新开发的代码注入其中。

因此,我获得了这个仅通过 Microsoft Media Foundation 从网络摄像头捕获、录制和流式传输实时视频和音频的 SDK。

Using the Code

CaptureManager SDK 演示程序

OpenGLWebCamViewerViaCOMServer

此演示程序仅展示了通过 OpenGL 渲染捕获和查看网络摄像头的简单配置。

要使用 CaptureManager,需要调用 CaptureManager COM 服务器中的适当接口。为了设置打印输出日志目标,需要获取 ILogPrintOutControl 接口

	// get COM log interface
	CComPtrCustom<iclassfactory> lCoLogPrintOut;

	HRESULT lhresult = CoGetClassObject
    (CLSID_CoLogPrintOut, CLSCTX_INPROC_SERVER, nullptr, IID_PPV_ARGS(&lCoLogPrintOut));

	if (FAILED(lhresult))
		return lhresult;

	CComPtrCustom<ilogprintoutcontrol> lLogPrintOutControl;

	lCoLogPrintOut->LockServer(true);

	lhresult = lCoLogPrintOut->CreateInstance(
		nullptr,
		IID_PPV_ARGS(&lLogPrintOutControl));

	if (FAILED(lhresult))
		return lhresult;

	// set log file for info
	lhresult = lLogPrintOutControl->addPrintOutDestination(
		(DWORD)INFO_LEVEL,
		L"Log.txt");

	if (FAILED(lhresult))
		return lhresult;

为了获取 CaptureManager 的主要方法,需要获取 ICaptureManagerControl 接口

	CComPtrCustom<IClassFactory> lCoCaptureManager;

	lhresult = CoGetClassObject
    (CLSID_CoCaptureManager, CLSCTX_INPROC_SERVER, nullptr, 
     IID_PPV_ARGS(&lCoCaptureManager));

	if (FAILED(lhresult))
		return lhresult;

	lCoCaptureManager->LockServer(true);

	// get ICaptureManagerControl interfrace
	CComPtrCustom<ICaptureManagerControl> lCaptureManagerControl;

	lhresult = lCoCaptureManager->CreateInstance(
		nullptr,
		IID_PPV_ARGS(&lCaptureManagerControl));

	if (FAILED(lhresult))
		return lhresult;

此演示程序的拓扑结构如下所示

您可以通过以下链接获取此演示程序

EVRWebCapViewerViaCOMServer

此演示程序展示了通过 CaptureManager COM 服务器使用增强型视频渲染器 (Enhanced Video Renderer) 渲染实时视频的方式。

此代码允许将此 CaptureManager SDK 轻松集成到已创建的 Windows 应用程序中 - 它只需要一个用于网络摄像头显示的 GUI 元素句柄。此代码控制 GUI 元素的调整大小,并修改渲染器以更改视频大小和比例。

您可以通过以下链接获取此演示程序

CaptureManagerToCSharpProxy

此演示程序展示了将 CaptureManager.dll 与 C# 项目链接的方式。在此项目中,CaptureManagerSDK 由 C# 类 CaptureManager 封装。该类隐藏了与 COM 接口的直接操作以及非托管代码中图像数据的封送处理。为了解决方案的灵活性,该类将 CaptureManager.dll 直接上传到进程中,并在不调用 COM 基础设施的情况下获取 COM 对象 - 这允许在系统中无需任何注册即可使用 CaptureManager COM 服务器。

您可以从以下链接获取此演示程序

WPFSourceInfoViewer

此演示程序展示了从源收集信息并以可读形式呈现信息的方式。源信息以 XML 文档的形式呈现。使用 XML 格式从 COM 服务器传输信息有一些原因

  1. 简单结构 - Microsoft Media Foundation 使用大量的 GUID 常量和类型,您需要了解它们才能理解信息,但在此解决方案中,所有这些都以友好的形式呈现。
  2. 易于从 COM 服务器传输
  3. 易于解析 - XPath 表达式允许获取几乎所有需要的信息并呈现它,这比使用 static 定义的类和信息列表更容易。
  4. 在大多数情况下,用户需要读取源支持哪些功能并选择其中之一。
  5. XML 文档可轻松将源集成到不同演示模型(例如 MVVM)的代码中

此演示程序可从以下链接获取

WPFWebViewerCall

此演示程序展示了通过 C# - WPF 上的 COM 服务器,通过从 WPF 线程调用视频帧来查看网络摄像头实时视频的方式。此代码从 CaptureManager 获取 XML 文档并解析它以获取一些信息:设备名称、数据流类型和支持的分辨率列表。此代码通过 ISampleGrabber 接口启动视频帧的抓取,并以同步模式工作。

您可以通过以下链接获取此演示程序

WPFWebViewerCallback

此演示程序展示了通过 C# - WPF 上的 COM 服务器,通过从 CaptureManager 线程调用视频帧来查看网络摄像头实时视频的方式。此代码从 CaptureManager 获取 XML 文档并解析它以获取一些信息:设备名称、数据流类型和支持的分辨率列表。此代码将 update 方法注册到具有 ISampleGrabberCallback 接口的类中,并将此类注入 COM 服务器。当 CaptureManager 获取新视频帧时,它会调用 WPF update 方法并发布消息以更新帧。在这种情况下,WPF 线程不会因抓取任务而过载。

此演示程序可从以下链接获取

WPFWebViewerEVR

此演示程序展示了通过 C# - WPF 上的 COM 服务器,使用 CaptureManager 线程中的增强型视频渲染器 (Enhanced Video Renderer) 查看网络摄像头实时视频的方式。此代码从 CaptureManager 获取 XML 文档并解析它以获取一些信息:设备名称、数据流类型和支持的分辨率列表。此代码通过设置集成 WindowForms 面板的 HWNDCaptureManager 获取 EVR 节点。在这种情况下,所有视频帧处理都在 WPF 线程之外执行。

此演示程序可从以下链接获取

WPFRecorder

此演示程序展示了如何使用 CaptureManager SDK,通过 C# - WPF 上的 COM 服务器,捕获、编码和录制/广播来自网络摄像头、麦克风、桌面屏幕和扬声器的视频和音频。

此演示程序的代码展示了通过 COM 服务器接口使用 CaptureManager SDK 的正确算法。数据流的终点可以是文件接收器或字节流接收器。后者比简单地保存到文件更有趣。在此演示程序中,字节流接收器以具有环回地址和 http 端口:8080 的 TCP 服务器的形式实现。在 C++ 演示程序中可以找到类似的实现,但此演示程序有一个显著区别 - 在此演示程序中,TCP 服务器是在 C#-.NET Framework 中实现的。CaptureManager SDK 用 C++ 编写,并使用 C 上的 Windows Media Foundation,但是,有一个好消息 - CaptureManager SDK 需要实现 Windows Media Foundation 接口 IMFByteStream 来流式传输字节。在 C# 上实现 C 接口 IMFByteStream 不需要任何特定的 Windows Media Foundation 函数 - 它需要定义接口:IMFByteStreamIMFAsyncCallbackIMFAsyncResult;以及 enum 常量

  • MFAsyncCallbackQueue
  • MFASync
  • MFByteStreamSeekingFlags
  • MFByteStreamSeekOrigin
  • MFByteStreamCapabilities
  • UnmanagedNameAttribute

这些接口的实现可以在文件 NetworkStreamSink.cs 中找到,但我想提请您注意以下几点

  1. IMFByteStream 接口中,有四个已实现的方法
    • GetCapabilities
    • Write
    • BeginWrite
    • EndWrite

    在第一个中,代码设置 IMFByteStream 接口的实现类型 - 可写但不可寻。方法 Write 用于同步将数据写入流,而方法 BeginWriteEndWrite 用于异步写入。但是,有一些重要的时刻:方法 Write 在开始时调用一次 - 它写入元数据流的头部:编码器类型、流的数量、流的名称和其他元数据。异步写入需要按以下顺序执行方法:BeginWrite、参数 IMFAsyncCallback pCallback.InvokeEndWrite,但方法 BeginWriteEndWrite 的调用可以被同一个互斥锁锁定。这意味着参数 IMFAsyncCallback pCallback.Invoke 必须在单独的线程中执行 - 例如,通过 ThreadPool.QueueUserWorkItem

  2. 在 TCP 服务器的实现中,我使用了 async 调用 BeginAcceptTcpClient,并在每次连接开始时写入头部数据 - 这允许将任意数量的客户端连接到媒体流服务器。
    public void Start()
    {
    try
    {
    tcpListener = new TcpListener(Configuration.IPAddress, Configuration.Port);
    tcpListener.Start();
    tcpListener.BeginAcceptTcpClient(
    new AsyncCallback(callBack),
    tcpListener);
    }
    catch (Exception e)
    {
    }
    }
    
    /// <summary>
    /// Stops the WebServer thread
    /// </summary>
    
    public void Stop()
    {
    try
    {
    tcpListener.Stop();
    foreach (var item in mClientBag)
    {
    item.Value.Client.Close();
    item.Value.Client.Dispose();
    item.Value.Close();
    }
    tcpListener.Server.Dispose();
    }
    catch (Exception e)
    {
    }
    }
    
    private void callBack(IAsyncResult aIAsyncResult)
    {
    TcpListener ltcpListener = (TcpListener)aIAsyncResult.AsyncState;
    if (ltcpListener == null)
    return;
    TcpClient lclient = null;
    try
    {
    lclient = ltcpListener.EndAcceptTcpClient(aIAsyncResult);
    }
    catch (Exception exc)
    {
    return;
    }
    if (lclient != null && lclient.Client.Connected)
    {
    StreamReader streamReader = new StreamReader(lclient.GetStream());
    
    // Read full request with client header
    StringBuilder receivedData = new StringBuilder();
    while (streamReader.Peek() > -1)
    receivedData.Append(streamReader.ReadLine());
    string request = GetRequest(receivedData.ToString());
    if (!SuportedMethod(request))
    {
    SendError(StatusCode.BadRequest, "Only GET is supported.", lclient);
    lclient.Client.Close();
    lclient.Close();
    }
    else
    {
    Socket socket = lclient.Client;
    if (socket.Connected)
    {
    SendHeader(StatusCode.OK, lclient);
    lock (this)
    {
    if (mHeaderMemory != null)
    {
    int sentBytes = socket.Send(mHeaderMemory);
    }
    mClientBag[lclient] = lclient;
    }
    }
    }
    }
    ltcpListener.BeginAcceptTcpClient(
    new AsyncCallback(callBack),
    ltcpListener);
    }
  3. 头部包含字节流的 MIME 类型,这允许在未来的版本中将相同的解决方案用于任何类型的媒体容器 - ASF、MP4、MKV。
    private void SendHeader(string mimeType, long totalBytes, 
                            StatusCode statusCode, TcpClient aTcpClient)
    {
    StringBuilder header = new StringBuilder();
    header.Append(string.Format("HTTP/1.1 {0}\r\n", GetStatusCode(statusCode)));
    header.Append(string.Format("Content-Type: {0}\r\n", mimeType));
    header.Append(string.Format("Accept-Ranges: bytes\r\n"));
    header.Append(string.Format("Server: {0}\r\n", Configuration.ServerName));
    header.Append(string.Format("Connection: close\r\n"));
    header.Append(string.Format("Content-Length: {0}\r\n", totalBytes));
    header.Append("\r\n");
    
    SendToClient(header.ToString(), aTcpClient);
    }

此演示程序可从以下链接获取

QtMinGWDemo

此演示程序展示了如何使用 Qt 框架通过 COM 服务器在 C# - WPF 上使用 CaptureManager SDK 捕获、编码和录制/广播来自网络摄像头、麦克风、桌面屏幕和扬声器的实时视频和音频。在 Windows 操作系统上,有 Visual Studio 编译器的 Qt 版本,但此演示展示了如何将 CaptureManager SDK 与 MinGW 编译器的 Qt 版本一起使用。当然,此演示可以重新编译为 Visual Studio 编译器,但 MinGW 编译器版本展示了此 SDK 的灵活性以及与许多其他编译器的兼容性。此演示包括用于从网络摄像头捕获实时视频并通过调用样本、通过从 CaptureManager SDK 内部线程回调视图更新代码或通过小部件的 HWND 直接绘制图像来查看的代码。

此演示中的另一个示例展示了将源、编码器和接收器连接到同一个处理管道中的方式。此演示展示了将视频和音频录制到文件中的方式,以及通过 Qt 框架的 QTcpServerQTcpSocket 类编写网络流广播到互联网的代码的真实代码。

您可以从以下链接获取此演示程序

CaptureManagerSDKPythonDemo

此演示程序展示了在 Windows OS 上的 Python 应用程序中,使用 CaptureManager SDK 捕获网络摄像头实时视频的方式。CaptureManager 使用桌面 Windows OS 上的 Microsoft Media Foundation 和 COM 技术构建。与任何 COM 服务器一样,CaptureManager 可以通过直接调用接口或类型库集成到项目中。然而,动态类型编程语言在使用类型库时存在一些问题。为了解决这些问题,我包含了 IDispatch 并为许多类编写了实现,但某些项目需要处理内存块指针,这可能会导致动态类型的问题。为了简化解决方案,我仅为有限的功能实现了 IDispatch 接口。此演示程序展示了选择源、编码器、通过 HWND 渲染视频以及保存到文件的功能。

此演示程序可从以下链接下载

WPFWebCamShot

此演示程序展示了使用 CaptureManager SDK 从视频流中捕获单个帧的方式。CaptureManager SDK 为 SampleGrabberCallSinkFactory 引入了新的模式 - PULL

此模式与 SYNCASYNC 模式不同。SYNCASYNC 模式以连续模式工作 - 它们在自动模式下接收当前样本后发送下一个样本的请求,而不会阻塞。它导致请求和样本队列,如下图所示

这种类型对于呈现视频很有用,但捕获单个帧的任务面临下一个困难 - 99% 的帧被捕获、解码并传递到接收器,但客户代码不从接收器中获取它。此任务在现实中可能存在 - 例如,当相机每秒产生 30 帧时,每秒捕获一个帧 - 在这种情况下,CPU 浪费时间和精力在 29 帧上,这些帧将被丢弃。对于此类任务,新模式 - PULL 可能更有用。接收器仅在客户代码请求新的单个帧时发送请求

它允许更有效地利用 CPU 资源仅用于一个任务。新模式也可用于许多图像识别项目 - 此类项目通常以低于相机可产生的帧速率处理帧,但视频管道将花费大量 CPU 资源在不会被处理的帧上。PULL 模式允许从视频管道中释放一些 CPU 资源用于程序的其他部分。

此演示程序可从以下链接下载

WPFWebCamSerialShots

此演示程序展示了如何使用 CaptureManager SDK 捕获视频流的最后连续帧。CaptureManager SDK 1.2.0 引入了新型流控制节点 - 样本累加器节点

它包含两个样本累加器节点,分别存储 5 个和 10 个最后样本。其思想是,在现实世界中,拍照并不是在事件发生的那一刻执行的。人类对事件的反应、按下拍照按钮的过程、将事件从硬件层发送到客户代码层以及请求单个帧需要一些时间——从 500 毫秒到 1 秒。这导致丢失了作为拍照原因的重要时刻。样本累加器节点可以通过累积视频流中的最后样本来补偿这种时间延迟。这些样本可以通过 SampleGrabberCallSinkFactoryPULL 模式获取。它发送请求

并接收最后样本

这对于解决某些问题可能很有用,但是样本的累积需要可以快速扩展的内存缓冲区——例如,为了保持 10 个最后 1080p 格式的 RGB32 图像样本,大约需要 60 MB——在这种视频中累积一秒钟可能需要大约 200 MB 或更多。当然,可以创建具有不同方案的样本累加器——例如,每三分之一——这意味着只保留视频流中每三分之一的样本。

您可以通过以下链接获取此演示程序

CaptureManagerToJavaProxy

此演示程序展示了将 CaptureManager.dll 与 Java VM 链接的方式。此项目通过调用 JNI 函数演示了 COM CaptureManager 的 Java 封装。它包括 CaptureManagerToJavaProxy 框架,用于将 Java 代码反射到 CaptureManagerNativeProxy.dll 的 C 代码上。CaptureManagerToJavaProxy 框架包括从 JAR 调用本地代码的代码 - 它允许构建包含打包的 CaptureManagerNativeProxy.dllCaptureManager.dll 的可运行 JAR 文件。此项目使用 Microsoft Media Foundation - 它将 Java 项目限制为仅从 Windows 7 开始的 Windows 操作系统。

此代理程序可从以下链接获取

CaptureManagerSDKJavaxDemo

此演示程序展示了通过 Java VM 在 Windows 操作系统上使用网络摄像头的方式。此演示程序允许通过 Javax.swing GUI 框架呈现网络摄像头的实时视频。它允许获取 GUI 组件的 HWND 描述符并使用 EVR 进行渲染,而无需通过 Java VM JNI 封送图像 - 这可以节省 CPU 功耗

此外,此演示程序还包括将网络摄像头视频录制到 ASF 视频文件的功能

您可以通过编译可运行 JAR 文件或下载包含 CaptureManagerSDKJavaxDemo.jarCaptureManagerSDKJavaxDemo.jar.zip 来测试此演示程序。VM 的架构 - x86 和 x86-64 - 可能是一个重要的关注点。在大多数情况下,Java 编译的代码可以在两种架构上执行。但是,CaptureManagerToJavaProxy 通过 JNI 调用本机代码 - 这会导致以下问题:用于 x86 的带 JNI 的本机 DLL 无法上传到 x86-64 Java VM,而用于 x86-64 的带 JNI 的本机 DLL 无法上传到 x86 Java VM。这些问题通过在运行时查找 Java VM 架构并将适合架构的 CaptureManagerNativeProxy.dll 上传到 Java VM 来解决。这意味着 JAR 包含两个 CaptureManagerNativeProxy.dll 和两个 CaptureManager.dll

我认为你会觉得它非常适合 - 当然,它只在 Windows 7 及更高版本的 Windows 操作系统上可用。

此演示程序可从以下链接下载

WindowsFormsDemo

此演示程序展示了在 WindowsForms GUI 上使用网络摄像头的方式。

此演示程序允许通过 WindowsForms GUI 框架呈现网络摄像头的实时视频。它允许获取 GUI 组件的 HWND 描述符并使用 EVR 进行渲染,而无需通过 C# 封送数据 - 这可以节省 CPU 功耗

此演示展示了如何选择不同类型的源。目前,CaptureManager 支持三种源类型

  • 网络摄像头
  • 屏幕 DirectShow
  • Crossbar

这些源是不同的,并且可能同时连接了其中一些源类型设备。这导致需要一个灵活选择合适源类型组的解决方案。以下生成 XPath 查询的代码在此演示中实现了此功能

string lXPath = "//*[";

if(toolStripMenuItem1.Checked)
{
    lXPath += "Source.Attributes/Attribute
    [@Name='MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_CATEGORY']
    /SingleValue[@Value='CLSID_WebcamInterfaceDeviceCategory']";
}
                        
if (toolStripMenuItem2.Checked)
{
    if (toolStripMenuItem1.Checked)
        lXPath += "or ";

    lXPath += "Source.Attributes/Attribute
    [@Name='MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_HW_SOURCE']
    /SingleValue[@Value='Software device']";
}

if (dSCrossbarToolStripMenuItem.Checked)
{
    if (toolStripMenuItem1.Checked || toolStripMenuItem2.Checked)
        lXPath += "or ";

        lXPath += "Source.Attributes/Attribute
        [@Name='MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_CATEGORY']
        /SingleValue[@Value='CLSID_VideoInputDeviceCategory']";
}                      
            
lXPath += "]";

此外,此演示还包括从媒体源录制视频和音频的代码

此演示程序可从以下链接获取

WaterMarkInjectorDemo

此演示程序展示了自定义 Microsoft Media Foundation Transform 与 CaptureManager 配合使用的方式。此演示程序允许通过特殊自定义 Microsoft Media Foundation Transform(其工作方式类似于“效果过滤器”)将图像“注入”实时视频。

您可以通过以下链接获取此演示程序

TextInjectorDemo

此演示程序展示了自定义 Microsoft Media Foundation Transform 与 CaptureManager 配合使用的方式。此演示程序允许通过特殊的自定义 Microsoft Media Foundation Transform(其工作方式类似于“效果滤镜”)将文本字符串“注入”实时视频并更改其内容。

文本字符串内容可以在应用程序端以连续模式更改,并立即显示结果。在此演示程序中,它是通过以下代码完成的

wchar_t ltext[MAXBYTE];

/* program main loop */
while (!bQuit)
{
	/* check for messages */
	if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	{
		/* handle or dispatch messages */
		if (msg.message == WM_QUIT)
		{
			bQuit = TRUE;
		}
		else
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	_itow_s(++lFrameCount, ltext, 10);

	lITextWriter->writeText(ltext);

	Sleep(200);
}

您可以从以下链接获取此演示程序

WPFImageViewer

此演示程序展示了使用 CaptureManagerToCSharpProxyICaptureProcessorIInitilaizeCaptureSourceICurrentMediaTypeISourceRequestResult 接口的方式。此演示程序在 ImageCaptureProcessor 类中实现了 ICaptureProcessor 接口 - 此类允许将自定义数据“注入”捕获会话。ICaptureProcessor 接口具有以下方法

  • void initilaize(IInitilaizeCaptureSource IInitilaizeCaptureSource)
  • void pause()
  • void setCurrentMediaType(ICurrentMediaType aICurrentMediaType)
  • void shutdown()
  • void sourceRequest(ISourceRequestResult aISourceRequestResult)
  • void start(long aStartPositionInHundredNanosecondUnits, ref Guid aGUIDTimeFormat)
  • void stop()

方法 void initilaize(IInitilaizeCaptureSource IInitilaizeCaptureSource) 必须在 IInitilaizeCaptureSource 参数中设置包含捕获源描述的 XML 文本 string - 此数据从图像信息中获取。方法 void setCurrentMediaType(ICurrentMediaType aICurrentMediaType) 允许选择正确的流索引和正确的媒体类型。方法 void sourceRequest(ISourceRequestResult aISourceRequestResult)CaptureManage SDK 执行,用于获取以 XML 文本 string 描述中定义的格式的原始数据。此类 XML 文档的格式如下

<!--?xml version='1.0' encoding='UTF-8'?-->
<presentationdescriptor streamcount="1">
	<presentationdescriptor.attributes title="Attributes of Presentation"> 
		<attribute description="Contains the unique symbolic link for a 
         video capture driver." guid="{58F0AAD8-22BF-4F8A-BB3D-D2C4978C6E2F}" 
         name="MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK" 
         title="The symbolic link for a video capture driver.">
			<singlevalue value="ImageCaptureProcessor">
		</singlevalue></attribute>
		<attribute description="The display name is a human-readable string, 
         suitable for display in a user interface." 
         guid="{60D0E559-52F8-4FA2-BBCE-ACDB34A8EC01}" 
         name="MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME" 
         title="The display name for a device."> 
			<singlevalue value="Image Capture Processor">
		</singlevalue></attribute>
	</presentationdescriptor.attributes> 
	<streamdescriptor index="0" majortype="MFMediaType_Video" 
     majortypeguid="{73646976-0000-0010-8000-00AA00389B71}"> 
		<mediatypes typecount="1"> 
			<mediatype index="0">
				<mediatypeitem description="Width and height of a video frame, 
                 in pixels." guid="{1652C33D-D6B2-4012-B834-72030849A37D}" 
                 name="MF_MT_FRAME_SIZE" title="Width and height of the video frame."> 
					<value.valueparts> 
						<valuepart title="Width" value="Temp_Width"> 
						<valuepart title="Height" value="Temp_Height"> 
					</valuepart></valuepart></value.valueparts>
				</mediatypeitem>
				<mediatypeitem description="Approximate data rate of the 
                 video stream, in bits per second, for a video media type." 
                 guid="{20332624-FB0D-4D9E-BD0D-CBF6786C102E}" 
                 name="MF_MT_AVG_BITRATE" 
                 title="Approximate data rate of the video stream."> 
					<singlevalue value="33570816">
				</singlevalue></mediatypeitem>
				<mediatypeitem description="The major type defines the 
                 overall category of the media data." 
                 guid="{48EBA18E-F8C9-4687-BF11-0A74C9F96A8F}" 
                 name="MF_MT_MAJOR_TYPE" title="Major type GUID for a media type."> 
					<singlevalue guid="{73646976-0000-0010-8000-00AA00389B71}" 
                     value="MFMediaType_Video"> 
				</singlevalue></mediatypeitem>
				<mediatypeitem description="Default surface stride, 
                 for an uncompressed video media type. 
                 Stride is the number of bytes needed to go from one row of pixels 
                 to the next." guid="{644B4E48-1E02-4516-B0EB-C01CA9D49AC6}" 
                 name="MF_MT_DEFAULT_STRIDE" title="Default surface stride."> 
					<singlevalue value="Temp_Stride"> 
				</singlevalue></mediatypeitem>
				<mediatypeitem description="Specifies for a media type 
                                            whether the samples have a fixed size." 
                               guid="{B8EBEFAF-B718-4E04-B0A9-116775E3321B}" 
                               name="MF_MT_FIXED_SIZE_SAMPLES" 
                               title="The fixed size of samples in stream."> 
					<singlevalue value="True"> 
				</singlevalue></mediatypeitem>
				<mediatypeitem description="Frame rate of a video media type, 
                                            in frames per second." 
                               guid="{C459A2E8-3D2C-4E44-B132-FEE5156C7BB0}" 
                               name="MF_MT_FRAME_RATE" title="Frame rate."> 
					<ratiovalue value="10.0"> 
						<value.valueparts> 
							<valuepart title="Numerator" value="10">  
							<valuepart title="Denominator" value="1">  
						</valuepart></valuepart></value.valueparts> 
					</ratiovalue> 
				</mediatypeitem>
				<mediatypeitem description="Pixel aspect ratio for a 
                 video media type." guid="{C6376A1E-8D0A-4027-BE45-6D9A0AD39BB6}" 
                 name="MF_MT_PIXEL_ASPECT_RATIO" title="Pixel aspect ratio."> 
					<ratiovalue value="1"> 
						<value.valueparts> 
							<valuepart title="Numerator" value="1">  
							<valuepart title="Denominator" value="1">  
						</valuepart></valuepart></value.valueparts> 
					</ratiovalue> 
				</mediatypeitem>
				<mediatypeitem description="Specifies for a media type 
                 whether each sample is independent of the other samples 
                 in the stream." guid="{C9173739-5E56-461C-B713-46FB995CB95F}" 
                 name="MF_MT_ALL_SAMPLES_INDEPENDENT" title="Independent of samples."> 
					<singlevalue value="True">  
				</singlevalue></mediatypeitem>
				<mediatypeitem description="Specifies the size of each sample, 
                                            in bytes, in a media type." 
                               guid="{DAD3AB78-1990-408B-BCE2-EBA673DACC10}" 
                               name="MF_MT_SAMPLE_SIZE" 
                               title="The fixed size of each sample in stream."> 
					<singlevalue value="Temp_SampleSize">  
				</singlevalue></mediatypeitem>
				<mediatypeitem description="Describes how the frames in a 
                                            video media type are interlaced." 
                               guid="{E2724BB8-E676-4806-B4B2-A8D6EFB44CCD}" 
                               name="MF_MT_INTERLACE_MODE" 
                               title="Describes how the frames are interlaced."> 
					<singlevalue value="MFVideoInterlace_Progressive">  
				</singlevalue></mediatypeitem>
				<mediatypeitem description="The subtype GUID defines a 
                               specific media format type within a major type." 
                               guid="{F7E34C9A-42E8-4714-B74B-CB29D72C35E5}" 
                               name="MF_MT_SUBTYPE" 
                               title="Subtype GUID for a media type."> 
					<singlevalue guid="{Temp_SubTypeGUID}">
				</singlevalue></mediatypeitem>
			</mediatype>
		</mediatypes>
    </streamdescriptor>
</presentationdescriptor>

Temp_SubTypeGUID 定义图像格式,Temp_SampleSize 是图像的字节大小,Temp_Stride 是字节步幅或压缩格式为 0,Temp_WidthTemp_Height 是图像的像素宽度和高度。

您可以从以下链接下载此演示程序

WPFIPCameraMJPEGViewer

此演示程序展示了使用 CaptureManagerToCSharpProxyICaptureProcessorIInitilaizeCaptureSourceICurrentMediaTypeISourceRequestResult 接口的方式。此演示程序在 IPCameraMJPEGCaptureProcessor 类中实现了 ICaptureProcessor 接口 - 此类允许“注入”来自 IP 摄像头的 MJPEG 帧。此类的对象通过 HTTP Web 套接字连接到选定的网络摄像头,并将接收到的数据重定向到捕获会话。

ICaptureProcessor 接口具有以下方法

  • void initilaize(IInitilaizeCaptureSource IInitilaizeCaptureSource)
  • void pause()
  • void setCurrentMediaType(ICurrentMediaType aICurrentMediaType)
  • void shutdown()
  • void sourceRequest(ISourceRequestResult aISourceRequestResult)
  • void start(long aStartPositionInHundredNanosecondUnits, ref Guid aGUIDTimeFormat)
  • void stop()

方法 void initilaize(IInitilaizeCaptureSource IInitilaizeCaptureSource) 必须在 IInitilaizeCaptureSource 参数中设置包含捕获源描述的 XML 文本字符串 - 此数据从图像中获取。方法 void setCurrentMediaType(ICurrentMediaType aICurrentMediaType) 允许选择正确的流索引和正确的媒体类型。方法 void sourceRequest(ISourceRequestResult aISourceRequestResult)CaptureManage SDK 执行,用于获取以 XML 文本字符串描述中定义的格式的原始数据。

此演示程序可从以下链接获取

WPFViewerEVRDisplay

此演示程序展示了如何使用 CaptureManagerToCSharpProxyIEVRMultiSinkFactoryIEVRStreamControl 接口。此演示程序展示了如何从 Direct3DSurface9 接口创建 EVR 接收器。WPF 允许通过 System.Windows.Interop.D3DImage 类将 DirectX 纹理显示为常规图像。然而,对广泛 Windows 操作系统兼容性的要求使得只能使用旧的 DirectX 技术 - DirectX9。此代码创建 Direct3DSurface9 接口的渲染目标纹理,并且 CaptureManager 使用此纹理作为目标表面。此解决方案允许轻松将渲染结果与 WPF GUI 组件集成。

此演示程序展示了如何使用 IEVRStreamControl 接口的缩放和 setSrcPosition 方法,以有效实现缩放、定义感兴趣区域 (ROI) 并对其进行控制。

setSrcPosition 方法的参数 LeftRightTopBottom 定义了 ROI 矩形的角点,取值范围为 0.0f 到 1.0f。此演示使用屏幕捕获源,以便在没有可访问的 USB 摄像头的情况下也能正常工作。

此演示程序可从以下链接获取

WPFMultiSourceViewer

此演示程序展示了如何使用 CaptureManagerToCSharpProxyIEVRMultiSinkFactoryIEVRStreamControl 接口。此演示程序展示了创建和控制两个 EVR 接收器的方式。

此演示展示了如何将它们连接到不同的捕获会话并控制目标:大小、位置、比例、刷新。此演示使用屏幕捕获源,以便在没有可访问的 USB 摄像头的情况下也能正常工作。

您可以从以下链接获取此演示程序

WPFIPCameraMJPEGMultiSourceViewer

此演示程序展示了如何使用 CaptureManagerToCSharpProxyICaptureProcessorIInitilaizeCaptureSourceICurrentMediaTypeISourceRequestResultIEVRMultiSinkFactoryIEVRStreamControl 接口。此演示程序展示了创建两个 EVR 接收器、控制它们并将它们与两个 IPCameraMJPEGCaptureProcessor 类实例连接的方式。

您可以从以下链接获取此演示程序

EVRVieweingAndRecording

此演示程序展示了使用 CaptureManager SDK 捕获来自源的实时视频和音频,对其进行编码并录制到文件中,同时预览实时视频的方式。组件的连接如下所示

演示程序有控制台界面

此演示程序可从以下链接获取

WPFVideoAndAudioRecorder

此演示程序展示了使用 CaptureManager SDK 捕获来自源的实时视频和音频,对其进行编码并录制到文件中,同时预览实时视频的方式。组件的连接如下所示

演示程序具有 WPF GUI,并允许更精确地控制

此演示程序可从以下链接获取

NativeMediaFoundationPlayer

此演示程序展示了如何使用 CaptureManager SDK,通过将其连接到 MediaFoundation 播放器代码来渲染媒体文件中的视频。MediaFoundation 的原始视频渲染器功能有限,可用于简单的程序,但对于更复杂的解决方案,它需要开发具有所需功能的渲染器。CaptureManager 1.7.0 免费版支持以下功能的视频渲染

  1. 多接收器视频渲染 - 这意味着可以在一个视频上下文中渲染多个视频流。每个视频渲染接收器都具有独立的控制。
  2. 它可以选择三种视频上下文之一
    1. 作为渲染目标的窗口句柄

    2. DirectX9 纹理 - 渲染目标纹理,可用于网格纹理化以进行最终渲染

    3. DirectX9 SwapChain - DirectX9 渲染器交换链中的渲染目标纹理

  3. 它允许控制渲染的位置、大小和背景颜色。

此演示程序可从以下链接获取

WPFMediaFoundationPlayer

此演示程序展示了如何使用 CaptureManager SDK,通过将其连接到 MediaFoundation 播放器代码来渲染媒体文件中的视频。

此演示程序具有 WPF 界面,并展示了如何正确创建具有 CaptureManager MultiSink 视频渲染器的一组 MediaFoundation 播放器。它支持 CaptureManager 1.7.0 免费版

此演示程序可从以下链接获取

UnityWebCamViewer

此演示程序展示了在 Windows 操作系统上 Unity 游戏开发 SDK 中使用 CaptureManager SDK 的方式。CaptureManager SDK 通过插件集成到 Unity 游戏开发 SDK 中,该插件允许将 DirectX9 渲染纹理的指针移动到 C++ 代码中,以将其设置为渲染目标。Unity 游戏开发 SDK 的 C# 代码从 CaptureManager SDK 获取文本字符串并显示有关源的信息。

此演示程序可从以下链接获取

WPFMultiSourceRecorder

此演示程序展示了将来自多个源的视频和音频数据录制到一个文件中的方法。CaptureManager 支持灵活管理多个源到多个接收器的连接。ASF 接收器实现了 Windows 操作系统对 ASF Microsoft 媒体格式的支持——一种具有许多功能的 Microsoft 平台专用格式。此演示程序允许测试其中一个功能——多流录制。可以为录制选择多个源。此外,这些源可以包含一个以上的视频源。它允许将来自多个实时视频源的同步视频录制到一个媒体文件中。

源连接的模式如下所示

VideoLAN 播放器可以打开此类多视频流文件

此演示程序可从以下链接获取

WPFScreenViewer

此演示程序展示了通过添加“normalize”模式来修改“屏幕捕获”源的方式。CaptureManager 具有从显示器屏幕捕获图像的功能。CaptureManager 支持多显示器输出,并可以从“屏幕捕获”源创建两种模式的实时视频流:常规和标准化。“常规”模式是“屏幕捕获”源的默认模式 - 它按原样从显示器获取图像 - 无需任何后处理

Landscape”图像以正常视图拍摄

Flipped Landscape”图像作为旋转 180 度的图像拍摄

Portrait”图像作为旋转 90 度的图像拍摄

Flipped Portrait”图像作为旋转 270 度的图像拍摄

这样做是为了保持视频编码的最大质量。

Normalize”模式通过执行图像后处理操作来克服屏幕比例不同导致的问题。目前,它支持一种标准化模式——“Landscape”——" --normalize=Landscape"——它将任何图像标准化为一种方向类型——图像顶部在上方,图像底部在下方。这可以通过代码完成

            if ((bool)mNormalizeChkBtn.IsChecked)
                lextendSymbolicLink += " --normalize=Landscape";

启用此模式将导致以下结果

Landscape”图像经过标准化后处理

Flipped Landscape”图像经过标准化后处理

Portrait”图像经过标准化后处理

Flipped Portrait”图像经过标准化后处理

您可以从以下链接获取此演示程序

WPFScreenRecorder

此演示程序展示了“屏幕捕获”源选项的设置方式。

Cursor”选项的扩展已通过新属性“Shape”进行扩展 - 它具有以下值

  • Rectangle” - 填充形状为矩形
  • Ellipse” - 填充形状为椭圆形

它允许设置背景图像的矩形形状

或背景图像的椭圆形形状

Clip”选项允许在桌面屏幕上定义“Region Of Interest”(感兴趣区域)并将其剪辑到输出视频流

新选项的 XML 形式为

<Option Type='Clip' >
    <Option.Extensions>
        <Extension Left='0' Top='0' Height='100' Width='100' />
    </Option.Extensions>
</Option>

此演示程序可从以下链接获取

WPFWindowScreenRecorder

此演示程序展示了“屏幕捕获”源选项的设置方式,用于捕获所选应用程序窗口的屏幕视图。

通过单击“按单击选择”按钮开始选择源应用程序窗口 - 它初始化 Windows OS API 以获取鼠标指针下的窗口的 HWND 句柄 - 鼠标指针下的窗口名称显示在“按单击选择”按钮下方

通过按下键盘“L”键来捕获窗口的 HWND 句柄。它允许获取捕获窗口的大小并选择帧速率

选择窗口后,可以应用“屏幕捕获”源的选项,以设置光标背景蒙版和裁剪感兴趣区域

通过向“屏幕捕获”符号链接添加参数“--HWND”来设置源屏幕视图窗口的 HWND

lextendSymbolicLink += " --HWND=" + m_CurrentHWND.ToInt32().ToString();

此演示程序可从以下链接下载

UnityScreenRecorder

此演示程序展示了在 Windows 操作系统上 Unity 游戏开发 SDK 中使用 CaptureManager SDK 的 ICaptureProcessor 接口的方式。CaptureManager SDK 通过插件集成到 Unity 游戏开发 SDK 中,该插件允许捕获游戏引擎渲染的屏幕并将其录制到输出视频文件中。

此演示程序支持两种工作模式

第一种模式 - 捕获并渲染图像以检查捕获处理器功能的正确性

第二种模式 - 录制,并选择编码器和输出格式文件

此演示程序可从以下链接获取

UnityVideoAndAudioRecorder

此演示程序展示了在 Windows 操作系统上 Unity 游戏开发 SDK 中查看和录制来自外部源的视频和音频的方式。

此演示程序可从以下链接获取

WPFViewerTrigger

此演示程序展示了如何使用切换器节点。此代码使用两个输出节点从源获取图像:EVR 和 Sample Grabber。Sample Grabber 以 1 秒的间隔获取样本并检查样本中的平均亮度 - 如果该值大于或等于阈值,则切换器节点被设置为开启,EVR 以 30 fps 获取样本。如果该值小于阈值,则切换器节点被设置为关闭,EVR 不获取任何东西。此模式如下所示

结果演示如下所示

此演示程序可从以下链接下载

WPFPauseResumeRecording

此演示程序展示了切换器节点的使用方式。切换器设置在录制流中,允许暂停/恢复此视频流,而 EVR 预览流仍以独立连续模式工作。

此演示程序可从以下链接下载

WPFHotRecording

此演示程序展示了如何在不停止源的情况下,在运行时使用切换器节点将录制流附加和分离到源。 ISwitcherControl 接口具有灵活更改切换器节点配置的命令。

此类解决方案对于固定大小的媒体文件录制任务可能很有用——当当前录制媒体文件的大小达到阈值时,当前文件将被分离,新文件将被附加到录制流中。

在此演示程序中,切换器节点(用于视频和音频流)是在没有录制媒体文件的情况下创建的——这是可能的。通过点击“开始录制”按钮来附加新的录制媒体文件。旧文件(如果存在)将被分离。

此演示程序可从以下链接下载

WPFAreaScreenRecorder

此演示程序展示了屏幕捕获源的使用方式。前段时间,我需要一个屏幕录像机应用程序,并尝试开发一个简单的应用程序,它可以覆盖几乎 99% 的其他类似应用程序的功能,但只使用 CaptureManager SDK。它有一个简单的界面

它通过“橡皮筋矩形”进行剪裁

它按当前日期生成文件名

它具有显示和隐藏光标图标和背景图像蒙版的配置

它具有带设置压缩质量的视频编码器配置

它具有配置选择输出文件格式和选择目录以存储录制的视频文件

当前配置可以保存并重置为默认值。

您可以从以下链接获取此演示程序

InterProcessRenderer

这些演示程序展示了如何使用 DirectX9 纹理共享句柄在两个进程之间共享渲染上下文。它们包括 WPFInterProcessClientInterProcessRenderer 项目。

Direct3D9Ex 允许创建具有共享句柄的 DirectX9 纹理

private Direct3DSurface9 GetSharedSurface(Direct3DDevice9Ex device)
{
    return device.CreateRenderTarget(
           m_width,
           m_height,
           Format, // D3DFMT_A8R8G8B8
           0,
           0,
           0  // UNLOCKABLE
           );
}

每个 Direct3DSurface9 都有一个 Handle 属性,可以将其用作另一个进程中的渲染目标。

此解决方案允许分离用户界面进程和捕获渲染进程

mOffScreenCaptureProcess = new Process();

mOffScreenCaptureProcess.StartInfo.FileName = "InterProcessRenderer.exe";

mOffScreenCaptureProcess.EnableRaisingEvents = true;

mOffScreenCaptureProcess.Exited += mOffScreenCaptureProcess_Exited;

mPipeProcessor = new PipeProcessor(
    "Server",
    "Client");

mPipeProcessor.MessageDelegateEvent += lPipeProcessor_MessageDelegateEvent;

mOffScreenCaptureProcess.StartInfo.Arguments =
    "SymbolicLink=" + aSymbolicLink + " " +
    "StreamIndex=" + aStreamIndex + " " +
    "MediaTypeIndex=" + aMediaTypeIndex + " " +
    "WindowHandler=" + mVideoPanel.Handle.ToInt64();

mOffScreenCaptureProcess.StartInfo.UseShellExecute = false;
            
try
{
    mIsStarted = mOffScreenCaptureProcess.Start();

    HeartBeatTimer.Start();
}
catch (Exception)
{
    mIsStarted = false;
}

这些演示程序可从以下链接获取

WPFDeviceInfoViewer

此演示程序展示了按唯一设备 ID 分组视频和音频源的方式。Windows 操作系统中的每个设备都有一个唯一的设备 ID 来标识每个设备。此演示程序显示此 ID 并根据此 ID 对设备进行分组。

此解决方案对于来自同一供应商的许多视频和音频 USB 源的任务可能很有用 - 此代码允许根据所属的真实 USB 设备对视频和音频源进行分组。

您可以从以下链接获取此演示程序

WPFWebViewerEVRHandler

此演示程序展示了如何使用 DirectX9 纹理共享句柄进行渲染。当前一代的 Windows 操作系统具有在不同版本的 DirectX 之间共享渲染纹理的功能。

您可以通过以下链接获取此演示程序

DShowPlayer

此演示程序展示了如何在 C++ Window 应用程序项目中使用 CaptureManager SDK 作为视频渲染器与 DirectShow 播放器。此演示程序使用 DirectShow 在 C++ 代码中播放视频,并使用 CaptureManagerEVRMultiSink 输出节点

    HRESULT hr = S_OK;

    IBaseFilter *pEVR = NULL;

    //hr = AddFilterByCLSID(pGraph, CLSID_EnhancedVideoRenderer, &pEVR, L"EVR");

    unsigned long lPtrOutputNodeAmount = 0;

    CaptureManagerLoader::getInstance().getMaxVideoRenderStreamCount
                                        (&lPtrOutputNodeAmount);

    std::vector<CComPtrCustom<IUnknown>> lOutputNodes;

    CaptureManagerLoader::getInstance().createRendererOutputNodes(
        hwnd,
        nullptr,
        1,
        lOutputNodes);
    
    if (!lOutputNodes.empty())
    {
        lOutputNodes[0]->QueryInterface(IID_PPV_ARGS(&pEVR));
    }
        
    CHECK_HR(hr = pGraph->AddFilter(pEVR, L"EVR"));

您可以从以下链接获取此演示程序

WPFDirectShowPlayer

此演示程序展示了如何在 WPF 应用程序中使用 CaptureManager SDK 作为视频渲染器与 DirectShow 播放器。此演示程序使用 DirectShowLib C# NuGet 在 C# 代码中播放视频,并使用 CaptureManagerEVRMultiSink 输出节点

            List<object> lOutputNodesList = new List<object>();

            lCaptureManagerEVRMultiSinkFactory.createOutputNodes(
                IntPtr.Zero,
                mEVRDisplay.Surface.texture,
                1,// lMaxVideoRenderStreamCount,
                out lOutputNodesList);

            if (lOutputNodesList.Count == 0)
                return;

            IBaseFilter lVideoMixingRenderer9 = (IBaseFilter)lOutputNodesList[0];

            var h = m_pGraph.AddFilter(lVideoMixingRenderer9, "lVideoMixingRenderer9");

DirectShow 版本仅支持软件渲染器。

此演示程序可从以下链接获取

WPFRtspServer

此演示程序展示了在 RTSP 流媒体服务中使用 CaptureManager SDK 的方式。此演示程序基于 GitHub 项目 SharpRTSP - 用于开发 RTSP 通信服务器和客户端的 C# 版 RTSP 处理库。

CaptureManager SDK 具有从视频和音频源获取原始数据的功能 - 演示程序 WPFWebViewerCallback 展示了获取 RGB32 图像以进行显示的方式。然而,此 SDK 允许从 SDK 管道获取任何原始数据 - 甚至编码的视频和音频数据。此演示程序定义了视频流的输出节点

                ISampleGrabberCallback lH264SampleGrabberCallback;

                aISampleGrabberCallbackSinkFactory.createOutputNode(
                    MFMediaType_Video,
                    MFVideoFormat_H264,
                    out lH264SampleGrabberCallback);

                object lOutputNode = lEVROutputNode;

                if (lH264SampleGrabberCallback != null)
                {
                    uint ltimestamp = 0;

                    lH264SampleGrabberCallback.mUpdateEvent += delegate
                        (byte[] aData, uint aLength)
                    {
                        if (s != null)
                        {
                            ThreadPool.QueueUserWorkItem((object state) =>
                            {
                                s.sendData(aIndexCount, ltimestamp, aData);

                                ltimestamp += 33;
                            });
                        }
                    };

以及音频流

                ISampleGrabberCallback lAACSampleGrabberCallback;

                aISampleGrabberCallbackSinkFactory.createOutputNode(
                    MFMediaType_Audio,
                    MFAudioFormat_AAC,
                    out lAACSampleGrabberCallback);

                if (lAACSampleGrabberCallback != null)
                {
                    lAACSampleGrabberCallback.mUpdateFullEvent += delegate
                        (uint aSampleFlags, long aSampleTime, 
                         long aSampleDuration, byte[] aData, uint aLength)
                    {
                        if (s != null)
                        {
                            ThreadPool.QueueUserWorkItem((object state) =>
                            {
                                s.sendData(aIndexCount, 
                                          (uint)aSampleTime / 100000, aData);
                            });
                        }
                    };

此 SDK 与 SharpRTSP 代码的结合允许创建可工作的 RTSP 视频和音频流媒体服务器版本

为了支持广泛的解码器,CaptureManager 允许将视频编码为 H264 和 H265 (HEVC) 视频格式

您可以从以下链接获取演示程序

WPFRTSPClient

此演示程序展示了在 RTSP 流媒体服务中使用 CaptureManager SDK 的方式。此演示程序基于 GitHub 项目 SharpRTSP - 用于开发 RTSP 通信服务器和客户端的 C# 版 RTSP 处理库。

CaptureManager SDK 具有从外部源捕获原始数据的功能。此源可以是编码数据 - 在此示例中,源是带有 H264 视频编码流的测试服务器

您可以从以下链接获取演示程序

WPFStreamer

此演示程序展示了在 RTMP(实时消息协议 (RTMP) 最初是 Macromedia 开发的一种专有协议,用于在 Flash 播放器和服务器之间通过互联网流式传输音频、视频和数据。Macromedia 现已归 Adobe 所有,Adobe 发布了该协议规范的不完整版本供公众使用)流媒体服务(Facebook (RTMPS)、YouTube 等)中使用 CaptureManager SDK 的方式。此演示程序基于 librtmp - 用于开发 RTMP 流媒体客户端的 C 语言版 RTMP 处理库。

您可以从以下链接获取演示程序

WPFMixer

此演示程序展示了如何使用 CaptureManager SDK 的混音功能。新的 SDK 接口

  • IMixerNodeFactory
  • IVideoMixerControl
  • IAudioMixerControl

允许将多个视频流合并为一个视频流,并将多个音频流合并为一个音频流。IMixerNodeFactory 接口创建包含一个输出节点的拓扑输入节点列表。IVideoMixerControl 接口允许控制每个源的视频混音

  • setPosition - 设置输入视频流在背景参考视频流上的位置
  • setSrcPosition - 设置输入视频流中的区域
  • setZOrder - 设置混音器的 Z 顺序
  • setOpacity - 设置输入视频流的不透明度
  • flush - 清空视频输入流缓冲区
public interface IVideoMixerControl
{
    bool setPosition(object aPtrVideoMixerNode,
        /* [in] */ float aLeft,
        /* [in] */ float aRight,
        /* [in] */ float aTop,
        /* [in] */ float aBottom);
        
    bool setSrcPosition(object aPtrVideoMixerNode,
        /* [in] */ float aLeft,
        /* [in] */ float aRight,
        /* [in] */ float aTop,
        /* [in] */ float aBottom);
        
    bool setZOrder(object aPtrVideoMixerNode,
        /* [in] */ UInt32 aZOrder);

    bool setOpacity(object aPtrVideoMixerNode,
        /* [in] */ float aOpacity);
        
    bool flush(object aPtrVideoMixerNode);
}

接口 IAudioMixerControl 允许控制音频流与参考背景音频流的混合:setRelativeVolume - 设置音频流相对于参考背景音频流的音量 = (输入音频流 * aRelativeVolume) + (参考背景音频流 * (1.0 - aRelativeVolume))。

public interface IAudioMixerControl
{
    bool setRelativeVolume(object aPtrAudioMixerNode, float aRelativeVolume);
}

您可以从以下链接获取演示程序

WPFGIFtoVideo

此演示程序展示了如何使用 CaptureManager SDK 将动画 gif 图像录制到视频文件中。为了注入解码后的 GIF 帧,演示程序使用了已实现的接口 ICaptureProcessor

此演示程序可从以下链接下载

AudioCaptureProcessor

此演示程序展示了如何使用 CaptureManager SDK 将系统混音音频输出录制到音频文件中。音频数据是一种需要低延迟才能正确录制的数据 - CaptureManager SDK 具有优化的媒体处理管道,可以高质量捕获音频数据。

您可以从以下链接获取此演示程序

WPFStreamingAudioRenderer

此演示程序展示了如何通过 CaptureManager SDK 将网络摄像头音频流播放到系统音频渲染器。

此演示程序可从以下链接下载

WPFVirtualCamera

此演示程序展示了如何创建“虚拟摄像头”的 DirectShow 过滤器 - 这是一种特殊的 Windows 操作系统软件媒体组件,被网络摄像头应用程序(如 Skype 或 Zoom)识别为真实的网络摄像头设备。这种解决方案允许将 CaptureManager SDK 的 C# 视频处理代码集成到第三方网络摄像头应用程序的视频处理管道中。

然而,此演示程序展示了一个更复杂的产品 - 此演示分为两个项目:C# DirectShow 过滤器和 WPF/C# 进程外 COM 服务器。这种魔力是通过使用 DirectX 11 的 Windows 操作系统跨进程渲染技术实现的。

编译此演示后,Bin 文件夹中有两个基本文件

  • WPFVirtualCameraServer.exe - 进程外 COM 服务器
  • VirtualCameraDShowFilter.dll - 进程内 DirectShow 过滤器

要将此演示安装到目标 Windows 操作系统中,需要执行 install.bat 脚本文件。

要从 Windows 操作系统中卸载此演示,需要执行 uninstall.bat 脚本文件。

因此,由于 DirectShow 过滤器是 Windows OS COM 基础设施的一部分,因此需要生成并注册 TLB(类型库)文件 - VirtualCameraDShowFilter.tlb, WPFVirtualCameraServer.tlb

此生成和注册在脚本 install.bat 中执行。执行 install.bat 后,此视频摄像头的软件仿真将显示为“Virtual Camera”在可访问网络摄像头列表中

同时,Windows 操作系统启动后台进程 WPFVirtualCameraServer 进行视频处理和渲染

它是如何工作的?!当网络摄像头应用程序将 VirtualCameraDShowFilter.dll 识别为真实的视频源时,此 DLL 会调用 Windows OS 通过 ProgID="WPFVirtualCameraServer.VirtualCameraServer" 创建一个 COM VirtualCameraServer 实例。由于 COM VirtualCameraServer 被注册为 LocalServer32,目标 Windows OS 将创建进程 WPFVirtualCameraServer,并自动在 WPFVirtualCameraServer 进程和网络摄像头应用程序进程之间进行跨进程数据封送处理。

VirtualCameraDShowFilter.dll 将通过 IVirtualCameraServer 接口与 WPFVirtualCameraServer 进程通信,该接口只有一个方法 - get_DirectX11TextureHandler

    [ComVisible(false)]
    [Guid("EEE2F595-722F-4279-B919-313189A72C36")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IVirtualCameraServer
    {
        IntPtr get_DirectX11TextureHandler(out int retVal);
    }

方法 get_DirectX11TextureHandler 返回 DirectX11 纹理共享句柄,该句柄允许在进程之间共享 DirectX 11 纹理(您可以在 ID3D11Device::OpenSharedResource 处获取更多信息)。

WPFVirtualCameraServer 进程活动在任务栏中显示为图标 。鼠标指针双击此图标的左键可显示 WPF UI,用于控制跨进程渲染 - 渲染的位置和大小

鼠标指针在 WPFVirtualCameraServer 进程图标上单击右键,将显示选择视频源的上下文菜单 - 默认视频源为 Screen Capture

此解决方案允许将一个真实视频源复制到多个网络摄像头应用程序

进程交互时间线图展示了 WPFVirtualCameraServer 进程与第三方网络摄像头应用程序相关的生命周期

此演示程序可从此处获取

WPFWebViewerEVRLog4net

此演示程序展示了将 log4net 框架集成到使用 CaptureManager SDK 的项目中的方法。为了实现此功能,已添加了新接口 ILogPrintOutCallback

[
    object,
    uuid(251E71F6-8C02-475F-B300-216D560426B2),
    oleautomation,
    helpstring("Interface for processing callback of log.")
]
interface ILogPrintOutCallback : IUnknown
{
    [
        helpstring("Method for callback invoking.")
    ]
    HRESULT invoke(
        [in] DWORD aLevelType,
        [in] BSTR aPtrLogString);
};

在项目中,此新接口以以下方式实现

LogManager.getInstance().WriteLogDelegateEvent += (aLogLevel, aPtrLogString)=>
{
    switch (aLogLevel)
    {
        case CaptureManagerToCSharpProxy.WrapClasses.LogLevel.INFO_LEVEL:
            log.Info(aPtrLogString);
            break;
        case CaptureManagerToCSharpProxy.WrapClasses.LogLevel.ERROR_LEVEL:
            log.Error(aPtrLogString);
            break;
        default:
            break;
    }
};

您可以从以下链接获取此演示程序

WPFHardwareButtonTrigger

此演示程序展示了如何将网络摄像头硬件按钮按下事件处理添加到使用 CaptureManager SDK 的项目中。这通过使用 SnapTrigger 扩展 SessionCallbackEventCode 来实现。代码可以按以下方式查看

 

void UpdateStateDelegate(uint aCallbackEventCode, uint aSessionDescriptor)
{
    SessionCallbackEventCode k = (SessionCallbackEventCode)aCallbackEventCode;

    switch (k)
    {
        case SessionCallbackEventCode.Unknown:
            break;
        case SessionCallbackEventCode.Error:
            break;
        case SessionCallbackEventCode.Status_Error:
            break;
        case SessionCallbackEventCode.Execution_Error:
            break;
        case SessionCallbackEventCode.ItIsReadyToStart:
            break;
        case SessionCallbackEventCode.ItIsStarted:
            break;
        case SessionCallbackEventCode.ItIsPaused:
            break;
        case SessionCallbackEventCode.ItIsStopped:
            break;
        case SessionCallbackEventCode.ItIsEnded:
            break;
        case SessionCallbackEventCode.ItIsClosed:
            break;
        case SessionCallbackEventCode.VideoCaptureDeviceRemoved:
            {


                Dispatcher.Invoke(
                DispatcherPriority.Normal,
                new Action(() => mLaunchButton_Click(null, null)));

            }
            break;
        case SessionCallbackEventCode.SnapTrigger:
            MessageBox.Show("Hardware button is pressed!!!");
            break;                    
        default:
            break;
    }
}

您可以从以下链接获取此演示程序

 

CaptureManager SDK 版本

版本 1.0.0

Capture Manager SDK v1.0.0 – 公开版本。我包含了具有稳定定义的接口

通过类 CoLogPrintOut – 接口 ILogPrintOut

通过类 CoCaptureManager – 接口 ICaptureManagerControl

方法 createControl – 创建具有接口的主要控制对象

  • ISourceControl,
  • ISinkColtrol,
  • IEncoderControl,
  • ISessionControl,
  • IStreamControl;

方法 createMisc – 创建具有接口的杂项对象

  • IMediaTypeParser
  • IStrideForBitmap
  • IVersionControl

通过具有接口 ISourceControl 的对象

  • IWebCamControl

通过具有接口 ISinkControl 的对象

  • IFileSinkFactory
  • ISampleGrabberCallSinkFactory
  • IEVRSinkFactory
  • ISampleGrabberCallbackSinkFactory
  • IByteStreamSinkFactory

通过具有接口 ISession 的对象

  • ISessionCallback

通过具有接口 ISampleGrabberCallSinkFactory 的对象

  • ISampleGrabberCall

通过具有接口 ISampleGrabberCallbackSinkFactory 的对象

  • ISampleGrabberCallback

这些接口执行以下工作

  • ILogPrintOut – 它管理日志信息的写入
  • ICaptureManagerControl – 它管理所有捕获控件和杂项对象的创建
  • ISourceControl – 它管理和创建视频和音频信号源
  • ISinkColtrol – 它管理和创建视频和音频流的接收器
  • IEncoderControl – 它管理和创建视频和音频编码器
  • ISessionControl – 它创建用于管理录制会话的对象
  • IStreamControl – 它创建用于控制媒体流的对象
  • IMediaTypeParser – 它创建 XML 格式的媒体类型的文本表示
  • IStrideForBitmap – 它计算特定位图格式的内存步幅
  • IVersionControl – 它管理有关 CaptureManager 当前版本的信息
  • IWebCamControl – 它管理网络摄像头的选项
  • IFileSinkFactory – 它创建与媒体文件链接的媒体接收器节点
  • ISampleGrabberCallSinkFactory – 它通过直接调用具有接口 ISessionCallback 的对象来创建用于抓取单个样本的媒体接收器
  • ISampleGrabberCall – 它管理单个样本的抓取
  • ISampleGrabberCallbackSinkFactory – 它通过从 CaptureManager 内部线程调用具有接口 ISampleGrabberCallback 的对象来创建用于抓取单个样本的媒体接收器
  • ISampleGrabberCallback – 它管理从 CaptureManager 内部线程抓取单个样本
  • IEVRSinkFactory – 它创建用于渲染视频流的媒体接收器
  • IByteStreamSinkFactory – 它创建与自定义字节流对象(具有接口 IMFByteStream)链接的媒体接收器节点
  • ISession – 它管理录制会话
  • ISessionCallback – 它管理从 CaptureManager 内部线程获取的当前录制会话的结果状态

这些接口的定义在 SDK 文件和 CaptureManagerToCSharpProxy 项目中呈现。为了通过非托管-托管代码的边界有效封送信息,CaptureManager 使用了八个结构如下的简单 XML 文档

<!--?xml version="1.0" ?-->
<!--XML Document of sources-->

这些文档包含有关源、编码器、接收器、流控制的信息。大多数类型和 GUID 取自 Microsoft Media Foundation MSDN:Microsoft Media Foundation,但这些 XML 文档中节点定义有一些规则

  • SingleValue - 仅用于呈现一个值的节点 - 名称、整数、类型
  • RatioValue - 以分子和分母表示浮点格式值的节点
  • Value.ValueParts - 用于存储具有相同类型的 ValuePart 节点集合的节点。这些 XML 文档的解析示例可以在 WPF 示例代码中找到。

版本 1.1.0,带 HEVC(H265) 编码器

CaptureManager SDK v1.1.0 – 公开版本。此版本包括以下更改

  • 添加支持 CaptureManager 作为 COM 服务器自动注册类型库
  • 添加对动态语言 Python 2.7 的支持
  • 在 Windows 10 中添加对 HEVC(H265) 编码器的支持:下载 HEVCEncoder_Windows10

版本 1.2.0 Beta

CaptureManager SDK v1.2.0 – Beta 版。此版本包括以下更改

  • 删除了通过库链接使用 CaptureManager 的旧功能。旧的演示程序已移至旧的演示
  • Microsoft Media Foundation 函数的延迟绑定。
  • 将网络摄像头属性功能从 DirectShow 替换为 DeviceIoControl
  • EVR 中添加了调整大小功能。

函数延迟绑定是通过在运行时加载 Microsoft Media Foundation 库实现的。在 Microsoft Media Foundation 库中找不到的函数将替换为存根函数。这可以防止由于使用不支持的 Microsoft Media Foundation 库函数而导致应用程序崩溃。

使用 DeviceIoControl 更改网络摄像头属性解决了网络摄像头驱动程序初始化延迟的问题。它允许通过“数字变焦量”、“数字变焦量上限”、“电源线频率”来扩展可用的网络摄像头属性集。

EVR 中的调整大小解决了渲染视频图像大小不变的问题。新实现控制渲染器 GUI 的当前图像大小,并更改图像大小和位置以保持视频比例。

版本 1.2.0

CaptureManager SDK v1.2.0 – 正式版。此版本包括以下更改

  • 删除了通过库链接使用 CaptureManager 的旧功能。旧的演示程序已移至旧的演示
  • Microsoft Media Foundation 函数的延迟绑定。
  • 将网络摄像头属性功能从 DirectShow 替换为 DeviceIoControl
  • EVR 中添加了调整大小功能。
  • SampleGrabberCallSinkFactory 中添加了 PULL 模式 - 允许获取单个样本的新模式。
  • 添加了样本累加器节点,用于在媒体流中存储最后 5 或 10 个样本。

函数延迟绑定是通过在运行时加载 Microsoft Media Foundation 库实现的。在 Microsoft Media Foundation 库中找不到的函数将替换为存根函数。这可以防止由于使用不支持的 Microsoft Media Foundation 库函数而导致应用程序崩溃。

使用 DeviceIoControl 更改网络摄像头属性可解决网络摄像头驱动程序初始化延迟的问题。它允许通过“数字变焦量”、“数字变焦量上限”、“电源线频率”来扩展可用网络摄像头属性集。

EVR 中的调整大小解决了渲染视频图像大小不变的问题。新实现控制渲染器 GUI 的当前图像大小,并更改图像大小和位置以保持视频比例。

版本 1.3.0 Beta,带 DirectShow Crossbar 捕获

CaptureManager SDK v1.3.0 – Beta 版。此版本包括以下更改

  • 添加支持通过 DirectShow Crossbar 技术捕获以下输入的视频
    • 复合
    • SVideo
    • USB
    • 1394 (火线)

在 MSDN 上 - Media Foundation 中的音频/视频捕获,您可以发现“视频捕获设备通过 UVC 类驱动程序支持,并且必须与 UVC 1.1 兼容”。这意味着 Microsoft Media Foundation 只能从支持 UVC 驱动程序的设备捕获视频 - USB 视频类驱动程序。它通常是 USB 网络摄像头 - 其他类型的视频捕获设备不受支持。根据我的经验,我可以说明 Microsoft Media Foundation 的目标平台是 WindowsStore 应用程序。WindowsStore 应用程序最初面向“平板” - “Surface”等便携式设备 - 此类设备没有用于连接捕获卡的外部端口。它们只能使用通过内部 USB 端口连接的嵌入式网络摄像头。我认为这是一个正确的想法,但我收到了许多关于将 CaptureManager 与支持 DirectShow Crossbar 的捕获卡一起使用的问题,并回答说这不可能。然而,这不是完整的答案 - 完整的答案是,通过 Microsoft Media Foundation 功能可以看到支持 DirectShow Crossbar 的捕获卡,但捕获的视频无法访问。经过一番研究,我找到了访问此类捕获卡的方法。用于处理捕获卡的信息可以从源的 XML 字符串中选择,通过检查名称为“MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_CATEGORY”的源的属性节点:“Source.Attributes/Attribute[@Name='MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_CATEGORY']

/SingleValue[@Value='CLSID_VideoInputDeviceCategory']” - Microsoft Media Foundation 为支持 UVC 的设备定义了 CLSID_WebcamInterfaceDeviceCategory - {E5323777-F976-4F5B-9B55-B94699C46E44},为 DirectShow 捕获设备定义了 CLSID_VideoInputDeviceCategory - {860BB310-5D01-11D0-BD3B-00A0C911CE86}。这允许轻松选择仅 DirectShow 视频捕获设备。DirectShow 的输入连接可以根据物理类型通过选择合适的媒体类型来选择 - 每个媒体类型节点都有一个名为 DirectShowPhysicalType - {09FCAE6B-8F00-4C0A-BBCA-C6820B576C99} 的子 MediaTypeItem 节点。这允许根据输入连接的物理类型对媒体类型进行分组。

<MediaTypeItem Name="DirectShowPhysicalType" 
 GUID="{09FCAE6B-8F00-4C0A-BBCA-C6820B576C99}" 
 Title="Physical type of input pin of Crossbar." 
 Description="Physical type of input pin of Crossbar.">
  <SingleValue Value="PhysConn_Video_Composite" /> 
  </MediaTypeItem>

版本 1.3.0,带 DirectShow Crossbar 捕获

CaptureManager SDK v1.3.0。此版本包括以下更改

  • 添加支持通过 DirectShow Crossbar 技术捕获以下输入的视频
    • 复合
    • SVideo
    • USB
    • 1394 (火线)
  • COM ThreadingModel 已从 Apartment 更改为 Both

版本 1.4.0 Beta

CaptureManager SDK v1.4.0 – Beta 版。此版本包括以下更改

  • 添加支持更改屏幕捕获源中光标的呈现方式:使光标不可见,并为当前光标图像添加绘制附加图像

此功能通过在符号链接中添加 " --options=" 获得。选项以 XML 格式设置

<!--?xml version='1.0' encoding='UTF-8'?-->
<Options>
    <Option Type="Cursor" Visiblity="True">
        <Option.Extensions>
            <Extension Fill="0x7055ff55" Height="100" Type="BackImage" Width="100"/>
        </Option.Extensions>
    </Option>
</Options>

通过 "Type='Cursor'" 属性选择光标选项。属性 "Visiblity='True'" 使当前类型 - Cursor 可见,属性 "Visiblity='False'" 使当前类型 - Cursor 不可见 - 它允许在不绘制光标的情况下捕获屏幕。XML 节点 "Option.Extensions" 允许添加扩展。目前,屏幕捕获源支持以下扩展

"<Extension Type='BackImage' Height='100' Width='100' Fill='0x7055ff55' />"

属性 "Type='BackImage'" 定义在光标图像之前绘制的“标记”图像

BackImage”的大小由属性 "Height='100' Width='100'" 定义,颜色由 ARGB 格式的属性 "Fill='0x7055ff55'" 定义。在代码中,可以通过这种方式实现

    string lextendSymbolicLink = lSymbolicLink + " --options=" +
        "<?xml version='1.0' encoding='UTF-8'?>" +
            "<Options>" +
                "<Option Type='Cursor' Visiblity='True'>" +
                    "<Option.Extensions>" +
                        "<Extension Fill='0x7055ff55' Height='100' 
                          Type='BackImage' Width='100'>" +
                        "</Extension> +
                        </Option.Extensions>" +
                "</Option>" +
            "</Options>";

版本 1.4.0

CaptureManager SDK v1.4.0。此版本包括以下更改

  • 添加支持更改屏幕捕获源中光标的呈现方式:使光标不可见,并为当前光标图像添加绘制附加图像。
  • 添加支持使用自定义 Microsoft Media Foundation Transform 来实现自定义效果(例如 - WaterMarkInjectorDemoTextInjectorDemo)。

版本 1.5.0

CaptureManager SDK v1.5.0。此版本包括以下更改

  • 添加支持多显示器输出

  • 添加对 DirectShow 软件过滤器的支持。

版本 1.6.0

Capture Manager SDK v1.6.0。此版本包括以下更改

  • 添加 IEVRMultiSinkFactoryIEVRStreamControl 接口:IEVRMultiSinkFactory 允许为单个渲染上下文(窗口的 HWNDDirect3DSurface9IDXGISwapChain)创建两个分离的拓扑节点。IEVRStreamControl 允许控制渲染结果 - 渲染位置、Z 顺序渲染、流缓冲区刷新、过滤器和功能。
  • IInitilaizeCaptureSourceICurrentMediaTypeISourceRequestResultICaptureProcessor 接口:IInitilaizeCaptureSource 接收捕获处理器的配置。ICurrentMediaType 包含所选媒体类型的信息。ISourceRequestResult 接收以 IInitialazeCaptureSource 中定义的格式的原始数据。ICaptureProcessor 控制捕获过程。

CaptureManager 通过共享上下文实现多接收器渲染。此解决方案与 Microsoft Media Foundation 的常见实现不同。原始解决方案使用“Session”模式 - 在一个会话中处理源、解码器、编码器和接收器。结果,它导致许多流管道从一个会话中同步,并且可以通过一个命令“Start”、“Stop”、“Pause”来控制它们。它在播放一个视频文件的情况下工作良好。Microsoft Media Foundation 具有从许多其他源创建单个源的功能 - MFCreateAggregateSource - 它允许实现“画中画”效果,用于渲染两个或更多视频文件。但是,它有局限性 - 它只能在一个会话的上下文中工作 - 这意味着会话的所有源同时启动、停止和暂停。

从文件播放视频可能没问题,但在处理具有不同延迟的源(例如一个来自本地网络摄像头,另一个通过HTTP来自IP摄像头)时,该怎么办?另一个问题是,如果我们只想停止一个源,代码会停止会话中的所有源。解决这些问题的方法是为每个源使用不同的独立会话,但Microsoft Media Foundation的常见实现不允许通过多个会话共享视频渲染接收器。CaptureManager 通过增加一个抽象层来解决这些问题——虽然Microsoft Media Foundation的常见实现从一个视频上下文创建一个视频渲染媒体接收器,但CaptureManager 从控制器创建多个视频渲染媒体接收器,这些接收器共享同一个视频上下文。

额外的层允许更灵活地管理渲染。用于测试此功能的演示程序可以在WPFMultiSourceViewer中找到。CaptureManager 免费版支持从一个渲染上下文最多渲染两个接收器。`Sink Factories` 中的`MultiRendering` 由新的`Sink Factory` 节点表示。

<sinkfactory guid="{10E52132-A73F-4A9E-A91B-FE18C91D6837}" 
 name="EVRMultiSinkFactory" title="Enhanced Video Renderer multi sink factory">
  <value.valueparts>
   <valuepart description="Default EVR implementation" 
    guid="{E926E7A7-7DD0-4B15-88D7-413704AF865F}" 
    maxportcount="2" mime="" title="Container format" value="Default"> 
  </valuepart>
  </value.valueparts>
</sinkfactory>

渲染可以通过IEVRStreamControl接口控制。

  • setPosition方法允许在共享渲染上下文中以0.0f到1.0f的相对值定位渲染。
  • getPosition方法允许在共享渲染上下文中以0.0f到1.0f的相对值获取渲染位置。
  • setZOrder方法允许设置流的渲染顺序,从0到最大渲染接收器。
  • getZOrder方法允许获取流的渲染顺序,从0到最大渲染接收器。
  • flush方法允许清除渲染接收器的渲染缓冲区。
  • setSrcPosition方法允许在输入流中设置矩形以选择感兴趣区域。
  • getSrcPosition方法允许在输入流中获取矩形以选择感兴趣区域。
  • getCollectionOfFilters方法允许获取包含当前渲染接收器过滤器列表的XML字符串。
    <!--?xml version="1.0" ?--> 
    <!-- XML Document of render stream filters --> 
      <filters> 
        <filter currentvalue="" max="" min="" step="" title=""> 
      </filter></filters> 
  • setFilterParametr方法允许为当前渲染接收器设置过滤器。
  • getCollectionOfOutputFeatures方法允许获取包含渲染上下文输出功能列表的XML string
      <!--?xml version="1.0" ?--> 
      <!-- XML Document of render stream output features --> 
      <features>
      <feature title="Background Color">
      <color>
      <channel currentvalue="0" index="1" max="255" min="0" step="1" title="Red"> 
      <channel currentvalue="0" index="2" max="255" min="0" step="1" title="Green"> 
      <channel currentvalue="0" index="3" max="255" min="0" step="1" title="Blue"> 
      </channel></channel></channel></color>
      </feature>
      </features>
  • setOutputFeatureParametr方法允许为渲染上下文设置输出功能;

    CaptureManager 具有从自定义捕获处理器捕获数据的功能。它包括用于实现自定义CaptureProcessor类的ICaptureProcessor接口。所需的配置在initialize方法的参数中通过IInitilaizeCaptureSource接口设置。所选配置在setCurrentMediaType方法的参数中通过ICurrentMediaType接口设置。所需数据在sourceRequest方法中通过ISourceRequestResult接口的参数请求。它允许开发捕获原始数据的解决方案,例如WPFImageViewerWPFIPCameraMJPEGViewer

版本 1.7.0

CaptureManager SDK v1.7.0。此版本包含以下更改:

  • 添加了与MediaFoundation播放器兼容的CaptureManager视频渲染器工厂。它通过额外的接收器工厂支持IEVRMultiSinkFactoryIEVRStreamControl接口。
<SinkFactory Name="CaptureManagerVRMultiSinkFactory" 
 GUID="{A2224D8D-C3C1-4593-8AC9-C0FCF318FF05}" 
 Title="CaptureManager Video Renderer multi sink factory">
<Value.ValueParts>
<ValuePart Title="Container format" Value="Default" 
 MIME="" Description="Default EVR implementation" 
 MaxPortCount="2" GUID="{E926E7A7-7DD0-4B15-88D7-413704AF865F}" /> 
 </Value.ValueParts>
</SinkFactory>

添加了以下新的演示程序:

CaptureManager视频渲染器工厂可用于在许多解决方案中实现用于从媒体文件渲染视频的视频接收器。

版本 1.8.0

CaptureManager SDK v1.8.0。此版本包含以下更改:

  • 为“屏幕捕获”源添加了“normalize”模式
  • 添加了IRenderingControl接口,用于控制目标进程上的渲染
  • 为“屏幕捕获”源的“光标”选项添加了“形状”属性
  • 为“屏幕捕获”源添加了“剪辑”类型选项

添加了以下新的演示程序:

屏幕捕获”源支持按原样捕获屏幕——这意味着旋转的图像按计算机创建时旋转的方式获取。然而,在某些情况下,可能需要以特定的图像方向获取图像,例如,以“横向”形式获取“纵向”屏幕。为此,“屏幕捕获”源支持“normalize”模式,用于将当前图像后处理到所需方向:“--normalize=Landscape”。

屏幕捕获”源支持背部图像的额外形状——“椭圆”,以及新选项“剪辑”。

新的IRenderingControl接口是从ICaptureManagerControl::createMisc方法创建的,并扩展了渲染过程的控制。IRenderingControl接口具有以下签名:

    MIDL_INTERFACE("1CBCAF1C-1809-41DE-A728-23DBF86A6170")
    IRenderingControl : public IDispatch
    {
    public:
        virtual /* [id][helpstring] */ HRESULT STDMETHODCALLTYPE enableInnerRendering( 
            /* [in] */ IUnknown *aPtrEVROutputNode,
            /* [in] */ BOOL aIsInnerRendering) = 0;
        
        virtual /* [id][helpstring] */ HRESULT STDMETHODCALLTYPE renderToTarget( 
            /* [in] */ IUnknown *aPtrEVROutputNode,
            /* [in] */ IUnknown *aPtrRenderTarget,
            /* [in] */ BOOL aCopyMode) = 0;
        
    };

方法enableInnerRendering允许在CaptureManager中切换渲染模型。方法renderToTarget允许从用户渲染线程执行到目标纹理的渲染。

引入新接口来控制渲染的原因是DirectX的线程安全。在CaptureManager的早期版本中,渲染是在CaptureManager内部线程的上下文中执行的。在大多数情况下,这种渲染方式不会导致问题——在演示程序中,DirectX9设备是支持多线程创建的,DirectX11设备默认是线程安全的。然而,在修改UnityWebCamViewer演示项目以包含DirectX11渲染时,我遇到了渲染问题。经过一些代码搜索,我发现ID3D11Device::CreateDeferredContext返回错误DXGI_ERROR_INVALID_CALL——根据此链接——“如果设备是使用D3D11_CREATE_DEVICE_SINGLETHREADED值创建的,CreateDeferredContext返回DXGI_ERROR_INVALID_CALL。”这意味着Unity引擎不支持从非渲染线程更新纹理。IRenderingControl接口允许从多线程渲染切换到单线程渲染模式。默认情况下,CaptureManager具有多线程渲染,可以通过方法enableInnerRenderingaIsInnerRendering设置为FALSE来切换到单线程渲染。方法renderToTarget允许在单个渲染线程的上下文中渲染到目标——对于目标,可以使用DirectX9的接口IDirect3DTexture9DirectX11的接口ID3D11Texture2D渲染纹理。对于DirectX9,参数aCopyMode必须设置为FALSE。对于DirectX11,参数aCopyMode可以是TRUEFALSE——这取决于渲染纹理的类型。DirectX11允许创建TYPELESS纹理格式以实现类型的灵活转换。但是,DirectX11视频处理器仅支持UNORM类型的渲染纹理。Unity引擎使用TYPELESS纹理格式创建渲染纹理——这会导致错误。将参数aCopyMode设置为TRUE允许创建额外的UNORM类型纹理,并将此纹理用作渲染目标,然后从该纹理复制到原始的TYPELESS纹理格式渲染纹理。此操作在GPU上下文中执行得非常快。

您可以从此处下载CaptureManager帮助文件。

版本 1.9.0

CaptureManager SDK v1.9.0。此版本包含以下更改:

  • 为“屏幕捕获”源添加了“HWND”模式。

添加了新的演示程序。

版本 1.10.0

CaptureManager SDK v1.10.0。此版本包含以下更改:

  • 添加了ISwitcherNodeFactory接口用于媒体流的暂停/恢复
  • 添加了ISwitcherControl接口用于控制(暂停、恢复、分离、连接)媒体流

添加了新的演示程序。

版本 1.11.0

CaptureManager SDK v1.11.0。此版本包含以下更改:

  • 添加了DirectX11纹理捕获
  • 屏幕捕获输出格式从RGB32更改为NV12

版本 1.12.0

CaptureManager SDK v1.12.0。此版本包含以下更改:

  • 添加了带有唯一设备ID的CM_DEVICE_LINK属性
  • 支持DirectX9Ex纹理共享处理器

添加了以下新的演示程序:

版本 1.13.0

CaptureManager SDK v1.13.0。此版本包含以下更改:

  • 添加了对DirectShow视频渲染器的支持

添加了以下新的演示程序:

版本 1.14.0

CaptureManager SDK v1.14.0。此版本包含以下更改:

  • 添加了H.264/MPEG-4 AVC视频流编码器(从256 kbit/c到64 kbit/c)
  • 添加了H.265/MPEG-4 HEVC视频流编码器(64 kbit/c)
  • 添加了AAC音频流编码器(96 kbit/c)
  • 添加了视频流解码器

添加了以下新的演示程序:

版本 1.15.0

CaptureManager SDK v1.15.0。此版本包含以下更改:

  • 添加了快速SSE2/SSSE3/SSE4.2复制功能

版本 1.16.0

CaptureManager SDK v1.16.0。此版本包含以下更改:

  • 添加了用于混合视频和音频流的接口
    • IMixerNodeFactory
    • IVideoMixerControl
    • IAudioMixerControl

添加了以下新的演示程序:

版本 1.17.0

CaptureManager SDK v1.17.0。此版本包含以下更改:

  • H264和H265视频编码器比特率扩展至2^25 (33,554,432)

版本 1.18.0

Capture Manager SDK v1.18.0。此版本包含以下更改:

  • 添加了音频渲染器

添加了新的演示程序

版本 1.19.0

Capture Manager SDK v1.19.0。此版本包含以下更改:

  • 添加了H264网络摄像头的支持

版本 1.19.1

CaptureManager SDK v1.19.1。此版本包含以下更改:

  • 修复了拉取模式的bug
  • 修复了摄像机已被占用时无法打开的bug

版本 1.20.0

CaptureManager SDK v1.20.0。此版本包含以下更改:

  • 添加了async/await调用模型

版本 1.21.0

CaptureManager SDK v1.21.0。此版本包含以下更改:

  • DirectX9渲染替换为DirectX11渲染。

添加了新的演示程序。

版本 1.22.0

CaptureManager SDK v1.22.0。此版本包含以下更改:

  • 添加了新接口ILogPrintOutCallback

添加了新的演示程序

版本 1.23.0

CaptureManager SDK v1.23.0。此版本包含以下更改:

  • 改进了对VMware虚拟机的支持。

版本 1.24.0

CaptureManager SDK v1.24.0。此版本包含以下更改:

  • 增加硬件按钮触发事件支持。

添加了新的演示程序

 

关注点

此前,我曾写道,有一个不寻常的任务,这就是开始开发CaptureManager的原因。该任务包括从两个来源录制工业过程的实时视频以及从两个传感器录制数据。我开始为Microsoft Media Foundation编写一个新解决方案,并考虑使用Microsoft ASF格式,因为存在MFMediaType_Binary主要类型。然而,一段时间后,我发现Microsoft Media Foundation中用于录制的Microsoft ASF格式实现仅支持MFMediaType_VideoMFMediaType_AudioMFMediaType_Script。这是停止解决该任务的主要原因。

更新

首次更新于2015年8月21日

  • 改进了AudioLoopback捕获的质量;
  • 解决了屏幕捕获源(GDIScreenCaptureAudioLoopback)的同步问题;
  • 为屏幕捕获源(GDIScreenCapture)添加了新的媒体类型,帧率如下:20 fps、25 fps和30 fps;
  • 在屏幕捕获源(GDIScreenCapture)中添加了对捕获光标并将其绘制到捕获视频中的支持。

第二次更新于2015年8月31日

  • 修复了AudioLoopback捕获中样本丢失的问题;
  • 为屏幕捕获源(GDIScreenCapture)添加了新的媒体类型,帧率如下:1 fps、5 fps和10 fps;
  • 添加了对WM Speech Encoder DMO的支持;
  • 添加了对WMVideo9 Screen Encoder MFT的支持——FOURCC "MSS2"
  • 添加了适用于x64 Windows操作系统的CaptureManager SDK版本。

第三次更新于2015年9月14日

  • 添加了新的屏幕捕获源 - DirectX9ScreenCapture,具有以下帧率:1 fps、5 fps、10 fps、15 fps、20 fps、25 fps、30 fps;
  • 添加了库CaptureManagerProxy.dll,用于以“C”样式调用CaptureManagerSDK描述符;
  • 添加了对第三方媒体接收器解决方案的支持 - RecordInto3rdPartyMediaSink
  • 添加了用于捕获DirectX9视频游戏屏幕的演示程序 - ScreenCaptureWithDirect3D9APIHooks

第四次更新于2015年9月30日

第五次更新于2015年11月13日

第六次更新于2016年3月7日

  • 第一个稳定版本1.0.0;
  • 在COM服务器中实现了对所有SDK功能的支持;
  • 编写了新的COM服务器接口;
  • 停止了C接口的开发;
  • GDIScreenCaptureDirectX9ScreenCapture替换为屏幕捕获;
  • 包含了对播放、暂停、停止和关闭功能的完全支持;
  • 更新了演示程序代码,用于展示通过C#-WPF上的COM使用CaptureManagerSDK
  • 添加了新的演示程序,用于演示录制和网络流媒体内容的功能。

第七次更新于2016年3月21日

  • 添加了在MinGW编译器上的Qt框架程序中使用CaptureManager SDK的演示程序:QtMinGWDemo

第八次更新于2016年4月12日

  • 添加了在Windows Store应用程序中使用CaptureManager SDK的演示程序。

第九次更新于2016年6月13日

稳定版本 1.1.0

  • 添加了对通过类型库自动注册CaptureManager为COM服务器的支持;
  • 添加了对动态语言Python 2.7的支持;
  • 添加了对Windows 10中HEVC(H265)编码器的支持:下载HEVCEncoder_Windows10

第十次更新于2016年8月1日

测试版 1.2.0

  • 删除了通过库链接使用CaptureManager的旧功能。旧的演示程序已移至旧演示
  • 实现了Microsoft Media Foundation函数的延迟绑定;
  • 将网络摄像头属性功能从DirectShow替换为DeviceIoControl
  • 在EVR中添加了调整大小功能。

第十一次更新于2016年9月5日

版本 1.2.0

  • 删除了通过库链接使用CaptureManager的旧功能。旧的演示程序已移至旧演示
  • 实现了Microsoft Media Foundation函数的延迟绑定;
  • 将网络摄像头属性功能从DirectShow替换为DeviceIoControl
  • 在EVR中添加了调整大小功能。
  • SampleGrabberCallSinkFactory中添加了PULL模式——一种允许获取单个样本的新模式;
  • 添加了样本累加器节点,用于在媒体流中存储最后5个或10个样本;
  • 添加了四个新的演示程序

第十二次更新于2016年10月3日

测试版 1.3.0

  • 添加了通过DirectShow Crossbar技术进行视频捕获的功能,支持以下输入:
    • 复合
    • SVideo
    • USB
    • 1394 (火线)

第十三次更新于2016年12月19日

为Java编程语言添加了两个新的演示程序

第十四次更新于2017年1月2日

  • 添加了NuGet CaptureManager SDK包——适用于NET 4.0项目的包。它基于CaptureManagerToCSharpProxy,但有一个区别——该包包含CaptureManager.dll作为嵌入资源。它允许在运行时将CaptureManager.dll解压到Temp文件夹中,并将其上传到应用程序进程中。这比在系统中注册CaptureManager.dll作为COM服务器模块更简单——它不需要管理员权限。此包支持x86和x64 CPU架构的CaptureManager.dll,并在运行时选择合适的。

第十五次更新于2017年1月9日

版本 1.3.0。此版本包含以下更改:

  • 添加了通过DirectShow Crossbar技术进行视频捕获的支持,用于以下输入:
    • 复合
    • SVideo
    • USB
    • 1394 (火线)
  • COM ThreadingModel 已从 Apartment 更改为 Both

第十六次更新于2017年1月16日

添加了支持源类型选择的WindowsForms GDI的新演示程序

测试版 1.4.0 - 版本 1.4.0 测试版

  • 添加支持更改屏幕捕获源中光标的呈现方式:使光标不可见,并为当前光标图像添加绘制附加图像。

第十七次更新于2017年2月6日

添加了新的演示程序

版本 1.4.0 - 版本 1.4.0。此版本包含以下更改:

  • 添加了对屏幕捕获源中光标显示更改的支持:使光标不可见,并为当前光标图像添加了额外的图像绘制。
  • 添加了对使用自定义Microsoft Media Foundation Transform实现自定义效果的支持。

下载 CaptureManager SDK v1.4.0

第十八次更新于2017年3月13日

版本 1.5.0 - 版本 1.5.0。此版本包含以下更改:

  • 添加了对多显示器输出的支持;
  • 添加了对DirectShow软件过滤器的支持。

第十九次更新于2017年5月1日

添加了新的演示程序

版本 1.6.0 - 版本 1.6.0。此版本包含以下更改:

  • 添加了IEVRMultiSinkFactoryIEVRStreamControl接口:IEVRMultiSinkFactory允许为同一个渲染上下文(窗口句柄HWNDDirect3DSurface9IDXGISwapChain)创建两个独立的拓扑节点。IEVRStreamControl允许控制渲染——渲染位置、Z轴渲染顺序、流缓冲区的刷新、过滤器和功能。
  • IInitilaizeCaptureSourceICurrentMediaTypeISourceRequestResultICaptureProcessor接口:IInitilaizeCaptureSource接收捕获处理器的配置。ICurrentMediaType包含所选媒体类型的信息。ISourceRequestResult接收IInitilaizeCaptureSource中定义的格式的原始数据。ICaptureProcessor控制捕获过程。

第二十次更新于2017年7月10日

添加了以下新的演示程序:

版本 1.7.0 - 版本 1.7.0。此版本包含以下更改:

  • 添加了与MediaFoundation播放器兼容的CaptureManager视频渲染器工厂。

下载 CaptureManager SDK v1.7.0

第二十一次更新于2017年7月31日

添加了新的演示程序

第二十二次更新于2017年8月21日

添加了新的演示程序

第二十三次更新于2017年10月23日

发布 1.8.0 - 版本 1.8.0。此版本包含以下更改:

  • 为“屏幕捕获”源添加了“normalize”模式;
  • 添加了IRenderingControl接口,用于控制目标进程上的渲染;
  • 为“屏幕捕获”源的“光标”选项添加了“形状”属性;
  • 为“屏幕捕获”源添加了“剪辑”类型选项。

添加了新的演示程序

第二十四次更新于2018年1月15日

版本 1.9.0 - 版本 1.9.0。此版本包含以下更改:

  • 为“屏幕捕获”源添加了“HWND”模式

添加了新的演示程序

第二十五次更新于2018年3月5日

添加了新的演示程序

第二十六次更新于2018年4月9日

发布 1.10.0 - 版本 1.10.0。此版本包含以下更改:

  • 添加了ISwitcherNodeFactory接口用于媒体流的暂停/恢复;
  • 添加了ISwitcherControl接口用于控制(暂停、恢复、分离、连接)媒体流。

添加了新的演示程序

第二十七次更新于2018年8月13日

发布 1.11.0 - 版本 1.11.0。此版本包含以下更改:

  • 添加了DirectX11纹理捕获
  • 屏幕捕获输出格式从RGB32更改为NV12

第二十八次更新于2018年10月1日

发布 1.12.0 - 版本 1.12.0。此版本包含以下更改:

  • 添加了带有唯一设备ID的CM_DEVICE_LINK属性;
  • 支持DirectX9Ex纹理共享句柄。

添加了新的演示程序

第二十九次更新于2018年11月19日

发布 1.13.0 - 版本 1.13.0。此版本包含以下更改:

  • 添加了对DirectShow视频渲染器的支持。

添加了新的演示程序。

第三十次更新于2019年2月11日

发布 1.14.0 - 版本 1.14.0。此版本包含以下更改:

  • 添加了H.264/MPEG-4 AVC视频流编码器(从256 kbit/c到64 kbit/c)
  • 添加了H.265/MPEG-4 HEVC视频流编码器(64 kbit/c)
  • 添加了AAC音频流编码器(96 kbit/c)
  • 添加了视频流解码器

添加了新的演示程序

第三十一次更新于2019年4月15日

发布 1.15.0 - 版本 1.15.0。此版本包含以下更改:

  • 添加了快速SSE2/SSSE3/SSE4.2复制功能。

第三十二次更新于2020年7月6日

发布 1.16.0 - 版本 1.16.0。此版本包含以下更改:

  • 添加了用于混合视频和音频流的接口
    • IMixerNodeFactory
    • IVideoMixerControl
    • IAudioMixerControl

添加了新的演示程序

第三十三次更新于2020年7月20日

  • 发布了CaptureManager SDK的源代码,采用开源许可。源代码可从此链接下载。

第三十四次更新于2020年11月16日

发布 1.17.0 - 版本 1.17.0。此版本包含以下更改:

  • H264和H265视频编码器比特率扩展至2^25 (33,554,432)

第三十五次更新于2021年2月1日

发布 1.18.0 - 版本 1.18.0。此版本包含以下更改:

第三十六次更新于2021年4月12日

发布 1.19.0 - 版本 1.19.0。此版本包含以下更改:

  • 添加了对H264网络摄像头的支持

第三十七次更新于2021年9月6日

发布 1.19.1 - 版本 1.19.1。此版本包含以下更改:

  • 修复了拉取模式的bug
  • 修复了摄像机已被占用时无法打开的bug

第三十八次更新于2021年12月6日

发布 1.20.0 - 版本 1.20.0。此版本包含以下更改:

  • 添加了async/await调用模型

第三十九次更新于2022年7月4日

发布 1.21.0 - 版本 1.21.0。此版本包含以下更改:

  • DirectX9渲染替换为DirectX11渲染

添加了新的演示程序。

第四十次更新于2022年12月12日

发布 1.22.0 - 版本 1.22.0。此版本包含以下更改:

  • 添加了新接口ILogPrintOutCallback

添加了新的演示程序。

第四十一次更新于2023年4月1日

发布 1.23.0 - 版本 1.23.0。此版本包含以下更改:

  • 改进了对VMware虚拟机的支持。

第四十二次更新于2023年12月18日

发布 1.24.0 - 版本 1.24.0。此版本包含以下更改:

  • 增加硬件按钮触发事件支持。

添加了新的演示程序。

 

历史

  • 2015年8月11日:版本1
  • 2015年8月21日:更新
  • 2015年8月31日:更新
  • 2015年11月13日:更新
  • 2016年3月7日:更新
  • 2016年3月21日:更新
  • 2016年4月12日:更新
  • 2016年6月13日:更新
  • 2016年8月1日:更新
  • 2016年9月5日:更新
  • 2016年10月3日:更新
  • 2016年12月19日:更新
  • 2017年1月2日:更新
  • 2017年1月9日:更新
  • 2017年1月16日:更新
  • 2017年2月6日:更新
  • 2017年3月13日:更新
  • 2017年5月1日:更新
  • 2017年7月10日:更新
  • 2017年7月31日:更新
  • 2017年8月21日:更新
  • 2017年10月23日:更新
  • 2018年1月15日:更新
  • 2018年3月5日:更新
  • 2018年4月9日:更新
  • 2018年8月13日:更新
  • 2018年10月1日:更新
  • 2018年11月19日:更新
  • 2019年2月11日:更新
  • 2019年4月15日:更新
  • 2020年7月6日:更新
  • 2020年7月20日:更新
  • 2020年11月16日:更新
  • 2021年2月1日:更新
  • 2021年4月12日:更新
  • 2021年9月6日:更新
  • 2021年12月6日:更新
  • 2022年7月4日:更新
  • 2022年12月12日:更新
  • 2023年5月1日:更新
  • 2023年12月18日:更新
© . All rights reserved.