Login | Register   
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

iPhone: Implementing Voice Chatting over Bluetooth

Another cool feature of the GameKit framework is the support for voice chat.


advertisement
In my previous iPhone article, you learned about Bluetooth communication between two devices using the GameKit framework. Another cool feature of the GameKit framework that you will explore in this article is the support for voice chat.

Figure 1. Button Up: Populate the View window with three Round Rect Button views.

The Voice Chat Service in the GameKit allows two iPhones/iPod Touches to establish a voice chat. The voice chat takes place over either an Internet or Bluetooth connection. In this article, you will see how you can implement voice chatting over a Bluetooth communication channel.



Creating the Project

Let's start by creating an iPhone application using Xcode. Using Xcode, create a new View-based Application project and name it as Bluetooth.

Add a new Framework to the project by right-clicking on the Frameworks group in Xcode and selecting Add, Existing Frameworks. Select GameKit.framework. Also, add the AVFoundation.Framework.

In the BluetoothViewController.h file, add the following statements in bold:

#import <UIKit/UIKit.h> #import <GameKit/GameKit.h> #import <AVFoundation/AVFoundation.h> @interface BluetoothViewController : UIViewController    <GKVoiceChatClient> {    GKSession *currentSession;        IBOutlet UIButton *connect;    IBOutlet UIButton *disconnect; } @property (nonatomic, retain) GKSession *currentSession; @property (nonatomic, retain) UIButton *connect; @property (nonatomic, retain) UIButton *disconnect; -(IBAction) btnMute:(id) sender; -(IBAction) btnUnmute:(id) sender; -(IBAction) btnConnect:(id) sender; -(IBAction) btnDisconnect:(id) sender; @end

Figure 2. Verify: Verify the connections for the outlets and actions.

Drag and drop a WAV file (in this article the WAV file is beep.wav) onto the Resources folder in Xcode.

Double-click the BluetoothViewController.xib file to edit it in Interface Builder. Populate the View window with the three Round Rect Button views (See Figure 1).

In the BluetoothViewController.xib window, perform the following connections:
    * Control-click the File's Owner item and drag and drop it over the Connect button. Select connect.

    * Control-click the File's Owner item and drag and drop it over the Disconnect button. Select disconnect.

    * Control-click the Connect button and drag and drop it over the File's Owner item. Select btnConnect:

    * Control-click the Disconnect button and drag and drop it over the File's Owner item. Select btnDisconnect:

    * Right-click on the Mute button and connect the Touch Down event to the File's Owner item. Select btnMute:

    * Right-click on the Mute button and connect the Touch Up Inside event to the File's Owner item. Select btnUnmute:

To verify that all the connections are made correctly, right-click on the File's Owner item and view its connections (see Figure 2).

In the BluetoothViewController.m file, add the following statements in bold:

#import "BluetoothViewController.h" #import <GameKit/GameKit.h> #import <AVFoundation/AVFoundation.h> @implementation BluetoothViewController @synthesize currentSession; @synthesize connect; @synthesize disconnect; GKPeerPickerController *picker; NSString *recorderFilePath; AVAudioPlayer *audioPlayer; - (void)viewDidLoad {    [connect setHidden:NO];    [disconnect setHidden:YES];    [super viewDidLoad]; } - (IBAction) btnConnect:(id) sender {    //---Select a nearby Bluetooth device---    picker = [[GKPeerPickerController alloc] init];    picker.delegate = self;    picker.connectionTypesMask = GKPeerPickerConnectionTypeNearby;          [connect setHidden:YES];    [disconnect setHidden:NO];        [picker show];     } -(IBAction) btnDisconnect:(id) sender {    //---disconnected from the other device---    [self.currentSession disconnectFromAllPeers];    [self.currentSession release];    currentSession = nil;        [connect setHidden:NO];    [disconnect setHidden:YES]; } - (void)peerPickerController:(GKPeerPickerController *)picker              didConnectPeer:(NSString *)peerID                   toSession: (GKSession *) session {    self.currentSession = session;    session.delegate = self;    [session setDataReceiveHandler: self withContext:nil];    picker.delegate = nil;    [picker dismiss];    [picker autorelease];     }     - (void)peerPickerControllerDidCancel:(GKPeerPickerController *)picker {    picker.delegate = nil;    [picker autorelease];        [connect setHidden:NO];    [disconnect setHidden:YES]; } -(IBAction) btnMute:(id) sender {    //---mute the voice chat---    [GKVoiceChatService defaultVoiceChatService].microphoneMuted = YES;     } -(IBAction) btnUnmute:(id) sender {    //---unmute the voice chat---    [GKVoiceChatService defaultVoiceChatService].microphoneMuted = NO; } //---returns a unique ID that identifies the local user--- -(NSString *) participantID {    return currentSession.peerID; } -(void) voiceChatService:(GKVoiceChatService *) voiceChatService                sendData:(NSData *) data         toParticipantID:(NSString *)participantID {    [currentSession sendData:data toPeers:        [NSArray arrayWithObject:participantID]        withDataMode:GKSendDataReliable error:nil]; } - (void)session:(GKSession *)session           peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state {    switch (state)    {        case GKPeerStateConnected:        {                        //---plays an audio file---            NSString *soundFilePath = [[NSBundle mainBundle]                pathForResource:@"beep" ofType:@"wav"];            NSURL *fileURL = [[NSURL alloc]                initFileURLWithPath: soundFilePath];            AVAudioPlayer *audioPlayer =                [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL                     error:nil];            [fileURL release];            [audioPlayer play];                                    NSError *error;            AVAudioSession *audioSession =                [AVAudioSession sharedInstance];            if (![audioSession                  setCategory:AVAudioSessionCategoryPlayAndRecord                error:&error]) {                NSLog(@"Error setting the AVAudioSessionCategoryPlayAndRecord category: %@",                [error localizedDescription]);            }                        if (![audioSession setActive: YES error: &error]) {                NSLog(@"Error activating audioSession: %@",                    [error description]);            }                        [GKVoiceChatService defaultVoiceChatService].client = self;                        //---initiating the voice chat---            if (![[GKVoiceChatService defaultVoiceChatService]                 startVoiceChatWithParticipantID:peerID error:&error]) {                NSLog(@"Error starting startVoiceChatWithParticipantID: %@",                    [error userInfo]);            }        } break;                    case GKPeerStateDisconnected:        {            [[GKVoiceChatService defaultVoiceChatService]                stopVoiceChatWithParticipantID:peerID];                        [self.currentSession release];            currentSession = nil;                                [connect setHidden:NO];            [disconnect setHidden:YES];                } break;    } } - (void) receiveData:(NSData *)data            fromPeer:(NSString *)peer           inSession:(GKSession *)session             context:(void *)context {    //---start the voice chat when initiated by the client---    [[GKVoiceChatService defaultVoiceChatService]        receivedData:data fromParticipantID:peer]; } - (void)dealloc {    if (currentSession) [currentSession release];    [connect release];    [disconnect release];    [super dealloc]; } @end

Understanding How Voice Chat Works

When two Bluetooth devices are connected, you first play the beep sound and start the audio session (you do so via the session:peer:didChangeState: method):

NSString *soundFilePath = [[NSBundle mainBundle]                pathForResource:@"beep" ofType:@"wav"];            NSURL *fileURL = [[NSURL alloc]                initFileURLWithPath: soundFilePath];            AVAudioPlayer *audioPlayer =                [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL                     error:nil];            [fileURL release];            [audioPlayer play];                                    NSError *error;            AVAudioSession *audioSession =                [AVAudioSession sharedInstance];            if (![audioSession                  setCategory:AVAudioSessionCategoryPlayAndRecord                error:&error]) {                NSLog(@"Error setting the AVAudioSessionCategoryPlayAndRecord category: %@",                [error localizedDescription]);            }                        if (![audioSession setActive: YES error: &error]) {                NSLog(@"Error activating audioSession: %@",                    [error description]);            }                        [GKVoiceChatService defaultVoiceChatService].client = self;

Tip: Interestingly, if you do not start the audio player, the voice chat will not work.

You then retrieve a singleton instance of the GKVoiceChatService class and call its startVoiceChatWithParticipantID:error: method to start the voice chat:

if (![[GKVoiceChatService defaultVoiceChatService]                 startVoiceChatWithParticipantID:peerID error:&error]) {                NSLog(@"Error starting startVoiceChatWithParticipantID: %@",                    [error userInfo]);            }

Calling the startVoiceChatWithParticipantID:error: method: will invoke the voiceChatService:sendData:toParticipantID: method, which will make use of the current Bluetooth session to send the configuration data to the other connected device:

-(void) voiceChatService:(GKVoiceChatService *) voiceChatService                sendData:(NSData *) data         toParticipantID:(NSString *)participantID {    [currentSession sendData:data toPeers:        [NSArray arrayWithObject:participantID]        withDataMode:GKSendDataReliable error:nil]; }

The other connected device, when it has received the configuration data, starts the voice chat service by calling the receivedData:fromParticipantID: method:

- (void) receiveData:(NSData *)data            fromPeer:(NSString *)peer           inSession:(GKSession *)session             context:(void *)context {    [[GKVoiceChatService defaultVoiceChatService]        receivedData:data fromParticipantID:peer]; }

The GKVoiceChatService uses the configuration information that was exchanged between the two devices and creates its own connection to transfer voice data. You can also mute the microphone by setting the microphoneMuted property to YES:

[GKVoiceChatService defaultVoiceChatService].microphoneMuted = YES;

Testing the Application

Figure 3. Chatter: The Griffin iTalk Pro.

To test the application, deploy the application onto two devices - either iPhone or iPod Touch. For iPod Touch, you need to connect it to an external microphone as it does not come with one. A good recommendation is the Griffin iTalk Pro (see Figure 3) - a mic and microphone accessory that plugs into the base of the iPod Touch. Alternatively, the Apple Earphones with Remote and Mic also works.

With the application deployed on the two devices, run the application and press the Connect button to use Bluetooth to connect to each other. As soon as the two devices are connected, you can now start chatting! To temporarily mute the conversation, press and hold on the Mute button. When it is released, the conversation resumes. Have fun!

In this article, you saw how the GameKit framework provides the GKVoiceChatService class that makes voice communication between two devices seamless. There is no need for you to know how the voices are transported between two devices - all you need to know is to call the relevant methods to initialize the chat. One important point you need to know though - voice chat works not just over Bluetooth; it works over any communication channel. In fact, if you have two devices connected using TCP/IP, you too can stream the voices over the wire. I will explore this in more detail in a future article.


   
Wei-Meng Lee is a Microsoft MVP and founder of Developer Learning Solutions, a technology company specializing in hands-on training on the latest Microsoft technologies. He is an established developer and trainer specializing in .NET and wireless technologies. Wei-Meng speaks regularly at international conferences and has authored and coauthored numerous books on .NET, XML, and wireless technologies. He writes extensively on topics ranging from .NET to Mac OS X. He is also the author of the .NET Compact Framework Pocket Guide, ASP.NET 2.0: A Developer's Notebook (both from O'Reilly Media, Inc.), and Programming Sudoku (Apress). Here is Wei-Meng's blog.
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap