1 /*
2     Copyright © 2014-2019 by The qTox Project Contributors
3 
4     This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6     qTox is libre software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10 
11     qTox is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with qTox.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "avform.h"
21 
22 #include <cassert>
23 #include <map>
24 
25 #include <QDebug>
26 #include <QDesktopWidget>
27 #include <QScreen>
28 #include <QShowEvent>
29 
30 #include "src/audio/audio.h"
31 #include "src/audio/iaudiosettings.h"
32 #include "src/audio/iaudiosource.h"
33 #include "src/core/core.h"
34 #include "src/core/coreav.h"
35 #include "src/video/cameradevice.h"
36 #include "src/video/camerasource.h"
37 #include "src/video/ivideosettings.h"
38 #include "src/video/videosurface.h"
39 #include "src/widget/tool/recursivesignalblocker.h"
40 #include "src/widget/tool/screenshotgrabber.h"
41 #include "src/widget/translator.h"
42 
43 #ifndef ALC_ALL_DEVICES_SPECIFIER
44 #define ALC_ALL_DEVICES_SPECIFIER ALC_DEVICE_SPECIFIER
45 #endif
46 
AVForm(IAudioControl & audio,CoreAV * coreAV,CameraSource & camera,IAudioSettings * audioSettings,IVideoSettings * videoSettings)47 AVForm::AVForm(IAudioControl& audio, CoreAV* coreAV, CameraSource& camera,
48                IAudioSettings* audioSettings, IVideoSettings* videoSettings)
49     : GenericForm(QPixmap(":/img/settings/av.png"))
50     , audio(audio)
51     , coreAV{coreAV}
52     , audioSettings{audioSettings}
53     , videoSettings{videoSettings}
54     , camVideoSurface(nullptr)
55     , camera(camera)
56 {
57     setupUi(this);
58 
59     // block all child signals during initialization
60     const RecursiveSignalBlocker signalBlocker(this);
61 
62     cbEnableTestSound->setChecked(audioSettings->getEnableTestSound());
63     cbEnableTestSound->setToolTip(tr("Play a test sound while changing the output volume."));
64 
65     connect(rescanButton, &QPushButton::clicked, this, &AVForm::rescanDevices);
66 
67     playbackSlider->setTracking(false);
68     playbackSlider->setMaximum(totalSliderSteps);
69     playbackSlider->setValue(getStepsFromValue(audioSettings->getOutVolume(),
70                                                audioSettings->getOutVolumeMin(),
71                                                audioSettings->getOutVolumeMax()));
72     playbackSlider->installEventFilter(this);
73 
74     microphoneSlider->setToolTip(tr("Use slider to set the gain of your input device ranging"
75                                     " from %1dB to %2dB.")
76                                      .arg(audio.minInputGain())
77                                      .arg(audio.maxInputGain()));
78     microphoneSlider->setMaximum(totalSliderSteps);
79     microphoneSlider->setTickPosition(QSlider::TicksBothSides);
80     static const int numTicks = 4;
81     microphoneSlider->setTickInterval(totalSliderSteps / numTicks);
82     microphoneSlider->setTracking(false);
83     microphoneSlider->installEventFilter(this);
84     microphoneSlider->setValue(
85         getStepsFromValue(audio.inputGain(), audio.minInputGain(), audio.maxInputGain()));
86 
87     audioThresholdSlider->setToolTip(tr("Use slider to set the activation volume for your"
88                                         " input device."));
89     audioThresholdSlider->setMaximum(totalSliderSteps);
90     audioThresholdSlider->setValue(getStepsFromValue(audioSettings->getAudioThreshold(),
91                                                      audio.minInputThreshold(),
92                                                      audio.maxInputThreshold()));
93     audioThresholdSlider->setTracking(false);
94     audioThresholdSlider->installEventFilter(this);
95 
96     volumeDisplay->setMaximum(totalSliderSteps);
97 
98     fillAudioQualityComboBox();
99 
100     eventsInit();
101 
102     QDesktopWidget* desktop = QApplication::desktop();
103     for (QScreen* qScreen : QGuiApplication::screens()) {
104         connect(qScreen, &QScreen::geometryChanged, this, &AVForm::rescanDevices);
105     }
106     auto* qGUIApp = qobject_cast<QGuiApplication *>(qApp);
107     assert (qGUIApp);
108     connect(qGUIApp, &QGuiApplication::screenAdded, this, &AVForm::trackNewScreenGeometry);
109     connect(qGUIApp, &QGuiApplication::screenAdded, this, &AVForm::rescanDevices);
110     connect(qGUIApp, &QGuiApplication::screenRemoved, this, &AVForm::rescanDevices);
111     Translator::registerHandler(std::bind(&AVForm::retranslateUi, this), this);
112 }
113 
~AVForm()114 AVForm::~AVForm()
115 {
116     killVideoSurface();
117     Translator::unregister(this);
118 }
119 
hideEvent(QHideEvent * event)120 void AVForm::hideEvent(QHideEvent* event)
121 {
122     audioSink.reset();
123     audioSrc.reset();
124 
125     if (camVideoSurface) {
126         camVideoSurface->setSource(nullptr);
127         killVideoSurface();
128     }
129     videoDeviceList.clear();
130 
131     GenericForm::hideEvent(event);
132 }
133 
showEvent(QShowEvent * event)134 void AVForm::showEvent(QShowEvent* event)
135 {
136     getAudioOutDevices();
137     getAudioInDevices();
138     createVideoSurface();
139     getVideoDevices();
140 
141     if (audioSrc == nullptr) {
142         audioSrc = audio.makeSource();
143         connect(audioSrc.get(), &IAudioSource::volumeAvailable, this, &AVForm::setVolume);
144     }
145 
146     if (audioSink == nullptr) {
147         audioSink = audio.makeSink();
148     }
149 
150     GenericForm::showEvent(event);
151 }
152 
open(const QString & devName,const VideoMode & mode)153 void AVForm::open(const QString& devName, const VideoMode& mode)
154 {
155     QRect rect = mode.toRect();
156     videoSettings->setCamVideoRes(rect);
157     videoSettings->setCamVideoFPS(static_cast<float>(mode.FPS));
158     camera.setupDevice(devName, mode);
159 }
160 
trackNewScreenGeometry(QScreen * qScreen)161 void AVForm::trackNewScreenGeometry(QScreen* qScreen) {
162     connect(qScreen, &QScreen::geometryChanged, this, &AVForm::rescanDevices);
163 }
164 
rescanDevices()165 void AVForm::rescanDevices()
166 {
167     getAudioInDevices();
168     getAudioOutDevices();
169     getVideoDevices();
170 }
171 
setVolume(float value)172 void AVForm::setVolume(float value)
173 {
174     volumeDisplay->setValue(getStepsFromValue(value, audio.minOutputVolume(), audio.maxOutputVolume()));
175 }
176 
on_videoModescomboBox_currentIndexChanged(int index)177 void AVForm::on_videoModescomboBox_currentIndexChanged(int index)
178 {
179     assert(0 <= index && index < videoModes.size());
180     int devIndex = videoDevCombobox->currentIndex();
181     assert(0 <= devIndex && devIndex < videoDeviceList.size());
182 
183     QString devName = videoDeviceList[devIndex].first;
184     VideoMode mode = videoModes[index];
185 
186     if (CameraDevice::isScreen(devName) && mode == VideoMode()) {
187         if (videoSettings->getScreenGrabbed()) {
188             VideoMode mode(videoSettings->getScreenRegion());
189             open(devName, mode);
190             return;
191         }
192 
193         auto onGrabbed = [devName, this](QRect region) {
194             VideoMode mode(region);
195             mode.width = mode.width / 2 * 2;
196             mode.height = mode.height / 2 * 2;
197 
198             // Needed, if the virtual screen origin is the top left corner of the primary screen
199             QRect screen = QApplication::primaryScreen()->virtualGeometry();
200             mode.x += screen.x();
201             mode.y += screen.y();
202 
203             videoSettings->setScreenRegion(mode.toRect());
204             videoSettings->setScreenGrabbed(true);
205 
206             open(devName, mode);
207         };
208 
209         // note: grabber is self-managed and will destroy itself when done
210         ScreenshotGrabber* screenshotGrabber = new ScreenshotGrabber;
211 
212         connect(screenshotGrabber, &ScreenshotGrabber::regionChosen, this, onGrabbed,
213                 Qt::QueuedConnection);
214         screenshotGrabber->showGrabber();
215         return;
216     }
217 
218     videoSettings->setScreenGrabbed(false);
219     open(devName, mode);
220 }
221 
selectBestModes(QVector<VideoMode> & allVideoModes)222 void AVForm::selectBestModes(QVector<VideoMode>& allVideoModes)
223 {
224     if (allVideoModes.isEmpty()) {
225         qCritical() << "Trying to select best mode from empty modes list";
226         return;
227     }
228 
229     // Identify the best resolutions available for the supposed XXXXp resolutions.
230     std::map<int, VideoMode> idealModes;
231     idealModes[120] = VideoMode(160, 120);
232     idealModes[240] = VideoMode(430, 240);
233     idealModes[360] = VideoMode(640, 360);
234     idealModes[480] = VideoMode(854, 480);
235     idealModes[720] = VideoMode(1280, 720);
236     idealModes[1080] = VideoMode(1920, 1080);
237     idealModes[1440] = VideoMode(2560, 1440);
238     idealModes[2160] = VideoMode(3840, 2160);
239 
240     std::map<int, int> bestModeInds;
241     for (int i = 0; i < allVideoModes.size(); ++i) {
242         VideoMode mode = allVideoModes[i];
243 
244         // PS3-Cam protection, everything above 60fps makes no sense
245         if (mode.FPS > 60)
246             continue;
247 
248         for (auto iter = idealModes.begin(); iter != idealModes.end(); ++iter) {
249             int res = iter->first;
250             VideoMode idealMode = iter->second;
251             // don't take approximately correct resolutions unless they really
252             // are close
253             if (mode.norm(idealMode) > idealMode.tolerance())
254                 continue;
255 
256             if (bestModeInds.find(res) == bestModeInds.end()) {
257                 bestModeInds[res] = i;
258                 continue;
259             }
260 
261             int index = bestModeInds[res];
262             VideoMode best = allVideoModes[index];
263             if (mode.norm(idealMode) < best.norm(idealMode)) {
264                 bestModeInds[res] = i;
265                 continue;
266             }
267 
268             if (mode.norm(idealMode) == best.norm(idealMode)) {
269                 // prefer higher FPS and "better" pixel formats
270                 if (mode.FPS > best.FPS) {
271                     bestModeInds[res] = i;
272                     continue;
273                 }
274 
275                 bool better = CameraDevice::betterPixelFormat(mode.pixel_format, best.pixel_format);
276                 if (mode.FPS >= best.FPS && better)
277                     bestModeInds[res] = i;
278             }
279         }
280     }
281 
282     QVector<VideoMode> newVideoModes;
283     for (auto it = bestModeInds.rbegin(); it != bestModeInds.rend(); ++it) {
284         VideoMode mode = allVideoModes[it->second];
285 
286         if (newVideoModes.empty()) {
287             newVideoModes.push_back(mode);
288         } else {
289             int size = getModeSize(mode);
290             auto result = std::find_if(newVideoModes.cbegin(), newVideoModes.cend(),
291                                        [size](VideoMode mode) { return getModeSize(mode) == size; });
292 
293             if (result == newVideoModes.end())
294                 newVideoModes.push_back(mode);
295         }
296     }
297     allVideoModes = newVideoModes;
298 }
299 
fillCameraModesComboBox()300 void AVForm::fillCameraModesComboBox()
301 {
302     qDebug() << "selected Modes:";
303     bool previouslyBlocked = videoModescomboBox->blockSignals(true);
304     videoModescomboBox->clear();
305 
306     for (int i = 0; i < videoModes.size(); ++i) {
307         VideoMode mode = videoModes[i];
308 
309         QString str;
310         std::string pixelFormat = CameraDevice::getPixelFormatString(mode.pixel_format).toStdString();
311         qDebug("width: %d, height: %d, FPS: %f, pixel format: %s\n", mode.width, mode.height,
312                mode.FPS, pixelFormat.c_str());
313 
314         if (mode.height && mode.width) {
315             str += QString("%1p").arg(mode.height);
316         } else {
317             str += tr("Default resolution");
318         }
319 
320         videoModescomboBox->addItem(str);
321     }
322 
323     if (videoModes.isEmpty())
324         videoModescomboBox->addItem(tr("Default resolution"));
325 
326     videoModescomboBox->blockSignals(previouslyBlocked);
327 }
328 
searchPreferredIndex()329 int AVForm::searchPreferredIndex()
330 {
331     QRect prefRes = videoSettings->getCamVideoRes();
332     float prefFPS = videoSettings->getCamVideoFPS();
333 
334     for (int i = 0; i < videoModes.size(); ++i) {
335         VideoMode mode = videoModes[i];
336         if (mode.width == prefRes.width() && mode.height == prefRes.height()
337             && (qAbs(mode.FPS - prefFPS) < 0.0001f)) {
338             return i;
339         }
340     }
341 
342     return -1;
343 }
344 
fillScreenModesComboBox()345 void AVForm::fillScreenModesComboBox()
346 {
347     bool previouslyBlocked = videoModescomboBox->blockSignals(true);
348     videoModescomboBox->clear();
349 
350     for (int i = 0; i < videoModes.size(); ++i) {
351         VideoMode mode = videoModes[i];
352         std::string pixelFormat = CameraDevice::getPixelFormatString(mode.pixel_format).toStdString();
353         qDebug("%dx%d+%d,%d FPS: %f, pixel format: %s\n", mode.width, mode.height, mode.x, mode.y,
354                mode.FPS, pixelFormat.c_str());
355 
356         QString name;
357         if (mode.width && mode.height)
358             name = tr("Screen %1").arg(i + 1);
359         else
360             name = tr("Select region");
361 
362         videoModescomboBox->addItem(name);
363     }
364 
365     videoModescomboBox->blockSignals(previouslyBlocked);
366 }
367 
fillAudioQualityComboBox()368 void AVForm::fillAudioQualityComboBox()
369 {
370     const bool previouslyBlocked = audioQualityComboBox->blockSignals(true);
371 
372     audioQualityComboBox->addItem(tr("High (64 kbps)"), 64);
373     audioQualityComboBox->addItem(tr("Medium (32 kbps)"), 32);
374     audioQualityComboBox->addItem(tr("Low (16 kbps)"), 16);
375     audioQualityComboBox->addItem(tr("Very low (8 kbps)"), 8);
376 
377     const int currentBitrate = audioSettings->getAudioBitrate();
378     const int index = audioQualityComboBox->findData(currentBitrate);
379 
380     audioQualityComboBox->setCurrentIndex(index);
381     audioQualityComboBox->blockSignals(previouslyBlocked);
382 }
383 
updateVideoModes(int curIndex)384 void AVForm::updateVideoModes(int curIndex)
385 {
386     if (curIndex < 0 || curIndex >= videoDeviceList.size()) {
387         qWarning() << "Invalid index:" << curIndex;
388         return;
389     }
390     QString devName = videoDeviceList[curIndex].first;
391     QVector<VideoMode> allVideoModes = CameraDevice::getVideoModes(devName);
392 
393     qDebug("available Modes:");
394     bool isScreen = CameraDevice::isScreen(devName);
395     if (isScreen) {
396         // Add extra video mode to region selection
397         allVideoModes.push_back(VideoMode());
398         videoModes = allVideoModes;
399         fillScreenModesComboBox();
400     } else {
401         selectBestModes(allVideoModes);
402         videoModes = allVideoModes;
403         fillCameraModesComboBox();
404     }
405 
406     int preferedIndex = searchPreferredIndex();
407     if (preferedIndex != -1) {
408         videoSettings->setScreenGrabbed(false);
409         videoModescomboBox->blockSignals(true);
410         videoModescomboBox->setCurrentIndex(preferedIndex);
411         videoModescomboBox->blockSignals(false);
412         open(devName, videoModes[preferedIndex]);
413         return;
414     }
415 
416     if (isScreen) {
417         QRect rect = videoSettings->getScreenRegion();
418         VideoMode mode(rect);
419 
420         videoSettings->setScreenGrabbed(true);
421         videoModescomboBox->setCurrentIndex(videoModes.size() - 1);
422         open(devName, mode);
423         return;
424     }
425 
426     // If the user hasn't set a preferred resolution yet,
427     // we'll pick the resolution in the middle of the list,
428     // and the best FPS for that resolution.
429     // If we picked the lowest resolution, the quality would be awful
430     // but if we picked the largest, FPS would be bad and thus quality bad too.
431     int mid = (videoModes.size() - 1) / 2;
432     videoModescomboBox->setCurrentIndex(mid);
433 }
434 
on_videoDevCombobox_currentIndexChanged(int index)435 void AVForm::on_videoDevCombobox_currentIndexChanged(int index)
436 {
437     assert(0 <= index && index < videoDeviceList.size());
438 
439     videoSettings->setScreenGrabbed(false);
440     QString dev = videoDeviceList[index].first;
441     videoSettings->setVideoDev(dev);
442     bool previouslyBlocked = videoModescomboBox->blockSignals(true);
443     updateVideoModes(index);
444     videoModescomboBox->blockSignals(previouslyBlocked);
445 
446     if (videoSettings->getScreenGrabbed()) {
447         return;
448     }
449 
450     int modeIndex = videoModescomboBox->currentIndex();
451     VideoMode mode = VideoMode();
452     if (0 <= modeIndex && modeIndex < videoModes.size()) {
453         mode = videoModes[modeIndex];
454     }
455 
456     camera.setupDevice(dev, mode);
457     if (dev == "none") {
458         // TODO: Use injected `coreAv` currently injected `nullptr`
459         Core::getInstance()->getAv()->sendNoVideo();
460     }
461 }
462 
on_audioQualityComboBox_currentIndexChanged(int index)463 void AVForm::on_audioQualityComboBox_currentIndexChanged(int index)
464 {
465     audioSettings->setAudioBitrate(audioQualityComboBox->currentData().toInt());
466 }
467 
getVideoDevices()468 void AVForm::getVideoDevices()
469 {
470     QString settingsInDev = videoSettings->getVideoDev();
471     int videoDevIndex = 0;
472     videoDeviceList = CameraDevice::getDeviceList();
473     // prevent currentIndexChanged to be fired while adding items
474     videoDevCombobox->blockSignals(true);
475     videoDevCombobox->clear();
476     for (QPair<QString, QString> device : videoDeviceList) {
477         videoDevCombobox->addItem(device.second);
478         if (device.first == settingsInDev)
479             videoDevIndex = videoDevCombobox->count() - 1;
480     }
481     videoDevCombobox->setCurrentIndex(videoDevIndex);
482     videoDevCombobox->blockSignals(false);
483     updateVideoModes(videoDevIndex);
484 }
485 
getModeSize(VideoMode mode)486 int AVForm::getModeSize(VideoMode mode)
487 {
488     return qRound(mode.height / 120.0) * 120;
489 }
490 
getAudioInDevices()491 void AVForm::getAudioInDevices()
492 {
493     QStringList deviceNames;
494     deviceNames << tr("Disabled") << audio.inDeviceNames();
495 
496     inDevCombobox->blockSignals(true);
497     inDevCombobox->clear();
498     inDevCombobox->addItems(deviceNames);
499     inDevCombobox->blockSignals(false);
500 
501     int idx = 0;
502     bool enabled = audioSettings->getAudioInDevEnabled();
503     if (enabled && deviceNames.size() > 1) {
504         QString dev = audioSettings->getInDev();
505         idx = qMax(deviceNames.indexOf(dev), 1);
506     }
507     inDevCombobox->setCurrentIndex(idx);
508 }
509 
getAudioOutDevices()510 void AVForm::getAudioOutDevices()
511 {
512     QStringList deviceNames;
513     deviceNames << tr("Disabled") << audio.outDeviceNames();
514 
515     outDevCombobox->blockSignals(true);
516     outDevCombobox->clear();
517     outDevCombobox->addItems(deviceNames);
518     outDevCombobox->blockSignals(false);
519 
520     int idx = 0;
521     bool enabled = audioSettings->getAudioOutDevEnabled();
522     if (enabled && deviceNames.size() > 1) {
523         QString dev = audioSettings->getOutDev();
524         idx = qMax(deviceNames.indexOf(dev), 1);
525     }
526     outDevCombobox->setCurrentIndex(idx);
527 }
528 
on_inDevCombobox_currentIndexChanged(int deviceIndex)529 void AVForm::on_inDevCombobox_currentIndexChanged(int deviceIndex)
530 {
531     const bool inputEnabled = deviceIndex > 0;
532     audioSettings->setAudioInDevEnabled(inputEnabled);
533 
534     QString deviceName{};
535     if (inputEnabled) {
536         deviceName = inDevCombobox->itemText(deviceIndex);
537     }
538 
539     const QString oldName = audioSettings->getInDev();
540     if (oldName != deviceName) {
541         audioSettings->setInDev(deviceName);
542         audio.reinitInput(deviceName);
543         audioSrc = audio.makeSource();
544         connect(audioSrc.get(), &IAudioSource::volumeAvailable, this, &AVForm::setVolume);
545     }
546 
547     microphoneSlider->setEnabled(inputEnabled);
548     if (!inputEnabled) {
549         volumeDisplay->setValue(volumeDisplay->minimum());
550     }
551 }
552 
on_outDevCombobox_currentIndexChanged(int deviceIndex)553 void AVForm::on_outDevCombobox_currentIndexChanged(int deviceIndex)
554 {
555     const bool outputEnabled = deviceIndex > 0;
556     audioSettings->setAudioOutDevEnabled(outputEnabled);
557 
558     QString deviceName{};
559     if (outputEnabled) {
560         deviceName = outDevCombobox->itemText(deviceIndex);
561     }
562 
563     const QString oldName = audioSettings->getOutDev();
564 
565     if (oldName != deviceName) {
566         audioSettings->setOutDev(deviceName);
567         audio.reinitOutput(deviceName);
568         audioSink = audio.makeSink();
569     }
570 
571     playbackSlider->setEnabled(outputEnabled);
572 }
573 
on_playbackSlider_valueChanged(int sliderSteps)574 void AVForm::on_playbackSlider_valueChanged(int sliderSteps)
575 {
576     const int settingsVolume = getValueFromSteps(sliderSteps, audioSettings->getOutVolumeMin(),
577                                                  audioSettings->getOutVolumeMax());
578     audioSettings->setOutVolume(settingsVolume);
579 
580     if (audio.isOutputReady()) {
581         const qreal volume =
582             getValueFromSteps(sliderSteps, audio.minOutputVolume(), audio.maxOutputVolume());
583         audio.setOutputVolume(volume);
584 
585         if (cbEnableTestSound->isChecked() && audioSink) {
586             audioSink->playMono16Sound(IAudioSink::Sound::Test);
587         }
588     }
589 }
590 
on_cbEnableTestSound_stateChanged()591 void AVForm::on_cbEnableTestSound_stateChanged()
592 {
593     audioSettings->setEnableTestSound(cbEnableTestSound->isChecked());
594 
595     if (cbEnableTestSound->isChecked() && audio.isOutputReady() && audioSink) {
596         audioSink->playMono16Sound(IAudioSink::Sound::Test);
597     }
598 }
599 
on_microphoneSlider_valueChanged(int sliderSteps)600 void AVForm::on_microphoneSlider_valueChanged(int sliderSteps)
601 {
602     const qreal dB = getValueFromSteps(sliderSteps, audio.minInputGain(), audio.maxInputGain());
603     audioSettings->setAudioInGainDecibel(dB);
604     audio.setInputGain(dB);
605 }
606 
on_audioThresholdSlider_valueChanged(int sliderSteps)607 void AVForm::on_audioThresholdSlider_valueChanged(int sliderSteps)
608 {
609     const qreal normThreshold =
610         getValueFromSteps(sliderSteps, audio.minInputThreshold(), audio.maxInputThreshold());
611     audioSettings->setAudioThreshold(normThreshold);
612     audio.setInputThreshold(normThreshold);
613 }
createVideoSurface()614 void AVForm::createVideoSurface()
615 {
616     if (camVideoSurface)
617         return;
618 
619     camVideoSurface = new VideoSurface(QPixmap(), CamFrame);
620     camVideoSurface->setObjectName(QStringLiteral("CamVideoSurface"));
621     camVideoSurface->setMinimumSize(QSize(160, 120));
622     camVideoSurface->setSource(&camera);
623     gridLayout->addWidget(camVideoSurface, 0, 0, 1, 1);
624 }
625 
killVideoSurface()626 void AVForm::killVideoSurface()
627 {
628     if (!camVideoSurface)
629         return;
630 
631     QLayoutItem* child;
632     while ((child = gridLayout->takeAt(0)) != nullptr)
633         delete child;
634 
635     camVideoSurface->close();
636     delete camVideoSurface;
637     camVideoSurface = nullptr;
638 }
639 
retranslateUi()640 void AVForm::retranslateUi()
641 {
642     Ui::AVForm::retranslateUi(this);
643 }
644 
getStepsFromValue(qreal val,qreal valMin,qreal valMax)645 int AVForm::getStepsFromValue(qreal val, qreal valMin, qreal valMax)
646 {
647     const float norm = (val - valMin) / (valMax - valMin);
648     return norm * totalSliderSteps;
649 }
650 
getValueFromSteps(int steps,qreal valMin,qreal valMax)651 qreal AVForm::getValueFromSteps(int steps, qreal valMin, qreal valMax)
652 {
653     return (static_cast<float>(steps) / totalSliderSteps) * (valMax - valMin) + valMin;
654 }
655