1. 引言
[WEBRTC-NV-USE-CASES] 文档描述了以下用例
-
不受信任的 JavaScript 云会议
本规范提供对编码媒体的访问, 编码媒体是编解码器的编码器部分的输出,也是编解码器的 解码器部分的输入,这允许用户代理在本地应用加密。
该接口受 [WEBCODECS] 启发, 旨在在保留 RTCPeerConnection 设置流程的同时, 提供对此类功能的访问
2. 规范
Streams 定义不怎么使用 WebIDL,但 WebRTC 规范会使用。 本规范展示 WebRTC 的 IDL 扩展。
它在 RTCRtpSender
和 RTCRtpReceiver
上使用附加 API,
以将处理插入到流水线中。
typedef (RTCRtpSFrameEncrypter or RTCRtpScriptTransform );RTCRtpSenderTransform typedef (RTCRtpSFrameDecrypter or RTCRtpScriptTransform ); // RTCRtpSender 和 RTCRtpReceiver 的新方法RTCRtpReceiverTransform partial interface RTCRtpSender {attribute RTCRtpSenderTransform ?transform ; };partial interface RTCRtpReceiver {attribute RTCRtpReceiverTransform ?transform ; };
此 API 允许在媒体
流水线中操控编码帧,
位置在 RTCRtpSender
的
底层编码器与打包器的处理步骤之间,和/或
在 RTCRtpReceiver
的
底层解包器
与解码器之间。
编码器和解包器各自都有一个 [[processedFramesQueue]] 内部槽,初始化为空的 队列, 以及一个 [[transformFrameAlgorithm]] 内部槽,初始化为 直通 算法,该算法给定一个编码帧 frame,返回 frame。
每当编码器输出一个编码帧时,用户 代理必须在其上调用编码器的[[transformFrameAlgorithm]],并将结果传递给关联的打包器,以替代 原始帧。
每当解包器输出 一个编码帧时, 用户代理必须在其上调用解包器的[[transformFrameAlgorithm]] ,并将结果传递给关联的解码器,以替代 原始帧。
2.1. 扩展操作
在构造每个 RTCRtpTransceiver
时,运行以下步骤:
-
将 this.
[[useSFrame]]初始化为 undefined。 -
如果 this 与媒体描述相关联,则从媒体描述初始化 this.
[[useSFrame]]。如果 this.[[useSFrame]]为 true,则为 this 启用 SFrame RTP 打包。 -
否则,排队一个任务来运行以下步骤:
RTCRtpTransceiver.[[useSFrame]]
应该被同步设置(如果它已经绑定到某个 SDP m-section),或在它
关联到某个 SDP m-section 之前异步设置。
这确保 RTCRtpTransceiver.[[useSFrame]]
与对应的 SDP 始终保持同步。
这样做后,与 negotiation-needed 标志相关的处理,例如检查 negotiation-needed 标志算法,
就不需要考虑 RTCRtpTransceiver.[[useSFrame]]。
在构造每个 RTCRtpSender
或 RTCRtpReceiver
时,运行以下步骤:
-
将 this.
[[transform]]初始化为 null。 -
将 this.
[[pipeToController]]初始化为 null。 -
如果 this 是 一个
RTCRtpSender, 则将 this.[[frameSource]]初始化为 this 的编码器, 否则初始化为 this 的解包器。
2.1.1. 流处理
Streams 背压可以通过尽早暂停数据流水线中的 数据生产,来优化吞吐量并限制处理和内存消耗。 这在可靠性至关重要而延迟不那么受关注的上下文中很有用。 另一方面,WebRTC 媒体流水线更偏向低延迟而不是可靠性,例如允许在 不同位置丢弃帧,并使用恢复机制。 在变换中进行缓冲会增加延迟,却不能让 Web 应用进行太多适配。 用户代理负责进行这些适配,尤其是因为它控制变换的两端。 基于这些原因,WebRTC 编码变换中禁用了 streams 背压。
readEncodedData 算法给定一个 RTCRtpScriptTransformer
transformer 作为参数,并以 frame 作为输入。它定义为运行以下
步骤:
-
将 frame.
[[owner]]设置为 transformer.[[frameSource]]。 -
将 frame.
[[counter]]设置为 transformer.[[lastEnqueuedFrameCounter]]。 -
如果 frame.
[[owner]]是解包器:-
如果相关 RTP 包包含 RTP Header Extension for Absolute Capture Time,则将 frame.
[[captureTime]]设置为 绝对 捕获时间戳字段,并且如果存在 frame.[[senderCaptureTimeOffset]], 则将其设置为捕获 时钟偏移字段。 -
否则,如果相关 RTP 包不包含 RTP Header Extension for Absolute Capture Time 但先前的 RTP 包包含, 则将 frame.
[[captureTime]]设置为按照 时间戳 插值计算绝对捕获时间戳的结果,并将 frame.[[senderCaptureTimeOffset]]设置为最近一次存在的值。 -
否则,将 frame.
[[captureTime]]设置为 undefined,并将 frame.[[senderCaptureTimeOffset]]设置为 undefined。
-
-
如果 frame.
[[owner]]是编码器,则使用 RTP Header Extension for Absolute Capture Time § absolute-capture-timestamp 中描述的方法, 将 frame.[[captureTime]]设置为捕获时间戳,并将 frame.[[senderCaptureTimeOffset]]设置为 undefined。 -
将 frame 入队到 transformer.
[[readable]]。
writeEncodedData 算法给定一个 RTCRtpScriptTransformer
transformer 作为参数,并以 frame 作为输入。它定义为运行以下
步骤:
-
如果 frame.
[[owner]]不等于 transformer.[[frameSource]], 则中止这些步骤并返回一个以 undefined 兑现的 promise。处理器不能 创建帧,也不能在流之间移动帧。 -
如果 frame.
[[counter]]小于或等于 transformer.[[lastReceivedFrameCounter]],则中止这些步骤并返回一个以 undefined 兑现的 promise。处理器不能 对帧重新排序,但可以延迟或丢弃它们。 -
将 transformer.
[[lastReceivedFrameCounter]]设置为 frame.[[counter]]。 -
令 data 为 frame.
[[data]]。 -
令 serializedFrame 为 StructuredSerializeWithTransfer(frame, « data »)。
-
令 frameCopy 为 StructuredDeserializeWithTransfer(serializedFrame, frame 的相关 realm)。
-
令 processedFrame 为 frameCopy 的底层编码帧。
-
并行地,将 processedFrame 入队到 transformer.
[[frameSource]].[[processedFramesQueue]]。 -
返回一个以 undefined 兑现的 promise。
在发送端,作为 readEncodedData 的一部分,由编码器生成的帧必须按
编码器的输出顺序被入队到 transformer.[[readable]]
中。
由于 writeEncodedData 确保变换不能
对帧重新排序,因此编码器的输出顺序也是打包器生成 RTP 包
并分配 RTP 包序列号时遵循的顺序。
打包器可以预期
变换后的数据仍然符合原始格式,例如由 Annex B
起始码分隔的一系列 NAL 单元。
在接收端,作为 readEncodedData 的一部分,由解包器生成的帧必须按
相同编码器的输出顺序被入队到 transformer.[[readable]]
中。
为确保遵守该顺序,解包器通常会使用 RTP 包序列号在需要时对
RTP 包重新排序,然后再将帧入队到 transformer.[[readable]]。
由于 writeEncodedData 确保变换不能
对帧重新排序,因此这将是解码器所期望的顺序。
2.1.2. RTCRtpTransform 通用处理
RTCRtpTransform 有一个私有槽:
-
[[owner]],类型为RTCRtpSender或RTCRtpReceiver, 初始化为 null。
每个 RTCRtpTransform 都有一个关联算法 和一个解除关联算法,二者默认 为空。
2.2. 扩展属性
transform getter 步骤为
返回 this.[[transform]]。setter 步骤为:
-
令 transform 为传给 setter 的实参。
-
令 transceiver 为与 this 关联的
RTCRtpTransceiver。 -
如果 transform.
[[useSFrame]]为 true,则运行以下步骤:-
如果 transceiver.
[[useSFrame]]为 false,则抛出InvalidModificationError并中止这些步骤。 -
否则,如果 transceiver.
[[useSFrame]]为 undefined,则运行 以下步骤:-
将 transceiver.
[[useSFrame]]设置为 true。 -
为 transceiver 启用 SFrame RTP 打包。
-
-
-
否则,运行以下步骤:
-
如果 transceiver.
[[useSFrame]]为 true,则抛出InvalidModificationError并中止这些步骤。 -
将 transceiver.
[[useSFrame]]设置为 false。
-
-
如果 transform 不为 null 且 transform.
[[owner]]不为 null, 则抛出InvalidStateError并中止这些步骤。 -
将 transform.
[[owner]]设置为 this。 -
令 oldTransform 为 this.
[[transform]]。 -
如果 oldTransform 不为 null,则运行 oldTransform 的解除关联 算法。
-
将 this.
[[transform]]设置为 transform。 -
如果 transform 为 null,则运行以下步骤:
-
令 frameSource 为 this.
[[frameSource]]。 -
并行地,将 frameSource.[[transformFrameAlgorithm]] 设置为直通算法。
-
此算法的定义方式使得变换可以动态更新。 无法保证从先前变换切换到新变换会发生在哪一帧上。
如果 Web 应用在创建 RTCRtpSender
时同步设置变换(例如调用 addTrack 时),则该变换将接收由 RTCRtpSender
的
编码器生成的第一帧。
类似地,如果 Web 应用在创建 RTCRtpReceiver
时同步设置变换(例如调用 addTrack 时,或在 track 事件处理程序中),则该变换将接收由 RTCRtpReceiver
的
打包器生成的第一个完整帧。
3. SFrame 变换
本节中介绍的 API 允许应用使用 [RFC9605] 中定义的特定密码套件来处理 SFrame 数据。
// List of supported cipher suites, as defined in [[RFC9605]] section 4.5 and in https://datatracker.ietf.org/doc/draft-barnes-sframe-iana-256/.enum {SFrameCipherSuite ,"AES_128_CTR_HMAC_SHA256_80" ,"AES_128_CTR_HMAC_SHA256_64" ,"AES_128_CTR_HMAC_SHA256_32" ,"AES_128_GCM_SHA256_128" ,"AES_256_GCM_SHA512_128" ,"AES_256_CTR_HMAC_SHA512_80" ,"AES_256_CTR_HMAC_SHA512_64" };"AES_256_CTR_HMAC_SHA512_32" dictionary {SFrameTransformOptions required SFrameCipherSuite ; };cipherSuite enum {SFrameType ,"per-frame" };"per-packet" dictionary :RTCRtpSFrameEncrypterOptions SFrameTransformOptions {SFrameType = "per-frame"; };type typedef [EnforceRange ]unsigned long long ;SmallCryptoKeyID typedef (SmallCryptoKeyID or bigint );CryptoKeyID interface mixin {SFrameEncrypterManager Promise <undefined >setEncryptionKey (CryptoKey ,key CryptoKeyID ); };keyId interface mixin {SFrameDecrypterManager Promise <undefined >addDecryptionKey (CryptoKey ,key CryptoKeyID );keyId Promise <undefined >removeDecryptionKey (CryptoKeyID );keyId attribute EventHandler ; }; [onerror Exposed =Window ]interface {RTCRtpSFrameEncrypter constructor (RTCRtpSFrameEncrypterOptions ); };options RTCRtpSFrameEncrypter includes SFrameEncrypterManager ; [Exposed =Window ]interface :RTCRtpSFrameDecrypter EventTarget {constructor (SFrameTransformOptions ); };options RTCRtpSFrameDecrypter includes SFrameDecrypterManager ; [Exposed =(Window ,DedicatedWorker )]interface {SFrameEncrypterStream constructor (SFrameTransformOptions ); };options SFrameEncrypterStream includes GenericTransformStream ;SFrameEncrypterStream includes SFrameEncrypterManager ; [Exposed =(Window ,DedicatedWorker )]interface :SFrameDecrypterStream EventTarget {constructor (SFrameTransformOptions ); };options SFrameDecrypterStream includes GenericTransformStream ;SFrameDecrypterStream includes SFrameDecrypterManager ;enum {SFrameTransformErrorEventType ,"authentication" ,"keyID" }; ["syntax" Exposed =(Window ,DedicatedWorker )]interface :SFrameTransformErrorEvent Event {(constructor DOMString ,type SFrameTransformErrorEventInit );eventInitDict readonly attribute SFrameTransformErrorEventType ;errorType readonly attribute CryptoKeyID ?;keyID readonly attribute any ; };frame dictionary :SFrameTransformErrorEventInit EventInit {required SFrameTransformErrorEventType ;errorType required any ;frame CryptoKeyID ?; };keyID
new RTCRtpSFrameEncrypter(options)
构造器步骤如下:
-
令 options 为该方法的第一个参数。
-
使用 this 和 options 运行 SFrame 初始化算法。
-
将 this.
[[role]]设置为 'encrypt'。 -
将 this.
[[useSFrame]]设置为 true。
new RTCRtpSFrameDecrypter(options)
构造器步骤如下:
-
令 options 为该方法的第一个参数。
-
使用 this 和 options 运行 SFrame 初始化算法。
-
将 this.
[[role]]设置为 'decrypt'。 -
将 this.
[[useSFrame]]设置为 true。
new SFrameEncrypterStream(options)
构造器步骤如下:
-
令 options 为该方法的第一个参数。
-
使用 this 和 options 运行 SFrame 初始化算法。
-
将 this.
[[role]]设置为 'encrypt'。
new SFrameDecrypterStream(options)
构造器步骤如下:
-
令 options 为该方法的第一个参数。
-
使用 this 和 options 运行 SFrame 初始化算法。
-
将 this.
[[role]]设置为 'decrypt'。
3.1. 算法
给定 this 和 options, SFrame 初始化算法运行以下步骤:
-
令 transformAlgorithm 为一个算法,该算法接受 frame 作为输入,并使用 this 和 frame 运行 SFrame 转换算法。
-
如果 options["
type"] 存在,则运行以下步骤:-
如果 options["
type"] 为 'per-frame',则使用 [RTP-SFRAME-PAYLOAD] 中定义的逐帧发送。 -
否则,使用 [RTP-SFRAME-PAYLOAD] 中定义的逐包发送。
-
-
将 this.
[[transform]]设置为一个新的TransformStream。 -
设置 this.
[[transform]],并将 transformAlgorithm 设置为 transformAlgorithm。 -
将 this.
[[cipherSuite]]设置为 options["cipherSuite"]。 -
将 this.
[[readable]]设置为 this.[[transform]].[[readable]]。 -
将 this.
[[writable]]设置为 this.[[transform]].[[writable]]。
给定 this 和 frame, SFrame 变换算法运行以下步骤:
-
令 role 为 this.
[[role]]。 -
如果 this.
[[owner]]是RTCRtpSender, 则将 role 设置为 'encrypt'。 -
如果 this.
[[owner]]是RTCRtpReceiver, 则将 role 设置为 'decrypt'。 -
令 data 为 undefined。
-
如果 frame 是
BufferSource, 则将 data 设置为 frame。 -
如果 frame 是
RTCEncodedAudioFrame, 则将 data 设置为 frame.data -
如果 frame 是
RTCEncodedVideoFrame, 则将 data 设置为 frame.data -
如果 data 为 undefined,则中止这些步骤。
-
令 buffer 为使用 data、 this.
[[cipherSuite]]和 role 作为参数运行 SFrame 算法的结果。 此算法由 [RFC9605] 定义,并返回一个ArrayBuffer。 -
如果 SFrame 算法因错误而突然退出,则排队一个任务来运行以下子步骤:
-
如果由于 data 不符合 SFrame 格式而在解密端处理失败,则在 this 上触发一个名为
error的事件, 使用SFrameTransformErrorEvent接口,并将其errorType属性设置为syntax, 将其frame属性设置为 frame。 -
如果由于在 data 中解析出的密钥标识符未知而在解密端处理失败, 则在 this 上触发一个名为
error的事件, 使用SFrameTransformErrorEvent接口,并将其errorType属性设置为keyID, 将其frame属性设置为 frame,并将其keyID属性设置为在 SFrame 标头中解析出的 keyID 值。 -
如果由于认证标签验证而在解密端处理失败,则在 this 上触发一个名为
error的事件, 使用SFrameTransformErrorEvent接口,并将其errorType属性设置为authentication, 将其frame属性设置为 frame。 -
中止这些步骤。
-
-
如果 frame 是
BufferSource, 则将 frame 设置为 buffer。 -
如果 frame 是
RTCEncodedAudioFrame, 则将 frame.data设置为 buffer。 -
如果 frame 是
RTCEncodedVideoFrame, 则将 frame.data设置为 buffer。 -
将 frame 入队到 this.
[[transform]]。
3.2. 方法
setEncryptionKey(key, keyId)
方法步骤为:
-
令 promise 为一个新的 promise。
-
如果 keyId 是一个无法表示为 0 到 264-1(含)之间整数的
bigint, 则用RangeError异常拒绝 promise 并中止这些步骤。 -
并行地运行以下步骤:
-
返回 promise。
addDecryptionKey(key, keyId)
方法步骤为:
-
令 promise 为一个新的 promise。
-
如果 keyId 是一个无法表示为 0 到 264-1(含)之间整数的
bigint, 则用RangeError异常拒绝 promise, 并中止这些步骤。 -
并行地运行以下步骤:
-
返回 promise。
removeDecryptionKey(keyId)
方法步骤为:
-
令 promise 为一个新的 promise。
-
如果 keyId 是一个无法表示为 0 到 264-1(含)之间整数的
bigint, 则用RangeError异常拒绝 promise, 并中止这些步骤。 -
并行地运行以下步骤:
-
返回 promise。
4. 脚本变换
在本节中,捕获系统指媒体来源所在的系统,发送端系统
指向接收端系统发送 RTP 和 RTCP 包的系统,在接收端系统中会填充 RTCEncodedFrameMetadata
数据。
4.1. RTCEncodedFrameMetadata 字典
dictionary RTCEncodedFrameMetadata {unsigned long synchronizationSource ;octet payloadType ;sequence <unsigned long >contributingSources ;unsigned long rtpTimestamp ;DOMHighResTimeStamp receiveTime ;DOMHighResTimeStamp captureTime ;DOMHighResTimeStamp senderCaptureTimeOffset ;DOMString mimeType ; };
4.1.1. 成员
-
synchronizationSource, 类型为 unsigned longunsigned long -
synchronization source(ssrc)标识符是按 [RFC3550] 定义的无符号整数值,用于标识该编码帧对象所描述的 RTP 包流。
-
payloadType, 类型为 octetoctet -
payload type 是按 [RFC3550] 定义的、范围为 0 到 127 的无符号整数值, 用于描述 RTP payload 的格式。
-
contributingSources, 类型为sequence<unsigned long>sequence<unsigned long> -
contribution sources 列表(csrc list),如 [RFC3550] 所定义。
-
rtpTimestamp, 类型为 unsigned longunsigned long -
RTP timestamp 标识符是按 [RFC3550] 定义的无符号整数值, 它反映 RTP 数据包中第一个 octet 的采样时刻。
-
receiveTime, 类型为 DOMHighResTimeStampDOMHighResTimeStamp -
对于来自 RTCRtpReceiver 的帧,表示用于产生此媒体帧的 最后接收到的包的时间戳。此 时间戳相对于
Performance.timeOrigin。 -
captureTime, 类型为 DOMHighResTimeStampDOMHighResTimeStamp -
此帧在捕获系统时钟中的捕获时间。 填充此成员时,用户代理必须返回该帧的
[[captureTime]]槽的值, 并将其偏移为相对于Performance.timeOrigin。 -
senderCaptureTimeOffset, 类型为 DOMHighResTimeStampDOMHighResTimeStamp -
senderCaptureTimeOffset是发送端系统对其自身 NTP 时钟与捕获系统 NTP 时钟之间偏移量的估计, 针对的是captureTime所来源的同一帧。 填充此成员时,用户代理必须返回该帧的[[senderCaptureTimeOffset]]槽的值。 -
mimeType, 类型为 DOMStringDOMString -
IANA 媒体类型注册表 [IANA-MEDIA-TYPES] 中定义的编解码器 MIME 媒体类型/子类型, 例如 audio/opus 或 video/VP8。
4.2.
RTCEncodedVideoFrameMetadata
字典
dictionary RTCEncodedVideoFrameMetadata :RTCEncodedFrameMetadata {unsigned long long frameId ;sequence <unsigned long long >dependencies ;unsigned short ;width unsigned short ;height unsigned long ;spatialIndex unsigned long ;temporalIndex long long timestamp ; // 微秒 };
4.2.1. 成员
-
frameId, 类型为 unsigned long longunsigned long long -
编码帧的标识符,按解码顺序单调递增。如果存在,其低 16 位匹配 [AV1-RTP-SPEC] 附录 A 中定义的 AV1 Dependency Descriptor Header Extension 的 frame_number。 仅当收到的帧中存在 Dependency Descriptor Header Extension 时才存在。
-
dependencies, 类型为sequence<unsigned long long>sequence<unsigned long long> -
此帧所引用的帧的 frameId 列表。 仅当收到的帧中存在 [AV1-RTP-SPEC] 附录 A 中定义的 AV1 Dependency Descriptor Header Extension 时才存在。
-
timestamp, 类型为 long longlong long -
原始帧的媒体呈现时间戳(PTS),单位为微秒,匹配 与此帧对应的原始帧的
timestamp。
4.3.
RTCEncodedVideoFrame 接口
dictionary {RTCEncodedVideoFrameOptions RTCEncodedVideoFrameMetadata ; }; // 用于定义 RTCRtpScriptTransform 所使用的 RTC 专用编码视频帧和音频帧的新接口。 [metadata Exposed =(Window ,DedicatedWorker ),Serializable ]interface RTCEncodedVideoFrame {(constructor RTCEncodedVideoFrame ,originalFrame optional RTCEncodedVideoFrameOptions = {});options readonly attribute EncodedVideoChunkType type ;attribute ArrayBuffer data ;RTCEncodedVideoFrameMetadata getMetadata (); };
4.3.1. 构造器
-
constructor() -
从给定的 originalFrame 和 options.
[metadata]创建一个新的RTCEncodedVideoFrame。 新创建的帧完全独立于 originalFrame,其[[data]]是 originalFrame.[[data]]的深拷贝。 新帧的[[metadata]]是 originalFrame.[[metadata]]的深拷贝,其中 字段会被 options.[metadata]中存在字段的深拷贝替换。调用时,运行以下步骤:
-
将 this.
[[type]]设置为 originalFrame.[[type]]。 -
令 this.
[[data]]为 [CloneArrayBuffer](originalFrame.[[data]], 0, originalFrame.[[data]].[[ArrayBufferByteLength]]) 的结果。 -
令
[[metadata]]表示与此新构造帧关联的元数据。-
对于 originalFrame.
[[getMetadata()]]的每个 {[[key]],[[value]]} 对, 将[[metadata]].[[key]]设置为[[value]]的深拷贝。 -
对于 options.
[metadata]的每个 {[[key]],[[value]]} 对, 将[[metadata]].[[key]]设置为[[value]]的深拷贝。
-
-
4.3.2. 成员
-
type, 类型为 EncodedVideoChunkType,readonlyEncodedVideoChunkType -
type 属性允许应用判断帧是关键帧还是增量帧。 获取时,必须返回 this.
[[type]]。 -
data, 类型为 ArrayBufferArrayBuffer -
编码帧数据。数据格式取决于用于编码/解码该帧的视频编解码器, 可通过查看
mimeType来确定。 对于 SVC,每个空间层 会被分别变换。 获取时,必须返回 this.[[data]]。设置时, 必须将 this.[[data]]设置为新值。由于打包器可能会丢弃某些元素,例如 AV1 temporal delimiter OBU, 接收端变换的输入可能不同于 发送端变换的输出。
下表给出了一些示例:
mimeType 数据格式 video/VP8 数据以 第 9.1 节中定义的“uncompressed data chunk”开始, 该节属于 [RFC6386],随后是帧数据的其余 部分。 VP8 payload descriptor 不可访问。 video/VP9 数据是 [VP9] 第 6 节中描述的帧。 VP9 payload descriptor 不可访问。 video/H264 数据是一系列 Annex B 格式的 NAL 单元, 如 [ITU-T-REC-H.264] Annex B 中定义。 video/AV1 数据是一系列符合 low-overhead bitstream format 的 OBU,如 [AV1] 第 5 节所述。 AV1 aggregation header 不可访问。
4.3.3. 方法
-
getMetadata() -
返回与该帧关联的元数据。
4.3.4. 序列化
RTCEncodedVideoFrame
对象是可序列化对象。
给定 value、serialized
和 forStorage,它们的序列化步骤为:
-
如果 forStorage 为 true,则抛出
DataCloneError。 -
将 serialized.
[[type]]设置为 value.[[type]]的值。 -
将 serialized.
[[metadata]]设置为 value 的元数据的内部表示。 -
将 serialized.
[[data]]设置为 value.[[data]]的子序列化。
给定 serialized、 value 和 realm,它们的反序列化步骤为:
-
将 value.
[[type]]设置为 serialized.[[type]]。 -
将 value 的元数据设置为 serialized.
[[metadata]]的平台对象表示。 -
将 value.
[[data]]设置为 serialized.[[data]]的子反序列化。
序列化的 RTCEncodedVideoFrame 的内部形式是不可观察的;
它的定义主要是为了能在
writeEncodedData 算法以及 structuredClone()
操作中用于帧克隆。
因此,实现可以自由选择最适合的方法。
4.4.
RTCEncodedAudioFrameMetadata
字典
dictionary RTCEncodedAudioFrameMetadata :RTCEncodedFrameMetadata {short sequenceNumber ;double audioLevel ; };
4.4.1. 成员
-
sequenceNumber, 类型为 shortshort -
RTP sequence number,如 [RFC3550] 中定义。 仅存在于传入音频帧。
比较两个序列号需要使用 [RFC1982] 中描述的 serial number arithmetic。
-
audioLevel, 类型为 doubledouble -
此帧的音频电平。该值介于 0..1(线性)之间, 其中 1.0 表示 0 dBov,0 表示静音,0.5 表示 相对于 0 dBov 的声压级约 6 dBSPL 变化。
如果帧来自远程来源的 track,则必须从 [RFC6464] 中定义的 level 值转换而来。如果收到的该帧包中不存在 [RFC6464] header extension, 则必须不存在此值。 该 RFC 将音频电平定义为从 0 到 127 的整数值, 表示相对于系统可能编码的 最响信号的负分贝音频电平。因此,0 表示系统可能编码的最响信号, 127 表示静音。要将这些值转换为线性 0..1 范围,值 127 会转换为 0,所有其他值 使用以下公式转换:
10^(-rfc_level/20)。如果帧来自本地来源的 track,则 level 必须 直接从源取得,并在已协商时用作生成 [RFC6464] header extension 值的输入。
4.5.
RTCEncodedAudioFrame 接口
dictionary {RTCEncodedAudioFrameOptions RTCEncodedAudioFrameMetadata ; }; [metadata Exposed =(Window ,DedicatedWorker ),Serializable ]interface RTCEncodedAudioFrame {(constructor RTCEncodedAudioFrame ,originalFrame optional RTCEncodedAudioFrameOptions = {});options attribute ArrayBuffer data ;RTCEncodedAudioFrameMetadata getMetadata (); };
4.5.1. 构造器
-
constructor() -
从给定的 originalFrame 和 options.
[metadata]创建一个新的RTCEncodedAudioFrame。 新创建的帧完全独立于 originalFrame,其[[data]]是 originalFrame.[[data]]的深拷贝。 新帧的[[metadata]]是 originalFrame.[[metadata]]的深拷贝,其中 字段会被 options.[metadata]中存在字段的深拷贝替换。调用时,运行以下步骤:
-
令 this.
[[data]]为 [CloneArrayBuffer](originalFrame.[[data]], 0, originalFrame.[[data]].[[ArrayBufferByteLength]]) 的结果。 -
令
[[metadata]]表示与此新构造帧关联的元数据。-
对于 originalFrame.
[[getMetadata()]]的每个 {[[key]],[[value]]} 对, 将[[metadata]].[[key]]设置为[[value]]的深拷贝。 -
对于 options.
[metadata]的每个 {[[key]],[[value]]} 对, 将[[metadata]].[[key]]设置为[[value]]的深拷贝。
-
-
4.5.2. 成员
-
data, 类型为 ArrayBufferArrayBuffer -
编码帧数据。数据格式取决于用于编码/解码该帧的音频编解码器, 可通过查看
mimeType来确定。 获取时,必须返回 this.[[data]]。设置时, 必须将 this.[[data]]设置为新值。 下表给出了一些示例:mimeType 数据格式 audio/opus 数据是 Opus 包,如 第 3 节所述,该节属于 [RFC6716]。 audio/PCMU 数据是任意长度的字节序列,其中每个字节都是 u-law 编码的 PCM 采样,如 [ITU-G.711] 中表 2a 和 2b 所定义。 audio/PCMA 数据是任意长度的字节序列,其中每个字节都是 A-law 编码的 PCM 采样,如 [ITU-G.711] 中表 1a 和 1b 所定义。 audio/G722 数据是 G.722 音频,如 [ITU-G.722] 所述。 audio/RED 数据是 Redundant Audio Data,如 第 3 节所述,该节属于 [RFC2198]。 audio/CN 数据是 Comfort Noise,如 第 3 节所述,该节属于 [RFC3389]。
4.5.3. 方法
-
getMetadata() -
返回与该帧关联的元数据。
4.5.4. 序列化
RTCEncodedAudioFrame
对象是可序列化对象。
给定 value、
serialized 和 forStorage,它们的序列化步骤为:
-
如果 forStorage 为 true,则抛出
DataCloneError。 -
将 serialized.
[[metadata]]设置为 value 的元数据的内部表示。 -
将 serialized.
[[data]]设置为 value.[[data]]的子序列化。
给定 serialized、 value 和 realm,它们的反序列化步骤为:
-
将 value 的元数据设置为 serialized.
[[metadata]]的平台对象表示 -
将 value.
[[data]]设置为 serialized.[[data]]的子反序列化。
5.
RTCRtpScriptTransform 接口
enum {RTCRtpScriptTransformType };"sframe" dictionary {WorkerAndParameters required Worker ;worker RTCRtpScriptTransformType ; };type typedef (Worker or WorkerAndParameters ); [WorkerOrWorkerAndParameters Exposed =Window ]interface RTCRtpScriptTransform {constructor (WorkerOrWorkerAndParameters ,workerOrWorkerAndParameters optional any ,options optional sequence <object >); };transfer
5.1. 内部槽
RTCRtpScriptTransform
对象具有以下内部槽:
| 内部槽 | 描述(非规范性) |
|---|---|
[[worker]]
| 构造器中提供的 Worker。
|
5.2. 构造器
new RTCRtpScriptTransform(workerOrWorkerAndParameters, options, transfer)
构造器步骤为:
-
令 worker 为 undefined。
-
令 useSFrame 为 undefined。
-
如果 workerOrWorkerAndParameters 是一个
Worker对象,则将 worker 设置为 workerOrWorkerAndParameters,并将 useSFrame 设置为 false。 -
否则,运行以下子步骤:
-
将 worker 设置为 workerOrWorkerAndParameters["worker"]。
-
如果 workerOrWorkerAndParameters["type"] 为 "sframe",则将 useSFrame 设置为 true,否则设置为 false。
-
-
按如下方式初始化 this 的内部槽:
[[worker]]-
worker
-
将 this.
[[useSFrame]]初始化为 useSFrame。 -
令 serializedOptions 为 StructuredSerializeWithTransfer(options, transfer) 的结果。
-
在 DOM 操作排队一个全局任务到 任务源,并使用 worker 的
WorkerGlobalScope来运行以下步骤:-
令 transformerOptions 为 StructuredDeserializeWithTransfer(serializedOptions, 当前 Realm) 的结果。
-
令 transformer 为用 transformerOptions 创建一个
RTCRtpScriptTransformer的结果。 -
在 transformer 的相关全局对象上,使用
RTCTransformEvent触发一个名为rtctransform的事件,并将transformer设置为 transformer。
-
// FIXME:描述错误处理(在创建 RTCRtpScriptTransform 时 worker closing flag 为 true,以及 worker 在 transform 正在处理数据时被终止)。
5.3. 算法
每个 RTCRtpScriptTransform
都有以下关联算法,给定
rtcObject:
-
令 transform 为拥有该关联算法的
RTCRtpScriptTransform对象。 -
令 frameSource 为 rtcObject 的
[[frameSource]]。 -
令 workerGlobalScope 为 transform.
[[worker]]的WorkerGlobalScope。 -
在 DOM 操作排队一个全局任务到 任务源,并使用 workerGlobalScope 来运行以下步骤:
-
令 transformer 为与 transform 关联的
RTCRtpScriptTransformer对象。 -
将 transformer.
[[frameSource]]设置为 frameSource。
-
-
并行地,将 frameSource.[[transformFrameAlgorithm]] 设置为以下步骤, 给定一个编码 帧 frame 作为输入:
-
在 DOM 操作排队一个全局任务到 任务源,并使用 workerGlobalScope 来运行以下步骤:
-
令 transformer 为与 transform 关联的
RTCRtpScriptTransformer对象。 -
如果 frame 是视频 帧,则令 jsFrame 为从 frame 创建的一个新的
RTCEncodedVideoFrame; 否则为从 frame 创建的一个新的RTCEncodedAudioFrame。 -
用 transformer 和 jsFrame 调用 readEncodedData。
-
-
等待 frameSource.[[processedFramesQueue]] 变为非空。
-
返回从 frameSource.[[processedFramesQueue]] 中出队的结果。
-
每个 RTCRtpScriptTransform
都有以下解除关联算法:
-
令 transform 为拥有该解除关联 算法的
RTCRtpScriptTransform对象。 -
在 DOM 操作排队一个全局任务到 任务源,并使用 transform.
[[worker]]的WorkerGlobalScope来运行以下步骤:-
令 transformer 为与 transform 关联的
RTCRtpScriptTransformer对象。 -
取消 transformer.
[[readable]]。 -
中止 transformer.
[[writable]]。
-
6.
RTCRtpScriptTransformer 接口
[Exposed =DedicatedWorker ]interface RTCRtpScriptTransformer :EventTarget { // 与 transformer 源相关的属性和方法readonly attribute ReadableStream readable ;Promise <undefined >generateKeyFrame (optional DOMString );rid Promise <undefined >sendKeyFrameRequest (); // 与 transformer 接收端相关的属性和方法readonly attribute WritableStream writable ;attribute EventHandler onkeyframerequest ; // 用于配置 JavaScript 代码的属性readonly attribute any options ; };
6.1. 内部槽
RTCRtpScriptTransformer
对象具有以下内部槽:
| 内部槽 | 描述(非规范性) |
|---|---|
[[frameSource]]
| 一个编码器、一个解包器,或 undefined。 |
[[options]]
| 一个可选的 Object,
或 null。
|
[[readable]]
| 一个 ReadableStream。
|
[[writable]]
| 一个 WritableStream。
|
[[lastReceivedFrameCounter]]
| 已接收帧的计数。 |
[[lastEnqueuedFrameCounter]]
| 已入队帧的计数。 |
RTCRtpScriptTransformer,
给定一个 options 对象,执行以下步骤:
-
令 transformer 为一个新的
RTCRtpScriptTransformer, 具有:[[frameSource]]-
undefined
[[options]]-
options
[[readable]]-
一个新的
ReadableStream [[writable]]-
一个新的
WritableStream [[lastReceivedFrameCounter]]-
0
[[lastEnqueuedFrameCounter]]-
0
-
设置 transformer.
[[readable]]。readEncodedData 算法给定 this 作为参数,会向其提供编码帧。
-
令 writeAlgorithm 为一个动作,给定 frame, 该动作以 this 为参数并以 frame 为输入运行 writeEncodedData。
-
设置 transformer.
[[writable]], 并将其 writeAlgorithm 设置为 writeAlgorithm,将其 highWaterMark 设置为Infinity。highWaterMark 被设置为 Infinity,以显式禁用背压。
-
返回 transformer。
6.2. 方法
generateKeyFrame(rid)
方法步骤为:
-
令 promise 为一个新的 promise。
-
用 promise、this.
[[frameSource]]和 rid 运行生成关键帧算法。 -
返回 promise。
sendKeyFrameRequest() 方法
步骤为:
-
令 promise 为一个新的 promise。
-
用 promise 和 this.
[[frameSource]]运行发送请求关键帧 算法。 -
返回 promise。
6.3. 属性
options getter 步骤为:
-
返回 this.
[[options]]。
readable getter 步骤为:
-
返回 this.
[[readable]]。
writable getter 步骤为:
-
返回 this.
[[writable]]。
onbandwidthestimate
EventHandler 的类型为 bandwidthestimate。
onkeyframerequest
EventHandler 的类型为 keyframerequest。
6.4. 事件
[Exposed =DedicatedWorker ]interface :RTCTransformEvent Event {readonly attribute RTCRtpScriptTransformer ; };transformer partial interface DedicatedWorkerGlobalScope {attribute EventHandler ; }; [onrtctransform Exposed =DedicatedWorker ]interface :KeyFrameRequestEvent Event {(constructor DOMString ,type optional DOMString );rid readonly attribute DOMString ?; };rid
以下事件会在 RTCRtpScriptTransformer
上触发:
-
类型为
KeyFrameRequestEvent的 keyframerequest —— 在接收端确定已请求关键帧时触发。
生成类型为 KeyFrameRequestEvent
的事件的步骤如下:
当关联的 RTCRtpScriptTransformer
transformer 的编码器
收到关键帧请求时,例如来自传入的 RTCP Picture Loss
Indication(PLI)
或 Full Intra Refresh(FIR),排队一个任务来执行以下步骤:
-
将 rid 设置为相应层的 RID;如果该请求不是针对 特定层,则设置为 undefined。
-
在 transformer 上触发一个名为
keyframerequest的事件, 使用KeyFrameRequestEvent, 将其cancelable属性初始化为 "true",并将rid设置为 rid。 -
如果事件的已取消标志为 true,则中止这些步骤。
-
用一个新的 promise、transformer.
[[frameSource]]和 rid 运行生成关键帧算法。
6.5. 关键帧算法
生成关键帧算法,给定 promise、frameSource 和 rid,定义为运行以下步骤:
-
如果 frameSource 不是编码器,则用
InvalidStateError拒绝 promise, 并中止这些步骤。 -
令 encoder 为 frameSource。
-
如果 encoder 不属于视频
RTCRtpSender, 则用InvalidStateError拒绝 promise, 并中止这些步骤。 -
如果 rid 已定义,但不符合 [RFC8851] 第 10 节中指定的语法要求, 则用
TypeError拒绝 promise 并中止 这些步骤。 -
并行地运行以下步骤:
-
令 layers 为此 encoder 的层组成的一个新的 列表, 按协商的 encoding index 排序。
-
从 layers 中移除所有不为
active的层,或其 对应RTCRtpSendertrack 已结束的层。 -
如果 rid 不是 undefined,则从 layers 中移除 所有 RID 不是 rid 的层。
注:如果未传入 rid, 则为所有 active 层生成关键帧。
-
如果 layers 现在为空,则排队一个任务,用
NotFoundError拒绝 promise 并中止这些步骤。 -
从 layers 中移除已经在 encoder.
[[pendingKeyFrameTasks]]中任何任务的任何[[layers]]内找到的所有层。 -
创建一个名为 task 的待处理关键帧任务,其中 task.
[[layers]]设置为 layers, task.[[promise]]设置为 promise。 -
如果 encoder.
[[pendingKeyFrameTasks]]为 undefined,则将 encoder.[[pendingKeyFrameTasks]]初始化为空集合。 -
将 task 追加到 encoder.
[[pendingKeyFrameTasks]]。 -
对于 layers 中的每个 layer(如果有),指示 encoder 为其下一个提供给该 layer 的视频帧生成关键帧。
-
对于与 RTCRtpScriptTransformer
transformer 关联的任何编码器,
用户代理必须在任何 frame 被入队到 transformer.[[readable]]
之前运行以下步骤:
-
令 encoder 为 transformer.
[[frameSource]]。 -
如果 encoder.
[[pendingKeyFrameTasks]]为 undefined,则中止这些步骤。 -
如果 frame 不是视频
"key"帧,则中止这些步骤。 -
对于 encoder.
[[pendingKeyFrameTasks]]中的每个 task, 运行以下步骤:
通过在相应关键帧入队到 RTCRtpScriptTransformer
的
readable 之前兑现 promise,
promise 的兑现回调总是会在相应关键帧被暴露之前执行。
如果 promise 与多个层相关联,则会在所有这些层的关键帧都已入队后
兑现。
发送请求关键帧算法,给定 promise 和 frameSource,定义为运行以下步骤:
-
如果 frameSource 不是解包器,则用
InvalidStateError拒绝 promise, 并中止这些步骤。 -
令 depacketizer 为 frameSource。
-
如果 depacketizer 不属于视频
RTCRtpReceiver, 则用InvalidStateError拒绝 promise, 并中止这些步骤。 -
并行地运行以下步骤:
7. 隐私和安全考量
此 API 使 JavaScript 能够访问媒体流的内容。这 也可通过其他来源获得,例如 Canvas 和 WebAudio。
但是,隔离的流(如 [WEBRTC-IDENTITY] 中所规定)或被其他源污染的流,不能 使用此 API 访问,因为那会破坏隔离规则。
该 API 将允许访问一些原本不可用的时序信息方面, 这会带来一些指纹识别面。
该 API 将提供对编码媒体的访问,这意味着 JS 应用 将完全控制交付给内部组件(如 打包器或解码器)的内容。这可能需要额外注意 审计这些组件内部如何处理数据。
例如,打包器可能预期只看到来自受信任编码器的数据, 并且可能没有针对接收来自不受信任来源的数据进行审计。
8. 示例
参见解释 文档。