mirror of
https://github.com/LCEMP/LCEMP.git
synced 2026-04-23 15:33:58 +00:00
284 lines
7.6 KiB
C++
284 lines
7.6 KiB
C++
#include "stdafx.h"
|
|
|
|
#ifdef _WINDOWS64
|
|
|
|
#include "VoiceChat.h"
|
|
#include <mmsystem.h>
|
|
#pragma comment(lib, "winmm.lib")
|
|
|
|
#include "../Network/WinsockNetLayer.h"
|
|
#include "../../../Minecraft.World/CustomPayloadPacket.h"
|
|
#include "../../../Minecraft.World/ArrayWithLength.h"
|
|
#include "../../Minecraft.h"
|
|
#include "../../MultiPlayerLocalPlayer.h"
|
|
#include "../../ClientConnection.h"
|
|
#include "../../KeyboardMouseInput.h"
|
|
#include "../KBMConfig.h"
|
|
|
|
#include <cmath>
|
|
|
|
#define VOICE_SAMPLE_RATE 16000
|
|
#define VOICE_BITS 16
|
|
#define VOICE_CHANNELS 1
|
|
#define VOICE_FRAME_MS 60
|
|
#define VOICE_FRAME_SAMPLES (VOICE_SAMPLE_RATE * VOICE_FRAME_MS / 1000)
|
|
#define VOICE_FRAME_BYTES (VOICE_FRAME_SAMPLES * (VOICE_BITS / 8) * VOICE_CHANNELS)
|
|
#define VOICE_CAPTURE_BUFFERS 6
|
|
#define VOICE_PLAY_BUFFERS 16
|
|
#define VOICE_MAX_PLAYERS 8
|
|
#define VOICE_SILENCE_THRESHOLD 500
|
|
#define VOICE_TALKING_TIMEOUT_MS 500
|
|
|
|
static const wstring VOICE_CHANNEL = L"MC|Voice";
|
|
|
|
static WAVEFORMATEX s_format;
|
|
static HWAVEIN s_waveIn = NULL;
|
|
static WAVEHDR s_captureHeaders[VOICE_CAPTURE_BUFFERS];
|
|
static char s_captureBuffers[VOICE_CAPTURE_BUFFERS][VOICE_FRAME_BYTES];
|
|
static volatile long s_captureReady[VOICE_CAPTURE_BUFFERS];
|
|
|
|
static HWAVEOUT s_playOut[VOICE_MAX_PLAYERS];
|
|
static WAVEHDR s_playHeaders[VOICE_MAX_PLAYERS][VOICE_PLAY_BUFFERS];
|
|
static char s_playData[VOICE_MAX_PLAYERS][VOICE_PLAY_BUFFERS][VOICE_FRAME_BYTES];
|
|
static int s_nextPlayBuffer[VOICE_MAX_PLAYERS];
|
|
|
|
static bool s_initialized = false;
|
|
static bool s_enabled = false;
|
|
static bool s_micOpen = false;
|
|
static DWORD s_talkingTime[VOICE_MAX_PLAYERS];
|
|
static bool s_localTalking = false;
|
|
|
|
static void CALLBACK voiceCaptureProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
|
|
{
|
|
if (uMsg == WIM_DATA)
|
|
{
|
|
WAVEHDR *hdr = (WAVEHDR *)dwParam1;
|
|
int idx = (int)(hdr - s_captureHeaders);
|
|
if (idx >= 0 && idx < VOICE_CAPTURE_BUFFERS)
|
|
InterlockedExchange(&s_captureReady[idx], 1);
|
|
}
|
|
}
|
|
|
|
static bool isAudioActive(const char *data, int size)
|
|
{
|
|
const short *samples = (const short *)data;
|
|
int count = size / 2;
|
|
long long sum = 0;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
int v = samples[i];
|
|
sum += (long long)v * v;
|
|
}
|
|
double rms = sqrt((double)sum / count);
|
|
return rms > VOICE_SILENCE_THRESHOLD;
|
|
}
|
|
|
|
bool VoiceChat::init()
|
|
{
|
|
if (s_initialized) return true;
|
|
|
|
memset(&s_format, 0, sizeof(s_format));
|
|
s_format.wFormatTag = WAVE_FORMAT_PCM;
|
|
s_format.nChannels = VOICE_CHANNELS;
|
|
s_format.nSamplesPerSec = VOICE_SAMPLE_RATE;
|
|
s_format.wBitsPerSample = VOICE_BITS;
|
|
s_format.nBlockAlign = s_format.nChannels * s_format.wBitsPerSample / 8;
|
|
s_format.nAvgBytesPerSec = s_format.nSamplesPerSec * s_format.nBlockAlign;
|
|
|
|
memset((void*)s_captureReady, 0, sizeof(s_captureReady));
|
|
memset(s_playOut, 0, sizeof(s_playOut));
|
|
memset(s_nextPlayBuffer, 0, sizeof(s_nextPlayBuffer));
|
|
memset(s_talkingTime, 0, sizeof(s_talkingTime));
|
|
|
|
s_initialized = true;
|
|
return true;
|
|
}
|
|
|
|
bool VoiceChat::openMicrophone()
|
|
{
|
|
if (s_micOpen) return true;
|
|
|
|
UINT devCount = waveInGetNumDevs();
|
|
if (devCount == 0) return false;
|
|
|
|
MMRESULT result = waveInOpen(&s_waveIn, WAVE_MAPPER, &s_format,
|
|
(DWORD_PTR)voiceCaptureProc, 0, CALLBACK_FUNCTION);
|
|
if (result != MMSYSERR_NOERROR) return false;
|
|
|
|
for (int i = 0; i < VOICE_CAPTURE_BUFFERS; i++)
|
|
{
|
|
memset(&s_captureHeaders[i], 0, sizeof(WAVEHDR));
|
|
s_captureHeaders[i].lpData = s_captureBuffers[i];
|
|
s_captureHeaders[i].dwBufferLength = VOICE_FRAME_BYTES;
|
|
waveInPrepareHeader(s_waveIn, &s_captureHeaders[i], sizeof(WAVEHDR));
|
|
waveInAddBuffer(s_waveIn, &s_captureHeaders[i], sizeof(WAVEHDR));
|
|
s_captureReady[i] = 0;
|
|
}
|
|
|
|
waveInStart(s_waveIn);
|
|
s_micOpen = true;
|
|
return true;
|
|
}
|
|
|
|
void VoiceChat::closeMicrophone()
|
|
{
|
|
if (!s_micOpen) return;
|
|
|
|
waveInStop(s_waveIn);
|
|
waveInReset(s_waveIn);
|
|
|
|
for (int i = 0; i < VOICE_CAPTURE_BUFFERS; i++)
|
|
waveInUnprepareHeader(s_waveIn, &s_captureHeaders[i], sizeof(WAVEHDR));
|
|
|
|
waveInClose(s_waveIn);
|
|
s_waveIn = NULL;
|
|
s_micOpen = false;
|
|
}
|
|
|
|
bool VoiceChat::openPlayback(int playerIndex)
|
|
{
|
|
if (playerIndex < 0 || playerIndex >= VOICE_MAX_PLAYERS) return false;
|
|
if (s_playOut[playerIndex]) return true;
|
|
|
|
MMRESULT result = waveOutOpen(&s_playOut[playerIndex], WAVE_MAPPER, &s_format,
|
|
0, 0, CALLBACK_NULL);
|
|
if (result != MMSYSERR_NOERROR) return false;
|
|
|
|
memset(s_playHeaders[playerIndex], 0, sizeof(WAVEHDR) * VOICE_PLAY_BUFFERS);
|
|
s_nextPlayBuffer[playerIndex] = 0;
|
|
return true;
|
|
}
|
|
|
|
void VoiceChat::closePlayback(int playerIndex)
|
|
{
|
|
if (playerIndex < 0 || playerIndex >= VOICE_MAX_PLAYERS) return;
|
|
if (!s_playOut[playerIndex]) return;
|
|
|
|
waveOutReset(s_playOut[playerIndex]);
|
|
|
|
for (int i = 0; i < VOICE_PLAY_BUFFERS; i++)
|
|
{
|
|
if (s_playHeaders[playerIndex][i].dwFlags & WHDR_PREPARED)
|
|
waveOutUnprepareHeader(s_playOut[playerIndex], &s_playHeaders[playerIndex][i], sizeof(WAVEHDR));
|
|
}
|
|
|
|
waveOutClose(s_playOut[playerIndex]);
|
|
s_playOut[playerIndex] = NULL;
|
|
}
|
|
|
|
void VoiceChat::shutdown()
|
|
{
|
|
if (!s_initialized) return;
|
|
closeMicrophone();
|
|
for (int i = 0; i < VOICE_MAX_PLAYERS; i++)
|
|
closePlayback(i);
|
|
s_initialized = false;
|
|
s_enabled = false;
|
|
}
|
|
|
|
void VoiceChat::tick()
|
|
{
|
|
if (!s_initialized || !s_enabled) return;
|
|
|
|
Minecraft *mc = Minecraft::GetInstance();
|
|
if (!mc || !mc->localplayers[0] || !mc->localplayers[0]->connection) return;
|
|
|
|
if (!s_micOpen)
|
|
openMicrophone();
|
|
|
|
if (!s_micOpen) return;
|
|
|
|
KBMConfig& cfg = KBMConfig::Get();
|
|
bool pttHeld = (cfg.voiceMode == 1) || !g_KBMInput.IsKBMActive() || g_KBMInput.IsKeyDown(cfg.keyVoice);
|
|
|
|
s_localTalking = false;
|
|
for (int i = 0; i < VOICE_CAPTURE_BUFFERS; i++)
|
|
{
|
|
if (InterlockedCompareExchange(&s_captureReady[i], 0, 1) == 1)
|
|
{
|
|
if (pttHeld && isAudioActive(s_captureBuffers[i], VOICE_FRAME_BYTES))
|
|
{
|
|
s_localTalking = true;
|
|
|
|
byteArray voiceData;
|
|
voiceData.data = new byte[VOICE_FRAME_BYTES];
|
|
voiceData.length = VOICE_FRAME_BYTES;
|
|
memcpy(voiceData.data, s_captureBuffers[i], VOICE_FRAME_BYTES);
|
|
|
|
mc->localplayers[0]->connection->send(
|
|
shared_ptr<CustomPayloadPacket>(new CustomPayloadPacket(VOICE_CHANNEL, voiceData)));
|
|
}
|
|
|
|
waveInAddBuffer(s_waveIn, &s_captureHeaders[i], sizeof(WAVEHDR));
|
|
}
|
|
}
|
|
}
|
|
|
|
void VoiceChat::onVoiceReceived(unsigned char senderSmallId, const char *data, int dataSize)
|
|
{
|
|
if (!s_initialized || !s_enabled) return;
|
|
if (senderSmallId >= VOICE_MAX_PLAYERS) return;
|
|
if (dataSize <= 0 || dataSize > VOICE_FRAME_BYTES) return;
|
|
|
|
if (!openPlayback(senderSmallId)) return;
|
|
|
|
s_talkingTime[senderSmallId] = GetTickCount();
|
|
|
|
int bufIdx = s_nextPlayBuffer[senderSmallId];
|
|
WAVEHDR *hdr = &s_playHeaders[senderSmallId][bufIdx];
|
|
|
|
if (hdr->dwFlags & WHDR_PREPARED)
|
|
{
|
|
if (!(hdr->dwFlags & WHDR_DONE))
|
|
return;
|
|
waveOutUnprepareHeader(s_playOut[senderSmallId], hdr, sizeof(WAVEHDR));
|
|
}
|
|
|
|
memcpy(s_playData[senderSmallId][bufIdx], data, dataSize);
|
|
|
|
memset(hdr, 0, sizeof(WAVEHDR));
|
|
hdr->lpData = s_playData[senderSmallId][bufIdx];
|
|
hdr->dwBufferLength = dataSize;
|
|
|
|
waveOutPrepareHeader(s_playOut[senderSmallId], hdr, sizeof(WAVEHDR));
|
|
waveOutWrite(s_playOut[senderSmallId], hdr, sizeof(WAVEHDR));
|
|
|
|
s_nextPlayBuffer[senderSmallId] = (bufIdx + 1) % VOICE_PLAY_BUFFERS;
|
|
}
|
|
|
|
bool VoiceChat::isTalking(unsigned char smallId)
|
|
{
|
|
if (!s_initialized || !s_enabled) return false;
|
|
|
|
if (smallId == WinsockNetLayer::GetLocalSmallId())
|
|
return s_localTalking;
|
|
|
|
if (smallId >= VOICE_MAX_PLAYERS) return false;
|
|
|
|
DWORD now = GetTickCount();
|
|
return (s_talkingTime[smallId] != 0 && (now - s_talkingTime[smallId]) < VOICE_TALKING_TIMEOUT_MS);
|
|
}
|
|
|
|
bool VoiceChat::hasVoice()
|
|
{
|
|
return s_initialized && s_enabled;
|
|
}
|
|
|
|
void VoiceChat::setEnabled(bool enabled)
|
|
{
|
|
s_enabled = enabled;
|
|
if (!enabled)
|
|
{
|
|
closeMicrophone();
|
|
for (int i = 0; i < VOICE_MAX_PLAYERS; i++)
|
|
closePlayback(i);
|
|
}
|
|
}
|
|
|
|
bool VoiceChat::isEnabled()
|
|
{
|
|
return s_enabled;
|
|
}
|
|
|
|
#endif
|