• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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