【问题标题】:Live streaming using MediaCapture使用 MediaCapture 进行实时流式传输
【发布时间】:2017-06-13 04:41:59
【问题描述】:

对于一个项目,我正在尝试创建一个视频通话应用。我正在为通用 Windows 平台执行此操作,所以我想我可以使用 MediaCapture 类。 这个类确实有StartRecordToCustomSinkAsync() 方法,但是为了使用它,我需要创建一个自定义接收器。我开始创建一个,但现在我被困在创建流接收器上。 This link 解释了媒体接收器,但我找不到任何关于流接收器的文档。

我还研究了Simple CommunicationWavSinkanother custom sink example,但代码要么缺少 cmets,要么解决了另一个问题。

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

附言。我知道这类问题问得更多,但没有可用的答案:(

【问题讨论】:

    标签: c# c++ uwp ms-media-foundation


    【解决方案1】:

    您的具体实现

    实现您自己的IMFMediaSinkIMFStreamSink 类最重要的第一步是弄清楚您真正想要对您将收到的IMFSample 实例做什么。一旦你知道你想要制作什么样的接收器,然后考虑你从哪里获取样本,无论是 UWP MediaCapture 类、IMFSinkWriterIMFTopology,甚至只是直接调用你的从客户端代码接收。

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

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

    您还需要建立一个方案来缓冲每一端的数据,这样小的网络延迟就不会导致音频和视频卡顿。如果您以 30 fps 的速度播放视频,则需要每 33 毫秒显示一个新帧,这意味着每个应用程序实例都需要缓冲足够的数据以保证演示者可以在该间隔显示一个帧。音频大致相同 - 音频样本具有持续时间,因此您需要确保有足够的样本数据进行连续播放。但是,您也不需要过多的缓冲,因为您将获得“卫星电视”效果,其中人类对话的心理会导致每个人说话之间存在很大的差距,因为他们正在等待您的应用程序传输的音频停止在他们开始说话之前,缓冲延迟会放大这些差距。 Microsoft 对缓冲 here 进行了一般性讨论,即使它专门与 ASF 流相关,也值得考虑。

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


    IMFMediaSinkIMFStreamSink 的概述

    我不能给你代码来做你想做的事,主要是因为实现一个自定义媒体接收器和一组流接收器需要大量的工作,而且在这种情况下你的需求是非常具体的。相反,我希望我能帮助你更好地理解媒体接收器和流接收器在 Media Foundation 中的作用。我远不是 UWP 专家,所以我会在媒体基金会方面尽我所能做出贡献。

    根据我对 UWP 的MediaCapture 类的了解,自定义接收器负责与 WinRT 端和 COM / Media Foundation 端进行接口,因此您必须在两端实现接口。这是因为MediaCapture 或多或少是对许多抽象出来的媒体基础技术的 UWP 包装器。 Simple Communication 示例实际上对此非常有用,并且有很多很棒的入门代码,尤其是在common/MediaExtensions 部分,它有一个自定义网络启用媒体接收器的 C++ 实现。该实现还向您展示了如何将您的自定义接收器与 WinRT 连接,以便它可以在 UWP 环境中使用。

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


    使用现有的IMFMediaSink

    在本节中,我将介绍如何在实践中使用媒体接收器和流接收器。这有望为 Microsoft 在构建 Media Foundation 的这一部分时所做的设计决策提供一些见解,并帮助您了解客户端代码(甚至您自己的客户端代码)将如何使用您的自定义实现 IMFMediaSinkIMFStreamSink


    保存数据的媒体接收器

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

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

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

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


    呈现数据的媒体接收器

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

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


    实现您自己的IMFMediaSinkIMFStreamSink

    在这一点上,我希望你能看到IMFStreamSink的实现完全依赖于IMFMediaSink的实现——换句话说,一个MPEG4媒体接收器将需要它自己的IMFStreamSink的具体实现,因为这些流接收器需要将原始媒体数据打包到 MPEG4 容器中,需要构建样本索引和时间戳以嵌入到 MPEG4 文件中,等等。


    编写您自己的 MPEG4 接收器版本

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

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

    【讨论】:

      猜你喜欢
      • 2017-11-10
      • 1970-01-01
      • 1970-01-01
      • 2015-11-10
      • 2016-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多