From fc15899f0f2dd73f083fdecaa9181899a78ff0f7 Mon Sep 17 00:00:00 2001 From: csoler Date: Wed, 15 Feb 2012 21:17:18 +0000 Subject: [PATCH] started conversion of VOIP code (from Joss) into a plugin. Unfinished! git-svn-id: http://svn.code.sf.net/p/retroshare/code/trunk@4945 b45a01b8-16f6-495d-af2f-9b41ad6348cc --- plugins/VOIP/AudioInputConfig.cpp | 274 ++++ plugins/VOIP/AudioInputConfig.h | 82 + plugins/VOIP/AudioInputConfig.ui | 378 +++++ plugins/VOIP/AudioStats.cpp | 411 +++++ plugins/VOIP/AudioStats.h | 96 ++ plugins/VOIP/AudioStats.ui | 327 ++++ plugins/VOIP/AudioWizard.cpp | 299 ++++ plugins/VOIP/AudioWizard.h | 96 ++ plugins/VOIP/AudioWizard.ui | 329 ++++ plugins/VOIP/SpeexProcessor.cpp | 521 ++++++ plugins/VOIP/SpeexProcessor.h | 121 ++ plugins/VOIP/VOIP.pro | 16 + plugins/VOIP/VOIPPlugin.h | 36 + plugins/VOIP/VOIP_images.qrc | 10 + plugins/VOIP/audiodevicehelper.cpp | 76 + plugins/VOIP/audiodevicehelper.h | 19 + plugins/VOIP/images/deafened_self.svg | 573 +++++++ plugins/VOIP/images/muted_self.svg | 1999 +++++++++++++++++++++++ plugins/VOIP/images/self_undeafened.svg | 561 +++++++ plugins/VOIP/images/talking_off.svg | 1982 ++++++++++++++++++++++ plugins/VOIP/images/talking_on.svg | 1988 ++++++++++++++++++++++ plugins/VOIP/p3Voip.h | 76 + plugins/VOIP/rsvoipitems.h | 18 + 23 files changed, 10288 insertions(+) create mode 100644 plugins/VOIP/AudioInputConfig.cpp create mode 100644 plugins/VOIP/AudioInputConfig.h create mode 100644 plugins/VOIP/AudioInputConfig.ui create mode 100644 plugins/VOIP/AudioStats.cpp create mode 100644 plugins/VOIP/AudioStats.h create mode 100644 plugins/VOIP/AudioStats.ui create mode 100644 plugins/VOIP/AudioWizard.cpp create mode 100644 plugins/VOIP/AudioWizard.h create mode 100644 plugins/VOIP/AudioWizard.ui create mode 100644 plugins/VOIP/SpeexProcessor.cpp create mode 100644 plugins/VOIP/SpeexProcessor.h create mode 100644 plugins/VOIP/VOIP.pro create mode 100644 plugins/VOIP/VOIPPlugin.h create mode 100644 plugins/VOIP/VOIP_images.qrc create mode 100644 plugins/VOIP/audiodevicehelper.cpp create mode 100644 plugins/VOIP/audiodevicehelper.h create mode 100644 plugins/VOIP/images/deafened_self.svg create mode 100644 plugins/VOIP/images/muted_self.svg create mode 100644 plugins/VOIP/images/self_undeafened.svg create mode 100644 plugins/VOIP/images/talking_off.svg create mode 100644 plugins/VOIP/images/talking_on.svg create mode 100644 plugins/VOIP/p3Voip.h create mode 100644 plugins/VOIP/rsvoipitems.h diff --git a/plugins/VOIP/AudioInputConfig.cpp b/plugins/VOIP/AudioInputConfig.cpp new file mode 100644 index 000000000..6b0845031 --- /dev/null +++ b/plugins/VOIP/AudioInputConfig.cpp @@ -0,0 +1,274 @@ +/* Copyright (C) 2005-2010, Thorvald Natvig + Copyright (C) 2008, Andreas Messer + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +//#include "AudioInput.h" +//#include "AudioOutput.h" +#include "AudioStats.h" +#include "AudioInputConfig.h" +//#include "Global.h" +//#include "NetworkConfig.h" +#include "rsharesettings.h" +#include "util/audiodevicehelper.h" +#include "AudioWizard.h" + +#define iroundf(x) ( static_cast(x) ) + +/*void AudioInputDialog::hideEvent(QHideEvent *) { + qtTick->stop(); +} + +void AudioInputDialog::showEvent(QShowEvent *) { + qtTick->start(20); +}*/ + +/** Constructor */ +AudioInputConfig::AudioInputConfig(QWidget * parent, Qt::WFlags flags) + : ConfigPage(parent, flags) +{ + /* Invoke the Qt Designer generated object setup routine */ + ui.setupUi(this); + + loaded = false; +} + +AudioInputConfig::~AudioInputConfig() +{ + if (inputDevice) { + inputDevice->stop(); + } +} + +/** Loads the settings for this page */ +void AudioInputConfig::load() +{ + //connect( ui.allowIpDeterminationCB, SIGNAL( toggled( bool ) ), this, SLOT( toggleIpDetermination(bool) ) ); + //connect( ui.allowTunnelConnectionCB, SIGNAL( toggled( bool ) ), this, SLOT( toggleTunnelConnection(bool) ) ); + + qtTick = new QTimer(this); + connect( qtTick, SIGNAL( timeout ( ) ), this, SLOT( on_Tick_timeout() ) ); + qtTick->start(20); + /*if (AudioInputRegistrar::qmNew) { + QList keys = AudioInputRegistrar::qmNew->keys(); + foreach(QString key, keys) { + qcbSystem->addItem(key); + } + } + qcbSystem->setEnabled(qcbSystem->count() > 1);*/ + + ui.qcbTransmit->addItem(tr("Continuous"), RshareSettings::AudioTransmitContinous); + ui.qcbTransmit->addItem(tr("Voice Activity"), RshareSettings::AudioTransmitVAD); + ui.qcbTransmit->addItem(tr("Push To Talk"), RshareSettings::AudioTransmitPushToTalk); + + abSpeech = new AudioBar(); + abSpeech->qcBelow = Qt::red; + abSpeech->qcInside = Qt::yellow; + abSpeech->qcAbove = Qt::green; + //abSpeech->setGeometry(9,20,50,10); + ui.qwVadLayout_2->addWidget(abSpeech,0,0,1,0); + + + //on_qcbPushClick_clicked(g.s.bPushClick); + //ui.on_Tick_timeout(); + loadSettings(); + inputProcessor = NULL; + inputDevice = NULL; +} + + +void AudioInputConfig::loadSettings() { + /*QList keys; + + if (AudioInputRegistrar::qmNew) + keys=AudioInputRegistrar::qmNew->keys(); + else + keys.clear(); + i=keys.indexOf(AudioInputRegistrar::current); + if (i >= 0) + loadComboBox(qcbSystem, i); + + loadCheckBox(qcbExclusive, r.bExclusiveInput);*/ + + //qlePushClickPathOn->setText(r.qsPushClickOn); + //qlePushClickPathOff->setText(r.qsPushClickOff); + + /*loadComboBox(qcbTransmit, r.atTransmit); + loadSlider(qsTransmitHold, r.iVoiceHold); + loadSlider(qsTransmitMin, iroundf(r.fVADmin * 32767.0f + 0.5f)); + loadSlider(qsTransmitMax, iroundf(r.fVADmax * 32767.0f + 0.5f)); + loadSlider(qsFrames, (r.iFramesPerPacket == 1) ? 1 : (r.iFramesPerPacket/2 + 1)); + loadSlider(qsDoublePush, iroundf(static_cast(r.uiDoublePush) / 1000.f + 0.5f));*/ + ui.qcbTransmit->setCurrentIndex(Settings->getVoipATransmit()-1); + on_qcbTransmit_currentIndexChanged(Settings->getVoipATransmit()-1); + ui.qsTransmitHold->setValue(Settings->getVoipVoiceHold()); + on_qsTransmitHold_valueChanged(Settings->getVoipVoiceHold()); + ui.qsTransmitMin->setValue(Settings->getVoipfVADmin()); + ui.qsTransmitMax->setValue(Settings->getVoipfVADmax()); + ui.qcbEchoCancel->setChecked(Settings->getVoipEchoCancel()); + //ui.qsDoublePush->setValue(iroundf(static_cast(r.uiDoublePush) / 1000.f + 0.5f)); + + //loadCheckBox(qcbPushClick, r.bPushClick); + //loadSlider(qsQuality, r.iQuality); + if (Settings->getVoipiNoiseSuppress() != 0) + ui.qsNoise->setValue(-Settings->getVoipiNoiseSuppress()); + else + ui.qsNoise->setValue(14); + + on_qsNoise_valueChanged(-Settings->getVoipiNoiseSuppress()); + + ui.qsAmp->setValue(20000 - Settings->getVoipiMinLoudness()); + on_qsAmp_valueChanged(20000 - Settings->getVoipiMinLoudness()); + //loadSlider(qsIdle, r.iIdleTime); + + /*int echo = 0; + if (r.bEcho) + echo = r.bEchoMulti ? 2 : 1; + + loadComboBox(qcbEcho, echo);*/ + connect( ui.qsTransmitHold, SIGNAL( valueChanged ( int ) ), this, SLOT( on_qsTransmitHold_valueChanged(int) ) ); + connect( ui.qsNoise, SIGNAL( valueChanged ( int ) ), this, SLOT( on_qsNoise_valueChanged(int) ) ); + connect( ui.qsAmp, SIGNAL( valueChanged ( int ) ), this, SLOT( on_qsAmp_valueChanged(int) ) ); + connect( ui.qcbTransmit, SIGNAL( currentIndexChanged ( int ) ), this, SLOT( on_qcbTransmit_currentIndexChanged(int) ) ); + loaded = true; +} + +bool AudioInputConfig::save(QString &/*errmsg*/) {//mainly useless beacause saving occurs in realtime + //s.iQuality = qsQuality->value(); + Settings->setVoipiNoiseSuppress((ui.qsNoise->value() == 14) ? 0 : - ui.qsNoise->value()); + Settings->setVoipiMinLoudness(20000 - ui.qsAmp->value()); + Settings->setVoipVoiceHold(ui.qsTransmitHold->value()); + Settings->setVoipfVADmin(ui.qsTransmitMin->value()); + Settings->setVoipfVADmax(ui.qsTransmitMax->value()); + /*s.uiDoublePush = qsDoublePush->value() * 1000;*/ + Settings->setVoipATransmit(static_cast(ui.qcbTransmit->currentIndex() + 1)); + Settings->setVoipEchoCancel(ui.qcbEchoCancel->isChecked()); + + return true; +} + +/*bool AudioInputDialog::expert(bool b) { + qgbInterfaces->setVisible(b); + qgbAudio->setVisible(b); + qliFrames->setVisible(b); + qsFrames->setVisible(b); + qlFrames->setVisible(b); + qswTransmit->setVisible(b); + qliIdle->setVisible(b); + qsIdle->setVisible(b); + qlIdle->setVisible(b); + return true; +}*/ + + +void AudioInputConfig::on_qsTransmitHold_valueChanged(int v) { + float val = static_cast(v * FRAME_SIZE); + val = val / SAMPLING_RATE; + ui.qlTransmitHold->setText(tr("%1 s").arg(val, 0, 'f', 2)); + Settings->setVoipVoiceHold(v); +} + +void AudioInputConfig::on_qsNoise_valueChanged(int v) { + QPalette pal; + + if (v < 15) { + ui.qlNoise->setText(tr("Off")); + pal.setColor(ui.qlNoise->foregroundRole(), Qt::red); + } else { + ui.qlNoise->setText(tr("-%1 dB").arg(v)); + } + ui.qlNoise->setPalette(pal); + Settings->setVoipiNoiseSuppress(- ui.qsNoise->value()); +} + +void AudioInputConfig::on_qsAmp_valueChanged(int v) { + v = 20000 - v; + float d = 20000.0f/static_cast(v); + ui.qlAmp->setText(QString::fromLatin1("%1").arg(d, 0, 'f', 2)); + Settings->setVoipiMinLoudness(20000 - ui.qsAmp->value()); +} + +void AudioInputConfig::on_qcbEchoCancel_clicked() { + Settings->setVoipEchoCancel(ui.qcbEchoCancel->isChecked()); +} + + +void AudioInputConfig::on_qcbTransmit_currentIndexChanged(int v) { + switch (v) { + case 0: + ui.qswTransmit->setCurrentWidget(ui.qwContinuous); + break; + case 1: + ui.qswTransmit->setCurrentWidget(ui.qwVAD); + break; + case 2: + ui.qswTransmit->setCurrentWidget(ui.qwPTT); + break; + } + if (loaded) + Settings->setVoipATransmit(static_cast(ui.qcbTransmit->currentIndex() + 1)); +} + + +void AudioInputConfig::on_Tick_timeout() { + if (!inputProcessor) { + inputProcessor = new QtSpeex::SpeexInputProcessor(); + inputProcessor->open(QIODevice::WriteOnly | QIODevice::Unbuffered); + + if (!inputDevice) { + inputDevice = AudioDeviceHelper::getPreferedInputDevice(); + } + inputDevice->start(inputProcessor); + connect(inputProcessor, SIGNAL(networkPacketReady()), this, SLOT(emptyBuffer())); + } + + abSpeech->iBelow = ui.qsTransmitMin->value(); + abSpeech->iAbove = ui.qsTransmitMax->value(); + if (loaded) { + Settings->setVoipfVADmin(ui.qsTransmitMin->value()); + Settings->setVoipfVADmax(ui.qsTransmitMax->value()); + } + + abSpeech->iValue = iroundf(inputProcessor->dVoiceAcivityLevel * 32767.0f + 0.5f); + + abSpeech->update(); +} + +void AudioInputConfig::emptyBuffer() { + while(inputProcessor->hasPendingPackets()) { + inputProcessor->getNetworkPacket(); //that will purge the buffer + } +} + +void AudioInputConfig::on_qpbAudioWizard_clicked() { + AudioWizard *aw = new AudioWizard(this); + aw->exec(); + delete aw; + loadSettings(); +} diff --git a/plugins/VOIP/AudioInputConfig.h b/plugins/VOIP/AudioInputConfig.h new file mode 100644 index 000000000..7cf98dff0 --- /dev/null +++ b/plugins/VOIP/AudioInputConfig.h @@ -0,0 +1,82 @@ +/* Copyright (C) 2005-2010, Thorvald Natvig + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _AUDIOINPUTCONFIG_H +#define _AUDIOINPUTCONFIG_H + +#include +#include + +#include "configpage.h" +#include "ui_AudioInputConfig.h" +#include "util/SpeexProcessor.h" +#include "AudioStats.h" + +class AudioInputConfig : public ConfigPage { + Q_OBJECT + + private: + Ui::AudioInput ui; + QAudioInput* inputDevice; + QtSpeex::SpeexInputProcessor* inputProcessor; + AudioBar* abSpeech; + bool loaded; + + + protected: + QTimer *qtTick; + /*void hideEvent(QHideEvent *event); + void showEvent(QShowEvent *event);*/ + + public: + /** Default Constructor */ + AudioInputConfig(QWidget * parent = 0, Qt::WFlags flags = 0); + /** Default Destructor */ + ~AudioInputConfig(); + + /** Saves the changes on this page */ + bool save(QString &errmsg); + /** Loads the settings for this page */ + void load(); + + private slots: + void loadSettings(); + void emptyBuffer(); + + void on_qsTransmitHold_valueChanged(int v); + void on_qsAmp_valueChanged(int v); + void on_qsNoise_valueChanged(int v); + void on_qcbTransmit_currentIndexChanged(int v); + void on_Tick_timeout(); + void on_qpbAudioWizard_clicked(); + void on_qcbEchoCancel_clicked(); +}; + +#endif diff --git a/plugins/VOIP/AudioInputConfig.ui b/plugins/VOIP/AudioInputConfig.ui new file mode 100644 index 000000000..806d1ba6f --- /dev/null +++ b/plugins/VOIP/AudioInputConfig.ui @@ -0,0 +1,378 @@ + + + AudioInput + + + + 0 + 0 + 508 + 378 + + + + Form + + + + + + Audio Wizard + + + + + + + + 0 + 0 + + + + Transmission + + + + + + &Transmit + + + qcbTransmit + + + + + + + When to transmit your speech + + + <b>This sets when speech should be transmitted.</b><br /><i>Continuous</i> - All the time<br /><i>Voice Activity</i> - When you are speaking clearly.<br /><i>Push To Talk</i> - When you hold down the hotkey set under <i>Shortcuts</i>. + + + + + + + 2 + + + + + + + DoublePush Time + + + qsDoublePush + + + + + + + If you press the PTT key twice in this time it will get locked. + + + <b>DoublePush Time</b><br />If you press the push-to-talk key twice during the configured interval of time it will be locked. Mumble will keep transmitting until you hit the key once more to unlock PTT again. + + + 1000 + + + 10 + + + 100 + + + Qt::Horizontal + + + + + + + TextLabel + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Voice &Hold + + + qsTransmitHold + + + + + + + How long to keep transmitting after silence + + + <b>This selects how long after a perceived stop in speech transmission should continue.</b><br />Set this higher if your voice breaks up when you speak (seen by a rapidly blinking voice icon next to your name). + + + 20 + + + 250 + + + Qt::Horizontal + + + + + + + Silence Below + + + + + + + Signal values below this count as silence + + + <b>This sets the trigger values for voice detection.</b><br />Use this together with the Audio Statistics window to manually tune the trigger values for detecting speech. Input values below "Silence Below" always count as silence. Values above "Speech Above" always count as voice. Values in between will count as voice if you're already talking, but will not trigger a new detection. + + + 1 + + + 32767 + + + 100 + + + 1000 + + + Qt::Horizontal + + + + + + + Speech Above + + + + + + + Signal values above this count as voice + + + <b>This sets the trigger values for voice detection.</b><br />Use this together with the Audio Statistics window to manually tune the trigger values for detecting speech. Input values below "Silence Below" always count as silence. Values above "Speech Above" always count as voice. Values in between will count as voice if you're already talking, but will not trigger a new detection. + + + 1 + + + 32767 + + + 100 + + + 1000 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + + + + + + + empty + + + + + + + + + + + + + + + Audio Processing + + + + + + Noise Suppression + + + qsNoise + + + + + + + true + + + Noise suppression + + + <b>This sets the amount of noise suppression to apply.</b><br />The higher this value, the more aggressively stationary noise will be suppressed. + + + 14 + + + 60 + + + 5 + + + Qt::Horizontal + + + + + + + + 30 + 0 + + + + + + + + + + + Amplification + + + qsAmp + + + + + + + Maximum amplification of input sound + + + <b>Maximum amplification of input.</b><br />Mumble normalizes the input volume before compressing, and this sets how much it's allowed to amplify.<br />The actual level is continually updated based on your current speech pattern, but it will never go above the level specified here.<br />If the <i>Microphone loudness</i> level of the audio statistics hover around 100%, you probably want to set this to 2.0 or so, but if, like most people, you are unable to reach 100%, set this to something much higher.<br />Ideally, set it so <i>Microphone Loudness * Amplification Factor >= 100</i>, even when you're speaking really soft.<br /><br />Note that there is no harm in setting this to maximum, but Mumble will start picking up other conversations if you leave it to auto-tune to that level. + + + 19500 + + + 500 + + + 2000 + + + Qt::Horizontal + + + + + + + + 30 + 0 + + + + + + + + + + + Echo Cancellation Processing + + + false + + + + + + + + + + Qt::Vertical + + + + 1 + 151 + + + + + + + + qcbTransmit + qsDoublePush + qsTransmitHold + qsTransmitMin + qsTransmitMax + qsNoise + qsAmp + + + + diff --git a/plugins/VOIP/AudioStats.cpp b/plugins/VOIP/AudioStats.cpp new file mode 100644 index 000000000..354fa367c --- /dev/null +++ b/plugins/VOIP/AudioStats.cpp @@ -0,0 +1,411 @@ +/* Copyright (C) 2005-2010, Thorvald Natvig + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define iroundf(x) ( static_cast(x) ) + +#include "AudioStats.h" +#include "AudioInputConfig.h" +//#include "Global.h" +//#include "smallft.h" + +AudioBar::AudioBar(QWidget *p) : QWidget(p) { + highContrast = false; + qcBelow = Qt::yellow; + qcAbove = Qt::red; + qcInside = Qt::green; + + iMin = 0; + iMax = 32768; + iBelow = 2000; + iAbove = 22000; + iValue = 1000; + iPeak = -1; + setMinimumSize(100,20); + + qlReplacableColors << Qt::yellow << Qt::red << Qt::green << Qt::blue; + qlReplacementBrushes << Qt::BDiagPattern << Qt::DiagCrossPattern << Qt::NoBrush << Qt::FDiagPattern; +} + +void AudioBar::paintEvent(QPaintEvent *) { + QPainter p(this); + + if (isEnabled()) { + qcBelow.setAlphaF(1.0f); + qcAbove.setAlphaF(1.0f); + qcInside.setAlphaF(1.0f); + } else { + qcBelow.setAlphaF(0.5f); + qcAbove.setAlphaF(0.5f); + qcInside.setAlphaF(0.5f); + } + + if (iBelow > iAbove) + iBelow = iAbove; + + if (iValue < iMin) + iValue = iMin; + else if (iValue > iMax) + iValue = iMax; + + float scale = static_cast(width()) / static_cast(iMax - iMin); + int h = height(); + + int val = iroundf(static_cast(iValue) * scale + 0.5f); + int below = iroundf(static_cast(iBelow) * scale + 0.5f); + int above = iroundf(static_cast(iAbove) * scale + 0.5f); + int max = iroundf(static_cast(iMax) * scale + 0.5f); + int min = iroundf(static_cast(iMin) * scale + 0.5f); + int peak = iroundf(static_cast(iPeak) * scale + 0.5f); + + if (highContrast) { + // Draw monochrome representation + QColor fg = QPalette().foreground().color(); + + p.fillRect(0, 0, below, h, QBrush(fg, qlReplacementBrushes.value(qlReplacableColors.indexOf(qcBelow), Qt::CrossPattern))); + p.fillRect(below, 0, above - below, h, QBrush(fg, qlReplacementBrushes.value(qlReplacableColors.indexOf(qcInside), Qt::NoBrush))); + p.fillRect(above, 0, max - above, h, QBrush(fg, qlReplacementBrushes.value(qlReplacableColors.indexOf(qcAbove), Qt::CrossPattern))); + p.fillRect(0, 0, val, h, QBrush(fg, Qt::SolidPattern)); + + p.drawRect(0, 0, max - 1, h - 1); + p.drawLine(below, 0, below, h); + p.drawLine(above, 0, above, h); + } else { + if (val <= below) { + p.fillRect(0, 0, val, h, qcBelow); + p.fillRect(val, 0, below-val, h, qcBelow.darker(300)); + p.fillRect(below, 0, above-below, h, qcInside.darker(300)); + p.fillRect(above, 0, max-above, h, qcAbove.darker(300)); + } else if (val <= above) { + p.fillRect(0, 0, below, h, qcBelow); + p.fillRect(below, 0, val-below, h, qcInside); + p.fillRect(val, 0, above-val, h, qcInside.darker(300)); + p.fillRect(above, 0, max-above, h, qcAbove.darker(300)); + } else { + p.fillRect(0, 0, below, h, qcBelow); + p.fillRect(below, 0, above-below, h, qcInside); + p.fillRect(above, 0, val-above, h, qcAbove); + p.fillRect(val, 0, max-val, h, qcAbove.darker(300)); + } + } + + if ((peak >= min) && (peak <= max)) { + if (peak <= below) + p.setPen(qcBelow.lighter(150)); + else if (peak <= above) + p.setPen(qcInside.lighter(150)); + else + p.setPen(qcAbove.lighter(150)); + p.drawLine(peak, 0, peak, h); + } + +} +/* +AudioEchoWidget::AudioEchoWidget(QWidget *p) : QWidget(p) { + setMinimumSize(100, 60); +} + +static inline const QColor mapEchoToColor(float echo) { + bool neg = (echo < 0.0f); + echo = fabsf(echo); + + float a, b, c; + + if (echo > 1.0f) { + echo = 1.0f; + c = 0.5f; + } else { + c = 0.0f; + } + + if (echo < 0.5f) { + a = echo * 2.0f; + b = 0.0f; + } else { + a = 1.0f; + b = (echo - 0.5f) * 2.0f; + } + + if (neg) + return QColor::fromRgbF(a, b, c); + else + return QColor::fromRgbF(c, b, a); +} + +#define WGT(x,y) st->W[(y)*N + 2*(x)+1] + +void AudioEchoWidget::paintEvent(QPaintEvent *) { + QPainter paint(this); + + paint.scale(width(), height()); + paint.fillRect(rect(), Qt::black); + + AudioInputPtr ai = g.ai; + if (! ai || ! ai->sesEcho) + return; + + ai->qmSpeex.lock(); + + spx_int32_t sz; + speex_echo_ctl(ai->sesEcho, SPEEX_ECHO_GET_IMPULSE_RESPONSE_SIZE, &sz); + + STACKVAR(spx_int32_t, w, sz); + STACKVAR(float, W, sz); + + speex_echo_ctl(ai->sesEcho, SPEEX_ECHO_GET_IMPULSE_RESPONSE, w); + + ai->qmSpeex.unlock(); + + int N = 160; + int n = 2 * N; + int M = sz / n; + + drft_lookup d; + mumble_drft_init(&d, n); + + for (int j=0;j(w[j*n+i]) / static_cast(n); + mumble_drft_forward(&d, & W[j*n]); + } + + mumble_drft_clear(&d); + + float xscale = 1.0f / static_cast(N); + float yscale = 1.0f / static_cast(M); + + for (int j = 0; j < M; j++) { + for (int i=1;i < N; i++) { + float xa = static_cast(i) * xscale; + float ya = static_cast(j) * yscale; + + float xb = xa + xscale; + float yb = ya + yscale; + + const QColor &c = mapEchoToColor(sqrtf(W[j*n+2*i]*W[j*n+2*i]+W[j*n+2*i-1]*W[j*n+2*i-1]) / 65536.f); + paint.fillRect(QRectF(QPointF(xa, ya), QPointF(xb, yb)), c); + } + } + + QPolygonF poly; + xscale = 1.0f / (2.0f * static_cast(n)); + yscale = 1.0f / (200.0f * 32767.0f); + for (int i = 0; i < 2 * n; i++) { + poly << QPointF(static_cast(i) * xscale, 0.5f + static_cast(w[i]) * yscale); + } + paint.setPen(QColor::fromRgbF(1.0f, 0.0f, 1.0f)); + paint.drawPolyline(poly); +} + +AudioNoiseWidget::AudioNoiseWidget(QWidget *p) : QWidget(p) { + setMinimumSize(100,60); +} + +void AudioNoiseWidget::paintEvent(QPaintEvent *) { + QPainter paint(this); + QPalette pal; + + paint.fillRect(rect(), pal.color(QPalette::Background)); + + AudioInputPtr ai = g.ai; + if (ai.get() == NULL || ! ai->sppPreprocess) + return; + + QPolygonF poly; + + ai->qmSpeex.lock(); + + spx_int32_t ps_size = 0; + speex_preprocess_ctl(ai->sppPreprocess, SPEEX_PREPROCESS_GET_PSD_SIZE, &ps_size); + + STACKVAR(spx_int32_t, noise, ps_size); + STACKVAR(spx_int32_t, ps, ps_size); + + speex_preprocess_ctl(ai->sppPreprocess, SPEEX_PREPROCESS_GET_PSD, ps); + speex_preprocess_ctl(ai->sppPreprocess, SPEEX_PREPROCESS_GET_NOISE_PSD, noise); + + ai->qmSpeex.unlock(); + + qreal sx, sy; + + sx = (static_cast(width()) - 1.0f) / static_cast(ps_size); + sy = static_cast(height()) - 1.0f; + + poly << QPointF(0.0f, height() - 1); + float fftmul = 1.0 / (32768.0); + for (int i=0; i < ps_size; i++) { + qreal xp, yp; + xp = i * sx; + yp = sqrtf(sqrtf(static_cast(noise[i]))) - 1.0f; + yp = yp * fftmul; + yp = qMin(yp * 3000.0f, 1.0f); + yp = (1 - yp) * sy; + poly << QPointF(xp, yp); + } + + poly << QPointF(width() - 1, height() - 1); + poly << QPointF(0.0f, height() - 1); + + paint.setPen(Qt::blue); + paint.setBrush(Qt::blue); + paint.drawPolygon(poly); + + poly.clear(); + + for (int i=0;i < ps_size; i++) { + qreal xp, yp; + xp = i * sx; + yp = sqrtf(sqrtf(static_cast(ps[i]))) - 1.0f; + yp = yp * fftmul; + yp = qMin(yp * 3000.0, 1.0); + yp = (1 - yp) * sy; + poly << QPointF(xp, yp); + } + + paint.setPen(Qt::red); + paint.drawPolyline(poly); +} + +AudioStats::AudioStats(QWidget *p) : QDialog(p) { + setAttribute(Qt::WA_DeleteOnClose, true); + + qtTick = new QTimer(this); + qtTick->setObjectName(QLatin1String("Tick")); + qtTick->start(50); + + setupUi(this); + AudioInputPtr ai = g.ai; + + if (ai && ai->sesEcho) { + qgbEcho->setVisible(true); + } else { + qgbEcho->setVisible(false); + } + + + bTalking = false; + + abSpeech->iPeak = -1; + abSpeech->qcBelow = Qt::red; + abSpeech->qcInside = Qt::yellow; + abSpeech->qcAbove = Qt::green; + + on_Tick_timeout(); +} + +AudioStats::~AudioStats() { +} + +void AudioStats::on_Tick_timeout() { + AudioInputPtr ai = g.ai; + + if (ai.get() == NULL || ! ai->sppPreprocess) + return; + + bool nTalking = ai->isTransmitting(); + + QString txt; + + txt.sprintf("%06.2f dB",ai->dPeakMic); + qlMicLevel->setText(txt); + + txt.sprintf("%06.2f dB",ai->dPeakSpeaker); + qlSpeakerLevel->setText(txt); + + txt.sprintf("%06.2f dB",ai->dPeakSignal); + qlSignalLevel->setText(txt); + + spx_int32_t ps_size = 0; + speex_preprocess_ctl(ai->sppPreprocess, SPEEX_PREPROCESS_GET_PSD_SIZE, &ps_size); + + STACKVAR(spx_int32_t, noise, ps_size); + STACKVAR(spx_int32_t, ps, ps_size); + + speex_preprocess_ctl(ai->sppPreprocess, SPEEX_PREPROCESS_GET_PSD, ps); + speex_preprocess_ctl(ai->sppPreprocess, SPEEX_PREPROCESS_GET_NOISE_PSD, noise); + + float s = 0.0f; + float n = 0.0001f; + + int start = (ps_size * 300) / SAMPLE_RATE; + int stop = (ps_size * 2000) / SAMPLE_RATE; + + for (int i=start;i(ps[i])); + n += sqrtf(static_cast(noise[i])); + } + + txt.sprintf("%06.3f",s / n); + qlMicSNR->setText(txt); + + spx_int32_t v; + speex_preprocess_ctl(ai->sppPreprocess, SPEEX_PREPROCESS_GET_AGC_GAIN, &v); + float fv = powf(10.0f, (static_cast(v) / 20.0f)); + txt.sprintf("%03.0f%%",100.0f / fv); + qlMicVolume->setText(txt); + + txt.sprintf("%03.0f%%",ai->fSpeechProb * 100.0f); + qlSpeechProb->setText(txt); + + txt.sprintf("%04.1f kbit/s",static_cast(ai->iBitrate) / 1000.0f); + qlBitrate->setText(txt); + + if (nTalking != bTalking) { + bTalking = nTalking; + QFont f = qlSpeechProb->font(); + f.setBold(bTalking); + qlSpeechProb->setFont(f); + } + + if (g.uiDoublePush > 1000000) + txt = tr(">1000 ms"); + else + txt.sprintf("%04llu ms",g.uiDoublePush / 1000); + qlDoublePush->setText(txt); + + abSpeech->iBelow = iroundf(g.s.fVADmin * 32767.0f + 0.5f); + abSpeech->iAbove = iroundf(g.s.fVADmax * 32767.0f + 0.5f); + + if (g.s.vsVAD == Settings::Amplitude) { +#ifndef COMPAT_CLIENT + abSpeech->iValue = iroundf((32767.f/96.0f) * (96.0f + ai->dPeakCleanMic) + 0.5f); +#else + abSpeech->iValue = iroundf((32767.f/96.0f) * (96.0f + ai->dPeakMic) + 0.5f); +#endif + } else { + abSpeech->iValue = iroundf(ai->fSpeechProb * 32767.0f + 0.5f); + } + + abSpeech->update(); + + anwNoise->update(); + if (aewEcho) + aewEcho->update(); +} +*/ diff --git a/plugins/VOIP/AudioStats.h b/plugins/VOIP/AudioStats.h new file mode 100644 index 000000000..dc4dc4166 --- /dev/null +++ b/plugins/VOIP/AudioStats.h @@ -0,0 +1,96 @@ +/* Copyright (C) 2005-2010, Thorvald Natvig + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _AUDIOSTATS_H +#define _AUDIOSTATS_H + +#include +#include + +//#include "mumble_pch.hpp" + +class AudioBar : public QWidget { + private: + Q_OBJECT + Q_DISABLE_COPY(AudioBar) + protected: + void paintEvent(QPaintEvent *event); + public: + AudioBar(QWidget *parent = NULL); + int iMin, iMax; + int iBelow, iAbove; + int iValue, iPeak; + bool highContrast; + QColor qcBelow, qcInside, qcAbove; + + QList qlReplacableColors; + QList qlReplacementBrushes; +}; +/* +class AudioEchoWidget : public QWidget { + private: + Q_OBJECT + Q_DISABLE_COPY(AudioEchoWidget) + public: + AudioEchoWidget(QWidget *parent); + protected slots: + void paintEvent(QPaintEvent *event); +}; + +class AudioNoiseWidget : public QWidget { + private: + Q_OBJECT + Q_DISABLE_COPY(AudioNoiseWidget) + public: + AudioNoiseWidget(QWidget *parent); + protected slots: + void paintEvent(QPaintEvent *event); +}; + +#include "ui_AudioStats.h" + +class AudioStats : public QDialog, public Ui::AudioStats { + private: + Q_OBJECT + Q_DISABLE_COPY(AudioStats) + protected: + QTimer *qtTick; + bool bTalking; + public: + AudioStats(QWidget *parent); + ~AudioStats(); + public slots: + void on_Tick_timeout(); +}; +*/ + +#else +class AudioStats; +#endif diff --git a/plugins/VOIP/AudioStats.ui b/plugins/VOIP/AudioStats.ui new file mode 100644 index 000000000..47d9f45c5 --- /dev/null +++ b/plugins/VOIP/AudioStats.ui @@ -0,0 +1,327 @@ + + + AudioStats + + + + 0 + 0 + 598 + 548 + + + + Audio Statistics + + + + + + + + Input Levels + + + + + + Peak microphone level + + + + + + + Peak power in last frame + + + This shows the peak power in the last frame (20 ms), and is the same measurement as you would usually find displayed as "input power". Please disregard this and look at <b>Microphone power</b> instead, which is much more steady and disregards outliers. + + + + + + + + + + Peak speaker level + + + + + + + Peak power in last frame + + + This shows the peak power of the speakers in the last frame (20 ms). Unless you are using a multi-channel sampling method (such as ASIO) with speaker channels configured, this will be 0. If you have such a setup configured, and this still shows 0 while you're playing audio from other programs, your setup is not working. + + + + + + + + + + Peak clean level + + + + + + + Peak power in last frame + + + This shows the peak power in the last frame (20 ms) after all processing. Ideally, this should be -96 dB when you're not talking. In reality, a sound studio should see -60 dB, and you should hopefully see somewhere around -20 dB. When you are talking, this should rise to somewhere between -5 and -10 dB.<br />If you are using echo cancellation, and this rises to more than -15 dB when you're not talking, your setup is not working, and you'll annoy other users with echoes. + + + + + + + + + + + + + Signal Analysis + + + + + + Microphone power + + + + + + + How close the current input level is to ideal + + + This shows how close your current input volume is to the ideal. To adjust your microphone level, open whatever program you use to adjust the recording volume, and look at the value here while talking.<br /><b>Talk loud, as you would when you're upset over getting fragged by a noob.</b><br />Adjust the volume until this value is close to 100%, but make sure it doesn't go above. If it does go above, you are likely to get clipping in parts of your speech, which will degrade sound quality. + + + + + + + + + + Signal-To-Noise ratio + + + + + + + Signal-To-Noise ratio from the microphone + + + This is the Signal-To-Noise Ratio (SNR) of the microphone in the last frame (20 ms). It shows how much clearer the voice is compared to the noise.<br />If this value is below 1.0, there's more noise than voice in the signal, and so quality is reduced.<br />There is no upper limit to this value, but don't expect to see much above 40-50 without a sound studio. + + + + + + + + + + Speech Probability + + + + + + + Probability of speech + + + This is the probability that the last frame (20 ms) was speech and not environment noise.<br />Voice activity transmission depends on this being right. The trick with this is that the middle of a sentence is always detected as speech; the problem is the pauses between words and the start of speech. It's hard to distinguish a sigh from a word starting with 'h'.<br />If this is in bold font, it means Mumble is currently transmitting (if you're connected). + + + + + + + + + + + + + + + Configuration feedback + + + + + + Current audio bitrate + + + + + + + + 20 + 0 + + + + Bitrate of last frame + + + This is the audio bitrate of the last compressed frame (20 ms), and as such will jump up and down as the VBR adjusts the quality. The peak bitrate can be adjusted in the Settings dialog. + + + + + + + + + + DoublePush interval + + + + + + + + 20 + 0 + + + + Time between last two Push-To-Talk presses + + + + + + + + + + Speech Detection + + + + + + + Current speech detection chance + + + <b>This shows the current speech detection settings.</b><br />You can change the settings from the Settings dialog or from the Audio Wizard. + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + 0 + + + + Signal and noise power spectrum + + + + + + Power spectrum of input signal and noise estimate + + + This shows the power spectrum of the current input signal (red line) and the current noise estimate (filled blue).<br />All amplitudes are multiplied by 30 to show the interesting parts (how much more signal than noise is present in each waveband).<br />This is probably only of interest if you're trying to fine-tune noise conditions on your microphone. Under good conditions, there should be just a tiny flutter of blue at the bottom. If the blue is more than halfway up on the graph, you have a seriously noisy environment. + + + + + + + + + + + 0 + 0 + + + + Echo Analysis + + + + + + + 0 + 0 + + + + Weights of the echo canceller + + + This shows the weights of the echo canceller, with time increasing downwards and frequency increasing to the right.<br />Ideally, this should be black, indicating no echo exists at all. More commonly, you'll have one or more horizontal stripes of bluish color representing time delayed echo. You should be able to see the weights updated in real time.<br />Please note that as long as you have nothing to echo off, you won't see much useful data here. Play some music and things should stabilize. <br />You can choose to view the real or imaginary parts of the frequency-domain weights, or alternately the computed modulus and phase. The most useful of these will likely be modulus, which is the amplitude of the echo, and shows you how much of the outgoing signal is being removed at that time step. The other viewing modes are mostly useful to people who want to tune the echo cancellation algorithms.<br />Please note: If the entire image fluctuates massively while in modulus mode, the echo canceller fails to find any correlation whatsoever between the two input sources (speakers and microphone). Either you have a very long delay on the echo, or one of the input sources is configured wrong. + + + + + + + + + + + AudioBar + QWidget +
AudioStats.h
+ 1 +
+ + AudioNoiseWidget + QWidget +
AudioStats.h
+ 1 +
+ + AudioEchoWidget + QWidget +
AudioStats.h
+ 1 +
+
+ + +
diff --git a/plugins/VOIP/AudioWizard.cpp b/plugins/VOIP/AudioWizard.cpp new file mode 100644 index 000000000..8bfe8e195 --- /dev/null +++ b/plugins/VOIP/AudioWizard.cpp @@ -0,0 +1,299 @@ +/* Copyright (C) 2005-2010, Thorvald Natvig + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "AudioWizard.h" +//#include "AudioInput.h" +//#include "Global.h" +//#include "Settings.h" +//#include "Log.h" +//#include "MainWindow.h" +#include "gui/settings/rsharesettings.h" +#include "audiodevicehelper.h" + +#define iroundf(x) ( static_cast(x) ) + +AudioWizard::~AudioWizard() +{ + if (inputDevice) { + inputDevice->stop(); + } + if (outputDevice) { + outputDevice->stop(); + } +} + +AudioWizard::AudioWizard(QWidget *p) : QWizard(p) { + bInit = true; + bLastActive = false; + //g.bInAudioWizard = true; + + ticker = new QTimer(this); + ticker->setObjectName(QLatin1String("Ticker")); + + setupUi(this); + inputProcessor = NULL; + inputDevice = NULL; + outputProcessor = NULL; + outputDevice = NULL; + + abAmplify = new AudioBar(this); + abAmplify->qcBelow = Qt::green; + abAmplify->qcInside = QColor::fromRgb(255,128,0); + abAmplify->qcAbove = Qt::red; + + verticalLayout_3->addWidget(abAmplify); + + if (Settings->getVoipATransmit() == RshareSettings::AudioTransmitPushToTalk) + qrPTT->setChecked(true); + else if (Settings->getVoipATransmit() == RshareSettings::AudioTransmitVAD) + qrVAD->setChecked(true); + else + qrContinuous->setChecked(true); + + abVAD = new AudioBar(this); + abVAD->qcBelow = Qt::red; + abVAD->qcInside = Qt::yellow; + abVAD->qcAbove = Qt::green; + + qsTransmitMin->setValue(Settings->getVoipfVADmin()); + qsTransmitMax->setValue(Settings->getVoipfVADmax()); + + verticalLayout_6->addWidget(abVAD); + + // Volume + qsMaxAmp->setValue(Settings->getVoipiMinLoudness()); + + setOption(QWizard::NoCancelButton, false); + resize(700, 500); + + updateTriggerWidgets(qrVAD->isChecked()); + + bTransmitChanged = false; + + iMaxPeak = 0; + iTicks = 0; + + qpTalkingOn = QPixmap::fromImage(QImage(QLatin1String("skin:talking_on.svg")).scaled(64,64)); + qpTalkingOff = QPixmap::fromImage(QImage(QLatin1String("skin:talking_off.svg")).scaled(64,64)); + + bInit = false; + + connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(showPage(int))); + + ticker->setSingleShot(false); + ticker->start(20); + connect( ticker, SIGNAL( timeout ( ) ), this, SLOT( on_Ticker_timeout() ) ); +} + +/*bool AudioWizard::eventFilter(QObject *obj, QEvent *evt) { + if ((evt->type() == QEvent::MouseButtonPress) || + (evt->type() == QEvent::MouseMove)) { + QMouseEvent *qme = dynamic_cast(evt); + if (qme) { + if (qme->buttons() & Qt::LeftButton) { + QPointF qpf = qgvView->mapToScene(qme->pos()); + fX = static_cast(qpf.x()); + fY = static_cast(qpf.y()); + } + } + } + return QWizard::eventFilter(obj, evt); +}*/ + + + +void AudioWizard::on_qsMaxAmp_valueChanged(int v) { + Settings->setVoipiMinLoudness(qMin(v, 30000)); +} + +void AudioWizard::on_Ticker_timeout() { + if (!inputProcessor) { + inputProcessor = new QtSpeex::SpeexInputProcessor(); + inputProcessor->open(QIODevice::WriteOnly | QIODevice::Unbuffered); + + if (!inputDevice) { + inputDevice = AudioDeviceHelper::getPreferedInputDevice(); + } + inputDevice->start(inputProcessor); + connect(inputProcessor, SIGNAL(networkPacketReady()), this, SLOT(loopAudio())); + } + + if (!outputProcessor) { + outputProcessor = new QtSpeex::SpeexOutputProcessor(); + outputProcessor->open(QIODevice::ReadOnly | QIODevice::Unbuffered); + + if (!outputDevice) { + outputDevice = AudioDeviceHelper::getPreferedOutputDevice(); + } + outputDevice->start(outputProcessor); + connect(outputProcessor, SIGNAL(playingFrame(QByteArray*)), inputProcessor, SLOT(addEchoFrame(QByteArray*))); + } + + abVAD->iBelow = qsTransmitMin->value(); + abVAD->iAbove = qsTransmitMax->value(); + Settings->setVoipfVADmin(qsTransmitMin->value()); + Settings->setVoipfVADmax(qsTransmitMax->value()); + + abVAD->iValue = iroundf(inputProcessor->dVoiceAcivityLevel * 32767.0f + 0.5f); + + abVAD->update(); + + int iPeak = inputProcessor->dMaxMic; + + if (iTicks++ >= 50) { + iMaxPeak = 0; + iTicks = 0; + } + if (iPeak > iMaxPeak) + iMaxPeak = iPeak; + + abAmplify->iBelow = qsMaxAmp->value(); + abAmplify->iValue = iPeak; + abAmplify->iPeak = iMaxPeak; + abAmplify->update(); + + bool active = inputProcessor->bPreviousVoice; + if (active != bLastActive) { + bLastActive = active; + qlTalkIcon->setPixmap(active ? qpTalkingOn : qpTalkingOff); + } +} + +void AudioWizard::loopAudio() { + while(inputProcessor && inputProcessor->hasPendingPackets()) { + packetQueue.enqueue(inputProcessor->getNetworkPacket()); + QTimer* playEcho = new QTimer(); + playEcho->setSingleShot(true); + connect( playEcho, SIGNAL( timeout ( ) ), this, SLOT( on_playEcho_timeout() ) ); + playEcho->start(1500); + } +} + +void AudioWizard::on_playEcho_timeout() { + if(!packetQueue.isEmpty()) { + if (!qcbStopEcho->isChecked()) { + if (outputDevice && outputDevice->error() != QAudio::NoError) { + std::cerr << "Stopping output device. Error " << outputDevice->error() << std::endl; + outputDevice->stop(); + //TODO : find a way to restart output device, but there is a pulseaudio locks that prevents it here but it works in ChatWidget.cpp + //outputDevice->start(outputProcessor); + } + outputProcessor->putNetworkPacket("myself_loop",packetQueue.dequeue()); + } else { + packetQueue.dequeue(); + } + } +} + +void AudioWizard::on_qsTransmitMax_valueChanged(int v) { + if (! bInit) { + Settings->setVoipfVADmax(v); + } +} + +void AudioWizard::on_qsTransmitMin_valueChanged(int v) { + if (! bInit) { + Settings->setVoipfVADmin(v); + } +} + +void AudioWizard::on_qrVAD_clicked(bool on) { + if (on) { + Settings->setVoipATransmit(RshareSettings::AudioTransmitVAD); + updateTriggerWidgets(true); + bTransmitChanged = true; + } +} + +void AudioWizard::on_qrPTT_clicked(bool on) { + if (on) { + Settings->setVoipATransmit(RshareSettings::AudioTransmitPushToTalk); + updateTriggerWidgets(false); + bTransmitChanged = true; + } +} + +void AudioWizard::on_qrContinuous_clicked(bool on) { + if (on) { + Settings->setVoipATransmit(RshareSettings::AudioTransmitContinous); + updateTriggerWidgets(false); + bTransmitChanged = true; + } +} + +/*void AudioWizard::on_skwPTT_keySet(bool valid, bool last) { + if (valid) + qrPTT->setChecked(true); + else if (qrPTT->isChecked()) + qrAmplitude->setChecked(true); + updateTriggerWidgets(valid); + bTransmitChanged = true; + + if (last) { + + const QList &buttons = skwPTT->getShortcut(); + QList ql; + bool found = false; + foreach(Shortcut s, g.s.qlShortcuts) { + if (s.iIndex == g.mw->gsPushTalk->idx) { + if (buttons.isEmpty()) + continue; + else if (! found) { + s.qlButtons = buttons; + found = true; + } + } + ql << s; + } + if (! found && ! buttons.isEmpty()) { + Shortcut s; + s.iIndex = g.mw->gsPushTalk->idx; + s.bSuppress = false; + s.qlButtons = buttons; + ql << s; + } + g.s.qlShortcuts = ql; + GlobalShortcutEngine::engine->bNeedRemap = true; + GlobalShortcutEngine::engine->needRemap(); + } +}*/ + + +void AudioWizard::updateTriggerWidgets(bool vad_on) { + if (!vad_on) + qwVAD->hide(); + else + qwVAD->show(); +} + +void AudioWizard::on_qcbHighContrast_clicked(bool on) { + abAmplify->highContrast = on; + abVAD->highContrast = on; +} diff --git a/plugins/VOIP/AudioWizard.h b/plugins/VOIP/AudioWizard.h new file mode 100644 index 000000000..cee60f4f5 --- /dev/null +++ b/plugins/VOIP/AudioWizard.h @@ -0,0 +1,96 @@ +/* Copyright (C) 2005-2010, Thorvald Natvig + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _AUDIOWIZARD_H +#define _AUDIOWIZARD_H + +#include +#include +#include + +#include "AudioStats.h" +#include "SpeexProcessor.h" + +#include "ui_AudioWizard.h" + +class AudioWizard: public QWizard, public Ui::AudioWizard { + private: + Q_OBJECT + Q_DISABLE_COPY(AudioWizard) + AudioBar* abAmplify; + AudioBar* abVAD; + QAudioInput* inputDevice; + QAudioOutput* outputDevice; + QtSpeex::SpeexInputProcessor* inputProcessor; + QtSpeex::SpeexOutputProcessor* outputProcessor; + QQueuepacketQueue; + + protected: + bool bTransmitChanged; + + QGraphicsScene *qgsScene; + QGraphicsItem *qgiSource; + //AudioOutputSample *aosSource; + float fAngle; + float fX, fY; + + //Settings sOldSettings; + + QTimer *ticker; + + bool bInit; + bool bDelay; + bool bLastActive; + + QPixmap qpTalkingOn, qpTalkingOff; + + int iMaxPeak; + int iTicks; + public slots: + void on_playEcho_timeout(); + void on_Ticker_timeout(); + void on_qsMaxAmp_valueChanged(int); + void on_qrPTT_clicked(bool); + void on_qrVAD_clicked(bool); + void on_qrContinuous_clicked(bool); + void on_qsTransmitMin_valueChanged(int); + void on_qsTransmitMax_valueChanged(int); + void on_qcbHighContrast_clicked(bool); + void updateTriggerWidgets(bool); + public: + AudioWizard(QWidget *parent); + ~AudioWizard(); + + private slots : + void loopAudio(); + +}; + +#endif diff --git a/plugins/VOIP/AudioWizard.ui b/plugins/VOIP/AudioWizard.ui new file mode 100644 index 000000000..63df16a66 --- /dev/null +++ b/plugins/VOIP/AudioWizard.ui @@ -0,0 +1,329 @@ + + + AudioWizard + + + + 0 + 0 + 757 + 823 + + + + Audio Tuning Wizard + + + + Introduction + + + Welcome to the Mumble Audio Wizard + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the audio tuning wizard for Mumble. This will help you correctly set the input levels of your sound card, and also set the correct parameters for sound processing in Retroshare. </p></body></html> + + + true + + + + + + + Qt::Vertical + + + + 20 + 291 + + + + + + + + + Volume tuning + + + Tuning microphone hardware volume to optimal settings. + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Open your sound control panel and go to the recording settings. Make sure the microphone is selected as active input with maximum recording volume. If there's an option to enable a &quot;Microphone boost&quot; make sure it's checked. </p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Speak loudly, as when you are annoyed or excited. Decrease the volume in the sound control panel until the bar below stays as high as possible in the green and orange but <span style=" font-weight:600;">not</span> the red zone while you speak. </p></body></html> + + + true + + + + + + + Talk normally, and adjust the slider below so that the bar moves into green when you talk, and doesn't go into the orange zone. + + + true + + + + + + + 1 + + + 32767 + + + 100 + + + 1000 + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 552 + + + + + + + + + + Stop looping echo for this wizard + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Apply some high contrast optimizations for visually impaired users + + + Use high contrast graphics + + + + + + + + + + Voice Activity Detection + + + Letting Mumble figure out when you're talking and when you're silent. + + + + + + This will help Retroshare figure out when you are talking. The first step is selecting which data value to use. + + + true + + + + + + + + + Push To Talk: + + + + + + + todo shortcut + + + + + + + + + + 64 + 64 + + + + + + + + + + + Voice Detection + + + + + + + + 0 + + + + + Next you need to adjust the following slider. The first few utterances you say should end up in the green area (definitive speech). While talking, you should stay inside the yellow (might be speech) and when you're not talking, everything should be in the red (definitively not speech). + + + true + + + + + + + 1 + + + 32767 + + + 100 + + + 1000 + + + Qt::Horizontal + + + + + + + 1 + + + 32767 + + + 100 + + + 1000 + + + Qt::Horizontal + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Continuous transmission + + + + + + + + Finished + + + Enjoy using Mumble + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Congratulations. You should now be ready to enjoy a richer sound experience with Retroshare. </p></body></html> + + + true + + + + + + + Qt::Vertical + + + + 20 + 267 + + + + + + + + + + diff --git a/plugins/VOIP/SpeexProcessor.cpp b/plugins/VOIP/SpeexProcessor.cpp new file mode 100644 index 000000000..ab148afe3 --- /dev/null +++ b/plugins/VOIP/SpeexProcessor.cpp @@ -0,0 +1,521 @@ +#include "SpeexProcessor.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "gui/settings/rsharesettings.h" + +#define iroundf(x) ( static_cast(x) ) + +using namespace QtSpeex; + +SpeexInputProcessor::SpeexInputProcessor(QObject *parent) : QIODevice(parent), + preprocessor(0), + enc_state(0), + enc_bits(), + send_timestamp(0), + echo_state(0), + inputBuffer(), + iMaxBitRate(16800), + bResetProcessor(true), + lastEchoFrame(NULL) +{ + enc_bits = new SpeexBits; + speex_bits_init(enc_bits); + speex_bits_reset(enc_bits); + enc_state = speex_encoder_init(&speex_wb_mode); + + int iArg = 0; + speex_encoder_ctl(enc_state,SPEEX_SET_VAD, &iArg); + speex_encoder_ctl(enc_state,SPEEX_SET_DTX, &iArg); + + float fArg=9.0; + speex_encoder_ctl(enc_state,SPEEX_SET_VBR_QUALITY, &fArg); + + iArg = iMaxBitRate; + speex_encoder_ctl(enc_state, SPEEX_SET_VBR_MAX_BITRATE, &iArg); + + iArg = 10; + speex_encoder_ctl(enc_state,SPEEX_SET_COMPLEXITY, &iArg); + + iArg = 9; + speex_encoder_ctl(enc_state,SPEEX_SET_QUALITY, &iArg); + + echo_state = NULL; + + //iEchoFreq = iMicFreq = iSampleRate; + + iSilentFrames = 0; + iHoldFrames = 0; + + bResetProcessor = true; + + //bEchoMulti = false; + + preprocessor = NULL; + echo_state = NULL; + //srsMic = srsEcho = NULL; + //iJitterSeq = 0; + //iMinBuffered = 1000; + + //psMic = new short[iFrameSize]; + psClean = new short[SAMPLING_RATE]; + + //psSpeaker = NULL; + + //iEchoChannels = iMicChannels = 0; + //iEchoFilled = iMicFilled = 0; + //eMicFormat = eEchoFormat = SampleFloat; + //iMicSampleSize = iEchoSampleSize = 0; + + bPreviousVoice = false; + + //pfMicInput = pfEchoInput = pfOutput = NULL; + + iRealTimeBitrate = 0; + dPeakSignal = dPeakSpeaker = dPeakMic = dPeakCleanMic = dVoiceAcivityLevel = 0.0; + + //if (g.uiSession) { + //TODO : get the maxbitrate from a rs service or a dynamic code + //iMaxBitRate = 10000; + //} + + //bRunning = true; + } + +SpeexInputProcessor::~SpeexInputProcessor() { + speex_preprocess_state_destroy(preprocessor); + if (echo_state) { + speex_echo_state_destroy(echo_state); + } + + speex_encoder_destroy(enc_state); + + + speex_bits_destroy(enc_bits); + delete enc_bits; + + free(psClean); +} + +QByteArray SpeexInputProcessor::getNetworkPacket() { + return outputNetworkBuffer.takeFirst(); +} + +bool SpeexInputProcessor::hasPendingPackets() { + return !outputNetworkBuffer.empty(); +} + +qint64 SpeexInputProcessor::writeData(const char *data, qint64 maxSize) { + int iArg; + int i; + float sum; + short max; + + inputBuffer += QByteArray(data, maxSize); + + while(inputBuffer.size() > FRAME_SIZE * sizeof(qint16)) { + + QByteArray source_frame = inputBuffer.left(FRAME_SIZE * sizeof(qint16)); + short* psMic = (short *)source_frame.data(); + + //let's do volume detection + sum=1.0f; + for (i=0;i(psMic[i] * psMic[i]); + } + dPeakMic = qMax(20.0f*log10f(sqrtf(sum / static_cast(FRAME_SIZE)) / 32768.0f), -96.0f); + + max = 1; + for (i=0;i(std::abs(psMic[i]) > max ? std::abs(psMic[i]) : max); + dMaxMic = max; + + dPeakSpeaker = 0.0; + + QMutexLocker l(&qmSpeex); + + if (bResetProcessor) { + if (preprocessor) + speex_preprocess_state_destroy(preprocessor); + + preprocessor = speex_preprocess_state_init(FRAME_SIZE, SAMPLING_RATE); + + iArg = 1; + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_SET_VAD, &iArg); + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_SET_AGC, &iArg); + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_SET_DENOISE, &iArg); + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_SET_DEREVERB, &iArg); + + iArg = 30000; + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_SET_AGC_TARGET, &iArg); + + iArg = -60; + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_SET_AGC_DECREMENT, &iArg); + + iArg = Settings->getVoipiNoiseSuppress(); + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &iArg); + + if (echo_state) { + iArg = SAMPLING_RATE; + speex_echo_ctl(echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &iArg); + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_SET_ECHO_STATE, echo_state); + } + + bResetProcessor = false; + } + + float v = 30000.0f / static_cast(Settings->getVoipiMinLoudness()); + + iArg = iroundf(floorf(20.0f * log10f(v))); + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_SET_AGC_MAX_GAIN, &iArg); + + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_GET_AGC_GAIN, &iArg); + float gainValue = static_cast(iArg); + iArg = Settings->getVoipiNoiseSuppress() - iArg; + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &iArg); + + short * psSource = psMic; + if (echo_state && Settings->getVoipEchoCancel()) { + speex_echo_playback(echo_state, (short*)lastEchoFrame->data()); + speex_echo_capture(echo_state,psMic,psClean); + psSource = psClean; + } + speex_preprocess_run(preprocessor, psSource); + + //we will now analize the processed signal + sum=1.0f; + for (i=0;i(psSource[i] * psSource[i]); + float micLevel = sqrtf(sum / static_cast(FRAME_SIZE)); + dPeakSignal = qMax(20.0f*log10f(micLevel / 32768.0f), -96.0f); + + spx_int32_t prob = 0; + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_GET_PROB, &prob);//speech probability + fSpeechProb = static_cast(prob) / 100.0f; + + // clean microphone level: peak of filtered signal attenuated by AGC gain + dPeakCleanMic = qMax(dPeakSignal - gainValue, -96.0f); + dVoiceAcivityLevel = 0.4f * fSpeechProb + 0.6f * (1.0f + dPeakCleanMic / 96.0f);//ponderation for speech detection and audio amplitude + + bool bIsSpeech = false; + + if (dVoiceAcivityLevel > (static_cast(Settings->getVoipfVADmax()) / 32767)) + bIsSpeech = true; + else if (dVoiceAcivityLevel > (static_cast(Settings->getVoipfVADmin()) / 32767) && bPreviousVoice) + bIsSpeech = true; + + if (! bIsSpeech) { + iHoldFrames++; + if (iHoldFrames < Settings->getVoipVoiceHold()) + bIsSpeech = true; + } else { + iHoldFrames = 0; + } + + + if (Settings->getVoipATransmit() == RshareSettings::AudioTransmitContinous) { + bIsSpeech = true; + } + else if (Settings->getVoipATransmit() == RshareSettings::AudioTransmitPushToTalk) + bIsSpeech = false;//g.s.uiDoublePush && ((g.uiDoublePush < g.s.uiDoublePush) || (g.tDoublePush.elapsed() < g.s.uiDoublePush)); + + //bIsSpeech = bIsSpeech || (g.iPushToTalk > 0); + + /*if (g.s.bMute || ((g.s.lmLoopMode != Settings::Local) && p && (p->bMute || p->bSuppress)) || g.bPushToMute || (g.iTarget < 0)) { + bIsSpeech = false; + }*/ + + if (bIsSpeech) { + iSilentFrames = 0; + } else { + iSilentFrames++; + } + + /*if (p) { + if (! bIsSpeech) + p->setTalking(Settings::Passive); + else if (g.iTarget == 0) + p->setTalking(Settings::Talking); + else + p->setTalking(Settings::Shouting); + }*/ + + + if (! bIsSpeech && ! bPreviousVoice) { + iRealTimeBitrate = 0; + /*if (g.s.iIdleTime && ! g.s.bDeaf && ((tIdle.elapsed() / 1000000ULL) > g.s.iIdleTime)) { + emit doDeaf(); + tIdle.restart(); + }*/ + spx_int32_t increment = 0; + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_SET_AGC_INCREMENT, &increment); + } else { + spx_int32_t increment = 12; + speex_preprocess_ctl(preprocessor, SPEEX_PREPROCESS_SET_AGC_INCREMENT, &increment); + } + + + int vbr_on=0; + //just use fixed bitrate for now + //encryption of VBR-encoded speech may not ensure complete privacy, as phrases can still be identified, at least in a controlled setting with a small dictionary of phrases, by analysing the pattern of variation of the bit rate. + if (Settings->getVoipATransmit() == RshareSettings::AudioTransmitVAD) {//maybe we can do fixer bitrate when voice detection is active + vbr_on = 1;//test it on for all modes + } else {//maybe we can do vbr for ppt and continuous + vbr_on = 1; + } + speex_encoder_ctl(enc_state,SPEEX_SET_VBR, &vbr_on); + + int br = 0; + speex_encoder_ctl(enc_state, SPEEX_GET_VBR_MAX_BITRATE, &br); + if (br != iMaxBitRate) { + br = iMaxBitRate; + speex_encoder_ctl(enc_state, SPEEX_SET_VBR_MAX_BITRATE, &br); + } + speex_encoder_ctl(enc_state, SPEEX_GET_BITRATE, &br); + if (br != iMaxBitRate) { + br = iMaxBitRate; + speex_encoder_ctl(enc_state, SPEEX_SET_BITRATE, &br); + } + + if (! bPreviousVoice) + speex_encoder_ctl(enc_state, SPEEX_RESET_STATE, NULL); + + if (bIsSpeech) { + speex_bits_reset(enc_bits); + speex_encode_int(enc_state, psSource, enc_bits); + QByteArray networkFrame; + networkFrame.resize(speex_bits_nbytes(enc_bits)+4);//add 4 for the frame timestamp for the jitter buffer + int packetSize = speex_bits_write(enc_bits, networkFrame.data()+4, networkFrame.size()-4); + ((int*)networkFrame.data())[0] = send_timestamp; + + outputNetworkBuffer.append(networkFrame); + emit networkPacketReady(); + + iRealTimeBitrate = packetSize * SAMPLING_RATE / FRAME_SIZE * 8; + } else { + iRealTimeBitrate = 0; + } + bPreviousVoice = bIsSpeech; + + //std::cerr << "iRealTimeBitrate : " << iRealTimeBitrate << std::endl; + + send_timestamp += FRAME_SIZE; + if (send_timestamp >= INT_MAX) + send_timestamp = 0; + + inputBuffer = inputBuffer.right(inputBuffer.size() - FRAME_SIZE * sizeof(qint16)); + } + + return maxSize; +} + + +SpeexOutputProcessor::SpeexOutputProcessor(QObject *parent) : QIODevice(parent), + outputBuffer() +{ +} + +SpeexOutputProcessor::~SpeexOutputProcessor() { + QHashIterator i(userJitterHash); + while (i.hasNext()) { + i.next(); + speex_jitter_destroy(*(i.value())); + free (i.value()); + } +} + +void SpeexOutputProcessor::putNetworkPacket(QString name, QByteArray packet) { + //buffer: + // timestamp | encodedBuf + // —————–———–——————–———–——————–———–——————– + // 4 | totalSize – 4 + //the size part (first 4 byets) is not actually used in the logic + if (packet.size() > 4) + { + SpeexJitter* userJitter; + if (userJitterHash.contains(name)) { + userJitter = userJitterHash.value(name); + } else { + userJitter = (SpeexJitter*)malloc(sizeof(SpeexJitter)); + speex_jitter_init(userJitter, speex_decoder_init(&speex_wb_mode), SAMPLING_RATE); + int on = 1; + speex_decoder_ctl(userJitter->dec, SPEEX_SET_ENH, &on); + userJitterHash.insert(name, userJitter); + } + + int recv_timestamp = ((int*)packet.data())[0]; + userJitter->mostUpdatedTSatPut = recv_timestamp; + if (userJitter->firsttimecalling_get) + return; + + speex_jitter_put(*userJitter, (char *)packet.data()+4, packet.size()-4, recv_timestamp); + } +} + +bool SpeexInputProcessor::isSequential() const { + return true; +} + +void SpeexInputProcessor::addEchoFrame(QByteArray* echo_frame) { + if (Settings->getVoipEchoCancel() && echo_frame) { + QMutexLocker l(&qmSpeex); + lastEchoFrame = echo_frame; + if (!echo_state) {//init echo_state + echo_state = speex_echo_state_init(FRAME_SIZE, ECHOTAILSIZE*FRAME_SIZE); + int tmp = SAMPLING_RATE; + speex_echo_ctl(echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &tmp); + bResetProcessor = true; + } + lastEchoFrame = echo_frame; + } +} + + +qint64 SpeexOutputProcessor::readData(char *data, qint64 maxSize) { + + int ts = 0; //time stamp for the jitter call + while(outputBuffer.size() < maxSize) { + QByteArray* result_frame = new QByteArray(); + result_frame->resize(FRAME_SIZE * sizeof(qint16)); + result_frame->fill(0,FRAME_SIZE * sizeof(qint16)); + QHashIterator i(userJitterHash); + while (i.hasNext()) { + i.next(); + SpeexJitter* jitter = i.value(); + QByteArray intermediate_frame; + intermediate_frame.resize(FRAME_SIZE * sizeof(qint16)); + if (jitter->firsttimecalling_get) + { + int ts = jitter->mostUpdatedTSatPut; + jitter->firsttimecalling_get = false; + } + speex_jitter_get(*jitter, (spx_int16_t*)intermediate_frame.data(), &ts); + for (int j = 0; j< FRAME_SIZE; j++) { + short sample1 = ((short*)result_frame->data())[j]; + short sample2 = ((short*)intermediate_frame.data())[j]; + float samplef1 = sample1 / 32768.0f; + float samplef2 = sample2 / 32768.0f; + float mixed = samplef1 + 0.8f * samplef2; + // hard clipping + if (mixed > 1.0f) mixed = 1.0f; + if (mixed < -1.0f) mixed = -1.0f; + ((spx_int16_t*)result_frame->data())[j] = (short)(mixed * 32768.0f); + } + } + outputBuffer += *result_frame; + emit playingFrame(result_frame); + } + + QByteArray resultBuffer = outputBuffer.left(maxSize); + memcpy(data, resultBuffer.data(), resultBuffer.size()); + + outputBuffer = outputBuffer.right(outputBuffer.size() - resultBuffer.size()); + + return resultBuffer.size(); +} + +bool SpeexOutputProcessor::isSequential() const { + return true; +} + +void SpeexOutputProcessor::speex_jitter_init(SpeexJitter *jit, void *decoder, int sampling_rate) +{ + jit->dec = decoder; + speex_decoder_ctl(decoder, SPEEX_GET_FRAME_SIZE, &jit->frame_size); + + jit->packets = jitter_buffer_init(jit->frame_size); + jit->current_packet = new SpeexBits; + speex_bits_init(jit->current_packet); + jit->valid_bits = 0; + jit->firsttimecalling_get = true; + jit->mostUpdatedTSatPut = 0; +} + +void SpeexOutputProcessor::speex_jitter_destroy(SpeexJitter jitter) +{ + if (jitter.dec) { + speex_decoder_destroy(jitter.dec); + } + jitter_buffer_destroy(jitter.packets); + speex_bits_destroy(jitter.current_packet); +} + +void SpeexOutputProcessor::speex_jitter_put(SpeexJitter jitter, char *packet, int len, int timestamp) +{ + JitterBufferPacket p; + p.data = packet; + p.len = len; + p.timestamp = timestamp; + p.span = jitter.frame_size; + jitter_buffer_put(jitter.packets, &p); +} + +void SpeexOutputProcessor::speex_jitter_get(SpeexJitter jitter, spx_int16_t *out, int *current_timestamp) +{ + int i; + int ret; + spx_int32_t activity; + int bufferCount = 0; + JitterBufferPacket packet; + char data[FRAME_SIZE * ECHOTAILSIZE * 10]; + packet.data = data; + packet.len = FRAME_SIZE * ECHOTAILSIZE * 10; + + if (jitter.valid_bits) + { + /* Try decoding last received packet */ + ret = speex_decode_int(jitter.dec, jitter.current_packet, out); + if (ret == 0) + { + jitter_buffer_tick(jitter.packets); + return; + } else { + jitter.valid_bits = 0; + } + } + + if (current_timestamp) + ret = jitter_buffer_get(jitter.packets, &packet, jitter.frame_size, current_timestamp); + else + ret = jitter_buffer_get(jitter.packets, &packet, jitter.frame_size, NULL); + + if (ret != JITTER_BUFFER_OK) + { + /* No packet found */ + speex_decode_int(jitter.dec, NULL, out); + } else { + speex_bits_read_from(jitter.current_packet, packet.data, packet.len); + /* Decode packet */ + ret = speex_decode_int(jitter.dec, jitter.current_packet, out); + if (ret == 0) + { + jitter.valid_bits = 1; + } else { + /* Error while decoding */ + for (i=0;i + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define SAMPLING_RATE 16000 //must be the same as the speex setted mode (speex_wb_mode) +#define FRAME_SIZE 320 //must be the same as the speex setted mode (speex_wb_mode) +#define ECHOTAILSIZE 25 + +class SpeexBits; + +/** Speex jitter-buffer state. Never use it directly! */ +typedef struct SpeexJitter { + SpeexBits *current_packet; /**< Current Speex packet */ + int valid_bits; /**< True if Speex bits are valid */ + JitterBuffer *packets; /**< Generic jitter buffer state */ + void *dec; /**< Pointer to Speex decoder */ + spx_int32_t frame_size; /**< Frame size of Speex decoder */ + int mostUpdatedTSatPut; /**< timestamp of the last packet put */ + bool firsttimecalling_get; +} SpeexJitter; + +namespace QtSpeex { + class SpeexInputProcessor : public QIODevice { + Q_OBJECT + + public: + float dPeakSpeaker, dPeakSignal, dMaxMic, dPeakMic, dPeakCleanMic, dVoiceAcivityLevel; + float fSpeechProb; + SpeexInputProcessor(QObject* parent = 0); + virtual ~SpeexInputProcessor(); + + bool hasPendingPackets(); + QByteArray getNetworkPacket(); + + int iMaxBitRate; + int iRealTimeBitrate; + bool bPreviousVoice; + + public slots: + void addEchoFrame(QByteArray*); + + signals: + void networkPacketReady(); + + protected: + virtual qint64 readData(char *data, qint64 maxSize) {return false;} //not used for input processor + virtual qint64 writeData(const char *data, qint64 maxSize); + virtual bool isSequential() const; + + private: + QByteArray* lastEchoFrame; + int iSilentFrames; + int iHoldFrames; + + QMutex qmSpeex; + void* enc_state; + SpeexBits* enc_bits; + int send_timestamp; //set at the encode time for the jitter buffer of the reciever + + bool bResetProcessor; + + SpeexPreprocessState* preprocessor; + SpeexEchoState *echo_state; + short * psClean; //temp buffer for audio sampling after echo cleaning (if enabled) + + QByteArray inputBuffer; + QList outputNetworkBuffer; + }; + + + class SpeexOutputProcessor : public QIODevice { + Q_OBJECT + + public: + SpeexOutputProcessor(QObject* parent = 0); + virtual ~SpeexOutputProcessor(); + + void putNetworkPacket(QString name, QByteArray packet); + + protected: + virtual qint64 readData(char *data, qint64 maxSize); + virtual qint64 writeData(const char *data, qint64 maxSize) {return 0;} //not used for output processor + virtual bool isSequential() const; + + signals: + void playingFrame(QByteArray*); + private: + QByteArray outputBuffer; + QList inputNetworkBuffer; + + QHash userJitterHash; + + //SpeexJitter jitter; + + void speex_jitter_init(SpeexJitter *jit, void *decoder, int sampling_rate); + void speex_jitter_destroy(SpeexJitter jitter); + void speex_jitter_put(SpeexJitter jitter, char *packet, int len, int timestamp); + void speex_jitter_get(SpeexJitter jitter, spx_int16_t *out, int *current_timestamp); + int speex_jitter_get_pointer_timestamp(SpeexJitter jitter); + }; + } + +#endif // SPEEXPROCESSOR_H diff --git a/plugins/VOIP/VOIP.pro b/plugins/VOIP/VOIP.pro new file mode 100644 index 000000000..035a23689 --- /dev/null +++ b/plugins/VOIP/VOIP.pro @@ -0,0 +1,16 @@ +!include("../Common/retroshare_plugin.pri"): error("Could not include file ../Common/retroshare_plugin.pri") + +CONFIG += qt uic qrc resources +CONFIG += mobility +QT += multimedia +MOBILITY = multimedia + +SOURCES = AudioInputConfig.cpp AudioStats.cpp AudioWizard.cpp SpeexProcessor.cpp audiodevicehelper.cpp +HEADERS = AudioInputConfig.h AudioStats.h AudioWizard.h SpeexProcessor.h audiodevicehelper.h +FORMS = AudioInputConfig.ui AudioStats.ui AudioWizard.ui + +TARGET = VOIP + +RESOURCES = VOIP_images.qrc + +LIBS += -lspeex -lspeexdsp diff --git a/plugins/VOIP/VOIPPlugin.h b/plugins/VOIP/VOIPPlugin.h new file mode 100644 index 000000000..d78a40f37 --- /dev/null +++ b/plugins/VOIP/VOIPPlugin.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include "p3Voip.h" + +class VOIPPlugin: public RsPlugin +{ + public: + VOIPPlugin() ; + virtual ~VOIPPlugin() {} + + virtual RsPQIService *rs_pqi_service() const ; + virtual MainPage *qt_page() const ; + virtual QIcon *qt_icon() const ; + virtual uint16_t rs_service_id() const { return RS_SERVICE_TYPE_VOIP ; } + + virtual QTranslator *qt_translator(QApplication *app, const QString& languageCode) const; + + virtual void getPluginVersion(int& major,int& minor,int& svn_rev) const ; + virtual void setPlugInHandler(RsPluginHandler *pgHandler); + + virtual std::string configurationFileName() const { return "voip.cfg" ; } + + virtual std::string getShortPluginDescription() const ; + virtual std::string getPluginName() const; + virtual void setInterfaces(RsPlugInInterfaces& interfaces); + + private: + mutable p3Voip *mVoip ; + mutable RsPluginHandler *mPlugInHandler; + mutable RsPeers* mPeers; + mutable MainPage* mainpage ; + mutable QIcon* mIcon ; +}; + diff --git a/plugins/VOIP/VOIP_images.qrc b/plugins/VOIP/VOIP_images.qrc new file mode 100644 index 000000000..71e7ca5ae --- /dev/null +++ b/plugins/VOIP/VOIP_images.qrc @@ -0,0 +1,10 @@ + + + images/deafened_self.svg + images/muted_self.svg + images/self_undeafened.svg + images/talking_on.svg + images/talking_off.svg + + + diff --git a/plugins/VOIP/audiodevicehelper.cpp b/plugins/VOIP/audiodevicehelper.cpp new file mode 100644 index 000000000..2692943b5 --- /dev/null +++ b/plugins/VOIP/audiodevicehelper.cpp @@ -0,0 +1,76 @@ +#include "audiodevicehelper.h" +#include + +AudioDeviceHelper::AudioDeviceHelper() +{ +} + +QAudioInput* AudioDeviceHelper::getDefaultInputDevice() { + QAudioFormat fmt; + fmt.setFrequency(16000); + fmt.setChannels(1); + fmt.setSampleSize(16); + fmt.setSampleType(QAudioFormat::SignedInt); + fmt.setByteOrder(QAudioFormat::LittleEndian); + fmt.setCodec("audio/pcm"); + + QAudioDeviceInfo it, dev; + + dev = QAudioDeviceInfo::defaultInputDevice(); + if (dev.deviceName() != "pulse") { + foreach(it, QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) { + if(it.deviceName() == "pulse") { + dev = it; + qDebug("Ok."); + break; + } + } + } + if (dev.deviceName() == "null") { + foreach(it, QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) { + if(it.deviceName() != "null") { + dev = it; + break; + } + } + } + std::cerr << "input device : " << dev.deviceName().toStdString() << std::endl; + return new QAudioInput(dev, fmt); +} +QAudioInput* AudioDeviceHelper::getPreferedInputDevice() { + return AudioDeviceHelper::getDefaultInputDevice(); +} + +QAudioOutput* AudioDeviceHelper::getDefaultOutputDevice() { + QAudioFormat fmt; + fmt.setFrequency(16000); + fmt.setChannels(1); + fmt.setSampleSize(16); + fmt.setSampleType(QAudioFormat::SignedInt); + fmt.setByteOrder(QAudioFormat::LittleEndian); + fmt.setCodec("audio/pcm"); + + QAudioDeviceInfo it, dev; + dev = QAudioDeviceInfo::defaultOutputDevice(); + if (dev.deviceName() != "pulse") { + foreach(it, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) { + if(it.deviceName() == "pulse") { + dev = it; + break; + } + } + } + if (dev.deviceName() == "null") { + foreach(it, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) { + if(it.deviceName() != "null") { + dev = it; + break; + } + } + } + std::cerr << "output device : " << dev.deviceName().toStdString() << std::endl; + return new QAudioOutput(dev, fmt); +} +QAudioOutput* AudioDeviceHelper::getPreferedOutputDevice() { + return AudioDeviceHelper::getDefaultOutputDevice(); +} diff --git a/plugins/VOIP/audiodevicehelper.h b/plugins/VOIP/audiodevicehelper.h new file mode 100644 index 000000000..6b37d0b58 --- /dev/null +++ b/plugins/VOIP/audiodevicehelper.h @@ -0,0 +1,19 @@ +#ifndef AUDIODEVICEHELPER_H +#define AUDIODEVICEHELPER_H +#include +#include + +class AudioDeviceHelper +{ +public: + AudioDeviceHelper(); + static QAudioInput* getDefaultInputDevice(); + static QAudioInput* getPreferedInputDevice(); + //static list getInputDeviceList(); + + static QAudioOutput* getDefaultOutputDevice(); + static QAudioOutput* getPreferedOutputDevice(); + //static list getOutputDeviceList(); +}; + +#endif // AUDIODEVICEHELPER_H diff --git a/plugins/VOIP/images/deafened_self.svg b/plugins/VOIP/images/deafened_self.svg new file mode 100644 index 000000000..8623a357d --- /dev/null +++ b/plugins/VOIP/images/deafened_self.svg @@ -0,0 +1,573 @@ + + + + + deafened_self + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + deafened_self + + 2009.08.17 + + + Martin Skilnand + + + + + Martin Skilnand + + + + + Mumble team + + + + + mumble deafened self + + + Icon for voice chat program mumble + deafened_self.svg + git://mumble.git.sourceforge.net/gitroot/mumble + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/VOIP/images/muted_self.svg b/plugins/VOIP/images/muted_self.svg new file mode 100644 index 000000000..0a31bdc1e --- /dev/null +++ b/plugins/VOIP/images/muted_self.svg @@ -0,0 +1,1999 @@ + + + + + muted_self + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + muted_self + + + + Martin Skilnand + + + + + Martin Skilnand + + + 2009.08.17 + + + Mumble team + + + Icon for voice chat program mumble + + + mumble muted self + + + git://mumble.git.sourceforge.net/gitroot/mumble + muted_self.svg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/VOIP/images/self_undeafened.svg b/plugins/VOIP/images/self_undeafened.svg new file mode 100644 index 000000000..f2fd00fb6 --- /dev/null +++ b/plugins/VOIP/images/self_undeafened.svg @@ -0,0 +1,561 @@ + + + + + deafened_self + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + deafened_self + + 2009.08.17 + + + Martin Skilnand + + + + + Martin Skilnand + + + + + Mumble team + + + + + mumble deafened self + + + Icon for voice chat program mumble + deafened_self.svg + git://mumble.git.sourceforge.net/gitroot/mumble + + + + + + + + + + + + + + + + + + + diff --git a/plugins/VOIP/images/talking_off.svg b/plugins/VOIP/images/talking_off.svg new file mode 100644 index 000000000..2d2387e04 --- /dev/null +++ b/plugins/VOIP/images/talking_off.svg @@ -0,0 +1,1982 @@ + + + + + talking_off + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + talking_off + + + + Martin Skilnand + + + + + Martin Skilnand + + + 2009.08.17 + + + Mumble team + + + Icon for voice chat program mumble + + + mumble talking off + + + talking_off.svg + git://mumble.git.sourceforge.net/gitroot/mumble + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/VOIP/images/talking_on.svg b/plugins/VOIP/images/talking_on.svg new file mode 100644 index 000000000..60b5e04ce --- /dev/null +++ b/plugins/VOIP/images/talking_on.svg @@ -0,0 +1,1988 @@ + + + + + talking_on + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + talking_on + + + + Martin Skilnand + + + + + Martin Skilnand + + + 2009.08.17 + + + Mumble team + + + Icon for voice chat program mumble + + + mumble talking on + + + git://mumble.git.sourceforge.net/gitroot/mumble + talking_on.svg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/VOIP/p3Voip.h b/plugins/VOIP/p3Voip.h new file mode 100644 index 000000000..c9f6a872c --- /dev/null +++ b/plugins/VOIP/p3Voip.h @@ -0,0 +1,76 @@ +/* + * Services for RetroShare. + * + * Copyright 2011-2012 by Cyril Soler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License Version 2 as published by the Free Software Foundation. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA. + * + * Please report all bugs and problems to "retroshare@lunamutt.com". + * + */ + + +#pragma once + +#include +#include +#include + +#include "serialiser/rsmsgitems.h" +#include "services/p3service.h" +#include "retroshare/rsmsgs.h" + +//!The basic VOIP service. + +class p3VoipService: public RsPQIService, public RsVoip +{ + public: + p3VoipService() : RsPQIService(RS_SERVICE_TYPE_VOIP) {} + + /***** overloaded from p3Service *****/ + /*! + * This retrieves all chat msg items and also (important!) + * processes chat-status items that are in service item queue. chat msg item requests are also processed and not returned + * (important! also) notifications sent to notify base on receipt avatar, immediate status and custom status + * : notifyCustomState, notifyChatStatus, notifyPeerHasNewAvatar + * @see NotifyBase + */ + virtual int tick(); + virtual int status(); + + /*************** pqiMonitor callback ***********************/ + virtual void statusChange(const std::list &plist); + + /*! + * public chat sent to all peers + */ + int sendVoipData(const void *data,uint32_t size); + + protected: + /************* from p3Config *******************/ + virtual RsSerialiser *setupSerialiser() ; + + /*! + * chat msg items and custom status are saved + */ + virtual bool saveList(bool& cleanup, std::list&) ; + virtual bool loadList(std::list& load) ; + + private: + //RsMutex mChatMtx; + + void receiveVoipQueue(); +}; + diff --git a/plugins/VOIP/rsvoipitems.h b/plugins/VOIP/rsvoipitems.h new file mode 100644 index 000000000..9570b2b17 --- /dev/null +++ b/plugins/VOIP/rsvoipitems.h @@ -0,0 +1,18 @@ +#pragma once + +const uint8_t RS_VOIP_SUBTYPE_RINGING = 0x01 ; +const uint8_t RS_VOIP_SUBTYPE_ACKNOWL = 0x02 ; +const uint8_t RS_VOIP_SUBTYPE_DATA = 0x03 ; + +class RsVoipItem: public RsItem +{ + public: + RsVoipItem(uint8_t turtle_subtype) : RsItem(RS_PKT_VERSION_SERVICE,RS_SERVICE_TYPE_VOIP,voip_subtype) {} + + virtual bool serialize(void *data,uint32_t& size) = 0 ; // Isn't it better that items can serialize themselves ? + virtual uint32_t serial_size() = 0 ; // deserialise is handled using a constructor + + virtual void clear() {} +}; + +// to derive: hanshake items, data items etc.