雑多なこと…
Ubuntu 14.04 LTS 日本語Remix版もリリースされたので、実機とVMware Workstationで13.10から環境を移行しました。
Qt5.2.1もインストールして一応動作確認し、問題ないことを確認しました。
« 2014年3月 | トップページ | 2014年5月 »
Ubuntu 14.04 LTS 日本語Remix版もリリースされたので、実機とVMware Workstationで13.10から環境を移行しました。
Qt5.2.1もインストールして一応動作確認し、問題ないことを確認しました。
中途半端ですが、進捗記録します。
Windows 8.1 update と Ubuntu 14.04 LTS (英語版)(on VMware)で動作確認しました。
サーバは無線LAN上のBrynhildr on Windows7 Home (x64)です。
実機のUbuntu 14.04 LTSでも同様に動作を確認しました。キャプチャが面倒なのでVMware上で動作確認します。
どちらも音は出ますが、バッファーアンダーランが発生中です、実装を変えるかも。
#サウンド処理は初めてなもので、進みません…
スクリーンショットです。デュアルディスプレイなので、左にUbuntu 14.04 LTS、右にWindows 8.1 updateです。
念のためですが、1つのサーバに複数のクライアントは接続できなさそうだったので、Windows版はVLCで再現したものです。Ubuntu 14.04 LTS on VMware Workstationの方は実際に動作しているものです。
デスクトップをキャプチャしました。今回からキャプチャソフトを Bandicam から oCam へ変更しました。
Ubuntu 14.04 LTSでは、端末上に密かにbuffer underrunのメッセージが出ており、最後に”コアダンプ”します。
お恥ずかしい限りですが…何かアプリケーション終了時のメモリ解放に問題あり(二重開放)なのかも。gdbでデバッグしないと。
入力デバイス(キーボード、マウスなど)の通信内容についてはBrynhildrの作者の方に問い合わせ中ですが、実装に関してはそーんなに大変ってことはなさそうなイメージなので、音がまともに出せればGitHubに載せられるかもしれません。公開前にハードコーディングしてるところをどうにかしないと。
#各自再コンパイルしてください、といったら怒られますよね…
Android, iOSでも試してみたいんですが…手持ちのiPhoneで…う、Macがないな。
ログを少し詳細に出しました。再生バッファへの書き込み間隔は60msです。
Boot : QDateTime("2014-04-26 10:22:32.926 JST Qt::LocalTime") Assertion SUCCESS : brynhildr.cpp : LINE : 253 [control] Connected for control. [graphics] Connected for graphics. [sound] Connected for sound. [sound] stop : IdleState [timerExpired()] output: bytesFree(): 38400 [timerExpired()] output: periodSize(): 7680 [timerExpired()] : chunk (5) [sound Buffer] get(int len) : size = 11520 : len = 7680 [sound] stop : ActiveState [timerExpired()] : chunk (4) [sound Buffer] get(int len) : size = 3840 : len = 7680 [timerExpired()] output: bytesFree(): 23040 [timerExpired()] output: periodSize(): 7680 [timerExpired()] : chunk (3) [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound] stop : IdleState [timerExpired()] output: bytesFree(): 38400 [timerExpired()] output: periodSize(): 7680 [timerExpired()] : chunk (5) [sound Buffer] get(int len) : size = 13312 : len = 7680 [sound] stop : ActiveState [timerExpired()] : chunk (4) [sound Buffer] get(int len) : size = 5632 : len = 7680 [timerExpired()] output: bytesFree(): 30720 [timerExpired()] output: periodSize(): 7680 [timerExpired()] : chunk (4) [sound Buffer] get(int len) : size = 11520 : len = 7680 [timerExpired()] : chunk (3) [sound Buffer] get(int len) : size = 3840 : len = 7680 [timerExpired()] output: bytesFree(): 23040 [timerExpired()] output: periodSize(): 7680 [timerExpired()] : chunk (3) [sound Buffer] get(int len) : size = 15360 : len = 7680 [timerExpired()] : chunk (2)
bytesFree()とあるのが、書き込み時の再生バッファの空きサイズです。再生バッファ自体のサイズは38400バイトのようです。"bytesFree"でログをgrepしてみます。
$ grep bytesFree log_60.txt [timerExpired()] output: bytesFree(): 38400 [timerExpired()] output: bytesFree(): 23040 [timerExpired()] output: bytesFree(): 38400 [timerExpired()] output: bytesFree(): 30720 [timerExpired()] output: bytesFree(): 23040 [timerExpired()] output: bytesFree(): 23040 [timerExpired()] output: bytesFree(): 30720 [timerExpired()] output: bytesFree(): 38400 [timerExpired()] output: bytesFree(): 15360 [timerExpired()] output: bytesFree(): 23040 [timerExpired()] output: bytesFree(): 38400 [timerExpired()] output: bytesFree(): 23040 [timerExpired()] output: bytesFree(): 38400 [timerExpired()] output: bytesFree(): 15360 [timerExpired()] output: bytesFree(): 38400 [timerExpired()] output: bytesFree(): 30720 [timerExpired()] output: bytesFree(): 23040 [timerExpired()] output: bytesFree(): 38400
再生バッファがあまり効率よく埋められていないようです。空きサイズが38400バイトということはバッファが空だった(で、再生がストップしていた)ことを示します。
ここでQtのサンプルソースaudiooutput.cppを思い出しました。PullモードとPushモード…
現状の実装はサンプルソースでいうところの、定期的に自発的にデータを転送するPushモードです。Pullモードは、必要になった場合にデータがPullされるモードのようです。自発的にバッファを効率よく書き込むには通信速度、CPUの処理速度などを考慮する必要があり、難しそうです。それなら必要になった時点でPullしてもらうほうがいいのではないでしょうか?
最後のつもりだったのですが、条件によってはやっぱり音がぶつ切れになります( ̄▽ ̄)
再生バッファへの書き込みは20msごとに行っていますが、ログを表示させると…
Boot : QDateTime("2014-04-26 09:38:58.542 JST Qt::LocalTime") Assertion SUCCESS : brynhildr.cpp : LINE : 253 [control] Connected for control. [graphics] Connected for graphics. [sound] Connected for sound. [sound] stop : IdleState [sound Buffer] get(int len) : size = 13312 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 5632 : len = 7680 [sound Buffer] get(int len) : size = 5888 : len = 7680 [sound Buffer] get(int len) : size = 3840 : len = 7680 [sound Buffer] get(int len) : size = 5632 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 9728 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 2048 : len = 7680 [sound Buffer] get(int len) : size = 5632 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 3840 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 11520 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 3840 : len = 7680 [sound Buffer] get(int len) : size = 3840 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 5888 : len = 7680 [sound] stop : ActiveState [sound] stop : IdleState [sound Buffer] get(int len) : size = 5632 : len = 7680 [sound] stop : ActiveState [sound] stop : IdleState [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 9728 : len = 7680
再生バッファへの書き込み時のバッファに置かれたデータサイズがsize、要求されているサイズがlenで出力されています。ことごとく要求されているデータサイズを下回っており、データ量が不足して再生処理が停止している(IdleStateに状態が遷移している)ようです。ActiveになったりIdleになったりを繰り返して、結果、音がぶつ切れになっているようです。
書き込みタイミングを40msに広げてみます。
Boot : QDateTime("2014-04-26 09:47:30.709 JST Qt::LocalTime") Assertion SUCCESS : brynhildr.cpp : LINE : 253 [control] Connected for control. [graphics] Connected for graphics. [sound] Connected for sound. [sound] stop : IdleState [sound Buffer] get(int len) : size = 23040 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 15360 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound Buffer] get(int len) : size = 11520 : len = 7680 [sound Buffer] get(int len) : size = 3840 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound Buffer] get(int len) : size = 13312 : len = 7680 [sound Buffer] get(int len) : size = 5632 : len = 7680 [sound Buffer] get(int len) : size = 2048 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 5632 : len = 7680 [sound] stop : ActiveState [sound] stop : IdleState [sound Buffer] get(int len) : size = 9728 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 2048 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 9472 : len = 7680 [sound Buffer] get(int len) : size = 1792 : len = 7680 [sound Buffer] get(int len) : size = 5888 : len = 7680 [sound Buffer] get(int len) : size = 9472 : len = 7680 [sound Buffer] get(int len) : size = 1792 : len = 7680 [sound Buffer] get(int len) : size = 5888 : len = 7680 [sound Buffer] get(int len) : size = 9472 : len = 7680 [sound Buffer] get(int len) : size = 1792 : len = 7680 [sound Buffer] get(int len) : size = 9728 : len = 7680 [sound Buffer] get(int len) : size = 2048 : len = 7680 [sound Buffer] get(int len) : size = 5632 : len = 7680 [sound Buffer] get(int len) : size = 2048 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 20992 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 13312 : len = 7680
ややリングバッファが満たされるようになり、IdleStateに落ちることも少なくはなっているようです。が、やはり再生バッファがemptyとなり、しばしばIdleStateに落ちているようです。これでは音がブツブツと切れます。理想的にはIdleStateに落ちることなく、バッファを書き込んでいきたいのです。
さらに60msに広げてみます。
Boot : QDateTime("2014-04-26 10:05:16.384 JST Qt::LocalTime") Assertion SUCCESS : brynhildr.cpp : LINE : 253 [control] Connected for control. [graphics] Connected for graphics. [sound] Connected for sound. [sound] stop : IdleState [sound Buffer] get(int len) : size = 20992 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 13312 : len = 7680 [sound Buffer] get(int len) : size = 5632 : len = 7680 [sound Buffer] get(int len) : size = 11520 : len = 7680 [sound Buffer] get(int len) : size = 3840 : len = 7680 [sound Buffer] get(int len) : size = 9728 : len = 7680 [sound Buffer] get(int len) : size = 2048 : len = 7680 [sound Buffer] get(int len) : size = 15360 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound Buffer] get(int len) : size = 11520 : len = 7680 [sound Buffer] get(int len) : size = 3840 : len = 7680 [sound Buffer] get(int len) : size = 15360 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound Buffer] get(int len) : size = 15360 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound Buffer] get(int len) : size = 15360 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound Buffer] get(int len) : size = 9472 : len = 7680 [sound Buffer] get(int len) : size = 1792 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 13568 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 5888 : len = 7680 [sound Buffer] get(int len) : size = 3840 : len = 7680 [sound Buffer] get(int len) : size = 11520 : len = 7680 [sound Buffer] get(int len) : size = 3840 : len = 7680 [sound Buffer] get(int len) : size = 11520 : len = 7680 [sound Buffer] get(int len) : size = 3840 : len = 7680 [sound Buffer] get(int len) : size = 5632 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 19200 : len = 7680
少し聴きやすくなりました。IdleStateになる状況も少なくなりました。
さらに80msに広げてみます。
Boot : QDateTime("2014-04-26 10:08:15.637 JST Qt::LocalTime") Assertion SUCCESS : brynhildr.cpp : LINE : 253 [control] Connected for control. [graphics] Connected for graphics. [sound] Connected for sound. [sound] stop : IdleState [sound Buffer] get(int len) : size = 26880 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 19200 : len = 7680 [sound Buffer] get(int len) : size = 11520 : len = 7680 [sound Buffer] get(int len) : size = 3840 : len = 7680 [sound Buffer] get(int len) : size = 15360 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 20992 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 13312 : len = 7680 [sound Buffer] get(int len) : size = 5632 : len = 7680 [sound Buffer] get(int len) : size = 17408 : len = 7680 [sound Buffer] get(int len) : size = 9728 : len = 7680 [sound Buffer] get(int len) : size = 2048 : len = 7680 [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 13312 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 5632 : len = 7680 [sound Buffer] get(int len) : size = 9728 : len = 7680 [sound Buffer] get(int len) : size = 2048 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 15360 : len = 7680 [sound] stop : ActiveState [sound Buffer] get(int len) : size = 7680 : len = 7680 [sound] stop : IdleState [sound Buffer] get(int len) : size = 17152 : len = 7680 [sound] stop : ActiveState
う、IdleStateが増えました。単純に間隔を開ければデータは沢山バッファリングできますが、今度は再生バッファが空になりやすくなりました。これって通信速度にも影響されるのではないでしょうか…
まず、音がずれる件ですが、これは実装の処理の書き方が悪かったようです。送られてくるPCMデータをそのまま再生バッファに書き込むことで大きな問題とはならないようです。
次にバッサリ落ちる件ですが、これはバッファが境界をまたがる時に不正なメモリアクセスを起こすという問題でした。テストのためリングバッファのサイズを1MBにしていたので、少し普通に再生した後落ちたようです。
gdbで追っかけると関数memcpyで落ちていました。サイズ計算に間違いがあり、バッファサイズよりも1バイト超えてメモリをアクセスしていました。よく確かめたつもりだったのですが(^_^;
このようにして、ようやく音が普通に聞こえるようになったのでした。しかし、こうして修正したはずのソースファイルを誤ってなくしてしまい、嫌な汗を書くことになるのですが…
WindowsとUbuntuでホームを共通化していたら、とんでもないことをしてしまいました。
これまで Windows 8.1 上の Cygwin x64 でホームディレクトリを整理していました。この度Ubuntu 14.04 LTSへの移行に伴い、この際 FreeBSD 10.0 ともホームディレクトリを共通化しようとしたのですが、そこで重大なミスを犯しました。
Ubuntu 13.10 上でいろいろ整理して、何気なくWindows上にtar.xzアーカイブファイルで転送し、上書きしてしまったのです。Qt Brynhildr(仮称)のソースは同期しておらず、gitリポジトリはローカルにしか存在していませんでした。Ubuntu 13.10上のソースは少し古いものだったのです。
つまり…古いソースファイルで上書きし、さらにgitリポジトリ自体も上書きしてしまったのです。いろいろなマシンのホームもすでに置き換え済みでした。恥ずかしいことに、すべてのファイルはgitで戻せるのでホームディレクトリは最新だけあればよいと思っていたのです。しかし、間違った認識でした、gitリポジトリ自体の整合性が崩れたのです…
#同時にMew-6.6rc3のメールホルダーの整合性も怪しくなりました。
久しぶりに、いやな汗が出ました。一日分の作業量でしたが、2時間位の修正がなくなってしまったのです。
早めにGitHubへ移行しようと決心した出来事でした。少なくともホーム以外にgitリポジトリを移そうと思います。
心臓に悪いです。まるで、重要なディレクトリで rm -rf * を実行した時のような。
void Brynhildr::timerExpired() { if (outputLog) cout << "[timer] timerExpired()." << endl << flush; if (audioOutput != 0 && audioOutput->state() != QAudio::StoppedState){ int chunks = audioOutput->bytesFree()/audioOutput->periodSize(); while (chunks){ qint64 len = soundBuffer->size(audioOutput->periodSize()); if (len != 0) output->write(soundBuffer->get(audioOutput->periodSize()), len); if (len != audioOutput->periodSize()) break; --chunks; } } }
定期的にPCMデータを再生用バッファに書き込みます。
この手順はサンプルソースaudiooutput.cppの関数pullTimerExpired()に集約されています。
QAudioOutputのインスタンスが生成されていることと状態がStoppedStateでないことを確認し、その場合のみ再生バッファへの書き込みを行います。
bytesFree()は現在の再生バッファのトータルの空きサイズ(バイト)を返します。periodSize()は一度の書き込みで必要なデータサイズ(バイト)を返します。一度にperiodSize()分だけ書き込んでトータルbytesFree()サイズ分まで書き込めるので、最大bytesFree()/periodSize()回だけ書き込みを行います。この回数がchunksの値ですね。
void AudioTest::pullTimerExpired() { if (m_audioOutput && m_audioOutput->state() != QAudio::StoppedState) { int chunks = m_audioOutput->bytesFree()/m_audioOutput->periodSize(); while (chunks) { const qint64 len = m_generator->read(m_buffer.data(), m_audioOutput->periodSize()); if (len) m_output->write(m_buffer.data(), len); if (len != m_audioOutput->periodSize()) break; --chunks; } } }
と、ここまで調べたのですが、大チョンボでございました。
別スレッドが上書きしていると予想したのですが、これが間違っていました…
BrynhildrサーバがPCMデータを送って来る時にPCMデータの前にPCMデータに関する情報を事前に送ってくるのですが、これの解釈が間違っていました。ヘッダーの中にサンプルレート値の情報があり、サンプルレート値が入ってくることもあれば、この値が0の場合も存在します。これまで0の場合は無効データだろうと勝手に判断して直後に送られてきたデータはリングバッファに入れていなかったのです。実はサンプルレートが0の場合は単にPCMデータの設定情報は前送ったのと変わらないから、再設定する必要はないよということらしいです。
作者の方に確認すれば、済んだ話だったみたいです。はぁー…全く。
受信データのファイルへの出力が以下です。
リングバッファ実装版の受信データのファイルへの出力が以下です。
私の耳では同じに聴こえます(^_^; Stirlingによるバイナリ比較でも差異がないことを確認しました。
スレッドの制御も必要なかったみたいですが、念のためQReadWriteLockによる排他制御は実装しておくことにします。
クラスのプライベートメンバに
QReadWriteLock lock;
を追加します。QReadWriteLockヘッダのインクルードも必要です。
で、バッファへの書き込み時の前でlock.lockForWrite()を呼び出し、書き込み直後にlock.unlock()を呼び出します。具体的なコードは以下の様な感じです。
// lock
lock.lockForWrite();
writtenSize = soundBuffer->put(buffer, recievedSize);
// unlock
lock.unlock();
うーん、これでO.K.なのかなぁ…
とりあえず、リングバッファへのバッファリングはうまくいきそうです。今の所タイマーを使って定期的に再生用バッファへの書き込みを行っていないので、データの消費が行われず、結果バッファが溢れ、その溢れた時点でのエラー処理の暫定実装としてバッファの内容をファイルへの書き出すようにしています。
ドキュメントのサンプルソースの通りに、20ms~40msで関数を起動し、再生用バッファへの書き込みを実装します。これでPCMデータを定期的に消費していくので、バッファが溢れることはなくなるはずです。
ついに音が出るのでしょうか。その4へつづきます…
Qt4の本を読むとデータやコードのスレッド間同期をとるためのクラスがQtに用意されているようです。最新のC++ではライブラリでサポートされているのですが、Qtでの実装を使ったほうが今はよさそうです。
QMutex, QReadWriteLock, QSemaphore, QWaitConditionなどのクラスらしいのですが、Qt5ではそのままなのか分からないので、いつものようにQt Assistantで確認しなければなりません。データのリード/ライトに関しての同期ならQReadWriteLockを用いるのがよいかもしれません、と書いてあります。
Qt Assistantで調べてみると、QReadLocker, QWriteLockerなどもあるようです。読み込み時/書き込み時アクセスのロックを行うものでしょうか。今回は読み込みも書き込みも排他的に行う必要があるのでQReadWriteLockが適切だと思われます。
それにしても、Qt Assistantはドキュメントが充実していますね。英語なのですが、分かりにくいより日本語よりは遥かにマシです。いろいろサンプルもあるし。「Synchronizing Threads」というドキュメントは最初にキチンと読んでおくと後々役に立ちそうです。
「入門Qt4プログラミング」の18章 スレッドの同期のところに書いてある共有循環バッファの記述はまさに今回の場合に当てはまります。参考になるかもしれません。
その3へつづく…
googleからリモートデスクトップソフトがリリースされました。
Androidがクライアントとして使えるとのことで話題です。遅れてはいますがiOS版アプリも開発中とのことです。モバイルデバイスとしてはMac、PC、iPhone,、iPad、Android、Kindle Fire HDXとのこと。PCって多分Linuxは入らないのでしょうか…Ubuntu 14.04も出たことだし、あとBSD(FreeBSD)も正式にサポートしてくれたらいいのに、と思うのですが(^_^;
ま、こちらはお気楽な個人プロジェクトなので、いいんですが…操作系とか機能とか参考にできるかもしれませんが…サムスンみたいに訴えられるといやなので…Appleじゃないから意外とO.K.だったりするかもしれないけど。
サーバとしてはWindowsだけでしょうか?iPadがサーバになったっていいですよね(^_^; Macもサーバにしたいな。後で少し調べてみないと。
音が出るまで、のことを少し書いておきます。
まず、バッファリングの仕組みを入れます。バッファは一般的に利用されるリングバッファを実装します。
といってもそんな大相なものではありませんが…
通信で送られてきたPCMデータをバッファに順次置いてゆきます。同時にタイマーを仕掛けて、定期的にサウンドの再生バッファにデータを書き込みます。書き込んだデータはバッファから取り出されます。実際には取り出されたデータが無効化されるだけですが。
そのようにしてバッファに保持されるPCMデータは、通信スレッドで蓄えられ、タイマー起動された関数によって取り出され消費されます。転送されるデータ量と再生バッファで消費されるデータ量はまちまちなのでバッファ長は伸びたり、縮んだりします。おおよそデータの溢れが起こらないであろうサイズを適当に指定しています。
リングバッファを実現するクラスを適当にでっち上げ(実装し)て、テストしてみました。
テストのため、PCMデータをファイルに順次書き込んでいく仕組みとリングバッファがいっぱいになるまでバッファリングし、いっぱいになったら一気にファイルに書き込む仕組みの2つを入れてテストしました。
リングバッファのサイズを1MBに設定し、バッファが一杯になった時点でファイルへ書き込み、その後プログラムを終了するようにしました。
で、以前書いたsoxコマンドを利用したwavファイルへの変換を行い、2つのPCMファイルを聴き比べました。
ところが、ファイルを順次書き込んでいったファイルは綺麗に聞こえるのですが、バッファリングによって貯めたPCMデータの方のwavファイルは妙に再生時間が短く、再生スピードが早いのです。
バッファリングしたデータは以下です。
#順次ファイルに書きだしたファイルについてもアップロードしようとしたら1MBの制限に引っかかってしまいました。
#soxコマンドでビットレートの低いMP3に変換できればアップロードできるかも、と思いましたがcygwinで配布しているsoxは当然ながら著作権保護されているMP3はサポートされていません。自分でsoxを作りなおせばいいのですが今回はそこまではしないということで。
#soxの日本語マニュアルはhttp://www.hcn.zaq.ne.jp/___/unix/sox-ja.htmlで公開されています。
soxでトリムしたファイル保存版PCMデータのwavファイルは以下です。
イメージとしては全く同じPCMデータであるはずなのですが、バイナリエディタで2つのPCM生データファイルを比較すると結構差異が出ています。今回はバイナリエディタStirlingを利用して、比較しました。
結構、値としては惜しい感じですが微妙にバイナリ値が異なります…うーん、これは…多分リングバッファへのコピーー時に別スレッドが上書きしている…みたいです。
リングバッファへのアクセス(書き込み、読み込み)は、1度に1スレッドのみに限定する必要があります。でないと別スレッドが上書きしていまい、結果PCMデータが適切に保持できないことになります。
さて、C++でスレッド制御するにはどうするんでしょうか…Javaなら言語仕様的に制御できるようになっているのですが…そこを調べなくてはなりませんでした。
その2へつづく…
メモリリークを調べるために、まずネットワーク通信のみの最低限の動作状態にして調べます。具体的にはデスクトップの表示とサウンドの再生を無効化して通信のみ行うように修正し、メモリの使用状況を調べました。
デバッグビルドした実行モジュールでは1秒に0.1MBずつメモリ消費が増えていきます。5分ほど眺めましたが10MBほど増加しました。大体においてmalloc()とfree()、newとdeleteが適切に対応できていない場合にリークするのですが、現在のソースコードではmalloc(), free()は使っておらず、new, deleteの対応も片手で数えられるくらいしかないため、問題ないように見えます。
Windows上ではすぐによいツールが見つからなかったので、Ubuntu x64 上で解析を試みることにしました。
まず、Ubuntu x64 にソースコードを転送して、ビルドできるようにします。後々のため"-g"オプションでコンパイルされるようにMakefileのCXXFLAGSに"-g"を追加してビルドします。ビルドしたバイナリを実行し、新規に端末を立ちあげて"topコマンド"を実行し、メモリ使用量の様子を観察します。
Windows上のようにメモリ使用量は増えません。おおよそ一定のように見えます。本体のコード自体にリークはなさそうです。
次にLinux上でメモリリークの検出によく使われるvalgrindというツールを用いて確認してみました。
valgrindは"sudo apt-get install valgrind"でインストールしました。100MB以上あります…
valgrind --leak-check=yes ./qtbrynhildr >& valgrind.log
ログ・ファイル valgrind.log を調べましたが、リーク自体はなさそうです。
1つだけ指摘がありました。
"new char []"で確保したメモリを"delete"で削除しているという指摘です。
これは"delete []"で削除しなくてはなりません。とりあえずそこは"delete []"に直しました。
他に指摘はなく、特にリークしている部分は本体のソースコードにはなさそうに見えます。
もう一度Windowsへ戻り、今度はリリース用にビルドしたバイナリを実行します。やはりこちらも大体一秒に0.1MB分だけメモリ使用量が増えていきます。時々急に使用量が減ることがありますが、これは常駐しているメモリ回収のフリーソフトウェアが回収しているためだと思われます。とりあえず、Windows版のQtまわりの実装のためかも、と勝手な判断をして、とりあえずペンディングとしました…
ストレステストしてみました。
タスクモニタを見ながら、しばらく動作させます。微妙にメモリ使用量が増えていきます。
標準画質だと平均的には22FPSの速度(Brynhildrだと表示的には22FPS付近)が出ているのですが、なぜかスローダウンする場合もあるようです。なぜだろう…クライアントだけの問題か否か。
時間とともに速度が低下する事例はBrynhildrでも報告されているようですが、再現しないようです。
メモリリークのチェックと描画速度の低下について少し調べてみる必要がありそうです。
30分の動作で40MBから112MBの増加はひどすぎます…Qtが原因か本体が原因か、ですね。
絵と音。
localhost(VMWare:WindowsXP Professional SP3)、最高画質モードで 8 FPSくらい。
相変わらず入力デバイス未対応。紙芝居モード風。
ソースコードは1600行程度。
通信で受信したPCMデータをバッファリングし、定期的に再生バッファに書き込むという方針は分かりました。
とりあえず、一定時間ごとに関数を起動するタイマー(QTimer)を追加します。
とりあえずなので、一秒(1000ms)ごとにtimeoutするタイマーを組み込みました。基本的にはサンプルソースautiooutput.{h|c}に書かれているままです。クラスメンバーを"m_xxxxxx"と記述するのはあまり好みではないのでそこは変えましたが(^_^;
以下ログです。ログはcoutで出力し、毎回flushしています。Qtのデバッグ出力(qDebug())もありますが、とりあえずcoutを使っています。
Boot : QDateTime("2014-04-13 09:19:07.126 JST Qt::LocalTime")
Assertion SUCCESS : brynhildr.cpp : LINE : 215
[control] Connected for control.
[graphics] Connected for graphics.
[sound] Connected for sound.
[sound] sample rate -> 44100
[sound] stop : IdleState
[sound] start
[timer] timer start
[sound] output: bytesFree(): 35280
[sound] output: periodSize(): 7056
[sound] output: recievedSize: 3584
[sound] output: totalRecievedSize: 3584
[sound] stop : ActiveState
[sound] stop : IdleState
[sound] sample rate -> 44100
[sound] output: bytesFree(): 35280
[sound] output: periodSize(): 7056
[sound] output: recievedSize: 8960
[sound] output: totalRecievedSize: 8960
[sound] stop : ActiveState
[sound] sample rate -> 44100
[sound] output: bytesFree(): 21168
[sound] output: periodSize(): 7056
[sound] output: recievedSize: 1792
[sound] output: totalRecievedSize: 1792
[sound] sample rate -> 44100
[sound] output: bytesFree(): 14112
[sound] output: periodSize(): 7056
[sound] output: recievedSize: 6912
[sound] output: totalRecievedSize: 6912
[sound] stop : IdleState
[sound] sample rate -> 44100
[sound] output: bytesFree(): 35280
[sound] output: periodSize(): 7056
[sound] output: recievedSize: 5376
[sound] output: totalRecievedSize: 5376
[sound] stop : ActiveState
[sound] stop : IdleState
[sound] sample rate -> 44100
[sound] output: bytesFree(): 35280
[sound] output: periodSize(): 7056
[sound] output: recievedSize: 6912
[sound] output: totalRecievedSize: 6912
[sound] stop : ActiveState
[sound] stop : IdleState
[sound] sample rate -> 44100
[sound] output: bytesFree(): 35280
[sound] output: periodSize(): 7056
[sound] output: recievedSize: 6912
[sound] output: totalRecievedSize: 6912
[sound] stop : ActiveState
[sound] stop : IdleState
[timer] timerExpired().
約1秒でタイムアウトし、関数timerExpired()が呼ばれているようです。
次は、PCMデータのリングバッファへの格納です。クラスを作ったほうがよさそうです。
全然関係ありませんが、Qtで提供されているサンプルソースにはコメントがありませんね…
バッファアンダーランでございます。#久しぶりにソースコードを眺めました
Nokiaのエンジニアさんが回答しています。
http://comments.gmane.org/gmane.comp.lib.qt.general/32710
20ms~40msのタイマーを使って、定期的に再生用バッファを満たすようにすればよい、とのことらしいです。
ネットワーク経由で、細々とPCMデータが送られてくるので、それをバッファリングしておき、タイマーで定期的に起動した関数内部で再生用のバッファの空きを調べて、チャンク単位で書き込んでください、と。
FPSが気になっていましたが、これはサーバに依存するので、そんなに改善できるわけではなさそうですね。
ログでは 8 FPS…大体 10 FPS前後っぽいです。無線LAN 回線速度70Mbpsくらいの場合ですが。
[現在のログ出力]
Boot : QDateTime("2014-04-08 13:32:40.073 JST Qt::LocalTime")
Assertion SUCCESS : brynhildr.cpp : LINE : 212
[control] Connected for control.
[graphics] Connected for graphics.
[sound] Connected for sound.
[sound] sample rate -> 48000
[sound] stop : IdleState
[sound] start
[sound] output: bytesFree(): 38400
[sound] output: periodSize(): 7680
[sound] output: recievedSize: 5632
[sound] output: totalRecievedSize: 5632
[sound] stop : ActiveState
[sound] stop : IdleState
[sound] sample rate -> 48000
[sound] output: bytesFree(): 38400
[sound] output: periodSize(): 7680
[sound] output: recievedSize: 5632
[sound] output: totalRecievedSize: 5632
[sound] stop : ActiveState
[sound] stop : IdleState
[sound] sample rate -> 48000
[sound] output: bytesFree(): 38400
[sound] output: periodSize(): 7680
[sound] output: recievedSize: 5888
[sound] output: totalRecievedSize: 5888
[sound] stop : ActiveState
:
:
[sound] output: bytesFree(): 23040
[sound] output: periodSize(): 7680
[sound] output: recievedSize: 7680
[sound] output: totalRecievedSize: 7680
[control] close().
[control] closeConnectionByServer_control().
[graphics] close().
[graphics] closeConnectionByServer_graphics().
[sound] close().
[sound] : closeConnectionByServer_sound().
Shutdown : QDateTime("2014-04-08 13:32:50.485 JST Qt::LocalTime")
Total Frame Counter : 80
FPS : 8 fps
日 | 月 | 火 | 水 | 木 | 金 | 土 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
最近のコメント