うならぼ

申し訳程度のアフィリエイトとか広告とか解析とかは/aboutを参照

async/awaitで一時停止可能なメソッドを作ってみる

サンプルは「呼び出すたびに開始と再開を繰り返すコルーチン的なもの」です。ボタンのイベントハンドラに割り当てると楽しいかもしれません。

前にも EnumerableEx.Create を作るためにawaitableなクラスを作りましたが、その時と比べると随分シンプルです。

public interface IAwaitable : ICriticalNotifyCompletion
{
    IAwaitable GetAwaiter();
    bool IsCompleted { get; }
    void GetResult();
}

public class YieldState : IAwaitable
{
    private Action _continuation;

   #region IAwaitable
    IAwaitable IAwaitable.GetAwaiter() => this;
    bool IAwaitable.IsCompleted => false;
    void IAwaitable.GetResult() { }
    [System.Security.SecurityCritical]
    void ICriticalNotifyCompletion.UnsafeOnCompleted(Action continuation) { _continuation = continuation; }
    void INotifyCompletion.OnCompleted(Action continuation) { _continuation = continuation; }
   #endregion

    public IAwaitable Yield() => this;
    public bool TryResume()
    {
        if (_continuation != null)
        {
            var cont = _continuation;
            _continuation = null;
            cont();
            return true;
        }
        else return false;
    }
}

で、こんな感じに使います。

void Main()
{
    for (int i = 0; i < 6; ++i)
        Foo();
}
YieldState fooState = new YieldState();
async void Foo()
{
    if (fooState.TryResume()) return;

    for (int i = 0; i < 2; ++i)
    {
        Console.WriteLine(i);
        await fooState.Yield();
    }
    Console.WriteLine("done.");
}

この実装は同期コンテキストの調整をしていませんから、TryResume() したコンテキストで続きが実行されることになります。まあイベントハンドラみたいな場面では特に問題ないですね。

例外処理もイベントハンドラがTaskを返せないので無視していますが、async Task に変えたうえでawaitなりContinueWithすれば処理することができます。再開した場所で捕捉したい場合は・・・前回の MoveNext() のように、Taskに格納された例外を投げなおすような実装が必要になります。

ちなみに、別の場所で立てるフラグを非同期に待ちたいだけなら、こんなことをせずとも TaskCompletionSource を使えばいいです。

BluetoothヘッドホンがA2DPで対応しているコーデックを調べる

初めてBluetoothヘッドホンを買いました。今になって思うと下調べが足りなかった気もする。

まあともかく、高音が足りない、いや全体的にこもってる感じ?いやビットレートが低い感じ??等々思い出して調べ始めたわけです。

A2DPで最低限サポートする必要があるコーデックはSBCで、その他にAACとかあります。iPhoneとかだとAACに対応してます。まあ詳しくは別途ググっていただくとして。

調べかた

ネゴシエーションを行う際にSink側でサポートするコーデックを問い合わせているので、それを覗きます。ググってみるとroot取ったAndroidでの情報が見つかりました。

bluetooth - How do I determine which A2DP codecs my phone supports/is currently using? - Android Enthusiasts Stack Exchange

Linux環境だとhcidumpというツールでダンプできますが、そもそもWiresharkが直接キャプチャできます。

接続の前後をキャプチャしておき、btavdtp でフィルタをかけると、 GetAllCapabilities というのが見つかります。中身はこんな感じ。

- Bluetooth AVDTP Protocol
 + Signal: GetAllCapabilities (ResponseAccept)
 - Capabilities
  + Service: Media Transport
  + Service: Media Codec - Audio SBC (16000 32000 44100 48000 | Mono DualChannel Stereo JointStereo | block: 4 8 12 16 | subbands: 4 8 | allocation: SNR Loudness | bitpool: 2..53)
  + Service: Content Protection - SCMS-T
  + Service: Delay Reporting

見事にSBCしか対応してませんね。ついでに対応するSetConfigurationも見てみましょう。

- Bluetooth AVDTP Protocol
 + Signal: SetConfiguration (Command)
 + ACP SEID [1 - Audio Sink]
 + INT SEID [1 - Audio Sink]
 - Capabilities
  + Service: Media Transport
  + Service: Media Codec - Audio SBC (44100 | JointStereo | block: 16 | subbands: 8 | allocation: Loudness | bitpool: 2..53)

はい。

あとがき

私が試した時、最初はWiresharkで直接キャプチャしようとすると Invalid argument といったエラーが出たり、hcidumpでキャプチャしてもペアリングや接続までは取れるのに Exit Sniff Mode といったメッセージを最後に途絶えてしまう、といったことがありました。直前に Encryption Change があったので試しに hcictl hci0 noencrypt とかやっていたら改善したものの、encryptに戻しても再発しないので関係ないのかも・・・?

なおきっかけとなった音質の話ですが、sbcencで上記のパラメータでエンコードし、これまで使っていた有線のヘッドホンで聞いてみたところ、特に問題は感じませんでした。bitpoolを下げてみてもまた違った劣化の仕方でしたし、結局BluetoothであることやSBCであることには関係なく、このヘッドホンの特性だったようで。

あとこのためにpulseaudioを導入しました。

参考 BluetoothのSBCって何ですか? - afnf.net