1 /* qv4l2: a control panel controlling v4l2 devices.
2 *
3 * Copyright (C) 2006 Hans Verkuil <hverkuil@xs4all.nl>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include <QToolBar>
21 #include <QToolButton>
22 #include <QMenuBar>
23 #include <QFileDialog>
24 #include <QStatusBar>
25 #include <QApplication>
26 #include <QMessageBox>
27 #include <QLineEdit>
28 #include <QValidator>
29 #include <QLayout>
30 #include <QLabel>
31 #include <QSlider>
32 #include <QSpinBox>
33 #include <QComboBox>
34 #include <QCheckBox>
35 #include <QPushButton>
36 #include <QToolTip>
37 #include <QImage>
38 #include <QWhatsThis>
39 #include <QThread>
40 #include <QCloseEvent>
41 #include <QInputDialog>
42 #include <QActionGroup>
43
44 #include <assert.h>
45 #include <sys/mman.h>
46 #include <sys/time.h>
47 #include <errno.h>
48 #include <sys/ioctl.h>
49 #include <dirent.h>
50
51 #include "qv4l2.h"
52 #include "general-tab.h"
53 #include "vbi-tab.h"
54 #include "capture-win.h"
55 #include "capture-win-qt.h"
56 #include "capture-win-gl.h"
57
58 #include <libv4l-plugin.h>
59 #include <libv4lconvert.h>
60
61 #define SDR_WIDTH 1024
62 #define SDR_HEIGHT 512
63
rawDevInit(int fd)64 static void *rawDevInit(int fd)
65 {
66 return NULL;
67 }
68
rawDevClose(void * dev_ops_priv)69 static void rawDevClose(void *dev_ops_priv)
70 {
71 }
72
rawDevIoctl(void * dev_ops_priv,int fd,unsigned long cmd,void * arg)73 static int rawDevIoctl(void *dev_ops_priv, int fd, unsigned long cmd, void *arg)
74 {
75 return ioctl(fd, cmd, arg);
76 }
77
rawDevRead(void * dev_ops_priv,int fd,void * buf,size_t len)78 static ssize_t rawDevRead(void *dev_ops_priv, int fd, void *buf, size_t len)
79 {
80 return read(fd, buf, len);
81 }
82
rawDevWrite(void * dev_ops_priv,int fd,const void * buf,size_t len)83 static ssize_t rawDevWrite(void *dev_ops_priv, int fd, const void *buf,
84 size_t len)
85 {
86 return write(fd, buf, len);
87 }
88
89 static const struct libv4l_dev_ops rawDevOps = {
90 .init = rawDevInit,
91 .close = rawDevClose,
92 .ioctl = rawDevIoctl,
93 .read = rawDevRead,
94 .write = rawDevWrite,
95 };
96
addSubMenuItem(QActionGroup * grp,QMenu * menu,const QString & text,int val)97 static QAction *addSubMenuItem(QActionGroup *grp, QMenu *menu, const QString &text, int val)
98 {
99 QAction *a = grp->addAction(menu->addAction(text));
100
101 a->setData(QVariant(val));
102 a->setCheckable(true);
103 return a;
104 }
105
ApplicationWindow()106 ApplicationWindow::ApplicationWindow() :
107 m_capture(NULL),
108 m_pxw(25),
109 m_minWidth(175),
110 m_vMargin(15),
111 m_hMargin(5),
112 m_genTab(NULL),
113 m_sigMapper(NULL)
114 {
115 setAttribute(Qt::WA_DeleteOnClose, true);
116
117 m_capNotifier = NULL;
118 m_outNotifier = NULL;
119 m_ctrlNotifier = NULL;
120 m_capImage = NULL;
121 m_frameData = NULL;
122 m_nbuffers = 0;
123 m_makeSnapshot = false;
124 m_singleStep = false;
125 m_tpgColorspace = 0;
126 m_tpgXferFunc = 0;
127 m_tpgYCbCrEnc = 0;
128 m_tpgQuantRange = 0;
129 m_tpgLimRGBRange = NULL;
130 for (bool &b : m_clear)
131 b = false;
132
133 QAction *openAct = new QAction(QIcon(":/fileopen.png"), "&Open Device", this);
134 openAct->setStatusTip("Open a v4l device, use libv4l2 wrapper if possible");
135 openAct->setShortcut(Qt::CTRL|Qt::Key_O);
136 connect(openAct, SIGNAL(triggered()), this, SLOT(opendev()));
137
138 QAction *openRawAct = new QAction(QIcon(":/fileopen.png"), "Open &Raw Device", this);
139 openRawAct->setStatusTip("Open a v4l device without using the libv4l2 wrapper");
140 openRawAct->setShortcut(Qt::CTRL|Qt::Key_R);
141 connect(openRawAct, SIGNAL(triggered()), this, SLOT(openrawdev()));
142
143 m_capStartAct = new QAction(QIcon(":/start.png"), "Start &Capturing", this);
144 m_capStartAct->setStatusTip("Start capturing");
145 m_capStartAct->setCheckable(true);
146 m_capStartAct->setDisabled(true);
147 m_capStartAct->setShortcut(Qt::CTRL|Qt::Key_V);
148 connect(m_capStartAct, SIGNAL(toggled(bool)), this, SLOT(capStart(bool)));
149
150 m_capStepAct = new QAction(QIcon(":/step.png"), "Single Step", this);
151 m_capStepAct->setDisabled(true);
152 connect(m_capStepAct, SIGNAL(triggered(bool)), this, SLOT(capStep(bool)));
153
154 m_snapshotAct = new QAction(QIcon(":/snapshot.png"), "&Make Snapshot", this);
155 m_snapshotAct->setStatusTip("Make snapshot");
156 m_snapshotAct->setDisabled(true);
157 connect(m_snapshotAct, SIGNAL(triggered()), this, SLOT(snapshot()));
158
159 m_saveRawAct = new QAction(QIcon(":/saveraw.png"), "Save Raw Frames", this);
160 m_saveRawAct->setStatusTip("Save raw frames to file.");
161 m_saveRawAct->setCheckable(true);
162 m_saveRawAct->setChecked(false);
163 connect(m_saveRawAct, SIGNAL(toggled(bool)), this, SLOT(saveRaw(bool)));
164
165 m_showFramesAct = new QAction(QIcon(":/video-television.png"), "&Show Frames", this);
166 m_showFramesAct->setStatusTip("Only show captured frames if set.");
167 m_showFramesAct->setCheckable(true);
168 m_showFramesAct->setChecked(true);
169
170 QAction *closeAct = new QAction(QIcon(":/fileclose.png"), "&Close Device", this);
171 closeAct->setStatusTip("Close");
172 closeAct->setShortcut(Qt::CTRL|Qt::Key_W);
173 connect(closeAct, SIGNAL(triggered()), this, SLOT(closeDevice()));
174
175 QAction *traceAct = new QAction("&Trace IOCTLs", this);
176 traceAct->setStatusTip("All V4L2 IOCTLs are traced on the console");
177 traceAct->setCheckable(true);
178 connect(traceAct, SIGNAL(toggled(bool)), this, SLOT(traceIoctls(bool)));
179
180 QAction *quitAct = new QAction(QIcon(":/exit.png"), "&Quit", this);
181 quitAct->setStatusTip("Exit the application");
182 quitAct->setShortcut(Qt::CTRL|Qt::Key_Q);
183 connect(quitAct, SIGNAL(triggered()), this, SLOT(close()));
184
185 QMenu *fileMenu = menuBar()->addMenu("&File");
186 fileMenu->addAction(openAct);
187 fileMenu->addAction(openRawAct);
188 fileMenu->addAction(closeAct);
189 fileMenu->addAction(m_snapshotAct);
190 fileMenu->addAction(m_saveRawAct);
191 fileMenu->addSeparator();
192 fileMenu->addAction(traceAct);
193 fileMenu->addSeparator();
194 fileMenu->addAction(quitAct);
195
196 QToolBar *toolBar = addToolBar("File");
197 toolBar->setObjectName("toolBar");
198 toolBar->addAction(openAct);
199 toolBar->addAction(m_capStartAct);
200 toolBar->addAction(m_capStepAct);
201 toolBar->addAction(m_snapshotAct);
202 toolBar->addAction(m_saveRawAct);
203
204 m_scalingAct = new QAction("&Enable Video Scaling", this);
205 m_scalingAct->setStatusTip("Scale video frames to match window size if set");
206 m_scalingAct->setCheckable(true);
207 m_scalingAct->setChecked(true);
208 connect(m_scalingAct, SIGNAL(toggled(bool)), this, SLOT(enableScaling(bool)));
209
210 m_resetScalingAct = new QAction("Resize to &Frame Size", this);
211 m_resetScalingAct->setStatusTip("Resizes the capture window to match frame size");
212 m_resetScalingAct->setShortcut(Qt::CTRL|Qt::Key_F);
213
214 m_overrideColorspace = -1;
215 QMenu *menu = new QMenu("Override Colorspace");
216 m_overrideColorspaceMenu = menu;
217 QActionGroup *grp = new QActionGroup(menu);
218 addSubMenuItem(grp, menu, "No Override", -1)->setChecked(true);
219 addSubMenuItem(grp, menu, "SMPTE 170M", V4L2_COLORSPACE_SMPTE170M);
220 addSubMenuItem(grp, menu, "Rec. 709", V4L2_COLORSPACE_REC709);
221 addSubMenuItem(grp, menu, "sRGB", V4L2_COLORSPACE_SRGB);
222 addSubMenuItem(grp, menu, "opRGB", V4L2_COLORSPACE_OPRGB);
223 addSubMenuItem(grp, menu, "BT.2020", V4L2_COLORSPACE_BT2020);
224 addSubMenuItem(grp, menu, "DCI-P3", V4L2_COLORSPACE_DCI_P3);
225 addSubMenuItem(grp, menu, "SMPTE 240M", V4L2_COLORSPACE_SMPTE240M);
226 addSubMenuItem(grp, menu, "470 System M", V4L2_COLORSPACE_470_SYSTEM_M);
227 addSubMenuItem(grp, menu, "470 System BG", V4L2_COLORSPACE_470_SYSTEM_BG);
228 connect(grp, SIGNAL(triggered(QAction *)), this, SLOT(overrideColorspaceChanged(QAction *)));
229
230 m_overrideXferFunc = -1;
231 menu = new QMenu("Override Transfer Function");
232 m_overrideXferFuncMenu = menu;
233 grp = new QActionGroup(menu);
234 addSubMenuItem(grp, menu, "No Override", -1)->setChecked(true);
235 addSubMenuItem(grp, menu, "Rec. 709", V4L2_XFER_FUNC_709);
236 addSubMenuItem(grp, menu, "sRGB", V4L2_XFER_FUNC_SRGB);
237 addSubMenuItem(grp, menu, "opRGB", V4L2_XFER_FUNC_OPRGB);
238 addSubMenuItem(grp, menu, "DCI-P3", V4L2_XFER_FUNC_DCI_P3);
239 addSubMenuItem(grp, menu, "SMPTE 2084", V4L2_XFER_FUNC_SMPTE2084);
240 addSubMenuItem(grp, menu, "SMPTE 240M", V4L2_XFER_FUNC_SMPTE240M);
241 addSubMenuItem(grp, menu, "None", V4L2_XFER_FUNC_NONE);
242 connect(grp, SIGNAL(triggered(QAction *)), this, SLOT(overrideXferFuncChanged(QAction *)));
243
244 m_overrideYCbCrEnc = -1;
245 menu = new QMenu("Override Y'CbCr/HSV Encoding");
246 m_overrideYCbCrEncMenu = menu;
247 grp = new QActionGroup(menu);
248 addSubMenuItem(grp, menu, "No Override", -1)->setChecked(true);
249 addSubMenuItem(grp, menu, "ITU-R 601", V4L2_YCBCR_ENC_601);
250 addSubMenuItem(grp, menu, "Rec. 709", V4L2_YCBCR_ENC_709);
251 addSubMenuItem(grp, menu, "xvYCC 601", V4L2_YCBCR_ENC_XV601);
252 addSubMenuItem(grp, menu, "xvYCC 709", V4L2_YCBCR_ENC_XV709);
253 addSubMenuItem(grp, menu, "BT.2020", V4L2_YCBCR_ENC_BT2020);
254 addSubMenuItem(grp, menu, "BT.2020 Constant Luminance", V4L2_YCBCR_ENC_BT2020_CONST_LUM);
255 addSubMenuItem(grp, menu, "SMPTE 240M", V4L2_YCBCR_ENC_SMPTE240M);
256 addSubMenuItem(grp, menu, "HSV with Hue 0-179", V4L2_HSV_ENC_180);
257 addSubMenuItem(grp, menu, "HSV with Hue 0-255", V4L2_HSV_ENC_256);
258
259 connect(grp, SIGNAL(triggered(QAction *)), this, SLOT(overrideYCbCrEncChanged(QAction *)));
260
261 m_overrideQuantization = -1;
262 menu = new QMenu("Override Quantization");
263 m_overrideQuantizationMenu = menu;
264 grp = new QActionGroup(menu);
265 addSubMenuItem(grp, menu, "No Override", -1)->setChecked(true);
266 addSubMenuItem(grp, menu, "Full Range", V4L2_QUANTIZATION_FULL_RANGE);
267 addSubMenuItem(grp, menu, "Limited Range", V4L2_QUANTIZATION_LIM_RANGE);
268 connect(grp, SIGNAL(triggered(QAction *)), this, SLOT(overrideQuantChanged(QAction *)));
269
270 m_capMenu = menuBar()->addMenu("&Capture");
271 m_capMenu->addAction(m_capStartAct);
272 m_capMenu->addAction(m_capStepAct);
273 m_capMenu->addMenu(m_overrideColorspaceMenu);
274 m_capMenu->addMenu(m_overrideXferFuncMenu);
275 m_capMenu->addMenu(m_overrideYCbCrEncMenu);
276 m_capMenu->addMenu(m_overrideQuantizationMenu);
277 m_capMenu->addAction(m_showFramesAct);
278 m_capMenu->addAction(m_scalingAct);
279
280 if (CaptureWinGL::isSupported()) {
281 m_renderMethod = QV4L2_RENDER_GL;
282
283 m_useGLAct = new QAction("Use Open&GL Rendering", this);
284 m_useGLAct->setStatusTip("Use GPU with OpenGL for video capture if set.");
285 m_useGLAct->setCheckable(true);
286 m_useGLAct->setChecked(true);
287 connect(m_useGLAct, SIGNAL(toggled(bool)), this, SLOT(setRenderMethod(bool)));
288 m_capMenu->addAction(m_useGLAct);
289
290 m_useBlendingAct = new QAction("Enable &Blending", this);
291 m_useBlendingAct->setStatusTip("Enable blending to test the alpha component in the image");
292 m_useBlendingAct->setCheckable(true);
293 m_useBlendingAct->setChecked(false);
294 connect(m_useBlendingAct, SIGNAL(toggled(bool)), this, SLOT(setBlending(bool)));
295 m_capMenu->addAction(m_useBlendingAct);
296
297 m_useLinearAct = new QAction("Enable &Linear filter", this);
298 m_useLinearAct->setStatusTip("Enable linear scaling filter");
299 m_useLinearAct->setCheckable(true);
300 m_useLinearAct->setChecked(false);
301 connect(m_useLinearAct, SIGNAL(toggled(bool)), this, SLOT(setLinearFilter(bool)));
302 m_capMenu->addAction(m_useLinearAct);
303
304 } else {
305 m_renderMethod = QV4L2_RENDER_QT;
306 m_useGLAct = m_useBlendingAct = m_useLinearAct = NULL;
307 }
308 m_capMenu->addAction(m_resetScalingAct);
309
310 m_makeFullScreenAct = new QAction(QIcon(":/fullscreen.png"), "Show Fullscreen", this);
311 m_makeFullScreenAct->setStatusTip("Capture in fullscreen mode");
312 m_makeFullScreenAct->setCheckable(true);
313 connect(m_makeFullScreenAct, SIGNAL(toggled(bool)), this, SLOT(makeFullScreen(bool)));
314 m_capMenu->addAction(m_makeFullScreenAct);
315 toolBar->addAction(m_makeFullScreenAct);
316
317 #ifdef HAVE_ALSA
318 m_capMenu->addSeparator();
319
320 m_audioBufferAct = new QAction("Set Audio &Buffer Capacity...", this);
321 m_audioBufferAct->setStatusTip("Set audio buffer capacity in amount of ms than can be stored");
322 connect(m_audioBufferAct, SIGNAL(triggered()), this, SLOT(setAudioBufferSize()));
323 m_capMenu->addAction(m_audioBufferAct);
324 #endif
325
326 QMenu *helpMenu = menuBar()->addMenu("&Help");
327 #if QT_VERSION < 0x060000
328 helpMenu->addAction("&About", this, SLOT(about()), Qt::Key_F1);
329 #else
330 helpMenu->addAction("&About", Qt::Key_F1, this, SLOT(about()));
331 #endif
332
333 QAction *whatAct = QWhatsThis::createAction(this);
334 helpMenu->addAction(whatAct);
335 toolBar->addAction(whatAct);
336
337 statusBar()->showMessage("Ready", 2000);
338
339 m_tabs = new QTabWidget;
340 setCentralWidget(m_tabs);
341 }
342
343
~ApplicationWindow()344 ApplicationWindow::~ApplicationWindow()
345 {
346 closeDevice();
347 }
348
updateColorspace()349 void ApplicationWindow::updateColorspace()
350 {
351 if (!m_capture)
352 return;
353
354 int colorspace = m_overrideColorspace;
355 int xferFunc = m_overrideXferFunc;
356 int ycbcrEnc = m_overrideYCbCrEnc;
357 int quantRange = m_overrideQuantization;
358 cv4l_fmt fmt(g_type());
359
360 // don't use the wrapped ioctl since it doesn't
361 // update colorspace correctly.
362 ::ioctl(g_fd(), VIDIOC_G_FMT, &fmt);
363
364 if (colorspace == -1)
365 colorspace = fmt.g_colorspace();
366 if (xferFunc == -1)
367 xferFunc = fmt.g_xfer_func();
368 if (ycbcrEnc == -1)
369 ycbcrEnc = fmt.g_ycbcr_enc();
370 if (quantRange == -1)
371 quantRange = fmt.g_quantization();
372 m_capture->setColorspace(colorspace, xferFunc, ycbcrEnc, quantRange,
373 m_genTab ? m_genTab->isSDTV() : true);
374 }
375
overrideColorspaceChanged(QAction * a)376 void ApplicationWindow::overrideColorspaceChanged(QAction *a)
377 {
378 m_overrideColorspace = a->data().toInt();
379 updateColorspace();
380 }
381
overrideXferFuncChanged(QAction * a)382 void ApplicationWindow::overrideXferFuncChanged(QAction *a)
383 {
384 m_overrideXferFunc = a->data().toInt();
385 updateColorspace();
386 }
387
overrideYCbCrEncChanged(QAction * a)388 void ApplicationWindow::overrideYCbCrEncChanged(QAction *a)
389 {
390 m_overrideYCbCrEnc = a->data().toInt();
391 updateColorspace();
392 }
393
overrideQuantChanged(QAction * a)394 void ApplicationWindow::overrideQuantChanged(QAction *a)
395 {
396 m_overrideQuantization = a->data().toInt();
397 updateColorspace();
398 }
399
setDevice(const QString & device,bool rawOpen)400 void ApplicationWindow::setDevice(const QString &device, bool rawOpen)
401 {
402 closeDevice();
403 m_sigMapper = new QSignalMapper(this);
404 #if QT_VERSION < 0x060000
405 connect(m_sigMapper, SIGNAL(mapped(int)), this, SLOT(ctrlAction(int)));
406 #else
407 connect(m_sigMapper, &QSignalMapper::mappedInt, this, &ApplicationWindow::ctrlAction);
408 #endif
409
410 s_direct(rawOpen);
411
412 if (open(device.toLatin1(), true) < 0) {
413 #ifdef HAVE_ALSA
414 m_audioBufferAct->setEnabled(false);
415 #endif
416 return;
417 }
418
419 newCaptureWin();
420
421 QWidget *w = new QWidget(m_tabs);
422 m_genTab = new GeneralTab(device, this, 4, w);
423 int m_winWidth = m_genTab->getWidth();
424
425 #ifdef HAVE_ALSA
426 if (m_genTab->hasAlsaAudio()) {
427 connect(m_genTab, SIGNAL(audioDeviceChanged()), this, SLOT(changeAudioDevice()));
428 m_audioBufferAct->setEnabled(true);
429 } else {
430 m_audioBufferAct->setEnabled(false);
431 }
432 #endif
433 connect(m_genTab, SIGNAL(pixelAspectRatioChanged()), this, SLOT(updatePixelAspectRatio()));
434 connect(m_genTab, SIGNAL(croppingChanged()), this, SLOT(updateCropping()));
435 connect(m_genTab, SIGNAL(clearBuffers()), this, SLOT(clearBuffers()));
436 m_tabs->addTab(w, "General Settings");
437
438 if (has_vid_out()) {
439 addTpgTab(m_minWidth);
440 tpg_init(&m_tpg, 640, 360);
441 updateLimRGBRange();
442 }
443
444 addTabs(m_winWidth);
445 m_vbiTab = NULL;
446 if (has_vbi_cap()) {
447 w = new QWidget(m_tabs);
448 m_vbiTab = new VbiTab(w);
449 m_tabs->addTab(w, "VBI");
450 }
451 if (QWidget *current = m_tabs->currentWidget()) {
452 current->show();
453 }
454 statusBar()->clearMessage();
455 m_tabs->show();
456 m_tabs->setFocus();
457 if (rawOpen)
458 m_convertData = v4lconvert_create_with_dev_ops(g_fd(), NULL, &rawDevOps);
459 else
460 m_convertData = v4lconvert_create(g_fd());
461 bool canStream = has_rw() || has_streaming();
462 bool isCapture = v4l_type_is_capture(g_type()) && !has_radio_tx();
463 m_capStartAct->setEnabled(canStream || isCapture);
464 m_capStepAct->setEnabled(canStream && isCapture && !has_radio_rx());
465 m_saveRawAct->setEnabled(canStream && has_vid_cap());
466 m_snapshotAct->setEnabled(canStream && has_vid_cap());
467 m_capMenu->setEnabled(canStream && isCapture && !has_radio_rx());
468 #ifdef HAVE_QTGL
469 m_useGLAct->setEnabled(CaptureWinGL::isSupported());
470 #endif
471 m_genTab->sourceChangeSubscribe();
472 subscribeCtrlEvents();
473 m_ctrlNotifier = new QSocketNotifier(g_fd(), QSocketNotifier::Exception, m_tabs);
474 connect(m_ctrlNotifier, SIGNAL(activated(int)), this, SLOT(ctrlEvent()));
475 }
476
opendev()477 void ApplicationWindow::opendev()
478 {
479 QFileDialog d(this, "Select v4l device", "/dev", "V4L Devices (video* vbi* radio* swradio* v4l-touch*)");
480
481 d.setFilter(QDir::AllDirs | QDir::Files | QDir::System);
482 d.setFileMode(QFileDialog::ExistingFile);
483 if (d.exec())
484 setDevice(d.selectedFiles().first(), false);
485 }
486
openrawdev()487 void ApplicationWindow::openrawdev()
488 {
489 QFileDialog d(this, "Select v4l device", "/dev", "V4L Devices (video* vbi* radio* swradio* v4l-touch*)");
490
491 d.setFilter(QDir::AllDirs | QDir::Files | QDir::System);
492 d.setFileMode(QFileDialog::ExistingFile);
493 if (d.exec())
494 setDevice(d.selectedFiles().first(), true);
495 }
496
setRenderMethod(bool checked)497 void ApplicationWindow::setRenderMethod(bool checked)
498 {
499 if (m_capStartAct->isChecked()) {
500 m_useGLAct->setChecked(m_renderMethod == QV4L2_RENDER_GL);
501 return;
502 }
503
504 m_renderMethod = checked ? QV4L2_RENDER_GL : QV4L2_RENDER_QT;
505 m_useBlendingAct->setEnabled(m_renderMethod == QV4L2_RENDER_GL);
506
507 newCaptureWin();
508 }
509
setBlending(bool checked)510 void ApplicationWindow::setBlending(bool checked)
511 {
512 if (m_capture)
513 m_capture->setBlending(checked);
514 }
515
setLinearFilter(bool checked)516 void ApplicationWindow::setLinearFilter(bool checked)
517 {
518 if (m_capture)
519 m_capture->setLinearFilter(checked);
520 }
521
setAudioBufferSize()522 void ApplicationWindow::setAudioBufferSize()
523 {
524 bool ok;
525 int buffer = QInputDialog::getInt(this, "Audio Device Buffer Size", "Capacity in ms:",
526 m_genTab->getAudioDeviceBufferSize(), 1, 65535, 1, &ok);
527
528 if (ok) {
529 m_genTab->setAudioDeviceBufferSize(buffer);
530 changeAudioDevice();
531 }
532 }
533
534
ctrlEvent()535 void ApplicationWindow::ctrlEvent()
536 {
537 v4l2_event ev;
538 int event_ret = 0;
539
540 while ((event_ret = dqevent(ev)) == 0) {
541 if (ev.type == V4L2_EVENT_SOURCE_CHANGE) {
542 m_genTab->sourceChange(ev);
543 continue;
544 }
545 if (ev.type != V4L2_EVENT_CTRL)
546 continue;
547 m_ctrlMap[ev.id].flags = ev.u.ctrl.flags;
548 m_ctrlMap[ev.id].minimum = ev.u.ctrl.minimum;
549 m_ctrlMap[ev.id].maximum = ev.u.ctrl.maximum;
550 m_ctrlMap[ev.id].step = ev.u.ctrl.step;
551 m_ctrlMap[ev.id].default_value = ev.u.ctrl.default_value;
552
553 bool disabled = m_ctrlMap[ev.id].flags & CTRL_FLAG_DISABLED;
554
555 if (qobject_cast<QLineEdit *>(m_widgetMap[ev.id]))
556 static_cast<QLineEdit *>(m_widgetMap[ev.id])->setReadOnly(disabled);
557 else
558 m_widgetMap[ev.id]->setDisabled(disabled);
559 if (m_sliderMap.find(ev.id) != m_sliderMap.end())
560 m_sliderMap[ev.id]->setDisabled(disabled);
561 if (ev.u.ctrl.changes & V4L2_EVENT_CTRL_CH_RANGE)
562 updateCtrlRange(ev.id, ev.u.ctrl.value);
563 switch (m_ctrlMap[ev.id].type) {
564 case V4L2_CTRL_TYPE_INTEGER:
565 case V4L2_CTRL_TYPE_INTEGER_MENU:
566 case V4L2_CTRL_TYPE_MENU:
567 case V4L2_CTRL_TYPE_BOOLEAN:
568 case V4L2_CTRL_TYPE_BITMASK:
569 setVal(ev.id, ev.u.ctrl.value);
570 break;
571 case V4L2_CTRL_TYPE_INTEGER64:
572 setVal64(ev.id, ev.u.ctrl.value64);
573 break;
574 default:
575 break;
576 }
577 if (m_ctrlMap[ev.id].type != V4L2_CTRL_TYPE_STRING)
578 continue;
579 query_ext_ctrl(m_ctrlMap[ev.id]);
580
581 struct v4l2_ext_control c;
582 struct v4l2_ext_controls ctrls;
583
584 c.id = ev.id;
585 c.size = m_ctrlMap[ev.id].maximum + 1;
586 c.string = (char *)malloc(c.size);
587 memset(&ctrls, 0, sizeof(ctrls));
588 ctrls.count = 1;
589 ctrls.which = 0;
590 ctrls.controls = &c;
591 if (!g_ext_ctrls(ctrls))
592 setString(ev.id, c.string);
593 free(c.string);
594 }
595
596 if (event_ret && errno == ENODEV) {
597 closeDevice();
598 if (m_capture) {
599 m_capture->stop();
600 delete m_capture;
601 m_capture = NULL;
602 }
603 }
604 }
605
newCaptureWin()606 void ApplicationWindow::newCaptureWin()
607 {
608 if (m_capture != NULL) {
609 m_capture->stop();
610 delete m_capture;
611 m_capture = NULL;
612 }
613
614 switch (m_renderMethod) {
615 case QV4L2_RENDER_GL:
616 m_capture = new CaptureWinGL(this);
617 break;
618 default:
619 m_capture = new CaptureWinQt(this);
620 break;
621 }
622
623 m_capture->setPixelAspectRatio(1.0);
624 m_capture->enableScaling(m_scalingAct->isChecked());
625 connect(m_capture, SIGNAL(close()), this, SLOT(closeCaptureWin()));
626 }
627
startStreaming()628 bool ApplicationWindow::startStreaming()
629 {
630 startAudio();
631
632 if (!m_genTab->isSDR() && m_genTab->isRadio()) {
633 s_priority(m_genTab->usePrio());
634 return true;
635 }
636
637 m_queue.init(g_type(), m_capMethod);
638
639 #ifdef HAVE_QTGL
640 m_useGLAct->setEnabled(false);
641 #endif
642
643 switch (m_capMethod) {
644 case methodRead:
645 m_snapshotAct->setEnabled(true);
646 m_genTab->setHaveBuffers(true);
647 s_priority(m_genTab->usePrio());
648 /* Nothing to do. */
649 return true;
650
651 case methodMmap:
652 case methodUser:
653 if (m_queue.reqbufs(this, m_genTab->getNumBuffers())) {
654 error("Cannot capture");
655 break;
656 }
657
658 if (m_queue.g_buffers() < 2) {
659 error("Too few buffers");
660 break;
661 }
662
663 if (m_queue.obtain_bufs(this)) {
664 error("Get buffers");
665 break;
666 }
667
668 if (v4l_type_is_capture(g_type())) {
669 for (unsigned i = 0; i < m_queue.g_buffers(); i++) {
670 cv4l_buffer buf;
671
672 m_queue.buffer_init(buf, i);
673 if (qbuf(buf)) {
674 error("Couldn't queue buffer\n");
675 goto error;
676 }
677 }
678 } else {
679 for (unsigned i = 0; i < m_queue.g_buffers(); i++) {
680 cv4l_buffer buf;
681
682 m_queue.buffer_init(buf, i);
683 buf.s_field(m_tpgField);
684 tpg_s_field(&m_tpg, m_tpgField, m_tpgFieldAlt);
685 if (m_tpgField == V4L2_FIELD_TOP)
686 m_tpgField = V4L2_FIELD_BOTTOM;
687 else if (m_tpgField == V4L2_FIELD_BOTTOM)
688 m_tpgField = V4L2_FIELD_TOP;
689 for (unsigned p = 0; p < m_queue.g_num_planes(); p++) {
690 tpg_fillbuffer(&m_tpg, m_tpgStd, p, (u8 *)m_queue.g_dataptr(i, p));
691 buf.s_bytesused(buf.g_length(p), p);
692 }
693 if (qbuf(buf)) {
694 error("Couldn't queue buffer\n");
695 goto error;
696 }
697 tpg_update_mv_count(&m_tpg, V4L2_FIELD_HAS_T_OR_B(m_tpgField));
698 }
699 }
700
701 if (streamon()) {
702 perror("VIDIOC_STREAMON");
703 break;
704 }
705 m_snapshotAct->setEnabled(true);
706 m_genTab->setHaveBuffers(true);
707 s_priority(m_genTab->usePrio());
708 return true;
709 }
710
711 error:
712 m_queue.free(this);
713 delete m_ctrlNotifier;
714 reopen(true);
715 m_genTab->sourceChangeSubscribe();
716 subscribeCtrlEvents();
717 m_ctrlNotifier = new QSocketNotifier(g_fd(), QSocketNotifier::Exception, m_tabs);
718 connect(m_ctrlNotifier, SIGNAL(activated(int)), this, SLOT(ctrlEvent()));
719 m_capStartAct->setChecked(false);
720 #ifdef HAVE_QTGL
721 m_useGLAct->setEnabled(CaptureWinGL::isSupported());
722 #endif
723 return false;
724 }
725
calculateFps()726 void ApplicationWindow::calculateFps()
727 {
728 static time_t last_sec;
729
730 if (m_frame == 0) {
731 clock_gettime(CLOCK_MONOTONIC, &m_startTimestamp);
732 last_sec = 0;
733 } else {
734 struct timespec ts_cur, res;
735
736 clock_gettime(CLOCK_MONOTONIC, &ts_cur);
737 res.tv_sec = ts_cur.tv_sec - m_startTimestamp.tv_sec;
738 res.tv_nsec = ts_cur.tv_nsec - m_startTimestamp.tv_nsec;
739 if (res.tv_nsec < 0) {
740 res.tv_sec--;
741 res.tv_nsec += 1000000000;
742 }
743 if (res.tv_sec > last_sec) {
744 m_fps = (10000 * m_frame) /
745 (res.tv_sec * 100 + res.tv_nsec / 10000000);
746 m_fps /= 100.0;
747 last_sec = res.tv_sec;
748 }
749 }
750 }
751
capVbiFrame()752 void ApplicationWindow::capVbiFrame()
753 {
754 cv4l_buffer buf(m_queue);
755 __u8 *data = NULL;
756 int s = 0;
757
758 if (m_singleStep)
759 m_capNotifier->setEnabled(false);
760
761 switch (m_capMethod) {
762 case methodRead:
763 s = read(m_frameData, m_vbiSize);
764 if (s < 0) {
765 if (errno != EAGAIN) {
766 error("read");
767 m_capStartAct->setChecked(false);
768 }
769 return;
770 }
771 data = m_frameData;
772 break;
773
774 case methodMmap:
775 case methodUser:
776 if (dqbuf(buf)) {
777 if (errno == EAGAIN)
778 return;
779 error("dqbuf");
780 m_capStartAct->setChecked(false);
781 return;
782 }
783 if (buf.g_flags() & V4L2_BUF_FLAG_ERROR) {
784 if (qbuf(buf)) {
785 error("Couldn't queue buffer\n");
786 m_capStartAct->setChecked(false);
787 }
788 return;
789 }
790 data = (__u8 *)m_queue.g_dataptr(buf.g_index(), 0);
791 s = buf.g_bytesused();
792 break;
793 }
794 if (g_type() == V4L2_BUF_TYPE_VBI_CAPTURE && s != m_vbiSize) {
795 error("incorrect vbi size");
796 m_capStartAct->setChecked(false);
797 return;
798 }
799 if (showFrames() && g_type() == V4L2_BUF_TYPE_VBI_CAPTURE) {
800 for (unsigned y = 0; y < m_vbiHeight; y++) {
801 __u8 *p = data + y * m_vbiWidth;
802 __u8 *q = m_capImage->bits() + y * m_capImage->bytesPerLine();
803
804 for (unsigned x = 0; x < m_vbiWidth; x++) {
805 *q++ = *p;
806 *q++ = *p;
807 *q++ = *p++;
808 }
809 }
810 }
811
812 struct v4l2_sliced_vbi_format sfmt;
813 struct v4l2_sliced_vbi_data sdata[m_vbiHandle.count[0] + m_vbiHandle.count[1]];
814 struct v4l2_sliced_vbi_data *p;
815
816 if (g_type() == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) {
817 p = (struct v4l2_sliced_vbi_data *)data;
818 } else {
819 vbi_parse(&m_vbiHandle, data, &sfmt, sdata);
820 s = sizeof(sdata);
821 p = sdata;
822 }
823
824 if (m_capMethod != methodRead) {
825 if (qbuf(buf)) {
826 error("Couldn't queue buffer\n");
827 m_capStartAct->setChecked(false);
828 return;
829 }
830 }
831
832 m_vbiTab->slicedData(p, s / sizeof(p[0]));
833
834 QString status, curStatus;
835
836 calculateFps();
837 status = QString("Frame: %1 Fps: %2").arg(++m_frame).arg(m_fps, 0, 'f', 2, '0');
838 if (showFrames() && g_type() == V4L2_BUF_TYPE_VBI_CAPTURE)
839 m_capture->setFrame(m_capImage->width(), m_capImage->height(),
840 m_capDestFormat.fmt.pix.pixelformat, m_capImage->bits(),
841 NULL, NULL);
842
843 curStatus = statusBar()->currentMessage();
844 if (curStatus.isEmpty() || curStatus.startsWith("Frame: "))
845 statusBar()->showMessage(status);
846 if (m_frame == 1)
847 refresh();
848 }
849
capSdrFrame()850 void ApplicationWindow::capSdrFrame()
851 {
852 cv4l_buffer buf(m_queue);
853 __u8 *data = NULL;
854 int s = 0;
855
856 if (m_singleStep)
857 m_capNotifier->setEnabled(false);
858
859 switch (m_capMethod) {
860 case methodRead:
861 s = read(m_frameData, m_sdrSize);
862 if (s < 0) {
863 if (errno != EAGAIN) {
864 error("read");
865 m_capStartAct->setChecked(false);
866 }
867 return;
868 }
869 data = m_frameData;
870 break;
871
872 case methodMmap:
873 case methodUser:
874 if (dqbuf(buf)) {
875 if (errno == EAGAIN)
876 return;
877 error("dqbuf");
878 m_capStartAct->setChecked(false);
879 return;
880 }
881 if (buf.g_flags() & V4L2_BUF_FLAG_ERROR) {
882 if (qbuf(buf)) {
883 error("Couldn't queue buffer\n");
884 m_capStartAct->setChecked(false);
885 }
886 return;
887 }
888 data = (__u8 *)m_queue.g_dataptr(buf.g_index(), 0);
889 s = buf.g_bytesused();
890 break;
891 }
892 if (s != m_sdrSize) {
893 error("incorrect sdr size");
894 m_capStartAct->setChecked(false);
895 return;
896 }
897 if (showFrames()) {
898 unsigned width = m_sdrSize / 2 - 1;
899
900 if (SDR_WIDTH < width)
901 width = SDR_WIDTH;
902
903 m_capImage->fill(0);
904 /*
905 * Draw two waveforms, each consisting of the first 'width + 1' samples
906 * of the buffer, the top is for the I, the bottom is for the Q values.
907 */
908 for (unsigned i = 0; i < 2; i++) {
909 unsigned start = 255 - data[i];
910
911 for (unsigned x = 0; x < width; x++) {
912 unsigned next = 255 - data[2 + 2 * x + i];
913 unsigned low = start < next ? start : next;
914 unsigned high = start > next ? start : next;
915 __u8 *q = m_capImage->bits() + x * 3 +
916 (i * 256 + low) * m_capImage->bytesPerLine();
917
918 while (low++ <= high) {
919 q[0] = 255;
920 q[1] = 255;
921 q[2] = 255;
922 q += m_capImage->bytesPerLine();
923 }
924 start = next;
925 }
926 }
927 }
928
929 if (m_capMethod != methodRead) {
930 if (qbuf(buf)) {
931 error("Couldn't queue buffer\n");
932 m_capStartAct->setChecked(false);
933 return;
934 }
935 }
936
937 QString status, curStatus;
938
939 calculateFps();
940 status = QString("Frame: %1 Fps: %2").arg(++m_frame).arg(m_fps, 0, 'f', 2, '0');
941 if (showFrames())
942 m_capture->setFrame(m_capImage->width(), m_capImage->height(),
943 m_capDestFormat.fmt.pix.pixelformat, m_capImage->bits(),
944 NULL, NULL);
945
946 curStatus = statusBar()->currentMessage();
947 if (curStatus.isEmpty() || curStatus.startsWith("Frame: "))
948 statusBar()->showMessage(status);
949 if (m_frame == 1)
950 refresh();
951 }
952
outFrame()953 void ApplicationWindow::outFrame()
954 {
955 cv4l_buffer buf(m_queue);
956 int s = 0;
957
958 switch (m_capMethod) {
959 case methodRead:
960 tpg_fillbuffer(&m_tpg, m_tpgStd, 0, (u8 *)m_frameData);
961 s = write(m_frameData, m_tpgSizeImage);
962 tpg_update_mv_count(&m_tpg, V4L2_FIELD_HAS_T_OR_B(m_tpgField));
963
964 if (s < 0) {
965 if (errno != EAGAIN) {
966 error("write");
967 m_capStartAct->setChecked(false);
968 }
969 return;
970 }
971 break;
972
973 case methodMmap:
974 case methodUser:
975 if (dqbuf(buf)) {
976 if (errno == EAGAIN)
977 return;
978 error("dqbuf");
979 m_capStartAct->setChecked(false);
980 return;
981 }
982 m_queue.buffer_init(buf, buf.g_index());
983 buf.s_field(m_tpgField);
984 tpg_s_field(&m_tpg, m_tpgField, m_tpgFieldAlt);
985 if (m_tpgField == V4L2_FIELD_TOP)
986 m_tpgField = V4L2_FIELD_BOTTOM;
987 else if (m_tpgField == V4L2_FIELD_BOTTOM)
988 m_tpgField = V4L2_FIELD_TOP;
989 for (unsigned p = 0; p < m_queue.g_num_planes(); p++) {
990 tpg_fillbuffer(&m_tpg, m_tpgStd, p, (u8 *)m_queue.g_dataptr(buf.g_index(), p));
991 buf.s_bytesused(buf.g_length(p), p);
992 }
993 tpg_update_mv_count(&m_tpg, V4L2_FIELD_HAS_T_OR_B(m_tpgField));
994 break;
995 }
996
997 QString status, curStatus;
998
999 calculateFps();
1000 status = QString("Frame: %1 Fps: %2").arg(++m_frame).arg(m_fps, 0, 'f', 2, '0');
1001
1002 if (m_capMethod == methodMmap || m_capMethod == methodUser) {
1003 if (m_clear[buf.g_index()]) {
1004 for (unsigned p = 0; p < m_queue.g_num_planes(); p++)
1005 memset(m_queue.g_dataptr(buf.g_index(), p), 0, buf.g_length(p));
1006 m_clear[buf.g_index()] = false;
1007 }
1008
1009 if (qbuf(buf)) {
1010 error("Couldn't queue buffer\n");
1011 m_capStartAct->setChecked(false);
1012 return;
1013 }
1014 }
1015
1016 curStatus = statusBar()->currentMessage();
1017 if (curStatus.isEmpty() || curStatus.startsWith("Frame: "))
1018 statusBar()->showMessage(status);
1019 if (m_frame == 1)
1020 refresh();
1021 }
1022
capFrame()1023 void ApplicationWindow::capFrame()
1024 {
1025 cv4l_buffer buf(m_queue);
1026 unsigned char *plane[3];
1027 unsigned bytesused[3];
1028 int s = 0;
1029 int err = 0;
1030 #ifdef HAVE_ALSA
1031 struct timeval tv_alsa;
1032 #endif
1033
1034 if (m_singleStep)
1035 m_capNotifier->setEnabled(false);
1036
1037 plane[0] = plane[1] = plane[2] = NULL;
1038 switch (m_capMethod) {
1039 case methodRead:
1040 s = read(m_frameData, m_capSrcFormat.g_sizeimage(0));
1041 #ifdef HAVE_ALSA
1042 alsa_thread_timestamp(&tv_alsa);
1043 #endif
1044
1045 if (s < 0) {
1046 if (errno != EAGAIN) {
1047 error("read");
1048 m_capStartAct->setChecked(false);
1049 }
1050 return;
1051 }
1052 if (m_makeSnapshot)
1053 makeSnapshot((unsigned char *)m_frameData, s);
1054 if (m_saveRaw.openMode())
1055 m_saveRaw.write((const char *)m_frameData, s);
1056
1057 plane[0] = m_frameData;
1058 if (showFrames() && m_mustConvert) {
1059 err = v4lconvert_convert(m_convertData, &m_capSrcFormat, &m_capDestFormat,
1060 m_frameData, s,
1061 m_capImage->bits(), m_capDestFormat.fmt.pix.sizeimage);
1062 if (err != -1)
1063 plane[0] = m_capImage->bits();
1064 }
1065 break;
1066
1067 case methodMmap:
1068 case methodUser:
1069 if (dqbuf(buf)) {
1070 if (errno == EAGAIN)
1071 return;
1072 error("dqbuf");
1073 m_capStartAct->setChecked(false);
1074 return;
1075 }
1076 if (buf.g_flags() & V4L2_BUF_FLAG_ERROR) {
1077 printf("error\n");
1078 if (qbuf(buf)) {
1079 error("Couldn't queue buffer\n");
1080 m_capStartAct->setChecked(false);
1081 }
1082 return;
1083 }
1084
1085 #ifdef HAVE_ALSA
1086 alsa_thread_timestamp(&tv_alsa);
1087 #endif
1088
1089 plane[0] = (__u8 *)m_queue.g_dataptr(buf.g_index(), 0);
1090 plane[1] = (__u8 *)m_queue.g_dataptr(buf.g_index(), 1);
1091 plane[2] = (__u8 *)m_queue.g_dataptr(buf.g_index(), 2);
1092 plane[0] += buf.g_data_offset(0);
1093 bytesused[0] = buf.g_bytesused(0) - buf.g_data_offset(0);
1094 if (plane[1]) {
1095 plane[1] += buf.g_data_offset(1);
1096 bytesused[1] = buf.g_bytesused(1) - buf.g_data_offset(1);
1097 }
1098 if (plane[2]) {
1099 plane[2] += buf.g_data_offset(2);
1100 bytesused[2] = buf.g_bytesused(2) - buf.g_data_offset(2);
1101 }
1102 if (showFrames() && m_mustConvert) {
1103 err = v4lconvert_convert(m_convertData, &m_capSrcFormat, &m_capDestFormat,
1104 plane[0], bytesused[0], m_capImage->bits(),
1105 m_capDestFormat.fmt.pix.sizeimage);
1106 if (err != -1) {
1107 plane[0] = m_capImage->bits();
1108 bytesused[0] = m_capDestFormat.fmt.pix.sizeimage;
1109 }
1110 }
1111 if (m_makeSnapshot)
1112 makeSnapshot(plane[0], bytesused[0]);
1113 if (m_saveRaw.openMode())
1114 m_saveRaw.write((const char *)plane[0], bytesused[0]);
1115
1116 break;
1117 }
1118 if (err == -1 && m_frame == 0)
1119 error(v4lconvert_get_error_message(m_convertData));
1120
1121 QString status, curStatus;
1122
1123 calculateFps();
1124
1125 float wscale = m_capture->getHorScaleFactor();
1126 float hscale = m_capture->getVertScaleFactor();
1127 status = QString("Frame: %1 Fps: %2 Scale Factors: %3x%4").arg(++m_frame)
1128 .arg(m_fps, 0, 'f', 2, '0').arg(wscale).arg(hscale);
1129 if (m_capMethod != methodRead)
1130 status.append(QString(" SeqNr: %1").arg(buf.g_sequence()));
1131 #ifdef HAVE_ALSA
1132 if (m_capMethod != methodRead && alsa_thread_is_running()) {
1133 if (tv_alsa.tv_sec || tv_alsa.tv_usec) {
1134 m_totalAudioLatency.tv_sec += buf.g_timestamp().tv_sec - tv_alsa.tv_sec;
1135 m_totalAudioLatency.tv_usec += buf.g_timestamp().tv_usec - tv_alsa.tv_usec;
1136 }
1137 status.append(QString(" Average A-V: %1 ms")
1138 .arg((m_totalAudioLatency.tv_sec * 1000 + m_totalAudioLatency.tv_usec / 1000) / m_frame));
1139 }
1140 #endif
1141 if (plane[0] == NULL && showFrames())
1142 status.append(" Error: Unsupported format.");
1143
1144 if (showFrames())
1145 m_capture->setFrame(m_capImage->width(), m_capImage->height(),
1146 m_capDestFormat.g_pixelformat(),
1147 plane[0], plane[1], plane[2]);
1148
1149 if (m_capMethod == methodMmap || m_capMethod == methodUser) {
1150 if (m_clear[buf.g_index()]) {
1151 memset(m_queue.g_dataptr(buf.g_index(), 0), 0, buf.g_length());
1152 if (V4L2_TYPE_IS_MULTIPLANAR(buf.g_type())) {
1153 memset(m_queue.g_dataptr(buf.g_index(), 1), 0, buf.g_length(1));
1154 if (m_queue.g_dataptr(buf.g_index(), 2))
1155 memset(m_queue.g_dataptr(buf.g_index(), 2), 0, buf.g_length(2));
1156 }
1157 m_clear[buf.g_index()] = false;
1158 }
1159
1160 if (qbuf(buf)) {
1161 error("Couldn't queue buffer\n");
1162 m_capStartAct->setChecked(false);
1163 return;
1164 }
1165 }
1166
1167 curStatus = statusBar()->currentMessage();
1168 if (curStatus.isEmpty() || curStatus.startsWith("Frame: ") || curStatus.startsWith("No frame"))
1169 statusBar()->showMessage(status);
1170 if (m_frame == 1)
1171 refresh();
1172 }
1173
stopStreaming()1174 void ApplicationWindow::stopStreaming()
1175 {
1176 bool canStream = g_fd() >= 0 && (v4l_type_is_capture(g_type()) || has_vid_out()) &&
1177 !has_radio_tx();
1178 v4l2_encoder_cmd cmd;
1179
1180 m_singleStep = false;
1181 m_capStepAct->setEnabled(canStream && v4l_type_is_capture(g_type()));
1182 stopAudio();
1183
1184 s_priority(V4L2_PRIORITY_DEFAULT);
1185
1186 if (!m_genTab->isSDR() && m_genTab->isRadio())
1187 return;
1188
1189 if (v4l_type_is_capture(g_type()) && m_capture)
1190 m_capture->stop();
1191
1192 m_snapshotAct->setDisabled(true);
1193 #ifdef HAVE_QTGL
1194 m_useGLAct->setEnabled(CaptureWinGL::isSupported());
1195 #endif
1196 switch (m_capMethod) {
1197 case methodRead:
1198 if (v4l_type_is_capture(g_type())) {
1199 memset(&cmd, 0, sizeof(cmd));
1200 cmd.cmd = V4L2_ENC_CMD_STOP;
1201 encoder_cmd(cmd);
1202 }
1203 break;
1204
1205 case methodMmap:
1206 case methodUser:
1207 m_queue.free(this);
1208 break;
1209 }
1210 delete m_ctrlNotifier;
1211 reopen(true);
1212 m_genTab->sourceChangeSubscribe();
1213 subscribeCtrlEvents();
1214 m_ctrlNotifier = new QSocketNotifier(g_fd(), QSocketNotifier::Exception, m_tabs);
1215 connect(m_ctrlNotifier, SIGNAL(activated(int)), this, SLOT(ctrlEvent()));
1216 m_genTab->setHaveBuffers(false);
1217 refresh();
1218 }
1219
showFrames()1220 bool ApplicationWindow::showFrames()
1221 {
1222 if (m_showFramesAct->isChecked() && !m_capture->isVisible() &&
1223 !m_genTab->isSlicedVbi())
1224 m_capture->show();
1225 if ((!m_showFramesAct->isChecked() && m_capture->isVisible()) ||
1226 m_genTab->isSlicedVbi())
1227 m_capture->hide();
1228 return m_showFramesAct->isChecked();
1229 }
1230
traceIoctls(bool enable)1231 void ApplicationWindow::traceIoctls(bool enable)
1232 {
1233 s_trace(enable);
1234 }
1235
enableScaling(bool enable)1236 void ApplicationWindow::enableScaling(bool enable)
1237 {
1238 if (m_capture != NULL)
1239 m_capture->enableScaling(enable);
1240 }
1241
updatePixelAspectRatio()1242 void ApplicationWindow::updatePixelAspectRatio()
1243 {
1244 if (m_capture != NULL && m_genTab != NULL)
1245 m_capture->setPixelAspectRatio(m_genTab->getPixelAspectRatio());
1246 }
1247
updateCropping()1248 void ApplicationWindow::updateCropping()
1249 {
1250 if (m_capture != NULL)
1251 m_capture->setCropMethod(m_genTab->getCropMethod());
1252 }
1253
clearBuffers()1254 void ApplicationWindow::clearBuffers()
1255 {
1256 if (m_capture)
1257 for (bool &b : m_clear)
1258 b = true;
1259 }
1260
startAudio()1261 void ApplicationWindow::startAudio()
1262 {
1263 #ifdef HAVE_ALSA
1264 m_totalAudioLatency.tv_sec = 0;
1265 m_totalAudioLatency.tv_usec = 0;
1266
1267 QString audIn = m_genTab->getAudioInDevice();
1268 QString audOut = m_genTab->getAudioOutDevice();
1269
1270 if (audIn != nullptr && audOut != nullptr && audIn.compare("None") && audIn.compare(audOut) != 0) {
1271 alsa_thread_startup(audOut.toLatin1().data(), audIn.toLatin1().data(),
1272 m_genTab->getAudioDeviceBufferSize(), NULL, 0);
1273
1274 if (m_genTab->isRadio())
1275 statusBar()->showMessage("Capturing audio");
1276 }
1277 #endif
1278 }
1279
stopAudio()1280 void ApplicationWindow::stopAudio()
1281 {
1282 #ifdef HAVE_ALSA
1283 if (m_genTab != NULL && m_genTab->isRadio())
1284 statusBar()->showMessage("");
1285 alsa_thread_stop();
1286 #endif
1287 }
1288
changeAudioDevice()1289 void ApplicationWindow::changeAudioDevice()
1290 {
1291 stopAudio();
1292 if (m_capStartAct->isChecked()) {
1293 startAudio();
1294 }
1295 }
1296
closeCaptureWin()1297 void ApplicationWindow::closeCaptureWin()
1298 {
1299 m_capStartAct->setChecked(false);
1300 }
1301
outStart(bool start)1302 void ApplicationWindow::outStart(bool start)
1303 {
1304 if (start) {
1305 cv4l_fmt fmt;
1306 v4l2_output out;
1307 v4l2_control ctrl = { V4L2_CID_DV_TX_RGB_RANGE };
1308 unsigned p;
1309 int factor = 1;
1310
1311 g_output(out.index);
1312 enum_output(out, true, out.index);
1313 m_frame = m_fps = 0;
1314 m_capMethod = m_genTab->capMethod();
1315 g_fmt(fmt);
1316 fmt.s_flags(0);
1317 if (out.capabilities & V4L2_OUT_CAP_STD)
1318 g_std(m_tpgStd);
1319 else
1320 m_tpgStd = 0;
1321 m_tpgField = fmt.g_first_field(m_tpgStd);
1322 m_tpgFieldAlt = fmt.g_field() == V4L2_FIELD_ALTERNATE;
1323 m_tpgSizeImage = fmt.g_sizeimage(0);
1324 tpg_alloc(&m_tpg, fmt.g_width());
1325 m_useTpg = tpg_s_fourcc(&m_tpg, fmt.g_pixelformat());
1326 if (V4L2_FIELD_HAS_T_OR_B(fmt.g_field()))
1327 factor = 2;
1328 tpg_reset_source(&m_tpg, fmt.g_width(), fmt.g_height() * factor, fmt.g_field());
1329 tpg_update_mv_step(&m_tpg);
1330 tpg_init_mv_count(&m_tpg);
1331 if (g_ctrl(ctrl))
1332 tpg_s_rgb_range(&m_tpg, V4L2_DV_RGB_RANGE_AUTO);
1333 else
1334 tpg_s_rgb_range(&m_tpg, ctrl.value);
1335 tpgColorspaceChanged();
1336 s_fmt(fmt);
1337
1338 if (out.capabilities & V4L2_OUT_CAP_STD) {
1339 tpg_s_pixel_aspect(&m_tpg, (m_tpgStd & V4L2_STD_525_60) ?
1340 TPG_PIXEL_ASPECT_NTSC : TPG_PIXEL_ASPECT_PAL);
1341 } else if (out.capabilities & V4L2_OUT_CAP_DV_TIMINGS) {
1342 v4l2_dv_timings timings;
1343
1344 g_dv_timings(timings);
1345 if (timings.bt.width == 720 && timings.bt.height <= 576)
1346 tpg_s_pixel_aspect(&m_tpg, timings.bt.height == 480 ?
1347 TPG_PIXEL_ASPECT_NTSC : TPG_PIXEL_ASPECT_PAL);
1348 else
1349 tpg_s_pixel_aspect(&m_tpg, TPG_PIXEL_ASPECT_SQUARE);
1350 } else {
1351 tpg_s_pixel_aspect(&m_tpg, TPG_PIXEL_ASPECT_SQUARE);
1352 }
1353
1354 for (p = 0; p < fmt.g_num_planes(); p++)
1355 tpg_s_bytesperline(&m_tpg, p, fmt.g_bytesperline(p));
1356 if (m_capMethod == methodRead)
1357 m_frameData = new unsigned char[fmt.g_sizeimage(0)];
1358 if (startStreaming()) {
1359 m_outNotifier = new QSocketNotifier(g_fd(), QSocketNotifier::Write, m_tabs);
1360 connect(m_outNotifier, SIGNAL(activated(int)), this, SLOT(outFrame()));
1361 }
1362 } else {
1363 stopStreaming();
1364 tpg_free(&m_tpg);
1365 delete m_frameData;
1366 m_frameData = NULL;
1367 delete m_outNotifier;
1368 m_outNotifier = NULL;
1369 }
1370 }
1371
capStep(bool checked)1372 void ApplicationWindow::capStep(bool checked)
1373 {
1374 if (!m_capStartAct->isChecked()) {
1375 m_singleStep = true;
1376 m_capStartAct->setChecked(true);
1377 } else if (m_singleStep) {
1378 m_capNotifier->setEnabled(true);
1379 }
1380 }
1381
capStart(bool start)1382 void ApplicationWindow::capStart(bool start)
1383 {
1384 if (!m_singleStep)
1385 m_capStepAct->setDisabled(true);
1386 if (m_genTab->isRadio() && !m_genTab->isSDR()) {
1387 if (start)
1388 startAudio();
1389 else
1390 stopAudio();
1391 return;
1392 }
1393 if (has_vid_out()) {
1394 outStart(start);
1395 return;
1396 }
1397
1398 if (!m_genTab->isSDR() && m_genTab->isRadio()) {
1399 if (start)
1400 startStreaming();
1401 else
1402 stopStreaming();
1403
1404 return;
1405 }
1406
1407 QImage::Format dstFmt = QImage::Format_RGB888;
1408 struct v4l2_fract interval;
1409 __u32 width, height, pixfmt;
1410 unsigned field;
1411
1412 if (!start) {
1413 stopStreaming();
1414 delete m_capNotifier;
1415 m_capNotifier = NULL;
1416 delete m_capImage;
1417 m_capImage = NULL;
1418 return;
1419 }
1420 m_frame = m_fps = 0;
1421 m_capMethod = m_genTab->capMethod();
1422
1423 if (m_genTab->isSlicedVbi()) {
1424 cv4l_fmt fmt;
1425 v4l2_std_id std;
1426
1427 s_type(V4L2_BUF_TYPE_SLICED_VBI_CAPTURE);
1428 if (g_std(std)) {
1429 error("this input isn't suitable for VBI\n");
1430 return;
1431 }
1432 if (g_fmt(fmt)) {
1433 error("could not obtain an VBI format\n");
1434 return;
1435 }
1436 fmt.fmt.sliced.service_set = (std & V4L2_STD_525_60) ?
1437 V4L2_SLICED_VBI_525 : V4L2_SLICED_VBI_625;
1438 s_fmt(fmt);
1439 memset(&m_vbiHandle, 0, sizeof(m_vbiHandle));
1440 m_vbiTab->slicedFormat(fmt.fmt.sliced);
1441 m_vbiSize = fmt.fmt.sliced.io_size;
1442 m_frameData = new unsigned char[m_vbiSize];
1443 if (startStreaming()) {
1444 m_capNotifier = new QSocketNotifier(g_fd(), QSocketNotifier::Read, m_tabs);
1445 connect(m_capNotifier, SIGNAL(activated(int)), this, SLOT(capVbiFrame()));
1446 }
1447 return;
1448 }
1449 if (m_genTab->isVbi()) {
1450 cv4l_fmt fmt;
1451 v4l2_std_id std;
1452
1453 s_type(V4L2_BUF_TYPE_VBI_CAPTURE);
1454 if (g_std(std)) {
1455 error("this input isn't suitable for VBI\n");
1456 return;
1457 }
1458 if (g_fmt(fmt)) {
1459 error("could not obtain an VBI format\n");
1460 return;
1461 }
1462 if (fmt.fmt.vbi.sample_format != V4L2_PIX_FMT_GREY) {
1463 error("non-grey pixelformat not supported for VBI\n");
1464 return;
1465 }
1466 s_fmt(fmt);
1467 if (!vbi_prepare(&m_vbiHandle, &fmt.fmt.vbi, std)) {
1468 error("no services possible\n");
1469 return;
1470 }
1471 m_capDestFormat.s_type(V4L2_BUF_TYPE_VIDEO_CAPTURE);
1472 m_capDestFormat.s_pixelformat(V4L2_PIX_FMT_RGB24);
1473 m_vbiTab->rawFormat(fmt.fmt.vbi);
1474 m_vbiWidth = fmt.fmt.vbi.samples_per_line;
1475 m_vbiHeight = fmt.fmt.vbi.count[0] + fmt.fmt.vbi.count[1];
1476 m_vbiSize = m_vbiWidth * m_vbiHeight;
1477 m_frameData = new unsigned char[m_vbiSize];
1478 m_capImage = new QImage(m_vbiWidth, m_vbiHeight, dstFmt);
1479 m_capImage->fill(0);
1480 m_capture->setWindowSize(QSize(m_vbiWidth, m_vbiHeight));
1481 m_capture->setFrame(m_capImage->width(), m_capImage->height(),
1482 m_capDestFormat.fmt.pix.pixelformat, m_capImage->bits(), NULL, NULL);
1483 if (showFrames())
1484 m_capture->show();
1485
1486 statusBar()->showMessage("No frame");
1487 if (startStreaming()) {
1488 m_capNotifier = new QSocketNotifier(g_fd(), QSocketNotifier::Read, m_tabs);
1489 connect(m_capNotifier, SIGNAL(activated(int)), this, SLOT(capVbiFrame()));
1490 }
1491 return;
1492 }
1493
1494 if (m_genTab->isSDR()) {
1495 cv4l_fmt fmt;
1496
1497 if (g_fmt(fmt)) {
1498 error("could not obtain an SDR format\n");
1499 return;
1500 }
1501 if (fmt.fmt.sdr.pixelformat != V4L2_SDR_FMT_CU8) {
1502 error("only CU8 is supported for SDR\n");
1503 return;
1504 }
1505 m_sdrSize = fmt.fmt.sdr.buffersize;
1506 m_capDestFormat.s_type(V4L2_BUF_TYPE_VIDEO_CAPTURE);
1507 m_capDestFormat.s_pixelformat(V4L2_PIX_FMT_RGB24);
1508 m_frameData = new unsigned char[m_sdrSize];
1509 m_capImage = new QImage(SDR_WIDTH, SDR_HEIGHT, dstFmt);
1510 m_capImage->fill(0);
1511 m_capture->setWindowSize(QSize(SDR_WIDTH, SDR_HEIGHT));
1512 m_capture->setFrame(m_capImage->width(), m_capImage->height(),
1513 m_capDestFormat.fmt.pix.pixelformat, m_capImage->bits(), NULL, NULL);
1514 if (showFrames())
1515 m_capture->show();
1516
1517 statusBar()->showMessage("No frame");
1518 if (startStreaming()) {
1519 m_capNotifier = new QSocketNotifier(g_fd(), QSocketNotifier::Read, m_tabs);
1520 connect(m_capNotifier, SIGNAL(activated(int)), this, SLOT(capSdrFrame()));
1521 }
1522 return;
1523 }
1524
1525 m_capSrcFormat.s_type(g_type());
1526 if (g_fmt(m_capSrcFormat)) {
1527 error("could not obtain a source format\n");
1528 return;
1529 }
1530 s_fmt(m_capSrcFormat);
1531 if (m_genTab->get_interval(interval))
1532 set_interval(interval);
1533
1534 m_frameData = new unsigned char[m_capSrcFormat.g_sizeimage(0) +
1535 m_capSrcFormat.g_sizeimage(1)];
1536 m_capDestFormat = m_capSrcFormat;
1537
1538 if (m_capture->hasNativeFormat(m_capSrcFormat.g_pixelformat())) {
1539 width = m_capSrcFormat.g_width();
1540 height = m_capSrcFormat.g_height();
1541 pixfmt = m_capSrcFormat.g_pixelformat();
1542 field = m_capSrcFormat.g_field();
1543 m_mustConvert = false;
1544 } else {
1545 m_mustConvert = true;
1546
1547 m_capDestFormat.s_pixelformat(V4L2_PIX_FMT_RGB24);
1548 // Make sure sizeimage is large enough. This is necessary if the mplane
1549 // plugin is in use since v4lconvert_try_format() bypasses the plugin.
1550 m_capDestFormat.s_sizeimage(m_capDestFormat.g_width() *
1551 m_capDestFormat.g_height() * 3);
1552 cv4l_fmt copy = m_capSrcFormat;
1553 v4lconvert_try_format(m_convertData, &m_capDestFormat, &m_capSrcFormat);
1554 // v4lconvert_try_format sometimes modifies the source format if it thinks
1555 // that there is a better format available. Restore our selected source
1556 // format since we do not want that happening.
1557 m_capSrcFormat = copy;
1558 width = m_capDestFormat.g_width();
1559 height = m_capDestFormat.g_height();
1560 pixfmt = m_capDestFormat.g_pixelformat();
1561 field = m_capDestFormat.g_field();
1562 }
1563
1564 // Ensure that the initial image is large enough for native 32 bit per pixel formats
1565 switch (pixfmt) {
1566 case V4L2_PIX_FMT_RGB32:
1567 case V4L2_PIX_FMT_BGR32:
1568 case V4L2_PIX_FMT_XRGB32:
1569 case V4L2_PIX_FMT_XBGR32:
1570 case V4L2_PIX_FMT_YUV32:
1571 case V4L2_PIX_FMT_XYUV32:
1572 case V4L2_PIX_FMT_VUYX32:
1573 case V4L2_PIX_FMT_YUVX32:
1574 dstFmt = QImage::Format_RGB32;
1575 break;
1576 case V4L2_PIX_FMT_ARGB32:
1577 case V4L2_PIX_FMT_ABGR32:
1578 case V4L2_PIX_FMT_AYUV32:
1579 case V4L2_PIX_FMT_VUYA32:
1580 case V4L2_PIX_FMT_YUVA32:
1581 dstFmt = QImage::Format_ARGB32;
1582 break;
1583 case V4L2_PIX_FMT_INZI:
1584 height *= 2;
1585 break;
1586 }
1587 m_capImage = new QImage(width, height, dstFmt);
1588 m_capImage->fill(0);
1589
1590 updatePixelAspectRatio();
1591 m_capture->setField(field);
1592
1593 m_capture->setWindowSize(QSize(width, height));
1594 m_capture->setFrame(m_capImage->width(), m_capImage->height(),
1595 pixfmt, m_capImage->bits(), NULL, NULL);
1596 m_capture->makeFullScreen(m_makeFullScreenAct->isChecked());
1597 updateColorspace();
1598 if (showFrames())
1599 m_capture->show();
1600
1601 statusBar()->showMessage("No frame");
1602 if (startStreaming()) {
1603 m_capNotifier = new QSocketNotifier(g_fd(), QSocketNotifier::Read, m_tabs);
1604 connect(m_capNotifier, SIGNAL(activated(int)), this, SLOT(capFrame()));
1605 }
1606 }
1607
makeFullScreen(bool checked)1608 void ApplicationWindow::makeFullScreen(bool checked)
1609 {
1610 if (m_capture && m_capStartAct->isChecked())
1611 m_capture->makeFullScreen(checked);
1612 }
1613
closeDevice()1614 void ApplicationWindow::closeDevice()
1615 {
1616 stopAudio();
1617 if (m_sigMapper) {
1618 m_sigMapper->deleteLater();
1619 m_sigMapper = NULL;
1620 }
1621 m_capStartAct->setEnabled(false);
1622 m_capStartAct->setChecked(false);
1623 m_capStepAct->setEnabled(false);
1624 m_saveRawAct->setEnabled(false);
1625 if (g_fd() >= 0) {
1626 if (m_capNotifier) {
1627 delete m_capNotifier;
1628 delete m_capImage;
1629 m_capNotifier = NULL;
1630 m_capImage = NULL;
1631 }
1632 if (m_outNotifier) {
1633 delete m_outNotifier;
1634 m_outNotifier = NULL;
1635 }
1636 if (m_ctrlNotifier) {
1637 m_ctrlNotifier->deleteLater();
1638 m_ctrlNotifier = NULL;
1639 }
1640 delete [] m_frameData;
1641 m_frameData = NULL;
1642 v4lconvert_destroy(m_convertData);
1643 cv4l_fd::close();
1644 delete m_capture;
1645 m_capture = NULL;
1646 }
1647 while (QWidget *page = m_tabs->widget(0)) {
1648 m_tabs->removeTab(0);
1649 delete page;
1650 }
1651 m_genTab = NULL;
1652 m_ctrlMap.clear();
1653 m_widgetMap.clear();
1654 m_sliderMap.clear();
1655 m_classMap.clear();
1656 m_tpgLimRGBRange = NULL;
1657 }
1658
setBuffer(unsigned char * buf,unsigned size)1659 bool SaveDialog::setBuffer(unsigned char *buf, unsigned size)
1660 {
1661 m_buf = new unsigned char[size];
1662 m_size = size;
1663 if (m_buf == NULL)
1664 return false;
1665 memcpy(m_buf, buf, size);
1666 return true;
1667 }
1668
selected(const QString & s)1669 void SaveDialog::selected(const QString &s)
1670 {
1671 if (!s.isEmpty()) {
1672 QFile file(s);
1673 file.open(QIODevice::WriteOnly | QIODevice::Truncate);
1674 file.write((const char *)m_buf, m_size);
1675 file.close();
1676 }
1677 delete [] m_buf;
1678 }
1679
makeSnapshot(unsigned char * buf,unsigned size)1680 void ApplicationWindow::makeSnapshot(unsigned char *buf, unsigned size)
1681 {
1682 m_makeSnapshot = false;
1683 SaveDialog *dlg = new SaveDialog(this, "Save Snapshot");
1684 dlg->setAttribute(Qt::WA_DeleteOnClose);
1685 dlg->setFileMode(QFileDialog::AnyFile);
1686 dlg->setAcceptMode(QFileDialog::AcceptSave);
1687 dlg->setModal(false);
1688 if (!dlg->setBuffer(buf, size)) {
1689 delete dlg;
1690 error("No memory to make snapshot\n");
1691 return;
1692 }
1693 connect(dlg, SIGNAL(fileSelected(const QString &)), dlg, SLOT(selected(const QString &)));
1694 dlg->show();
1695 }
1696
snapshot()1697 void ApplicationWindow::snapshot()
1698 {
1699 m_makeSnapshot = true;
1700 }
1701
rejectedRawFile()1702 void ApplicationWindow::rejectedRawFile()
1703 {
1704 m_saveRawAct->setChecked(false);
1705 }
1706
openRawFile(const QString & s)1707 void ApplicationWindow::openRawFile(const QString &s)
1708 {
1709 if (s.isEmpty())
1710 return;
1711
1712 if (m_saveRaw.openMode())
1713 m_saveRaw.close();
1714 m_saveRaw.setFileName(s);
1715 m_saveRaw.open(QIODevice::WriteOnly | QIODevice::Truncate);
1716 m_saveRawAct->setChecked(true);
1717 }
1718
saveRaw(bool checked)1719 void ApplicationWindow::saveRaw(bool checked)
1720 {
1721 if (!checked) {
1722 if (m_saveRaw.openMode())
1723 m_saveRaw.close();
1724 return;
1725 }
1726
1727 SaveDialog *dlg = new SaveDialog(this, "Save Raw Frames");
1728 dlg->setAttribute(Qt::WA_DeleteOnClose);
1729 dlg->setFileMode(QFileDialog::AnyFile);
1730 dlg->setAcceptMode(QFileDialog::AcceptSave);
1731 dlg->setModal(false);
1732 connect(dlg, SIGNAL(fileSelected(const QString &)), this, SLOT(openRawFile(const QString &)));
1733 connect(dlg, SIGNAL(rejected()), this, SLOT(rejectedRawFile()));
1734 dlg->show();
1735 }
1736
about()1737 void ApplicationWindow::about()
1738 {
1739 #ifdef HAVE_ALSA
1740 bool alsa = true;
1741 #else
1742 bool alsa = false;
1743 #endif
1744 #ifdef HAVE_QTGL
1745 bool gl = true;
1746 #else
1747 bool gl = false;
1748 #endif
1749
1750 QMessageBox::about(this, "V4L2 Test Bench",
1751 QString("This program allows easy experimenting with video4linux devices.\n"
1752 "v. %1\n\nALSA support : %2\nOpenGL support : %3")
1753 .arg(V4L_UTILS_VERSION)
1754 .arg(alsa ? "Present" : "Not Available")
1755 .arg(gl ? "Present" : "Not Available")
1756 );
1757 }
1758
error(const QString & error)1759 void ApplicationWindow::error(const QString &error)
1760 {
1761 statusBar()->showMessage(error, 20000);
1762 if (!error.isEmpty())
1763 fprintf(stderr, "%s\n", error.toUtf8().data());
1764 }
1765
error(int err)1766 void ApplicationWindow::error(int err)
1767 {
1768 error(QString("Error: %1").arg(strerror(err)));
1769 }
1770
errorCtrl(unsigned id,int err)1771 void ApplicationWindow::errorCtrl(unsigned id, int err)
1772 {
1773 error(QString("Error %1: %2")
1774 .arg((const char *)m_ctrlMap[id].name).arg(strerror(err)));
1775 }
1776
errorCtrl(unsigned id,int err,const QString & v)1777 void ApplicationWindow::errorCtrl(unsigned id, int err, const QString &v)
1778 {
1779 error(QString("Error %1 (%2): %3")
1780 .arg((const char *)m_ctrlMap[id].name).arg(v).arg(strerror(err)));
1781 }
1782
errorCtrl(unsigned id,int err,long long v)1783 void ApplicationWindow::errorCtrl(unsigned id, int err, long long v)
1784 {
1785 error(QString("Error %1 (%2): %3")
1786 .arg((const char *)m_ctrlMap[id].name).arg(v).arg(strerror(err)));
1787 }
1788
info(const QString & info)1789 void ApplicationWindow::info(const QString &info)
1790 {
1791 statusBar()->showMessage(info, 5000);
1792 }
1793
closeEvent(QCloseEvent * event)1794 void ApplicationWindow::closeEvent(QCloseEvent *event)
1795 {
1796 closeDevice();
1797 delete m_capture;
1798 m_capture = NULL;
1799 event->accept();
1800 }
1801
1802 ApplicationWindow *g_mw;
1803
usage()1804 static void usage()
1805 {
1806 printf(" Usage:\n"
1807 " qv4l2 [-R] [-h] [-d <dev>] [-r <dev>] [-V <dev>] [-S <dev>]\n"
1808 "\n -d, --device=<dev> use device <dev> as the video device\n"
1809 " if <dev> is a number, then /dev/video<dev> is used\n"
1810 " -V, --vbi-device=<dev> use device <dev> as the vbi device\n"
1811 " if <dev> is a number, then /dev/vbi<dev> is used\n"
1812 " -r, --radio-device=<dev> use device <dev> as the radio device\n"
1813 " if <dev> is a number, then /dev/radio<dev> is used\n"
1814 " -S, --sdr-device=<dev> use device <dev> as the SDR device\n"
1815 " if <dev> is a number, then /dev/swradio<dev> is used\n"
1816 " -t, --touch-device=<dev> use device <dev> as the touch device\n"
1817 " if <dev> is a number, then /dev/v4l-touch<dev> is used\n"
1818 " -h, --help display this help message\n"
1819 " -R, --raw open device in raw mode.\n");
1820 }
1821
usageError(const char * msg)1822 static void usageError(const char *msg)
1823 {
1824 printf("Missing parameter for %s\n", msg);
1825 usage();
1826 }
1827
getDeviceName(QString dev,QString & name)1828 static QString getDeviceName(QString dev, QString &name)
1829 {
1830 bool ok;
1831 name.toInt(&ok);
1832 return ok ? QString("%1%2").arg(dev).arg(name) : name;
1833 }
1834
processShortOption(const QStringList & args,int & i,QString & dev)1835 static bool processShortOption(const QStringList &args, int &i, QString &dev)
1836 {
1837 if (args[i].length() < 2)
1838 return false;
1839 if (args[i].length() == 2) {
1840 if (i + 1 >= args.size()) {
1841 usageError(args[i].toUtf8());
1842 return false;
1843 }
1844 dev = args[++i];
1845 return true;
1846 }
1847 dev = args[i].mid(2);
1848 return true;
1849 }
1850
processLongOption(const QStringList & args,int & i,QString & dev)1851 static bool processLongOption(const QStringList &args, int &i, QString &dev)
1852 {
1853 int index = args[i].indexOf('=');
1854
1855 if (index >= 0) {
1856 dev = args[i].mid(index + 1);
1857 if (dev.length() == 0) {
1858 usageError("--device");
1859 return false;
1860 }
1861 return true;
1862 }
1863 if (i + 1 >= args.size()) {
1864 usageError(args[i].toUtf8());
1865 return false;
1866 }
1867 dev = args[++i];
1868 return true;
1869 }
1870
main(int argc,char ** argv)1871 int main(int argc, char **argv)
1872 {
1873 QApplication a(argc, argv);
1874 bool raw = false;
1875 QString device;
1876 QString video_device;
1877 QString vbi_device;
1878 QString radio_device;
1879 QString sdr_device;
1880 QString touch_device;
1881
1882 a.setWindowIcon(QIcon(":/qv4l2.png"));
1883 g_mw = new ApplicationWindow();
1884 g_mw->setWindowTitle("V4L2 Test Bench");
1885
1886 QStringList args = a.arguments();
1887 for (int i = 1; i < args.size(); i++) {
1888 if (args[i].startsWith("-d")) {
1889 if (!processShortOption(args, i, video_device))
1890 return 0;
1891 } else if (args[i].startsWith("--device")) {
1892 if (!processLongOption(args, i, video_device))
1893 return 0;
1894 } else if (args[i].startsWith("-V")) {
1895 if (!processShortOption(args, i, vbi_device))
1896 return 0;
1897 } else if (args[i].startsWith("--vbi-device")) {
1898 if (!processLongOption(args, i, vbi_device))
1899 return 0;
1900 } else if (args[i].startsWith("-r")) {
1901 if (!processShortOption(args, i, radio_device))
1902 return 0;
1903 } else if (args[i].startsWith("--radio-device")) {
1904 if (!processLongOption(args, i, radio_device))
1905 return 0;
1906 } else if (args[i].startsWith("-S")) {
1907 if (!processShortOption(args, i, sdr_device))
1908 return 0;
1909 } else if (args[i].startsWith("--sdr-device")) {
1910 if (!processLongOption(args, i, sdr_device))
1911 return 0;
1912 } else if (args[i].startsWith("-t")) {
1913 if (!processShortOption(args, i, touch_device))
1914 return 0;
1915 } else if (args[i].startsWith("--touch-device")) {
1916 if (!processLongOption(args, i, touch_device))
1917 return 0;
1918 } else if (args[i] == "-h" || args[i] == "--help") {
1919 usage();
1920 return 0;
1921
1922 } else if (args[i] == "-R" || args[i] == "--raw") {
1923 raw = true;
1924 } else {
1925 printf("Invalid argument %s\n", args[i].toUtf8().data());
1926 return 0;
1927 }
1928 }
1929
1930 if (video_device != nullptr)
1931 device = getDeviceName("/dev/video", video_device);
1932 else if (vbi_device != nullptr)
1933 device = getDeviceName("/dev/vbi", vbi_device);
1934 else if (radio_device != nullptr)
1935 device = getDeviceName("/dev/radio", radio_device);
1936 else if (sdr_device != nullptr)
1937 device = getDeviceName("/dev/swradio", sdr_device);
1938 else if (touch_device != nullptr)
1939 device = getDeviceName("/dev/v4l-touch", touch_device);
1940 else
1941 device = "/dev/video0";
1942
1943 g_mw->setDevice(device, raw);
1944 g_mw->show();
1945 a.connect(&a, SIGNAL(lastWindowClosed()), &a, SLOT(quit()));
1946 return a.exec();
1947 }
1948