使用MediaCapture直播

Miz*_*izz 2 c# c++ ms-media-foundation uwp

对于一个项目,我正在尝试创建一个视频通话应用程序.我正在为通用Windows平台做这个,所以我想我可以使用MediaCapture类.这个类确实有这个StartRecordToCustomSinkAsync()方法,但是为了使用它,我需要创建一个自定义接收器.我开始创建一个,但现在我不得不创建流水槽.此链接解释了Media Sinks,但我找不到关于流接收器的任何文档.

我还研究了Simple Communication,WavSink另一个自定义接收器示例,但代码缺少注释或解决了另一个问题.

有谁知道我如何实施UWP视频通话应用程序,或指出我正确的方向?

PS.我知道这类问题更多,但没有可用的答案:(

oze*_*nix 8

您的具体实施

实现自己IMFMediaSinkIMFStreamSink类的最重要的第一步是弄清楚你实际想要对IMFSample你将要接收的实例做什么.一旦你知道你想要制作什么样的接收器,那么考虑你从哪里得到样本,无论是UWP MediaCapture类,一个IMFSinkWriter,IMFTopology甚至是从客户端代码直接调用你的接收器.

如果您的目标是使用双向音频/视频聊天应用程序,则应用的每个实例都需要捕获音频和视频,以压缩格式对其进行编码,然后将这些样本发送到另一个实例.MediaCapture除网络传输部分外,几乎所有这些都将处理.因此,另一个实例必须能够解码那些音频和视频样本,并将它们呈现给用户.因此,您需要一个自定义IMFMediaSink实现,它从中接收IMFSample实例MediaCapture,然后通过网络传输它们.您还需要一个IMFMediaSource实现,它从网络源接收样本并将它们发送给演示者以呈现给用户.在这种情况下,演示者将是一个实例MediaCapture.每个应用实例都将同时运行两个自定义源和接收器 - 一个始终捕获音频和视频并通过网络发送,另一个从另一个应用实例接收并呈现它.

一种方法是为传输媒体接收器和接收媒体源提供套接字接口.您需要找到一种方法来处理网络中断并干净地恢复数据传输和双方显示.处理此问题的最简单方法之一是在实际的视频和音频样本传输之上设置轻量级消息传递层,以便您可以表示从发送方到接收方的流消息的开始,这将允许接收方转储任何排队的样本,并使用第一个到达的新样本重新启动其显示时钟,然后在此之后保持一致的显示.同样,MediaCapture该类可能会为您处理很多逻辑,但您必须小心,您的源和接收器的行为就像MediaCapture期望它们一样.

您还需要建立一个方案来缓冲每侧的数据,以便小的网络延迟不会导致音频和视频中的卡顿.如果您以30 fps播放视频,则需要每33毫秒显示一个新帧,这意味着每个应用实例都需要缓冲足够的数据以保证演示者能够以该间隔显示帧.音频大致相同 - 音频样本具有持续时间,因此您需要确保有足够的样本数据用于连续播放.但是,你也不需要太多的缓冲,因为你会得到一个"卫星电视"效果,人类对话的心理造成每个人说话之间的巨大差距,因为他们正在等待你的应用程序传输的音频停止在他们开始谈话之前,缓冲延迟会放大这些差距.Microsoft 在这里对缓冲进行了一般性的讨论,即使它特别适用于ASF流,它也很有用.

我最后一个实现相关的建议是看看Media Foundation可以使用的现有网络流格式.虽然您仍然需要实现自己的源,媒体接收器和流接收器,但通过围绕现有Media Foundation实现编写支持WinRT的包装器,您可以避免担心绝大多数低级别细节.我在这里看到的最直接的路径是编写一个实现的包装类IMFMediaSink,并保存一个用它创建的媒体接收器的内部副本MFCreateASFStreamingMediaSink.您可能还需要编写IMFByteSource可以提供给媒体接收器的启用WinRT的实现,然后将其传递到ASF流式接收器.在源端,您将编写一个IMFMediaSource从网络字节流中读取并包装ASF文件媒体源的文件.


IMFMediaSink和的概述IMFStreamSink

我无法为您提供能够满足您需求的代码,主要是因为实现自定义媒体接收器和一组流接收器需要大量工作,在这种情况下您的需求非常具体.相反,我希望我能帮助您更好地理解Media Foundation中媒体接收器和流接收器的作用.我离UWP专家很远,所以我会尽可能多地在媒体基金会方面做出贡献.

根据我对UWP MediaCapture类的理解,自定义接收器负责与WinRT端和COM/Media Foundation端的接口,因此您必须在两端实现接口.这是因为MediaCapture或多或少是一个UWP包装器,而不是很多被抽象出来的Media Foundation技术.的简易通信样品实际上是这是非常有用的,并且具有大量的大起动代码,尤其是在公共/ MediaExtensions部,其中有一个C++实现具有网络功能的定制媒体宿的.该实现还向您展示了如何将自定义接收器与WinRT接口,以便它可以在UWP环境中使用.

下面是关于媒体和流接收器功能的一般性讨论,客户端代码使用它们的一般方式,以及它们的设计和实现的一般方法.


使用现有的 IMFMediaSink

在本节中,我将介绍如何在实践中使用媒体接收器和流接收器.希望这将给予一定的见解,以设计决策微软架构媒体基金会的这部分时所做,并帮助您了解客户代码(甚至是你自己的客户端代码)将如何使用您的自定义执行IMFMediaSinkIMFStreamSink.


媒体接收器可以保存数据

让我们来看看你如何在Media Foundation中使用特定的媒体接收器实现.我们将从您拨打电话时收到的媒体接收器开始MFCreateMPEG4MediaSink.请记住,IMFMediaSink无论它们代表什么类型的媒体,所有媒体接收器都实现了接口.该功能创建了一个IMFMediaSink内置于Media Foundation的功能,负责使用MPEG4容器结构创建输出.创建它时,必须提供IMFByteStream应写入输出MP4数据的内容.媒体接收器负责维护IMFStreamSink对象集合,每个对象用于媒体接收器的每个输入流.在MPEG4媒体接收器的上下文中,仅存在两个可能的输出流,因此存在预定数量的流接收器.这是因为MPEG4格式仅允许一个视频流和/或一个音频流.通过这种方式,MPEG4媒体接收器强制执行各种合同 - 它限制客户端代码可以使用它来遵守它所编写的媒体容器的规范.要访问这些接收器,您可以使用IMFMediaSink::GetStreamSinkCountIMFMediaSink::GetStreamSinkByIndex.如果您有音频和视频,则总共有2个接收器,其中索引0用于视频主要类型,索引1用于音频主要类型.

其他类型的媒体接收器(如您在呼叫时获得的接收器)MFCreateASFMediaSink允许多个音频和视频流,并要求您调用IMFMediaSink::AddStreamSink要写入媒体接收器的每个流.查看文档,您可以看到您必须提供一个流接收器标识符(一个唯一的DWORDint您想用于在介质接收器中引用该流的标识符),并IMFMediaType通知媒体接收器您将发送哪种数据流下沉.作为回报,您会收到一个IMFStreamSink,并使用该流接收器来写入该流的数据.

另外,您可以IMFMediaSink通过IMFMediaSink::GetCharacteristics检查输出标志来确定任意是否支持固定或可变数量的流MEDIASINK_FIXED_STREAMS.此外,大多数将数据存储到字节流的媒体接收器也会有一个标志MEDIASINK_RATELESS,这意味着它将尽可能快地使用采样,因为它们都被写入字节流并且没有理由等待或者将该过程同步到演示时钟.下一节将对此进行更多讨论.

这是一个简单的实例,一步一步.如果您有H.264视频流和AAC音频流,它们中的每一个都是一系列IMFSample实例,您可能希望将它们保存到硬盘驱动器上的文件中.假设您想要使用ASF容器格式.您将创建一个IMFByteStream使用文件作为其存储方法,然后创建一个ASF媒体接收器MFCreateASFMediaSink.然后,您将调用IMFMediaSink::AddStreamSink添加H.264视频流.您将传入该流的唯一标识符,如0,并IMFMediaType指定媒体的主要类型(视频),媒体的子类型(H.264),帧大小和帧速率等内容.您将收到一个IMFStreamSink后退,您可以使用该特定的流接收器及其IMFStreamSink::ProcessSample方法发送所有H.264编码的视频样本.然后,您AddStreamSink再次拨打AAC音频流,再次收到IMFStreamSink您可以用来发送所有AAC编码音频样本的信息.流接收器也知道他们的父亲IMFMediaSink是谁,并且每个流接收器与媒体接收器一起将数据打包成单个媒体流,其输出在IMFByteStream您之前创建的.设置好所有流接收器后,您将反复呼叫IMFStreamSink::ProcessSample每个接收器,IMFSample从源流中提供每种适当的类型(即视频或音频).


媒体汇集了数据

在这种情况下,存在意味着"在数据到达时呈现数据".一个例子是增强视频渲染器(EVR),它实现了IMFMediaSink接口.EVR负责IMFSample通过其流接收器接收实例,您可以通过IMFMediaSink::AddStreamSink或者IMFMediaSink::GetStreamSinkByIndex像我上面讨论的那样访问它.如果使用您查询EVR媒体接收器,IMFMediaSink::GetCharacteristics您将看到它没有提供MEDIASINK_RATELESS标志.这是因为作为视频演示者的EVR应该将它接收到的视频样本显示在屏幕上,并且它应该以每个IMFSample都有其开始时间和持续时间的方式这样做.这意味着EVR需要通过IMFPresentationClock接口提供一个演示时钟,以便它能够以正确的帧速率显示视频样本并与时间的推移同步.请记住,其工作只是存储数据的媒体接收器不必担心这一点,因为不需要以某种稳定或一致的速率存储数据.

有一个类似于EVR的音频渲染器,称为流式音频渲染器(SAR),它以类似的方式工作.作为一个思想实验,如果您编写了自己的包含EVR和SAR的媒体接收器,请考虑基本架构,这样您就可以将视频和音频流连接到自定义接收器,并且自定义流接收器实现会将示例提供给相应的渲染器基于每个流接收器的媒体类型.这样你就可以创建一个媒体接收器而不是担心EVR和SAR作为单独的媒体接收器.


实现自己的IMFMediaSinkIMFStreamSink

此时,我希望你能看到实现IMFStreamSink完全依赖于实现IMFMediaSink- 换句话说,MPEG4媒体接收器将需要自己特定的实现IMFStreamSink,因为那些流接收器需要将原始媒体数据封装在里面MPEG4容器需要构建一个样本索引和嵌入MPEG4文件的时间戳,等等.


编写自己的MPEG4接收器版本

鉴于上面已经介绍过的内容,如果您正在编写自己的MPEG4接收器版本,请考虑如何构建隐藏在这些接口后面的实际类.您需要一种实现IMFMediaSink,一种创建该实现的实例的方法,然后您需要有一个或多个实现IMFStreamSink直接依赖于您的实现IMFMediaSink.

这是构建实现的一种方法.你将从一个名为CMPEG4MediaSinkImplimplements 的类开始IMFMediaSink,也许你有一个静态的C风格的函数来创建该类的实例,就像MFCreateMPEG4MediaSink.您只支持一个视频和一个音频流,并且每个媒体类型的媒体类型都提供给您的CMPEG4MediaSinkImpl类的非公共构造函数.假设您希望根据媒体的主要类型(即音频或视频)设置不同的流接收器,可能是因为您需要单独计算您收到的音频和视频样本数量.构造函数将检查每种媒体类型,对于视频流,它将创建一个实例CMPEG4StreamSinkVideoImpl,并且对于音频,它将创建一个实例CMPEG4StreamSinkAudioImpl.每个类实现都将实现IMFStreamSink并负责IMFSample在其IMFStreamSink::ProcessSample实现中接收各个实例.也许在这些实现中,每个流接收器将调用CMPEG4MediaSinkImpl::WriteSampleBlock将创建适当的MPEG4数据结构来包装样本数据,以及在MPEG4结构中记录采样时间和持续时间,然后将其嵌入到输出中IMFByteStream.