Skip to content
Naohide Sano edited this page Oct 16, 2020 · 1 revision

AndroidがAppleに恋をした?(AndroidでAirPlay)

umjammer です、 ひさびさの小ネタです。

Apple はいくつか前のバージョンで AirPlay という機能を iOS や MacOSX に搭載しました。iPhone や iTunes で再生できるメディアを Apple TV2 や AirPlay 対応オーディオ機器にネットワークを介して転送、再生できるというものです。Apple TV2 を AV システムにつないでおくと Mac 手元にオーディオコントロールできます。

ただ、大人の事情かなんなのか iTunes -> iPhone とか Apple TV2 -> iPad とかできないんですよね。ベランダで iPhone で音楽聞きながらネットブラウズとかするときに便利だと思うのは私だけでしょうか?さすがに iPhone に 2万曲入らないし。 AirPlay は一般には仕様が全く公開されていないです。魅力的な機能なのに拘束されて手も足も出ないのは面白くありません。さあ、ハッカーたちの出番です。

ビデオの方は Erica さんに即行ハックされてましたが、オーディオの方は Erica さんも諦めてたように、プロテクトが強固でした。しかし世の中には強者がいるんですねぇ、オーディ転送のプロトコルである RAOP に使用されている暗号化用の秘密鍵を取り出すのに成功しオープンソースとして公開した人がいました。 公開されたのはかなり前でしたが、Shairport が Avahi というオープンソースの Bonjour クローンを使用しているため Mac で走らせるのが面倒だったので、一回動くのを試してから放置してました。しばらくぶりにどうなってるのかとサイトを見ていたら Java に移植してる人がいるじゃありませんか!

JAirPort

  • jmdns という Pure Java の Bonjour クローンを使用している
  • iTunes に見つけてもらえない、なんで?

RPlay

  • dns_sd という Apple 製のオリジナル Bonjour 実装を JNI で使用している
  • オーディオの再生ができた!

試していてピンときました。 これは Android に移植だと!

まず実際に音の鳴った RPlay を採用し JNI の移植を試みたのですが Windows ではうまく行ったのですが Android でうまく行かなくて、デバッグする暇もないので、RPlay の dns_sd を jmdns で置き換える方法にしました。意外とあっさり動きました。 Pure Java になったので Java が動けばどこでも AirPlay 可能になりました。

# PS3 の BD-J で動かしたかったな〜

さあ Android に移植です。 オーディオ再生のところだけ API が違うので Android の API に変更します。ちょっとハマりました。Android のオーディオ API に signed, endian を指定する項目がありません。仕様書にもなにも書かれていないので地道に試すしかありません。そして何度試しても雑音しか出ないんですよね。なんでだろうと探してるとありました。AudioFormat.ENCODING_PCM_16BIT のときは AudioTrack#write(short[],,) を使用するらしいです。だから short[] なんて API があるのかよ!そりゃ endian 指定する必要ないわけだ。

# だとしたら AudioFormat.ENCODING_PCM_16BIT 指定が要らないんじゃあ?

あと Android ではバッテリー持ちのためにマルチキャストがデフォルトでは許可されていないので許可してあげます。

    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
    WifiManager.MulticastLock lock;
            :
        WifiManager wifi = (WifiManager)
            getSystemService(android.content.Context.WIFI_SERVICE);
        lock = wifi.createMulticastLock("ShairPort");
        lock.setReferenceCounted(true);
        lock.acquire();
            :
    protected void onDestroy() {
        super.onDestroy();
        if (lock != null) {
            lock.release();
        }

もうひとつ Android 2.2 だと Mac アドレスが JavaSE の様に取れないのでAndroid の API から取得します。

        WifiManager wifi = (WifiManager)
            getSystemService(android.content.Context.WIFI_SERVICE);
        WifiInfo wifiInfo = wifi.getConnectionInfo();
        macAddress = wifiInfo.getMacAddress();

完成しました。おー、ちゃんと音鳴るじゃないですか!

https://lh5.googleusercontent.com/-3KAiYMS9rdY/TmcPlX1ktDI/AAAAAAAABhQ/kZLc2TqbIlg/s400/%2525E3%252582%2525B9%2525E3%252582%2525AF%2525E3%252583%2525AA%2525E3%252583%2525BC%2525E3%252583%2525B3%2525E3%252582%2525B7%2525E3%252583%2525A7%2525E3%252583%252583%2525E3%252583%252588%2525202011-09-07%25252015.27.18.png

https://lh3.googleusercontent.com/-qzX-n0fe04c/TmcP-fHqg4I/AAAAAAAABhU/nfhzWSGA-0I/s800/%2525E5%252590%25258D%2525E7%2525A7%2525B0%2525E6%25259C%2525AA%2525E8%2525A8%2525AD%2525E5%2525AE%25259A.png

ソースはここに公開しておきます。 秘密鍵はさすがに付属する度胸はないので Shairport から拾ってきてくださいね。

まとめ

Android で AirPlay が動いちゃいました。まあ冒頭に書いたベランダの話くらいしか Android 端末では使用する場面が無いのは確かですが。 しかし Android は電話端末だけじゃないんですよね〜 先を越されたらイヤなので次に何を作るかは秘密です。できたら公開しますね。


Clone this wiki locally