From adfa9fbec1f3fe1a4827e84d3ede0b141fee4d79 Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Thu, 12 Nov 2009 15:12:14 +0000 Subject: first shy implementation of dcc video; highly instable and bandwitdth-sucker, bad coded, puppy-killer, you've been warned. git-svn-id: https://svn.kvirc.de/svn/trunk/kvirc@3597 17fca916-40b9-46aa-a4ea-0a15b648b75c --- src/modules/dcc/CMakeLists.txt | 2 + src/modules/dcc/broker.cpp | 70 +++++ src/modules/dcc/broker.h | 4 + src/modules/dcc/codec.cpp | 67 ++++ src/modules/dcc/codec.h | 26 ++ src/modules/dcc/libkvidcc.cpp | 65 ++++ src/modules/dcc/requests.cpp | 79 ++++- src/modules/dcc/video.cpp | 686 +++++++++++++++++++++++++++++++++++++++++ src/modules/dcc/video.h | 155 ++++++++++ 9 files changed, 1152 insertions(+), 2 deletions(-) create mode 100644 src/modules/dcc/video.cpp create mode 100644 src/modules/dcc/video.h (limited to 'src/modules/dcc') diff --git a/src/modules/dcc/CMakeLists.txt b/src/modules/dcc/CMakeLists.txt index 71da80ff6..c380cb998 100644 --- a/src/modules/dcc/CMakeLists.txt +++ b/src/modules/dcc/CMakeLists.txt @@ -6,6 +6,7 @@ SET(kvidcc_MOC_HDRS dialogs.h marshal.h send.h + video.h voice.h window.h ) @@ -26,6 +27,7 @@ SET(kvidcc_SRCS send.cpp thread.cpp utils.cpp + video.cpp voice.cpp window.cpp ) diff --git a/src/modules/dcc/broker.cpp b/src/modules/dcc/broker.cpp index 4d0453aa8..d0c64b145 100644 --- a/src/modules/dcc/broker.cpp +++ b/src/modules/dcc/broker.cpp @@ -27,6 +27,7 @@ #include "chat.h" #include "send.h" #include "voice.h" +#include "video.h" #ifdef COMPILE_DCC_CANVAS #include "canvas.h" @@ -397,6 +398,75 @@ void KviDccBroker::passiveVoiceExecute(KviDccDescriptor * dcc) } +/////////////////////////////////////////////////////////////////////////////// +// ACTIVE VIDEO +/////////////////////////////////////////////////////////////////////////////// + +void KviDccBroker::activeVideoManage(KviDccDescriptor * dcc) +{ + if(!dcc->bAutoAccept) + { + // need confirmation + QString tmp = __tr2qs_ctx( + "%1 [%2@%3] requests a
" \ + "Direct Client Connection in VIDEO mode.
" \ + "The connection target will be host %4 on port %5
" \ + ,"dcc" \ + ).arg(dcc->szNick).arg(dcc->szUser).arg(dcc->szHost).arg(dcc->szIp).arg(dcc->szPort); + + KviDccAcceptBox * box = new KviDccAcceptBox(this,dcc,tmp,__tr2qs_ctx("DCC VIDEO request","dcc")); + m_pBoxList->append(box); + connect(box,SIGNAL(accepted(KviDccBox *,KviDccDescriptor *)), + this,SLOT(activeVideoExecute(KviDccBox *,KviDccDescriptor *))); + connect(box,SIGNAL(rejected(KviDccBox *,KviDccDescriptor *)), + this,SLOT(cancelDcc(KviDccBox *,KviDccDescriptor *))); + box->show(); + } else { + // auto accept + activeVideoExecute(0,dcc); + } +} + +void KviDccBroker::activeVideoExecute(KviDccBox *box,KviDccDescriptor * dcc) +{ + if(box)box->forgetDescriptor(); + + if(!g_pApp->windowExists(dcc->console())) + { + // rebind to the first available console.... + dcc->setConsole(g_pApp->activeConsole()); + } + + KviStr tmp(KviStr::Format,"dcc: video %s@%s:%s",dcc->szNick.toUtf8().data(),dcc->szIp.toUtf8().data(),dcc->szPort.toUtf8().data()); + KviDccVideo * v = new KviDccVideo(dcc->console()->frame(),dcc,tmp.ptr()); + + bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : \ + (KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccVideo) || \ + (dcc->bAutoAccept && KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccVideoWhenAutoAccepted))); + + dcc->console()->frame()->addWindow(v,!bMinimized); + if(bMinimized)v->minimize(); + + m_pDccWindowList->append(v); +} + + +/////////////////////////////////////////////////////////////////////////////// +// PASSIVE VOICE +/////////////////////////////////////////////////////////////////////////////// + +void KviDccBroker::passiveVideoExecute(KviDccDescriptor * dcc) +{ + KviStr tmp(KviStr::Format,"dcc: video %s@%s:%s",dcc->szNick.toUtf8().data(),dcc->szIp.toUtf8().data(),dcc->szPort.toUtf8().data()); + KviDccVideo * v = new KviDccVideo(dcc->console()->frame(),dcc,tmp.ptr()); + //#warning "Create minimized dcc video ?... or maybe it's too much ? :)" + bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChat); + dcc->console()->frame()->addWindow(v,!bMinimized); + if(bMinimized)v->minimize(); + m_pDccWindowList->append(v); +} + + /////////////////////////////////////////////////////////////////////////////// // ACTIVE CANVAS /////////////////////////////////////////////////////////////////////////////// diff --git a/src/modules/dcc/broker.h b/src/modules/dcc/broker.h index 5be531571..338ebb853 100644 --- a/src/modules/dcc/broker.h +++ b/src/modules/dcc/broker.h @@ -86,6 +86,9 @@ public: void activeVoiceManage(KviDccDescriptor * dcc); void passiveVoiceExecute(KviDccDescriptor * dcc); + void activeVideoManage(KviDccDescriptor * dcc); + void passiveVideoExecute(KviDccDescriptor * dcc); + void recvFileManage(KviDccDescriptor * dcc); void sendFileManage(KviDccDescriptor * dcc); @@ -101,6 +104,7 @@ public slots: void activeCanvasExecute(KviDccBox * box,KviDccDescriptor * dcc); void activeVoiceExecute(KviDccBox * box,KviDccDescriptor * dcc); + void activeVideoExecute(KviDccBox * box,KviDccDescriptor * dcc); void sendFileExecute(KviDccBox * box,KviDccDescriptor * dcc); void recvFileExecute(KviDccBox * box,KviDccDescriptor * dcc); diff --git a/src/modules/dcc/codec.cpp b/src/modules/dcc/codec.cpp index d08a1c87f..1ea1172c9 100644 --- a/src/modules/dcc/codec.cpp +++ b/src/modules/dcc/codec.cpp @@ -88,3 +88,70 @@ int KviDccVoiceNullCodec::decodedFrameSize() { return 1024; } + +//-------------------------- + +KviDccVideoCodec::KviDccVideoCodec() +{ +} + +KviDccVideoCodec::~KviDccVideoCodec() +{ +} + +void KviDccVideoCodec::encode(KviDataBuffer *,KviDataBuffer *) +{ +} + +void KviDccVideoCodec::decode(KviDataBuffer *,KviDataBuffer *) +{ +} + +int KviDccVideoCodec::encodedFrameSize() +{ + return 0; +} + +int KviDccVideoCodec::decodedFrameSize() +{ + return 0; +} + +const char * KviDccVideoCodec::name() +{ + return m_szName.ptr(); +} + +KviDccVideoNullCodec::KviDccVideoNullCodec() +: KviDccVideoCodec() +{ + m_szName = "null (no compression)"; +} + +KviDccVideoNullCodec::~KviDccVideoNullCodec() +{ +} + +void KviDccVideoNullCodec::encode(KviDataBuffer * signal,KviDataBuffer * stream) +{ + if(signal->size() < 1)return; + stream->append(signal->data(),signal->size()); + signal->resize(0); +} + +void KviDccVideoNullCodec::decode(KviDataBuffer * stream,KviDataBuffer * signal) +{ + if(stream->size() < 1)return; + signal->append(stream->data(),stream->size()); + stream->resize(0); +} + +int KviDccVideoNullCodec::encodedFrameSize() +{ + return 1024; +} + +int KviDccVideoNullCodec::decodedFrameSize() +{ + return 1024; +} diff --git a/src/modules/dcc/codec.h b/src/modules/dcc/codec.h index 5cd8ee85b..7f0ed4f58 100644 --- a/src/modules/dcc/codec.h +++ b/src/modules/dcc/codec.h @@ -54,4 +54,30 @@ public: virtual int decodedFrameSize(); }; +class KviDccVideoCodec +{ +public: + KviDccVideoCodec(); + virtual ~KviDccVideoCodec(); +protected: + KviStr m_szName; +public: + const char * name(); + virtual void encode(KviDataBuffer * signal,KviDataBuffer * stream); + virtual void decode(KviDataBuffer * stream,KviDataBuffer * signal); + virtual int encodedFrameSize(); + virtual int decodedFrameSize(); +}; + +class KviDccVideoNullCodec : public KviDccVideoCodec +{ +public: + KviDccVideoNullCodec(); + virtual ~KviDccVideoNullCodec(); +public: + virtual void encode(KviDataBuffer * signal,KviDataBuffer * stream); + virtual void decode(KviDataBuffer * stream,KviDataBuffer * signal); + virtual int encodedFrameSize(); + virtual int decodedFrameSize(); +}; #endif //_CODEC_H_ diff --git a/src/modules/dcc/libkvidcc.cpp b/src/modules/dcc/libkvidcc.cpp index 620a6f3e6..3231a68df 100644 --- a/src/modules/dcc/libkvidcc.cpp +++ b/src/modules/dcc/libkvidcc.cpp @@ -25,6 +25,7 @@ #include "gsmcodec.h" #include "broker.h" #include "voice.h" +#include "video.h" #include "utils.h" #include "send.h" #include "window.h" @@ -1181,6 +1182,69 @@ static bool dcc_kvs_cmd_voice(KviKvsModuleCommandCall * c) } +static bool dcc_kvs_cmd_video(KviKvsModuleCommandCall * c) +{ + QString szTarget; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("target",KVS_PT_NONEMPTYSTRING,0,szTarget) + KVSM_PARAMETERS_END(c) + +#ifdef COMPILE_DISABLE_DCC_VIDEO + c->warning(__tr2qs_ctx("DCC VIDEO support not enabled at compilation time","dcc")); + return true; +#endif + + KviDccDescriptor * d = new KviDccDescriptor(c->window()->console()); + + d->szNick = szTarget; // we always specify the nickname + d->szUser = __tr2qs_ctx("unknown","dcc"); // username is always unknown + d->szHost = d->szUser; // host is always unknown + + if(!dcc_kvs_parse_default_parameters(d,c))return false; + + d->szCodec = "null"; + + if(KviKvsVariant * pCodec = c->switches()->find('g',"codec")) + { + QString szCodec; + pCodec->asString(szCodec); + + if(!kvi_dcc_video_is_valid_codec(szCodec.toUtf8().data())) + { + c->warning(__tr2qs_ctx("Invalid codec specified, defaulting to 'null'","dcc")); + d->szCodec = "null"; + } + } + + dcc_module_set_dcc_type(d,"VIDEO"); + if(c->switches()->find('c',"connect")) + { + if(!(c->switches()->find('i',"ip") && c->switches()->find('p',"port"))) + { + delete d; + c->error(__tr2qs_ctx("-c requires -i and -p","dcc")); + return false; + } + d->szIp = d->szListenIp; + d->szPort = d->szListenPort; + d->szListenIp = ""; // useless + d->szListenPort = ""; // useless + d->bActive = true; + + d->triggerCreationEvent(); + g_pDccBroker->activeVideoExecute(0,d); + } else { + d->szIp = __tr2qs_ctx("unknown","dcc"); + d->szPort = d->szIp; + d->bActive = false; + d->bSendRequest = !(c->switches()->find('n',"no-ctcp")); + + d->triggerCreationEvent(); + g_pDccBroker->passiveVideoExecute(d); + } + + return true; +} /* @@ -2698,6 +2762,7 @@ static bool dcc_module_init(KviModule * m) KVSM_REGISTER_SIMPLE_COMMAND(m,"send",dcc_kvs_cmd_send); KVSM_REGISTER_SIMPLE_COMMAND(m,"chat",dcc_kvs_cmd_chat); + KVSM_REGISTER_SIMPLE_COMMAND(m,"video",dcc_kvs_cmd_video); KVSM_REGISTER_SIMPLE_COMMAND(m,"voice",dcc_kvs_cmd_voice); KVSM_REGISTER_SIMPLE_COMMAND(m,"recv",dcc_kvs_cmd_recv); KVSM_REGISTER_SIMPLE_COMMAND(m,"rsend",dcc_kvs_cmd_rsend); diff --git a/src/modules/dcc/requests.cpp b/src/modules/dcc/requests.cpp index 5078dea91..3b27014c5 100644 --- a/src/modules/dcc/requests.cpp +++ b/src/modules/dcc/requests.cpp @@ -26,6 +26,7 @@ #include "gsmcodec.h" #include "broker.h" +#include "video.h" #include "voice.h" #include "utils.h" #include "send.h" @@ -1040,6 +1041,79 @@ static void dccModuleParseDccVoice(KviDccRequest *dcc) } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// VIDEO +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static void dccModuleParseDccVideo(KviDccRequest *dcc) +{ + // + // We have received a DCC VIDEO request in the following form: + // + // DCC VIDEO codec + // + // This means that we're requested to setup an ACTIVE voice connection + // ... Easy task :) + // + + if(!dcc_module_check_limits(dcc))return; + + if(!dcc_module_normalize_target_data(dcc,dcc->szParam2,dcc->szParam3))return; + +#ifdef COMPILE_DISABLE_DCC_VIDEO + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCERROR, + __tr2qs_ctx("The above request cannot be accepted: DCC VIDEO support not enabled at compilation time ","dcc")); + return; + } +#endif + // Actually unused parameter + if(!kvi_dcc_video_is_valid_codec(dcc->szParam1.ptr())) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCERROR, + __tr2qs_ctx("The above request cannot be accepted: Unsupported codec '%s'","dcc"),dcc->szParam1.ptr()); + return; + } + } + +// bool bOk; + +// int iSampleRate = dcc->szParam4.toInt(&bOk); +// if(!bOk) +// { +// if(!dcc->ctcpMsg->msg->haltOutput()) +// { +// dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, +// __tr2qs_ctx("The above request appears to be broken: Invalid sample-rate '%s', defaulting to 8000","dcc"),dcc->szParam4.ptr()); +// } +// iSampleRate = 8000; +// } + + + KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole); + d->szNick = dcc->ctcpMsg->pSource->nick(); + d->szUser = dcc->ctcpMsg->pSource->user(); + d->szHost = dcc->ctcpMsg->pSource->host(); + dcc_fill_local_nick_user_host(d,dcc); + + + d->szIp = dcc->szParam2.ptr(); + d->szPort = dcc->szParam3.ptr(); + d->bActive = true; // we have to connect + d->bIsTdcc = false; + d->bNoAcks = false; // this has no meaning in voice + d->szCodec = dcc->szParam1; +// d->iSampleRate = iSampleRate; + d->bOverrideMinimize = false; + d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccVideo); + dcc_module_set_dcc_type(d,"VIDEO"); + d->triggerCreationEvent(); + g_pDccBroker->activeVideoManage(d); +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CANVAS //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1101,7 +1175,7 @@ typedef struct _dccParseProcEntry dccParseProc proc; } dccParseProcEntry; -#define KVI_NUM_KNOWN_DCC_TYPES 27 +#define KVI_NUM_KNOWN_DCC_TYPES 28 static dccParseProcEntry dccParseProcTable[KVI_NUM_KNOWN_DCC_TYPES]= { @@ -1131,7 +1205,8 @@ static dccParseProcEntry dccParseProcTable[KVI_NUM_KNOWN_DCC_TYPES]= { "STRSEND", dccModuleParseDccRSend }, { "TSRSEND", dccModuleParseDccRSend }, { "CANVAS" , dccModuleParseDccCanvas }, - { "VOICE" , dccModuleParseDccVoice } + { "VOICE" , dccModuleParseDccVoice }, + { "VIDEO" , dccModuleParseDccVideo } }; diff --git a/src/modules/dcc/video.cpp b/src/modules/dcc/video.cpp new file mode 100644 index 000000000..525b356c7 --- /dev/null +++ b/src/modules/dcc/video.cpp @@ -0,0 +1,686 @@ +//============================================================================= +// +// File : video.cpp +// Creation date : Tue Nov 10 18:08:09 2009 GMT by Fabio Bas +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2009 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 "video.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 "kvi_tal_vbox.h" + +#include +#include +#include + +extern KviDccBroker * g_pDccBroker; + +//Check for the *SS Api....we don't want to fail here... + +bool kvi_dcc_video_is_valid_codec(const char * codecName) +{ +// if(kvi_strEqualCI("jpeg",codecName))return true; + if(kvi_strEqualCI("null",codecName))return true; + return false; +} + +static KviDccVideoCodec * kvi_dcc_video_get_codec(const char *) +{ +// if(kvi_strEqualCI("jpeg",codecName))return new KviDccVoiceAdpcmCodec(); + //if(kvi_strEqualCI("null",codecName)) + return new KviDccVideoNullCodec(); +// return new KviDccVoiceAdpcmCodec(); +} + + +KviDccVideoThread::KviDccVideoThread(KviWindow * wnd,kvi_socket_t fd,KviDccVideoThreadOptions * opt) +: KviDccThread(wnd,fd) +{ +#ifndef COMPILE_DISABLE_DCC_VIDEO + m_pOpt = opt; + m_bPlaying = false; + m_bRecording = false; + m_pInfoMutex = new KviMutex(); + m_bRecordingRequestPending = false; +#endif + startRecording(); + startPlaying(); +} + +KviDccVideoThread::~KviDccVideoThread() +{ + stopRecording(); +#ifndef COMPILE_DISABLE_DCC_VIDEO + delete m_pOpt->pCodec; + delete m_pOpt; + delete m_pInfoMutex; +#endif +} + +bool KviDccVideoThread::readWriteStep() +{ +#ifndef COMPILE_DISABLE_DCC_VIDEO + // 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 KviDccVideoThread::videoStep() +{ +#ifndef COMPILE_DISABLE_DCC_VIDEO + // Are we playing ? + if(m_bPlaying) + { + if(m_inSignalBuffer.size() > 0) + { + unsigned const char jpg_magic_init[4] = { 0xFF, 0xD8, 0xFF, 0xE0}; //SOI + APP0 + unsigned const char jpg_magic_end[2] = { 0xFF, 0xD9}; //EOI + int start = m_inSignalBuffer.find(jpg_magic_init, 4); + int end = m_inSignalBuffer.find(jpg_magic_end, 2); + qDebug("start %d end %d", start, end); + if(start != -1 && end != -1) + { + //remove junk before jpeg start + m_inSignalBuffer.remove(start); + int len = end - start + 1; + QImage img; + img.loadFromData(m_inSignalBuffer.data(), len, "JPEG"); + if(!img.isNull()) + { + m_inImage = img; + qDebug("img in"); + } + m_inSignalBuffer.remove(len); + } + } + } + + // Are we recording ? + if(m_bRecording) + { + //grab the image, compress using the codec + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + //0 to obtain small compressed files, + //100 for large uncompressed files, + //and -1 (the default) to use the default settings. + ((KviDccVideo*)KviDccThread::parent())->m_Image.save(&buffer, "JPEG", 20); + if(ba.size() > 0) + { + m_outSignalBuffer.append((const unsigned char*) ba.data(), ba.size()); + m_pOpt->pCodec->encode(&m_outSignalBuffer,&m_outFrameBuffer); + } + } + +#endif // !COMPILE_DISABLE_DCC_VIDEO + return true; +} + +void KviDccVideoThread::startRecording() +{ +#ifndef COMPILE_DISABLE_DCC_VIDEO + //debug("Start recording"); + if(m_bRecording)return; // already started + + //if(openSoundcardForReading()) + if(1) + { +// debug("Posting event"); + KviThreadDataEvent * e = new KviThreadDataEvent(KVI_DCC_THREAD_EVENT_ACTION); + e->setData(new int(KVI_DCC_VIDEO_THREAD_ACTION_START_RECORDING)); + postEvent(KviDccThread::parent(),e); + + m_bRecording = true; + m_bRecordingRequestPending = false; + } else { + m_bRecordingRequestPending = true; + } +#endif +} + +void KviDccVideoThread::stopRecording() +{ +#ifndef COMPILE_DISABLE_DCC_VIDEO + //debug("Stop recording"); + m_bRecordingRequestPending = false; + if(!m_bRecording)return; // already stopped + + KviThreadDataEvent * e = new KviThreadDataEvent(KVI_DCC_THREAD_EVENT_ACTION); + e->setData(new int(KVI_DCC_VIDEO_THREAD_ACTION_STOP_RECORDING)); + postEvent(KviDccThread::parent(),e); + + m_bRecording = false; +// if(!m_bPlaying)closeSoundcard(); +#endif +} + +void KviDccVideoThread::startPlaying() +{ +#ifndef COMPILE_DISABLE_DCC_VIDEO + //debug("Start playing"); + if(m_bPlaying)return; + + KviThreadDataEvent * e = new KviThreadDataEvent(KVI_DCC_THREAD_EVENT_ACTION); + e->setData(new int(KVI_DCC_VIDEO_THREAD_ACTION_START_PLAYING)); + postEvent(KviDccThread::parent(),e); + m_bPlaying = true; + +#endif +} + +void KviDccVideoThread::stopPlaying() +{ +#ifndef COMPILE_DISABLE_DCC_VIDEO + //debug("Stop playing"); + if(!m_bPlaying)return; + + KviThreadDataEvent * e = new KviThreadDataEvent(KVI_DCC_THREAD_EVENT_ACTION); + e->setData(new int(KVI_DCC_VIDEO_THREAD_ACTION_STOP_PLAYING)); + postEvent(KviDccThread::parent(),e); + + m_bPlaying = false; +#endif +} + +void KviDccVideoThread::run() +{ +#ifndef COMPILE_DISABLE_DCC_VIDEO + 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 *)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(!videoStep())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(); + + // Start recording if the request was not fulfilled yet + if(m_bRecordingRequestPending)startRecording(); + } + + +exit_dcc: + +#endif //! COMPILE_DISABLE_DCC_VIDEO + kvi_socket_close(m_fd); + m_fd = KVI_INVALID_SOCKET; +} + +KviDccVideo::KviDccVideo(KviFrame *pFrm,KviDccDescriptor * dcc,const char * name) +: KviDccWindow(KVI_WINDOW_TYPE_DCCVOICE,pFrm,name,dcc) +{ + m_pDescriptor = dcc; + m_pSlaveThread = 0; + m_pszTarget = 0; + + m_pLayout = new QGridLayout(); + + //remote video + m_pInVideoLabel = new QLabel(); + m_pInVideoLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_pInVideoLabel->setMinimumSize(320, 240); + m_pInVideoLabel->setFrameShape(QFrame::Box); + m_pInVideoLabel->setScaledContents(true); + m_pInVideoLabel->setAlignment(Qt::AlignCenter); + m_pLayout->addWidget(m_pInVideoLabel, 0, 0, 10, 1); + + //local video + m_pOutVideoLabel = new QLabel(); + m_pOutVideoLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_pOutVideoLabel->setMinimumSize(320, 240); + m_pOutVideoLabel->setFrameShape(QFrame::Box); + m_pOutVideoLabel->setScaledContents(true); + m_pOutVideoLabel->setAlignment(Qt::AlignCenter); + m_pLayout->addWidget(m_pOutVideoLabel, 11, 0, 10, 1); + + //local video input + m_pVideoDevicePool = Kopete::AV::VideoDevicePool::self(); + m_pVideoDevicePool->open(); + m_pVideoDevicePool->setSize(320, 240); + + //local video input: config + m_pLabel[0] = new QLabel(); + m_pLabel[0]->setText(__tr2qs_ctx("Video device:","dcc")); + m_pLayout->addWidget(m_pLabel[0], 11, 1, 1, 1); + + m_pCDevices = new QComboBox(); + m_pVideoDevicePool->fillDeviceQComboBox(m_pCDevices); + m_pLayout->addWidget(m_pCDevices, 12, 1, 1, 1); + + m_pLabel[1] = new QLabel(); + m_pLabel[1]->setText(__tr2qs_ctx("Input:","dcc")); + m_pLayout->addWidget(m_pLabel[1], 13, 1, 1, 1); + + m_pCInputs = new QComboBox(); + m_pVideoDevicePool->fillInputQComboBox(m_pCInputs); + m_pLayout->addWidget(m_pCInputs, 14, 1, 1, 1); + + m_pLabel[2] = new QLabel(); + m_pLabel[2]->setText(__tr2qs_ctx("Standard:","dcc")); + m_pLayout->addWidget(m_pLabel[2], 15, 1, 1, 1); + + m_pCStandards = new QComboBox(); + m_pVideoDevicePool->fillStandardQComboBox(m_pCStandards); + m_pLayout->addWidget(m_pCStandards, 16, 1, 1, 1); + + setLayout(m_pLayout); + + m_pVideoDevicePool->startCapturing(); + + connect(m_pVideoDevicePool, SIGNAL(deviceRegistered(const QString &) ), SLOT(deviceRegistered(const QString &)) ); + connect(m_pVideoDevicePool, SIGNAL(deviceUnregistered(const QString &) ), SLOT(deviceUnregistered(const QString &)) ); + + connect(&m_Timer, SIGNAL(timeout()), this, SLOT(slotUpdateImage()) ); + if ( m_pVideoDevicePool->hasDevices() ) + { + m_Timer.start(40); //25fps + } + + 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())); + + startConnection(); +} + +KviDccVideo::~KviDccVideo() +{ + m_pVideoDevicePool->close(); + + if(m_pInVideoLabel) + { + delete m_pInVideoLabel; + m_pInVideoLabel=0; + } + if(m_pOutVideoLabel) + { + delete m_pOutVideoLabel; + m_pOutVideoLabel=0; + } + if(m_pCDevices) + { + delete m_pCDevices; + m_pCDevices=0; + } + if(m_pCInputs) + { + delete m_pCInputs; + m_pCInputs=0; + } + if(m_pCStandards) + { + delete m_pCStandards; + m_pCStandards=0; + } + if(m_pLabel[0]) + { + delete m_pLabel[2]; + delete m_pLabel[1]; + delete m_pLabel[0]; + m_pLabel[2] = 0; + m_pLabel[1] = 0; + m_pLabel[0] = 0; + } + if(m_pLayout) + { + delete m_pLayout; + m_pLayout=0; + } + + g_pDccBroker->unregisterDccWindow(this); + + if(m_pSlaveThread) + { + m_pSlaveThread->terminate(); + delete m_pSlaveThread; + m_pSlaveThread = 0; + } + + KviThreadManager::killPendingEvents(this); + + if(m_pszTarget) + { + delete m_pszTarget; + m_pszTarget = 0; + } +} + +void KviDccVideo::startConnection() +{ + if(!(m_pDescriptor->bActive)) + { + // PASSIVE CONNECTION + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Attempting a passive DCC VIDEO 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 VIDEO connection","dcc")); + int ret = m_pMarshal->dccConnect(m_pDescriptor->szIp.toUtf8().data(),m_pDescriptor->szPort.toUtf8().data(),m_pDescriptor->bDoTimeout); + if(ret != KviError_success)handleMarshalError(ret); + } +} + +void KviDccVideo::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) + { + QString 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(KviNetUtils::stringIpToBinaryIp(ip,&a)) { + ip.setNum(htonl(a.s_addr)); + } + + m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC VIDEO %s %Q %s %d%c", + m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(), + 0x01,m_pDescriptor->szCodec.ptr(), + &ip,port.ptr(),m_pDescriptor->iSampleRate,0x01); + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Sent DCC VIDEO (%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 VIDEO request not sent: awaiting manual connections","dcc")); + } +} + +const QString & KviDccVideo::target() +{ + // This may change on the fly... + if(!m_pszTarget) + m_pszTarget = new QString(); + + m_pszTarget->sprintf("%s@%s:%s",m_pDescriptor->szNick.toUtf8().data(),m_pDescriptor->szIp.toUtf8().data(),m_pDescriptor->szPort.toUtf8().data()); + return *m_pszTarget; +} + +void KviDccVideo::getBaseLogFileName(QString &buffer) +{ + buffer.sprintf("dccvideo_%s_%s_%s",m_pDescriptor->szNick.toUtf8().data(),m_pDescriptor->szLocalFileName.toUtf8().data(),m_pDescriptor->szPort.toUtf8().data()); +} + +void KviDccVideo::fillCaptionBuffers() +{ + KviStr tmp(KviStr::Format,"DCC Video %s@%s:%s %s", + m_pDescriptor->szNick.toUtf8().data(),m_pDescriptor->szIp.toUtf8().data(),m_pDescriptor->szPort.toUtf8().data(), + m_pDescriptor->szLocalFileName.toUtf8().data()); + + m_szPlainTextCaption = tmp; +} + +QPixmap * KviDccVideo::myIconPtr() +{ + return g_pIconManager->getSmallIcon(KVI_SMALLICON_DCCVOICE); +} + +bool KviDccVideo::event(QEvent *e) +{ + if(e->type() == KVI_THREAD_EVENT) + { + switch(((KviThreadEvent *)e)->id()) + { + case KVI_DCC_THREAD_EVENT_ERROR: + { + int * err = ((KviThreadDataEvent *)e)->getData(); + QString ssss = KviError::getDescription(*err); + output(KVI_OUT_DCCERROR,__tr2qs_ctx("ERROR: %Q","dcc"),&(ssss)); + delete err; +/* m_pTalkButton->setEnabled(false); + m_pRecordingLabel->setEnabled(false); + m_pPlayingLabel->setEnabled(false);*/ + return true; + } + break; + case KVI_DCC_THREAD_EVENT_MESSAGE: + { + KviStr * str = ((KviThreadDataEvent *)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 *)e)->getData(); + switch(*act) + { + case KVI_DCC_VIDEO_THREAD_ACTION_START_RECORDING: +// m_pRecordingLabel->setEnabled(true); + break; + case KVI_DCC_VIDEO_THREAD_ACTION_STOP_RECORDING: +// m_pRecordingLabel->setEnabled(false); + break; + case KVI_DCC_VIDEO_THREAD_ACTION_START_PLAYING: +// m_pPlayingLabel->setEnabled(true); + break; + case KVI_DCC_VIDEO_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 KviDccVideo::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->setChecked(false); + m_pRecordingLabel->setEnabled(false); + m_pPlayingLabel->setEnabled(false);*/ +} + +void KviDccVideo::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(); + + KviDccVideoThreadOptions * opt = new KviDccVideoThreadOptions; + + opt->pCodec = kvi_dcc_video_get_codec(m_pDescriptor->szCodec.ptr()); + + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Actual codec used is '%s'","dcc"),opt->pCodec->name()); + + m_pSlaveThread = new KviDccVideoThread(this,m_pMarshal->releaseSocket(),opt); + m_pSlaveThread->start(); + +// m_pTalkButton->setEnabled(true); +} + +void KviDccVideo::stopTalking() +{ + KviThreadDataEvent * e = new KviThreadDataEvent(KVI_DCC_THREAD_EVENT_ACTION); + e->setData(new int(0)); + m_pSlaveThread->enqueueEvent(e); +} + +void KviDccVideo::startTalking() +{ + KviThreadDataEvent * e = new KviThreadDataEvent(KVI_DCC_THREAD_EVENT_ACTION); + e->setData(new int(1)); + m_pSlaveThread->enqueueEvent(e); +} + +void KviDccVideo::startOrStopTalking(bool bStart) +{ + if(bStart)startTalking(); + else stopTalking(); +} + +void KviDccVideo::slotUpdateImage() +{ + m_pVideoDevicePool->getFrame(); + m_pVideoDevicePool->getImage(&m_Image); + if(isVisible()) + { + m_pOutVideoLabel->setPixmap(QPixmap::fromImage(m_Image.mirrored(m_pVideoDevicePool->getImageAsMirror(),false))); + if(m_pSlaveThread) + m_pInVideoLabel->setPixmap(QPixmap::fromImage(m_pSlaveThread->m_inImage)); + } +} + +void KviDccVideo::deviceRegistered(const QString &) +{ + m_pVideoDevicePool->fillDeviceQComboBox(m_pCDevices); + m_pVideoDevicePool->fillInputQComboBox(m_pCInputs); + m_pVideoDevicePool->fillStandardQComboBox(m_pCStandards); + + // update the mVideoImageLabel to show the camera frames + m_pVideoDevicePool->open(); + m_pVideoDevicePool->setSize(320, 240); + m_pVideoDevicePool->startCapturing(); + +// setVideoInputParameters(); + + if ( m_pVideoDevicePool->hasDevices() ) + { + m_Timer.start(200); //5fps + } +} + + +void KviDccVideo::deviceUnregistered(const QString & ) +{ + m_pVideoDevicePool->fillDeviceQComboBox(m_pCDevices); + m_pVideoDevicePool->fillInputQComboBox(m_pCInputs); + m_pVideoDevicePool->fillStandardQComboBox(m_pCStandards); +} + +#ifndef COMPILE_USE_STANDALONE_MOC_SOURCES +#include "m_video.moc" +#endif //!COMPILE_USE_STANDALONE_MOC_SOURCES diff --git a/src/modules/dcc/video.h b/src/modules/dcc/video.h new file mode 100644 index 000000000..abae4db38 --- /dev/null +++ b/src/modules/dcc/video.h @@ -0,0 +1,155 @@ +#ifndef _VIDEO_H_ +#define _VIDEO_H_ +//============================================================================= +// +// File : video.h +// Creation date : Tue Nov 10 18:08:09 2009 GMT by Fabio Bas +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2009 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 "codec.h" +#include "descriptor.h" +#include "thread.h" +#include "window.h" + +#include "kvi_window.h" +#include "kvi_databuffer.h" +#include "kvi_sockettype.h" +#include "kvi_tal_hbox.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "avdevice/videodevicepool.h" + +class QSlider; +class KviDccMarshal; + +#ifndef _DCC_VIDEO_CPP_ + extern bool kvi_dcc_video_is_valid_codec(const char * codecName); +#endif + +#define KVI_DCC_VIDEO_THREAD_ACTION_START_RECORDING 0 +#define KVI_DCC_VIDEO_THREAD_ACTION_STOP_RECORDING 1 +#define KVI_DCC_VIDEO_THREAD_ACTION_START_PLAYING 2 +#define KVI_DCC_VIDEO_THREAD_ACTION_STOP_PLAYING 3 + +typedef struct _KviDccVideoThreadOptions +{ + QString szVideoDevice; + KviDccVideoCodec * pCodec; +} KviDccVideoThreadOptions; + +class KviDccVideoThread : public KviDccThread +{ + friend class KviDccVideo; +public: + KviDccVideoThread(KviWindow * wnd,kvi_socket_t fd,KviDccVideoThreadOptions * opt); + ~KviDccVideoThread(); +protected: + KviDccVideoThreadOptions * m_pOpt; + KviDataBuffer m_outFrameBuffer; + KviDataBuffer m_inFrameBuffer; + KviDataBuffer m_inSignalBuffer; + KviDataBuffer m_outSignalBuffer; + bool m_bPlaying; + bool m_bRecording; + bool m_bRecordingRequestPending; + KviMutex * m_pInfoMutex; + // stuff protected by the mutex: + int m_iInputBufferSize; + int m_iOutputBufferSize; + QImage m_inImage; +protected: + bool readWriteStep(); + bool videoStep(); + void startRecording(); + void stopRecording(); + void startPlaying(); + void stopPlaying(); + inline bool isPlaying() { return m_bPlaying; }; + virtual void run(); +}; + + +class KviDccVideo : public KviDccWindow +{ + Q_OBJECT + friend class KviDccVideoThread; +public: + KviDccVideo(KviFrame *pFrm,KviDccDescriptor * dcc,const char * name); + ~KviDccVideo(); +protected: + QLabel *m_pInVideoLabel; + QLabel *m_pOutVideoLabel; + Kopete::AV::VideoDevicePool *m_pVideoDevicePool; + QComboBox *m_pCDevices; + QComboBox *m_pCInputs; + QComboBox *m_pCStandards; + QGridLayout *m_pLayout; + QImage m_Image; + QTimer m_Timer; + QLabel * m_pLabel[2]; +/* + KviTalHBox * m_pHBox; + QSlider * m_pVolumeSlider; + QLabel * m_pInputLabel; + QLabel * m_pOutputLabel; + QLabel * m_pRecordingLabel; + QLabel * m_pPlayingLabel; + QToolButton * m_pTalkButton; +*/ + QString * m_pszTarget; + KviDccVideoThread * m_pSlaveThread; +protected: +// virtual void focusInEvent(QFocusEvent *); + virtual const QString & target(); + virtual void fillCaptionBuffers(); + virtual QPixmap * myIconPtr(); +/* + virtual void resizeEvent(QResizeEvent *e); + virtual QSize sizeHint() const; +*/ + virtual bool event(QEvent *e); + virtual void getBaseLogFileName(QString &buffer); + void startTalking(); + void stopTalking(); + void startConnection(); +// int getMixerVolume(void) const; +protected slots: + void handleMarshalError(int err); + void connected(); + void startOrStopTalking(bool bStart); +/* + void setMixerVolume(int); +*/ + void connectionInProgress(); + void slotUpdateImage(); + void deviceRegistered(const QString &); + void deviceUnregistered(const QString &); +}; + + +#endif //_VOICE_H_ -- cgit v1.3.1-10-gc9f91