« キーボードコントロールを実装しました その3 | トップページ | 進捗記録 2014.11.7 »

2014年11月 6日 (木)

JPEG イメージが壊れています…その4

最低限のクライアントとしての動作を確認した…のですが…

時々Motion JPEGのデスクトップ画像が乱れることがあったり、サウンドにプチノイズが発生したりして、どうにも気になっていました。

気になってしまい、モチベーションが上がらないので、ちゃんと調べることにしました。

まずデスクトップ画像の乱れから。

デバッグ用に、送られてきたJPEGデータ(ファイル)すべてを残すフラグを設けているので、これをONにして実行してみました。

カレントフォルダにjpgというフォルダを作っておけば、この下にフレーム番号を連番とするファイル名でサーバから送られてきたデスクトップ画像が保存されます。数秒間分なので、100枚くらいのJPEGファイルが現れます。

エクスプローラでjpgフォルダを表示させると10枚に1枚くらいの間隔で、おかしな画像が表示されています。
やはりQtに描画を依頼する前のJPEGデータの時点でデータが壊れているようです。

ではJPEGデータはどの時点で壊れるのでしょうか?

まず、最初はBrynhildrサーバさんが送信する時点です。しかし、これはBrynhildrのクライアントモード、zeroshikikai.cppで正しく表示できていることから、壊れている可能性はありません。

次に考えられるのは、複数のパケットに分割されて送られてくるJPEGデータの断片を受け取って、1つのJPEGデータ全体の受け取りを完了するまでの間です。Qtのネットワークライブラリ経由で小さなデータを何回かに分けて受け取り、1つのJPEGデータをローカルバッファに貯める途中で、なんらかのデータ破壊が起こっている可能性が考えられました。

これを確認するため、データの断片を受取る前と受け取った後にローカルバッファに受信したデータのチェックサムを計算し、表示させるようにしました。時系列でこの2つを表示させれば、前回の受信から次の受信の間にデータの書き換えが発生したか否かが分かります。

つまり、想定外のデータの破壊が発生していれば、前回の受信後のチェックサムと受信前のチェックサムが異なっているはずです。

標準出力をログに取りながら、実行してみました。壊れている画像の転送に関する部分に関して、確認してみると、チェックサムはすべて等しく、データ破壊は発生していないという結論になりました。

だとすれば…JPEGデータを受け取った時点ですでにデータが壊れているとしか考えられません。

となると疑うべきはQtのネットワークライブラリしかありません。

ネットワーク通信はQtのQTcpSocketというクラスを利用しています。送受信に用いるのはwrite()とread()というメンバ関数です。このread()を利用して、ローカルバッファにデータをコピーした時点で壊れているとしか考えられません。

もう一度この現象が発生した頃のことを思い出すと、丁度3つあるネットワーク通信をシングルスレッドからマルチスレッドに変更した頃と重なります。

もしかして…と思いgoogleさんにお伺いをたてました。

"QTcpSocket Multiple Threads"

ツラツラと眺めると、以下の様なページが見つかりました。

http://qt-project.org/forums/viewthread/2309

QIODeviceのドキュメントをQt Assistantで確認してみると確かに以下のような記述があります。

Certain subclasses of QIODevice, such as QTcpSocket and QProcess, are asynchronous. This means that I/O functions such as write() or read() always return immediately, while communication with the device itself may happen when control goes back to the event loop. QIODevice provides functions that allow you to force these operations to be performed immediately, while blocking the calling thread and without entering the event loop. This allows QIODevice subclasses to be used without an event loop, or in a separate thread:

つまるところ、複数のスレッドの中でそれぞれQTcpSocketを生成して、event loopを利用してネットワーク送受信していたのがマズかったらしいです。この場合正確な受信データがread()では得られないのが仕様だということ…でした。

結局3つのネットワーク通信スレッドを1つのスレッドにまとめて、1つのスレッド内で3つの通信を行うようにしました。GUIスレッドと1つのネットワーク通信スレッドの2つのスレッドで再構成しました。

この修正により、JPEGデータの破壊は回避出来ました。

次にサウンドのプチノイズの確認です。まず上記の修正を行う前のソースコードで確認しました。

デバッグ用に、PCMデータをファイルに追記しながら残す機能も入れてあるのでこれをONにして実行しました。

数秒間実行して、PCMデータを出力させます。この生のPCMデータをcygwinのsoxコマンドでwavファイルに変換して実際に聞いてみました。

見事にプチノイズが再現されました。やはりこれもデータ自体が壊れていたということです、同じ原因ですね、たぶん。

ここで、ネットワーク通信を1スレッド化した修正後バージョンで同様にPCMデータを出力させてみました。同様にwavファイルに変換すると綺麗に聴けました…

結論として、単純にネットワーク通信を3つのスレッドに分けたのはマズかったということでした…(;ω;)

zeroshikikai.cppのようにWinsock + multiple threadsなら問題はないようです。

PCMデータがきちんと届いていることは確認できたのですが、プチノイズを改善するにはもう1つ検討すべき課題があることが分かりました…(長くなったので、とりあえずここまでにします)

« キーボードコントロールを実装しました その3 | トップページ | 進捗記録 2014.11.7 »

コメント

コメントを書く

コメントは記事投稿者が公開するまで表示されません。

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: JPEG イメージが壊れています…その4:

« キーボードコントロールを実装しました その3 | トップページ | 進捗記録 2014.11.7 »