‘解決’ カテゴリーのアーカイブ

NSISから使える、起動中のプロセスを削除するプラグイン

常駐アプリケーションを作ったはいいが、アンインストールしようとしたときにプロセスが起動していると、削除できなくて困る。

というわけで、プロセスを殺す目的のプラグインDLLを作成。コンパイル済みファイルはエントリの最後に。

taskkillコマンド使えという話ですが、Windows XP Home Editionにはtaskkillコマンドが含まれていないので。

今回もさっくりC#で。C#.NETで作ってしまうと、そのままではNSISで使えないのだけど、.NETで作ったDLLをNSISのプラグインとして使う方法は以下のエントリを参照。

NSISで、C#で書かれた自作のDLLを使う : blog.loadlimit – digital matter –

ソースは以下。C#でクラスライブラリとしてプロジェクトを作成。プロセス名を指定すると、マッチしたプロセスを全部終了してくれます。

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace TaskKill
{
    public class TaskKill
    {
        [DllImport("Psapi.dll", SetLastError = true)]
        static extern bool EnumProcesses(
           [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4)] [In][Out] UInt32[] processIds,
             UInt32 arraySizeBytes,
             [MarshalAs(UnmanagedType.U4)] out UInt32 bytesCopied
          );

        [DllImport("kernel32.dll")]
        static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
           uint dwProcessId);

        [Flags]
        public enum ProcessAccessFlags : uint
        {
            All = 0x001F0FFF,
            Terminate = 0x00000001,
            CreateThread = 0x00000002,
            VMOperation = 0x00000008,
            VMRead = 0x00000010,
            VMWrite = 0x00000020,
            DupHandle = 0x00000040,
            SetInformation = 0x00000200,
            QueryInformation = 0x00000400,
            Synchronize = 0x00100000
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool CloseHandle(IntPtr hObject);

        [StructLayout(LayoutKind.Sequential)]
        public struct StringBuffer
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public string text;
        }

        [DllImport("psapi.dll")]
        private static extern bool EnumProcessModules(IntPtr hProcess, out IntPtr lphModule, int cb, out int lpcbNeeded);
        [DllImport("psapi.dll")]
        private static extern int GetModuleBaseName(IntPtr hProcess, IntPtr hModule, out StringBuffer lpBaseName, int nSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool TerminateProcess(IntPtr hProcess, int uExitCode);

        public static void KillByName(string pname)
        {

            UInt32 arraySize = 1024;
            UInt32 arrayBytesSize = arraySize * sizeof(UInt32);
            UInt32[] processIds = new UInt32[arraySize];
            UInt32 bytesCopied;

            bool success = EnumProcesses(processIds, arrayBytesSize, out bytesCopied);

            if (!success)
            {
                return;
            }
            if (0 == bytesCopied)
            {
                return;
            }

            UInt32 numIdsCopied = bytesCopied >> 2;

            if (0 != (bytesCopied & 3))
            {
                UInt32 partialDwordBytes = bytesCopied & 3;

                return;
            }

            for (UInt32 index = 0; index < numIdsCopied; index++)
            {
                IntPtr hProcess = OpenProcess(
                    ProcessAccessFlags.QueryInformation | ProcessAccessFlags.VMRead | ProcessAccessFlags.Terminate,
                    false,processIds[index]);
                if (hProcess.ToInt32() != 0)
                {
                    IntPtr hMod = IntPtr.Zero;
                    int cbNeeded;
                    StringBuffer szProcessName;
                    szProcessName.text = "<unknown>";

                    if (EnumProcessModules(hProcess, out hMod, Marshal.SizeOf(hMod), out cbNeeded))
                    {
                        GetModuleBaseName(hProcess, hMod, out szProcessName, Marshal.SizeOf(szProcessName));
                    }

                    if (szProcessName.text == pname)
                    {
                        TerminateProcess(hProcess, -1);
                    }

                    CloseHandle(hProcess);
                }
            }

        }

    }
}

で、これをNSISの配布ファイルに含めておいて、

; アンインストーラ
Section "Uninstall"
  ; アンインストールで使用するプラグインの準備
  InitPluginsDir
  SetOutPath $PLUGINSDIR
  File "TaskKill.dll"

  ; hogehoge.exeが起動していたら、それを落とす
  CLR::Call /NOUNLOAD "TaskKill.dll" "TaskKill.TaskKill" "KillByName" 1 "hogehoge.exe"

  ; インストールしたファイル群削除
  Delete $INSTDIR\*.*

  CLR::Destroy
SectionEnd

という感じでnsiファイルを記述。

ファイルは下記リンクからダウンロードしてお使いください。

download TaskKill.dll


NSISで、C#で書かれた自作のDLLを使う

NSISでインストーラ&アンインストーラを作っていて、インストール時に一緒にインストールしたAdobe AIRアプリケーションを、アンインストール時に同時にアンインストールしたいという要件。

Adobe AIRアプリケーションのアンインストールは、手動ならWindowsのコントロールパネルからできるので、ここの情報をレジストリから引っ張り出して実行すればOK。この辺は良くある手段。

で、レジストリからアンインストールに必要なプログラムの実行パス情報を持ってこようとしたら、Adobe AIRでは、Uninstall以下のサブキーの名前が毎回変わるので、検索する必要があることが発覚。

NSISにはレジストリの特定のキーを読む機能はあっても、キーを検索する機能はないので、DLLで拡張を作成。さっくり適当にC#でレジストリを検索して文字列を返すDLLを作成してみた。

ちなみにアンインストール情報はHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall以下にあります。

ということで、必要な動作はUninstall以下のサブキーを全部開いてDisplayNameが目的の名前と一致するサブキーを探すこと。ソースは以下。

SearchUninstall.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace SearchUninstall
{
    public class SearchUninstall
    {
        public static string Find(string name)
        {
            string keyPath = "";

            // レジストリからの読み取り
            Microsoft.Win32.RegistryKey regkey =
                Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", false);
            // キーが存在しないときは null が返される
            if (regkey == null) return null;

            // 文字列を読み込む
            // 読み込む値が存在しないときは null が返される
            string stringValue = (string)regkey.GetValue("string");

            // sub以下のすべてのキー名を取得
            string[] keyNames = regkey.GetSubKeyNames();
            foreach (string k in keyNames)
            {
                string tmp_keyPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + k;
                Microsoft.Win32.RegistryKey regkey2 =
                    Microsoft.Win32.Registry.LocalMachine.OpenSubKey(tmp_keyPath, false);
                try
                {
                    string displayName = (string)regkey2.GetValue("DisplayName");
                    if (displayName == name)
                    {
                        keyPath = tmp_keyPath;
                        break;
                    }
                }
                catch (Exception e)
                {
                    ;
                }
                finally
                {
                    regkey2.Close();
                }
            }

            //閉じる
            regkey.Close();

            return keyPath;
        }
    }
}

で、出来上がったDLLをNSISに読み込ませようとしても、どうにも動かない。

Dependency walkerで他のDLLと見比べてみると、C#で作られたDLLはエントリポイントが見えない。

そう言えばそんな話もあったような。

参考:WINAMP.COM | Forums – How can I use a C# DLL in NSIS

NSISからでは.NETのDLLが読めないらしい。ということで、見つけたのが以下。.NETのDLLを使えるようにするプラグイン。

Call .NET DLL methods plug-in – NSIS

CLR.zipをダウンロードしてきて、NSISをインストールしたフォルダのPluginsフォルダに入れておけばOK。

先に作ったSearchUninstall.dllはnsiファイルと同じ場所に入れておく。

で、nsiファイルは以下の感じ。Uninstall部分の必要部分を抜粋。

; アンインストーラ
Section "Uninstall"
  ; アンインストールで使用するプラグインの準備
  InitPluginsDir
  SetOutPath $PLUGINSDIR
  File "SearchUninstall.dll"

  ; AIRアプリケーションのアンインストーラを探す
  CLR::Call /NOUNLOAD "SearchUninstall.dll" "SearchUninstall.SearchUninstall" "Find" 1 "HogeHogeAir"
  pop $0
  CLR::Destroy

  ReadRegStr $4 HKLM $0 "UninstallString"

  ; AIRアプリケーションのアンインストーラをPassiveモード(自動処理)で実行する
  ExecWait "$4 /passive"

  ; 後片付け
  Delete "$PLUGINSDIR\SearchUninstall.dll"
SectionEnd

これで、アンインストール時にAdobe AIRアプリケーションのアンインストーラも勝手に起動して削除できる。

CLR超便利。プラグインを作るのが相当楽に。


FON2202のファームウェアのビルド

La Fonera2.0ことFON2202のファームウェアのビルドに成功したのでメモ。

環境はDebian lenny。主な手順はここ。

build fon-ng

でもこのままやってもビルドできませんでした。

まずは手順にある通り、必要なパッケージをインストール。

$ sudo aptitude install subversion g++ libdigest-crc-perl ncurses-dev zlib1g-dev gawk bison flex autoconf intltool-debian intltool

他のサイトで、以下も必要みたいな記事を読んだのでインストール。

$ sudo aptitude install gettext pkg-config libz-dev ccache libncurses-dev unzip

libdigest-crc-perlがaptだと古いという話なので、CPANから最新をインストール。

$ cpan
CPAN> install Digest
CPAN> install Digest::CRC
CPAN> install YAML

で、手順通りレポジトリからtrunkを落とすとビルドできないので、tags/Flipper-2.2.5.0をダウンロードします。

$ svn co http://svn.fonosfera.org/fon-ng/tags/Flipper-2.2.5.0
$ cd Flipper-2.2.5.0/
$ ./install.sh
$ cd openwrt/
$ make menuconfig

メニュー画面ではとりあえず何もせずにそのままExitで設定を保存。

いざmake。V=99は詳細表示オプション。なくてもいい。

$ make V=99

超時間かかる。Core2QudaのVista上のVirtualPCで2時間くらい。

で、完了。

$ ll bin/
合計 8388
-rw-r–r– 1 dev dev   65536 2009-08-13 09:24 loader.bin
-rwxr-xr-x 1 dev dev   11860 2009-08-13 09:24 meraki-loader.elf
-rwxr-xr-x 1 dev dev 3039219 2009-08-13 09:24 openwrt-fonera2-vmlinux.elf
-rw-r–r– 1 dev dev  786432 2009-08-13 09:24 openwrt-fonera2-vmlinux.image
-rw-r–r– 1 dev dev 4653060 2009-08-13 09:24 openwrt-fonera2.image
drwxr-xr-x 3 dev dev    4096 2009-08-13 08:32 packages

packagesディレクトリにはipk形式のパッケージファイル一式ができてます。
これが
http://www.fonera.be/rep/FON2202/index.php
これってことか。サイトにはpackagesディレクトリに含まれていないものもたくさんあるけど。
逆に言うと、ipkファイルを作る必要性はなさそう。

このままでは正しくファームウェアを書き換えられたかわからないので、ちょっと軽くいじっておきます。
Fonera->facebookをスペースでチェックを外す

ちょっとここで通常のファームウェアの構成を調べる。

$ wget http://download.fonosfera.org/LATEST/20090421_FON2202_2.2.5.0_Flipper_DEVELOPER.tar.gz
$ tar xvzf ./20090421_FON2202_2.2.5.0_Flipper_DEVELOPER.tar.gz
20090421_FON2202_2.2.5.0_Flipper_DEVELOPER.image
upgrade
fonera20_fon.image

できたファイルはディレクトリを含まず上記3つ。

$ ll
-rw-r–r– 1 dev dev 4653060 2009-04-22 01:05 20090421_FON2202_2.2.5.0_Flipper_DEVELOPER.image
-rw-r–r– 1 dev dev 4529210 2009-04-22 02:50 20090421_FON2202_2.2.5.0_Flipper_DEVELOPER.tar.gz
lrwxrwxrwx 1 dev dev      48 2009-08-13 19:25 fonera20_fon.image -> 20090421_FON2202_2.2.5.0_Flipper_DEVELOPER.image
-rwxr-xr-x 1 dev dev     305 2009-04-01 00:57 upgrade

fonera20_fon.imageはシンボリックリンクでimageファイルの実体へ。で、upgradeがシェルスクリプトになってて、これが実行される、と。

$ cat upgrade
#!/bin/ash

VERSION=$(cat /etc/fon_version)
FAILSAFE=$(cat /etc/failsafe)

if [ "$VERSION" = "2.2.1" -o "$VERSION" = "2.2.2" -o "$VERSION" = "2.2.3" -o "$VERSION" = "2.2.4" -o "$FAILSAFE" = 1 ]
then
mtd -r write fonera20_fon.image image > /dev/null 2>&1
#(sleep 4; reboot) &
return 0

else return 1; fi

なるほど、バージョンチェックして必要ならmtdコマンドで書き換え実行ということね。ということは、すでに2.2.5になってるこのFONでは上書きできないということか。

というわけで書き換える。

$ vi ./upgrade

シンプルに以下の感じに。

#!/bin/ash

mtd -r write fonera20_fon.image image > /dev/null 2>&1
return 0

で、リンク先を書き直し。tar.gz作成。

$ rm fonera20_fon.image
$ ln -s openwrt-fonera2.image fonera20_fon.image
$ tar zcvf openwrt-fonera2.tar.gz openwrt-fonera2.image fonera20_fon.image upgrade

で、できあがったopenwrt-fonera2.tar.gzを管理画面経由でFONにアップロード。

Firmware Update
Failed to identify upload.

Σ(゚Д゚)ガーン

なんだろ。ちなみに公開されているtar.gzを展開→再圧縮しただけでも反映に失敗したので、何かtarの作り方に秘密があるっぽい。
仕方ないのでimageファイルを転送して、FON上のSSHで反映することに。
以下、FONのSSH。

# cd /tmp/
# mtd -r write fonera20_fon.image image
Unlocking image …
Writing from fonera20_fon.image to image …  [w]
Rebooting …

で、どうやら書き込み完了。wktkしながらブラウザで確認…

緑の画面から書き変わってオレンジの画面に…あれ?SSHが効かない…?ん?Facebookのアイコンは残ったまま…?

Facebookは画面から消すのに何か別の作業がいるのかもしれないと思うことにして、SSHは…?プラグインのところにも出てこないし…

しかもブラウザからファーム書き戻そうとしてもバージョンが同じだから上書きできないし!!

\(^o^)/オワタ

シリアルケーブル手に入れるまで放置で…

もしくは管理画面からアップできるtar.gzの作り方、か。

※追記(2009/10/12)

lostmanさんに教えていただいた方法で復旧できました。
FON2202をFailSafeモードで復旧させる : blog.loadlimit – digital matter –


FON2202の電源を入れたら無線クライアントとして繋がるようにする

引き続き、La Fonera 2.0ことFON2202をいじる。

電源を入れたら自動で無線LANに接続して、動作を開始するようにする。これができれば電源ケーブルだけで取り回しがかなり自由に。あ、SSH開放したDEVELOPERファームを入れていること前提です。

無線LANに接続するところの参考サイトはこちら。
仙石浩明の日記: La Fonera を無線LAN 端末として使ってみる

まずはFONのFREESPOTとして待ち受けている無線を止めるところから。
というかアレなので一回全部止めてしまいましょう。
# wlanconfig ath0 destroy
# wlanconfig ath1 destroy

で、新たに1つ、staモード、通常の無線クライアントとしてath0を作成。
# wlanconfig ath0 create wlandev wifi0 wlanmode sta

接続先の設定。
# iwconfig ath0 essid "HogeHoge"
# iwconfig ath0 key s:hogehogewep00
# iwpriv ath0 mode 11g

で、起動。
# ifconfig ath0 up

一応これだけでアクセスポイントには接続できているはずなのだけど、IPは勝手に設定したりしないので、DHCPクライアントを使って取得。
# udhcpc -i ath0

では早速Pingを…
# ping www.google.co.jp
ping: bad address ‘www.google.co.jp’

あれ?

名前解決を…
# nslookup www.google.co.jp
Server:    xxx.xxx.xxx.xxx
Address 1: xxx.xxx.xxx.xxx

nslookup: can’t resolve ‘www.google.co.jp’: Name or service not known

おぉぉ

どうやら調べたらFirewallらしい。Firewall有効にしていると、無線側からのSSHもPingも通らない。
今回はLAN内に設置するため、その辺のセキュリティはいらないので、さっくりとFirewallを外すことに。

# /etc/init.d/firewall stop
# nslookup www.google.co.jp
Server:    xxx.xxx.xxx.xxx
Address 1: xxx.xxx.xxx.xxx

Name:      www.google.co.jp
Address 1: 66.249.89.99 jp-in-f99.google.com
Address 2: 66.249.89.147 jp-in-f147.google.com
Address 3: 66.249.89.104 jp-in-f104.google.com

おぉ、できたー。

…と、喜んだのもつかの間。1分ほど経つとath0の接続が切れる現象が。
色々調べて見ると、どうやらchillispotというものが起動して、設定を書きなおしてるらしいということが判明。
なるほど、無線アクセスポイントを構築するプログラムとのこと。
いらないね。

というわけで、起動を解除…しようとしたがどうにもどこから起動しているのかがわからない…
WANが有効になったことをトリガに起動されているらしいのだけど…
/etc/hotplug.d/以下にnetとifaceというディレクトリがあって、その辺を探ってみたのだけど、どうにも止められず。

起動するものはしょうがないということで、起動スクリプトを書き換えて対処。
# vi /etc/init.d/chillispot
do_start() {
の次の行に
return 1
として、即終了させておく。

この辺、OpenWRTにしてあれば入ってないと思うので無用な苦労しなくていいはず。
次回の課題だなぁ。

まとめ。
電源入で自動接続の設定はchillispotの編集をした後、適当に/root/にでもシェルスクリプトを作って起動時に実行するように仕込むだけ。
# vi /root/wlaninit.sh

#!/bin/sh

wlanconfig ath0 destroy
wlanconfig ath1 destroy
wlanconfig ath0 create wlandev wifi0 wlanmode sta 
iwconfig ath0 essid "HogeHoge" 
iwconfig ath0 key s:hogehogewep00
iwpriv ath0 mode 11g

ifconfig ath0 up
udhcpc -i ath0
/etc/init.d/firewall stop

で保存。

# chmod +x /root/wlaninit.sh
# cd /etc/rc.d/
# ln -s /root/wlaninit.sh S96wlaninit

いじょ。一度起動してから終了させるのが非常に無駄くさいがアクセスできなくなったりしてハマれる時間がないので今回はこれで。


マウスを動かしただけでCPU使用率が100%に行く現象

Windowsを使っていると、だんだんと重くなってきて、マウスもガクガク飛ぶようになった。うーん?以前はこんなことなかったけどなぁと思って原因究明に乗り出すことに。

使用OSはWindows Vista Ultimate 32bit。半日使ってると重くなってきて、1日経つとまともにブラウザも扱えない。

Core2Quadでメモリ4GBな上、ブラウザしか起動していなくても遅いので、マシンスペック云々ではないことを先に断っておきます。

まぁ、結論から言うとDisplayLink製のUSBモニタが問題でした。というか、それに付随するアプリケーションとサービスか。当該のプロセスは以下の3つ。

  • DisplayLinkUI.exe
  • DisplayLinkService.exe
  • DisplayLinkManager.exe

ServiceとManagerは全ユーザのプロセスを表示で、管理権限にならないと削除できないので注意。

3つ落としてやったら途端に快適な環境が復活しました。これ単体ではさほどCPU食ったりしないので、意外と気づきにくいかも?ちなみに使ってるUSBモニタはSAMSUNGのSyncMaster U70。

OS起動時に2回に1回ブルースクリーンになったり、色々あやしさは感じていたのだけど、ドライバの出来が良くないなぁ…USBサブモニタ流行ってるから、同じような現象が大量発生しているんじゃないかと不安だわ。

調べてみたら、バージョンは4.6.17952.0。付属のCDから一度バージョンアップしてます。

今見たら新しいメジャーバージョン(5.1)出てたので試してみます。この分野は可能性感じるし、U70自体は結構気に入ってるので、早々に安定してくれることを期待。

ダウンロードはこちらから。

DisplayLink: DisplayLink: Windows Drivers

ウィルスバスター2009とか疑ってすまんかった。同じような現象に悩まされている人の一助になれば幸いです。

追記

5.1にしたら遅くなる現象は直った模様。USBモニタ上でのマウスポインタの動作が遅くなったけど、別にクリティカルじゃないのでOK。


Photoshopの強制終了と対応

Photoshop CS3を起動しようとしたら、起動スプラッシュ表示中に突然強制終了する現象に見舞われた。

同じPCに入っているCS2もダメ。

スプラッシュの文字を追っていくと、どうやら「TWAIN メニュー項目を作成」というところで止まっているようだ。

うーん、最近何か入れた覚えはないけどなぁ…

とりあえず検索すると

TWAIN プラグインの読み込み中にアプリケーションが終了する(Photoshop Elements 4.0/5.0/Photoshop CS2/CS3)

これらしい。

原因となるドライバが不明なので、TWAINプラグイン自体外すことにした。説明の通りにプラグインのファイル名の先頭に「~(チルダ)」をつけるだけでOK。

会社で使ってるPCだからTWAIN経由ではスキャンしないしね。

で、思ったのは、AdobeのTechNoteに大抵の問題は情報があるらしいということ。

Photoshop他Adobe製品で起動しないとかの対応方法を探すには、ちゃんと正確にどこで止まったかを調べないといけないってことですな。基本だけど。「Photoshop 強制終了」とかで探すとハマるよ!


FON2202の自動アップデートを停止する

色々いじる関係上、自動でアップデートとかされても困るので自動アップデートを停止する。

SSHでログイン後、
# vi /bin/thinclient

. /tmp/.thinclient.sh
の行を以下のように書き換えてコメントアウト。
# . /tmp/.thinclient.sh

これでOK。
やり方は基本的に他のFONと同じ。DEVELOPERファームで最初からSSH使えるので手順が大幅に楽。


Linuxでパノラマ画像結合をバッチ処理してみる

ある固定の複数のカメラから定期的に送信されてくる写真をパノラマ結合する。
環境はDebian Linux(lenny)。

カメラとしてはこんなものを用意。

P1020072

Logicool Qcam Pro 9000の中身2つ。ノーダルポイントとか考慮した方がいいのでしょうが、遠景だと問題にならないのでパス。画角はそれぞれ75度。

で、これが元画像。

image6_2cam image6_1cam

とりあえず前提条件として、カメラが固定で移動しないので、毎回同じ変換処理をかけるだけ。コントロールポイントの計算とかはしない。
で、変形の計算については、GUIが便利なWindowsのHuginを使ってあらかじめ計算しておきます。

Huginの使い方とかは
Hugin / Panorama Toolsによりパノラマ写真を作る
とかを参照。
諸々設定が完了したら、メニューの[ファイル]→[PTStitcher用スクリプトの書き出し]でパラメータ類をファイルに書き出します。

次はLinux側。
まずはPanoToolsをSourceForgeからダウンロード。バージョンはひとつ前のメジャーバージョンの12を使用。
$ wget http://downloads.sourceforge.net/sourceforge/panotools/libpano12-2.8.6.tar.gz?use_mirror=jaist -O libpano12-2.8.6.tar.gz
panotool13もあるけど、PTStitcherがlibpano12.soを所望で13だと使えないので12で。
どうやら13からはPTStitcherのオープンソース移植版のPTmenderというものを使うらしい。
一応、スクリプトとかオプションは互換とのこと。でもハマるのが怖いので今回は見送り。

ダウンロード後、展開。
$ tar xvzf libpano12-2.8.6.tar.gz
$ cd libpano12-2.8.6
libpng-devとかlibtiff-devが入ってないと文句言われるので、
$ sudo aptitude install libpng-dev libtiff-dev
しておく。
$ ./configure –prefix=/usr
$ make
$ sudo make install
これでPTStitcherを使う準備完了。

$ wget http://www.all-in-one.ee/~dersch/PanoTools.tar.gz
$ tar xvzf PanoTools.tar.gz
$ cd PTLinux/Helpers/
$ chmod +x PTStitcher
$ ./PTStitcher -h
Usage: PTStitcher [options] file1 file2 …

オプションとかはPTStitcher.txtに書いてある。
で、先ほどのスクリプトファイルを設置して
$ ./PTStitcher -o output.jpg sample.pto
とかやれば出力完了!

image6_2cam-image6_1cam

バッチ処理部分は適当に…


FON2202にcurlを入れてファイルをアップロードする

特に難しいことはなく、ただそろそろディスク容量が厳しくなってくるのも事実。

# cd /tmp/
# wget http://www.fonera.be/rep/FON2202/index.php?get=245 -O libcurl_7.17.1-1_mips.ipk
# wget http://www.fonera.be/rep/FON2202/index.php?get=35 -O curl_7.17.1-1_mips.ipk
# wget http://www.fonera.be/rep/FON2202/index.php?get=277 -O libopenssl_0.9.8h-1_mips.ipk
# opkg install libcurl_7.17.1-1_mips.ipk curl_7.17.1-1_mips.ipk libopenssl_0.9.8h-1_mips.ipk

上記コマンドでcurlはインストールできる。

curlの動作に必要なOpenSSLのライブラリが476.84 kBもあるので、正直オススメできない…

ただし、その分メリットは大きい。ファイルのアップロードも簡単。

# curl -F FILE=@snap0.jpg http://192.168.0.x/upload.php

FUSEのパッケージもあるから、ネットワークドライブでマウントしてしまった方がいいのかもしれない。片っ端からAmazonS3に上げてくとか。


FON2202でスナップショットを撮影する

La Fonera 2.0、FON2202を使ってUVCのデバイスからスナップショットをキャプチャする企画完結編。

一連のエントリは以下。

  1. FON2202にDEVELOPERファームを入れてSSHアクセスできるようにする
  2. FON2202に、Webカメラを複数接続する

まずUVCからスナップショットを撮る目的のプログラムを探してみると、そのままずばりなuvccaptureというソフトを発見。

ソースを見てみると、結構簡単。これなら動かすのに修正が必要でも何とかなるかな、と。

あ、ちなみにストリーミング配信したいだけなら、パッケージがすでにあるのでfonera.beから探してください。

今回はそこそこ綺麗な画質で数秒おきに画像が撮りたいニーズなんで、上記のuvccaptureを使うことに。

使ったのはuvccapture-0.5.tar.bz2

で、uvccaptureをFonera本体でコンパイルすればいいかなーとか思ってたのだけれども、どうにも開発環境がどこに入ったのかわからない。

あ、上記のfonera.beからgccとか入れてみたんですけれどもね?

じゃあということで、VirtualPC 2007上に構築したDebian(lenny)でクロスコンパイルすることに。

Debian上には、以前OpenWRTをmake worldしようとして途中で失敗して放置しっぱなしの環境が。

でもmips-linux-gccとかはディスク上に見つかったので、どうやらこれで行けるっぽい。

ここまで構築したい人は以下のサイト参照。

build fon-ng

色々やってよくわからなくなったetchと、これのために新規にインストールしたlennyで試してみたけど、どちらも成功しませんでした。

etchの方はlibdigest-crc-perlが古かったらしく、CPANからDigest::CRCをインストールしたらもう少し進んだけど。

ちなみに隣にいたUbuntu使いに頼んでやってみてもらったら全部通ったらしいです。謎。

確認してないけど、svnからcheckoutしてくればいいだけの気がする。

で、さっそくuvccaptureのクロスコンパイル開始。

とりあえず基礎知識を仕入れる。Makefileの環境変数の意味だけ確認しておこう。

porting_0_lesson2 – PukiWiki

作業用のディレクトリを作成

$ cd ~/fonera/trunk/openwrt/
$ mkdir test
$ cd test/

FON用のgccはここに入ってる。

trunk/openwrt/staging_dir/toolchain-mips_gcc4.1.2/bin/mips-linux-gcc

uvccaptureにはlibjpegが必要になるので、aptitudeで…と言いたくなるのですが、mips-linux-gcc用にインストールしないといけないので、今回はソースから。

参考にしたサイトはLinkStation-Debianテストサイト::2008年12月10日の、libjpegの項。

ほぼそのまんまですけど一応書いておきます。

あ、PATHを通すのを忘れずに。

$ export PATH=~/fonera/trunk/openwrt/staging_dir/toolchain-mips_gcc4.1.2/bin:$PATH
$ wget ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz
$ tar xvzf jpegsrc.v6b.tar.gz
$ cd jpeg-6b/
$ ./configure –build=i686-linux –host=mips-linux –enable-shared –prefix=~/fonera/trunk/openwrt/staging_dir/toolchain-mips_gcc4.1.2/

で、できあがったMakefileを手で修正。

$ vi Makefile

CC=mips-linux-gcc
AR=mips-linux-ar rc
の2行だけ直せばOK。これってconfigure時点で設定できないのかなぁ。わからんけど。

その後の手順も参考サイトの通りに

$ make CC=mips-linux-gcc
$ make install

でOK。make installするのにsudoする必要はなし。

これでtrunk/openwrt/staging_dir/toolchain-mips_gcc4.1.2/lib/にlibjpeg関連のファイルが一通り入る。

というところでuvccaptureのmakeに移る。

$ cd ~/fonera/trunk/openwrt/test/
$ wget http://staticwave.ca/source/uvccapture/uvccapture-0.5.tar.bz2
$ tar xjf uvccapture-0.5.tar.bz2
$ cd uvccapture-0.5/

こちらはすでにMakefileがあるので、書き換えてやるところから。

CC=mips-linux-gcc
PREFIX=~/fonera/trunk/openwrt/staging_dir/toolchain-mips_gcc4.1.2

CPP書き換えるの忘れたままコンパイルしちゃったけど、通ったので問題なし。

書き換えるなら多分CPP=mips-linux-g++かな。

installしないので、PREFIXとか書き換える必要あるのか不明。

で、

$ make

成功すればuvccaptureというファイルが同じところに作成される。

で、これをFON本体に転送する。面倒なので、Apache起動してHTTP経由で転送する。

$ sudo aptitude install apache2
$ sudo cp uvccapture /var/www/

併せてlibjpeg.so.62というファイルが実行に必要なので、それもコピーする。

$ sudo cp ../../staging_dir/toolchain-mips_gcc4.1.2/lib/libjpeg.so.62 /var/www/

ここまででdebian側の作業は終了!

FONのSSHに戻って作業を続けます。あと少し。

FON側からはDebianのIP指定してファイルをwgetで取ってくればOK。

# cd ~
# wget http://192.168.0.x/uvccapture
# wget http://192.168.0.x/libjpeg.so.62
# mv libjpeg.so.62 /lib/

で、

# ./uvccapture

で、
snap.jpgができたー!ヽ(´ー`)ノ

# ./uvccapture -h

でヘルプ出せます。

2台目のカメラにアクセスしたいときは/dev/video1を指定してやればOK。

# ./uvccapture -d/dev/video1 -osnap1.jpg -x960 -y720

とかすれば、2台目のカメラから960×720でスナップショットを撮影して、snap1.jpgというファイルに保存できます。

思ったより処理も遅くないので使えそう。

ちなみに960×720よりも大きい解像度で撮影しようとすると、YUYVフォーマットに変更するらしいのだけど、試しに1280×960にしてみたら問答無用でFONが落ちたのでもうやりません。

YUYVフォーマットモードは-mオプションでも指定可能。YUYVにするだけなら落ちない。-qオプションでJPEG圧縮率変更するとYUYVになります。

以下は全滅の例。

# ./uvccapture -d/dev/video1 -osnap1.jpg -x1280 -y960 -q60
# ./uvccapture -d/dev/video1 -osnap1.jpg -x1600 -y1200 -m -q30
# ./uvccapture -d/dev/video1 -osnap1.jpg -x2304 -y1728 -q30 -v

あとuvccapture、便利なことにインターバル撮影機能も付いてます。撮影したあとに指定したコマンドも実行できるという。

これでファイルPOSTすればいいってことですな。uvccaptureをふたつ起動してしまうと同期取れないのでその辺はちょっとこれから改造。

しかしこれは結構遊べるなぁ。コンセント差すだけで動くリモートWebカメラを作りたい。