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での情報が見つかりました。
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を導入しました。