RTPの時刻と再生バッファ
到着時刻の揺れから、再生待ち時間と遅延廃棄の関係を確認します。
説明
音声のパケットは、送信間隔が一定でも同じ間隔で到着するとは限りません。再生バッファは到着の揺れを吸収しますが、待つ時間を増やすほど会話の遅延も増えます。
RTPヘッダーのsequenceとtimestampを解析し、20ms間隔の音声が順序を変えて届く記録を調べます。再生待ちを40msと100msに変え、間に合うデータ、遅れたデータ、欠けたままのデータを比較します。
1RTPの固定ヘッダー
version2、拡張なし、CSRCなし、PT0、sequence100、timestamp0、SSRC0x12345678のヘッダーを作ります。
import structRTPのバイト列; header=固定ヘッダーを組み立てるstruct.pack("!BBHII",0x80,0,100,0,0x12345678); (header.hex12バイトを確認(" "),len(header))固定部分は12バイトです。sequenceはパケットごとに増え、timestampは標本の時刻を表します。ここではPT0のPCMU、クロック8000Hzとして、20msに160標本を送る条件です。
2連番と標本時刻を別々に読む
先頭2バイトのフラグを復号し、sequence・timestamp・SSRCを取り出します。
first,second各欄を復号,seq,stamp,ssrc=struct.unpack("!BBHII",header); (first>>6version・PT・番号・時刻・標本数,second&127,seq,stamp,hex(ssrc),8000*0.020)version2、PT0、sequence100、timestamp0、同じ送信源のSSRCが得られます。20msごとのtimestamp増分は160で、sequenceの増分1とは違います。timestampをそのままミリ秒と読むことはできません。
3順序が入れ替わった到着列
送信時刻0・20・40・60・80・100msに対し、到着は30・55・120・95・未着・135msです。時計の対応は既知とする教材モデルです。
records=番号・標本時刻・送信ms・到着ms[(100+i,i*160,i*20,a) for i,a in enumerate([30,55,120,95,None,135])]; sorted実際の到着順に並べる([r for r in records if r[3] is not None],key=lambda r:r[3])sequence103は102より先に着きます。timestampにより再生する標本順を判断し、sequence104はまだ届いていないと分かります。実環境で一方向遅延を計算するには、送受信時計の対応も必要です。
440ms待つ場合
送信時刻に40msを加えた時点を再生期限とし、到着が期限以下なら採用します。
delay=4040msの再生待ち; [(seq各パケットを期限と比較,sent+delay,arrival,"missing" if arrival is None else "play" if arrival<=sent+delay else "late") for seq,ts,sent,arrival in records]102は到着120msが期限80msを超えているため遅延廃棄、104は未着です。それ以外の4件は間に合います。後から102が届いても、既に再生した位置へ戻して通常どおり再生するわけにはいきません。
5100ms待つ場合
同じ到着記録で待ち時間だけ100msへ変えます。
delay=100待ち時間を60ms増やす; [(seq同じ記録で再評価,sent+delay,arrival,"missing" if arrival is None else "play" if arrival<=sent+delay else "late") for seq,ts,sent,arrival in records]102も期限140msに間に合い、5件を再生できます。ただし最初の再生開始も40msから100msへ60ms遅れます。104の欠落は待ちを長くしただけでは回復せず、補間や損失隠蔽等は別の処理です。
6到着間隔のばらつきをサンプル単位で求める
RTPのジッター計算では、受信順に到着時刻と送信timestampの差の変化を使います。msを8倍して8000Hzの標本単位へそろえます。
ordered=受信順の記録sorted([r for r in records if r[3] is not None],key=lambda r:r[3]); transit=時刻の単位をそろえる[r[3]*8-r[1] for r in ordered]; differences=隣り合う差分[transit[i]-transit[i-1] for i in range(1,len(transit))]; (transit,differences); jitter=0変化を確認転送時間相当の値は240・280・280・640・280で、変化は40・0・360・-360です。送信順ではなく受信順で隣り合うパケットを使い、再生バッファの待ち時間とは別の統計量を求めています。
7ジッターの平滑化
初期値を0として、Jを差分の絶対値へ16分の1ずつ近付けます。
for difference in differences:受信順に更新
jitter+=16分の1の平滑化(abs(difference)-jitter)/16
print標本数とms(round(jitter,6),round(jitter/8,6))各行は標本単位とmsです。最後は約5.71msになります。この推定値がそのまま必要なバッファ長を決めるわけではなく、再生期限、許容遅延、損失の扱いを含めて設計します。
まとめ
sequenceはパケット順、timestampは標本時刻であり、用途と単位が異なります。
待ちを増やすと遅延廃棄を減らせても、再生遅延と未着の問題は残ります。
操作履歴と解説を振り返れます。
Python 3
import struct; header=struct.pack("!BBHII",0x80,0,100,0,0x12345678); (header.hex(" "),len(header))('80 00 00 64 00 00 00 00 12 34 56 78', 12)first,second,seq,stamp,ssrc=struct.unpack("!BBHII",header); (first>>6,second&127,seq,stamp,hex(ssrc),8000*0.020)(2, 0, 100, 0, '0x12345678', 160.0)
records=[(100+i,i*160,i*20,a) for i,a in enumerate([30,55,120,95,None,135])]; sorted([r for r in records if r[3] is not None],key=lambda r:r[3])[(100, 0, 0, 30), (101, 160, 20, 55), (103, 480, 60, 95), (102, 320, 40, 120), (105, 800, 100, 135)]
delay=40; [(seq,sent+delay,arrival,"missing" if arrival is None else "play" if arrival<=sent+delay else "late") for seq,ts,sent,arrival in records][(100, 40, 30, 'play'), (101, 60, 55, 'play'), (102, 80, 120, 'late'), (103, 100, 95, 'play'), (104, 120, None, 'missing'), (105, 140, 135, 'play')]
delay=100; [(seq,sent+delay,arrival,"missing" if arrival is None else "play" if arrival<=sent+delay else "late") for seq,ts,sent,arrival in records][(100, 100, 30, 'play'), (101, 120, 55, 'play'), (102, 140, 120, 'play'), (103, 160, 95, 'play'), (104, 180, None, 'missing'), (105, 200, 135, 'play')]
ordered=sorted([r for r in records if r[3] is not None],key=lambda r:r[3]); transit=[r[3]*8-r[1] for r in ordered]; differences=[transit[i]-transit[i-1] for i in range(1,len(transit))]; (transit,differences); jitter=0([240, 280, 280, 640, 280], [40, 0, 360, -360])
for difference in differences:
jitter+=(abs(difference)-jitter)/16
print(round(jitter,6),round(jitter/8,6))2.5 0.3125 2.34375 0.292969 24.697266 3.087158 45.653687 5.706711
演習完了