PCとAVアンプをHDMIで接続するときの問題点を解決しよう

現在PCとAVアンプを接続する方法として一般的な方法はHDMIです。
最近のグラフィックカードはサウンドデバイスとしても働くので、AVアンプのHDMI INにPCを接続するだけでHDMI OUTに接続したモニタ(やテレビ)から映像が出て、音声もAVアンプから出力されます。

ただし、以下の問題があります。

  • サウンドのみの出力ができない (映像出力が必ずセットになる)
  • AVアンプの電源を切ると映像出力が切れ、更にサウンドデバイスを見失う
  • 機種(AVアンプ/グラフィックカード)にもよるが、AVアンプの電源を切るとディスプレイ自体も見失う (デュアルモニタにしていたらシングルモニタ環境になる)

1番目はどうしようもないです。
クローンモードにするなり、2枚目として使うなりしましょう。ただしクローンモードにするとHDCPが使えなくなります。

2番目以降は結構切実です。
DVIで接続しているモニタは電源を切っても認識されっぱなしですが、HDMIだとそうもいきません(たぶん)。

ディスプレイ自体を見失うのでDirectXなゲームをしていたりすると、だいたい落ちます。
また、サウンドデバイスがどこかにいくので、音も行方不明になります。もう一度AVアンプの電源を入れてもだいたい戻ってきません。

更に悪いことに、これはAVアンプのメーカーにも寄りますが、出力先のディスプレイの電源のON/OFFだけでも上記現象が起ることがあります(SONY製は少なくともそうなってます)。
これはAVアンプのEDID情報(使用できる解像度やサウンドのコーデック、周波数 etc. な情報)が出力先のディスプレイに連動しているためで、たとえば私が使用している SONY STR-DN2030 では、

  • ディスプレイをON → サウンドデバイス名はモニタの名前
  • ディスプレイをOFF → サウンドデバイス名は SONY AVSYSTEM

と変化し、ディスプレイの解像度情報としても、ONのときはディスプレイが使用できる解像度まで、OFFのときはAVアンプがサポートしている解像度(STR-DN2030では4K解像度まで選べました)が現れます。

要は「AVアンプのON/OFFでHDMI接続を見失うのが悪い」ということになります。
言い換えれば「常に一定のEDID情報を出力してくれていればいい」ということです。

長々と書いてきましたが、世の中には「EDIDエミュレータ(信号保持器)」という、これまたそのまんまな機械が存在し、これを使えば一気に解決します。

こんなふうに接続します:
PC --[HDMI]-- EDIDエミュレータ --[HDMI]-- AVアンプ --[HDMI]-- ディスプレイ

EDIDエミュレータには「クローンモード」というモードが存在し、ディスプレイのEDID情報を学習し、それを常にシステム側(ここではPC)に送出してくれます。
一度AVアンプから送られるEDID情報を学習させることで、AVアンプの電源がどうなっていようが、出力先ディスプレイがどうなっていようが、PCからは「つながっている」と見なされるので、ディスプレイを見失ったり、サウンドデバイスを見失うことは無くなります。

AVアンプの電源が切れていると映像出力も途切れるのは解決できませんが、AVアンプのパススルー機能を使用することで一応解決できます。デメリットとしてAVアンプの電源が切れていても電力を消費してしまいますが。
ちなみに、パススルーを設定していても、EDIDエミュレータを使用しない場合、電源のON/OFFで一度ディスプレイを見失います。

最後にうちの環境です:

Gauche から wkb (well-known binary) を読み込めるライブラリを書きました

wkb とは

GIS 分野において、点や線分、多角形を扱うフォーマットです。
binary の名の示すとおり、バイナリフォーマットです。

wkb と wkbhex

wkbhex は wkb を hex 表記したもので、0x00 0x80 0xff 0x00 というバイト列ならば、"0080FF00" といった文字列表記になります。

みどころ

本題です。
wkb パーサは実のどころどうでもよくて、wkbhex をバイナリポートに変換するルーチンです。

(define (read-hex-string :optional (iport (current-input-port)))
  (glet* ([higher (read-char iport)]
          [lower (read-char iport)])
    (string->number (string higher lower) 16)))

(define (open-input-hex-string str)
  (make <virtual-input-port> :getb (cute read-hex-string (open-input-string str))))

read-hex-string はポートを引数に取ります。
glet* は and-let* のジェネレータ版です (要 gauche.generator)。
and-let* は #f が評価された時点で停止し、#f が返りますが、glet* は (eof-object) が評価された時点で停止し、(eof-object) が返ります。
read-hex-string は2バイト(正確には2文字)をポートから読み込み、読み込まれた文字列を数値に変換して返します。
つまり、ポートに "FFFE" という文字列が残っている場合、255, 254 という数値を順番に返します。
ポートの終端までたどり着いた場合は glet* により (eof-object) が返ります。
つまり、read-hex-string はジェネレータです。

open-input-hex-string は、文字列を受け取り、バイナリとして読み込めるポートを返します。
wkbhex to wkbトランスレータです。
read-hex-string は 0-255 の数値、もしくは (eof-object) を返すので、virtual-input-port の :getb に突っ込むだけでバイナリデータを読み込むことができるポートを作成できます。

ちなみにエラーチェックがないのでつけるべきですね ;-)

そのうち

Gauche のジェネレータは面白いのでなんか書きたい

課題

  • 2次元座標にしか対応していないので、多次元座標に対応させたい
  • wkb/wkbhex の出力もしたい

Galaxy Nexus で apt-X を使えるようにしたい (手詰まり中)

apt-X 対応の Bluetooth ヘッドフォンを購入したものの、スマホ側 (Galaxy Nexus) が対応していないので SBC にフォールバックされて 64kbps MP3 を聞いているような感覚に陥りながら、外で聞く用だからそんな音質こだわらねーしwwwと粋がりながらも、ふぇえやっぱり音質しょっぱすぎるよう;;; となっているアカウントがこちらになります。

なんか最近のシャープのナントカってやつとか、HTC J とかは apt-X に対応してるっていうじゃん?Galaxy S3 も対応してるっていうじゃん?じゃあ移植してみればいいじゃん?
ということで、HTC One S の ROM から物故抜いて移植を試みてみました。

  • /system/lib/libbt-aptx-4.0.3.so
  • /system/lib/bluez-plugin/audio.so
  • /system/lib/hw/audio.a2dp.default.so
  • /system/etc/bluetooth/audio.conf (APTXSources=1 を記述)

が関係ありそげ。こいつら全部 Galaxy Nexus に放り込んでみたら、見事にブートアニメーションから先に進みませんでした。残念無念。
AOKP のソースを眺めてみたところ、やはりというか、hw/audio.a2dp.default.so はハード依存っぽく、そら単純に移植しても動かんわなー。
apt-X 自体はソフトウェアレイヤなはずなので、どうにかすればいけそうな気はします。
が、ライセンスなどの関係でこいつらのソースコードが出てくることはなさそうなので、手詰まり。

gappend-map を gconcatenate で / Re: 特定のビット列が現れる場所を探す (Gauche)

Gauche の最新 HEAD で gconcatenate 手続きが追加されました。

というわけで、gappend-map と byte-generator->bit-generator は以下のように書き直せます。
ただし、gappend-map は proc 内で返す物がリストではなくジェネレータになります。

(define (gappend-map proc . gens)
  (gconcatenate (apply gmap proc gens)))

(define (byte-generator->bit-generator gen)
  (gappend-map (^x (list->generator (integer->list x 8))) gen))

特定のビット列が現れる場所を探す (Gauche)

ジェネレータ、遅延シーケンス、パターンマッチングを使ってみました。
書いた後に気づいた、Shiro さんによる gappend-map (& byte-generator->bit-generator) の別解 (http://blog.practical-scheme.net/shiro/20120217-nash-cipherer, bytes->bools)。継続が使用されない分、こっちのほうが速そう。

最後のテストで "hogefuga" という文字列の (Gauche 内部エンコーディングな) ビット列から、010110 というパターンを探しています。
その結果、3 バイト目の 1 (= 7 - 6) ビット目に見つかったことが分かります。
遅延シーケンスを用いているので、その先のビット列は読み込まれないため、巨大なファイルでも安心。
ちなみに Gauche リファレンスの遅延シーケンスの項目に書かれている内容そのままです。てへぺろ

IronScheme を使ってみた

C# から手軽に Scheme のコードを呼び出したくて。


IronScheme をインストールした後、.NET なプロジェクトに IronScheme.dll と IronScheme.Closures.dll を参照設定に追加すれば一通り使えました。

using System;
using IronScheme;
using IronScheme.Runtime;

static void Main(string[] args) {
    Console.WriteLine("(+ 1 2)".Eval()); // => 3
    Console.ReadKey();
}

あっけなく。


次に Scheme 側の手続きを C# で呼び出してみます。

"(define (add-one-func i) (+ i 1))".Eval();
var addOneFunc = "add-one-func".Eval<Callable>();
Console.WriteLine(addOneFunc.Call(0)); // => 1
Console.WriteLine(addOneFunc.Call(5)); // => 6
Console.WriteLine(addOneFunc.Call(10)); // => 11

あっけなく。


最後に C# 側の関数 (Func) を Scheme で呼び出してみます。

int i = 0;
Func<int> incrementFunc = () => i++;

"(define increment-func {0})".Eval(incrementFunc.ToSchemeProcedure());

が、no expression in body と怒られてしまいます。
IronScheme の Eval() が悪さをしているっぽいのですが (引数で渡したオブジェクトがいったんシンボルになってる)、Scheme レベル 0 なのであんまり深く追っていません。

"(define increment-func '())".Eval();
"(set! increment-func {0})".Eval(incrementFunc.ToSchemeProcedure());
Console.WriteLine("(increment-func)".Eval()); // => 0
Console.WriteLine("(increment-func)".Eval()); // => 1
Console.WriteLine("(increment-func)".Eval()); // => 2
Console.WriteLine("(increment-func)".Eval()); // => 3

とすると、とりあえず動きました。


他にも、

foreach (var val in "'(1 2 3 4 5)".Eval<Cons>()) {
    Console.WriteLine(val);
}

のように、Cons が IEnumerable だったりして面白いです。


IronScheme 自体の初期化が重いのが難点ですが、常駐系のアプリであれば結構使えそうです。