|
|
|
/*==LICENSE==*
|
|
|
|
|
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools
|
|
|
|
Copyright (C) 2011 Cyan Worlds, Inc.
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Additional permissions under GNU GPL version 3 section 7
|
|
|
|
|
|
|
|
If you modify this Program, or any covered work, by linking or
|
|
|
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
|
|
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
|
|
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
|
|
|
(or a modified version of those libraries),
|
|
|
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
|
|
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
|
|
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
|
|
|
licensors of this Program grant you additional
|
|
|
|
permission to convey the resulting work. Corresponding Source for a
|
|
|
|
non-source form of such a combination shall include the source code for
|
|
|
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
|
|
|
work.
|
|
|
|
|
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|
|
|
or by snail mail at:
|
|
|
|
Cyan Worlds, Inc.
|
|
|
|
14617 N Newport Hwy
|
|
|
|
Mead, WA 99021
|
|
|
|
|
|
|
|
*==LICENSE==*/
|
|
|
|
#include "hsTypes.h"
|
|
|
|
#include "hsWindows.h"
|
|
|
|
#include "hsTimer.h"
|
|
|
|
#include "hsResMgr.h"
|
|
|
|
#include "al.h"
|
|
|
|
#include "alc.h"
|
|
|
|
#include "plDSoundBuffer.h"
|
|
|
|
#include "speex.h"
|
|
|
|
#include "speex_bits.h"
|
|
|
|
#include "hsGeometry3.h"
|
|
|
|
#include "plVoiceChat.h"
|
|
|
|
#include "plAudioSystem.h"
|
|
|
|
#include "plgDispatch.h"
|
|
|
|
#include "../plAudible/plWinAudible.h"
|
|
|
|
#include "../plNetMessage/plNetMessage.h"
|
|
|
|
#include "../plPipeline/plPlates.h"
|
|
|
|
#include "hsConfig.h"
|
|
|
|
#include "../plAvatar/plAvatarMgr.h"
|
|
|
|
#include "../plAvatar/plArmatureMod.h"
|
|
|
|
#include "hsQuat.h"
|
|
|
|
#include "../plAudioCore/plAudioCore.h"
|
|
|
|
|
|
|
|
// DEBUG for printing to the console
|
|
|
|
#include "../plMessage/plConsoleMsg.h"
|
|
|
|
#include "../plPipeline/plDebugText.h"
|
|
|
|
#include "../plStatusLog/plStatusLog.h"
|
|
|
|
|
|
|
|
#define MICROPHONE "ui_microphone.png"
|
|
|
|
#define TALKING "ui_speaker.png"
|
|
|
|
#define NUM_CHANNELS 1
|
|
|
|
#define VOICE_STOP_MS 2000
|
|
|
|
#define MAX_DATA_SIZE 1024 * 4 // 4 KB
|
|
|
|
|
|
|
|
hsBool plVoiceRecorder::fCompress = true;
|
|
|
|
hsBool plVoiceRecorder::fRecording = true;
|
|
|
|
hsBool plVoiceRecorder::fNetVoice = false;
|
|
|
|
short plVoiceRecorder::fSampleRate = FREQUENCY;
|
|
|
|
hsScalar plVoiceRecorder::fRecordThreshhold = 200.0f;
|
|
|
|
hsBool plVoiceRecorder::fShowIcons = true;
|
|
|
|
hsBool plVoiceRecorder::fMicAlwaysOpen = false;
|
|
|
|
hsBool plVoicePlayer::fEnabled = true;
|
|
|
|
|
|
|
|
plVoiceRecorder::plVoiceRecorder()
|
|
|
|
{
|
|
|
|
plPlateManager::Instance().CreatePlate( &fDisabledIcon );
|
|
|
|
fDisabledIcon->CreateFromResource( MICROPHONE );
|
|
|
|
fDisabledIcon->SetPosition(-0.90, -0.90);
|
|
|
|
fDisabledIcon->SetSize(0.064, 0.064, true);
|
|
|
|
fDisabledIcon->SetVisible(false);
|
|
|
|
|
|
|
|
plPlateManager::Instance().CreatePlate( &fTalkIcon );
|
|
|
|
fTalkIcon->CreateFromResource( TALKING );
|
|
|
|
fTalkIcon->SetPosition(-0.9,-0.9);
|
|
|
|
fTalkIcon->SetSize(0.0675, 0.09);
|
|
|
|
fTalkIcon->SetVisible(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
plVoiceRecorder::~plVoiceRecorder()
|
|
|
|
{
|
|
|
|
if(fDisabledIcon)
|
|
|
|
plPlateManager::Instance().DestroyPlate( fDisabledIcon);
|
|
|
|
fDisabledIcon = nil;
|
|
|
|
|
|
|
|
if (fTalkIcon)
|
|
|
|
plPlateManager::Instance().DestroyPlate( fTalkIcon );
|
|
|
|
fTalkIcon = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceRecorder::IncreaseRecordingThreshhold()
|
|
|
|
{
|
|
|
|
fRecordThreshhold += (100 * hsTimer::GetDelSysSeconds());
|
|
|
|
if (fRecordThreshhold >= 10000.0f)
|
|
|
|
fRecordThreshhold = 10000.0f;
|
|
|
|
|
|
|
|
plDebugText &txt = plDebugText::Instance();
|
|
|
|
char str[256];
|
|
|
|
sprintf(str, "RecordThreshhold %f\n", fRecordThreshhold);
|
|
|
|
txt.DrawString(400,300,str);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceRecorder::DecreaseRecordingThreshhold()
|
|
|
|
{
|
|
|
|
fRecordThreshhold -= (100 * hsTimer::GetDelSysSeconds());
|
|
|
|
if (fRecordThreshhold <= 50.0f)
|
|
|
|
fRecordThreshhold = 50.0f;
|
|
|
|
|
|
|
|
plDebugText &txt = plDebugText::Instance();
|
|
|
|
char str[256];
|
|
|
|
sprintf(str, "RecordThreshhold %f\n", fRecordThreshhold);
|
|
|
|
txt.DrawString(400,300,str);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the quality of speex encoder
|
|
|
|
void plVoiceRecorder::SetQuality(int quality)
|
|
|
|
{
|
|
|
|
char str[] = "Voice quality setting out of range. Must be between 1 and 10 inclusive";
|
|
|
|
if(quality < 1 || quality > 10)
|
|
|
|
{
|
|
|
|
plConsoleMsg *cMsg = TRACKED_NEW plConsoleMsg( plConsoleMsg::kAddLine, str );
|
|
|
|
plgDispatch::MsgSend( cMsg );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(plSpeex::GetInstance()->IsUsingVBR())
|
|
|
|
{
|
|
|
|
// Sets average bit rate between 4kb and 13kb
|
|
|
|
int AverageBitrate = quality * 1000 + 3000;
|
|
|
|
plSpeex::GetInstance()->SetABR(AverageBitrate);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
plSpeex::GetInstance()->SetQuality(quality);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// toggle variable bit rate
|
|
|
|
void plVoiceRecorder::SetVBR(bool vbr)
|
|
|
|
{
|
|
|
|
plSpeex::GetInstance()->VBR(vbr);
|
|
|
|
SetQuality(plSpeex::GetInstance()->GetQuality()); // update proper quality param
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceRecorder::SetComplexity(int c)
|
|
|
|
{
|
|
|
|
char str[] = "Voice quality setting out of range. Must be between 1 and 10 inclusive";
|
|
|
|
if(c < 1 || c > 10)
|
|
|
|
{
|
|
|
|
plConsoleMsg *cMsg = TRACKED_NEW plConsoleMsg( plConsoleMsg::kAddLine, str );
|
|
|
|
plgDispatch::MsgSend( cMsg );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
plSpeex::GetInstance()->SetComplexity((UInt8) c);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceRecorder::SetENH(hsBool b)
|
|
|
|
{
|
|
|
|
plSpeex::GetInstance()->SetENH(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceRecorder::SetMikeOpen(hsBool b)
|
|
|
|
{
|
|
|
|
ALCdevice *device = plgAudioSys::GetCaptureDevice();
|
|
|
|
if (fRecording && device)
|
|
|
|
{
|
|
|
|
if (b)
|
|
|
|
{
|
|
|
|
alcCaptureStart(device);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
alcCaptureStop(device);
|
|
|
|
}
|
|
|
|
DrawTalkIcon(b);
|
|
|
|
fMikeOpen = b;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DrawDisabledIcon(b); // voice recording is unavailable or disabled
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceRecorder::DrawDisabledIcon(hsBool b)
|
|
|
|
{
|
|
|
|
if (!fDisabledIcon)
|
|
|
|
{
|
|
|
|
// at least try and make one here...
|
|
|
|
plPlateManager::Instance().CreatePlate( &fDisabledIcon );
|
|
|
|
if (fDisabledIcon)
|
|
|
|
{
|
|
|
|
fDisabledIcon->CreateFromResource( MICROPHONE );
|
|
|
|
fDisabledIcon->SetPosition(-0.90, -0.90);
|
|
|
|
fDisabledIcon->SetSize(0.064, 0.064, true);
|
|
|
|
fDisabledIcon->SetVisible(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fDisabledIcon)
|
|
|
|
{
|
|
|
|
fDisabledIcon->SetSize(0.064, 0.064, true); // Re-compute plate size in case the aspect ratio has changed.
|
|
|
|
fDisabledIcon->SetVisible(b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceRecorder::DrawTalkIcon(hsBool b)
|
|
|
|
{
|
|
|
|
if (!fTalkIcon)
|
|
|
|
{
|
|
|
|
plPlateManager::Instance().CreatePlate( &fTalkIcon );
|
|
|
|
if (fTalkIcon)
|
|
|
|
{ fTalkIcon->CreateFromResource( TALKING );
|
|
|
|
fTalkIcon->SetPosition(-0.9,-0.9);
|
|
|
|
fTalkIcon->SetSize(0.064, 0.064, true);
|
|
|
|
fTalkIcon->SetVisible(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fTalkIcon)
|
|
|
|
{
|
|
|
|
fTalkIcon->SetSize(0.064, 0.064, true); // Re-compute plate size in case the aspect ratio has changed.
|
|
|
|
fTalkIcon->SetVisible(b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceRecorder::Update(double time)
|
|
|
|
{
|
|
|
|
if(!fRecording)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int EncoderFrameSize = plSpeex::GetInstance()->GetFrameSize();
|
|
|
|
if(EncoderFrameSize == -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ALCdevice *captureDevice = plgAudioSys::GetCaptureDevice();
|
|
|
|
if(!captureDevice)
|
|
|
|
return;
|
|
|
|
|
|
|
|
unsigned minSamples = EncoderFrameSize * 10;
|
|
|
|
|
|
|
|
ALCint samples;
|
|
|
|
alcGetIntegerv(captureDevice, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples );
|
|
|
|
|
|
|
|
if (samples > 0)
|
|
|
|
{
|
|
|
|
if (samples >= minSamples)
|
|
|
|
{
|
|
|
|
int numFrames = (int)(samples / EncoderFrameSize); // the number of frames that have been captured
|
|
|
|
int totalSamples = numFrames * EncoderFrameSize;
|
|
|
|
|
|
|
|
// cap uncompressed data
|
|
|
|
if(totalSamples > MAX_DATA_SIZE)
|
|
|
|
totalSamples = MAX_DATA_SIZE;
|
|
|
|
|
|
|
|
// convert to correct units:
|
|
|
|
short *buffer = TRACKED_NEW short[totalSamples];
|
|
|
|
|
|
|
|
alcCaptureSamples(captureDevice, buffer, totalSamples);
|
|
|
|
|
|
|
|
if (!CompressionEnabled())
|
|
|
|
{
|
|
|
|
plNetMsgVoice pMsg;
|
|
|
|
pMsg.SetNetProtocol(kNetProtocolCli2Game);
|
|
|
|
pMsg.SetVoiceData((char *)buffer, totalSamples * sizeof(short));
|
|
|
|
// set frame size here;
|
|
|
|
pMsg.SetPlayerID(plNetClientApp::GetInstance()->GetPlayerID());
|
|
|
|
//if (false) //plNetClientApp::GetInstance()->GetFlagsBit(plNetClientApp::kEchoVoice))
|
|
|
|
// pMsg.SetBit(plNetMessage::kEchoBackToSender);
|
|
|
|
plNetClientApp::GetInstance()->SendMsg(&pMsg);
|
|
|
|
|
|
|
|
}
|
|
|
|
else // use the speex voice compression lib
|
|
|
|
{
|
|
|
|
UInt8 *packet = TRACKED_NEW UInt8[totalSamples]; // packet to send encoded data in
|
|
|
|
int packedLength = 0; // the size of the packet that will be sent
|
|
|
|
hsRAMStream ram; // ram stream to hold output data from speex
|
|
|
|
UInt8 numFrames = totalSamples / EncoderFrameSize; // number of frames to be encoded
|
|
|
|
|
|
|
|
// encode the data using speex
|
|
|
|
plSpeex::GetInstance()->Encode(buffer, numFrames, &packedLength, &ram);
|
|
|
|
|
|
|
|
if (packedLength)
|
|
|
|
{
|
|
|
|
// extract data from ram stream into packet
|
|
|
|
ram.Rewind();
|
|
|
|
ram.Read(packedLength, packet);
|
|
|
|
plNetMsgVoice pMsg;
|
|
|
|
pMsg.SetNetProtocol(kNetProtocolCli2Game);
|
|
|
|
|
|
|
|
pMsg.SetVoiceData((char *)packet, packedLength);
|
|
|
|
pMsg.SetPlayerID(plNetClientApp::GetInstance()->GetPlayerID());
|
|
|
|
pMsg.SetFlag(VOICE_ENCODED); // Set encoded flag
|
|
|
|
pMsg.SetNumFrames(numFrames);
|
|
|
|
if (plNetClientApp::GetInstance()->GetFlagsBit(plNetClientApp::kEchoVoice))
|
|
|
|
pMsg.SetBit(plNetMessage::kEchoBackToSender);
|
|
|
|
|
|
|
|
plNetClientApp::GetInstance()->SendMsg(&pMsg);
|
|
|
|
}
|
|
|
|
delete[] packet;
|
|
|
|
}
|
|
|
|
delete[] buffer;
|
|
|
|
}
|
|
|
|
else if(!fMikeOpen)
|
|
|
|
{
|
|
|
|
short *buffer = TRACKED_NEW short[samples];
|
|
|
|
// the mike has since closed, and there isn't enough data to meet our minimum, so throw this data out
|
|
|
|
alcCaptureSamples(captureDevice, buffer, samples);
|
|
|
|
delete[] buffer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plVoicePlayer::plVoicePlayer()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
plVoicePlayer::~plVoicePlayer()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoicePlayer::PlaybackUncompressedVoiceMessage(void* data, unsigned size)
|
|
|
|
{
|
|
|
|
if(fEnabled)
|
|
|
|
{
|
|
|
|
if(!fSound.IsPlaying())
|
|
|
|
{
|
|
|
|
fSound.Play();
|
|
|
|
}
|
|
|
|
fSound.AddVoiceData(data, size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoicePlayer::PlaybackVoiceMessage(void* data, unsigned size, int numFramesInBuffer)
|
|
|
|
{
|
|
|
|
if(fEnabled)
|
|
|
|
{
|
|
|
|
int numBytes; // the number of bytes that speex decompressed the data to.
|
|
|
|
int bufferSize = numFramesInBuffer * plSpeex::GetInstance()->GetFrameSize();
|
|
|
|
short *nBuff = TRACKED_NEW short[bufferSize];
|
|
|
|
memset(nBuff, 0, bufferSize);
|
|
|
|
|
|
|
|
// Decode the encoded voice data using speex
|
|
|
|
if(!plSpeex::GetInstance()->Decode((UInt8 *)data, size, numFramesInBuffer, &numBytes, nBuff))
|
|
|
|
{
|
|
|
|
delete[] nBuff;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
BYTE* newBuff;
|
|
|
|
newBuff = (BYTE*)nBuff; // Convert to byte data
|
|
|
|
PlaybackUncompressedVoiceMessage(newBuff, numBytes); // playback uncompressed data
|
|
|
|
delete[] nBuff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoicePlayer::SetVelocity(const hsVector3 vel)
|
|
|
|
{
|
|
|
|
fSound.SetVelocity(vel);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoicePlayer::SetPosition(const hsPoint3 pos)
|
|
|
|
{
|
|
|
|
fSound.SetPosition(pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoicePlayer::SetOrientation(const hsPoint3 pos)
|
|
|
|
{
|
|
|
|
fSound.SetConeOrientation(pos.fX, pos.fY, pos.fZ);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
*
|
|
|
|
* plVoiceSound
|
|
|
|
*
|
|
|
|
***/
|
|
|
|
unsigned plVoiceSound::fCount = 0;
|
|
|
|
|
|
|
|
plVoiceSound::plVoiceSound()
|
|
|
|
{
|
|
|
|
fInnerCone = 90;
|
|
|
|
fOuterCone = 240;
|
|
|
|
fOuterVol = -2000;
|
|
|
|
|
|
|
|
fMinFalloff = 15;
|
|
|
|
fMaxFalloff = 75;
|
|
|
|
|
|
|
|
fProperties = 0;
|
|
|
|
fCurrVolume = 1.0;
|
|
|
|
fDesiredVol = 1.0;
|
|
|
|
|
|
|
|
fPriority = 1;
|
|
|
|
fType = plgAudioSys::kVoice;
|
|
|
|
|
|
|
|
fEAXSettings.SetRoomParams(-1200, -100, 0, 0);
|
|
|
|
fLastUpdate = 0;
|
|
|
|
|
|
|
|
char keyName[32];
|
|
|
|
StrPrintf(keyName, arrsize(keyName), "VoiceSound_%d", fCount);
|
|
|
|
fCount++;
|
|
|
|
hsgResMgr::ResMgr()->NewKey(keyName, this, plLocation::kGlobalFixedLoc);
|
|
|
|
}
|
|
|
|
|
|
|
|
plVoiceSound::~plVoiceSound()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plVoiceSound::LoadSound( hsBool is3D )
|
|
|
|
{
|
|
|
|
if( fFailed )
|
|
|
|
return false;
|
|
|
|
if( !plgAudioSys::Active() || fDSoundBuffer )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if( fPriority > plgAudioSys::GetPriorityCutoff() )
|
|
|
|
return false; // Don't set the failed flag, just return
|
|
|
|
|
|
|
|
plWAVHeader header;
|
|
|
|
header.fFormatTag = WAVE_FORMAT_PCM;
|
|
|
|
header.fBitsPerSample = 16;
|
|
|
|
header.fNumChannels = 1;
|
|
|
|
header.fNumSamplesPerSec = FREQUENCY;
|
|
|
|
header.fBlockAlign = header.fNumChannels * header.fBitsPerSample / 2;
|
|
|
|
header.fAvgBytesPerSec = header.fNumSamplesPerSec * header.fBlockAlign;
|
|
|
|
|
|
|
|
fDSoundBuffer = TRACKED_NEW plDSoundBuffer(0, header, true, false, false, true);
|
|
|
|
if(!fDSoundBuffer)
|
|
|
|
return false;
|
|
|
|
fDSoundBuffer->SetupVoiceSource();
|
|
|
|
|
|
|
|
IRefreshParams();
|
|
|
|
IRefreshEAXSettings( true );
|
|
|
|
fDSoundBuffer->SetScalarVolume(1.0);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceSound::Play()
|
|
|
|
{
|
|
|
|
fPlaying = true;
|
|
|
|
if( IWillBeAbleToPlay() )
|
|
|
|
{
|
|
|
|
IRefreshParams();
|
|
|
|
SetVolume( fDesiredVol );
|
|
|
|
IActuallyPlay();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceSound::IDerivedActuallyPlay( void )
|
|
|
|
{
|
|
|
|
if( !fReallyPlaying )
|
|
|
|
{
|
|
|
|
fDSoundBuffer->Play();
|
|
|
|
fReallyPlaying = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceSound::AddVoiceData(void *data, unsigned bytes)
|
|
|
|
{
|
|
|
|
unsigned size;
|
|
|
|
unsigned bufferId;
|
|
|
|
if(!fDSoundBuffer)
|
|
|
|
{
|
|
|
|
if(!LoadSound(true))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fDSoundBuffer->UnQueueVoiceBuffers(); // attempt to unque any buffers that have finished
|
|
|
|
while(bytes > 0)
|
|
|
|
{
|
|
|
|
size = bytes < STREAM_BUFFER_SIZE ? bytes : STREAM_BUFFER_SIZE;
|
|
|
|
if(!fDSoundBuffer->GetAvailableBufferId(&bufferId))
|
|
|
|
break; // if there isn't any room for the data, it is currently thrown out
|
|
|
|
|
|
|
|
fDSoundBuffer->VoiceFillBuffer(data, size, bufferId);
|
|
|
|
bytes -= size;
|
|
|
|
}
|
|
|
|
fLastUpdate = hsTimer::GetMilliSeconds();
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceSound::Update()
|
|
|
|
{
|
|
|
|
if(IsPlaying())
|
|
|
|
{
|
|
|
|
if((hsTimer::GetMilliSeconds() - fLastUpdate) > VOICE_STOP_MS)
|
|
|
|
{
|
|
|
|
Stop(); // terminating case for playback. Wait for x number of milliseconds, and stop.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plVoiceSound::IRefreshParams()
|
|
|
|
{
|
|
|
|
plSound::IRefreshParams();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
*
|
|
|
|
* Speex Voice Encoding/Decoding
|
|
|
|
*
|
|
|
|
***/
|
|
|
|
|
|
|
|
plSpeex::plSpeex() :
|
|
|
|
fBits(nil),
|
|
|
|
fEncoderState(nil),
|
|
|
|
fDecoderState(nil),
|
|
|
|
fSampleRate(plVoiceRecorder::GetSampleRate()),
|
|
|
|
fFrameSize(-1),
|
|
|
|
fQuality(7),
|
|
|
|
fVBR(true), // variable bit rate on
|
|
|
|
fAverageBitrate(8000), // 8kb bitrate
|
|
|
|
fComplexity(3),
|
|
|
|
fENH(false),
|
|
|
|
fInitialized(false)
|
|
|
|
{
|
|
|
|
fBits = TRACKED_NEW SpeexBits;
|
|
|
|
Init(kNarrowband); // if no one initialized us initialize using a narrowband encoder
|
|
|
|
}
|
|
|
|
|
|
|
|
plSpeex::~plSpeex()
|
|
|
|
{
|
|
|
|
Shutdown();
|
|
|
|
delete fBits;
|
|
|
|
fBits = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plSpeex::Init(Mode mode)
|
|
|
|
{
|
|
|
|
int enh = 1;
|
|
|
|
|
|
|
|
// setup speex
|
|
|
|
speex_bits_init(fBits);
|
|
|
|
fBitsInit = true;
|
|
|
|
|
|
|
|
if(mode == kNarrowband)
|
|
|
|
{
|
|
|
|
fEncoderState = speex_encoder_init(&speex_nb_mode); // narrowband
|
|
|
|
fDecoderState = speex_decoder_init(&speex_nb_mode);
|
|
|
|
}
|
|
|
|
else if(mode == kWideband)
|
|
|
|
{
|
|
|
|
fEncoderState = speex_encoder_init(&speex_wb_mode);
|
|
|
|
fDecoderState = speex_decoder_init(&speex_wb_mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
speex_encoder_ctl(fEncoderState, SPEEX_GET_FRAME_SIZE, &fFrameSize); // get frame size
|
|
|
|
speex_encoder_ctl(fEncoderState, SPEEX_SET_COMPLEXITY, &fComplexity); // 3
|
|
|
|
speex_encoder_ctl(fEncoderState, SPEEX_SET_SAMPLING_RATE, &fSampleRate); // 8 khz
|
|
|
|
speex_encoder_ctl(fEncoderState, SPEEX_SET_VBR_QUALITY, &fQuality); // 7
|
|
|
|
speex_encoder_ctl(fEncoderState, SPEEX_SET_VBR, &fVBR); // use variable bit rate
|
|
|
|
speex_encoder_ctl(fEncoderState, SPEEX_SET_ABR, &fAverageBitrate); // default to 8kb
|
|
|
|
|
|
|
|
speex_decoder_ctl(fDecoderState, SPEEX_SET_ENH, &fENH); // perceptual enhancement
|
|
|
|
|
|
|
|
fInitialized = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plSpeex::Shutdown()
|
|
|
|
{
|
|
|
|
//shutdown speex
|
|
|
|
if(fDecoderState)
|
|
|
|
{
|
|
|
|
speex_decoder_destroy(fDecoderState);
|
|
|
|
fDecoderState = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(fEncoderState)
|
|
|
|
{
|
|
|
|
speex_encoder_destroy(fEncoderState);
|
|
|
|
fEncoderState = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(fBitsInit)
|
|
|
|
{
|
|
|
|
speex_bits_destroy(fBits);
|
|
|
|
fBitsInit = false;
|
|
|
|
}
|
|
|
|
fInitialized = false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plSpeex::Encode(short *data, int numFrames, int *packedLength, hsRAMStream *out)
|
|
|
|
{
|
|
|
|
*packedLength = 0;
|
|
|
|
|
|
|
|
short *pData = data; // pointer to input data
|
|
|
|
float *input = TRACKED_NEW float[fFrameSize]; // input to speex - used as am intermediate array since speex requires float data
|
|
|
|
BYTE frameLength; // number of bytes speex compressed frame to
|
|
|
|
BYTE *frameData = TRACKED_NEW BYTE[fFrameSize]; // holds one frame of encoded data
|
|
|
|
|
|
|
|
// encode data
|
|
|
|
for( int i = 0; i < numFrames; i++ )
|
|
|
|
{
|
|
|
|
// convert input data to floats
|
|
|
|
for( int j = 0; j < fFrameSize; j++ )
|
|
|
|
{
|
|
|
|
input[j] = pData[j];
|
|
|
|
}
|
|
|
|
|
|
|
|
speex_bits_reset(fBits); // reset bit structure
|
|
|
|
|
|
|
|
// encode data using speex
|
|
|
|
speex_encode(fEncoderState, input, fBits);
|
|
|
|
frameLength = speex_bits_write(fBits, (char *)frameData, fFrameSize);
|
|
|
|
|
|
|
|
// write data - length and bytes
|
|
|
|
out->WriteSwap(frameLength);
|
|
|
|
*packedLength += sizeof(frameLength); // add length of encoded frame
|
|
|
|
out->Write(frameLength, frameData);
|
|
|
|
*packedLength += frameLength; // update length
|
|
|
|
|
|
|
|
pData += fFrameSize; // move input pointer
|
|
|
|
}
|
|
|
|
|
|
|
|
delete[] frameData;
|
|
|
|
delete[] input;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plSpeex::Decode(UInt8 *data, int size, int numFrames, int *numOutputBytes, short *out)
|
|
|
|
{
|
|
|
|
if(!fInitialized) return false;
|
|
|
|
*numOutputBytes = 0;
|
|
|
|
|
|
|
|
hsReadOnlyStream stream( size, data );
|
|
|
|
float *speexOutput = TRACKED_NEW float[fFrameSize]; // holds output from speex
|
|
|
|
short *pOut = out; // pointer to output short buffer
|
|
|
|
|
|
|
|
// create buffer for input data
|
|
|
|
BYTE *frameData = TRACKED_NEW BYTE[fFrameSize]; // holds the current frames data to be decoded
|
|
|
|
BYTE frameLen; // holds the length of the current frame being decoded.
|
|
|
|
|
|
|
|
|
|
|
|
// Decode data
|
|
|
|
for (int i = 0; i < numFrames; i++)
|
|
|
|
{
|
|
|
|
stream.ReadSwap( &frameLen ); // read the length of the current frame to be decoded
|
|
|
|
stream.Read( frameLen, frameData ); // read the data
|
|
|
|
|
|
|
|
memset(speexOutput, 0, fFrameSize * sizeof(float));
|
|
|
|
speex_bits_read_from(fBits, (char *)frameData, frameLen); // give data to speex
|
|
|
|
speex_decode(fDecoderState, fBits, speexOutput); // decode data
|
|
|
|
|
|
|
|
for(int j = 0; j < fFrameSize; j++)
|
|
|
|
{
|
|
|
|
pOut[j] = (short)(speexOutput[j]); // convert floats to shorts
|
|
|
|
}
|
|
|
|
|
|
|
|
pOut += fFrameSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete[] frameData;
|
|
|
|
delete[] speexOutput;
|
|
|
|
|
|
|
|
*numOutputBytes = (numFrames * fFrameSize) * sizeof(short); // length of decoded voice data(out) in bytes
|
|
|
|
if(*numOutputBytes == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sets variable bit rate on/off
|
|
|
|
void plSpeex::VBR(hsBool b)
|
|
|
|
{
|
|
|
|
fVBR = b;
|
|
|
|
speex_encoder_ctl(fEncoderState, SPEEX_SET_VBR, &fVBR);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Sets the average bit rate
|
|
|
|
void plSpeex::SetABR(UInt32 abr)
|
|
|
|
{
|
|
|
|
fAverageBitrate = abr;
|
|
|
|
speex_encoder_ctl(fEncoderState, SPEEX_SET_ABR, &fAverageBitrate);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sets the quality of encoding
|
|
|
|
void plSpeex::SetQuality(UInt32 quality)
|
|
|
|
{
|
|
|
|
fQuality = quality;
|
|
|
|
speex_encoder_ctl(fEncoderState, SPEEX_SET_QUALITY, &fQuality);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plSpeex::SetENH(hsBool b)
|
|
|
|
{
|
|
|
|
fENH = b;
|
|
|
|
speex_decoder_ctl(fDecoderState, SPEEX_SET_ENH, &fENH);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plSpeex::SetComplexity(UInt8 c)
|
|
|
|
{
|
|
|
|
fComplexity = c;
|
|
|
|
speex_encoder_ctl(fEncoderState, SPEEX_SET_COMPLEXITY, &fComplexity);
|
|
|
|
}
|