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