Discussing the nuts and bolts of software development

Tuesday, May 15, 2007

 

Bluetooth HandsFree Profile Support for Windows Mobile 5 - The Series (part 3)

In the last two articles, we talked about the different ways to interact with Microsoft's Audio Gateway (AG). We then focused on how to start writing your own light-weight Audio Gateway, since some versions of the PPC and Smartphone SDK do not include mechanisms to support control of the AG. In the second article, we took a system level view of the AG core and gave enough detail to create a Parser and Interface Module.

Today, we'll get to the real guts, and this is what heroes are made of: the Handler Module. However, be warned that this is not for the timid, nor the faint of heart. We'll dive into the deep end right away!

This third and last installment will discuss how to create the Handler Module, which includes processing the services and network commands from the Interface module; processing the AT Commands from the Parser Module, starting the service/control level socket connection with the Bluetooth Device; and routing audio to the Bluetooth chip.

Yeah, that's right--what you've been waiting for! With this information, you can do loads of cool things like streaming mp3 music from your mobile device to your bluetooth headset. Wow! Imagine what else you can do with a little imagination!

The Guts of the Handler Module


The guts of the Handler Module contains four small sub-modules, the connection processing, the AT command processing, network notification processing and the services processing. We'll break down these four sub-modules in this article.

Creating a Socket Connection with the Bluetooth HandsFree device:


First, we assume that the Bluetooth device has already been paired with WM5 device, if not then do so right now! Start->Settings->Connections->Bluetooth... you know the rest, if not consult your WM5 Device User's Manual on how to pair with a Bluetooth device.

Once the pair has been completed and Microsoft's AG has been turned off (See the previous article), we're ready to create a service level connection with the Bluetooth device.

The pairing of the Bluetooth device will create an entry in the registry, under [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Bluetooth\\AudioGateway\\Devices]. In this registry folder will be numbered folders for the different Bluetooth devices that have been paired with the WM5 device. These folders consist of a socket address and the associated service value which the connection offers, in our case, the Handsfree profile service. To create a service/control-level socket connection, first iterate through the numbered folders to get the right address and service that corresponds to the device that you want to create a connection with. Then, simply connect with the Bluetooth device by using the standard winsock socket connection scheme as shown in the code below:

// make sure to include these
#include <Winsock2.h>
#include <ws2bth.h>

// in a function to create the connection, do this
// create a Socket
m_socket = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
if (INVALID_SOCKET == m_socket)
{
// Failed to create socket: Log something here with GetLastError();
return;
}

// Fill the socket addr structure with the bluetooth device information
SOCKADDR_BTH socketAddr = {0};
socketAddr.serviceClassId = /*Service value from the registry goes here*/;
socketAddr.addressFamily = AF_BTH;
socketAddr.btAddr = /*Address from the registry goes here*/;

// connect to the device
if( SOCKET_ERROR == connect(m_socket, (SOCKADDR *)&m_addr, sizeof(m_addr)) )
{
// Failed to connect: Log something here with GetLastError();
return;
}
Simply put, get the registry value, create the socket, fill the SOCKADDR_BTH structure with the registry value and try to connect to the Bluetooth device.

Parse AT Command Processing and Initialization Hand-shaking


So, we've created a service/control level connection with the Bluetooth device, now what? Well, once the Bluetooth device has detected a connection, it will send initialization codes as AT Commands. It's the Parser's job to parse these initialization codes and send them to us to send to the Handler Module for processing.

To receive these commands, we'll need to receive, from the socket, i.e.
// create an array of char with a size of RECEIVE_BUFFER_SIZE (that's your define)
// with s(the socket that was just connected)
int result = recv(s, buffer, RECEIVE_BUFFER_SIZE, 0);

And subsequently send out responses through the same socket the AT Command responses, i.e.
// "command" is a std::string and "s" is the socket
// that has the connection to the Bluetooth device
::send(s, command.c_str(), command.size(), 0);


The initialization hand-shaking is outlined below. However, for a detailed overview of this call flow go to the Bluetooth website .
        Headset                 AG
| |
| AT+BRSF=<supported> |
|--------------------->| 1.
| |
| +BRSF:<AG supported> |
|<---------------------|
| OK |
|<---------------------|
| |
| AT+CIND=? |
|--------------------->| 2.
| |
| +CIND:... |
|<---------------------|
| OK |
|<---------------------|
| |
| AT+CIND |
|--------------------->| 3.
| |
| +CIND:... |
|<---------------------|
| OK |
|<---------------------|
| |
| AT+CMER= |
|--------------------->| 4.
| OK |
|<---------------------|
| |
| |

Note that the OK command send string as follows: "\r\nOK\r\n"

1. The Headset first sends the AG the features that it supports and in turn the AG sends back features that are supported.

Listed below are constants to define the end of the AT command (after the "=" sign)

#define HF_FEATURE_EC_ANDOR_NR                   0x01
#define HF_FEATURE_CALL_WAITING_THREEWAY_CALLING 0x02
#define HF_FEATURE_CLI_PRESENTATION 0x04
#define HF_FEATURE_VOICE_RECOGNITION_ACTIVATION 0x08
#define HF_FEATURE_REMOTE_VOLUME_CONTROL 0x10

#define AG_FEATURE_THREE_WAY_CALLING 0x01
#define AG_FEATURE_EC_ANDOR_NR 0x02
#define AG_FEATURE_VOICE_RECOGNITION 0x04
#define AG_FEATURE_INBAND_RINGTONE 0x08
#define AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG 0x10
#define AG_FEATURE_REJECT_A_CALL 0x20

These values can be OR'ed together to create the support value, e.g. if we only want to support three-way calling and reject a call features the value would be 0x21. Subsequently, if the headset only supports voice recognition activation and remote volume control, it would send to the AG 0x18. Hence, the string which would be sent out from the AG to the headset would be "\r\n+BRSF:%d\r\n", where %d is equal to the features supported.

Note the new line and carriage return before and after the command. These are classic AT Command delimiters. See the Bluetooth Spec for details.

2. The next message sent from the Headset to the AG is a request by the Headset to the AG to indicate which indicators are supported by the AG. An example of the response string is as follows: "\r\n+CIND:

(\"service\",(0,1)),(\"call\",(0,1)),(\"callsetup\",(0-3))\r\n"

3. The headset then requests the status of those indicators. In our example we respond with the string "\r\n+CIND: %d,%d,%d\r\n", where the %d are defined:

first %d indicates that we have cellular service (from the listed values this could be 0-1)
second %d indicates that we're in a call: 0 for false (from the listed values this could be 0-1)
third %d indicates the current call set-up: 0 for idle (from the listed values this could be 0-3)

4. Finally, the headset sends a request to enable the indicator status update in the AG.

NOTE: From time to time, in my experience, what follows with some devices that the microphone volume information is sent after step 4.

ALSO NOTE: In the last article, we discussed the different AT commands which can be expected to be received by the AG, the additional commands not mentioned in this article are out of scope, i.e. "AT+BLDN" - last number dialed, which requires the developer to have some grasp of the RIL layer. However, from a purely simple view, sending back an "\r\nOK\r\n" to the headset for these commands will be sufficient. But, I've given enough information that if the developer does understand the RIL and has implemented telephony functionality, they will be able to piece together how processing of these AT commands can be done. Keep a look out for additional articles about RIL if interested.

OK, we're done with the initialization! WOW! that was a lot of information. Let's move on to network signaling.

Network Signaling of In-call States:


As mentioned in the second article from the Interface layer, there are a few messages which will be signaled by the Interface Module to the Handler module that need to be supported: Incoming Call, Outgoing Call, Call Connected, Call Disconnected, Call Rejected, Call Busy, Call Waiting and Call Ringing. Listed below are the AT Commands sent to the headset for the corresponding Network event to signal the Headset into a different state

Incoming Call
"\r\n+BSIR:0\r\n"
"\r\n+CIEV:3,1\r\n"

Outgoing Call
"\r\n+CIEV:3,3\r\n"

Call Connected
"\r\n+CIEV:2,1\r\n"
"\r\n+CIEV:3,0\r\n"

Call Disconnected and Call Rejected
"\r\n+CIEV:2,0\r\n"

Call Waiting
"\r\n+CCWA:\"0\",0,1\r\n"

Call Ringing
"\r\nRING\r\n"

Almost done! Let’s move to the final section--voice routing.

Services Processing:


In the last article, we talked about the Interface Module and how the Handler Module needs to support method calls from the Interface Layer. In this section, we'll go deeper into these calls to see how we can accurately process them. The commands are listed as:

Opening Audio
Closing audio
Opening a control channel
Closing a control channel
Getting/setting the mic volume
Getting/setting the speaker volume
Getting/setting the power mode

I leave it up to the reader to be able to handle the start and stop of your own AG service and also the mic/speaker and power getting and setting methods, since they are trivial.

Open/Close Control Channel: from the previous section, we learned how to connect to the Bluetooth socket to create a service level connection with the remote Bluetooth device. Hence, to handle the Open Control Channel command, we'll need to create some reliability and error checking around this socket connection. To close the control channel, all that is needed is to close the connected socket with a Winsock call. I leave it up to the reader to choose an appropriate method to shut down the socket connection.

Finally, the last piece is to open and close the audio, and this is not a trivial task.

First, we need to create a synchronous connection with the Bluetooth device. If you're lucky enough, you'll already have the libraries for creating this connection. Unfortunately, this is an article that describes a work-around if you don't have this library.

In the Windows directory, you'll find the DLL "btdrt.dll." You'll need to dynamically link to a few methods to get the audio connection. Listed below is example code to call the create synchronous connection and shut down the synchronous connection (which we'll need to route audio):

//******Note that the developer needs to call 
//before these calls call ::LoadLibrary(...)
//once finished with these calls call ::FreeLibrary(...)

// Code for creating a SCO connection
typedef int (*BthCreateSCOConnectionWrapper)( BT_ADDR*, unsigned short* );
BthCreateSCOConnectionWrapper proc;
HMODULE hMod;
if(NULL == (hMod = ::GetModuleHandle(L"\\Windows\\btdrt.dll")))
{
// Log something since we can't find the module
return;
}

if(NULL == (proc = (BthCreateSCOConnectionWrapper)::GetProcAddress(hMod,
L"BthCreateSCOConnection")))
{
// Log something since we can't find the method
return;
}
//ptrBTAddr is a pointer to the Bluetooth Address--the same address we created the service level connection with
//ptrHandle is a pointer to an unsigned short which will be filled with the handle to the SCO connection
proc(ptrBTAddr, ptrHandle);


// Code for closing the SCO connection
typedef int (*BthCloseConnectionWrapper)( unsigned short );
BthCloseConnectionWrapper proc;
HMODULE hMod;
if(NULL == (hMod = ::GetModuleHandle(L"\\Windows\\btdrt.dll")))
{
// Log something since we can't find the module
return;
}

if(NULL == (proc = (BthCloseConnectionWrapper)::GetProcAddress(hMod,
L"BthCloseConnection")))
{
// Log something since we can't find the method
return;
}
// handle is the pointer to the unsigned short value that was passed in to create the SCO connection
return proc( handle );


Once we have a service level connection, a SCO connection with the Bluetooth device,
we can call the audio driver to route to the Bluetooth chip.

#define WODM_BT_SCO_AUDIO_CONTROL 500
waveOutMessage(0, WODM_BT_SCO_AUDIO_CONTROL, 0, TRUE);

To close the audio channel, first signal the audio driver to stop routing to the Bluetooth chip and close the SCO connection.
waveOutMessage(0, WODM_BT_SCO_AUDIO_CONTROL, 0, FALSE);

And that's it!

I hope that this has been an adventure. Play around with what has been shown; contact me if there is anything missing. I've omitted the getting and setting of the registry values with regards to different call states, volume settings and feature supports. However, if you're interested, take a look on the MSDN websites for registry settings related to the Audio Gateway and apply them into your architecture (and AT Command processing).

Best of luck in your Bluetooth Handsfree Adventures!

If you've read this series in its entirety drop me a line, I would love to hear what you've thought about the article.

Look for more articles related to Bluetooth and Telephony in the near feature by me!

Labels: , ,


Comments:
Hello Quan,

great article :dumpup:
I've enjoyed reading a lot :)

Could you give a hint how to redirect a2dp to wiress stereo headset?
 
Hi,
Thanks for the comments. A2DP is quite involved and possibly would require an additional article(s) ;-) to describe. I would like to note that A2DP streaming audio is not done through the narrow band voice on the SCO channel. In any case it's not a trivial task to undertake. What exactly are you having trouble with? Have you referred to the bluetooth spec for A2DP profile? A2DP Profile Link
 
Hi Quan,

thanks for the link. Great info.
I tried to set up a a2dp connection and wasn't successful so far.

I would love to read an a2dp article by you :)
 
Hi Quan,

It is very useful to understand , how to switch audio between device and bluetooth hands free. I am facing issue here in DOPOD 810 is I am able to route audio from device to bluetooth hands free as your suggested way, but routing from hands free to device is something funny. I just close SCO connection and send 500 (FALSE) to driver and I loose audio from either side, what it require is stop/start audio after starting it again I am able to route. Same thing is done in default phone hands free usage, it can route audio anywhere without stop/start. Only difference I am getting here is I am able to route audio from hands free to device for cell call only but If I try same for music or audio played with WaveOut API then above problem occurs. Any idea will help me lot I stuck with it.
 
This comment has been removed by the author.
 
Hi Paresh,
So after closing the SCO conneciton calling
waveOutMessage(0, 500, 0, FALSE); gives you no audio?
Does the call fail or is it successful, waveOutMessage(...) usually comes with a return code. If it is successful quite possibly you need to call the RIL audio set audio method, i.e. call RIL_SetAudioDevices(HRIL, RILAUDIODEVICEINFO* ); which is from the ril.dll within the Windows system folder.
If you're having a hard time finding the definition of this method try finding the ril.h file on google.

Basically this translates to a IOControl call to the WAV driver to route audio
 
Hello Quan,

I am trying to switch audio from HF to device with DeviceIOControl() with IOCTL IOCTL_WAV_MESSAGE and
suggested msg value 500 with dwParam2 -> TRUE/FALSE in MMDRV_MESSAGE_PARAMS structure. I am able to route cell call audio only without STOP/START but not other audio.
 
hi this is good article but it would have been better for me if i could have it for linux. such as handfree profile implememntation on linuc system..

thanks but..
good one
thanks a lot
 
please give me some guidance for the same for linux
 
Hello QUAN,

It is really very good article.
I am quite n ew to this Bluetooth technology. However I am in need of Bluetooth API's for Application level on Linux so that with those API's I should be able to call driver level functions.Can you suggest me something.

Regards
Nisha
 
Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?