summaryrefslogtreecommitdiffstats
path: root/src/modules/dcc/voice.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/dcc/voice.cpp')
-rw-r--r--src/modules/dcc/voice.cpp1041
1 files changed, 1041 insertions, 0 deletions
diff --git a/src/modules/dcc/voice.cpp b/src/modules/dcc/voice.cpp
new file mode 100644
index 00000000..2e34df2f
--- /dev/null
+++ b/src/modules/dcc/voice.cpp
@@ -0,0 +1,1041 @@
+//
+// File : voice.cpp
+// Creation date : Thu Aug 23 04:08:09 2001 GMT by Szymon Stefanek
+//
+// This file is part of the KVirc irc client distribution
+// Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net)
+//
+// 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 2
+// of the License, or (at your opinion) 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, write to the Free Software Foundation,
+// Inc. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//
+
+#include "voice.h"
+#include "marshal.h"
+#include "broker.h"
+
+#include "kvi_settings.h"
+#include "kvi_iconmanager.h"
+#include "kvi_ircview.h"
+#include "kvi_locale.h"
+#include "kvi_out.h"
+#include "kvi_error.h"
+#include "kvi_netutils.h"
+#include "kvi_options.h"
+#include "kvi_console.h"
+#include "kvi_malloc.h"
+#include "kvi_socket.h"
+#include "kvi_ircconnection.h"
+
+#include "adpcmcodec.h"
+#include "gsmcodec.h"
+
+#include <qframe.h>
+#include <qsplitter.h>
+#include "kvi_tal_vbox.h"
+#include <qslider.h>
+#include <qtooltip.h>
+
+#ifndef COMPILE_ON_WINDOWS
+ #include <sys/time.h>
+ #include <sys/types.h>
+ #include <unistd.h>
+ #include <errno.h>
+ #include <fcntl.h>
+//#include "kvi_error.h"
+
+#include <sys/stat.h> // for open()
+#include <sys/ioctl.h> // for ioctl()
+#endif //!COMPILE_ON_WIDNOWS
+
+extern KviDccBroker * g_pDccBroker;
+
+//Check for the *SS Api....we don't want to fail here...
+
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ #ifdef HAVE_LINUX_SOUNDCARD_H
+ #include <linux/soundcard.h>
+ #else
+ #ifdef HAVE_SYS_SOUNDCARD_H
+ #include <sys/soundcard.h>
+ #else
+ #ifdef HAVE_SOUNDCARD_H
+ #include <soundcard.h>
+ #else
+ //CAN NOT COMPILE :(
+ #define COMPILE_DISABLE_DCC_VOICE
+ #ifndef COMPILE_ON_WINDOWS
+ #warning "Cannot find the soundcard.h header; you will NOT be able to use DCC Voice"
+ #endif
+ #endif
+ #endif
+ #endif
+#endif
+
+
+//#define KVI_AUDIO_DEVICE "/dev/dsp"
+// 32 fragments , 512 bytes
+#define KVI_SNDCTL_FRAG_SIZE 0x00B00009
+#define KVI_FRAGMENT_SIZE_IN_BYTES 512
+#define KVI_FORMAT AFMT_S16_LE
+#define KVI_NUM_CHANNELS 1
+
+
+bool kvi_dcc_voice_is_valid_codec(const char * codecName)
+{
+#ifdef COMPILE_USE_GSM
+ if(kvi_strEqualCI("gsm",codecName))
+ {
+ return kvi_gsm_codec_init();
+ }
+#endif
+ if(kvi_strEqualCI("adpcm",codecName))return true;
+ if(kvi_strEqualCI("null",codecName))return true;
+ return false;
+}
+
+static KviDccVoiceCodec * kvi_dcc_voice_get_codec(const char * codecName)
+{
+#ifdef COMPILE_USE_GSM
+ if(kvi_strEqualCI("gsm",codecName))
+ {
+ if(kvi_gsm_codec_init())return new KviDccVoiceGsmCodec();
+ }
+#endif
+ if(kvi_strEqualCI("adpcm",codecName))return new KviDccVoiceAdpcmCodec();
+ if(kvi_strEqualCI("null",codecName))return new KviDccVoiceNullCodec();
+ return new KviDccVoiceAdpcmCodec();
+}
+
+
+KviDccVoiceThread::KviDccVoiceThread(KviWindow * wnd,kvi_socket_t fd,KviDccVoiceThreadOptions * opt)
+: KviDccThread(wnd,fd)
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ m_pOpt = opt;
+ m_bPlaying = false;
+ m_bRecording = false;
+ m_bSoundcardChecked = false;
+ m_soundFd = -1;
+ m_soundFdMode = 0;
+ m_pInfoMutex = new KviMutex();
+ m_bRecordingRequestPending = false;
+#endif
+}
+
+KviDccVoiceThread::~KviDccVoiceThread()
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ delete m_pOpt->pCodec;
+ delete m_pOpt;
+ delete m_pInfoMutex;
+#endif
+}
+
+
+bool KviDccVoiceThread::checkSoundcard()
+{
+#ifdef COMPILE_DISABLE_DCC_VOICE
+ return false;
+#else
+ bool bOpened = false;
+ if(m_soundFd == -1)
+ {
+ if(!openSoundcard(O_RDONLY))return false;
+ bOpened = true;
+ }
+
+ int caps;
+
+ m_bSoundcardChecked = true;
+
+ if(ioctl(m_soundFd,SNDCTL_DSP_GETCAPS,&caps) < 0)
+ {
+ postMessageEvent(__tr2qs_ctx("WARNING: failed to check the soundcard duplex capabilities: if this is a half-duplex soundcard , use the DCC VOICE option to force half-duplex algorithm","dcc"));
+ if(bOpened)closeSoundcard();
+ return false;
+ }
+
+ if(!(caps & DSP_CAP_DUPLEX))
+ {
+ m_pOpt->bForceHalfDuplex = true; // the device is half duplex...use it in that way
+ postMessageEvent(__tr2qs_ctx("Half duplex soundcard detected, you will not be able to talk and listen at the same time","dcc"));
+ }
+
+ if(bOpened)closeSoundcard();
+
+ return true;
+#endif
+}
+
+
+bool KviDccVoiceThread::openSoundcard(int mode)
+{
+#ifdef COMPILE_DISABLE_DCC_VOICE
+ return false;
+#else
+ int speed = m_pOpt->iSampleRate;
+ static int chans=KVI_NUM_CHANNELS;
+ static int fmt=KVI_FORMAT;
+ static int frag = KVI_SNDCTL_FRAG_SIZE;
+
+
+ if(m_soundFd != -1)
+ {
+ if(m_soundFdMode == mode)return true; // already open
+ closeSoundcard();
+ }
+
+ m_soundFd = ::open(m_pOpt->szSoundDevice.ptr(),mode | O_NONBLOCK);
+ if(m_soundFd < 0)return false;
+
+ if(!m_pOpt->bForceHalfDuplex)
+ {
+ if(ioctl(m_soundFd,SNDCTL_DSP_SETDUPLEX,0) < 0)goto exit_false;
+ }
+
+ if(ioctl(m_soundFd,SNDCTL_DSP_SETFRAGMENT,&frag)<0)goto exit_false;
+ if(ioctl(m_soundFd,SNDCTL_DSP_SETFMT,&fmt)<0)goto exit_false;
+ if(ioctl(m_soundFd,SNDCTL_DSP_CHANNELS,&chans)<0)goto exit_false;
+ if(ioctl(m_soundFd,SNDCTL_DSP_SPEED,&speed)<0)goto exit_false;
+ if(speed != m_pOpt->iSampleRate)
+ {
+ KviStr tmp(KviStr::Format,__tr2qs_ctx("WARNING: failed to set the requested sample rate (%d): the device used closest match (%d)","dcc"),
+ m_pOpt->iSampleRate,speed);
+ postMessageEvent(tmp.ptr());
+ }
+
+ // TODO: #warning "We could also support blocking operations mode"
+
+ m_soundFdMode = mode;
+
+
+ return true;
+
+exit_false:
+ closeSoundcard();
+ return false;
+#endif
+}
+
+bool KviDccVoiceThread::openSoundcardForWriting()
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ return openSoundcardWithDuplexOption(O_WRONLY,O_RDONLY);
+#else
+ return false;
+#endif
+}
+
+bool KviDccVoiceThread::openSoundcardForReading()
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ return openSoundcardWithDuplexOption(O_RDONLY,O_WRONLY);
+#else
+ return false;
+#endif
+}
+
+bool KviDccVoiceThread::openSoundcardWithDuplexOption(int openMode,int failMode)
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ if(m_soundFd == -1)
+ {
+ // soundcard not open yet...open for write (at least)
+ if(m_pOpt->bForceHalfDuplex)
+ {
+ // Forcing half duplex... (user option or the card does not support full duplex mode)
+ if(!openSoundcard(openMode))return false;
+ } else {
+ // Try read/write open
+ if(!openSoundcard(O_RDWR))
+ {
+ // half-duplex sound card ?
+ if(!m_bSoundcardChecked)
+ {
+ // We haven't checked the full-duplex support yet...
+ // Try to open in RDONLY o WRONLY mode
+ if(!openSoundcard(openMode))return false;
+ if(!checkSoundcard())
+ {
+ postMessageEvent(__tr2qs_ctx("Ops...failed to test the soundcard capabilities...expect problems...","dcc"));
+ }
+ } // else the test has been done and it is a full duplex card that is just busy
+ }
+ }
+ } else {
+ // Hmmm...already open
+ // If it is open in O_RDWR or O_WRONLY mode...it is ok for us
+ // but if it is open in O_RDONLY mode...we can do nothing...just wait
+ return (m_soundFdMode != failMode);
+ }
+
+
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+void KviDccVoiceThread::closeSoundcard()
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ if(m_soundFd != -1)
+ {
+ ::close(m_soundFd);
+ m_soundFd = -1;
+ m_soundFdMode = 0;
+ }
+#endif
+}
+
+
+
+bool KviDccVoiceThread::readWriteStep()
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ // Socket management
+ bool bCanRead;
+ bool bCanWrite;
+
+ if(kvi_select(m_fd,&bCanRead,&bCanWrite))
+ {
+ if(bCanRead)
+ {
+ unsigned int actualSize = m_inFrameBuffer.size();
+ m_inFrameBuffer.resize(actualSize + 1024);
+ int readLen = kvi_socket_recv(m_fd,(void *)(m_inFrameBuffer.data() + actualSize),1024);
+ if(readLen > 0)
+ {
+ if(readLen < 1024)m_inFrameBuffer.resize(actualSize + readLen);
+ m_pOpt->pCodec->decode(&m_inFrameBuffer,&m_inSignalBuffer);
+//#warning "A maximum length for the signal buffer is actually needed!!!"
+ } else {
+ if(!handleInvalidSocketRead(readLen))return false;
+ m_inFrameBuffer.resize(actualSize);
+ }
+ }// else {
+ // m_uSleepTime += 100;
+ //}
+
+ if(bCanWrite)
+ {
+ // Have somethihg to write ?
+ if(m_outFrameBuffer.size() > 0)
+ {
+ int written = kvi_socket_send(m_fd,m_outFrameBuffer.data(),m_outFrameBuffer.size());
+ if(written > 0)
+ {
+ m_outFrameBuffer.remove(written);
+ } else {
+ if(!handleInvalidSocketRead(written))return false;
+ }
+ }// else {
+ // m_uSleepTime += 100;
+ // }
+ }// else {
+ // m_uSleepTime += 100;
+// }
+//#warning "Usleep here ?"
+ }// else {
+// if(!(m_bPlaying || m_bRecording))
+// {
+// // Really NOTHING is happening...sleep a bit more
+// m_uSleepTime += 800;
+// } else {
+// m_uSleepTime += 100;
+// }
+// }
+
+#endif // !COMPILE_DISABLE_DCC_VOICE
+ return true;
+}
+
+bool KviDccVoiceThread::soundStep()
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ // Are we playing ?
+ if(m_bPlaying)
+ {
+ // Do we have something to write ?
+ audio_buf_info info;
+ if(m_inSignalBuffer.size() > 0)
+ {
+ // Get the number of fragments that can be written to the soundcard without blocking
+
+ if(ioctl(m_soundFd,SNDCTL_DSP_GETOSPACE,&info) < 0)
+ {
+ debug("get o space failed");
+ info.bytes = KVI_FRAGMENT_SIZE_IN_BYTES; // dummy... if this is not correct...well...we will block for 1024/16000 of a sec
+ info.fragments = 1;
+ info.fragsize = KVI_FRAGMENT_SIZE_IN_BYTES;
+ }
+ if(info.fragments > 0)
+ {
+ int toWrite = info.fragments * info.fragsize;
+ //debug("Can write %d bytes",toWrite);
+ if(m_inSignalBuffer.size() < toWrite)toWrite = m_inSignalBuffer.size();
+ int written = write(m_soundFd,m_inSignalBuffer.data(),toWrite);
+ if(written > 0)m_inSignalBuffer.remove(written);
+ else {
+//#warning "Do something for -1 here ?"
+//#warning "Usleep ?"
+ }
+ } //else {
+ // No stuff can be written...we are running too fast ?
+ // m_uSleepTime += 100; // sleep for a while
+ //}
+ } else {
+ // hmmmm....playing , but nothing to write , possible underrun or EOF
+ // a nice idea would be to use SNDCTL_DSP_GETODELAY here...
+ // but it appears to be broken on some audio devices
+ if(ioctl(m_soundFd,SNDCTL_DSP_GETOSPACE,&info) < 0)info.fragstotal = info.fragments; // dummy...but what should we do ?
+ if(info.fragstotal == info.fragments)
+ {
+ // underrun or EOF: close the device
+ stopPlaying();
+ }
+ }
+ } else {
+ // do we have anything to play ?
+ if(m_inSignalBuffer.size() > 0)
+ {
+ if(m_inSignalBuffer.size() >= m_pOpt->iPreBufferSize)
+ {
+ // yep...stuff to play... open the soundcard , if possible
+ startPlaying();
+
+ m_iLastSignalBufferSize = m_inSignalBuffer.size();
+ } else {
+ // have stuff to play , but it's not enough to fill the pre-buffer
+ //
+ struct timeval tv;
+ gettimeofday(&tv,0);
+
+ long int sigBufferTime = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
+
+ if(m_inSignalBuffer.size() == m_iLastSignalBufferSize)
+ {
+ // the same signal buffer size... check the time
+ // m_pOpt->iPreBufferSize / 16 gives us the preBufferTime in msecs
+ // we calc the remaining preBufferTime by subtracting the
+ // size of buffer already filled and we also add 50 milliseconds... smart heuristic
+ int preBufferTime = ((m_pOpt->iPreBufferSize - m_iLastSignalBufferSize) / 16) + 50;
+ // if the buffer size hasn't changed since preBufferTime
+ // it's time to start playing anyway, since there is
+ // either a network stall or it was just a really short data stream
+ if((sigBufferTime - m_iLastSignalBufferTime) > preBufferTime)
+ {
+ startPlaying();
+ if(m_bPlaying)m_iLastSignalBufferSize = 0;
+ }
+ } else {
+ // signal buffer size differs...we have received new packets
+ // and still pre-buffering
+ m_iLastSignalBufferSize = m_inSignalBuffer.size();
+ m_iLastSignalBufferTime = sigBufferTime;
+ }
+ }
+
+ }
+ }
+
+ // Are we recording ?
+ if(m_bRecording)
+ {
+ fd_set rs;
+ FD_ZERO(&rs);
+ FD_SET(m_soundFd,&rs);
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 10;
+ int ret = select(m_soundFd + 1,&rs,0,0,&tv);
+ if(ret > 0)
+ {
+ // This is rather easy...
+ audio_buf_info info;
+ if(ioctl(m_soundFd,SNDCTL_DSP_GETISPACE,&info) < 0)
+ {
+ debug("Ispace failed");
+ info.fragments = 0; // dummy...
+ info.bytes = 0;
+ }
+
+ //debug("INFO: fragments: %d, fragstotal: %d, fragsize: %d, bytes: %d",info.fragments,info.fragstotal,info.fragsize,info.bytes);
+
+ if(info.fragments == 0 && info.bytes == 0)
+ {
+ // force a dummy read: the device needs to be triggered
+ info.fragments = 1;
+ }
+
+ if(info.fragments > 0)
+ {
+ int oldSize = m_outSignalBuffer.size();
+ int available = info.fragments * info.fragsize;
+ m_outSignalBuffer.addSize(available);
+ int readed = read(m_soundFd,m_outSignalBuffer.data() + oldSize,available);
+
+ if(readed < available)
+ {
+ // huh ? ...error ?
+ if(readed >= 0)m_outSignalBuffer.resize(oldSize + readed);
+ else {
+ if((errno == EINTR) || (errno == EAGAIN))
+ {
+ m_outSignalBuffer.resize(oldSize);
+ } else {
+//#warning "Critical error...do something reasonable!"
+ m_outSignalBuffer.resize(oldSize);
+ }
+ }
+ }
+/*
+ debug("Signal buffer:");
+ for(int i=0;i<200;i+=2)
+ {
+ if(i >= m_outSignalBuffer.size())break;
+ printf("%04x ",*(((unsigned short *)(m_outSignalBuffer.data() + i))));
+ if((i % 6) == 0)printf("\n");
+ }
+ debug("END\n");
+*/
+ m_pOpt->pCodec->encode(&m_outSignalBuffer,&m_outFrameBuffer);
+ }
+ }// else {
+ // Nothing to read
+ // m_uSleepTime += 100;
+ // }
+ }
+
+#endif // !COMPILE_DISABLE_DCC_VOICE
+ return true;
+}
+
+void KviDccVoiceThread::startRecording()
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ //debug("Start recording");
+ if(m_bRecording)return; // already started
+ if(openSoundcardForReading())
+ {
+// debug("Posting event");
+ KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
+ e->setData(new int(KVI_DCC_VOICE_THREAD_ACTION_START_RECORDING));
+ postEvent(parent(),e);
+
+ m_bRecording = true;
+ m_bRecordingRequestPending = false;
+ } else {
+ m_bRecordingRequestPending = true;
+ }
+#endif
+}
+
+void KviDccVoiceThread::stopRecording()
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ //debug("Stop recording");
+ m_bRecordingRequestPending = false;
+ if(!m_bRecording)return; // already stopped
+
+ KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
+ e->setData(new int(KVI_DCC_VOICE_THREAD_ACTION_STOP_RECORDING));
+ postEvent(parent(),e);
+
+ m_bRecording = false;
+ if(!m_bPlaying)closeSoundcard();
+#endif
+}
+
+void KviDccVoiceThread::startPlaying()
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ //debug("Start playing");
+ if(m_bPlaying)return;
+
+ if(openSoundcardForWriting())
+ {
+ KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
+ e->setData(new int(KVI_DCC_VOICE_THREAD_ACTION_START_PLAYING));
+ postEvent(parent(),e);
+ m_bPlaying = true;
+ }
+#endif
+}
+
+void KviDccVoiceThread::stopPlaying()
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ //debug("Stop playing");
+ if(!m_bPlaying)return;
+
+ KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
+ e->setData(new int(KVI_DCC_VOICE_THREAD_ACTION_STOP_PLAYING));
+ postEvent(parent(),e);
+
+ m_bPlaying = false;
+ if(!m_bRecording)closeSoundcard();
+#endif
+}
+
+void KviDccVoiceThread::run()
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ for(;;)
+ {
+// m_uSleepTime = 0;
+
+ // Dequeue events
+ while(KviThreadEvent * e = dequeueEvent())
+ {
+ if(e->id() == KVI_THREAD_EVENT_TERMINATE)
+ {
+ delete e;
+ goto exit_dcc;
+ } else if(e->id() == KVI_DCC_THREAD_EVENT_ACTION)
+ {
+ int * act = ((KviThreadDataEvent<int> *)e)->getData();
+ if(*act)startRecording();
+ else stopRecording();
+ delete act;
+ delete e;
+ } else {
+ // Other events are senseless to us
+ delete e;
+ }
+ }
+
+ if(!readWriteStep())goto exit_dcc;
+ if(!soundStep())goto exit_dcc;
+
+ m_pInfoMutex->lock();
+ m_iInputBufferSize = m_inSignalBuffer.size();
+ m_iOutputBufferSize = (m_outFrameBuffer.size() / m_pOpt->pCodec->encodedFrameSize()) * m_pOpt->pCodec->decodedFrameSize();
+ m_pInfoMutex->unlock();
+
+ // Actually the maximum that we can sleep here is
+ // around 500 usecs... = 0.0005 sec -> 8 bytes at 8 KHz
+
+ // if(m_uSleepTime)usleep(m_uSleepTime);
+
+ // Start recording if the request was not fulfilled yet
+ if(m_bRecordingRequestPending)startRecording();
+ }
+
+
+exit_dcc:
+
+#endif //! COMPILE_DISABLE_DCC_VOICE
+ closeSoundcard();
+ kvi_socket_close(m_fd);
+ m_fd = KVI_INVALID_SOCKET;
+}
+
+
+
+
+KviDccVoice::KviDccVoice(KviFrame *pFrm,KviDccDescriptor * dcc,const char * name)
+: KviDccWindow(KVI_WINDOW_TYPE_DCCVOICE,pFrm,name,dcc)
+{
+ m_pDescriptor = dcc;
+ m_pSlaveThread = 0;
+
+ m_pSplitter = new QSplitter(Qt::Horizontal,this,"splitter");
+ m_pIrcView = new KviIrcView(m_pSplitter,pFrm,this);
+
+ m_pHBox = new KviTalHBox(this);
+
+ KviTalVBox * vbox = new KviTalVBox(m_pHBox);
+
+ m_pInputLabel = new QLabel(__tr2qs_ctx("Input buffer","dcc"),vbox);
+ m_pInputLabel->setFrameStyle(QFrame::Sunken | QFrame::Panel);
+ m_pOutputLabel = new QLabel(__tr2qs_ctx("Output buffer","dcc"),vbox);
+ m_pOutputLabel->setFrameStyle(QFrame::Sunken | QFrame::Panel);
+ vbox->setSpacing(1);
+
+ KviTalVBox * vbox2 = new KviTalVBox(m_pHBox);
+
+ m_pRecordingLabel = new QLabel(vbox2);
+ m_pRecordingLabel->setPixmap(*(g_pIconManager->getSmallIcon(KVI_SMALLICON_RECORD)));
+ m_pRecordingLabel->setEnabled(false);
+ m_pRecordingLabel->setFrameStyle(QFrame::Raised | QFrame::Panel);
+
+ m_pPlayingLabel = new QLabel(vbox2);
+ m_pPlayingLabel->setPixmap(*(g_pIconManager->getSmallIcon(KVI_SMALLICON_PLAY)));
+ m_pPlayingLabel->setEnabled(false);
+ m_pPlayingLabel->setFrameStyle(QFrame::Raised | QFrame::Panel);
+
+ vbox2->setSpacing(1);
+
+//#warning "The volume slider should be enabled only when receiving data"
+ m_pVolumeSlider = new QSlider(-100, 0, 10, 0, Qt::Vertical, m_pHBox, "dcc_voice_volume_slider");
+ m_pVolumeSlider->setValue(getMixerVolume());
+/* Update the tooltip */
+ setMixerVolume(m_pVolumeSlider->value());
+ m_pVolumeSlider->setMaximumWidth(16);
+ m_pVolumeSlider->setMaximumHeight(2*m_pPlayingLabel->height());
+ connect(m_pVolumeSlider, SIGNAL(valueChanged(int)), this, SLOT(setMixerVolume(int)));
+
+ m_pTalkButton = new QToolButton(m_pHBox);
+ m_pTalkButton->setEnabled(false);
+ m_pTalkButton->setToggleButton(true);
+#if QT_VERSION >= 300
+ QIconSet iset;
+ iset.setPixmap(*(g_pIconManager->getBigIcon(KVI_BIGICON_DISCONNECTED)),QIconSet::Large,QIconSet::Normal,QIconSet::Off);
+ iset.setPixmap(*(g_pIconManager->getBigIcon(KVI_BIGICON_CONNECTED)),QIconSet::Large,QIconSet::Normal,QIconSet::On);
+ m_pTalkButton->setIconSet(iset);
+#else
+ m_pTalkButton->setOffIconSet(*(g_pIconManager->getBigIcon(KVI_BIGICON_DISCONNECTED)));
+ m_pTalkButton->setOnIconSet(*(g_pIconManager->getBigIcon(KVI_BIGICON_CONNECTED)));
+#endif
+ m_pTalkButton->setUsesBigPixmap(true);
+
+ connect(m_pTalkButton,SIGNAL(toggled(bool)),this,SLOT(startOrStopTalking(bool)));
+
+ m_pHBox->setStretchFactor(vbox,1);
+ m_pHBox->setMargin(2);
+ m_pHBox->setSpacing(1);
+
+ //setFocusHandler(m_pIrcView,this);
+
+ m_pMarshal = new KviDccMarshal(this);
+ connect(m_pMarshal,SIGNAL(error(int)),this,SLOT(handleMarshalError(int)));
+ connect(m_pMarshal,SIGNAL(connected()),this,SLOT(connected()));
+ connect(m_pMarshal,SIGNAL(inProgress()),this,SLOT(connectionInProgress()));
+
+ m_pUpdateTimer = new QTimer();
+
+ startConnection();
+}
+
+KviDccVoice::~KviDccVoice()
+{
+ g_pDccBroker->unregisterDccWindow(this);
+ if(m_pSlaveThread)
+ {
+ m_pSlaveThread->terminate();
+ delete m_pSlaveThread;
+ m_pSlaveThread = 0;
+ }
+
+ KviThreadManager::killPendingEvents(this);
+
+ delete m_pUpdateTimer;
+// delete m_pDescriptor;
+// delete m_pMarshal;
+}
+
+
+void KviDccVoice::startConnection()
+{
+ if(!(m_pDescriptor->bActive))
+ {
+ // PASSIVE CONNECTION
+ output(KVI_OUT_DCCMSG,__tr2qs_ctx("Attempting a passive DCC VOICE connection","dcc"));
+ int ret = m_pMarshal->dccListen(m_pDescriptor->szListenIp,m_pDescriptor->szListenPort,m_pDescriptor->bDoTimeout);
+ if(ret != KviError_success)handleMarshalError(ret);
+ } else {
+ // ACTIVE CONNECTION
+ output(KVI_OUT_DCCMSG,__tr2qs_ctx("Attempting an active DCC VOICE connection","dcc"));
+ int ret = m_pMarshal->dccConnect(m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data(),m_pDescriptor->bDoTimeout);
+ if(ret != KviError_success)handleMarshalError(ret);
+ }
+}
+
+void KviDccVoice::connectionInProgress()
+{
+ if(m_pDescriptor->bActive)
+ {
+ output(KVI_OUT_DCCMSG,__tr2qs_ctx("Contacting host %Q on port %Q","dcc"),&(m_pDescriptor->szIp),&(m_pDescriptor->szPort));
+ } else {
+
+ output(KVI_OUT_DCCMSG,__tr2qs_ctx("Listening on interface %Q port %Q","dcc"),
+ &(m_pMarshal->localIp()),&(m_pMarshal->localPort()));
+
+ if(m_pDescriptor->bSendRequest)
+ {
+ KviStr ip = !m_pDescriptor->szFakeIp.isEmpty() ? m_pDescriptor->szFakeIp : m_pDescriptor->szListenIp;
+ KviStr port = !m_pDescriptor->szFakePort.isEmpty() ? m_pDescriptor->szFakePort : m_pMarshal->localPort();
+//#warning "OPTION FOR SENDING 127.0.0.1 and so on (not an unsigned nuumber)"
+ struct in_addr a;
+ if(kvi_stringIpToBinaryIp(ip.ptr(),&a))ip.setNum(htonl(a.s_addr));
+
+ m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC VOICE %s %s %s %d%c",
+ m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(),
+ 0x01,m_pDescriptor->szCodec.ptr(),
+ ip.ptr(),port.ptr(),m_pDescriptor->iSampleRate,0x01);
+ output(KVI_OUT_DCCMSG,__tr2qs_ctx("Sent DCC VOICE (%s) request to %Q, waiting for the remote client to connect...","dcc"),
+ m_pDescriptor->szCodec.ptr(),&(m_pDescriptor->szNick));
+ } else output(KVI_OUT_DCCMSG,__tr2qs_ctx("DCC VOICE request not sent: awaiting manual connections","dcc"));
+ }
+}
+
+const QString & KviDccVoice::target()
+{
+ // This may change on the fly...
+ m_szTarget.sprintf("%s@%s:%s",
+ m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data());
+ return m_szTarget;
+}
+
+void KviDccVoice::getBaseLogFileName(KviStr &buffer)
+{
+ buffer.sprintf("dccvoice_%s_%s_%s",m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szLocalFileName.utf8().data(),m_pDescriptor->szPort.utf8().data());
+}
+
+void KviDccVoice::fillCaptionBuffers()
+{
+ KviStr tmp(KviStr::Format,"DCC Voice %s@%s:%s %s",
+ m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data(),
+ m_pDescriptor->szLocalFileName.utf8().data());
+
+ m_szPlainTextCaption = tmp;
+
+ m_szHtmlActiveCaption.sprintf("<nobr><font color=\"%s\"><b>%s</b></font></nobr>",
+ KVI_OPTION_COLOR(KviOption_colorCaptionTextActive).name().ascii(),tmp.ptr());
+ m_szHtmlInactiveCaption.sprintf("<nobr><font color=\"%s\"><b>%s</b></font></nobr>",
+ KVI_OPTION_COLOR(KviOption_colorCaptionTextInactive).name().ascii(),tmp.ptr());
+}
+
+QPixmap * KviDccVoice::myIconPtr()
+{
+ return g_pIconManager->getSmallIcon(KVI_SMALLICON_DCCVOICE);
+}
+
+bool KviDccVoice::event(QEvent *e)
+{
+ if(e->type() == KVI_THREAD_EVENT)
+ {
+ switch(((KviThreadEvent *)e)->id())
+ {
+ case KVI_DCC_THREAD_EVENT_ERROR:
+ {
+ int * err = ((KviThreadDataEvent<int> *)e)->getData();
+ QString ssss = KviError::getDescription(*err);
+ output(KVI_OUT_DCCERROR,__tr2qs_ctx("ERROR: %Q","dcc"),&(ssss));
+ delete err;
+ m_pUpdateTimer->stop();
+ updateInfo();
+ m_pTalkButton->setEnabled(false);
+ m_pRecordingLabel->setEnabled(false);
+ m_pPlayingLabel->setEnabled(false);
+ return true;
+ }
+ break;
+ case KVI_DCC_THREAD_EVENT_MESSAGE:
+ {
+ KviStr * str = ((KviThreadDataEvent<KviStr> *)e)->getData();
+ outputNoFmt(KVI_OUT_DCCMSG,__tr_no_xgettext_ctx(str->ptr(),"dcc"));
+ delete str;
+ return true;
+ }
+ break;
+ case KVI_DCC_THREAD_EVENT_ACTION:
+ {
+ int * act = ((KviThreadDataEvent<int> *)e)->getData();
+ switch(*act)
+ {
+ case KVI_DCC_VOICE_THREAD_ACTION_START_RECORDING:
+ m_pRecordingLabel->setEnabled(true);
+ break;
+ case KVI_DCC_VOICE_THREAD_ACTION_STOP_RECORDING:
+ m_pRecordingLabel->setEnabled(false);
+ break;
+ case KVI_DCC_VOICE_THREAD_ACTION_START_PLAYING:
+ m_pPlayingLabel->setEnabled(true);
+ break;
+ case KVI_DCC_VOICE_THREAD_ACTION_STOP_PLAYING:
+ m_pPlayingLabel->setEnabled(false);
+ break;
+ }
+ delete act;
+ return true;
+ }
+ break;
+ default:
+ debug("Invalid event type %d received",((KviThreadEvent *)e)->id());
+ break;
+ }
+
+ }
+
+ return KviWindow::event(e);
+}
+
+void KviDccVoice::updateInfo()
+{
+ if(m_pSlaveThread)
+ {
+ m_pSlaveThread->m_pInfoMutex->lock();
+ int iOSize = m_pSlaveThread->m_iOutputBufferSize;
+ int iISize = m_pSlaveThread->m_iInputBufferSize;
+ m_pSlaveThread->m_pInfoMutex->unlock();
+ KviStr tmp(KviStr::Format,__tr_ctx("Input buffer: %d bytes","dcc"),iISize);
+ m_pInputLabel->setText(tmp.ptr());
+ tmp.sprintf(__tr_ctx("Output buffer: %d bytes","dcc"),iOSize);
+ m_pOutputLabel->setText(tmp.ptr());
+ }
+}
+
+void KviDccVoice::resizeEvent(QResizeEvent *e)
+{
+ int hght2 = m_pHBox->sizeHint().height();
+ m_pHBox->setGeometry(0,0,width(),hght2);
+ m_pSplitter->setGeometry(0,hght2,width(),height() - hght2);
+}
+
+QSize KviDccVoice::sizeHint() const
+{
+ int w = m_pIrcView->sizeHint().width();
+ int w2 = m_pHBox->sizeHint().width();
+ QSize ret(w > w2 ? w : w2, m_pIrcView->sizeHint().height() + m_pHBox->sizeHint().height());
+ return ret;
+}
+
+void KviDccVoice::handleMarshalError(int err)
+{
+ QString ssss = KviError::getDescription(err);
+ output(KVI_OUT_DCCERROR,__tr2qs_ctx("DCC Failed: %Q","dcc"),&ssss);
+ m_pTalkButton->setEnabled(false);
+ m_pTalkButton->setOn(false);
+ m_pRecordingLabel->setEnabled(false);
+ m_pPlayingLabel->setEnabled(false);
+}
+
+void KviDccVoice::connected()
+{
+ output(KVI_OUT_DCCMSG,__tr2qs_ctx("Connected to %Q:%Q","dcc"),
+ &(m_pMarshal->remoteIp()),&(m_pMarshal->remotePort()));
+ output(KVI_OUT_DCCMSG,__tr2qs_ctx("Local end is %Q:%Q","dcc"),
+ &(m_pMarshal->localIp()),&(m_pMarshal->localPort()));
+ if(!(m_pDescriptor->bActive))
+ {
+ m_pDescriptor->szIp = m_pMarshal->remoteIp();
+ m_pDescriptor->szPort = m_pMarshal->remotePort();
+ m_pDescriptor->szHost = m_pMarshal->remoteIp();
+ }
+ updateCaption();
+
+ connect(m_pUpdateTimer,SIGNAL(timeout()),this,SLOT(updateInfo()));
+ m_pUpdateTimer->start(1000);
+
+ KviDccVoiceThreadOptions * opt = new KviDccVoiceThreadOptions;
+
+
+ opt->pCodec = kvi_dcc_voice_get_codec(m_pDescriptor->szCodec.ptr());
+
+ output(KVI_OUT_DCCMSG,__tr2qs_ctx("Actual codec used is '%s'","dcc"),opt->pCodec->name());
+
+ opt->bForceHalfDuplex = KVI_OPTION_BOOL(KviOption_boolDccVoiceForceHalfDuplex);
+// opt->bForceDummyReadTrigger = false;
+ opt->iPreBufferSize = KVI_OPTION_UINT(KviOption_uintDccVoicePreBufferSize);
+ opt->szSoundDevice = KVI_OPTION_STRING(KviOption_stringDccVoiceSoundDevice).utf8().data();
+ opt->iSampleRate = m_pDescriptor->iSampleRate;
+
+ m_pSlaveThread = new KviDccVoiceThread(this,m_pMarshal->releaseSocket(),opt);
+ connect(m_pUpdateTimer,SIGNAL(timeout()),this,SLOT(updateInfo()));
+ m_pSlaveThread->start();
+
+ m_pTalkButton->setEnabled(true);
+}
+
+void KviDccVoice::stopTalking()
+{
+ KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
+ e->setData(new int(0));
+ m_pSlaveThread->enqueueEvent(e);
+}
+
+void KviDccVoice::startTalking()
+{
+ KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
+ e->setData(new int(1));
+ m_pSlaveThread->enqueueEvent(e);
+}
+
+void KviDccVoice::startOrStopTalking(bool bStart)
+{
+ if(bStart)startTalking();
+ else stopTalking();
+}
+
+int KviDccVoice::getMixerVolume(void) const
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ int fd;
+ int ret;
+ int left; //, right;
+ int req;
+
+ if((fd = ::open(KVI_OPTION_STRING(KviOption_stringDccVoiceMixerDevice).utf8().data(), O_RDONLY)) == -1)
+ {
+ return 0;
+ }
+
+ req = KVI_OPTION_BOOL(KviOption_boolDccVoiceVolumeSliderControlsPCM) ? SOUND_MIXER_READ_PCM : SOUND_MIXER_READ_VOLUME;
+
+ if(::ioctl(fd,req,&ret))
+ {
+ ::close(fd);
+ return 0;
+ }
+
+ left = (ret & 0x00ff);
+// right = (ret & 0xff00) >> 8;
+
+ ::close(fd);
+
+ return -left;
+#else
+ return 0;
+#endif
+}
+
+void KviDccVoice::setMixerVolume(int vol)
+{
+#ifndef COMPILE_DISABLE_DCC_VOICE
+ int fd;
+ int val;
+ int req;
+
+ if((fd = ::open(KVI_OPTION_STRING(KviOption_stringDccVoiceMixerDevice).utf8().data(), O_WRONLY)) == -1)
+ return;
+
+ req = KVI_OPTION_BOOL(KviOption_boolDccVoiceVolumeSliderControlsPCM) ? SOUND_MIXER_WRITE_PCM : SOUND_MIXER_WRITE_VOLUME;
+
+ val = (-vol << 8) | -vol;
+ ::ioctl(fd, req, &val);
+ ::close(fd);
+
+ QString s;
+ s.sprintf(__tr_ctx("Volume: %i","dcc"), -vol);
+ QToolTip::add(m_pVolumeSlider, s);
+#endif
+}
+
+
+/* The code below doesn't work. Guess I have to catch some other widget's focusInEvent. Which one ? */
+/* The point is to move the volume slider to correct position if for example user switched to
+ * another KVirc window, fired up xmms, changed the volume, and returned to our dcc voice window */
+void KviDccVoice::focusInEvent(QFocusEvent *e)
+{
+// debug("focusInEvent()");
+ m_pVolumeSlider->setValue(getMixerVolume());
+ setMixerVolume(m_pVolumeSlider->value());
+
+ KviWindow::focusInEvent(e);
+}
+
+#include "m_voice.moc"