« 2014年10月 | トップページ | 2015年1月 »

2014年11月

2014年11月19日 (水)

スレッドの変遷のメモ その2

結局…3つのネットワーク通信をシングルスレッド内で処理すると十分なPCMデータを受信できないと判明しました…

30ms毎に起動するタイマーを設定し、届いたPCMデータを定期的に再生用バッファに書き込むのですが、
30ms再生するのに必要なPCMデータはサンプリングレート48000HZ, 2チャンネル,16ビットの場合5760バイトとなります。

で、再生用バッファは1秒間分のデータを保持できるように確保しますので、この場合192000バイト確保しているのですが、一度再生を開始しAcftive状態になったあと、再生用バッファが空になってIdle状態に遷移しています。
これはIdle状態になったあとに再生用バッファの空きサイズが192000バイトになっていることからも分かります。

もちろん、ずっと十分なPCMデータを供給できればIdle状態になることはありません。

タイマー起動による定期的な再生用バッファへの転送ではなく、受け取ったらすぐ書き込む方式にしてもやはり足らないようです。

ちなみにzeroshikikai.cppでは、0.1秒分のバッファリングを行い、DirectSoundのバッファへ書き込むようになっていますね。再生用バッファのサイズは3秒間分のデータサイズになっているようです。

というわけで…3つのスレッドでそれぞれQTcpSocketを生成し、ネットワーク通信をしなくてはなりません。

Qtでソケットとマルチスレッドでハマっているという記事がたっくさんありますね…

[シングルスレッド版ネットワーク通信のログ(サウンド関連のみ)]

------------------------------------------
changeSamplerate()
------------------------------------------
sampleRate : 48000
sampleSize : 16
channelCount : 2
byteOrder : LittleEndian
sampleType : SignedInt
------------------------------------------
bytesForDuration : 5760
------------------------------------------
format is valid.
bufferSize (bytes) = 192000
------------------------------------------
[SoundThread] handleStateChanged(): IdleState 0
[SoundThread] timerExpired() : soundBuffer->size() = 1920
[SoundThread] timerExpired() : audioOutput->bytesFree() = 192000
[SoundThread] handleStateChanged(): ActiveState 1
output->write() : size = 1920
[SoundThread] handleStateChanged(): IdleState 2
[SoundThread] timerExpired() : soundBuffer->size() = 13440
[SoundThread] timerExpired() : audioOutput->bytesFree() = 192000
[SoundThread] handleStateChanged(): ActiveState 3
output->write() : size = 13440
[SoundThread] timerExpired() : soundBuffer->size() = 1920
[SoundThread] timerExpired() : audioOutput->bytesFree() = 153600
output->write() : size = 1920
[SoundThread] timerExpired() : soundBuffer->size() = 5760
[SoundThread] timerExpired() : audioOutput->bytesFree() = 115200
output->write() : size = 5760
[SoundThread] timerExpired() : soundBuffer->size() = 9600
[SoundThread] timerExpired() : audioOutput->bytesFree() = 153600
output->write() : size = 9600
[SoundThread] timerExpired() : soundBuffer->size() = 5760
[SoundThread] timerExpired() : audioOutput->bytesFree() = 153600
output->write() : size = 5760
[SoundThread] timerExpired() : soundBuffer->size() = 3840
[SoundThread] timerExpired() : audioOutput->bytesFree() = 115200
output->write() : size = 3840
[SoundThread] handleStateChanged(): IdleState 4
[SoundThread] timerExpired() : soundBuffer->size() = 7680
[SoundThread] timerExpired() : audioOutput->bytesFree() = 192000
[SoundThread] handleStateChanged(): ActiveState 5
output->write() : size = 7680
[SoundThread] timerExpired() : soundBuffer->size() = 13440
[SoundThread] timerExpired() : audioOutput->bytesFree() = 153600
output->write() : size = 13440
[SoundThread] timerExpired() : soundBuffer->size() = 7680
[SoundThread] timerExpired() : audioOutput->bytesFree() = 153600
output->write() : size = 7680
[SoundThread] timerExpired() : soundBuffer->size() = 3840
[SoundThread] timerExpired() : audioOutput->bytesFree() = 153600
output->write() : size = 3840
[SoundThread] timerExpired() : soundBuffer->size() = 5760
[SoundThread] timerExpired() : audioOutput->bytesFree() = 115200
output->write() : size = 5760
[SoundThread] timerExpired() : soundBuffer->size() = 5760
[SoundThread] timerExpired() : audioOutput->bytesFree() = 153600
output->write() : size = 5760
[SoundThread] timerExpired() : soundBuffer->size() = 3840
[SoundThread] timerExpired() : audioOutput->bytesFree() = 153600
output->write() : size = 3840
[SoundThread] handleStateChanged(): IdleState 6
[SoundThread] timerExpired() : soundBuffer->size() = 5760
[SoundThread] timerExpired() : audioOutput->bytesFree() = 192000
[SoundThread] handleStateChanged(): ActiveState 7
output->write() : size = 5760

2014年11月13日 (木)

スレッドの変遷のメモ

スレッドの構成の変遷について

----------------------------------------------------------------------------------
[最初のバージョン]
----------------------------------------------------------------------------------

シングルスレッドバージョンでした。1つのスレッド内で以下を行います。

1,キーボード、マウスのコントロール情報の送受信
2,JPEGファイルデータの受信、JPEGファイルの描画
3,PCMデータの受信、サウンド再生バッファへの転送

1つのスレッド内で非同期に行いますが、一度に1つの処理しかできないので、JPEGファイルデータの描画を待っている間はPCMデータのサウンド再生バッファへの転送が待たされるため、音が途切れる現象が発生しました。

----------------------------------------------------------------------------------
[3つの通信を別スレッドに分けたバージョン]
----------------------------------------------------------------------------------

キーボード、マウスのコントロール情報の送受信で1つのスレッド、JPEGファイルデータの受信、JPEGファイルデータの描画で1つのスレッド、PCMデータの受信、サウンド再生バッファへの転送で1つのスレッド、計3つのスレッドを設けました。

各スレッドは、マルチスレッドで動作するので、JPEGデータの描画を待たずに、PCMデータの転送が行えるのでノイズを回避出来ます。が、QTcpSocketを複数のスレッドで生成して送受信するため、正確なデータが受信できないことがあり、不正なJPEG、PCMデータを受信してしまい、結局デスクトップ画像も、サウンドもノイズが入ってしまうという結果になりました。

----------------------------------------------------------------------------------
[ネットワーク通信だけを行うスレッドとPCMデータのサウンド再生バッファへの転送を行うスレッド]
----------------------------------------------------------------------------------

ネットワーク通信を行うスレッドは1つであるのが望ましい、但しJPEGデータの描画を行うスレッドとPCMデータをサウンド再生バッファへ転送するスレッドは分けたい、ということで2つのスレッドでの構成にしました。データの送受信が正確にでき、JPEGファイルデータの描画とPCMデータの転送が効率よく行えます。

きれいな構成としては、JPEGファイルデータの描画も別スレッドにするのがいいのですが、あまりスレッドを設けすぎるのも問題ですね、たぶん。

(追記)
ネットワーク通信を行うスレッド中からJPEGデータの描画を行う処理も別スレッドで分離する必要があるようです。同じスレッドで処理している現在の構成では、PCMデータの再生にデータ転送が間に合っていません。
困りました…

----------------------------------------------------------------------------------
とりあえず現在は以下の3スレッド構成です。
----------------------------------------------------------------------------------

1, ネットワーク通信を行うスレッド

3つのソケットを接続し、キーボード/マウス情報送信、JPEGファイルデータ、PCMデータの受信のみを行います。JPEGファイルデータ、PCMデータはリングバッファへの転送まで行います。キーボード/マウス(コントロール)情報の送受信のみ同じスレッド内で行います。

2, JPEGファイルデータ描画を行うスレッド

リングバッファから1枚ずつJPEGファイルデータを取り出し、描画します。リングバッファが空ならなにもしません。

3,PCMデータの再生を行うスレッド

タイマーを使って、定期的にリングバッファからPCMデータを取り出し、再生用のバッファへ転送します。再生用のバッファが空になると音が途切れてしますので、途切れないように転送する必要があります。

2014年11月10日 (月)

キーボードコントロールを実装しました その4

後回しにしていたUS Keyboardの差分を実装しました。

基本的には、Qt::Key_* と Windowsの仮想キーコード VK_* の対応表を地道に確認、修正するだけです。

USキーボードだと`@`を押すと"VK_2"が送られなければなりません。JPキーボードだと"VK_OEM_3"です。

とりあえず、目視でUSキーボード用の仮想キーコードが送られていることを確認できました。


さて、キーボードコントロールの残り作業は、少し前に検討したIME関連の実装です。WindowsキーをHookすることが出来ないのはそういう仕様だとすればよいのですが、前回調べたeventFilerをインストールしても、IME関連のキーの扱いは単純にはいかないようです。

考慮すべき点は以下の2点です。いずれもWindowsの時のみですが…

1,クライアント側PCのIMEを常にOFFにする

2,漢字、変換、無変換、かな/ひら の4つのキーのイベントの取得

少し調べたのですが、どうやらWindows APIを直に叩いてやる必要があるようです…

# Ubuntu 14.10 日本語 Remix がリリースされましたね…
http://www.ubuntulinux.jp/News/ubuntu1410-ja-remix

2014年11月 8日 (土)

ノイズが出るのは…その2

JPEGデータの再生を行うスレッドとサウンドの再生を行うスレッドを分けたら、大体はうまくサウンドが再生できるのですが、別スレッドでそれぞれネットワーク通信を行うとそれぞれのデータの受信が正しくできない場合があるということでどうやらこれは不可避なことのようです。

ということで、まずはデータを正しく受診することが先決ですのでネットワーク通信を行うスレッドはやはり1つにまとめて、その中でデータの送受信は行うことにしました。しかしながら、JPEGデータの再生を行っている間は、PCM再生バッファへのデータ転送が行えないので、バッファーアンダーランによるPCMデータ不足でプチノイズが発生します。

この問題の解決方法としては、ネットワーク通信のスレッドとJPEGデータ再生/PCMデータ再生のスレッドを分けるということになりそうです。

Qt5とWindows XP/Vista

XP/Vistaのサポートについて調べたので…

http://qt-project.org/doc/qt-5/supported-platforms.html

によりますと、Qt Projectでコンフィギュレーションテストしているのは、

Windows7(32bit)、Windows8(32bit/64bit)、Windows8.1(32bit/64bit)

というような情報がありますね。

WindowsXP(32bit/MSVC2008)という情報もあるので、XP/Vistaでのビルドも可能みたいです。
DirectX9とかが必要スペックみたいですね。

が、そこまでして作る必要もないでしょうね…Windows2000もサポートできるBrynhildrがあるのですから…
#と考えると、Windows版自体Brynhildrで必要十分、という結論になる…


(追記:2017.11.18)

Qt5.6 (現時点で最新は5.6.3)でビルドしたものまでXP/Vistaで動作します。Qt5.7以降でビルドしたものはXP/Vistaでは動きません。

ですので、XP/Vista以降をサポートしたい場合はQt5.6.3を使ってビルドします。

2014年11月 7日 (金)

進捗記録 2014.11.7

やっと目処がついたので、久しぶりに進捗記録します。

テスト動画をYoutubeにアップロードしました。

Windows版 0.10 テスト動画

サーバ (server) : Brynhildr 1.0.5 on Windows 7 Home (x64)

クライアント (client): Qt Brynhildr (仮称) 0.10 on Windows 8.1 Pro update (x64)

公開されているプロトコル(PUBLIC MODE)のため、ファイル転送、クリップボード転送には未対応。

サウンドの転送は一時的に無効にしている。

恐らく32bit/64bitのWindows7以降のWindows OSに対応。Vistaは環境がないため未確認。

Ubuntu 32bit/64bitでも動作は一応する。が、要調整。

Qt5環境があれば多分大丈夫…のはず…、でもCPUパワーがある程度必要かも…

(追記)

サーバをXPにしてみました。

Windows版 0.10 テスト動画 その2

サーバ (server) : Brynhildr 1.0.5 on Windows XP Pro (x32) on VMware Workstation 10.0.4

クライアント (client): Qt Brynhildr (仮称) 0.10 on Windows 8.1 Pro update (x64)

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つ検討すべき課題があることが分かりました…(長くなったので、とりあえずここまでにします)

« 2014年10月 | トップページ | 2015年1月 »