gstreamer の調査

調べたかったのはgstreamerにおける再送処理 RR(receiver report)をもとに再送しているのでは?と思いソースを読んだが、以下のドキュメントを読むと良い。 gitlab.freedesktop.org

GStreamer の主要な RTP コンポーネントのほとんどは gst-plugins-good に収められています。

  • rtpmanager プラグイン にはrtpbin とrtpjitterbuffer

  • rtpプラグイン には、多くの異なるコーデックとコンテナフォーマットに対する RTP ペイロードとディペイロードの要素が含まれています。 コーデックやコンテナ形式と、gst-plugins-base にあるいくつかの低レベルのライブラリが含まれています。

  • GStreamer RTP ライブラリ RTP ペイローダ/デペイローダのベースクラス、RTP および RTCP バッファを処理する関数などが含まれます。

  • GStreamer MIKEY ライブラリ セキュアな RTP のための MIKEY メッセージを扱うヘルパー関数が含まれています。
  • GStreamer RTSP ライブラリ には、gst-rtsp-server や rtspsrc などの高レベルオブジェクトで使用される低レベル RTSP 機能が含まれています。
  • GStreamer SDP ライブラリ は、SDP メッセージのパースと生成のためのユーティリティ関数を含んでいます。

主なコンポーネントをいくつか紹介します。

  • rtpbin は高レベルのRTPコンポーネントであり、送信と受信をサポートする。 と受信、データの送信のみ、受信のみ、RTCPサポートあり/なしをサポートします。 サポートします。これはすべてを行うbinで、要求されたパッドに基づいてニーズに動的に適応します。 また、rtpjitterbufferも含まれています。
  • rtpjitterbuffer は、ネットワークジッタを制御し、パケットを並べ替える RTP バッファです。またパケットの再送信やパケット紛失の通知も行います。パケット通知、送信者-受信者間のクロックドリフトを調整します。
  • rtpptdemux は、通常、rtpbin srcパッドに配置され、RTPストリームに到着した新しいペイロードタイ プを検出する。そして、その新しいペイロードのためのパッドを作成し、そのパッドにデペイローダ/デコーダのパイプラインをそのパッドに接続することができます。
  • rtpssrcdemux は、通常、rtpbin srcパッドに配置され、RTPストリームに到着した新しいSSRCを検出する。 そして、その新しいSSRCのためのパッドを作成し、そのパッドにデペイロダー/デコーダーパイプラインをそのパッドに接続することができます。
  • GstRTPBaseDepayload は、RTPディペイローダのベースクラスである
  • GstRTPBasePayload は、RTP ペイローダの基底クラスです。
  • GstRTPBaseAudioPayload (オーディオペイロード)は、オーディオRTPペイローダのベースクラスである。

Note: 多くのRTPエレメントは、RTPバッファの受信にGstNetAddressMetaメタデータが設定されたRTPバッファを受信すると仮定している(udpsrcが生成するように)。

メモ

RTSP,RTCP,RTPの関係が怪しい場合、いかのプロトコルスタックの図がわかりやすいため確認しておくと良い https://www.ieice-hbkb.org/files/03/03gun_04hen_05.pdf

  • RTSPにて交換されたSDPに基づいた映像・音声設定
  • RTSPにて開始・停止制御
  • サーバーからのデータ送信はRTP
  • RR,SRはRTCP

sourceの場所

gitlab

gstreamer,gst-plugins-good などのリポジトリがあるが、gstreamer配下にsubprojectsがあり、そこにgst-plugins-goodなどが位置するため、リポジトリを個別にcloneする必要はない

gst-plugins-good/gst 下に以下のものが位置する - rtp - rtsp - rtpmanager - udp

gst-plugins-good/rtpmanager/rtpsession.c にRRなどがありそう

rtp_source_get_last_rb: rtp_source_process_rb() で設定された最後のRBレポートの値を取得します。

rb: receiverと思われる

// A receiver report structure.
typedef struct {
  gboolean is_valid;
  guint32 ssrc; /* which source is the report about */
  guint8  fractionlost;
  guint32 packetslost;
  guint32 exthighestseq;
  guint32 jitter;
  guint32 lsr;
  guint32 dlsr;
  guint32 round_trip;
} RTPReceiverReport;

統計情報を持っていて、そこにRR情報が含まれる.

// Stats about a source.
// ここでいうsourceはsincに対するsourceか?
typedef struct {
  guint64      packets_received;
  guint64      octets_received;
  guint64      bytes_received;

  guint32      prev_expected;
  guint32      prev_received;

  guint16      max_seq;
  guint64      cycles;
  guint32      base_seq;
  guint32      bad_seq;
  guint32      transit;
  guint32      jitter;

  guint64      packets_sent;
  guint64      octets_sent;

  guint        sent_pli_count;
  guint        recv_pli_count;
  guint        sent_fir_count;
  guint        recv_fir_count;
  guint        sent_nack_count;
  guint        recv_nack_count;

  /* when we received stuff */
  GstClockTime prev_rtptime;
  GstClockTime prev_rtcptime;
  GstClockTime last_rtptime;
  GstClockTime last_rtcptime;

  /* sender and receiver reports */
  gint              curr_rr;
  RTPReceiverReport rr[2];
  gint              curr_sr;
  RTPSenderReport   sr[2];
} RTPSourceStats;

rtp_session_process_rb: 受信したreceiver reportをRTPSourceStatsに保存する RTPSourceStats をもとに再送する場所は別らしい。

rtp_source_process_rtp: src に @pinfo に記述された着信 RTP パケットを処理させる。 多分RTP送信をする前にrtpmanagerにおいて統計情報を更新する 統計情報とは、jitter, bitrateなど あくまで統計情報の更新しか行なっておらず、再送処理などは行なっていない。

似たようなrtp処理が複数ある process_rtp push_rtp send_rtp

source_push_rtpでは source->internalの場合にsend_rtp, そうでない場合process_rtpを行う internalって何のinternal? obtain_internal_source でsource->internal = TRUE;を設定している gstreamerから送信するものと、gstreamerが受信するものがある。送信はgstremer内部から出ていくからそれを内部と言っている?

  • gstrtpbin RTP binは、#GstRtpSession、#GstRtpSsrcDemux、#GstRtpJitterBuffer、#GstRtpPtDemuxの機能を1つのエレメントにまとめたものである。これにより、RTCP SRパケットを使用して同期される複数のRTPセッションが可能になります。

  • gstrtpfunnel RTPファンネルは基本的に通常のファンネルと同様であるが、バンドルに対応するため、 機能がいくつか追加されている。

funnel:漏斗、つまり2つのsincを1つのsrcにする。multiplexerと言ってもよい。

  • rtphdrextmid RTP Bundle Media Identification (MID) RTP Header Extension (RFC8843)

  • GstRtphdrext-TWCC Helper methods for dealing with RTP header extensions in the Audio/Video RTP Profile for transport-wide-cc

  • retransmission(rtx) rtxってre-transmissionか。

rtprtxsend

例については#GstRtpRtxReceiveを参照のこと。 送信側RTXオブジェクトの目的は、設定可能な制限値(max-size-timeまたは max-size-packets)までのRTPパケットの履歴を保持することである。 これは、ダウンストリーム(#GstRtpSession)から来るアップストリームカスタム再送信 イベント(GstRTPRetransmissionRequest)をリッスンしている。 要求を受け取ると、要求されたseqnumを保存されたパケットの一覧から探します。 そのパケットが利用可能であれば、RFC4588に従ってRTXパケットを作成し、これを補助ストリームとして送信します。RTXはSSRC多重化されています。

rtprtxreceive

rtprtxreceiveは下流のrtpjitterbufferからの再送信イベントをリッスンし、ストリームのSSRC(ssrc1)と要求されたシーケンス番号を記憶しています。記憶しているものと同じシーケンス番号で、異なるSSRCを持つパケットを受信すると、新しいSSRC(ssrc2)をssrc1の再送ストリームとして識別する。 この時点から、rtpjitterbufferが元のストリームを再構築できるように、ssrc2ストリームのすべてのパケットでssrc2をssrc1に置き換え、それらを再送信であるとフラグを立てます。

このアルゴリズムは、RFC 4588で規定されているように実装されている。

このエレメントは、送信側でrtprtxsendと共に使用されることを意図している。 GstRtpRtxSend を参照してください。

以下に、rtprtxreceive と rtprtxsend が他の rtp エレメントにどのように適合し、内部でどのように動作するかを示す例をいくつか示します。 しかし、通常はこのようなパイプラインの使用は避け、代わりに #GstRtpBin::request-aux-sender と #GstRtpBin::request-aux-receiver のシグナルを持つ rtpbin を使用する必要があります。詳細は #GstRtpBin を参照してください。

pipelineのサンプル

Send audio stream through port 5000 (5001 and 5002 are just the rtcp link with the receiver

 gst-launch-1.0 rtpsession name=rtpsession rtp-profile=avpf \
     audiotestsrc is-live=true ! opusenc ! rtpopuspay pt=96 ! \
         rtprtxsend payload-type-map="application/x-rtp-pt-map,96=(uint)97" ! \
         rtpsession.send_rtp_sink \
     rtpsession.send_rtp_src ! identity drop-probability=0.01 ! \
         udpsink host="127.0.0.1" port=5000 \
     udpsrc port=5001 ! rtpsession.recv_rtcp_sink \
     rtpsession.send_rtcp_src ! udpsink host="127.0.0.1" port=5002 \
         sync=false async=false

Receive audio stream from port 5000 (5001 and 5002 are just the rtcp link with the sender)

gst-launch-1.0 rtpsession name=rtpsession rtp-profile=avpf \
     udpsrc port=5000 caps="application/x-rtp,media=(string)audio,clock-rate=(int)48000,encoding-name=(string)OPUS,payload=(int)96" ! \
         rtpsession.recv_rtp_sink \
     rtpsession.recv_rtp_src ! \
         rtprtxreceive payload-type-map="application/x-rtp-pt-map,96=(uint)97" ! \
         rtpssrcdemux ! rtpjitterbuffer do-retransmission=true ! \
         rtpopusdepay ! opusdec ! audioconvert ! audioresample ! autoaudiosink \
     rtpsession.send_rtcp_src ! \
         udpsink host="127.0.0.1" port=5001 sync=false async=false \
     udpsrc port=5002 ! rtpsession.recv_rtcp_sink

この例では、OPUSストリームの単純なストリーミングで、パケットの一部がIDエレメントによって人為的にドロップされる様子を見ることができます。 再送信のおかげで、drop-probabilityを0より大きいものに設定しても、クリアなサウンドを聞くことができるはずです。

内部的には、rtpjitterbufferは、1つのパケットが欠落していることを検出すると、カスタムアップストリームイベントであるGstRTPRetransmissionRequestを生成します。 次に、このリクエストは、rtpsessionによってrtcpリンクのFB NACKに変換される。 最後に、送信側のrtpsessionは、rtprtxsendが処理するGstRTPRetransmissionRequestに再変換する。rtprtxsendはその後、新しいsrrcと異なるペイロードタイプ (ここでは97)で、元のシーケンス番号と同じで、見つからないパケットを再送信する。受信側では、rtprtxreceiveがこの新しいストリームを元のストリームと関連付け、再送パケットを元のssrcとペイロードタイプでrtpjitterbufferに転送します。

Send two audio streams to port 5000.

 gst-launch-1.0 rtpsession name=rtpsession rtp-profile=avpf \
     audiotestsrc is-live=true ! opusenc ! rtpopuspay pt=97 seqnum-offset=1 ! \
         rtprtxsend payload-type-map="application/x-rtp-pt-map,97=(uint)99" ! \
         funnel name=f ! rtpsession.send_rtp_sink \
     audiotestsrc freq=660.0 is-live=true ! opusenc ! \
         rtpopuspay pt=97 seqnum-offset=100 ! \
         rtprtxsend payload-type-map="application/x-rtp-pt-map,97=(uint)99" ! \
         f. \
     rtpsession.send_rtp_src ! identity drop-probability=0.01 ! \
         udpsink host="127.0.0.1" port=5000 \
     udpsrc port=5001 ! rtpsession.recv_rtcp_sink \
     rtpsession.send_rtcp_src ! udpsink host="127.0.0.1" port=5002 \
         sync=false async=false

Receive two audio streams from port 5000.

gst-launch-1.0 rtpsession name=rtpsession rtp-profile=avpf \
     udpsrc port=5000 caps="application/x-rtp,media=(string)audio,clock-rate=(int)48000,encoding-name=(string)OPUS,payload=(int)97" ! \
         rtpsession.recv_rtp_sink \
     rtpsession.recv_rtp_src ! \
         rtprtxreceive payload-type-map="application/x-rtp-pt-map,97=(uint)99" ! \
         rtpssrcdemux name=demux \
     demux. ! queue ! rtpjitterbuffer do-retransmission=true ! rtpopusdepay ! \
         opusdec ! audioconvert ! autoaudiosink \
     demux. ! queue ! rtpjitterbuffer do-retransmission=true ! rtpopusdepay ! \
         opusdec ! audioconvert ! autoaudiosink \
     udpsrc port=5002 ! rtpsession.recv_rtcp_sink \
     rtpsession.send_rtcp_src ! udpsink host="127.0.0.1" port=5001 \
         sync=false async=false

ポート5000から2つのオーディオストリームを受信します。

この例では、同じタイプの2つのストリームを同じポートでストリーミングしています。ただし、これらは異なるSSRCを使用しているため(ssrcは各ペイローダ(この例ではrtpopuspay)でランダムに生成)、受信側のrtpssrcdemuxによって識別および多重化解除が可能になっています。これは、SSRC多重化の例です。

これは、rtprtxreceiveが再送ストリームを識別するために最初に使用する唯一の識別手段であるため、異なる開始シーケンス番号(seqnum-offset)を使用することがここで重要である。 RFC4588によると、2つの異なるストリームに属するが同じシーケンス番号を持つパケットに対して2つの再送要求があるのはエラーである。 デフォルトのseqnum-offset値(-1、つまりランダム)でも問題ないが、ここでは説明のためにオーバーライドしていることに注意。

rtpjitter

RTP_JITTER_BUFFER_MODE_NONE: スキュー補正を行わず、送信タイムスタンプはRTPタイムスタンプから直接計算される。このモードは録画には向いているが、リアルタイムアプリケーションには向いていない。 RTP_JITTER_BUFFER_MODE_SLAVE: 送信者と受信者間のスキューを計算し、平滑化された調整済み送信タイムスタンプを生成する。このモードは低遅延の通信に適している。 RTP_JITTER_BUFFER_MODE_BUFFER: 低ウォーターマーク/高ウォーターマーク間のパケットをバッファリングする。このモードはストリーミング通信に向いている。 RTP_JITTER_BUFFER_MODE_SYNCED: 送信側と受信側のクロックは #RTP_JITTER_BUFFER_MODE_SLAVE のように同期しているが、スキューは 0 と仮定する。 送信側と受信側のクロックが同期しており、クロックスキューがない場合の低レイテンシ通信によい。 RTP_JITTER_BUFFER_MODE_LAST: 最後のバッファーモード。