From 08dddef861bda0b0bd8667c825d1a6bf22ff009a Mon Sep 17 00:00:00 2001 From: Ray Zhang <41682958+zhang-ray@users.noreply.github.com> Date: Thu, 1 Nov 2018 20:35:55 +0800 Subject: [PATCH] [UI] brand new UI! --- client_essential/Worker.cpp | 24 +- client_qt5/CMakeLists.txt | 9 +- client_qt5/mainwindow.cpp | 256 +++++-- client_qt5/mainwindow.hpp | 46 +- client_qt5/mainwindow.ui | 716 ++++++++++-------- client_qt5/resources/Resource.qrc | 9 + client_qt5/resources/microphone.png | Bin 0 -> 1802 bytes client_qt5/resources/speaker.png | Bin 0 -> 19857 bytes client_qt5/resources/vertical_bar_empty.png | Bin 0 -> 2883 bytes client_qt5/resources/vertical_bar_full.png | Bin 0 -> 2883 bytes .../resources/vertical_bar_half_full.png | Bin 0 -> 2883 bytes include/evc/AudioVolume.hpp | 6 +- include/evc/Worker.hpp | 34 +- misc/resources/vertical_bar_empty.svg | 66 ++ misc/resources/vertical_bar_full.svg | 66 ++ misc/resources/vertical_bar_half_full.svg | 66 ++ server/BoostAsioTcpServer.cpp | 5 +- 17 files changed, 914 insertions(+), 389 deletions(-) create mode 100644 client_qt5/resources/Resource.qrc create mode 100644 client_qt5/resources/microphone.png create mode 100644 client_qt5/resources/speaker.png create mode 100644 client_qt5/resources/vertical_bar_empty.png create mode 100644 client_qt5/resources/vertical_bar_full.png create mode 100644 client_qt5/resources/vertical_bar_half_full.png create mode 100644 misc/resources/vertical_bar_empty.svg create mode 100644 misc/resources/vertical_bar_full.svg create mode 100644 misc/resources/vertical_bar_half_full.svg diff --git a/client_essential/Worker.cpp b/client_essential/Worker.cpp index 6ea1a6d..9ae77d6 100644 --- a/client_essential/Worker.cpp +++ b/client_essential/Worker.cpp @@ -20,7 +20,7 @@ decltype(WebRtcNsx_Create()) ns_ = nullptr; using namespace webrtc; -const char *constPort ="1222"; +//const char *constPort ="80"; Worker::Worker(bool needAec) :needAec_(needAec) @@ -82,8 +82,7 @@ bool Worker::initCodec(){ } bool Worker::initDevice(std::function reportInfo, - std::function reportMicVolume, - std::function reportSpkVolume, + std::function reportVolume, std::function vadReporter){ device_ = &(Factory::get().create()); @@ -91,20 +90,19 @@ bool Worker::initDevice(std::functioninit(micInfo, spkInfo)){ reportInfo(micInfo, spkInfo); - micVolumeReporter_ = reportMicVolume; - spkVolumeReporter_ = reportSpkVolume; + volumeReporter_ = reportVolume; vadReporter_ = vadReporter; return true; } return false; } -void Worker::asyncStart(const std::string &host, std::function toggleState){ +void Worker::asyncStart(const std::string &host,const std::string &port, std::function toggleState){ syncStop(); - netThread_.reset(new std::thread(std::bind(&Worker::syncStart, this, host, toggleState))); + netThread_.reset(new std::thread(std::bind(&Worker::syncStart, this, host, port, toggleState))); } -void Worker::syncStart(const std::string &host, +void Worker::syncStart(const std::string &host,const std::string &port, std::function toggleState ){ @@ -114,7 +112,7 @@ void Worker::syncStart(const std::string &host, bool isLogin = false; TcpClient client( host.c_str(), - constPort, + port.c_str(), [&](TcpClient *_TcpClient, const NetPacket& netPacket){ // on Received Data switch (netPacket.payloadType()){ @@ -147,9 +145,9 @@ void Worker::syncStart(const std::string &host, } } auto ret = device_->write(decodedPcm); - if (spkVolumeReporter_){ + if (volumeReporter_){ static SuckAudioVolume sav; - spkVolumeReporter_(sav.calculate(decodedPcm)); + volumeReporter_({AudioInOut::Out, sav.calculate(decodedPcm, AudioIoVolume::MAX_VOLUME_LEVEL)}); } if (!ret) { std::cout << ret.message() << std::endl; @@ -251,9 +249,9 @@ void Worker::syncStart(const std::string &host, - if (micVolumeReporter_){ + if (volumeReporter_){ static SuckAudioVolume sav; - micVolumeReporter_(sav.calculate(denoisedBuffer)); + volumeReporter_({AudioInOut::In, sav.calculate(denoisedBuffer, AudioIoVolume::MAX_VOLUME_LEVEL)}); } auto haveVoice = (1==WebRtcVad_Process(vad, sampleRate, denoisedBuffer.data(), sampleRate/100)); diff --git a/client_qt5/CMakeLists.txt b/client_qt5/CMakeLists.txt index f80578c..796d077 100644 --- a/client_qt5/CMakeLists.txt +++ b/client_qt5/CMakeLists.txt @@ -49,14 +49,19 @@ set(SRCS mainwindow.ui mainwindow.cpp main.cpp + ) +# QRC files +set(QRC_SOURCE_FILES resources/Resource.qrc) +qt5_add_resources(QRC_FILES ${QRC_SOURCE_FILES}) + if(WIN32) - add_executable(${PROJECT_NAME} WIN32 ${SRCS} resources/Resource.rc) + add_executable(${PROJECT_NAME} WIN32 ${SRCS} ${QRC_FILES} resources/Resource.rc) else(WIN32) - add_executable(${PROJECT_NAME} ${SRCS}) + add_executable(${PROJECT_NAME} ${SRCS} ${QRC_FILES}) endif(WIN32) target_link_libraries( diff --git a/client_qt5/mainwindow.cpp b/client_qt5/mainwindow.cpp index f65e0d5..6f228a7 100644 --- a/client_qt5/mainwindow.cpp +++ b/client_qt5/mainwindow.cpp @@ -12,15 +12,21 @@ #include #include #include +#include +#include -/// TODO: -/// - display VAD result in real-time +QEvent::Type AudioVolumeEvent::sType = (QEvent::Type)QEvent::registerEventType(); +QEvent::Type VadEvent::sType = (QEvent::Type)QEvent::registerEventType(); -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) + +MainWindow::MainWindow(QWidget *parent) + :QMainWindow(parent) + ,ui(new Ui::MainWindow) + ,vertical_bar_full(":/vertical_bar_full.png") + ,vertical_bar_empty(":/vertical_bar_empty.png") + ,vertical_bar_half_full(":/vertical_bar_half_full.png") { ui->setupUi(this); @@ -47,20 +53,94 @@ MainWindow::MainWindow(QWidget *parent) : - // init UI + /// Init UI { - // -> center of screen - setGeometry(QStyle::alignedRect( - Qt::LeftToRight, - Qt::AlignCenter, - size(), - qApp->desktop()->availableGeometry() - ) - ); - setFixedSize(width(), height()); - updateUiState(NetworkState::Disconnected); + /// -> center of screen, and fix window Size + { + setGeometry(QStyle::alignedRect( + Qt::LeftToRight, + Qt::AlignCenter, + size(), + qApp->desktop()->availableGeometry() + ) + ); + setFixedSize(width(), height()); + + } + + + + /// insert images to qlabels + { + ui->label_img_ain->setPixmap(QPixmap(":/microphone.png")); + ui->label_img_ain->setScaledContents(true); + + ui->label_img_aout->setPixmap(QPixmap(":/speaker.png")); + ui->label_img_aout->setScaledContents(true); + } + + + + /// FIX ME.... ugly enough + { + { + auto type = 0; + auto index = 0; + label_img_[type][index++]=ui->label_img_ain_bar0; + label_img_[type][index++]=ui->label_img_ain_bar1; + label_img_[type][index++]=ui->label_img_ain_bar2; + label_img_[type][index++]=ui->label_img_ain_bar3; + label_img_[type][index++]=ui->label_img_ain_bar4; + label_img_[type][index++]=ui->label_img_ain_bar5; + label_img_[type][index++]=ui->label_img_ain_bar6; + label_img_[type][index++]=ui->label_img_ain_bar7; + label_img_[type][index++]=ui->label_img_ain_bar8; + label_img_[type][index++]=ui->label_img_ain_bar9; + } + + { + auto type = 1; + auto index = 0; + label_img_[type][index++]=ui->label_img_aout_bar0; + label_img_[type][index++]=ui->label_img_aout_bar1; + label_img_[type][index++]=ui->label_img_aout_bar2; + label_img_[type][index++]=ui->label_img_aout_bar3; + label_img_[type][index++]=ui->label_img_aout_bar4; + label_img_[type][index++]=ui->label_img_aout_bar5; + label_img_[type][index++]=ui->label_img_aout_bar6; + label_img_[type][index++]=ui->label_img_aout_bar7; + label_img_[type][index++]=ui->label_img_aout_bar8; + label_img_[type][index++]=ui->label_img_aout_bar9; + } + for (int i = 0; i setScaledContents(true); + label_img_[1][i]->setScaledContents(true); + } + } + + - connect(ui->lineEdit_serverHost, SIGNAL(returnPressed()), ui->pushButton_connecting, SLOT(click())); + /// tool tip + { + ui->lineEdit_serverHost->setToolTip("example: your.server.com\n or: 12.45.67.89"); + } + + + { + connect(ui->lineEdit_serverHost, SIGNAL(returnPressed()), ui->pushButton_connecting, SLOT(click())); + connect(ui->lineEdit_serverPort, SIGNAL(returnPressed()), ui->pushButton_connecting, SLOT(click())); + } + + + + /// init state + { + onVolumeChanged({AudioInOut::In, 0u}); + onVolumeChanged({AudioInOut::Out, 0u}); + updateUiState(NetworkState::Disconnected); + toggleAdvancedMode(true); + showMessage("F1: help F2: advanced mode"); + } } } @@ -90,49 +170,7 @@ MainWindow::~MainWindow() void MainWindow::on_pushButton_connecting_clicked(){ switch (currentUiState_){ case NetworkState::Disconnected: { - { - updateUiState(NetworkState::Connecting); - - - worker_ = std::make_shared(ui->checkBox_needAec->checkState()==Qt::CheckState::Checked); - - try { - if (!worker_->initCodec()){ - throw "worker_.initCodec failed"; - } - if (!worker_->initDevice( - [this](const std::string &micInfo, - const std::string &spkInfo){ - ui->label_micInfo->setText(micInfo.c_str()); - ui->label_spkInfo->setText(spkInfo.c_str()); - },[this](const uint8_t newVolume){ - ui->label_volumeMic->setText(std::to_string(newVolume).c_str()); - }, [this](const uint8_t newVolume){ - ui->label_volumeSpk->setText(std::to_string(newVolume).c_str()); - }, [this] (const bool isActive){ - ui->label_vad->setText(isActive? "active" : "inactive"); - } - )){ - throw "worker_.initDevice failed"; - } - } - catch (std::exception &e){ - qDebug() << "Exception: " << e.what() << "\n"; - } - - - worker_->asyncStart( - ui->lineEdit_serverHost->text().toStdString(), - [this]( - const NetworkState newState, - const std::string extraMessage - ) - { - showMessage(extraMessage); - updateUiState(newState); - } - ); - } + gotoWork(); break; } case NetworkState::Connected:{ @@ -152,6 +190,7 @@ void MainWindow::updateUiState(const NetworkState networkState){ switch(networkState){ case NetworkState::Disconnected:{ ui->lineEdit_serverHost->setEnabled(true); + ui->lineEdit_serverPort->setEnabled(true); ui->checkBox_needAec->setEnabled(true); ui->pushButton_connecting->setEnabled(true); ui->pushButton_connecting->setText("Connect!"); @@ -160,6 +199,7 @@ void MainWindow::updateUiState(const NetworkState networkState){ } case NetworkState::Connecting:{ ui->lineEdit_serverHost->setEnabled(false); + ui->lineEdit_serverPort->setEnabled(false); ui->checkBox_needAec->setEnabled(false); ui->pushButton_connecting->setEnabled(false); ui->pushButton_connecting->setText("Connecting..."); @@ -167,6 +207,7 @@ void MainWindow::updateUiState(const NetworkState networkState){ } case NetworkState::Connected:{ ui->lineEdit_serverHost->setEnabled(false); + ui->lineEdit_serverPort->setEnabled(false); ui->checkBox_needAec->setEnabled(false); ui->pushButton_connecting->setEnabled(true); ui->pushButton_connecting->setText("Disconnect!"); @@ -178,13 +219,81 @@ void MainWindow::updateUiState(const NetworkState networkState){ currentUiState_=networkState; } +void MainWindow::onVolumeChanged(const AudioIoVolume aivl) { + if(mainThreadId_ == std::this_thread::get_id()){ + for (int i =0;i< AudioIoVolume::MAX_VOLUME_LEVEL;i++){ + label_img_[(uint8_t)aivl.io_][i]->setPixmap(ilabel_vad->setText(isActive? "active" : "inactive"); + } + else{ + QCoreApplication::postEvent(this, new VadEvent(isActive)); + } +} + +void MainWindow::toggleAdvancedMode(bool newMode) +{ + advancedMode_ = newMode; + ui->groupBox_details->setVisible(advancedMode_); + ui->lineEdit_serverPort->setVisible(advancedMode_); + setFixedSize(advancedMode_?600:300,400); +} + +void MainWindow::gotoWork(){ + try { + updateUiState(NetworkState::Connecting); + + + worker_ = std::make_shared(ui->checkBox_needAec->checkState()==Qt::CheckState::Checked); + + if (!worker_->initCodec()){ + throw "worker_.initCodec failed"; + } + + if (!worker_->initDevice( + std::bind(&MainWindow::onDeviceNameChanged, this, std::placeholders::_1,std::placeholders::_2), + std::bind(&MainWindow::onVolumeChanged, this, std::placeholders::_1), + std::bind(&MainWindow::onVad, this, std::placeholders::_1) + )){ + throw "worker_.initDevice failed"; + } + + + worker_->asyncStart( + ui->lineEdit_serverHost->text().toStdString(), + ui->lineEdit_serverPort->text().toStdString(), + [this]( + const NetworkState newState, + const std::string extraMessage + ) + { + showMessage(extraMessage); + updateUiState(newState); + } + ); + + } + catch (std::exception &e){ + qDebug() << "Exception: " << e.what() << "\n"; + } + +} + void MainWindow::showMessage(const std::string &message){ try { if (message.empty()){ ui->statusBar->clearMessage(); } else { - ui->statusBar->showMessage(message.c_str()); // TODO: thread-unsafety? + ui->statusBar->showMessage(message.c_str()); qDebug() << message.c_str(); } } @@ -192,3 +301,28 @@ void MainWindow::showMessage(const std::string &message){ qDebug() << e.what(); } } + +bool MainWindow::event(QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(event); + if (ke->key() == Qt::Key_F1){ + QDesktopServices::openUrl(QUrl("https://github.com/zhang-ray/easy-voice-call")); + } + else if (ke->key() == Qt::Key_F2){ + toggleAdvancedMode(); + } + } else if (event->type() == AudioVolumeEvent::sType) { + AudioVolumeEvent *myEvent = static_cast(event); + onVolumeChanged({myEvent->io_, myEvent->level_}); + return true; + } + else if (event->type() == VadEvent::sType){ + VadEvent *myEvent = static_cast(event); + onVad(myEvent->isActive_); + return true; + } + + return QWidget::event(event); +} + diff --git a/client_qt5/mainwindow.hpp b/client_qt5/mainwindow.hpp index acda25c..bfc54a9 100644 --- a/client_qt5/mainwindow.hpp +++ b/client_qt5/mainwindow.hpp @@ -4,16 +4,39 @@ #include #include #include "evc/Worker.hpp" - - +#include +#include namespace Ui { class MainWindow; } +class AudioVolumeEvent : public QEvent, public AudioIoVolume { +public: + // char test[1<<20]; //for memory test + static QEvent::Type sType; + AudioVolumeEvent(const AudioIoVolume aiv) + : QEvent(AudioVolumeEvent::sType) + , AudioIoVolume(aiv) + { + } + + AudioVolumeEvent(const AudioInOut io, const uint8_t level) + : QEvent(sType) + , AudioIoVolume(io, level) + { + } +}; +class VadEvent : public QEvent { +private: +public: + bool isActive_; + static QEvent::Type sType; + VadEvent(const bool isActive):isActive_(isActive), QEvent(sType){} +}; class MainWindow : public QMainWindow { @@ -27,6 +50,14 @@ private slots: void on_pushButton_connecting_clicked(); void updateUiState(const NetworkState networkState); + + /// TODO: render max volume bar + void onVolumeChanged(const AudioIoVolume); + void onDeviceNameChanged(const std::string &newMic, const std::string &newSpk){} + void onVad(bool isActive); + void toggleAdvancedMode(){toggleAdvancedMode(!advancedMode_);} + void toggleAdvancedMode(bool newMode); + void gotoWork(); private: Ui::MainWindow *ui = nullptr; @@ -37,5 +68,16 @@ private slots: private: void showMessage(const std::string &message); + QPixmap vertical_bar_empty; + QPixmap vertical_bar_full; + QPixmap vertical_bar_half_full; + QLabel* label_img_[2][AudioIoVolume::MAX_VOLUME_LEVEL]; + bool advancedMode_ = true; + + std::thread::id mainThreadId_ = std::this_thread::get_id(); + + // QObject interface +public: + virtual bool event(QEvent *event) override; }; diff --git a/client_qt5/mainwindow.ui b/client_qt5/mainwindow.ui index 0d63cc0..2ae1adb 100644 --- a/client_qt5/mainwindow.ui +++ b/client_qt5/mainwindow.ui @@ -6,10 +6,16 @@ 0 0 - 403 - 244 + 600 + 400 + + + 0 + 0 + + 12 @@ -19,335 +25,449 @@ Easy Voice Call - + 20 - 89 - 91 - 21 + 20 + 250 + 60 - - - 18 - - - - Server - - - - - - 30 - 116 - 161 - 25 - - - - - 14 - - - - 127.0.0.1 - - - - - - 220 - 90 - 171 - 61 - - - - - 14 - - - - Connect! - - - - - - 190 - 8 - 191 - 20 - - - - - 14 - 75 - true - false - - - - true - - - - - - Qt::AlignCenter - + + audio-in + + + + + 10 + 30 + 20 + 20 + + + + + + + + + + 50 + 30 + 10 + 20 + + + + + + + + + + 65 + 30 + 10 + 20 + + + + + + + + + + 80 + 30 + 10 + 20 + + + + + + + + + + 95 + 30 + 10 + 20 + + + + + + + + + + 110 + 30 + 10 + 20 + + + + + + + + + + 125 + 30 + 10 + 20 + + + + + + + + + + 140 + 30 + 10 + 20 + + + + + + + + + + 155 + 30 + 10 + 20 + + + + + + + + + + 170 + 30 + 10 + 20 + + + + + + + + + + 185 + 30 + 10 + 20 + + + + + + - + 20 - 8 - 71 - 20 + 100 + 250 + 60 - - - 14 - - - - Mic - + + audio-out + + + + + 10 + 30 + 20 + 20 + + + + + + + + + + 50 + 30 + 10 + 20 + + + + + + + + + + 80 + 30 + 10 + 20 + + + + + + + + + + 95 + 30 + 10 + 20 + + + + + + + + + + 110 + 30 + 10 + 20 + + + + + + + + + + 125 + 30 + 10 + 20 + + + + + + + + + + 140 + 30 + 10 + 20 + + + + + + + + + + 155 + 30 + 10 + 20 + + + + + + + + + + 170 + 30 + 10 + 20 + + + + + + + + + + 65 + 30 + 10 + 20 + + + + + + + + + + 185 + 30 + 10 + 20 + + + + + + - + 20 - 30 - 71 - 20 + 190 + 250 + 60 - - - 14 - - - - Speaker - - - - - - 190 - 30 - 191 - 20 - - - - - 14 - 75 - true - false - - - - true - - - - - - Qt::AlignCenter - - - - - - 85 - 8 - 21 - 20 - - - - - 10 - - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + + server + + + + + 10 + 30 + 160 + 20 + + + + + + + 180 + 30 + 60 + 20 + + + + 80 + + - - - - 85 - 30 - 21 - 20 - - - - - 10 - - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 32 - 142 - 181 - 17 - - - - - 10 - - - - example: your.server.com - - - - - - 70 - 158 - 131 - 17 - - - - - 10 - - - - or: 123.45.67.89 - - - - - - 111 - 8 - 71 - 20 - - - - - 10 - - - - %(volume) - - - - - - 111 - 30 - 71 - 20 - - - - - 10 - - - - %(volume) - - - + 20 - 59 - 71 - 20 - - - - - 14 - - - - VAD - - - - - - 80 - 59 - 71 - 20 + 280 + 250 + 60 - - - 10 - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + - 160 - 59 - 111 - 23 + 300 + 20 + 250 + 340 - - - 14 - - - - AEC - - - true - + + details + + + + + 10 + 30 + 231 + 51 + + + + VAD + + + + + 10 + 28 + 67 + 17 + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 10 + 100 + 231 + 61 + + + + AEC + + + + + 9 + 26 + 92 + 23 + + + + AEC + + + true + + + + + + + 10 + 180 + 221 + 151 + + + - - - - 0 - 0 - 403 - 23 - - - diff --git a/client_qt5/resources/Resource.qrc b/client_qt5/resources/Resource.qrc new file mode 100644 index 0000000..e940e1b --- /dev/null +++ b/client_qt5/resources/Resource.qrc @@ -0,0 +1,9 @@ + + + microphone.png + speaker.png + vertical_bar_empty.png + vertical_bar_full.png + vertical_bar_half_full.png + + diff --git a/client_qt5/resources/microphone.png b/client_qt5/resources/microphone.png new file mode 100644 index 0000000000000000000000000000000000000000..50c8ffde737129ce9aa932177a8b0551ef790519 GIT binary patch literal 1802 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 A+W-In literal 0 HcmV?d00001 diff --git a/client_qt5/resources/vertical_bar_empty.png b/client_qt5/resources/vertical_bar_empty.png new file mode 100644 index 0000000000000000000000000000000000000000..d47894181d5e12a9f5cf23db71b18950b1fb2530 GIT binary patch literal 2883 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 literal 0 HcmV?d00001 diff --git a/client_qt5/resources/vertical_bar_full.png b/client_qt5/resources/vertical_bar_full.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca5725f89d826da22e92fdc2a60941c7dd8b576 GIT binary patch literal 2883 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 literal 0 HcmV?d00001 diff --git a/client_qt5/resources/vertical_bar_half_full.png b/client_qt5/resources/vertical_bar_half_full.png new file mode 100644 index 0000000000000000000000000000000000000000..2b8a8c112f23494ad155d28231b11f6ec8e1598c GIT binary patch literal 2883 zcmb7GX*kqh|Nh$dEGcA3WNB<8ljb+cI`+Xdh+)V!_PvR*k0wmAln8AymXSe1B3n$7 zB}KN1gvh?{2G9Ji>;LBe;(760*Eyg2KIe1a=RPmai*pxgY0Srch8q9?K2sAoih-hx z3E^a6R7wYD5&&?W_c1U)`nbCT0RBf-mKX9NTBKuTLu)QUS4ckzY489fN5~X#Q0%9c z=*~Vv8v2v!n3a-A5JQ1$x~;Yss;0SOL^P)`LPViKw$mX-Yi1F|I2YK~7 z^cfwm<*PeS`9*-+P5)3TFQ)N9Y`Z%$ zaCq@r_GDXs*EEH*pNUYFZLgtp6`RYirhZlHNGZ?BvlSI@9_^l6(6Tl={4_{;|MAqq zXQ#+h+XMGo5+ry&ISuiTbI%U~%C({O7*um~?vL6=khy^=KZ zN~;#Do;``TPg=}`3zdYEmL>6_4Nlw_|HH?omyd*GvSM}XzlN1?f)Yw!z)HCG*PQ?yDe!a59vUc8rtFr9Qh(~^xgCLX7m3kuS0qIEWYN7U z_&)`7WV8*)SEE$aHUXA4ue(KF&i{y%84!VpsiY6-IlhRP)x|+zJWd2vniE2UXlz&J*G|Wd{w~y-8_R81Usl+#JkuJ2#xe;c6xoy^M zp)k%X@rMN2$;@CQs4)ht%9Wj@)NyG_buuydVO>Nc)LAkpe&@aM@5{f5_ET z*yqxEE`nv8&3B+}n)+<{S;47bGrakEAzRW6T+K~6MCtWyd@`O0HKLo4GippRD9#JS znkTpvT<#`m^ASSc?|wjM>mS^DVCcy`(4N>HQPcuqm7;L^T&-c5Lz#iU$>Jq8t80{C z#RbKGGDEw>x+J?&pw1YBr%F=t&FDr4`qN#~ZW*gZ8?wzn<{H_ajGiq1AbiSv@C6Jh zTe3GGJ3t)>Iiqh~6yg*8Bg}zi=}f@wwEXH>tw@O`Qf!8{u81j0E>-SOu3C;Nr)I(} z#%Vxobw3X_6Qzjpi{=#y7eY}FPz{9+MTvzoh0}!rMY2UaML!FZigY@`SP73w>~eGer)?KMFu7Yt&7n6toW|55r1s*@+Kn_NDbz3^f<9Eac38n3ot3!^Atg zHp}=J9wrMqWiZ~8^;^SmM0)<;;%v(B%N>Us7CQEEuVV9#L|9G|S&8gKg+vnA`+qfxfa*Mipbcq;i7`2^VKIRd#GCC`fsiw~Si;oIg9 z# zE=|a2XU7qaL2a63Wj9zqs0tE#u?tiR@df<=Mc&xNrC1;$dJxK&YeCx|Nip}_q1?gTvsLu!UJr8*1?*1H*I$)>G>f~k8M1qMQxbbNz&A5M*_R%z zJeRXtx3l(3zPh;Bb8jZ&be&uQb|3!fZiB*-RoI*P+MzeD|IPhP^#5-C20O(e;a<8G_%#W@@yj?jlI4lQrFED}UME_Uqa9caZ}>^&Q| zW&GnT&&fTtPikEXS@KVcQ;K`7u2_!8)Xz+Rpk~@_Xt2eZ(a{?Y8(ZI7{Ez2F*plUc zasE<1a-v_KS$ZA*aV668s#ji8O(LH-=sq$YOk(q}@$W761?_4*-Na0I<*4BFqB7EieGgy8r+z2LMEZpT4`M z#b{_C5jKX$$H%t0|HEMDwV~`ZhMgaWva|*S!%KS_ay^&FG-OC{TU&a)I-|wpW^N1z zNNkcOj0IP)3Hl}j|K%otQKz3agK*$XEets(d6?M2%mHfwjOzjZrf@wQX!la4o)AHr zm&JgMRqx<#?G61&sA0`clv%-e!BI_G$f;xp|L5 zMy?#r&>rXEio?R(L$M41E`rseD(dPgV09aH2n+&+fiEh785MjJ-Y@X~3;{tNKAsW( z-M}p7TgEU*{+ked6K3Lr!rs&mx)tc*6X>NMga &buffer) = 0; + virtual uint8_t calculate(const std::vector &buffer, const uint8_t MAX_VOLUME_LEVEL) = 0; }; class SuckAudioVolume : public AudioVolume { public: - virtual uint8_t calculate(const std::vector &buffer) override { + virtual uint8_t calculate(const std::vector &buffer, const uint8_t MAX_VOLUME_LEVEL) override { double sum =0; for (const auto &sample: buffer){ sum+=std::abs(sample); } - sum *= 100; + sum *= MAX_VOLUME_LEVEL; auto ret = sum / (1<<15) / buffer.size(); return ret ; } diff --git a/include/evc/Worker.hpp b/include/evc/Worker.hpp index c74be61..63b4e99 100644 --- a/include/evc/Worker.hpp +++ b/include/evc/Worker.hpp @@ -8,6 +8,30 @@ class AudioDecoder; class AudioEncoder; class AudioDevice; + +enum class AudioInOut : uint8_t{ + In, + Out +}; + + +class AudioIoVolume { +public: + enum { MAX_VOLUME_LEVEL = 10}; + + using Level = uint8_t; + + AudioInOut io_; + Level level_; +public: + AudioIoVolume(const AudioInOut io, const Level level) + :io_(io) + ,level_(level) + { + + } +}; + enum class NetworkState : unsigned char{ Disconnected, Connecting, @@ -29,8 +53,7 @@ class Worker { AudioDevice * device_ = nullptr; bool gotoStop_ = false; std::shared_ptr netThread_ = nullptr; - std::function micVolumeReporter_ = nullptr; - std::function spkVolumeReporter_ = nullptr; + std::function volumeReporter_ = nullptr; std::function vadReporter_ = nullptr; bool needAec_ = false; public: @@ -39,16 +62,15 @@ class Worker { bool initCodec(); bool initDevice(std::function reportInfo, - std::function reportMicVolume, - std::function reportSpkVolume, + std::function reportVolume, std::function vadReporter ); - void asyncStart(const std::string &host, + void asyncStart(const std::string &host, const std::string &port, std::function toggleState ); - void syncStart(const std::string &host, + void syncStart(const std::string &host, const std::string &port, std::function toggleState ); diff --git a/misc/resources/vertical_bar_empty.svg b/misc/resources/vertical_bar_empty.svg new file mode 100644 index 0000000..c7c6170 --- /dev/null +++ b/misc/resources/vertical_bar_empty.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/misc/resources/vertical_bar_full.svg b/misc/resources/vertical_bar_full.svg new file mode 100644 index 0000000..f40daf2 --- /dev/null +++ b/misc/resources/vertical_bar_full.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/misc/resources/vertical_bar_half_full.svg b/misc/resources/vertical_bar_half_full.svg new file mode 100644 index 0000000..514d732 --- /dev/null +++ b/misc/resources/vertical_bar_half_full.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/server/BoostAsioTcpServer.cpp b/server/BoostAsioTcpServer.cpp index 09ebd6f..5004509 100644 --- a/server/BoostAsioTcpServer.cpp +++ b/server/BoostAsioTcpServer.cpp @@ -252,16 +252,13 @@ class Server { int main(int argc, char* argv[]) { try { - int port = 1222; + int port = 80; // init port { if (argc == 2) { port = std::atoi(argv[1]); } - else if (argc == 1){ - port = 1222; - } else { std::cerr << "Usage: EvcServer \n"; return -1;