RetroShare/plugins/VOIP/gui/VOIPConfigPanel.cpp

474 lines
14 KiB
C++

/*******************************************************************************
* plugins/VOIP/gui/AudioInputConfig.h *
* *
* Copyright (C) 2008, Andreas Messer <andi@bupfen.de> *
* Copyright (C) 2005-2010 Thorvald Natvig <thorvald@natvig.com> *
* Copyright (C) 2012 by Retroshare Team <retroshare.project@gmail.com> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include "AudioStats.h"
#include "VOIPConfigPanel.h"
#include "audiodevicehelper.h"
#include "AudioWizard.h"
#include "gui/VideoProcessor.h"
#include "gui/VideoProcessor.h"
#include "util/misc.h"
#include "util/RsProtectedTimer.h"
#include <interface/rsVOIP.h>
#define iroundf(x) ( static_cast<int>(x) )
class voipGraphSource: public RSGraphSource
{
public:
voipGraphSource() : video_input(NULL) {}
void setVideoInput(const QVideoInputDevice *vid) { video_input = vid ; }
virtual QString displayName(int) const { return tr("Required bandwidth") ;}
virtual QString displayValue(float v) const
{
if(v < 1000)
return QString::number(v,10,2) + " B/s" ;
else if(v < 1000*1024)
return QString::number(v/1024,10,2) + " KB/s" ;
else
return QString::number(v/(1024*1024),10,2) + " MB/s" ;
}
virtual void getValues(std::map<std::string,float>& vals) const
{
vals.clear() ;
if(video_input)
vals[std::string("bw")] = video_input->currentBandwidth() ;
}
private:
const QVideoInputDevice *video_input ;
};
void voipGraph::setVoipSource(voipGraphSource *gs)
{
_src = gs ;
RSGraphWidget::setSource(gs) ;
}
voipGraph::voipGraph(QWidget *parent)
: RSGraphWidget(parent)
{
setFlags(RSGraphWidget::RSGRAPH_FLAGS_SHOW_LEGEND) ;
setFlags(RSGraphWidget::RSGRAPH_FLAGS_PAINT_STYLE_PLAIN) ;
_src = NULL ;
}
/** Constructor */
VOIPConfigPanel::VOIPConfigPanel(QWidget * parent, Qt::WindowFlags flags)
: ConfigPage(parent, flags)
{
std::cerr << "Creating audioInputConfig object" << std::endl;
/* Invoke the Qt Designer generated object setup routine */
ui.setupUi(this);
loaded = false;
inputAudioProcessor = NULL;
inputAudioDevice = NULL;
graph_source = nullptr;
videoInput = nullptr;
videoProcessor = nullptr;
qtTick = NULL;
ui.qcbTransmit->addItem(tr("Continuous"), RsVOIP::AudioTransmitContinous);
ui.qcbTransmit->addItem(tr("Voice Activity"), RsVOIP::AudioTransmitVAD);
ui.qcbTransmit->addItem(tr("Push To Talk"), RsVOIP::AudioTransmitPushToTalk);
ui.abSpeech->qcBelow = Qt::red;
ui.abSpeech->qcInside = Qt::yellow;
ui.abSpeech->qcAbove = Qt::green;
QList<QString> input_devices;
QVideoInputDevice::getAvailableDevices(input_devices);
ui.inputDevice_CB->clear();
ui.inputDevice_CB->addItem(tr("[No video]"),QString(""));
for(auto& s:input_devices)
ui.inputDevice_CB->addItem(s,QVariant(s));
if(!input_devices.empty())
whileBlocking(ui.inputDevice_CB)->setCurrentIndex(1); // select default cam
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) ) );
connect( ui.inputDevice_CB, SIGNAL( currentIndexChanged ( int ) ), this, SLOT( on_changedCurrentInputDevice(int) ) );
}
void VOIPConfigPanel::showEvent(QShowEvent *)
{
std::cerr << "Creating the audio pipeline" << std::endl;
inputAudioProcessor = new QtSpeex::SpeexInputProcessor();
inputAudioProcessor->open(QIODevice::WriteOnly | QIODevice::Unbuffered);
inputAudioDevice = AudioDeviceHelper::getPreferedInputDevice();
inputAudioDevice->start(inputAudioProcessor);
connect(inputAudioProcessor, SIGNAL(networkPacketReady()), this, SLOT(emptyBuffer()));
std::cerr << "Creating the video pipeline" << std::endl;
// Create the video pipeline.
//
videoInput = new QVideoInputDevice(this) ;
videoProcessor = new VideoProcessor() ;
videoProcessor->setDisplayTarget(NULL) ;
videoProcessor->setMaximumBandwidth(ui.availableBW_SB->value()) ;
videoInput->setVideoProcessor(videoProcessor) ;
graph_source = new voipGraphSource ;
ui.voipBwGraph->setSource(graph_source);
graph_source->setVideoInput(videoInput) ;
graph_source->setCollectionTimeLimit(1000*300) ;
graph_source->start() ;
if(ui.showEncoded_CB->isChecked())
{
videoInput->setEchoVideoTarget(nullptr) ;
videoProcessor->setDisplayTarget(ui.videoDisplay) ;
}
else
{
videoInput->setEchoVideoTarget(ui.videoDisplay) ;
videoProcessor->setDisplayTarget(nullptr);
}
QObject::connect(ui.showEncoded_CB,SIGNAL(toggled(bool)),this,SLOT(togglePreview(bool))) ;
QObject::connect(ui.availableBW_SB,SIGNAL(valueChanged(double)),this,SLOT(updateAvailableBW(double))) ;
loadSettings();
qtTick = new RsProtectedTimer(this);
connect( qtTick, SIGNAL( timeout ( ) ), this, SLOT( on_Tick_timeout() ) );
qtTick->start(20);
videoInput->start();
}
void VOIPConfigPanel::hideEvent(QHideEvent *)
{
std::cerr << "Deleting the video pipeline" << std::endl;
clearPipeline();
}
void VOIPConfigPanel::updateAvailableBW(double r)
{
std::cerr << "Setting max bandwidth to " << r << " KB/s" << std::endl;
videoProcessor->setMaximumBandwidth((uint32_t)(r*1024)) ;
}
void VOIPConfigPanel::togglePreview(bool b)
{
if(b)
{
videoInput->setEchoVideoTarget(NULL) ;
videoProcessor->setDisplayTarget(ui.videoDisplay) ;
}
else
{
videoProcessor->setDisplayTarget(NULL) ;
videoInput->setEchoVideoTarget(ui.videoDisplay) ;
}
}
VOIPConfigPanel::~VOIPConfigPanel()
{
clearPipeline();
}
void VOIPConfigPanel::clearPipeline()
{
if (qtTick) {
delete qtTick;
qtTick = nullptr;
}
if (graph_source) {
graph_source->stop() ;
graph_source->setVideoInput(NULL) ;
graph_source=nullptr; // is deleted by setSource below. This is a bad design.
}
ui.voipBwGraph->setSource(nullptr);
std::cerr << "Deleting audioInputConfig object" << std::endl;
if(videoInput != NULL)
{
videoInput->stop() ;
delete videoInput ;
videoInput = nullptr;
}
if (videoProcessor) {
delete videoProcessor;
videoProcessor = nullptr;
}
if (inputAudioDevice) {
inputAudioDevice->stop();
delete inputAudioDevice ;
inputAudioDevice = nullptr ;
}
if(inputAudioProcessor)
{
delete inputAudioProcessor ;
inputAudioProcessor = nullptr ;
}
}
void VOIPConfigPanel::load()
{
}
/** Loads the settings for this page */
void VOIPConfigPanel::loadSettings()
{
ui.qcbTransmit->setCurrentIndex(rsVOIP->getVoipATransmit());
on_qcbTransmit_currentIndexChanged(rsVOIP->getVoipATransmit());
ui.qsTransmitHold->setValue(rsVOIP->getVoipVoiceHold());
on_qsTransmitHold_valueChanged(rsVOIP->getVoipVoiceHold());
ui.qsTransmitMin->setValue(rsVOIP->getVoipfVADmin());
ui.qsTransmitMax->setValue(rsVOIP->getVoipfVADmax());
ui.qcbEchoCancel->setChecked(rsVOIP->getVoipEchoCancel());
if (rsVOIP->getVoipiNoiseSuppress() != 0)
ui.qsNoise->setValue(-rsVOIP->getVoipiNoiseSuppress());
else
ui.qsNoise->setValue(14);
on_qsNoise_valueChanged(-rsVOIP->getVoipiNoiseSuppress());
ui.qsAmp->setValue(20000 - rsVOIP->getVoipiMinLoudness());
on_qsAmp_valueChanged(20000 - rsVOIP->getVoipiMinLoudness());
loaded = true;
}
bool VOIPConfigPanel::save(QString &/*errmsg*/)
{
//mainly useless beacause saving occurs in realtime
//s.iQuality = qsQuality->value();
rsVOIP->setVoipiNoiseSuppress((ui.qsNoise->value() == 14) ? 0 : - ui.qsNoise->value());
rsVOIP->setVoipiMinLoudness(20000 - ui.qsAmp->value());
rsVOIP->setVoipVoiceHold(ui.qsTransmitHold->value());
rsVOIP->setVoipfVADmin(ui.qsTransmitMin->value());
rsVOIP->setVoipfVADmax(ui.qsTransmitMax->value());
/*s.uiDoublePush = qsDoublePush->value() * 1000;*/
rsVOIP->setVoipATransmit(static_cast<RsVOIP::enumAudioTransmit>(ui.qcbTransmit->currentIndex() ));
rsVOIP->setVoipEchoCancel(ui.qcbEchoCancel->isChecked());
return true;
}
void VOIPConfigPanel::on_qsTransmitHold_valueChanged(int v) {
float val = static_cast<float>(v * FRAME_SIZE);
val = val / SAMPLING_RATE;
ui.qlTransmitHold->setText(tr("%1 s").arg(val, 0, 'f', 2));
rsVOIP->setVoipVoiceHold(v);
}
void VOIPConfigPanel::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);
rsVOIP->setVoipiNoiseSuppress(- ui.qsNoise->value());
}
void VOIPConfigPanel::on_qsAmp_valueChanged(int v) {
v = 20000 - v;
float d = 20000.0f/static_cast<float>(v);
ui.qlAmp->setText(QString::fromLatin1("%1").arg(d, 0, 'f', 2));
rsVOIP->setVoipiMinLoudness(20000 - ui.qsAmp->value());
}
void VOIPConfigPanel::on_qcbEchoCancel_clicked() {
rsVOIP->setVoipEchoCancel(ui.qcbEchoCancel->isChecked());
}
void VOIPConfigPanel::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)
rsVOIP->setVoipATransmit(static_cast<RsVOIP::enumAudioTransmit>(ui.qcbTransmit->currentIndex() ));
}
void VOIPConfigPanel::on_Tick_timeout()
{
// update the sound capture bar
ui.abSpeech->iBelow = ui.qsTransmitMin->value();
ui.abSpeech->iAbove = ui.qsTransmitMax->value();
if (loaded) {
rsVOIP->setVoipfVADmin(ui.qsTransmitMin->value());
rsVOIP->setVoipfVADmax(ui.qsTransmitMax->value());
}
ui.abSpeech->iValue = iroundf(inputAudioProcessor->dVoiceAcivityLevel * 32767.0f + 0.5f);
ui.abSpeech->update();
// also transmit encoded video
RsVOIPDataChunk chunk ;
while((!videoInput->stopped()) && videoInput->getNextEncodedPacket(chunk))
{
videoProcessor->receiveEncodedData(chunk) ;
chunk.clear() ;
}
}
void VOIPConfigPanel::emptyBuffer() {
while(inputAudioProcessor->hasPendingPackets()) {
inputAudioProcessor->getNetworkPacket(); //that will purge the buffer
}
}
void VOIPConfigPanel::on_qpbAudioWizard_clicked() {
AudioWizard aw(this);
aw.exec();
loadSettings();
}
void VOIPConfigPanel::on_changedCurrentInputDevice(int i)
{
QString s = dynamic_cast<RSComboBox*>(sender())->itemData(i).toString();
videoInput->stop();
// check that the camera still exists
QList<QString> input_devices;
QVideoInputDevice::getAvailableDevices(input_devices);
for(const QString& cams:input_devices)
if(s == cams)
{
std::cerr << "Switching to camera \"" << s.toStdString() << "\"" << std::endl;
videoInput->start(s);
return;
}
// if not, re-create the ComboBox
checkAvailableCameras();
}
void VOIPConfigPanel::checkAvailableCameras()
{
// save current camera
QString current_cam = videoInput->currentCameraDescriptionString();
// Check that the list of cams we had previously is the same than the list of available camera.
QList<QString> input_devices;
QVideoInputDevice::getAvailableDevices(input_devices);
bool same = true;
if(input_devices.size() != ui.inputDevice_CB->count())
same = false;
if(same)
{
int n=0;
for(auto& s:input_devices)
{
if(ui.inputDevice_CB->itemData(n).toString() != s)
{
same = false;
break;
}
n++;
}
}
// If not, re-add them to the comboBox and make sure to re-select the same camera.
if(!same)
{
whileBlocking(ui.inputDevice_CB)->clear(); // remove existing entries
whileBlocking(ui.inputDevice_CB)->addItem(tr("[No video]"),QString(""));
int n=0;
int found_index = -1;
for(auto& s:input_devices)
{
whileBlocking(ui.inputDevice_CB)->addItem(s,QVariant(s));
if(s == current_cam)
found_index = n;
n++;
}
if(found_index >= 0)
ui.inputDevice_CB->setCurrentIndex(found_index);
else
ui.inputDevice_CB->setCurrentIndex(0); // no video
}
}