2017年9月17日日曜日

C#でエンコーディング変換

 基本的にC#はエンコーディングの事を考えなくても良いのだけど、今回諸事情でUInt16で表現された文字を別のエンコードにする必要があったので、試してみた。

 今回はShift JISとUnicodeの変換を行った。

static UInt16 ShiftJis2Unicode(UInt16 ShiftJisChar)
{
 byte[] buff2 = Encoding.Unicode.GetBytes(
  new[] {
   Encoding.GetEncoding("Shift_JIS").GetChars(
    new [] {
     (byte)(ShiftJisChar / 256),
     (byte)(ShiftJisChar % 256),
    }
   )[0]
  });

 return ((UInt16)(buff2[0] | buff2[1] << 8));
}

static UInt16 Unicode2ShiftJis(UInt16 UnicodeChar)
{
 byte[] buff2 = Encoding.GetEncoding("Shift_JIS").GetBytes(
  new[] {
   Encoding.Unicode.GetChars(
    new[] {
     (byte)(UnicodeChar % 256),
     (byte)(UnicodeChar / 256),
    }
   )[0]
  });

 return ((UInt16)(buff2[0] << 8 | buff2[1]));
}

 Shift JISはASCIIと互換性があり、また半角カナも1バイトで表現できる。が、今回はマルチバイト文字しか考えていないので、シングルバイト文字を入れると問題が起きるはず。
 Unicodeは文字にかかわらず2バイトで表現するので、たぶん動くはず。

 UnicodeとUTF-8とかの変換は、また別の問題なので今回は扱わない。

 ちなみにC#のcharはUnicodeらしく、intに入れるとUnicodeのインデックスになる。intをcharにキャストするとUnicodeで文字化される。今回はEncoding.Unicode.GetCharsとか使ったけど、Unicode対ならcharのキャストでも良いかも。

 他のエンコーディングとの変換も同じようにすれば動くはずだけど、それぞれバイトオーダーや固定長・可変長みたいなバリエーションがあるので、ちょっと面倒。

 今回は文字単位での変換だったけど、文字列の変換だともっと簡単にできるみたいね(未確認)。

2017年9月14日木曜日

PNGファイルシグネチャ

 ソース:PNG 仕様書:原理 12.12.PNGファイルシグネチャ
 ソースの文章を理解できるなら、以下の文章は読まなくていい。

 
 PNGファイルには最初の1バイトが0x89で、次の7バイトが"PNG\r\n\x1A\n"となっている。これの理由について。

 まず最初の0x89だが、これは2進数だと10001001になる。当時、上位1bitを捨てて、7bitで転送するシステムが有ったため、これを通したPNGデータは最初の1バイトが0x09となり、データが破損していることが確認できる。
 また、ASCIIコードは0x00から0x7Fの範囲を使用するので、ソフトウェアによっては明らかにASCIIコードでないファイルとして、テキストデータと誤認されることを防ぐことができる。とはいえ、他の文字コードでは1文字目が何らかの文字として誤認されることもある。例えば、Shift JISでは"\x89P"の一連の文字(0x8950)は’臼’(ウス)の文字が割り当てられている。そのため、日本向けWindowsのメモ帳でPNGファイルを開くと、先頭は"臼NG"で始まる。

 次の"PNG"の3文字は、誤ってPNGファイルをテキストエディタで開いてしまった際に、"PNG"の文字列を表示することにより、PNGデータだと人間に通知することができる。ただし、上記の通り、システムのエンコードによっては正しくPNGと表示されない場合もある。

 次の"\r\n"は、ファイルシステムによって"\r\n"を"\n"に変換されたことを検出することができる。またその後の"\n"も、ファイルシステムによって"\n"が"\r\n"に変換されたことを検出することができる。
 これは、OSによって異なる改行コードの違いをファイルシステムで吸収する際に発生する問題を検出することができる。例えば、C言語のfopenでテキストファイルとして開くと、\r\nと\nの変換が行われる(バイナリファイルとして開けば変換は行われない)。

 その間にある、'\x1A'は、一部のファイルシステムではEnd of Fileを意味し、この文字を検出したシステム(テキストエディタ)はそれ以降のファイルの読み込みを終了する場合がある。

 PNGのファイルシグネチャはこの8バイトだが、場合によっては続けて4バイト程度を読み出す場合もある。
 この4バイトはIHDRチャンクのサイズで、32bitのネットワークバイトオーダーで13が記録されている。これは0x00 0x00 0x00 0x0Dの4バイトだが、システムによってはヌル文字(0x00)を省略する場合があり、その場合は00 00 00 0Dを正しく読めないため、データが破損していることが確認できる。


 以上、PNGのファイルシグネチャの話でした。
 古いフォーマットを見てると当時のシステムの特徴とか出てきて面白いよね。

2017年9月13日水曜日

PNG

 C#で64bit(16bit x 4ch)のPNGを吐き出すライブラリを作っていました。といっても、まだライブラリと言うほどまとまってはいませんが。

 気付いた点とか。

・フィルタについて。
 フィルタは1バイト毎に行われる。1色16bitでも、フィルタは1バイト単位で行われる。なので8bitでも16bitでも同じ関数を使いまわすことができる(1行の幅を、画素数ではなく、バイト数で持たせる)。

追記:2017/09/15
 フィルタの計算量は、None<Sub<Up<Average<Paethという感じか。Noneは計算量ゼロ。SubはUpより少し少ない(ソースコード量とトレードオフ)。
 フィルタは各行毎に設定されるので、forで行を回し、その中でフィルタに応じてforで列を回すことになる。
 Subはfor[y]の中で直接for[x-1]を回せるので、計算量が最小(Noneを除くと)。
 Upはfor[y]の中でy==0をチェックし、trueならNoneとして扱う。それ以外はfor[x]で回すので、ifがあること、1列多く回すこと、等からSubよりも計算量が若干多い。
 Averageはfor[y] for[x]の中で2個のifがあり、また四則演算も多いので遅くなる。ただ、四則演算を除けば、分岐予測が間違わない限りはかなり早いと思う。分岐が必用なのはx==0とy==0のときだけなので、これもソースコード量とトレードオフかな。
 Paethもfor[y] for[x]のループだが、中にifが6個ある。内3個はx==0、y==0、x==y==0なので、予測分岐がミスらなければ早く、ソースコード量とトレードオフになる。一方、残りの3個はピクセル値によって変化するので、それなりの確率で予測が外れることになる(if-else if-else ifなので、1回目で通ればハザードは少なくて済む。が、コレばっかりは画像次第)。
 予測分岐については、CPUとかコンパイラの判断になるだろうから、こればっかりはどうしようもない気がする。

 フィルタリングは、エンコードは減算、デコードは加算で、計算式自体は共通。ただしループの向きが違うので、そこはエンコードとデコードで作り分ける必要がある。
 ループの中でテーブルに対して関数の加算・減算を行う、という流れが楽でいい。ただし計算内容で必要な情報が多いので、関数を呼ぶとスタック操作が発生する。計算式自体はかなり少ないので、インライン展開を期待するか、あるいはべた書きで同じコードを2箇所に書いてしまうか。

・圧縮について。
 PNGの圧縮はDeflateのラッパーであるzlibを使う。が、なぜかDeflateでもpaint.netで読めた(詳細未確認)。


 とりあえず、64bitRGBAのPNGを保存することができるようになった。IDATの圧縮がなんとなくわかってきたので、多倍長独自チャンクも似たような感じで作れそう。

 PNGにはテキストを保存できるチャンクがあるので、中間データとして使う際は、ここにファイルの内容を入れると良さそう。
 基本的にNULL文字終端っぽいので、PNGで使われるテキスト2組の後ろに、バイナリデータを入れられないかと考えている。ソフトウェアで中間データを扱う際に、続くデータチャンクの内容を知らせるためのモノ。
 バイナリデータを入れるのがマズければ、テキスト形式である程度種類を識別させて、続くデータチャンクにバイナリデータを入れても良い。

 以前に考えた時は、IDATが8bit、idBTが16bit、idCTが32bit、のようにしようと思っていたが、コレだとPNGのフォーマットにより8bitデータが1個しか持てない。これはちょっとまずいと思うので、もうちょっと違う形式にする必要がありそう。ま、idATとかでも良いのだけど。
 あと、データチャンクの符号をどうするか。ベクトルデータとかを扱うなら、当然符号なしはマズイわけで、とはいえすべてを符号ありとして扱うと画像データを扱う時にマズイわけで。3文字目の大文字小文字で分けてもいいけど、いちおう予約されてる以上はいじらずに置いたほうが良い気がする。
 例えばidATがu8bit、idBTがs8bit、idCTがu16bit、idDTがs16bit、みたいな感じだとどうだろうか。この形式でも最大65536bitまでの値を持てるから、30年先くらいは安心して使えると思う。


 とりあえず、中間データをPNGに埋め込むライブラリは、PNG画像に中間データを注入する、あるいは中間データを抽出するような動きになると思う。もしくは、画像はBitmapかImageにして扱うとか。画像処理ライブラリまで作り込むのは面倒なので。
 ま、そのあたりは追々。

追記:2017/09/14
 よく考えれば、というか、普通に考えれば、PNGパケットからデータを抜き出す際に、画像を別に取り出す必要性は皆無なんだよな。だってC#のImageなりBitmapで開けば画像は取り出せる。ということは、PNGパケットからデータを取り出す部分と、PNGパケットにデータを埋め込む処理を作ればいい。

C#でDeflateをMemoryStreamで

 C#でMemoryStreamを使って(ファイル操作をせずに)Deflateの圧縮をしようと思ったら、結構穴にハマったのでメモ。

byte[] src = new byte[1024],dst;

for(int i = 0; i < src.Length; i++)
{
 src[i] = (byte)(i % 256);
}

using (MemoryStream ms = new MemoryStream())
using (DeflateStream ds = new DeflateStream(ms, CompressionMode.Compress, true))
{
 ds.Write(src, 0, src.Length);
 ds.Dispose();
 ms.Position = 0;

 dst = new byte[ms.Length];
 ms.Read(dst, 0, dst.Length);
}

src = dst;

using (MemoryStream msSrc = new MemoryStream())
using (MemoryStream msDst = new MemoryStream())
using (DeflateStream ds = new DeflateStream(msSrc, CompressionMode.Decompress))
{
 msSrc.Write(src, 0, src.Length);
 msSrc.Position = 0;

 ds.CopyTo(msDst);
 msDst.Position = 0;

 dst = new byte[msDst.Length];
 msDst.Read(dst, 0, dst.Length);
}

 圧縮と展開、両方のソース。

 圧縮はCompressionMode.Compressで行う。
 DeflateStreamのコンストラクタで第3引数を省略するとfalseになるが、この場合は圧縮が終わった時点で第1引数に指定したStreamをCloseする。この動作は、FileStreamを使ったときには問題にならないが、MemoryStreamを使った場合は、あとから読めなくなる。なので、trueを指定してStreamがCloseされないようにする。
 また、DeflateStreamは、StreamがCloseされた際に圧縮を行うらしい。ということで、DeflateStream.Dispose()で強制的に閉じておく。
 そして、StreamのPositionはStreamの最後に移動しているので、あらかじめ0に戻しておく。
 最後に、Stream.Lengthを使って圧縮後の容量を確認し、Stream.Readでメモリに戻す。

 展開はCompressionMode.Decompressで行う。
 この際は読み込みStreamは閉じられてもかまわないので、第3引数は省略可能。
 まずはSrcStreamにデータをコピーする。DeflateStreamは入力StreamのPositionを戻さないので、自分でSrcStream.Position=0にしておく。
 そしてDeflateStream.CopyToでDstStreamにデータを移動する。CopyToやReadでDeflateStreamを読み出した際に、SrcStreamからの展開を行うらしい。
 またCopyToで読み出すと、Positionも移動するので、ここでも0にしておく。
 最後に、DstStreamのLengthで容量を確認し、Readでメモリに戻す。


 とりあえず、上記のサンプルデータだとちゃんと展開はできることを確認している。

 最初、入力に乱数を使っていて、全く圧縮されなく悩んだ。Deflateはハフマン符号圧縮と違い、過去のメッセージと同じデータを探してくるので、乱数のような同じメッセージが複数回出てこないようなデータに対しては圧縮率が極めて低い。
 今回のサンプルのような、同じメッセージが複数回出てくるようなデータに対しては、かなり圧縮率が良い。例えば、今回のサンプルデータは0x00から0xFFまでの256バイトのメッセージが4回出てくる。最初の1回は圧縮できないが、残りの3回は「nバイト前から長さlバイトと同じ」という情報があれば良いので、その分を圧縮できる。今回は1024バイトが280バイトまで圧縮された。

/*
 そう考えると、PNGの圧縮効率ってあんまり高くない気がする。ピクセルデータを直接ハフマン符号圧縮したほうが効率良さそう。当時のコンピュータの処理能力とか、そういう制限だったんだろうなぁ。
 PNGは浮動小数点演算とかいらない分、JPEGよりは組み込み向けといえるのか? どうだろう。JPEGはブロック単体で展開できるはずだけど、PNGを展開するには最大で圧縮窓全体をカバーするメモリ(=32KiB)が必要になりそう。PNGはフォーマットや圧縮方法にバリエーションが多いので、自前でライブラリ書くのは面倒だな。
 最近扱った組み込みの画像を扱う処理だと、マイコン内の処理が8bitIndexedだったので、BMP8bppIndexed限定にした。コレなら読み込みプログラムも凄まじく簡単に書ける。JPEGだとマイコン内で減色アルゴリズムを走らせる必要がある。PNGは8bppIndexedも保存できるけど、大抵のドローソフトでは8bppPNGでは保存できない気がする。結局、リソースの制限された組み込み系では8bppIndexedBMPが楽だと思う。読み込み早いし。プログラム楽だし。大抵のドローソフトで保存できるし。
*/

2017年9月12日火曜日

STM32/FreeRTOSでCPU使用率的な

 STM32のTIMを使ってFreeRTOSのCPU使用率的なパルスを出す。

 まずTIMxをPWM1で初期化する。プリスケーラは500kHzくらいになるように設定。カウンタは最大(65535)に設定する。1周約0.13秒くらいになる。パルス幅は1に設定し、約2usecくらいのパルスを作る。
 FreeRTOSのIdleHookでTIMx.CNT = 0を行う。これにより、IdleHookが呼ばれてから2usecのパルスが出力される。
 優先度が0より高いタスクが実行中の場合、IdleHookが呼ばれなくなるので、最後にIdleHookが呼ばれてから2usec以降はパルスがLowとなる。
 つまり、このGPIOを見ていればMCUがアイドル状態の割合を知ることができる。
 2usec未満の処理であれば出力されないが、168MHzのコアでは2usecに300命令くらいしか処理できないし、OSのタスクスケジューラでも結構な命令数が必用なので、2usecくらいの分解能でも充分だと思う。

 この方法だとTIM1個とGPIO1本が必用で、マイコン内でアイドル率を知ることができないが、結構信頼性の高いデータだと思う。
 あとはZEROPLUSのロジアナでデューティー比を見れれば最高なんだけど、LAP-Cではデューティー比は見れないらしい。デジオシだとデューティー比を表示できるけど、あんまり信頼性高くない。

 もう一本タイマを使えるなら、TIMxをIdleHookでクリアしてアイドル率のパルスを作り、TIMyをGatedして、10msec毎にTIMyのカウンタを確認して、という感じだろうか。これならマイコン内で使用率を知ることができる。それにGPIOも必要ない。

銃撃ち比べ

 なろうで現代兵器チート的なの読んでると、主人公が銃を持つ時に、安全管理は重要だぜっ!みたいなスタンスの割に、結構簡単に銃のセーフティーを外している描写が見受けられます。討伐系のクエスト受けて森に入って、物音がしたら安全装置解除、とか。おそらくは即応性を高めるための描写ですが、本当に事前にセーフティーを解除したら早くなるの? ってことで、試してみました。
 といっても、統計的には相当酷い計測方法なので、あくまで参考程度に。そもそも僕は武器操作マスターチートとか一切無い素人だしね。

 計測方法は、Stealth-Target ST17のタクティカルターゲットで65枚のターゲットを撃ち終わるまでの時間です。銃はM4を使用しました。弾倉は30発入りを3本使用し、エマージェンシーリロードを使用しています。


 今回はハイレディで1セットごとにセーフティを使用する撃ち方と、ハイレディで最初から最後までセーフティをセミにして撃つ方法を試しました。他に依託射撃(セーフ有りのみ)とローレディ(セーフ有りのみ)も計測しました。

 結果
1) ハイレディ/セーフティ使用:160秒/70発(5発ミス)
2) ハイレディ/セーフティ未使用:166秒/76発(11発ミス)
3) 依託/セーフティ使用:145秒/74発(9発ミス)
4) ローレディ/セーフティ使用:180秒(62枚で時間切れ)/68発(6発ミス)

 という感じでした(先頭の数字は射撃順)。
 セーフティを使うほうが早いですが、後に撃ったセーフティ未使用は、疲れてきたためにミスファイアが増えて時間が遅くなったものと思われます。
 三脚を使った依託射撃をまじめにやったのは初めてなので、調整が合わずにちょっと撃ちづらかったです。もうちょっと早くなるかも。でも銃の先端が固定されてしまうと、照準を変えるのに体の位置を動かす必要があるので、立射よりはかなり撃ちづらいです。
 ハイレディは銃の重心をあまり移動させることがないので、重量物を持ってる程度の疲れしかありません。一方、ローレディは銃を大きく移動させるため、サポートハンドがかなり疲れます。

 ということで、簡単に計測した限りでは、セーフティの有無では有意差は無い気がします。
 機会があれば、セーフティ未使用→セーフティ使用で時間を計測して、平均を取ったほうが良いかな、と思っています。


 今回は操作性のいいM4を使いましたが、89式小銃とかだとどうなるんだろう。アレも慣れてる人は操作が凄まじく早いので、89式の操作に慣れてる人に計測してもらいたいなぁ。セーフ・セミ・3点・フルのセレクタの写真が出てきている以上、セーフ・フル・3点・セミは遅い(あるいは同じ速度でも面倒)ってことなんでしょうけども。


 そもそも、何らかのイベントでセーフティを解除する、という描写は、「このキャラクタは安全装置を使用しているんだ」という描写をするのが最大の目的だと思います。なので即応性とかどうでもいいのでわ

 個人的には使う銃をコロコロ変える主人公は信用ならないわけですが、でも操作マスターチートあれば良いのかなー。いや、チーム内で弾薬の共通化すらできてない主人公はダメだッ。そういうこと考えてるから楽しく読めないんだろうなぁ。

メモ:STM32F4 USBブートローダーで"USB デバイスが認識されません"

 STM32F4のシステムメモリからUSB DFUで起動しようとした時に"USB デバイスが認識されません"「このコンピュータに最後に接続された USB デバイスが正しく機能していないため、Windows によって認識されません」という警告。
 この場合、STMのペリフェラルに外部がマスターになるバスが通信を行っていないか確認すること。
 僕の場合は、相手の都合を考えずにUSART3にPCからデータを送り続けるソフトを動かしていたのが原因。この場合、STMはUSART3からファームウェアを更新するモードに入ってしまい、USBのポートは初期化されるのにUSBデバイスとして動作しないため、PCから警告が出る。もちろん、USB DFUで更新することもできない。

 STMのアプリケーションノート AN2606(Rev31)によると、デバイスによって異なるが、USART1, USART2, USART3, I2C1, I2C2, I2C3, I2C4, SPI1, SPI2, SPI3, SPI4, CAN1, CAN2 からファームウェアの更新が行える(もっとあるかも)。
 ブートローダーはの優先順位は、USART > I2C > SPI > CAN > USB となっているらしい(デバイスによってペリフェラルの有無が異なったり、場合によっては優先度の違いがあるかも)。
 USARTでデータをブロードキャストするような使い方をする場合、ブートモードに入るときにはバスが切断されるような工夫をする、あるいは、ブートローダーで使用されないUSARTでデータを受信する、といった使い方が必用そう。

 CANでファームウェア書き換えってのは、車載とかを想定してるんだろうなぁ。どうやってデバイスを指定するんだろうか。あとどうやってブートローダを起動するんだろうか。ファームウェアはバス上に流せばいいとしても、マイコンがブートローダーを起動しなきゃいけないから、完全にリモートで操作するのは難しそう。CANから特定のデータを受け取ったらブートローダーモードでリブートする、みたいなプログラムが入ってるんだろうか。

 ということで、USB DFUが起動しない場合は回りの怪しいデータの流れを気にしてみよう、という話でした。
 気がつくまでに結構手間取ったので、危ないところはマーキング。