openmohaa/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.cpp
2023-02-04 21:00:01 +01:00

397 lines
12 KiB
C++

// VoiceSessionDlg.cpp : implementation file
//
#include "stdafx.h"
#include "Voice2BuddyMFC.h"
#include "VoiceSessionDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define THINK_TIMER_ID 100+2
#define THINK_TIMER_DELAY 20
/////////////////////////////////////////////////////////////////////////////
// CVoiceSessionDlg dialog
struct VoicePacket
{
GVFrameStamp mFrameStamp;
int mDataLength;
GVByte mBuffer[1024]; // most likely get < 256 bytes
};
const int gVoicePacketHeaderSize = sizeof(GVFrameStamp)+sizeof(int);
CVoiceSessionDlg::CVoiceSessionDlg(CWnd* pParent /*=NULL*/)
: CDialog(CVoiceSessionDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CVoiceSessionDlg)
m_DisplayText = _T("");
//}}AFX_DATA_INIT
}
void CVoiceSessionDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CVoiceSessionDlg)
DDX_Control(pDX, IDC_SPEAKING2, m_RemoteSpeaking);
DDX_Control(pDX, IDC_SPEAKING1, m_LocalSpeaking);
DDX_Text(pDX, IDC_DISPLAYTEXT, m_DisplayText);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CVoiceSessionDlg, CDialog)
//{{AFX_MSG_MAP(CVoiceSessionDlg)
ON_WM_TIMER()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CVoiceSessionDlg message handlers
void CVoiceSessionDlg::OnTimer(UINT nIDEvent)
{
// Win32 timers can be called concurrently by the OS.
// (you can get a second timer callback before the first finishes!)
// CRITICAL_SECTION will not prevent this as Win32 allows concurrent access
static bool inTimer = false;
if (inTimer)
return;
// Prevent windows from entering the timer again
inTimer = true;
// Process voice data
gvThink();
// Process NN
NNThink();
// Process network traffic
if (m_Socket)
gt2Think(m_Socket);
// If we're not connected we don't need to do the rest
if (!m_Connection || (gt2GetConnectionState(m_Connection) != GT2Connected))
{
inTimer = false;
return;
}
// Show a bitmap if the sources are speaking
static bool wasLocalSpeaking = false;
static bool wasRemoteSpeaking = false;
bool isLocalSpeaking = (GVTrue == gvIsSourceTalking(m_SetupInfo.m_PlaybackDevice, 0));
bool isRemoteSpeaking = (GVTrue == gvIsSourceTalking(m_SetupInfo.m_PlaybackDevice, m_RemoteAddr));
if (isLocalSpeaking != wasLocalSpeaking)
{
wasLocalSpeaking = isLocalSpeaking;
m_LocalSpeaking.ShowWindow(isLocalSpeaking ? SW_SHOW:SW_HIDE);
}
if (isRemoteSpeaking != wasRemoteSpeaking)
{
wasRemoteSpeaking = isRemoteSpeaking;
m_RemoteSpeaking.ShowWindow(isRemoteSpeaking ? SW_SHOW:SW_HIDE);
}
// Check for microphone activity
int aBytesAvailable = gvGetAvailableCaptureBytes(m_SetupInfo.m_CaptureDevice);
if (aBytesAvailable > 0)
{
// Voice packet struct, so we don't have to do a buffer copy
// We just record the data directly into the packet
// (the actual packet may be smaller
VoicePacket aVoicePacket;
memset(&aVoicePacket, 0, sizeof(aVoicePacket));
aVoicePacket.mDataLength = sizeof(aVoicePacket.mBuffer);
GVScalar aVolume; // this doesn't need to be sent
// Read in a voice packet
GVBool gotPacket = gvCapturePacket(m_SetupInfo.m_CaptureDevice, aVoicePacket.mBuffer, &aVoicePacket.mDataLength, &aVoicePacket.mFrameStamp, &aVolume);
if (gotPacket == GVTrue)
{
// Playback for local echo
//gvPlayPacket(m_SetupInfo.m_PlaybackDevice, aVoicePacket.mBuffer, aVoicePacket.mDataLength, 0, aVoicePacket.mFrameStamp);
// Contruct a message for the buddy
int aTotalPacketSize = gVoicePacketHeaderSize + aVoicePacket.mDataLength;
gt2Send(m_Connection, (GT2Byte*)&aVoicePacket, aTotalPacketSize, GT2False);
}
}
// Allow new timers to be triggered
inTimer = false;
CDialog::OnTimer(nIDEvent);
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Callbacks for GT2
void GT2SocketErrorCallback(GT2Socket theSocket)
{
CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)gt2GetSocketData(theSocket);
aDialog->m_DisplayText = "GT2SocketErrorCallback. Connection Closed";
aDialog->UpdateData(FALSE);
}
void GT2ConnectedCallback(GT2Connection theConnection, GT2Result theResult, unsigned char* theMessage, int theLength)
{
GT2Socket aSocket = gt2GetConnectionSocket(theConnection);
CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)gt2GetSocketData(aSocket);
// Was there an error?
if (theResult != GT2Success)
{
aDialog->MessageBox("GT2ConnectedCallback returned failure!");
aDialog->PostMessage(WM_CLOSE, 0, 0);
}
else
{
// Remember our connection
aDialog->m_Connection = theConnection;
aDialog->m_DisplayText = "Session established.\r\n(You may begin speaking)";
aDialog->UpdateData(FALSE);
// Start the voice devices
if (aDialog->m_SetupInfo.m_CaptureDevice != NULL)
gvStartDevice(aDialog->m_SetupInfo.m_CaptureDevice, GV_CAPTURE);
if (aDialog->m_SetupInfo.m_PlaybackDevice != NULL)
gvStartDevice(aDialog->m_SetupInfo.m_PlaybackDevice, GV_PLAYBACK);
}
GSI_UNUSED(theMessage);
GSI_UNUSED(theLength);
GSI_UNUSED(theResult);
}
void GT2ReceivedCallback(GT2Connection theConnection, unsigned char* theBuffer, int theLength, int wasReliable)
{
// Get the ptr to the dialog
CVoiceSessionDlg* aDlg = (CVoiceSessionDlg*)gt2GetSocketData(gt2GetConnectionSocket(theConnection));
// Cast to a voice packet so we can extract data
VoicePacket* aVoicePacket = (VoicePacket*)theBuffer;
// Sanity check the packet
assert(theLength >= gVoicePacketHeaderSize);
assert(theLength == (gVoicePacketHeaderSize + aVoicePacket->mDataLength));
// Play the voice data
GVSource aSource = gt2GetRemoteIP(theConnection);
gvPlayPacket(aDlg->m_SetupInfo.m_PlaybackDevice, aVoicePacket->mBuffer, aVoicePacket->mDataLength, aSource, aVoicePacket->mFrameStamp, 0);
OutputDebugString("Playing voice packet\r\n");
GSI_UNUSED(wasReliable);
GSI_UNUSED(theLength);
}
GT2Bool GT2UnrecognizedMessageCallback(GT2Socket theSocket, unsigned int theIp, unsigned short thePort, GT2Byte* theData, int theLength)
{
static unsigned char aNNHeader[NATNEG_MAGIC_LEN] =
{ NN_MAGIC_0, NN_MAGIC_1, NN_MAGIC_2,
NN_MAGIC_3, NN_MAGIC_4, NN_MAGIC_5 };
// Bail out if it's too short
if (theLength < NATNEG_MAGIC_LEN)
{
OutputDebugString("\tDiscarded message (too small)\r\n");
return GT2False;
}
// Check if it matches the NN Magic bytes
if (0==memcmp(theData, aNNHeader,NATNEG_MAGIC_LEN))
{
// Hand off to the NN SDK
sockaddr_in anAddr;
anAddr.sin_family = AF_INET;
anAddr.sin_port = htons(thePort);
anAddr.sin_addr.s_addr = theIp;
NNProcessData((char*)theData, theLength, &anAddr);
OutputDebugString("\tProcessed NN message\r\n");
return GT2True;
}
// Not handled by us
OutputDebugString("\tDiscarded message (not recognized)\r\n");
GSI_UNUSED(theSocket);
return GT2False;
}
void GT2ClosedCallback(GT2Connection theConnection, GT2CloseReason theReason)
{
GT2Socket aSocket = gt2GetConnectionSocket(theConnection);
CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)gt2GetSocketData(aSocket);
aDialog->m_DisplayText = "Connection closed. (GT2ClosedCallback)";
aDialog->UpdateData(FALSE);
GSI_UNUSED(theReason);
}
void GT2ConnectAttemptCallback(GT2Socket theSocket, GT2Connection theConnection, unsigned int theIp, unsigned short thePort, int theLatency, GT2Byte * theMessage, int theLength)
{
CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)gt2GetSocketData(theSocket);
// Set the callbacks so we can receive data
GT2ConnectionCallbacks aCallbackList;
aCallbackList.received = GT2ReceivedCallback;
aCallbackList.connected = GT2ConnectedCallback; // we're connected
aCallbackList.closed = GT2ClosedCallback;
// Accept the connection
aDialog->m_Connection = theConnection;
gt2Accept(theConnection, &aCallbackList);
// Store off the remote addr
aDialog->m_RemoteAddr = gt2GetRemoteIP(theConnection);
// Start the voice devices
if (aDialog->m_SetupInfo.m_CaptureDevice != NULL)
gvStartDevice(aDialog->m_SetupInfo.m_CaptureDevice, GV_CAPTURE);
if (aDialog->m_SetupInfo.m_PlaybackDevice != NULL)
gvStartDevice(aDialog->m_SetupInfo.m_PlaybackDevice, GV_PLAYBACK);
aDialog->m_DisplayText = "Session established.\r\n(You may begin speaking)";
aDialog->UpdateData(FALSE);
GSI_UNUSED(theMessage);
GSI_UNUSED(theLength);
GSI_UNUSED(theIp);
GSI_UNUSED(thePort);
GSI_UNUSED(theLatency);
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Callbacks for the NN SDK
void NNProgressCallback(NegotiateState theState, void* theParam)
{
CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)theParam;
aDialog->m_DisplayText.Format("NNProgressCallback: %d\r\n", theState);
aDialog->UpdateData(FALSE);
}
void NNCompletedCallback(NegotiateResult theResult, SOCKET theSocket, sockaddr_in* theAddr, void* theParam)
{
CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)theParam;
// Check the result, are we connected at the socket layer?
if (theResult == nr_success)
{
// Convert sockaddr_in to a string
CString aAddrString;
aAddrString.Format("%s:%d", inet_ntoa(theAddr->sin_addr), ntohs(theAddr->sin_port));
aDialog->m_DisplayText = "NN Successful";
// If hosting, listen for the client...
if (aDialog->m_IsHost)
{
gt2Listen(aDialog->m_Socket, GT2ConnectAttemptCallback);
aDialog->m_DisplayText = "Listening for GT2 connection";
}
// ...otherwise connect to the host
else
{
// Store off the remote addr
aDialog->m_RemoteAddr = theAddr->sin_addr.s_addr;
// Set our gt2 callbacks, so we can receive messages
GT2ConnectionCallbacks aCallbackList;
aCallbackList.connected = GT2ConnectedCallback; // we're connected
aCallbackList.received = GT2ReceivedCallback; // we've received data
aCallbackList.closed = GT2ClosedCallback;
int aTimeout = 0;
GT2Result aResult = gt2Connect(aDialog->m_Socket, &aDialog->m_Connection, aAddrString, NULL, 0, aTimeout, &aCallbackList, GT2False);
if (aResult != GT2Success)
{
// Error, bail out
aDialog->m_DisplayText = "Failed on gt2Connect";
//aDialog->PostMessage(WM_CLOSE, 0, 0);
}
aDialog->m_DisplayText = "Connecting with GT2";
}
}
else
{
aDialog->m_DisplayText = "Failed to negotiate a connection.";
}
// Draw the new display text
aDialog->UpdateData(FALSE);
GSI_UNUSED(theSocket);
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// MFC Event handlers
BOOL CVoiceSessionDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Init some variables
m_Socket = NULL;
m_Connection = NULL;
// Set the gt2Callbacks
// Create our gt2 socket (we'll need it for NN)
// We use NULL for the local addr because we don't care which
// port we're bound to. We could specify one if we wanted by
// using ":5000" as the addr, for example
GT2Result aResult = gt2CreateSocket(&m_Socket, NULL, 0, 0, GT2SocketErrorCallback);
if (aResult != GT2Success)
{
// We're hosed, bail out by closing the dialog
MessageBox("Failed on gt2CreateSocket");
PostMessage(WM_CLOSE, 0, 0);
return TRUE;
}
// Set the socket data (the user param) to our dialog
gt2SetSocketData(m_Socket, this);
gt2SetUnrecognizedMessageCallback(m_Socket, GT2UnrecognizedMessageCallback);
// Begin nat negotiation (use the gt2 socket)
// m_NNCookie and m_IsHost are set before the dialog is run
SOCKET aWinSocket = gt2GetSocketSOCKET(m_Socket);
NNBeginNegotiationWithSocket(aWinSocket, m_NNCookie, m_IsHost, NNProgressCallback, NNCompletedCallback, this);
m_DisplayText = "Establishing connection...";
UpdateData(FALSE);
// Set the think timer
SetTimer(THINK_TIMER_ID, THINK_TIMER_DELAY, NULL);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
BOOL CVoiceSessionDlg::DestroyWindow()
{
// Clean up NN SDK
NNFreeNegotiateList();
// Close the gt2 socket (will close the connection if connected)
if (m_Socket)
gt2CloseSocket(m_Socket);
// Stop the voice devices
if (m_SetupInfo.m_CaptureDevice != NULL)
gvStopDevice(m_SetupInfo.m_CaptureDevice, GV_CAPTURE);
if (m_SetupInfo.m_PlaybackDevice != NULL)
gvStopDevice(m_SetupInfo.m_PlaybackDevice, GV_PLAYBACK);
// Kill the timer
KillTimer(THINK_TIMER_ID);
return CDialog::DestroyWindow();
}