mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 21:57:57 +03:00
397 lines
12 KiB
C++
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();
|
|
}
|