//============================================================================= // // 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