对于我正在尝试创建视频通话应用的项目。我是为通用Windows平台做的,所以我想我可以使用MediaCapture类。
这个类确实有StartRecordToCustomSinkAsync()
方法,但为了使用它,我需要创建一个自定义接收器。我开始创建一个,但现在我不得不创建流水槽。 This link解释了Media Sinks,但我无法找到关于流水槽的任何文档。
我还查看了Simple Communication,WavSink和another custom sink example,但代码缺少评论或解决了其他问题。
有谁知道如何实施UWP视频通话应用程序,或指出我的方向正确?
聚苯乙烯。我知道这类问题更多,但没有可用的答案:(
答案 0 :(得分:8)
实施您自己的IMFMediaSink
和IMFStreamSink
课程最重要的第一步是弄清楚您实际想要对您将要接收的IMFSample
个实例做些什么。一旦你知道你想要制作什么样的接收器,那么考虑你从哪里得到样本,是否是UWP MediaCapture
类,IMFSinkWriter
,{ {1}},甚至只是从客户端代码直接调用您的接收器。
如果您的目标是使用双向音频/视频聊天应用程序,则应用的每个实例都需要捕获音频和视频,以压缩格式对其进行编码,然后将这些示例发送到另一个实例。除了网络传输部分之外,IMFTopology
将处理几乎所有这些事情。因此,另一个实例必须能够解码那些音频和视频样本,并将它们呈现给用户。因此,您需要一个自定义MediaCapture
实现,它从IMFMediaSink
接收IMFSample
个实例,然后通过网络传输它们。您还需要一个MediaCapture
实现,该实现从网络源接收样本并将它们发送给演示者以呈现给用户。在这种情况下,演示者将是IMFMediaSource
的实例。每个应用程序实例都将同时运行两个自定义源和接收器 - 一个始终捕获音频和视频并通过网络发送,另一个从另一个应用程序实例接收并呈现它。
执行此操作的一种方法是为传输媒体接收器和接收媒体源提供套接字接口。您需要找到一种方法来处理网络中断并干净地恢复数据传输和双方显示。处理此问题的最简单方法之一是在实际的视频和音频样本传输之上设置轻量级消息传递层,以便您可以表示从发送方到接收方的流消息的开始,这将允许接收方转储任何排队的样本,并使用第一个到达的新样本重新启动其显示时钟,然后在此之后保持一致的显示。同样,MediaCapture
类可能会为您处理很多逻辑,但您必须小心,您的源和接收器的行为与MediaCapture
期望的一样。
您还需要建立一个方案来缓冲每一侧的数据,这样小的网络延迟不会导致音频和视频的卡顿。如果您以30 fps的速度播放视频,则需要每33毫秒显示一个新帧,这意味着每个应用实例都需要缓冲足够的数据,以保证演示者能够以该间隔显示帧。音频大致相同 - 音频样本具有持续时间,因此您需要确保有足够的样本数据用于连续播放。但是,你也不想要太多的缓冲,因为你会得到一个卫星电视"效果,人类对话心理导致每个人说话之间存在较大差距,因为他们等待你的应用程序传输的音频在开始讲话之前停止,而缓冲延迟会放大这些差距。 Microsoft对缓冲here进行了一般性讨论,即使它特别适用于ASF流媒体,它也很有用。
我最后一个实现相关的建议是看看Media Foundation可以使用的现有网络流格式。虽然您仍然需要实现自己的源,媒体接收器和流接收器,但通过围绕现有Media Foundation实现编写支持WinRT的包装器,您可以避免担心绝大多数低级别细节。我在这里看到的最简单的路径是编写一个实现MediaCapture
的包装类,并保存使用MFCreateASFStreamingMediaSink
创建的媒体接收器的内部副本。您可能还需要编写一个启用WinRT的IMFByteSource
实现,您可以将其提供给媒体接收器,然后将其传递到ASF流式接收器。在源端,您正在编写一个IMFMediaSink
,它从网络字节流中读取并包装ASF文件媒体源。
IMFMediaSource
和IMFMediaSink
我无法为您提供能够满足您需求的代码,主要是因为实现自定义媒体接收器和一组流接收器需要大量工作,在这种情况下您的需求非常具体。相反,我希望我能帮助您更好地理解Media Foundation中媒体接收器和流接收器的作用。我离UWP专家很远,所以我会在媒体基金会方面尽我所能。
根据我对UWP的IMFStreamSink
类的理解,自定义接收器负责与WinRT端和COM / Media Foundation端的接口,因此您必须在两端实现接口。这是因为MediaCapture
或多或少是一个UWP包装器,而不是很多被抽象出来的Media Foundation技术。 Simple Communication示例实际上非常有用,并且有很多很棒的入门代码,特别是在common/MediaExtensions部分,它具有自定义网络媒体接收器的C ++实现。该实现还向您展示了如何将自定义接收器与WinRT接口,以便它可以在UWP环境中使用。
下面是关于媒体和流接收器功能的一般性讨论,客户端代码使用它们的一般方式,以及它们的设计和实现的一般方法。
<小时/>
MediaCapture
在本节中,我将介绍如何在实践中使用媒体接收器和流接收器。这有望为Microsoft在构建Media Foundation的这一部分时所做的设计决策提供一些见解,并帮助您了解客户端代码(甚至您自己的客户端代码)将如何使用IMFMediaSink
和{{1}的自定义实现}。
让我们看一下您如何在Media Foundation中使用特定的媒体接收器实现。我们会在您致电MFCreateMPEG4MediaSink
时收到您收到的媒体接收器。请记住,无论它们代表什么类型的媒体,所有媒体接收器都会实现IMFMediaSink
接口。该函数创建一个内置于Media Foundation的IMFMediaSink
,并负责使用MPEG4容器结构创建输出。创建它时,必须提供一个IMFByteStream
,其中应写入输出MP4数据。媒体接收器负责维护IMFStreamSink
个对象的集合,每个对象用于媒体接收器的每个输入流。在MPEG4媒体接收器的上下文中,仅存在两个可能的输出流,因此存在预定数量的流接收器。这是因为MPEG4格式仅允许一个视频流和/或一个音频流。通过这种方式,MPEG4媒体接收器实施了各种合同 - 它限制了客户端代码可以用它做什么来尊重它所写的媒体容器的规范。要访问这些接收器,您可以使用IMFMediaSink::GetStreamSinkCount
和IMFMediaSink::GetStreamSinkByIndex
。如果您有音频和视频,则总共有2个接收器,其中索引0用于视频主要类型,索引1用于音频主要类型。
其他类型的媒体接收器,例如您在拨打MFCreateASFMediaSink
时获得的接收器,允许多个音频和视频流,并要求您为要写入媒体接收器的每个流调用IMFMediaSink::AddStreamSink
。查看文档,您可以看到必须提供流接收器标识符(要用于在介质接收器中引用该流的唯一IMFStreamSink
或IMFMediaSink
)和{{3}通知媒体接收器您将发送哪个数据流。作为回报,您会收到DWORD
,并使用该流接收器为该流写入数据。
另外,您可以通过IMFMediaType
确定任意int
是否支持固定或可变数量的流,并检查IMFStreamSink
的输出标志。此外,大多数将数据存储到字节流的媒体接收器也会有一个名为IMFMediaSink
的标志,这意味着它将尽可能快地使用采样,因为它们都被写入字节流并且#39;没有理由等待或将该过程同步到演示时钟。下一节将对此进行更多讨论。
这是一个简单明了的例子,一步一个脚印。如果您有H.264视频流和AAC音频流,每个都是一系列MEDIASINK_FIXED_STREAMS
实例,您可能希望将它们保存到硬盘驱动器上的文件中。我们假设你想使用ASF容器格式。您创建了一个MEDIASINK_RATELESS
,它使用文件作为存储方法,然后使用IMFSample
创建一个ASF媒体接收器。然后,您拨打IMFByteStream
来添加H.264视频流。您传入该流的唯一标识符(如0)和MFCreateASFMediaSink
,其中指定的内容类似于媒体的主要类型(视频),媒体的子类型(H .264),帧大小和帧速率等。您将收到IMFMediaSink::AddStreamSink
后退,并且您可以使用该特定流接收器及其IMFMediaType
方法发送所有H.264编码的视频样本。然后,您再次为{A}音频流拨打IMFStreamSink
,然后再次收到IMFStreamSink::ProcessSample
,您可以使用该AddStreamSink
发送所有AAC编码的音频样本。流接收器也知道他们的父IMFStreamSink
是谁,并且每个流接收器与媒体接收器一起将数据打包成单个媒体流,其输出位于您之前创建的IMFMediaSink
中。设置完所有流式接收器后,您需要在每个接收器上重复调用IMFMediaSink::GetCharacteristics
,从源流中提供相应类型(即视频或音频)的每个IMFByteStream
。
现在,在这种情况下,意味着&#34;在数据到达时呈现数据。&#34;一个例子是增强视频渲染器(EVR),它实现了IMFSample
接口。 EVR负责通过其流接收器接收IMFMediaSink
个实例,您可以通过IMFSample
或IMFMediaSink::AddStreamSink
访问这些实例,就像我上面讨论过的那样。如果您使用IMFMediaSink::GetStreamSinkByIndex
查询EVR媒体接收器,则会发现它未提供IMFMediaSink::GetCharacteristics
标记。这是因为作为视频演示者的EVR应该将它接收到的视频样本显示在屏幕上,并且它应该以每个MEDIASINK_RATELESS
的开始时间和持续时间得到尊重的方式进行。这意味着EVR需要通过IMFSample
接口提供演示时钟,以便它能够以正确的帧速率显示视频样本并与时间流逝同步。请记住,媒体汇的工作就是简单地存储数据而不必担心这一点,因为不需要以稳定或一致的速率存储数据。
有一个类似于EVR的音频渲染器,称为Streaming Audio Renderer(SAR),它的工作方式类似。作为一个思想实验,如果您编写了自己的包含EVR和SAR的媒体接收器,请考虑基本架构,这样您就可以将视频和音频流连接到自定义接收器,并且自定义流接收器实现会将示例提供给相应的渲染器基于每个流接收器的媒体类型。这样你就可以创建一个媒体接收器而不是担心EVR和SAR作为单独的媒体接收器。
<小时/>
IMFPresentationClock
和IMFMediaSink
此时,我希望您可以看到IMFStreamSink
的实现完全取决于IMFStreamSink
的实现 - 换句话说,MPEG4媒体接收器将需要其自己的特定实现IMFMediaSink
,因为那些流接收器需要将原始媒体数据打包到MPEG4容器中,需要构建一个样本索引和时间戳来嵌入MPEG4文件,等等。
鉴于上面已经介绍过的内容,如果您正在编写自己的MPEG4接收器版本,请考虑如何构建隐藏在这些接口后面的实际类。您需要IMFStreamSink
的实现,这是一种创建该实现的实例的方法,然后您需要有一个或多个直接依赖于IMFMediaSink
的{{1}}实现您对IMFStreamSink
。
这是构建实施的一种方法。您从一个名为IMFMediaSink
的类开始实现CMPEG4MediaSinkImpl
,并且您可能有一个静态C风格的函数来创建该类的实例,就像{{1}一样}。您只支持一个视频和一个音频流,并且每个媒体类型的媒体类型都提供给IMFMediaSink
类的非公共构造函数。我们假设您希望根据媒体的主要类型(即音频或视频)设置不同的流式传输,这可能是因为您需要单独计算您收到的音频和视频样本数量。构造函数将检查每种媒体类型,对于视频流,它将创建MFCreateMPEG4MediaSink
的实例,对于音频,它将创建CMPEG4MediaSinkImpl
的实例。每个类实现都将实现CMPEG4StreamSinkVideoImpl
,并负责在CMPEG4StreamSinkAudioImpl
实现中接收单个IMFStreamSink
实例。也许在这些实现中,每个流接收器将调用IMFSample
来创建适当的MPEG4数据结构来包装样本数据,以及在MPEG4结构中记录采样时间和持续时间,然后将其嵌入到输出中IMFStreamSink::ProcessSample
。