A2DP [VGC-RM95S]
MS STack で "ワイヤレスステレオ" と "ハンズフリー" の Shink 側を実現する道のりであるが、難関は A2DP である。SCO の場合は PSM を意識することがなかったが L2CAP では、明示しないといけない。
brb->BtAddress = 0; //BTH_ADDR_NULL;
brb->PSM = 0x19; //we have already registered the PSM
SDP に A2DP を登録して、プロトコルとしては AVDTP を実装する方法をとってみた。正確には AVDTP のドライバも登録して呼び出すのが正統かもしれない。
00000019-0000-1000-8000-00805F9B34FB AVDTP
0000110b-0000-1000-8000-00805f9b34fb A2DP
スマホの動きを見ていると、ペアリングの時に以下の2つが飛んで来ている。
AVDTP_DISCOVER
AVDTP_GET_CAPABILITIES
来ているかどうかのの確認は、カーネル内部の以下を BP すると分かる。
bthport!L2CapInt_ProcessL2capConnectReq
bthport!BthFindChildPdoFromPsm
bthport!RBTreeFindNodeRecurse
ここからは結構ややこしいが、電文自体が短いので、なんとかなるかもしれない。
selfsign_example.cmd [VGC-RM95S]
driver の sign のサンプルが以下のところにあるようだ。
C:\WinDDK\7600.16385.1\src\general\build\driversigning
バージョンごとに違うので、困ったものだ。
署名について Verisign 流儀にこだわらなければ、以下の2つのコマンドでいいようだ。
makecert -r -pe -ss MyTempCert -n "CN=bourbaki.com" MyTempCert.cer
signtool sign /a /s MyTempCert /n "bourbaki.com" BthScoAudioSrv.sys
通常は赤い警告なのだが、たまに緑の警告画面が出てくるのはなぜだろう。
SCO sample application [VGC-RM95S]
家族のスマホでデバイスチェンジの試験をやってみた。アプリケーション側は以下のシーケンスで問題ないようだ。
=======================================
/* SCO device open */
hDevice = CreateFile(deviceInterfaceDetailData->DevicePath, ...);
/* RFCOMM socket open */
m_socketClient = socket (AF_BT, SOCK_STREAM, BTHPROTO_RFCOMM);
sa.addressFamily = AF_BT;
sa.serviceClassId=ServerGuid;
sa.btAddr=m_saClient.btAddr;
sa.port = SDPGetPort(btaddr, (LPGUID) &ServerGuid);
// SCO server accept address setting
DeviceIoControl(hDevice, &address, ...);
// RFCOMM client connect
connect (m_socketClient, (SOCKADDR *)&sa, sizeof(sa));
========================================
IoControl の SCO ドライバ内部では、デバイスチェンジの時に、以下のような Unregister と Register で接続要求を受け付けるアドレスを変更する。
DevCtx = GetServerDeviceContext(WdfIoQueueGetDevice(Queue));
BthSrvUnregisterScoServer(DevCtx);
BthSrvRegisterScoServer(DevCtx);
L2CAP のサンプルどおりだと起動時に ADDRESS_ANY で Register するようになっているが、SCO ではエラーになるので target_address = 0xffffffffffff; をダミーで登録するようにしてみた。
brb->BtAddress = target_address;
ドライバ内部は、本当に細かな操作が必要になってくる。ちょっと気を抜くと BSOD となってしまう。
PAGE_FAULT_IN_NONPAGED_AREA [VGC-RM95S]
【user-land】
DeviceIoControl(hDevice,
0x00074098,
replyData_2,
replyLeng_2,
voice_buf,
IRPLEN,
&voice_len, NULL);
voice_buf を 16KB アローケーションしておいて IRPLEN = 0x100 を指定する。
【kernel-land】
WdfRequestRetrieveOutputBuffer( // recv voice
Request,
rx_len,
&pRecv,
&iRet
);
ここで pRecv から 0x100 を超えるデータをコピーすると BSOD になる。 Mutex, SpinLock のかけ方によりエラーの種別は異なって表示される。当然のことに見えるが voice_buf が十分な連続領域であるという情報は kernel-land にはいかない。うっかりすると間違い安いミスと思う。
MS Hands-Free Profile [VGC-RM95S]
SCO_TRANSFER_DIRECTION_OUT, 0xc000020c [VGC-RM95S]
また EVT_WDF_IO_QUEUE_IO_WRITE 経由で出力しようとすると IoWritre まで来ない。これは
Wdf01000!FxIoQueue::DispatchRequestToDriver
の中で フラグのチェックをおこなっており 以下で 1 にすると call back される。
cmp byte ptr [rdi+93h],r13b
すんなりとはいかないものだ。
MM_WOM_DONE [VGC-RM95S]
マルチバッファによる waveOut をやると音声の途切れはなくなった。 NTT 117 で時報を聞くと、約1秒遅れている。やはりシャーというノイズが入っている。 DigionSound5 で生のデータを聞くと同じように聞こえる。 sox で変換して media_player で聞くと消えている。どこかでノイズカットしているのだろう。
【MM_WOM_DONE でやること】
//再生終了したバッファの処理
for(k=0; k<BNUM; k++){
if((LPWAVEHDR)lp==&whdr[k]){
buf_num=k;
break;
}
}
memcpy(whdr[buf_num].lpData, buf, sum_read);
waveOutWrite((HWAVEOUT)wp,(LPWAVEHDR)lp,sizeof(WAVEHDR));
//再生中バッファの処理
buf_num=(buf_num+1)%BNUM;
SCO HV3 [VGC-RM95S]
sox -t raw -r 8000 -c 1 -w -s audio.raw -w -s a.wav
HCI レイヤのヘッダなどはカットされてくるので、そのままファイル化すればよいようだ。ユーザランドに転送する部分は、考えすぎると泥沼にはまってしまうので、シンプルに考えたところうまくいった。
結論からいえば、ドライバのサンプルプログラムとしてもよいぐらいのレベルではないだろうか。 WinsSock を使う制御部分とは、別プロセスで SCO HV3 を受信できるので、64 bit media player の plugin でも実現できるだろう。
DigiOnSound5 for VAIO で読み込ませたときの設定は、以下のようになっている。
waveOutWrite で出力する場合の設定は以下のようになっている。
wf.wFormatTag = WAVE_FORMAT_PCM; // PCM形式
wf.nChannels = 1; // ステレオかモノラルか
wf.nSamplesPerSec = 8000; // サンプリングレート 8.0KHz
wf.wBitsPerSample = 16; // 量子化レベル
wf.nBlockAlign = wf.wBitsPerSample * wf.nChannels / 8; // バイトあたりのビット数[PCMの仕様]
wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign; // 1 秒あたりのバイト数
BRB_SCO_OPEN_CHANNEL_RESPONSE [VGC-RM95S]
RBR の dispatch は以下の階層でおこなっているが、内部でスレッド化しているので完全な追跡は難しい。
bthport!BthHandleBrbDispatch
bthport!BthDispatchBrb
bthport!BthCompleteRequestEx
BRB_SCO_OPEN_CHANNEL_RESPONSE をマニュアルどおりに任意に設定すると c0000030 や c000000d のエラーとなる。以下の設定で音声データの受信まで確認できた。
brb->BtAddress = ConnectParams->BtAddress;
brb->TransmitBandwidth = 8000;
brb->ReceiveBandwidth = 8000;
brb->MaxLatency = 0xffff;
brb->PacketType = SCO_HV3;
brb->ContentFormat = SCO_VS_SETTING_DEFAULT;
brb->Reserved = 0;
brb->ChannelFlags = SCO_CF_LINK_SUPPRESS_PIN;
brb->CallbackFlags = SCO_CALLBACK_DISCONNECT;
brb->Callback = &BthEchoSrvConnectionIndicationCallback;
brb->CallbackContext = connectionObject;
brb->ReferenceObject = (PVOID) WdfDeviceWdmGetDeviceObject(DevCtx->Header.Device);
brb->ChannelHandle = ConnectParams->ConnectionHandle;
brb->Response = SCO_CONNECT_RSP_RESPONSE_SUCCESS;
MS Stack は 64bit での動作も可能で、複数のスマホからの受信もさばけるので、やはり捨てがたい。
MS Stack SCO connection request [VGC-RM95S]
MS Stack でなんとか Hands Free をと思い SCO link のカーネル内部を追跡していると、やはり来ていた。
bthport!ScoCxnS_ConnectionRequestEvent
ここに bp をかけると break することを確認できた。SCO の driver もどうにか動き始めたので、どのよう driver とつながるかを追いかけてみる。
0: kd> k
Child-SP RetAddr Call Site
fffff800`00b9c5b8 fffff880`02ce238b bthport!ScoCxnS_ConnectionRequestEvent
fffff800`00b9c5c0 fffff880`02cd8e79 bthport!Fn_EVENT_ConnectionRequest+0x127
fffff800`00b9c670 fffff880`02cee08b bthport!HCI_DoCmdCompletion+0x3d1
fffff800`00b9c750 fffff880`02cfb832 bthport!HCI_ProcessAsynchronousEvent+0x97
fffff800`00b9c7a0 fffff880`02cfb967 bthport!HCI_ProcessEventAtDPC+0x182
fffff800`00b9c810 fffff880`02cc9201 bthport!HCI_ProcessMpBip+0xef
fffff800`00b9c880 fffff880`0686c01e bthport!BTHPORT_RecvMpBip+0x41
fffff800`00b9c8d0 fffff880`0687299f BTHUSB!BthUsb_EventTransferComplete+0xca
fffff800`00b9c930 fffff880`06872c31 BTHUSB!UsbWrapWorkRoutine+0x17b
fffff800`00b9c9b0 fffff800`030d0516 BTHUSB!UsbWrapInterruptReadComplete+0x1dd
fffff800`00b9ca40 fffff880`05d2c5d9 nt!IopfCompleteRequest+0x3a6
fffff800`00b9cb20 fffff880`05d2cab7 USBPORT!USBPORT_Core_iCompleteDoneTransfer+0xa15
fffff800`00b9cc00 fffff880`05d2a64f USBPORT!USBPORT_Core_iIrpCsqCompleteDoneTransfer+0x3a7
fffff800`00b9cc60 fffff880`05d1bf89 USBPORT!USBPORT_Core_UsbIocDpc_Worker+0xf3
fffff800`00b9cca0 fffff800`030d95dc USBPORT!USBPORT_Xdpc_Worker+0x1d9
fffff800`00b9ccd0 fffff800`030d66fa nt!KiRetireDpcList+0x1bc
fffff800`00b9cd80 00000000`00000000 nt!KiIdleLoop+0x5a