🐯

RTMPのWindow Controlの深掘り

2023/08/31に公開

RTMP Window Controlの概要

Window ControlはRTMPでのデータ送信を調整する機能です。通信の速さや遅延に対応するためにあります。TCPと似た制御方式です。

この制御には、Window Acknowledgement Size、Acknowledgement、そしてSet Peer Bandwidthの3つのメッセージが使われます。

Window Control用のRTMP Message

Window Acknowledgement Size (5)

このメッセージは、クライアントとサーバーの間でどれだけのバイト数を確認するかを指定します。通常、この値は大きな数(例えば2,500,000バイト)に設定されます。
サーバとクライアント側問わず受け取ったデータが値を超えたらAcknowledgement Messageを送信します。

Acknowledgement (3)

Acknowledgementメッセージは、特定の量のデータが受信された後に送られ、そのデータが確認されたことを示します。数はWindow Acknowledgement Sizeで設定されます。
また、相手側がWindow Acknowledgement Sizeを超えるデータを送信した場合、このAcknowledgementメッセージを受け取るまでデータの送信を一時停止する必要があります。これは、送信速度が速すぎる可能性があるという指標にもなります。

Set Peer Bandwidth(6)

このメッセージは、通常Set Peer Bandwidthとして知られ、相手(サーバーまたはクライアント)に自分の最大アウトバウンド(送信可能な)帯域幅を通知します。例として、もしサーバーからこのメッセージを受け取り、その値が2500000であれば、クライアントはその後のデータ送信を秒間2500000バイト以内に制限する必要があります。

これにより、送受信の帯域幅を管理して、通信の安定を維持することができます。

type説明

  • limit type = 0(ハードリミット): 出口帯域幅のサイズを即座に更新します。このタイプは、帯域幅に厳格な制限を設ける場合に使用されます。

  • limit type = 1(ソフトリミット): 出口帯域幅のサイズを更新することも、元の値を保持することもできます。ただし、元の値は新しく設定される帯域幅よりも小さくなければなりません。

  • limit type = 2(ダイナミックリミット): 以前がハードリミットであった場合、このメッセージもハードリミットとして扱われます。そうでない場合、このメッセージは無視されます。

flow図

Swiftによるサンプルコード

WindowControl

// WindowControl actor manages the flow control in RTMP communication
actor WindowControl {
    
  // The window size for flow control, usually set by a peer. default: 2.4mb
  private(set) var windowSize: UInt32 = 2500000
  
  // Total number of incoming bytes, updated as data is received.
  private(set) var totalInBytesCount: UInt32 = 0
  // Sequence number for tracking incoming bytes, incremented after each window.
  private(set) var totalInBytesSeq: UInt32 = 1

  // Total number of outgoing bytes, updated as data is sent.
  private(set) var totalOutBytesCount: UInt32 = 0
  // Sequence number for tracking outgoing bytes, incremented after each window.
  private(set) var totalOutBytesSeq: UInt32 = 1

  // Callback function triggered when incoming bytes reach the window limit.
  private(set) var inBytesWindowEvent: ((UInt32) async -> Void)? = nil
  
  // The last byte count acknowledged by a peer.
  private(set) var receivedAcknowledgement: UInt32 = 0
  
  // Sets the callback function for incoming byte window events.
  func setInBytesWindowEvent(_ inBytesWindowEvent:((UInt32) async -> Void)?) {
    self.inBytesWindowEvent = inBytesWindowEvent
  }
  
  // Sets the window size for flow control, usually updated from a peer.
  func setWindowSize(_ size: UInt32) {
    self.windowSize = size
  }
  
  // Updates the last acknowledged byte count, usually set by a peer.
  func updateReceivedAcknowledgement(_ size: UInt32) {
    receivedAcknowledgement = size
  }
  
  // Adds to the total count of incoming bytes and triggers the window event if necessary.
  func addInBytesCount(_ count: UInt32) async {
    totalInBytesCount += count
    if totalInBytesCount >= windowSize * totalInBytesSeq {
      // need send Acknowledgement message
      await inBytesWindowEvent?(totalInBytesCount)
      totalInBytesSeq += 1
    }
  }
  
  // Adds to the total count of outgoing bytes and updates the sequence number if necessary.
  func addOutBytesCount(_ count: UInt32) {
    totalOutBytesCount += count
    if totalOutBytesCount >= windowSize * totalOutBytesSeq {
      totalOutBytesSeq += 1
    }
  }
  
  // Determines whether the actor should wait for an acknowledgement from a peer.
  var shouldWaitAcknowledgement: Bool {
    Int64(totalOutBytesCount) - Int64(receivedAcknowledgement) >= windowSize
  }
}

参考文献

rtmp_specification_1.0.pdf
https://www.infraexpert.com/study/tcpip11.html

Discussion