Chromium浏览器media子系统mojo说明
该文件夹包含mojo接口、客户端和实现,它们扩展了核心media来支持大多数进程外使用,包括media Player、Metrics(WatchTime)等。
目前,media不依赖于mojo,因此其他应用程序可以使用media,而无需引入mojo依赖。
文章目录
Media Player
Media组件
Media Player(WebMediaPlayer)支持HTML5 <video>
播放。在内部,它依赖于许多media组件来执行某些特定任务,例如media renderer(媒体渲染器)、audio decoder(音频解码器)、video decoder(视频解码器)和content decryption module(内容解密模块)(CDM)。媒体渲染器、音频解码器或视频解码器需要CDM来处理受保护的内容。
虽然大多数HTML5媒体播放器栈和加密媒体扩展(EME)栈都位于沙盒渲染进程中(例如出于安全原因),但在某些情况下,某些媒体组件必须位于不同的进程中。
例如:
- 基于硬件的媒体渲染器,其中所有音频/视频解码和渲染都在硬件中进行,沙盒渲染进程无法访问。
- 基于硬件的视频解码器,沙盒渲染进程不可访问。
- 在Android系统中,依赖于Android Java API的媒体组件,沙盒渲染进程不可访问。
- 包含第三方代码的CDM,应在其自己的沙盒进程中运行。
我们提供了一个通用框架来支持Chromium中的大多数进程外(OOP)媒体组件。
Media Player Mojo接口
我们使用mojom接口作为每个媒体组件的传输层,以支持远程托管。这些接口称为Media Player Mojo接口。
media::Renderer
->media::mojom::Renderer
media::AudioDecoder
->media::mojom::AudioDecoder
media::VideoDecoder
->media::mojom::VideoDecoder
media::ContentDecryptionModule
->media::mojom::ContentDecryptionModule
media::Decryptor
->media::mojom::Decryptor
开启Remote Media组件
这些接口的标准客户端和实现都已经提供。例如,对于media::Renderer
,MojoRenderer
实现media::Renderer
并将调用转发到media::mojom::RendererPtr
。MojoRendererService
实现media::mojom::Renderer
,并且可以托管任何media::Renderer
实现。
Remote Media组件可以轻松开启并无缝集成到当前媒体管道中。只需设置gn参数mojo_media_services
以指定要启用的远程媒体组件。例如,使用以下gn参数,媒体管道将启用MojoRenderer
和MojoCdm
:
mojo_media_services = ["renderer", "cdm"]
Media Mojo Interface Factory
media::mojom::InterfaceFactory
具有工厂方法,如CreateRenderer()
、CreateCdm()
等。它用于请求媒体播放器mojo接口。
在渲染进程中,每个RenderFrameImpl
都有一个mojo::PendingRemote<media::mojom::InterfaceFactory>
,用于从浏览器进程请求该框架的所有媒体播放器mojo接口。在浏览器进程中,每个RenderFrameHostImpl
都有一个MediaInterfaceProxy
,它实现了media::mojom::InterfaceFactory
。
MediaInterfaceProxy
是处理媒体播放器mojo接口请求的中心。默认情况下,它会将所有请求转发到[MediaService
]。但它也可以灵活地处理一些特殊或更复杂的用例。
例如:
- 在桌面平台上,当启用CDM时,
media::mojom::ContentDecryptionModule
请求将转发到[CdmService
],[CdmService
]在其自己的CDM进程中运行。 - 在Android系统中,
media::mojom::Renderer
请求通过在浏览器进程创建MediaPlayerRenderer
,在RenderFrameHostImpl
上下文进行处理,即使MediaService
配置为在GPU进程中运行。 - 在Chromecast上,
media::mojom::Renderer
和media::mojom::ContentDecryptionModule
请求由运行在浏览器进程中的[MediaRendererService
]处理。media::mojom::VideoDecoder
请求由运行在GPU进程的MediaService
处理。
注意:media::mojom::InterfaceFactory
接口在MediaInterfaceProxy
和MediaService
之间的通信中被重用。
Frameless Media Interface Factory
除了处理普通媒体播放请求的MediaInterfaceProxy
之外,FramelessMediaInterfaceProxy
处理不需要或没有框架的媒体案例请求。
受保护的媒体播放需要框架,因为媒体解码和CDM在框架内相关联。
FramelessMediaInterfaceProxy
由WebCodecs、WebRTC使用,用于早期查询支持的编解码器。
MediaService
MediaService是一个提供媒体播放器mojo接口实现的mojoservice_manager::service
。它的好处如下:
Flexible Process Model
不同的平台或产品,对远程媒体组件应该在哪运行的需求不同。例如,硬件解码器通常应该在GPU进程中运行。
ServiceManagerContext
能够在浏览器进程或GPU进程启动一个服务。因此,通过使用MediaService
,很容易支持托管远程媒体组件在多数Chromium进程种类(Browser/GPU)。这可以使用gn参数mojo_media_host
来设置。
例如:
mojo_media_host = "browser" or “gpu”
MediaService使用kMediaServiceName
在ServiceManagerContext
中注册。mojo_media_host
用于确定服务注册在哪个进程中运行。
Connects Different Media Components
一些远程媒体组件依赖于其他组件来工作。例如,渲染器、音频解码器或视频解码器需要CDM才能处理加密流。通常会调用SetCdm()
来连接CDM渲染器或CDM解码器。例如,如果渲染器接口和CDM接口是单独托管的,那么很难实现SetCdm()
调用。它需要一个对象或实体,同时知道这两者
以便能够连接它们。MediaService
在内部处理此问题,实际上充当这样一个对象或实体,所以你不必重新发明
车轮。
Customization through MojoMediaClient
MediaService
提供托管OOP媒体组件所需的一切,但它不提供媒体组件本身。MediaService
的客户端负责提供具体的媒体组件实现。
MojoMediaClient
接口为MediaService
客户端提供了具体媒体组件实现的方式。创建MediaService
时,必须传入一个MojoMediaClient
,以便media Service
知道如何创建媒体组件。
例如,ChromeCast使用MediaService
在浏览器进程中托管媒体渲染器和CDM,并通过CastMojoMediaClient
(一种MojoMediaClient
实现)提供“CastRenderer”和“CastCdm”。注意,这种重写机制并不是在任何地方都实现的。添加支持很简单,我们只会在需要时添加它。
Site Isolation
在Blink中,媒体元素和EME MediaKeys都属于WebLocalFrame
。在Chromium中,这转换为属于RenderFrame
的媒体播放器和CDM。
在渲染过程中,这一点很清楚。但是,当托管所有远程媒体在一个MediaService
(服务管理器仅支持一个进程单个服务)时,框架边界可能变得模糊。这对于彼此交互的媒体组件来说尤其危险。
例如,foo.com中的Renderer与bar.net中的CDM共用1MediaService
。如果设置foo.com Renderer去处理加密,就会产生错误。
为了防止这种情况发生,我们引入了一个额外的层来模拟RenderFrame
边界。MediaService托管多个InterfaceFactory(每个RenderFrame
一个),每个InterfaceFactory创建和管理它创建的媒体组件。
因此,在MediaInterfaceProxy
和MediaService
之间的通信中media::mojom::InterfaceFactory
被重用。
Specialized Out-of-Process media::Renderers
media::Renderer
接口是一个简单的API,它非常通用,用于执行上层媒体播放命令。我们可以通过专用渲染器来扩展WebMediaPlayer
的功能。
具体来说,我们可以构建一个子组件,它封装了高级场景复杂性,编写一个小适配层,将组件公开为media::Renderer
,并将其嵌入现有的media::Pipeline
状态机。专用渲染器通过限制文件和类的细节,通过需要很少的控制流量样板等方式降低复杂度。
由专用渲染器启用的2个复杂场景如下:一个是通过授权给Android Media Player(请参见MediaPlayerRenderer
)处理Android系统的HLS播放,另一个是将“src=”媒体从Android手机转换为cast设备(请参见FlingingRenderer
)。这两个示例都有子组件需要在浏览器进程中运行。因此,我们使用在renderer.mojom和render_extensions.mojom中定义的Mojo接口将MediaPlayerRenderer
和 FlingingRenderer
代理到浏览器进程
这个想法可以概括为处理任何特殊情况Foo场景的专门的OOP FooRenderer。
创建专用OOP FooRenderer所需的类成对出现,在其各自的过程中用于类似的目的。按照惯例FooRenderer
位于目标进程中,而FooRendererClient
位于渲染器进程。MojoRenderer
和MojoRenderService
代理media::Renderer
和media::RendererClient
,在FooRenderer
和FooRendererClient
之间调用。
无法表示为media::Renderer[Client]
的命令和事件通过renderer extensions(请参见“render_extension.mojom”)进行跨进程操作。
FooRenderer[Client]Extension
mojo接口由相应的FooRenderer[Client]
实现。
FooRenderer[Client]Factory
设置特定场景的样板,和所有与其他进程对话所需的mojo接口指针/请求。当调用mojom::InterfaceFactory::CreateFooRenderer()
时,接口指针和请求进行跨进程。
如下所示:
要允许在WebMediaPlayer中创建和使用FooRenderer,FooRendererClientFactory
必须生成并传递给RendererFactorySelector
。还必须为RendererFactorySelector
提供查询当前是否需要使用FooRenderer
。当我们进入一个Foo scenario时,通过suspend()/resume()循环media::Pipeline
应该足够下次调用
RendererFactorySelector::GetCurrentFactory()
返回
FooRendererClientFactory
。当调用RendererFactory::CreateRenderer()
时,
管道将接收FooRendererClient
作为不透明的media::Renderer
。
默认管道状态机将控制OOP FooRenderer
。当我们退出Foo scenario时,再次循环管道应该会进入正确的状态。
Support Other Clients
MediaService
作为service_manager::Service
,可以由除媒体播放器之外的客户端在渲染过程中使用。例如,我们可以有另一个(mojo)服务来处理音频数据,并希望在媒体渲染器中播放它。由于MediaService
是一个mojo服务,因此任何其他mojo服务都可以通过service_manager::mojom::Connector
连接到它,并使用它所托管的remote media Renderer。
CdmService
尽管MediaService
支持media::mojom::CDM
,但在某些情况下(例如,桌面应用的CDM库),通常出于安全原因远程CDM需要在其自己的进程中运行,CdmService
被引入来处理此问题。
它也实现了service_manager::Service
”,并使用kCdmServiceName
在
ServiceManagerContext
中注册。目前它总是注册为在实用程序进程中运行(使用CDM沙盒类型)。
CdmService
还具有对CDM库的额外支持,例如加载CDM库等。
注意:CdmService
仅支持media::mojom::CDM
,不支持其他媒体播放器mojo接口。
MediaRendererService
MediaRendererService
支持media::mojom::Renderer
和media::mojom::CDM
。它被托管在与默认MediaService
不同的进程中。它使用kMediaRendererServiceName
在ServiceManagerContext
中注册。这允许运行media::mojom::VideoDecoder
和media::mojom::Renderer
在两个不同的进程中。目前Chromecast使用它来支持浏览器进程中的CastRenderer
CDM
和GPU进程中的GPU加速视频解码器。主要目标是:
- 允许两个页面同时保存自己的视频管道,因为
CastRenderer
一次只支持一个视频管道。 - 支持GPU加速视频解码器用于RTC路径。
Mojo CDM and Mojo Decryptor
Mojo CDM在所有媒体播放器Mojo接口中很特殊,因为所有本地/远程媒体组件处理加密缓冲区都需要它。
- 本地媒体组件,如
DecryptingDemuxerStream
,DecryptingAudioDecoder
和Decrypting VideoDecoder
。 - 托管在
MediaService
中的远程媒体组件,例如MojoRendererService
、MojoAudioDecoderService
和
MojoVideoDecoderService
。
在JavaScript层,媒体播放器和MediaKeys通过
[setMediaKeys()
](https://w3c.github.io/encrypted-media/#dom-htmlmediaelement setmediakeys)连接。这由渲染进程的SetCdm()
实现。
媒体组件可以通过两种方式使用CDM。
Using a Decryptor (via CdmContext)
某些CDM提供了Decryptor
实现,支持解密方法,例如Decrypt()
, DecryptAndDecode()
等。AesDecryptor
和CDM库都支持Decryptor
接口。
对于远程CDM,例如由MediaService
或者CdmService
中的MojoCdmService
托管的CDM,如果远程CDM支持Decryptor
接口,
MojoCdm
就也支持Decryptor
接口,这通过MojoDecryptor
实现,它设置了一个新的消息管道来转发所有Decryptor
调用远程CDM中的Decryptor
。
Using CdmContext
在某些情况下,媒体组件被设置为与特定CDM一起工作。
例如,在Android系统中,基于MediaCodec的解码器(例如MediaCodecAudioDecoder
)只能通过MediaCryptoContext
使用基于MediaDrm的CDM 。媒体组件和CDM必须在同一个过程中,因为这两者的相互作用通常发生在OS级别的深处。理论上,他们都可以在
渲染进程。但在实践中,通常是CDM和媒体组件由MediaService在远程(例如GPU)进程中托管。
为了能够将远程CDM连接到远程媒体组件,每个MediaService
中的InterfaceFactoryImpl
实例(对应一个RenderFrame
)维护一个MojoCdmServiceContext
,用于跟踪所有为RenderFrame
创建的远程CDM。每个远程CDM分配一个唯一的CDM ID,这个CDM ID在渲染进程中发送回MojoCdm
。在渲染进程,当SetCdm()
被调用,CDM ID将传递给本地媒体组件(例如MojoRenderer
),转发给远程媒体组件
(例如MojoRendererService
)。远程媒体组件将从
MojoCdmServiceContext
获取与CDM ID关联的CdmContext
,以及
完成连接。
Secure Auxiliary Services
媒体组件通常需要其他服务才能工作。在渲染进程中,本地媒体组件通过MediaClient
从内容层获取服务。在MediaService
和 CdmService
中,远程媒体组件通过secure auxiliary services获取服务。
某些服务确实需要RenderFrame
或用户配置文件标识,例如文件系统。由于媒体组件都属于给定的RenderFrame
,我们出于安全原因,在访问这些服务时必须维护框架标识。这些服务称为安全辅助服务。FrameServiceBase
是所有安全辅助服务的基类,以帮助管理这些服务(例如处理导航)。
创建MediaInterfaceProxy
时,除了提供media::mojom::InterfaceFactory
,RenderFrame
配备了media::mojom::FrameInterfaceFactory
,基于每帧的服务开放这些安全辅助。FrameInterfaceFactory
直接提供一种方法
通过BindEmbedderReceiver()
方法注册其他辅助服务。
目前,只有远程CDM需要安全的辅助服务。这是一个当前支持服务的列表
OutputProtection
:检查输出保护状态PlatformVerification
:检查平台是否安全CdmFileIO
:用于CDM存储持久数据ProvisionFetcher
:用于Android MediaDrm设备配置
Security
在大多数情况下,客户端运行的最不可信的渲染进程。此外,始终假设客户端代码可能受到破坏,例如以随机顺序调用或传入垃圾参数。
由于[Flexible Process Model],有时很难知道服务端在哪个进程中运行。根据经验,假设所有服务端代码可以在特权进程(例如浏览器进程)中运行,包括常见的支持代码,如MojoVideoDecoderService
,以及
具体的[Media Component],例如Android的MediaCodecVideoDecoder。要准确了解 [Media Component]在哪个进程中运行,请参见下面的 [Adoption]。
另请注意,所有 [Secure Auxiliary Services]在一个比媒体所在的进程更有特权的进程中运行。例如,所有现有服务都运行在浏览器进程中。他们必须防御受损的媒体组件。
Adoption
Android
MediaService
in the GPU process (registered inGpuServiceFactory
with
GpuMojoMediaClient
)MojoCdm
+MediaDrmBridge
(CDM)MediaDrmBridge
uses mojoProvisionFetcher
service for CDM provisioningMojoAudioDecoder
+MediaCodecAudioDecoder
MojoVideoDecoder
+MediaCodecVideoDecoder
(in progress)- HLS support:
MojoRenderer
+MediaPlayerRenderer
- NOT using
MediaService
. Instead,MojoRendererService
is hosted by
RenderFrameHostImpl
/MediaInterfaceProxy
in the browser process
directly.
- Flinging media to cast devices (RemotePlayback API):
MojoRenderer
+FlingingRenderer
- NOT using
MediaService
. Instead,MojoRendererService
is hosted by
RenderFrameHostImpl
/MediaInterfaceProxy
in the browser process
directly.
ChromeCast
MediaService
in the Browser process (registered in
CastContentBrowserClient
withCastMojoMediaClient
)MojoRenderer
+CastRenderer
MojoCdm
+CastCdm
Desktop (ChromeOS/Linux/Mac/Windows)
- CdmService
CdmService
in the utility process (registered inUtilityServiceFactory
withContentCdmServiceClient
)MojoCdm
+CdmAdapter
+ Library CDM implementationCdmAdapter
uses various secure auxiliary services
- MediaService (in progress)
MediaService
in the GPU process (registered inGpuServiceFactory
withGpuMojoMediaClient
)MojoVideoDecoder
+ hardware video decoders such as D3D11VideoDecoder
Other Services
TODO:添加其他mojo服务的文档,例如远程处理等。