2007/02/04(日)Windows Media Service(WMS) 代替サーバ wmrelay

wmrelayって?

Windows Media エンコーダ(WME9)には小規模なライブ中継機能(ストリーミングサーバ機能)を持っていますが、最大接続数に制限があり*1、中継元のアップ回線が細いといろいろと不都合が起こります。

Windows Media Services(WMS) などを用いて大規模な中継を行ったり、icecastのように複数の協力者で大規模中継を試みたいところですが、WMS は Windows Serverにしか付属せず、高い上に Windows を外部向けサーバとして運用させるのも気乗りしません。

wmrelay を利用すれば、Linux、FreeBSD、通常のWindowsマシンなどで誰でも手軽に中継サーバを実現できます(mmsプロトコルは非対応です)。協力者を集めれば大規模中継も簡単に行えます。

*1 : 標準で5人、最大で50人まで

ダウンロード

wmrelay_win-img.jpg

ライセンス:GPL (Version 2 or later)

  • wmrelay.zip Version 2.13
    • (Windows) wmrelay.exe を実行(ファイル単体でok)
    • (Linux/FreeBSD) wmrelay.pl を Perl 5.8.1 以降で実行

変更履歴

  • Ver2.13 2010/04/10 -b オプションの追加。バッファディフォルトを 64 に変更。
  • Ver2.12 2009/08/24 [Windows] EXE化をActivePerl5.10ベースにしました。
  • Ver2.11 2007/03/08 [Windows] GUIで動作しない不具合を修正しました
  • Ver2.10 2007/02/13 [Windows] GUI操作画面を作成
  • Ver2.01 2007/02/05 Mac等で動かない問題を修正(Thanks to RXさん)
  • Ver2.00 2007/02/03 全面書き直し。pushサーバ機能追加
  • Ver1.00 2005/06/07 初公開

Linux/FreeBSD等での動作

Linux, FreeBSDなどではPerl 5.8.1以降でithreadが有効なものが必要です。.plをWindows環境(GUI)で使用する場合は、GUI-Loftのインストールが必要です(参考サイト)。

「perl -V」として実行したとき「useithreads=define」と出力されればOKです。

~$ perl -V
Summary of my perl5 (revision 5 version 8 subversion 8) configuration:
<中略>
hint=recommended, useposix=true, d_sigaction=define
usethreads=define use5005threads=undef useithreads=define usemultiplicity=define

動作確認

  • エンコードソフト:Windows Media Encoder 9 (WME9)
  • 再生ソフト:Windows Media Player 6.4*2/9.x, Winamp, MPlayer(Linux)
  • wmrelay動作環境:FreeBSD 6.2、Ubuntu 6.10(Linux 2.6)、Windows 2000

音声のみ、動画再生共に確認してます。

*2 : WMV9 Codecが必要です

使い方

wmrelayはコマンドラインから起動します。使用例は次のようになります。

  • 配信元 http://192.168.1.50:8888/
  • 送信ポート 標準値(8080)
    wmrelay http://192.168.1.50:8888/
    
  • 配信元 http://test-stream.dyndns.org:8080/
  • 送信ポート 8000
    wmrelay -p 8000 http://test-stream.dyndns.org:8080/
    
  • 配信元 http://test-stream.dyndns.org:8080/path/file.asf
  • 送信ポート 9000
  • 最大接続数 200
    wmrelay.pl -p 9000 -m 200 http://test-stream.dyndns.org:8080/path/file.asf
    

オプションの解説

-hコマンドラインヘルプを表示します
-cCUI(GUIをオフにして)使用します(Windowsでの動作時のみ)
-p [num]送信に利用するポート番号を指定します(省略時:8080)
-m [num]最大接続数を指定します(省略時:100)
-b [num]バッファサイズを指定。16,32,64,128,256,512,1024 どれか(省略時:64)
-l [file]ログファイル名を指定します。%p はポート番号で置き換わります
-f [file]プッシュサーバモードでのパスワードファイルを指定します(後述)
-a [pass]プッシュサーバモードでのパスワードを指定します(後述)
-sリレー元からのパケット受信情報を表示しません
-dデバッグ情報を表示します

最大接続数の目安

回線速度(アップ速度)をストリームのビットレートで割れば目安がわかります。スピードテストサイトなどで「Upload Speed」を計測してみてください*3

「アップ速度:0.5Mbps ストリーム:136kbps」の場合0.5*1024 / 136 = 3.7647... (connections max)端数は切り捨てて(必ず切り捨てること)3人が理論限界です。

「アップ速度:12.58Mbps ストリーム:226kbps」の場合12.63*1024 / 226 = 57.226... (connections max) 57人が理論限界ですが50人程度にしておくほうが無難です。

*3 : 「Download Speed」は無関係です

push配信モード

ストリーム受信元URLを指定しないと、wmrelayはpushサーバモードで動作します。このモードでは中継クライアント(Windows Media Encoder9)から中継要求(サーバへのプッシュ要求)があった際に中継が開始されます。

中継先ポートには配信に利用するポート(-p port_number)と同じ番号を指定します。公開ポイントは適当で構いません(wmrelayは公開ポイントを無視します)。

wme9_push.png

また1つのプログラム(1つのポート)につき同時に1つのストリームのみが中継可能です。パスワードを指定しないと誰でも中継可能となりますのでご注意ください。サーバを止めるときは CTRL-C を何度か押してください。

共通パスワードモード

ユーザー名に関わらず、特定のパスワードを入力したクライアントの接続をすべて受け付けるモードです。パスワードは起動時に次のように指定します。

wmrelay -a password

ユーザー別パスワードモード

起動時にパスワードファイルを指定することで、ユーザー別のパスワードを設定します。

wmrelay -f password_file

パスワードファイルは「ユーザー名=パスワード」という書式になります。

# この行はコメントです
user1=pass1
user2=himitsu

パスワードの設定されているユーザーが居ない場合や、パスワードファイルがない場合には、誰でも中継可能になりますので注意してください。

クライアント(リスナー)の使いかた

例えば、10.11.22.33 というIPのマシンにおいて、標準状態の 8080 で中継した場合は、クライアントは次のようにアクセスします。

http://10.11.22.33:8080/

ブラウザから .asx ファイルを経由して表示させたい場合は、ブラウザで次のアドレスにアクセスするよう指示します。

http://10.11.22.33:8080/relay.asx

参考資料

2007/02/03(土)PARでPerlをWindows実行ファイルに(with ithreads)

ActivePerl 5.10の場合はこちら。以下はActivePerl5.8.x用です。

PARとは

PARとは、Perlのパッケージツールの1種なのですが、これを利用することでWindows上で(Perlがなくても)実行出来るEXEファイルを生成することができます。ActivePerlなら通常

> ppm install PAR
> pp -o test.exe test.pl

で済むのですが、この標準の PPM パッケージが古く、ithreads が利用出来ないなどの欠点があります。これは perl 5.8.0 ベースで作られているせいであり、同じ理由で「use 5.8.1;」なども書くことができなくなります。

PARによるPerlスクリプトのEXE化を参考に、Mingwを入れて最新版をインストールしたのですが、どうしても次のエラーが出て実行できませんでした。

Can't locate Win32.pm in @INC (@INC contains: .) at xxx.pl line 817.

この問題をうまく解決出来たので報告します。

PARのインストール

そのときの最新版で問題ないと思いますが、不安な場合はまったく同じバージョンを用意してください。まず、ActivePerlをインストールします。その後、それぞれのファイルを解凍します。

まず、ExtUtils::FakeConfigが必要なので、ppmで構いませんからインストールしてください。

> ppm install ExtUtils-FakeConfig

次に PAR-Packer にパッチを充てます。必ず make 前に行ってください*1

PAR-Packer-0.973\script\par.plの690行目付近
    require PAR::Dist;
    require PAR::Filter::PodStrip;
    require Win32 if $^O eq 'MSWin32';		# この行を追加
    eval { require Scalar::Util };

あとは普通に make してインストールしてください。make 時に不足するライブラリやツールは自動で取ってくれると思います(ただし再度同じコマンドを実行する必要が出たりします)。

> cd PAR-0.973
> perl Makefile.PL
> nmake
> nmake install
> cd ..
> cd PAR-Packer-0.973
> perl -MConfig_m Makefile.PL
> nmake
> nmake install

これで、ithreads が問題なく動く pp が生成できると思います。

*1 : make済の場合は修正後にmake cleanを

2007/01/25(木)HTTP asf (wmv/wma)ストリームのpush配信プロトコル

Windows Media Player 9(WMV9) などによるHTTPストリームデータの仕様は以下のサイトに書かれていますが、push配信については記述がありません。その点について解析を行いました。

http://sdp.ppona.com/

匿名push配信時のプロトコル解析

クライアントからサーバへのリクエスト。

POST /test HTTP/1.1
Content-Type: application/x-wms-pushsetup
X-Accept-Authentication: NTLM, Digest
User-Agent: WMEncoder/9.0.0.3287
Host: 192.168.0.1:8080
Content-Length: 0
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: push-id=0

サーバからクライアントへの応答。

HTTP/1.1 204 No Content
Server: Servet-agent
Content-Length: 0
Date: Tue, 09 Jan 2007 10:02:58 GMT
Pragma: no-cache, timeout=60000
Cache-Control: no-cache
Set-Cookie: push-id=35201712
Supported: com.microsoft.wm.srvppair, com.microsoft.wm.sswitch, com.microsoft.wm.predstrm, 
com.microsoft.wm.fastcache, com.microsoft.wm.startupprofile

注意点

  • (サーバ側では)Connection が keep alive される
  • クライアントが接続テストだけを行ってきた場合は、この時点で終了する。

配信時の接続ではさらに次のように続きます。

POST /test HTTP/1.1
Content-Type: application/x-wms-pushstart
X-Accept-Authentication: NTLM, Digest
User-Agent: WMEncoder/9.0.0.3287
Host: 192.168.0.1:8000
Content-Length: 2147483647
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: push-id=35201712

$H~以下ストリームデータ~

ストリームデータの取り扱い注意

push配信によって渡されるストリームデータは、HTTP Protocol (Pesudo-Stream) のうち「Type Header」のあとに含まれる、「MSS Pre-header」(8byte)がありません。よってこのヘッダに含まれるシーケンス番号やフラグなどを中継サーバ側で生成し追加してやる必要があります(この際、Type Headerにあるパケットサイズも書き換える必要があります)。

  • MSS Pre-header の先頭2byteは、$Hや$Eの場合 "00 0C"、$Dの場合 "00 00" で問題ありません。
  • MSS Pre-header の Length は $D などのパケットヘッダの Length と同じ値を設定します。

Digest認証時のプロトコル解析

Digest認証を求める場合、サーバ側は次のような応答を返します。

HTTP/1.1 401 Unauthorized
Server: Servet-agent
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="XX~XX",charset=utf-8,realm="Digest"
Date: Tue, 09 Jan 2007 10:34:57 GMT
Pragma: no-cache, timeout=60000
Set-Cookie: push-id=98422531
Supported: com.microsoft.wm.srvppair, com.microsoft.wm.sswitch, com.microsoft.wm.predstrm, 
com.microsoft.wm.fastcache, com.microsoft.wm.startupprofile
Content-Length: 0

ナップサック問題をポケコンで解く

ナップサック問題は重いらしい

手元に舞い込んできたとある資料によると

ks.gif

この問題は,14個ある荷物を入れるか入れないかで考えるので 214 = 16,384 通りの組合せとなり,すべての組合せをコンピュータに計算させると最適な組合せ(解)を見つけるのに数十分もかかってしまいます。(中略)遺伝的アルゴリズムを用いると,わずか数秒で答を求めることができます

要するにGAの優位性を訴えているのですが、この程度の問題に、どうやったら数十分もかかるのか疑問で仕方ありません。というわけで実際に試してみました。

C言語で解いてみる

Pentium3@800MHzの、今となっては決して速くないCPUとgcc3.3を使いこの問題を総当たりで解いてみました。ソースファイルはこちら

nabe~$ gcc -O2 ks.c -o ks.o
nabe~$ time ./ks.o
Best pairing : 0(A) 1(B) 2(C) 4(E) 6(G) 7(H) 9(J)
Best value   : 530

real    0m0.005s
user    0m0.001s
sys     0m0.004s

プログラムの起動時間を入れても5ms、実際に問題を解いている時間だけみれば1msです。数秒をうたっているGAよりもはるかに速い結果です。困ったことになってしまいました。どうやら実験に使った環境が速すぎたようです。

JavaScriptで解いてみる

遅いマシンをすぐに用意できればよかったのですか、今更Pentium(初代)レベルのマシンやそれ以前のマシンを用意するのも大変です。マシンを遅くする代わりに処理系を遅くしてしまうことにしました。きっとコンパイルしてマシン語に変換されたのが問題だと思うので、処理が遅い言語としてインタプリタを使用することします。

ブウラザがあればどこでも動くJavaScriptによる実装です。

14個の荷物について計算します
最大重量 = 200
組み合わせ : A B C E G H J
最的値 = 530
計算時間 = 90 ms

実行マシンはCeleronの1GHzで、gccと比べても実行環境として100倍以上遅いのですが1秒を切ってしまいました。単純計算で100MHz程度のマシンをもってきても1秒程度で答えが出るようです。どうしましょう、数十分どころか、数秒と言われるGAよりも高速です

考えが甘かった。まだまだマシンが速すぎたようです。もっと決定的に遅いマシンならいいのですが、身近に、もっと遅くてプログラマブルのコンピューターがなかなか思い当たりません。ファミコン? 携帯? あーこれがありましたっ!

ナップザック問題をポケコンで解いてみる

pc-g815.jpg

まず簡単にスペックをおさらいしておきましょう。

型番SHARP PC-G815
CPUZ80相当 3.58MHz
メモリ(RAM)32KB

3.58MHzです! 今までより格段に遅いマシンです。最近のマシンと比較すれば1000分の1のクロックで「64ビットだー!」と世間が騒がしいこのご時世に8bit。単純計算で8分の1ですが、潜在的な能力差はその数倍にも昇ります。

これだけのマシンを持ってくれば誰も速いなんて文句を付けるような無粋な人間はいないと思いますが、念には念を入れて次の選択をしました。

プログラム言語BASIC

(パソコンと比べれば)劇遅なZ80の上で動くBASICインタプリタ。もはや文句はないでしょう。

さて実際にプログラムを打ち込むのですが、たった4行の狭い画面に打ち込むのが大変な上、BASICの文法が(CやPerlに慣れた今になっては)違和感ありまくりで一苦労でした。できあがったソースはこちら

きちんと正解が出るのか走らせてみましょう。多少緊張しつつ、RUNと入力します。

pc-g815_kekka.jpg

解けました、解けました。さて気になる時間は135秒!(手動計測) 2分15秒ですから、さすがに時間掛かってます。ですが、数十分どころか、数分にすらなりません! わざわざインタプリタでこれですから、普通にCで書いたとして一体どんなマシン持ってきたら数十分になるでしょう。誰か教えてっ!!

2006/08/09(水)Perlによるbase64エンコード/デコードの実装

エンコード

MIME::Base64(C実装)を使えば速いのですが、メールのタイトル程度でそこまでする必要はないし*1、かと言って日本で有名な某MIMEルーチンは、巨大変換テーブルという非効率な実装なので、自作した奴。

# テーブル
my $base64table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
# 変換処理
$subject =~ s/(\e\$[\@B].*?\e\([BJ])/ '=?ISO-2022-JP?B?' . &base64encode($1) . '?=' /eg;

sub base64encode {
	my $str = shift;
	my $table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
	my $ret;

	# 2 : 0000_0000 1111_1100
	# 4 : 0000_0011 1111_0000
	# 6 : 0000_1111 1100_0000
	my ($i, $j, $x, $y);
	for($i=$x=0, $j=2; $i<length($str); $i++) {
		$x    = ($x<<8) + ord(substr($str,$i,1));
		$ret .= substr($table, ($x>>$j) & 0x3f, 1);

		if ($j != 6) { $j+=2; next; }
		# j==6
		$ret .= substr($table, $x & 0x3f, 1);
		$j    = 2;
	}
	if ($j != 2)    { $ret .= substr($table, ($x<<(8-$j)) & 0x3f, 1); }
	if ($j == 4)    { $ret .= '=='; }
	elsif ($j == 6) { $ret .= '=';  }

	return $ret;
}

*1 : Perlは外部モジュールロードが速くない

デコード

# テーブル
my @base64ary = (
 0, 0, 0, 0,  0, 0, 0, 0,   0, 0, 0, 0,  0, 0, 0, 0,	# 0x00~0x1f
 0, 0, 0, 0,  0, 0, 0, 0,   0, 0, 0, 0,  0, 0, 0, 0,	# 0x10~0x1f
 0, 0, 0, 0,  0, 0, 0, 0,   0, 0, 0,62,  0, 0, 0,63,	# 0x20~0x2f
52,53,54,55, 56,57,58,59,  60,61, 0, 0,  0, 0, 0, 0,	# 0x30~0x3f
 0, 0, 1, 2,  3, 4, 5, 6,   7, 8, 9,10, 11,12,13,14,	# 0x40~0x4f
15,16,17,18, 19,20,21,22,  23,24,25, 0,  0, 0, 0, 0,	# 0x50~0x5f
 0,26,27,28, 29,30,31,32,  33,34,35,36, 37,38,39,40,	# 0x60~0x6f
41,42,43,44, 45,46,47,48,  49,50,51, 0,  0, 0, 0, 0	# 0x70~0x7f
);
# デコード処理
$subject =~ s/=\?ISO-2022-JP\?B\?([A-Za-z0-9\+\/=]*)\?=/ &base64decode($1) /eg;

sub base64decode {
	my $str  = shift;

	my $ret;
	my $buf;
	my $f;
	if (substr($str, -1) eq  '=') { $f=1; }
	if (substr($str, -2) eq '==') { $f=2; }
	for(my $i=0; $i<length($str); $i+=4) {
		$buf  = ($buf<<6) + $base64ary[ ord(substr($str,$i  ,1)) ];
		$buf  = ($buf<<6) + $base64ary[ ord(substr($str,$i+1,1)) ];
		$buf  = ($buf<<6) + $base64ary[ ord(substr($str,$i+2,1)) ];
		$buf  = ($buf<<6) + $base64ary[ ord(substr($str,$i+3,1)) ];
		$ret .= chr(($buf & 0xff0000)>>16) . chr(($buf & 0xff00)>>8) . chr($buf & 0xff);

	}
	if ($f>0) { chop($ret); }
	if ($f>1) { chop($ret); }
	return $ret;
}

ライセンス

修正BSDで。