Monthly Archives: 8月 2007

MPEG-2の各ピクチャタイプのサイズ

ピクチャに含まれるスライスのサイズを数えて足してピクチャのサイズを算出した。映像ソースは1440×1080のHDV。

[code]
GOP_START_CODE 0:0:00:00 1 0
PICTURE_START_CODE 0 I
SLICE_START_CODE 1 [3255]
SLICE_START_CODE 2 [3203]
SLICE_START_CODE 3 [3181]
SLICE_START_CODE 4 [3220]
SLICE_START_CODE 5 [3170]
SLICE_START_CODE 6 [3074]
SLICE_START_CODE 7 [3435]
SLICE_START_CODE 8 [2836]
SLICE_START_CODE 9 [3062]
SLICE_START_CODE 10 [3140]
:
:
[/code]

Iピクチャ 20〜22kByte 1スライスあたり 3k〜4kByte
Pピクチャ 15〜16kByte 1スライスあたり 2.2k〜2.3kByte
Bピクチャ 6〜7kByte 1スライスあたり 0.9k〜1.1kByte

RTPもしくはDCCPの1つあたりに188バイト(TS)×7パケット=1316バイトで、これが損失することで1スライス0.9k〜4kByte巻き込んで損失することになる。

当初はスライスヘッダを数えて損失の評価ができるかどうか考えていたが、1スライス内に1RTP分のペイロードの損失が見られるので、スライスの数に異常がなくてもマクロブロック・ブロックが消えてしまっているという状況がありえることが分かった。

スライスごとのサイズは算出できているので、サイズ比較をすれば上手くいくかもしれない。

補足:
VLCにESを食わせて吐かせたTSからさらにESを取り出すとスライスのサイズが変わっている。食わせたESがPESでAudioを含み、吐かせたESからはVideoしか抽出していないからかもしれない。

MPEG-2 TSのスライスヘッダ

画像情報特論 授業資料 ストリーミングソフトウェアについてがMPEGについて分かりやすい。

この授業では、MPEG-2内部について考察させたりRTPやRTCPの送受信プログラムを組ませたりと、実装させている。時間がかかりそうな課題だ。

スライスヘッダのスタートコードを検索すると1ピクチャーあたり68のスライスがあった。このスライスは16ライン幅のマクロブロックの帯であるらしいので、16ライン×68スライス=1088ライン?

スライスレイヤの下位にマクロブロックレイヤ、ブロックレイヤがある。この2つはそれぞれ動き補償・DCT処理を行うのだが、これらにはスタートコードがない。よってエラー発生時には次のスライスレイヤを探索してデコードを行うらしい。スライスレイヤはエラー発生時のときに再同期するために用意しているレイヤらしい。

MPEG-2 TSのGOPとPICTUREをパース

VLCが吐いたMPEG TSからPESを取り出してESを取り出してMPEG VIDEO解析。スタートコードという0x000001b8などが来るまで待って、シーケンスヘッダということが分かるからその後は解析、という手順。

[code]
0:0:00:00 1 0 0 I 3 P 1 B 2 B 6 P 4 B 9 P 7 B 8 B 12 P 10 B 11 B
0:0:00:13 0 0 2 I 0 B 1 B 5 P 3 B 4 B 8 P 6 B 7 B 11 P 9 B 10 B 14 P 12 B 13 B
0:0:00:28 0 0 2 I 0 B 1 B 5 P 3 B 4 B 8 P 6 B 7 B 11 P 9 B 10 B 14 P 12 B 13 B
0:0:01:13 0 0 2 I 0 B 1 B 5 P 3 B 4 B 8 P 6 B 7 B 11 P 9 B 10 B 14 P 12 B 13 B
0:0:01:28 0 0 2 I 0 B 1 B 5 P 3 B 4 B 8 P 6 B 7 B 11 P 9 B 10 B 14 P 12 B 13 B
0:0:02:13 0 0 2 I 0 B 1 B 5 P 3 B 4 B 8 P 6 B 7 B 11 P 9 B 10 B 14 P 12 B 13 B
0:0:02:28 0 0 2 I 0 B 1 B 5 P 3 B 4 B 8 P 6 B 7 B 11 P 9 B 10 B 14 P 12 B 13 B
0:0:03:13 0 0 2 I 0 B 1 B 5 P 3 B 4 B 8 P 6 B 7 B 11 P 9 B 10 B 14 P 12 B 13 B
0:0:03:28 1 0 0 I 1 P
0:0:04:00 1 0 0 I 3 P 1 B 2 B 6 P 4 B 5 B 9 P 7 B 8 B 12 P 10 B 11 B 15 P 13 B 14 B
0:0:04:16 0 0 2 I 0 B 1 B 5 P 3 B 4 B 8 P 6 B 7 B 11 P 9 B 10 B 14 P 12 B 13 B 17 P 15 B 16 B
0:0:05:04 0 0 2 I 0 B 1 B 5 P 3 B 4 B 8 P 6 B 7 B 11 P 9 B 10 B 14 P 12 B 13 B 17 P 15 B 16 B
0:0:05:22 0 0 2 I 0 B 1 B 5 P 3 B 4 B 7 P 6 B
[/code]

まずはGOPヘッダとPICTUREヘッダまでを解析。hour:minute:second:picture closed_gop broken_link (temporal_referece picture_coding_type)の順序。

GOPがIピクチャーから始まっていなかったり、GOPが15ピクチャー以上あったり。この方法で比較してはいけないということが分かってきた。これ以上はスライスレイヤ・マクロブロックレイヤ・ブロックレイヤに下ることになる。これをソースと比較してするのは難しいのではないかと思う。

途中損失した場合の挙動はデコーダごとに異なるので、既存のデコーダを使って静止画をdumpさせて単純にSNR比較した方がいい気がする。

評価方法は別にして、IピクチャPピクチャBピクチャの探索の仕方が分かったのは大きい。Iピクチャは大事なので誤り訂正を付加したり、Bピクチャを捨ててしまおうと考えていた時期があったが、単にTSを見るだけでは無理だということが分かった。TSからESを取り出して、かつPICTUREヘッダを探してピクチャを見つけ、それに依存する下位のレイヤを全て抽出する作業が必要になってくる。

再生にPESヘッダが必要なのか疑問になってきた。DTS、PTSなくてもGOPにtime_codeあるし。ここら辺がよく分からない。

MPEG-2ビデオコーデックに潜伏中

やけくそでMPEG-2コーデックに潜航してたらWeb上の資料が意外と多くて驚いた。

真空波動研がPCRとかGOPを解析しているとか知らなかったし。

mediatoolsのm2vhdrというプログラムが使いやすそうだけどソースコードがないし。

まるもさんのMPEG-2 VIDEO VFAPI Plug-Inにはaviutl時代にはお世話になった。まるもさんの日記がMPEGが濃くて一番面白い。

以下、参考資料。
Diary 2000-7
プログラミング万屋
MPEG.htm
MPEG1

MPEG-2 TSのPTS・DTS取得

MPEG-2 TSのPTS・DTS取得が出来た。

mpegts.PNG

最後の1つ、2つの値が、PTSもしくはDTS/PTSの値。単位は秒。

30秒の動画で試したら、始まりが14810.282733s、終わりが14840.145900、ちゃんと30秒できている。

どうもMPEG_Primer_PV-050106-00.pdf (application/pdf オブジェクト)と同じ作業としているような…

で、PTSとDTSを取り出してから思った。これだけじゃ何処が損失したか分からない。GOPまで解析せにゃあかんのだろうか。

たぶんPESヘッダのRTPデータグラムをロスすると60kByteが全滅する可能性が高そう。たぶん次のPESヘッダ0x000001が来るまで回復できないんじゃなかろうか。たぶんPESヘッダは2つくらい冗長して送っても問題ないと思う。

てことが、CiNii – 高品質なMPEG2ビデオストリーミング配信のためのPESヘッダ保護の検討(Webサービスベースのオフィスアプリケーション・ネットワーキング・マネジメント及び一般)に書いてありそうなのだが、2年経過していないので読めない。悲しい。

補足:
ブロげ » MPEG-2ヘッダ解析プログラム

MPEG 2-TSとPESの詳細

MPEG 2-TSについて調べた。今まではWebの情報とffmpegのソースコードを見て理解してきたが、デジタル放送教科書を拾ってきて読んだら、ソースコードの意味がよく分かった。まだ、紙資源は必要だ。

まずMPEG−2には「MPEG-2システム」という概念があり、その中にフォーマットとしてPSとTSがある。このPSとTSはPESを入れる入れ物でヘッダを持つ。PESとは符号化したばっかりのものであるESをパケット化したもので、これもまたヘッダを持つ。PSは蓄積メディア向けのヘッダであり、TSは放送メディア向けのヘッダである。

このPESにPTSとDTSという2つタイムスタンプが入っている。このPESがPSには複数入っているのだが、TSはPESを分割して入れてあるらしい。今日知った。

今まで全てのTSにPESヘッダが入っていると思ってパースして変になっていた。で、どのTSにPESヘッダが入っているのか分からないじゃないか、と思っていたら、TSにペイロードユニットスタートインジケータというビットがあり、このビットが1だとPESヘッダが入っていることが分かるらしい。

といってもPESヘッダはTSペイロードの途中に入ってもいいので、どこから始まりなのか分からんじゃないか、と思っていたらPESヘッダは必ず0x000001で始まるらしいことが分かった。

TSでも巡回カウンターというシーケンス番号っぽいものを持っているのだが、4ビットしか持たないため16パケット以上の損失が分からない。今回の実験ではMTU 1500としているため188バイト×7で7つのTSパケットを1データグラムに持つ。つまり、1データグラム損失してしまうと7進んでしまうのでTSの巡回カウンターでは損失は検知できても量まではよくわからない。やはりPTSで判断すべきか。

PTSは90KHzのクロックで計測した値を33ビット長で表すらしい。90kHzの理由はPALとNTSCの公約数で、33ビット長は24時間を表現するためらしい。計算すると確かに26時間以上表現できることが分かる。

PESヘッダを解析していくと、PESのペイロードのサイズは大きいもので65,508バイトあたりであった。つまり1つのPESを運ぶためには50データグラムのRTPの転送に成功する必要があるらしい。このPESペイロードに抜けができた場合、つまり損失があった場合にMPEGデコーダはどのような挙動をするのだろうか。

やはり、PES丸ごと捨ててしまうのだろうか。

参考:
MPEG-2システム – Wikipedia

MPEG-2 TSの概要 前編

MPEG-2 TSの概要 後編

フォーマット辞典 映像編 – しいしせねっと

MPEG技術解説 -第3章 MPEG-2 – [3.1 MPEG-2システム]

MPEG2/TS

プロトコル解析プログラムの備忘録

作ってるプロトコル解析プログラムの備忘録。ソース見れば思い出すかもしれないけれど、きっと忘れる。

main.cの動き。

  1. libpcapにてpcapファイルからプロトコル情報を全てメモリ上のリスト構造体にコピーする。
  2. リストから最もはじめに受信したTCP, UDP, DCCPのうちのどれかを解析対象とする。
  3. 解析対象プロトコルのうち、「送信元IPアドレス・あて先IPアドレス・送信元ポート番号・あて先ポート番号」が一致するフレームを数える。
  4. 最も一致した「送信元IPアドレス・あて先IPアドレス・送信元ポート番号・あて先ポート番号」の組を新しいリストにフィルタリングする
  5. フィルタリングした新しいリストに対してMAC層解析とトランスポート層解析を行い、テキスト出力する

pcapファイルの全フレームのヘッダ情報をメモリ上にコピーするのでメモリを食う。topコマンドで見ると5MB強。約6万フレームのヘッダ約80B程度とすると4.8MBなので、そんなもの。

サイズ(ストリップ込み)
[code]
noch@debian-noch:~/wa2/wa2$ ls -l a.out
-rwxr-xr-x 1 noch noch 12208 Aug 20 02:22 a.out
[/code]

速度
[code]
noch@debian-noch:~/wa2/wa2$ time ./a.out \
/tmp/tshark-tcp-snd070817-2257 > /dev/null

real 0m0.369s
user 0m0.268s
sys 0m0.100s
[/code]
グラフ作成込みで
[code]
noch@debian-noch:~/wa2/wa2$ time ./tcp.seq \
/tmp/tshark-tcp-snd070817-2257 > /dev/null

real 0m2.383s
user 0m1.020s
sys 0m0.124s
[/code]

依存関係はlibpcap.soくらい。
[code]
noch@debian-noch:~/wa2/wa2$ ldd a.out
libpcap.so.0.7 => /usr/lib/libpcap.so.0.7 (0xb7f4c000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e1b000)
/lib/ld-linux.so.2 (0xb7f7b000)
[/code]

WiresharkはMPEG-TSヘッダを解する

WiresharkがMPEG-TSヘッダをパースしているので驚いた。ちゃんとHeaderの先頭が0x47。

wireshark.PNG

このRTPを認識させるのに、「Edit」-「Preferences」-「Protocols」-「RTP」の「Try to decode RTP outside of conversations:」のチェックが必要だった。

DCCPの場合はペイロードにRTPなしでMPEG-TSを直置きしているのでパースしない。

今、MPEG-TSのさらに下のPESヘッダに下って、MPEGソースレベルで損失を表せないかプログラムを書いている。DTS(デコードするタイムスタンプ)とPTS(プレゼンテーションするタイムスタンプ)を使って上手くできないかやっているが、なかなか予備知識なしでは厳しいものがある。

例えば0x47という値があるが、これは188バイトごとに出てくる。つまり、TSパケット境界が分からなくなった場合は0x47を探せばMPEG-TSの先頭が分かる、という仕組みなどがある。

MPEG-TSにこだわらずに出力されたファイルの比較をする方法も考慮したが、出力されたMPEGファイルのMPEG-TSを解して比較を行ったほうが分かりやすいだろうと思っているが、どうするべきだろうか。

TCPでバッファオーバーフローは発生しているのか

UDPでバッファオーバーフローが発生し、DCCPでは抑制されていることが分かった。それではTCPではどうなっているのだろうか。

LinuxのデフォルトのTCPにてバッファオーバーフローが発生しているのかどうかを確認する実験を行った。

送信側
tshark-tcp-070819-2111-sndtcp.png

受信側
tshark-tcp-070819-2111-rcvtcp.png

赤のドットはTCPシーケンス図である。

diffの緑のインパルスは「受信したTCPシーケンス番号ーそれまで受信した最大のTCPシーケンス番号」を表している。このdiffは通常はMSS一杯である1448 Bになるが、TCP送信後にMAC層にてバッファオーバーフローした場合には1448以上の値が来るはずである。ただし受信したTCPシーケンス番号よりそれまで受信した最大のTCPシーケンス番号が大きい場合、つまり、再送が行われた場合にはdiffは0に固定されるようにしている。

送信側グラフ、受信側グラフを比較すると、値の大きいdiffでは類似した傾向が見られる。受信グラフでは7.5秒付近にdiffのピーク値が見られる。キャプチャミスは送信側グラフの1448 * 2付近にて多く見られ、RTP、DCCPの場合と似たような傾向が見られている。

(Firefoxを使っている場合は各グラフを「Ctrl+左クリック」で新しいタブとして開き、タブ番号2,3である場合は「Ctrl+2」「Ctrl+3」を交互に押すと比較しやすい。)

さらにMAC層の損失があるかどうか調べた。

tshark-tcp-070819-2111-sndmac.png

このMAC層シーケンス送信回数の傾向からは、特に伝播状況の悪さは見られない。

結果として、LinuxデフォルトのTCPでは、送信されたTCPセグメントは無線LANで送信された時点で既に損失が発生している、ことが確認できた。この原因としては、やはりMACのバッファオーバーフローが考えられる。

この現象はデフォルトTCPの大きすぎる広告ウィンドウによって送信ウィンドウが開きすぎ、キャパシティを越えるトラヒックがMAC層に到着し、その時点でMAC層バッファが溢れ、損失し、TCPは高速再転送もしくはタイムアウト再送を行ったもの、と判断できる。そのためタイムアウト再送待ちの時間が10秒付近で発生していたり、無駄な高速再転送が起きたりすることで実効帯域を狭めてしまっている。

この挙動に対してTCPでは、広告ウィンドウサイズを設定する等の対処が必要になる。

送信側と受信側でキャプチャした結果が違う?

送信側
[code]
noch@debian-noch:~/wa2/wa2$ tshark -r /tmp/tshark-tcp-snd070817-2257 -R “tcp.port == 51401” | tail
94107 33.154248 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
94108 33.154573 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
94109 33.154897 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
94110 33.155221 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
94111 33.155548 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
94112 33.155802 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
94113 33.156058 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=191 Ack=81738979 Win=563392 Len=0 TSV=179444729 TSER=113330957
94119 33.204014 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [FIN, ACK] Seq=81748203 Ack=191 Win=6912 Len=0 TSV=113330974 TSER=179444730
94120 33.204199 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [FIN, ACK] Seq=191 Ack=81748204 Win=563392 Len=0 TSV=179444742 TSER=113330974
94122 33.204530 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [ACK] Seq=81748204 Ack=192 Win=6912 Len=0 TSV=113330974 TSER=179444742
[/code]
受信側
[code]
noch@debian-noch:~/wa2/wa2$ tshark -r /tmp/tshark-tcp-rcv070817-2257 -R “tcp.port == 51401” | tail
84694 27.916116 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
84695 27.916136 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=81744770 Win=8803 Len=0 TSV=179444730 TSER=113330959
84696 27.916439 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
84697 27.916764 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
84698 27.916782 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=81747666 Win=8803 Len=0 TSV=179444730 TSER=113330959
84699 27.917022 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
84700 27.917054 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=81748202 Win=8803 Len=0 TSV=179444730 TSER=113330959
84701 27.965231 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [FIN, ACK] Seq=81748202 Ack=0 Win=108 Len=0 TSV=113330974 TSER=179444730
84702 27.965275 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [FIN, ACK] Seq=0 Ack=81748203 Win=8803 Len=0 TSV=179444742 TSER=113330974
84703 27.965748 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [ACK] Seq=81748203 Ack=1 Win=108 Len=0 TSV=113330974 TSER=179444742
[/code]

送信側のFIN/ACKがSeq=81748203
受信側のFIN/ACKがSeq=81748202

なぜ違う?

理由は、wireshark(tshark)のTCPシーケンス番号の計算方法が、そのストリームで一番初めに受信したTCPセグメントのシーケンス番号(ランダムに初期化された番号)を基準0として扱うため、「一番初めに受信したTCPセグメント」が送信側、受信側で異なると番号がずれる。

送信側
[code]
noch@debian-noch:~/wa2/wa2$ tshark -r /tmp/tshark-tcp-snd070817-2257 -R “tcp.port == 51401” | head
100 4.791747 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [SYN] Seq=0 Len=0 MSS=1460 TSV=179437639 TSER=0 WS=6
102 4.791882 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [SYN, ACK] Seq=0 Ack=1 Win=5792 Len=0 MSS=1460 TSV=113323870 TSER=179437639 WS=6
103 4.792085 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=1 Ack=1 Win=5888 Len=0 TSV=179437639 TSER=113323870
106 4.792353 192.168.2.12 -> 192.168.2.10 TCP [TCP ACKed lost segment] 1234 > 51401 [ACK] Seq=1 Ack=42 Win=5824 Len=0 TSV=113323871 TSER=179437639
107 4.792843 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [PSH, ACK] Seq=42 Ack=1 Win=5888 Len=149 TSV=179437639 TSER=113323871
109 4.792954 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [ACK] Seq=1 Ack=191 Win=6912 Len=0 TSV=113323871 TSER=179437639
112 4.892060 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [PSH, ACK] Seq=1 Ack=191 Win=6912 Len=84 TSV=113323896 TSER=179437639
113 4.892243 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=191 Ack=85 Win=5888 Len=0 TSV=179437664 TSER=113323896
115 4.892549 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [PSH, ACK] Seq=85 Ack=191 Win=6912 Len=188 TSV=113323896 TSER=179437664
116 4.892726 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=191 Ack=273 Win=6912 Len=0 TSV=179437664 TSER=113323896
[/code]

受信側
[code]
noch@debian-noch:~/wa2/wa2$ tshark -r /tmp/tshark-tcp-rcv070817-2257 -R “tcp.port == 51401” | head
1 0.000000 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [PSH, ACK] Seq=0 Ack=0 Win=108 Len=84 TSV=113323896 TSER=179437639
2 0.000026 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=84 Win=92 Len=0 TSV=179437664 TSER=113323896
3 0.000031 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [PSH, ACK] Seq=84 Ack=0 Win=108 Len=188 TSV=113323896 TSER=179437664
4 0.000035 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=272 Win=108 Len=0 TSV=179437664 TSER=113323896
5 0.000040 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [ACK] Seq=272 Ack=0 Win=108 Len=1448 TSV=113323946 TSER=179437664
6 0.000045 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=1720 Win=154 Len=0 TSV=179437714 TSER=113323946
7 0.000049 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [ACK] Seq=1720 Ack=0 Win=108 Len=1448 TSV=113323946 TSER=179437664
8 0.000054 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=3168 Win=199 Len=0 TSV=179437714 TSER=113323946
9 0.000059 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [PSH, ACK] Seq=3168 Ack=0 Win=108 Len=1448 TSV=113323946 TSER=179437664
10 0.000122 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=4616 Win=244 Len=0 TSV=179437714 TSER=113323946
[/code]

192.168.2.10から192.168.2.12に向かうSYNがキャプチャできていないことが分かる。