Chapter 16: DirectPlay Voice

This chapter's sole purpose is to show you how to use the DirectPlay Voice API. This is a little-known component of DirectPlay that facilitates real-time chat between players in a multiplayer game. It's one of those rare technologies that's relatively easy to implement, yet adds a powerful new dimension to your game.
THE DIRECTPLAY VOICE API
It's possible that the DirectPlay Voice API owes its existence to one simple fact: multiplayer games are more fun if you can hear the other player scream. Few things are more gratifying than ambushing another player and listening to him swear as he realizes you now own him. With DirectPlay Voice, you can add this level of immersion in your game with only a few lines of code.
The Basic Concept
DirectPlay Voice works by sending voice data over a DirectPlay connection. It begins by recording the player's speech. The recording trigger can be manual (for example, the user has to press a key or a joystick button to start talking), or it can be automatic (the system starts recording when it detects a sharp change in volume on the mic). In its simplest usage, when one player starts talking, DirectPlay Voice samples his voice from the microphone (at a low sampling rate), then compresses it using a specially designed voice compression algorithm. DirectPlay Voice then sends this compressed data to the other computer via the DirectPlay networking protocol. The other computer decompresses the voice data, and plays it back out the sound card. Ideally this happens fast enough so that the chat appears instantaneous, but if there's network traffic, there might be a slight lag between when one player speaks and the other player hears that speech.
DirectPlay Voice Network Topologies
What I just described is an example of peer-to-peer DirectPlay Voice communication. In this topology, every computer is responsible for sending its player's voice data to every other computer. This means that if you're playing with five other people, when you talk, your computer must send your voice data to five different computers (see Figure 16.1).

Figure 16.1: In a peer-to-peer environment with six players, every time you talk, your voice must be sent to the five other computers.
As you can imagine, this can clog your upstream and downstream pipes. A typical compressed voice needs about 8 kilobytes per second of bandwidth (though you can use different compression codecs with bandwidth requirements ranging from 1.2kbps to 13kbps). This means you need 40kbps (8kbps 5) of network bandwidth whenever you speak, and you also need to be able to download at least 40kbps (in case everybody else speaks at once). If you're playing on a LAN, no problem—LANs have a minimum bandwidth of 10Mbps. But, if you're playing through a modem, or even through a broadband connection with slow upload speeds, a simultaneous 40kbps upload/download speed may be unattainable.For these situations, DirectPlay Voice allows you to choose one of two alternative topologies—the Forwarding Server Topology or the Mixing Server Topology.In the Forwarding Server Topology, when you speak, your computer sends your voice stream to a central voice server. The server then sends your voice to all other computers in the game (see Figure 16.2).

Figure 16.2: In a forwarding server topology, you send your voice data to a central server; the server takes care of sending it to everyone else.
This frees your computer from having to send multiple copies of the data—the voice server does that instead. That way, only the voice server needs to have fast upload speeds. Note, however, that the clients still need to have fast download speeds, because if five people talk at once, one client is going to have to be able to download five streams simultaneously (see Figure 16.3).

Figure 16.3: If everybody talks at once, even with a voice server, you'll need 40 kpbs of download bandwidth to receive all 5 streams.
Think of the Forwarding Server Topology as the "one up, many down" topology—in the worstcase scenario, each client will only upload one voice stream; however, they may be downloading many voice streams simultaneously.The third topology—the Mixing Server Topology—is the "one up, one down" topology. In this topology, the server receives data from all the clients, and decompresses the voice streams it receives. It then sends out a specially made stream to each client containing the mix of all the voices appropriate for that client (see Figure 16.4).

Figure 16.4: A mixing server sends each computer a mix of all the voices it needs.
For example, if player 1 and player 2 talk simultaneously, the server will send player 2's voice stream to player 1, player 1's voice stream to player 2, and a mix of both voice streams to all other players. That way, each client only needs to download one stream. However, the server has to work significantly harder—instead of just routing voice streams as it does in the forward server topology, it must now decompress, mix, and recompress the mixed streams for each client. The Mixing Server Topology trades processing power for smaller bandwidth utilization.Of course, the best topology to use depends on where and how the game is being played. If it's being played on a LAN, usually you'll want to just use peer-to-peer—you've got plenty of bandwidth, and peer-to-peer ensures that each computer is doing the same amount of work. If the game is being played over the Internet, the choice hinges on whether there's a dedicated server present. The mixing server takes a large chunk of processing power—large enough that for most games, it's not feasible to also run the game itself because the frame rate would be too low. If the voice server is also a game player, the forwarding server topology is the best choice. However, if the voice server is just a dedicated game server, and doesn't have to worry about pumping triangles or anything else besides networking, it can easily handle the load of mixing the voices, and the best solution is to use a mixing server topology.
Tip | Instead of just presenting these topology options to your players, you can write code that can automatically decide which topology to use, based on the other network settings your players have specified. For example, if any client is connecting to the game through a modem, your code can automatically rule out the use of the peer-to-peer topology. |
First, a Crash Course in DirectPlay
DirectPlay Voice runs atop DirectPlay. This means that if you've never written code that uses DirectPlay before, you need to understand DirectPlay's basic principles before you can get chatting through DirectPlay Voice.This isn't a book on DirectPlay, so I'm going to concentrate on the basics, and move fairly fast through this part. However, it's imperative that you understand how the core of DirectPlay works in order to properly understand how DirectPlay Voice works. So, if you're having trouble understanding things from this brief description, or you want to learn about some things that I've specifically left out (for example, lobbies or player groups), you owe it to yourself to buttress your knowledge by looking at the DirectPlay sample programs and/or picking up some additional resources more focused on DirectPlay or networking.
The Flow of Events in a DirectPlay Session
To some people, DirectPlay can be a tough nut to crack. The trick is to break it down and look at the high-level steps you need to take in order to connect and play a multiplayer game.There are three basic steps to any multiplayer game. First, you connect to the game. Then, as you're playing, your code sends and receives messages about the state of the game—for example, the position of the units in an RTS, or the fact that a player has just fired a weapon, and so on.The bulk of your game code's time is going to be spent sending and receiving messages. You need to send and receive your own message types, and you need to process the DirectPlay system messages (player joined, player disconnected, and so on) as well.
Tip | Different games use different methods of conveying information using the smallest possible messages. Most games sync up the state of the game world at the beginning of the game, and then only send and receive messages containing specific changes to the world (for example, the server will send a message to a client saying, "tank number 4 has just moved to position (5,16)"). |
Finally, there's the obligatory "uninitialize and clean up" phase. This is always important, but it's especially important here, because if you forget to do it, DirectPlay may not notice that your game has stopped, and the other people playing won't receive a notification that you've bailed.
Tip | DirectPlay, like DirectMusic, makes heavy use of GUIDs (globally unique identifiers). DirectPlay uses them to identify games, service providers, and several other things during a multiplayer game. A GUID is unique in both time and space—when you create a GUID, you have a guarantee that it's not the same as any GUID ever created, or any GUID that ever will be created. |
Now that you've got the high-level overview, let's zoom in a little closer. The following sections explain, in order, the detailed steps you should go through to use DirectPlay.
Creating the DirectPlay Interface
The first order of business is to create the interface to the DirectPlay COM object. The interface you use depends on the network topology you want for your game—peer-to-peer or client server. Table 16.1 summarizes the available interfaces.
Interface | Description |
---|---|
IDirectPlay8Peer | Use this interface for a peer-to-peer topology. |
IDirectPlay8Client | Use this interface when connecting as a client to a clientserver based topology. |
IDirectPlay8Server | Use this interface to create a new server and a new clientserver session. |
The sample programs in this book use a peer-to-peer topology, so they use an IDirectPlay8Peer interface. From this point forward, I'll describe things only in terms of that interface. However, aside from the connection process, most things translate fairly easily—all three interfaces have a nearly identical set of methods.
Selecting a Service Provider
Once you've created the appropriate DirectPlay interface for your network topology, you'll need to specify a service provider. DirectPlay can operate over different kinds of communication links—TCP/IP, as well as modems and serial connections. Each communication link has a different service provider. You'll need to enumerate the list of service providers from DirectPlay, and then present that list to your user, so he can select a service provider.You can enumerate the available server providers by calling the EnumServiceProviders method of your chosen DirectPlay interface.
Starting a Game or Connecting to One
You've enumerated the available service providers, displayed them to your player, and received his selection. In a peer-to-peer world, the next step is to ask the player whether he would like to connect to an existing game, or start a new one.You can determine what games are out there by calling the EnumHosts method of IDirectPlay8Peer. You'll get back a list of available hosts—computers running a session of your game.You may be wondering how DirectPlay knows whether a computer is running your game. It knows thanks to the power of GUIDs. Each DirectPlay application has its own GUID, and to DirectPlay internally, this GUID is the name of the game. When you call EnumHosts, you can imagine DirectPlay broadcasting a question to all computers in your local realm: "Hey, if you're hosting a game with this GUID, please identify yourself." It then collects the responses and forwards them to your code. Think of each host responding with a GUID that identifies itself, as if saying, "Hello, this is host (GUID), and I'm hosting the game you're interested in."You in turn present this list to your user, who chooses a host. You then pass the GUID of the host he selected back to DirectPlay via the Connect method of IDirectPlay8Peer, and wait for DirectPlay to return success or failure. If it returns success, you're connected, and you can start sending and receiving messages!If your user chooses to host a new game instead of connecting to an existing one, the process is easier. All you do is call the Host method of IDirectPlay8Peer, giving it a DPN_APPLICATION_DESC structure containing information about your new game (for example, the name of this particular instance of the game, the password if it's private, the maximum number of players, and so on).If the Host method returns success, you're ready to start sending and receiving messages.
Receiving Messages
Most of the data you'll send back and forth during a typical multiplayer game will be in your own format. However, there are canned messages that DirectPlay sends you and that you need to respond to. The full list of message types isn't listed here (it's in the DirectPlay docs), but Table 16.2 lists the basic set of messages you'll need to watch for.
Message | Name Description |
---|---|
DPN_MSGID_CREATE_PLAYER | DirectPlay sends your code this message when a new player joins your game. When you receive this message, you should query for the player's name and other data by calling the GetPeerInfo method of the IDirectPlay8Peer interface, then use that information to initialize your game-specific player data. |
DPN_MSGID_DESTROY_PLAYER | DirectPlay sends your code this message when a player disconnects from your game. Your job is to make sure that player is no longer referenced by your game code. |
DPN_MSGID_RECEIVE | DirectPlay sends your code this message when it receives some game-specific data from another computer. Inside this message is a pointer to the raw bytes of the data received; your job is to parse those bytes into whatever format you've created, then process that data. |
DPN_MSGID_SEND_COMPLETE | DirectPlay sends your code this message when the data that you've told it to send has actually gone out over the wire. In DirectPlay, data sends are usually asynchronous, which means once you kick them off, they happen in the background, while your game is doing other things. |
These four messages represent the most common messages you're likely to see in a DirectPlay session.When you initialize your IDirectPlay8Peer interface, one of the parameters you must provide is a pointer to a callback function that DirectPlay will call whenever a message comes in. In this respect, programming DirectPlay is a bit like programming Windows dialog boxes. Your callback function receives, as arguments, the ID of the message, along with a pointer to a message buffer.Your job in this callback function is to look at the message and decide what to do. Usually, you accomplish this by using a switch statement, as shown in the following code:
HRESULT WINAPI OnDirectPlayMessage(
PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer )
{
switch( dwMessageId ) {
case DPN_MSGID_CREATE_PLAYER:
{
// a create player message has been received!
HRESULT hr;
PDPNMSG_CREATE_PLAYER pCreatePlayerMsg;
pCreatePlayerMsg =
reinterpret_cast<PDPNMSG_CREATE_PLAYER>(pMsgBuffer);
DWORD dwSize = 0;
DPN_PLAYER_INFO* pdpPlayerInfo = NULL;
// do additional things here...
}
break;
// other cases snipped
}
return(S_OK);
}
If your callback function correctly interprets the message, you should return S_OK. You can give other return values in response to particular messages as detailed in the DirectX documentation.
Sending Messages
To send some data to the other computers in a multiplayer game, you use the SendTo method of IDirectPlay8Peer. It's very easy to figure out—it takes, as arguments, the ID of the player you want to send to (or the predefined DPNID_ALL_PLAYERS_GROUP for all players), a DPN_BUFFER_DESC structure containing a pointer to the data, and the number of bytes to send. It also takes some flags and other arguments you can use to fine-tune the behavior. For example, you can specify the DPNSEND_GUARANTEED flag to make DirectPlay guarantee that your data gets to the intended recipient.
Threads and Synchronization
One of the most difficult aspects of programming using DirectPlay is that DirectPlay is a multithreaded component. One of the most critical to understand caveats of this is that DirectPlay can call your receive callback function simultaneously from several different threads.This means that you need to make sure your callback function is thread safe. Make sure that if you're changing global data (for example, anything that isn't declared on the stack, or is declared globally), those changes are wrapped in a critical section or some other thread synchronization primitive.
Disconnecting
When your player decides to exit your game, you need to make sure you call the Close method of IDirectPlay8Peer, so that DirectPlay has a chance to let everybody else know that this player is leaving. You also need to call Close for the lobby interface, IDirectPlay8LobbiedApplication. Once you've closed both interfaces, you can release them as you would any other COM interface, namely, by calling their Release methods or by using the SAFE_RELEASE macro.
Using CNetConnectWizard
The previous three steps can take a fair amount of code. Microsoft realized this, and included as part of the DirectX SDK, a couple of C++ classes designed to make it very easy to get through the banalities of setting up DirectPlay and connecting to a game.This class is called CNetConnectWizard, and it's available inside the Samples directory of your DirectX installation. It does an excellent job of walking the user through selecting a service provider and joining an existing game, or starting a new game. The only drawback to it is that it does this through a series of regular dialog boxes. Your game may want to present options to your players using a custom GUI, with cool-looking buttons and scrollbars. There's no easy way to do this with the wizard, so in those situations you'll have to write your own code. But if you don't mind seeing regular dialog boxes during your game, you can use CNetConnectWizard to save you some time.
Creating and Initializing
CNetConnectWizard is usually created dynamically, like in the following code, taken from Ch26p1_VoiceChat:
CNetConnectWizard* g_pNetConnectWizard = NULL; // Connection wizard
g_pNetConnectWizard = new CNetConnectWizard( hInstance, NULL,
"Ch26p1_VoiceChat", &g_GameGUID );]
g_pNetConnectWizard->Init( g_pDPPeer, g_pDPLobbiedApp );
Here you can see the code creating a new CNetConnectWizard and calling its Init method. The Init method takes two interfaces—one IDirectPlay8Peer and one IDirectPlay8LobbiedApplication.I've specifically avoided talking about lobbies in general and the IDirectPlay8LobbiedApplication in particular; lobbies are an advanced feature of DirectPlay you need only concern yourself with if you want your game to be launchable from a game-browsing program. The lobby is a mechanism to pass connection and game settings from one program to another. You can read more about it in the DirectX documentation if you're interested, but all you need to know for this chapter is that the CNetConnectWizard needs a lobbied application interface.
Hooking Your Receive Callback Function
The CNetConnectWizard can only work its magic if it can process DirectPlay messages. This means that your DirectPlay receive callback must call the MessageHandler method of CNetConnectWizard, passing it the same arguments DirectPlay supplied to your callback.The IDirectPlay8LobbiedApplication interface requires a callback function, just like the IDirectPlay8Peer interface, and the CNetConnectWizard must hook this callback in the same way. Both hooks are illustrated in the following code, taken from Ch26p1_VoiceChat:
HRESULT WINAPI OnDirectPlayMessage(
PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer )
{
switch( dwMessageId ) {
case DPN_MSGID_CREATE_PLAYER:
// stripped out
break;
case DPN_MSGID_DESTROY_PLAYER:
// stripped out
break;
// other messages
}
// give the wizard a chance to react to the message
return g_pNetConnectWizard->MessageHandler(
pvUserContext, dwMessageId, pMsgBuffer );
}
HRESULT WINAPI OnDirectPlayLobbyMessage(
PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer )
{
return g_pNetConnectWizard->LobbyMessageHandler(
pvUserContext, dwMessageId, pMsgBuffer );
}
The IDirectPlay8LobbiedApplication callback function, OnDirectPlayLobbyMessage, doesn't do any processing at all—it just hands what it got directly to the wizard. The main IDirectPlay8Peer callback, however, watches for specific types of messages, processes them, and then gives the wizard a chance to do the same and return a value.
Setting Default Values and Displaying the Wizard
After you've created the CNetConnectWizard and have hooked it up to your IDirectPlay8Peer and IDirectPlay8LobbiedApplication receive callback functions, you're ready to set its default values, and tell it to display its dialogs and query the user for his choices.The following code shows how this is done:
// set defaults
g_pNetConnectWizard->SetPlayerName( "Anonymous" );
g_pNetConnectWizard->SetSessionName( "New Game" );
g_pNetConnectWizard->SetPreferredProvider( "DirectPlay8 TCP/IP Service Provider" );
// display the connection wizard
hr = g_pNetConnectWizard->DoConnectWizard( FALSE );
if (FAILED(hr)) throw(hr, "Connection wizard failed.");
if( hr == NCW_S_QUIT ) { return(0); }
As you can see, the code puts default values into the wizard by calling the SetPlayerName, SetSessionName, and SetPreferredProvider methods. These default values will be displayed in the dialog boxes presented to the user (see Figures 16.5 and Figures 16.6).

Figure 16.5: CNetConnectWizard's connect dialog.

Figure 16.6: CNetConnectWizard's new game dialog.
The DoConnectWizard is where all the fun occurs. When you call this method, the wizard dialogs are displayed, and the user inputs his choices. The DoConnectWizard method returns an HRESULT; if this failed, something unexpected has gone wrong. If the HRESULT is NCW_S_QUIT, this means the user chose to quit or close the wizard before it was finished. Usually, this means that the user wants to quit the multiplayer game altogether, so you need to back out and return him to your game's title screen, or exit, in the case of the Ch26p1_VoiceChat sample program.If the user does get all the way through the wizard, the CNetConnectWizard class will automatically call either the Host or Connect methods of IDirectPlay8Peer. In other words, it takes care of everything for you!If you want to save the values the user selected so that they're the defaults the next time the wizard is launched, you can get them by calling the GetPlayerName, GetSessionName, and GetPreferredProvider methods. Then, just store them in the registry or in a preferences file somewhere, and read them back in before you display the wizard the next time around. Also, you can call the IsHostPlayer method to determine whether the user ended up hosting a new game.
Shutting Down the Wizard
When you're all done with the wizard, cleaning it up is as easy as calling its Shutdown method, and then deleting the instance of the object itself (via a call to delete or by using the SAFE_DELETE macro).
Programming DirectPlay Voice
Whew! Now that you've had the whirlwind tour of DirectPlay, you can start on the focus of the chapter—using DirectPlay Voice to implement real-time voice chat in your games.DirectPlay Voice, in many ways, is a mirror image of DirectPlay. There is a message callback function for DirectPlay Voice messages, as well as separate interfaces for voice clients (IDirectPlayVoiceClient) and servers (IDirectPlayVoiceServer). There's also a tiny interface called IDirectPlayVoiceTest, with just one method: CheckAudioSetup. This method runs DirectPlay Voice's audio setup wizard so that users can adjust their playback and recording volumes, and specify which hardware (mic input) to record (see Figure 16.7).

Figure 16.7: The sound hardware test wizard.
The following sections detail how to program DirectPlay Voice.
Initializing DirectPlay Voice
DirectPlay Voice has no peer-to-peer interface; there are only IDirectPlayVoiceServer and IDirectPlayVoiceClient. This means that before you can initialize a DirectPlay Voice interface, you have to figure out whether you're a voice server or a client. There's no requirement that the DirectPlay server must be a DirectPlay Voice server, and there's nothing stopping a DirectPlay client from being a DirectPlay Voice server. However, the easiest way to decide is to simply mirror your DirectPlay role—if you're a host in a peer-to-peer session, or you're a DirectPlay server, you're a voice server; otherwise, you're a voice client.
Voice Messages
When you create your DirectPlay Voice interface, you specify a callback function to receive DirectPlay Voice messages. Table 16.3 shows the most common voice messages you'll receive.
DirectPlay Voice Message ID | Description |
---|---|
DVMSGID_CREATEVOICEPLAYER | An existing DirectPlay player has just activatedDirectPlay Voice and has become a DirectPlay Voice player. Inside the message is the ID of the player who upgraded to voice. You need to use this ID to set up any additional player parameters in your game to use DirectPlay Voice. |
DVMSGID_DELETEVOICEPLAYER | An existing DirectPlay player has stopped using DirectPlay Voice. Note that this doesn't mean he is no longer playing; he may still be a normal DirectPlay player. |
DVMSGID_PLAYERVOICESTART | DirectPlay Voice sends you this message when it starts playing an incoming voice stream. |
DVMSGID_PLAYERVOICESTOP | DirectPlay Voice sends you this message when it stops playing an incoming voice stream. |
DVMSGID_RECORDSTART | DirectPlay Voice sends you this message when it starts recording a voice stream from the local player. |
DVMSGID_RECORDSTOP | DirectPlay Voice sends you this message when it stops recording a voice stream. |
DVMSGID_LOSTFOCUS | DirectPlay Voice sends this message when the local player has lost the ability to record audio. Usually it means the player has switched to another application and your application no longer has free reign over the audio capture hardware. |
DVMSGID_GAINFOCUS | DirectPlay Voice lets you know when the player can once again send audio by giving you this message. Usually it means that the player has task-switched back into your game and you once again have control over the audio capture hardware. |
Testing Your Audio Setup
To get the most out of real-time chat, it's very important that your player properly calibrate his mic and speakers. You can launch the setup dialog at any time by creating a IDirectPlayVoiceTest interface and calling its sole CheckAudioSetup method.By passing arguments to the CheckAudioSetup method, you can specify the device for playback as well as for capture.
Half versus Full Duplex Sound Cards
Voice capture hardware falls into two main categories—those cards that support full-duplex audio and those that do not. Full duplex simply means that you can record and playback at the same time. Half duplex means that you can't record when you're playing any sound, and vice-versa.Unfortunately, DirectPlay Voice doesn't allow voice recording from half-duplex hardware. Any users with half-duplex sound cards will be able to hear other player's voices, but won't be able to talk themselves.The vast majority of the sound cards on the market today are full duplex, so it's usually not an issue, but you can determine whether the user is running on a fullor half-duplex sound card by using the GetSoundDeviceConfig method of either the client or server DirectPlay Voice interfaces. This method returns a structure full of all sorts of information about the hardware, including whether it's full or half duplex.
Using CNetVoice
CNetVoice, similar to CnetConnectionWizard, is a helper class written by Microsoft and distributed as part of the DirectX SDK samples. It's designed to hide all the intricacies of dealing with DirectPlay Voice, and it does a very good job of that.To use CNetVoice, all you need to do is instantiate it, then supply some default options and call its Init method, as shown by the following code:
g_pNetVoice = new CNetVoice(
OnVoiceClientMessage, OnVoiceServerMessage );
// Set default DirectPlayVoice setup options
DVCLIENTCONFIG config;
ZeroMemory( &config, sizeof(config) );
config.dwSize = sizeof(config);
config.dwFlags = DVCLIENTCONFIG_AUTOVOICEACTIVATED |
DVCLIENTCONFIG_AUTORECORDVOLUME;
config.lPlaybackVolume = DVPLAYBACKVOLUME_DEFAULT;
config.dwBufferQuality = DVBUFFERQUALITY_DEFAULT;
config.dwBufferAggressiveness = DVBUFFERAGGRESSIVENESS_DEFAULT;
config.dwThreshold = DVTHRESHOLD_UNUSED;
config.lRecordVolume = DVRECORDVOLUME_LAST;
config.dwNotifyPeriod = 0;
GUID codecguid = DPVCTGUID_DEFAULT;
// initialize voice class
g_pNetVoice->Init( hDlg, g_pNetConnectWizard->IsHostPlayer(),
TRUE, g_pDPPeer, DVSESSIONTYPE_PEER, &codecguid, &config );
As you can see, you supply your two callback function names to the CNetVoice constructor. Also, note that most of the members of the DVCLIENTCONFIG structure can simply be set to their default values.Once this has been done, all that remains is to flesh out the client and server message handlers. After that, just remember to delete your CNetVoice object, and you're all set!
Reference Counted Player Objects
In the Ch26p1_VoiceChat sample program, you'll notice that a player's voice settings and regular DirectPlay settings are integrated into a single object: CPlayer. This is a typical thing to do—most games prefer having one class or structure that represents a player, including core DirectPlay properties, like his name, as well as DirectPlay voice properties (like full duplex/half duplex).The only thing you need to watch out for with this is that you don't accidentally delete a player structure when, in fact, that player is still playing the game, but they've just stopped using DirectPlay Voice.The Ch26p1_VoiceChat sample program guards against this by using reference counts. When it receives a new player message, the sample program creates a new player object and sets its reference count to one. When it receives a DirectPlay Voice "create voice player" message, it looks up the correct player structure (stored in an STL map of DPNIDs to CPlayer objects), and increments its reference count. So, by the time everything is created, all of the player objects have a reference count of two—one reference for core DirectPlay and one for DirectPlay Voice.When it comes time to delete the object, the code starts by decrementing the object's reference count. Only if the reference count falls to zero does the object actually get deleted and removed from the map.If you do things like this, it doesn't matter which "delete player" message you get first. The object only gets deleted when both DirectPlay and DirectPlay Voice have received the delete message.
Voices as 3D Sound Buffers
One of the cool features of DirectPlay Voice is that it lets you tie the incoming voice streams to 3D sound sources. That way, if another player is standing to the left of you when he speaks, his voice will appear to come from your left.
Tip | Note that integration of DirectPlay Voice with 3D sound sources can only occur if you're not using a mixing server topology. In a mixing server topology, all of the streams are mixed together before they reach your computer, and the server that did the mixing has no idea where the players are located. |
You can set this up by calling the Create3DsoundBuffer method of the client DirectPlay Voice interface (IDirectPlayVoiceClient). This method lets you tie a 3D sound source to a particular player ID. From there, you just need to position this buffer where the player is actually standing, and DirectX Audio will do the rest!