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. |
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. |
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 #import @implementation [email protected] 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. |