aboutsummaryrefslogtreecommitdiffstats
path: root/utils/v4l2-ctl/v4l2-ctl-tuner.cpp
blob: f5a04d25ee40d01922b5ec674ee2598d07a8083a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
#include <cmath>

#include "v4l2-ctl.h"

static int tuner_index;
static struct v4l2_tuner tuner;        	/* set_freq/get_freq */
static struct v4l2_modulator modulator;	/* set_freq/get_freq */
static int txsubchans;			/* set_modulator */
static double freq;			/* get/set frequency */
static struct v4l2_frequency vf;	/* get_freq/set_freq */
static struct v4l2_hw_freq_seek freq_seek; /* freq-seek */
static double low, high;		/* freq-seek frequency range */
static int mode = V4L2_TUNER_MODE_STEREO;  /* set audio mode */

void tuner_usage()
{
	printf("\nTuner/Modulator options:\n"
	       "  -F, --get-freq     query the frequency [VIDIOC_G_FREQUENCY]\n"
	       "  -f, --set-freq <freq>\n"
	       "                     set the frequency to <freq> MHz [VIDIOC_S_FREQUENCY]\n"
	       "  -T, --get-tuner    query the tuner settings [VIDIOC_G_TUNER]\n"
	       "  -t, --set-tuner <mode>\n"
	       "                     set the audio mode of the tuner [VIDIOC_S_TUNER]\n"
	       "                     Possible values: mono, stereo, lang2, lang1, bilingual\n"
	       "  --tuner-index <idx> Use idx as tuner idx for tuner/modulator commands\n"
	       "  --list-freq-bands  display all frequency bands for the tuner/modulator\n"
	       "                     [VIDIOC_ENUM_FREQ_BANDS]\n"
	       "  --get-modulator    query the modulator settings [VIDIOC_G_MODULATOR]\n"
	       "  --set-modulator <txsubchans>\n"
	       "                     set the sub-carrier modulation [VIDIOC_S_MODULATOR]\n"
	       "                     <txsubchans> is one of:\n"
	       "                     mono:	 Modulate as mono\n"
	       "                     mono-rds:	 Modulate as mono with RDS (radio only)\n"
	       "                     stereo:	 Modulate as stereo\n"
	       "                     stereo-rds: Modulate as stereo with RDS (radio only)\n"
	       "                     bilingual:	 Modulate as bilingual\n"
	       "                     mono-sap:	 Modulate as mono with Second Audio Program\n"
	       "                     stereo-sap: Modulate as stereo with Second Audio Program\n"
	       "  --freq-seek dir=<0/1>,wrap=<0/1>,spacing=<hz>,low=<freq>,high=<freq>\n"
	       "                     perform a hardware frequency seek [VIDIOC_S_HW_FREQ_SEEK]\n"
	       "                     dir is 0 (seek downward) or 1 (seek upward)\n"
	       "                     wrap is 0 (do not wrap around) or 1 (wrap around)\n"
	       "                     spacing sets the seek resolution (use 0 for default)\n"
	       "                     low and high set the low and high seek frequency range in MHz\n"
	       );
}

static const char *audmode2s(int audmode)
{
	switch (audmode) {
		case V4L2_TUNER_MODE_STEREO: return "stereo";
		case V4L2_TUNER_MODE_LANG1: return "lang1";
		case V4L2_TUNER_MODE_LANG2: return "lang2";
		case V4L2_TUNER_MODE_LANG1_LANG2: return "bilingual";
		case V4L2_TUNER_MODE_MONO: return "mono";
		default: return "unknown";
	}
}

static const char *ttype2s(int type)
{
	switch (type) {
		case V4L2_TUNER_RADIO: return "radio";
		case V4L2_TUNER_ANALOG_TV: return "Analog TV";
		case V4L2_TUNER_DIGITAL_TV: return "Digital TV";
		case V4L2_TUNER_SDR: return "SDR";
		case V4L2_TUNER_RF: return "RF";
		default: return "unknown";
	}
}

static std::string rxsubchans2s(int rxsubchans)
{
	std::string s;

	if (rxsubchans & V4L2_TUNER_SUB_MONO)
		s += "mono ";
	if (rxsubchans & V4L2_TUNER_SUB_STEREO)
		s += "stereo ";
	if (rxsubchans & V4L2_TUNER_SUB_LANG1)
		s += "lang1 ";
	if (rxsubchans & V4L2_TUNER_SUB_LANG2)
		s += "lang2 ";
	if (rxsubchans & V4L2_TUNER_SUB_RDS)
		s += "rds ";
	return s;
}

static std::string txsubchans2s(int txsubchans)
{
	std::string s;

	if (txsubchans & V4L2_TUNER_SUB_MONO)
		s += "mono ";
	if (txsubchans & V4L2_TUNER_SUB_STEREO)
		s += "stereo ";
	if (txsubchans & V4L2_TUNER_SUB_LANG1)
		s += "bilingual ";
	if (txsubchans & V4L2_TUNER_SUB_SAP)
		s += "sap ";
	if (txsubchans & V4L2_TUNER_SUB_RDS)
		s += "rds ";
	return s;
}

static std::string tcap2s(unsigned cap)
{
	std::string s;

	if (cap & V4L2_TUNER_CAP_LOW)
		s += "62.5 Hz ";
	else if (cap & V4L2_TUNER_CAP_1HZ)
		s += "1 Hz ";
	else
		s += "62.5 kHz ";
	if (cap & V4L2_TUNER_CAP_NORM)
		s += "multi-standard ";
	if (cap & V4L2_TUNER_CAP_HWSEEK_BOUNDED)
		s += "hwseek-bounded ";
	if (cap & V4L2_TUNER_CAP_HWSEEK_WRAP)
		s += "hwseek-wrap ";
	if (cap & V4L2_TUNER_CAP_STEREO)
		s += "stereo ";
	if (cap & V4L2_TUNER_CAP_LANG1)
		s += "lang1 ";
	if (cap & V4L2_TUNER_CAP_LANG2)
		s += "lang2 ";
	if (cap & V4L2_TUNER_CAP_RDS)
		s += "rds ";
	if (cap & V4L2_TUNER_CAP_RDS_BLOCK_IO)
		s += "rds-block-I/O ";
	if (cap & V4L2_TUNER_CAP_RDS_CONTROLS)
		s += "rds-controls ";
	if (cap & V4L2_TUNER_CAP_FREQ_BANDS)
		s += "freq-bands ";
	if (cap & V4L2_TUNER_CAP_HWSEEK_PROG_LIM)
		s += "hwseek-prog-lim ";
	return s;
}

static std::string modulation2s(unsigned modulation)
{
	switch (modulation) {
	case V4L2_BAND_MODULATION_VSB:
		return "VSB";
	case V4L2_BAND_MODULATION_FM:
		return "FM";
	case V4L2_BAND_MODULATION_AM:
		return "AM";
	}
	return "Unknown";
}

static void parse_freq_seek(char *optarg, struct v4l2_hw_freq_seek &seek)
{
	char *value;
	char *subs = optarg;

	while (*subs != '\0') {
		static const char *const subopts[] = {
			"dir",
			"wrap",
			"spacing",
			"low",
			"high",
			NULL
		};

		switch (parse_subopt(&subs, subopts, &value)) {
		case 0:
			seek.seek_upward = strtol(value, 0L, 0);
			break;
		case 1:
			seek.wrap_around = strtol(value, 0L, 0);
			break;
		case 2:
			seek.spacing = strtol(value, 0L, 0);
			break;
		case 3:
			low = strtod(value, NULL);
			break;
		case 4:
			high = strtod(value, NULL);
			break;
		default:
			tuner_usage();
			std::exit(EXIT_FAILURE);
		}
	}
}

void tuner_cmd(int ch, char *optarg)
{
	switch (ch) {
	case OptSetFreq:
		freq = strtod(optarg, NULL);
		break;
	case OptSetTuner:
		if (!strcmp(optarg, "stereo"))
			mode = V4L2_TUNER_MODE_STEREO;
		else if (!strcmp(optarg, "lang1"))
			mode = V4L2_TUNER_MODE_LANG1;
		else if (!strcmp(optarg, "lang2"))
			mode = V4L2_TUNER_MODE_LANG2;
		else if (!strcmp(optarg, "bilingual"))
			mode = V4L2_TUNER_MODE_LANG1_LANG2;
		else if (!strcmp(optarg, "mono"))
			mode = V4L2_TUNER_MODE_MONO;
		else {
			fprintf(stderr, "Unknown audio mode\n");
			tuner_usage();
			std::exit(EXIT_FAILURE);
		}
		break;
	case OptSetModulator:
		txsubchans = strtol(optarg, 0L, 0);
		if (!strcmp(optarg, "stereo"))
			txsubchans = V4L2_TUNER_SUB_STEREO;
		else if (!strcmp(optarg, "stereo-sap"))
			txsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_SAP;
		else if (!strcmp(optarg, "bilingual"))
			txsubchans = V4L2_TUNER_SUB_LANG1;
		else if (!strcmp(optarg, "mono"))
			txsubchans = V4L2_TUNER_SUB_MONO;
		else if (!strcmp(optarg, "mono-sap"))
			txsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_SAP;
		else if (!strcmp(optarg, "stereo-rds"))
			txsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_RDS;
		else if (!strcmp(optarg, "mono-rds"))
			txsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_RDS;
		else {
			fprintf(stderr, "Unknown txsubchans value\n");
			tuner_usage();
			std::exit(EXIT_FAILURE);
		}
		break;
	case OptFreqSeek:
		parse_freq_seek(optarg, freq_seek);
		break;
	case OptTunerIndex:
		tuner_index = strtoul(optarg, NULL, 0);
		break;
	}
}

void tuner_set(cv4l_fd &_fd)
{
	int fd = _fd.g_fd();
	__u32 type = (capabilities & V4L2_CAP_RADIO) ?
		V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
	double fac = 16;

	if (!options[OptSetFreq] && !options[OptSetTuner] && !options[OptListFreqBands]
	    && !options[OptSetModulator] && !options[OptFreqSeek]) {
		/* Don't actually call G_[MODULATOR/TUNER] if we don't intend to
		   actually perform any tuner related function */
		return;
	}

	if (capabilities & V4L2_CAP_MODULATOR) {
		type = V4L2_TUNER_RADIO;
		modulator.index = tuner_index;
		if (doioctl(fd, VIDIOC_G_MODULATOR, &modulator) == 0) {
			if (modulator.capability & V4L2_TUNER_CAP_LOW)
				fac = 16000;
			else if (modulator.capability & V4L2_TUNER_CAP_1HZ)
				fac = 1000000;
			else
				fac = 16;
		}
	} else if (capabilities & V4L2_CAP_TUNER) {
		tuner.index = tuner_index;
		if (doioctl(fd, VIDIOC_G_TUNER, &tuner) == 0) {
			if (tuner.capability & V4L2_TUNER_CAP_LOW)
				fac = 16000;
			else if (tuner.capability & V4L2_TUNER_CAP_1HZ)
				fac = 1000000;
			else
				fac = 16;

			type = tuner.type;
		}
	}
	if (options[OptSetFreq]) {
		vf.type = type;
		vf.tuner = tuner_index;
		vf.frequency = __u32(freq * fac);
		if (doioctl(fd, VIDIOC_S_FREQUENCY, &vf) == 0)
			printf("Frequency for tuner %d set to %d (%f MHz)\n",
			       vf.tuner, vf.frequency, vf.frequency / fac);
	}

	if (options[OptSetTuner]) {
		struct v4l2_tuner vt;

		memset(&vt, 0, sizeof(struct v4l2_tuner));
		vt.index = tuner_index;
		if (doioctl(fd, VIDIOC_G_TUNER, &vt) == 0) {
			vt.audmode = mode;
			doioctl(fd, VIDIOC_S_TUNER, &vt);
		}
	}

	if (options[OptListFreqBands]) {
		struct v4l2_frequency_band band;

		memset(&band, 0, sizeof(band));
		band.tuner = tuner_index;
		band.type = type;
		band.index = 0;
		printf("ioctl: VIDIOC_ENUM_FREQ_BANDS\n");
		while (test_ioctl(fd, VIDIOC_ENUM_FREQ_BANDS, &band) >= 0) {
			if (band.index)
				printf("\n");
			printf("\tIndex          : %d\n", band.index);
			printf("\tModulation     : %s\n", modulation2s(band.modulation).c_str());
			printf("\tCapability     : %s\n", tcap2s(band.capability).c_str());
			if (band.capability & V4L2_TUNER_CAP_LOW)
				printf("\tFrequency Range: %.3f MHz - %.3f MHz\n",
				     band.rangelow / 16000.0, band.rangehigh / 16000.0);
			else if (band.capability & V4L2_TUNER_CAP_1HZ)
				printf("\tFrequency Range: %.6f MHz - %.6f MHz\n",
				     band.rangelow / 1000000.0, band.rangehigh / 1000000.0);
			else
				printf("\tFrequency Range: %.3f MHz - %.3f MHz\n",
				     band.rangelow / 16.0, band.rangehigh / 16.0);
			band.index++;
		}
	}

	if (options[OptSetModulator]) {
		struct v4l2_modulator mt;

		memset(&mt, 0, sizeof(struct v4l2_modulator));
		mt.index = tuner_index;
		if (doioctl(fd, VIDIOC_G_MODULATOR, &mt) == 0) {
			mt.txsubchans = txsubchans;
			doioctl(fd, VIDIOC_S_MODULATOR, &mt);
		}
	}

	if (options[OptFreqSeek]) {
		freq_seek.tuner = tuner_index;
		freq_seek.type = type;
		freq_seek.rangelow = __u32(low * fac);
		freq_seek.rangehigh = __u32(high * fac);
		doioctl(fd, VIDIOC_S_HW_FREQ_SEEK, &freq_seek);
	}
}

void tuner_get(cv4l_fd &_fd)
{
	int fd = _fd.g_fd();

	if (options[OptGetFreq]) {
		double fac = 16;

		if (capabilities & V4L2_CAP_MODULATOR) {
			vf.type = V4L2_TUNER_RADIO;
			modulator.index = tuner_index;
			if (doioctl(fd, VIDIOC_G_MODULATOR, &modulator) == 0) {
				if (modulator.capability & V4L2_TUNER_CAP_LOW)
					fac = 16000;
				else if (modulator.capability & V4L2_TUNER_CAP_1HZ)
					fac = 1000000;
				else
					fac = 16;
			}
		} else {
			vf.type = V4L2_TUNER_ANALOG_TV;
			tuner.index = tuner_index;
			if (doioctl(fd, VIDIOC_G_TUNER, &tuner) == 0) {
				if (tuner.capability & V4L2_TUNER_CAP_LOW)
					fac = 16000;
				else if (tuner.capability & V4L2_TUNER_CAP_1HZ)
					fac = 1000000;
				else
					fac = 16;
				vf.type = tuner.type;
			}
		}
		vf.tuner = tuner_index;
		if (doioctl(fd, VIDIOC_G_FREQUENCY, &vf) == 0)
			printf("Frequency for %s %d: %d (%f MHz)\n",
			       (capabilities & V4L2_CAP_MODULATOR) ?
					"modulator" : "tuner",
			       vf.tuner, vf.frequency, vf.frequency / fac);
	}

	if (options[OptGetTuner]) {
		struct v4l2_tuner vt;

		memset(&vt, 0, sizeof(struct v4l2_tuner));
		vt.index = tuner_index;
		if (doioctl(fd, VIDIOC_G_TUNER, &vt) == 0) {
			printf("Tuner %d:\n", vt.index);
			printf("\tName                 : %s\n", vt.name);
			printf("\tType                 : %s\n", ttype2s(vt.type));
			printf("\tCapabilities         : %s\n", tcap2s(vt.capability).c_str());
			if (vt.capability & V4L2_TUNER_CAP_LOW)
				printf("\tFrequency range      : %.3f MHz - %.3f MHz\n",
				     vt.rangelow / 16000.0, vt.rangehigh / 16000.0);
			else if (vt.capability & V4L2_TUNER_CAP_1HZ)
				printf("\tFrequency range      : %.6f MHz - %.6f MHz\n",
				     vt.rangelow / 1000000.0, vt.rangehigh / 1000000.0);
			else
				printf("\tFrequency range      : %.3f MHz - %.3f MHz\n",
				     vt.rangelow / 16.0, vt.rangehigh / 16.0);

			if (vt.type != V4L2_TUNER_SDR && vt.type != V4L2_TUNER_RF) {
				printf("\tSignal strength/AFC  : %ld%%/%d\n",
				       lround(vt.signal / 655.35), vt.afc);
				printf("\tCurrent audio mode   : %s\n", audmode2s(vt.audmode));
				printf("\tAvailable subchannels: %s\n", rxsubchans2s(vt.rxsubchans).c_str());
			}
		}
	}

	if (options[OptGetModulator]) {
		struct v4l2_modulator mt;

		memset(&mt, 0, sizeof(struct v4l2_modulator));
		modulator.index = tuner_index;
		if (doioctl(fd, VIDIOC_G_MODULATOR, &mt) == 0) {
			printf("Modulator %d:\n", modulator.index);
			printf("\tName                 : %s\n", mt.name);
			printf("\tType                 : %s\n", ttype2s(mt.type));
			printf("\tCapabilities         : %s\n", tcap2s(mt.capability).c_str());
			if (mt.capability & V4L2_TUNER_CAP_LOW)
				printf("\tFrequency range      : %.1f MHz - %.1f MHz\n",
				     mt.rangelow / 16000.0, mt.rangehigh / 16000.0);
			else if (mt.capability & V4L2_TUNER_CAP_1HZ)
				printf("\tFrequency range      : %.6f MHz - %.6f MHz\n",
				     mt.rangelow / 1000000.0, mt.rangehigh / 1000000.0);
			else
				printf("\tFrequency range      : %.1f MHz - %.1f MHz\n",
				     mt.rangelow / 16.0, mt.rangehigh / 16.0);
			printf("\tSubchannel modulation: %s\n",
					txsubchans2s(mt.txsubchans).c_str());
		}
	}
}

Privacy Policy