784 lines
15 KiB
C
784 lines
15 KiB
C
/*
|
|
* TV Input - Linux analogue (v4lv2) interface
|
|
* Copyright (C) 2007 Andreas Öman
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <pthread.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <poll.h>
|
|
#include <inttypes.h>
|
|
|
|
#include "settings.h"
|
|
|
|
#include "tvheadend.h"
|
|
#include "service.h"
|
|
#include "v4l.h"
|
|
#include "parsers.h"
|
|
#include "notify.h"
|
|
#include "psi.h"
|
|
#include "channels.h"
|
|
|
|
struct v4l_adapter_queue v4l_adapters;
|
|
|
|
static void v4l_adapter_notify(v4l_adapter_t *va);
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
v4l_input(v4l_adapter_t *va)
|
|
{
|
|
service_t *t = va->va_current_service;
|
|
th_stream_t *st;
|
|
uint8_t buf[4000];
|
|
uint8_t *ptr, *pkt;
|
|
int len, l, r;
|
|
|
|
len = read(va->va_fd, buf, 4000);
|
|
if(len < 1)
|
|
return;
|
|
|
|
ptr = buf;
|
|
|
|
pthread_mutex_lock(&t->s_stream_mutex);
|
|
|
|
service_set_streaming_status_flags(t,
|
|
TSS_INPUT_HARDWARE | TSS_INPUT_SERVICE);
|
|
|
|
while(len > 0) {
|
|
|
|
switch(va->va_startcode) {
|
|
default:
|
|
va->va_startcode = va->va_startcode << 8 | *ptr;
|
|
va->va_lenlock = 0;
|
|
ptr++; len--;
|
|
continue;
|
|
|
|
case 0x000001e0:
|
|
st = t->s_video;
|
|
break;
|
|
case 0x000001c0:
|
|
st = t->s_audio;
|
|
break;
|
|
}
|
|
|
|
if(va->va_lenlock == 2) {
|
|
l = st->st_buf_ps.sb_size;
|
|
st->st_buf_ps.sb_data = pkt = realloc(st->st_buf_ps.sb_data, l);
|
|
|
|
r = l - st->st_buf_ps.sb_ptr;
|
|
if(r > len)
|
|
r = len;
|
|
memcpy(pkt + st->st_buf_ps.sb_ptr, ptr, r);
|
|
|
|
ptr += r;
|
|
len -= r;
|
|
|
|
st->st_buf_ps.sb_ptr += r;
|
|
if(st->st_buf_ps.sb_ptr == l) {
|
|
|
|
service_set_streaming_status_flags(t, TSS_MUX_PACKETS);
|
|
|
|
parse_mpeg_ps(t, st, pkt + 6, l - 6);
|
|
|
|
st->st_buf_ps.sb_size = 0;
|
|
va->va_startcode = 0;
|
|
} else {
|
|
assert(st->st_buf_ps.sb_ptr < l);
|
|
}
|
|
|
|
} else {
|
|
st->st_buf_ps.sb_size = st->st_buf_ps.sb_size << 8 | *ptr;
|
|
va->va_lenlock++;
|
|
if(va->va_lenlock == 2) {
|
|
st->st_buf_ps.sb_size += 6;
|
|
st->st_buf_ps.sb_ptr = 6;
|
|
}
|
|
ptr++; len--;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&t->s_stream_mutex);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void *
|
|
v4l_thread(void *aux)
|
|
{
|
|
v4l_adapter_t *va = aux;
|
|
struct pollfd pfd[2];
|
|
int r;
|
|
|
|
pfd[0].fd = va->va_pipe[0];
|
|
pfd[0].events = POLLIN;
|
|
pfd[1].fd = va->va_fd;
|
|
pfd[1].events = POLLIN;
|
|
|
|
while(1) {
|
|
|
|
r = poll(pfd, 2, -1);
|
|
if(r < 0) {
|
|
tvhlog(LOG_ALERT, "v4l", "%s: poll() error %s, sleeping one second",
|
|
va->va_path, strerror(errno));
|
|
sleep(1);
|
|
continue;
|
|
}
|
|
|
|
if(pfd[0].revents & POLLIN) {
|
|
// Message on control pipe, used to exit thread, do so
|
|
break;
|
|
}
|
|
|
|
if(pfd[1].revents & POLLIN) {
|
|
v4l_input(va);
|
|
}
|
|
}
|
|
|
|
close(va->va_pipe[0]);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static int
|
|
v4l_service_start(service_t *t, unsigned int weight, int force_start)
|
|
{
|
|
v4l_adapter_t *va = t->s_v4l_adapter;
|
|
int frequency = t->s_v4l_frequency;
|
|
struct v4l2_frequency vf;
|
|
int result;
|
|
v4l2_std_id std = 0xff;
|
|
int fd;
|
|
|
|
if(va->va_current_service != NULL)
|
|
return 1; // Adapter busy
|
|
|
|
fd = tvh_open(va->va_path, O_RDWR | O_NONBLOCK, 0);
|
|
if(fd == -1) {
|
|
tvhlog(LOG_ERR, "v4l",
|
|
"%s: Unable to open device: %s\n", va->va_path,
|
|
strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if(!va->va_file) {
|
|
|
|
result = ioctl(fd, VIDIOC_S_STD, &std);
|
|
if(result < 0) {
|
|
tvhlog(LOG_ERR, "v4l",
|
|
"%s: Unable to set PAL -- %s", va->va_path, strerror(errno));
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
memset(&vf, 0, sizeof(vf));
|
|
|
|
vf.tuner = 0;
|
|
vf.type = V4L2_TUNER_ANALOG_TV;
|
|
vf.frequency = (frequency * 16) / 1000000;
|
|
result = ioctl(fd, VIDIOC_S_FREQUENCY, &vf);
|
|
if(result < 0) {
|
|
tvhlog(LOG_ERR, "v4l",
|
|
"%s: Unable to tune to %dHz", va->va_path, frequency);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
tvhlog(LOG_INFO, "v4l",
|
|
"%s: Tuned to %dHz", va->va_path, frequency);
|
|
}
|
|
if(pipe(va->va_pipe)) {
|
|
tvhlog(LOG_ERR, "v4l",
|
|
"%s: Unable to create control pipe", va->va_path, strerror(errno));
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
|
|
va->va_fd = fd;
|
|
va->va_current_service = t;
|
|
pthread_create(&va->va_thread, NULL, v4l_thread, va);
|
|
v4l_adapter_notify(va);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
v4l_service_refresh(service_t *t)
|
|
{
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
v4l_service_stop(service_t *t)
|
|
{
|
|
char c = 'q';
|
|
v4l_adapter_t *va = t->s_v4l_adapter;
|
|
|
|
assert(va->va_current_service != NULL);
|
|
|
|
if(write(va->va_pipe[1], &c, 1) != 1)
|
|
tvhlog(LOG_ERR, "v4l", "Unable to close video thread -- %s",
|
|
strerror(errno));
|
|
|
|
pthread_join(va->va_thread, NULL);
|
|
|
|
close(va->va_pipe[1]);
|
|
close(va->va_fd);
|
|
|
|
va->va_current_service = NULL;
|
|
v4l_adapter_notify(va);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
v4l_service_save(service_t *t)
|
|
{
|
|
v4l_adapter_t *va = t->s_v4l_adapter;
|
|
htsmsg_t *m = htsmsg_create_map();
|
|
|
|
htsmsg_add_u32(m, "frequency", t->s_v4l_frequency);
|
|
|
|
if(t->s_ch != NULL) {
|
|
htsmsg_add_str(m, "channelname", t->s_ch->ch_name);
|
|
htsmsg_add_u32(m, "mapped", 1);
|
|
}
|
|
|
|
|
|
pthread_mutex_lock(&t->s_stream_mutex);
|
|
psi_save_service_settings(m, t);
|
|
pthread_mutex_unlock(&t->s_stream_mutex);
|
|
|
|
hts_settings_save(m, "v4lservices/%s/%s",
|
|
va->va_identifier, t->s_identifier);
|
|
|
|
htsmsg_destroy(m);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static int
|
|
v4l_service_quality(service_t *t)
|
|
{
|
|
return 100;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static int
|
|
v4l_grace_period(service_t *t)
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
|
|
/**
|
|
* Generate a descriptive name for the source
|
|
*/
|
|
static void
|
|
v4l_service_setsourceinfo(service_t *t, struct source_info *si)
|
|
{
|
|
char buf[64];
|
|
memset(si, 0, sizeof(struct source_info));
|
|
|
|
si->si_adapter = strdup(t->s_v4l_adapter->va_displayname);
|
|
|
|
snprintf(buf, sizeof(buf), "%d Hz", t->s_v4l_frequency);
|
|
si->si_mux = strdup(buf);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
service_t *
|
|
v4l_service_find(v4l_adapter_t *va, const char *id, int create)
|
|
{
|
|
service_t *t;
|
|
char buf[200];
|
|
|
|
int vaidlen = strlen(va->va_identifier);
|
|
|
|
if(id != NULL) {
|
|
|
|
if(strncmp(id, va->va_identifier, vaidlen))
|
|
return NULL;
|
|
|
|
LIST_FOREACH(t, &va->va_services, s_group_link)
|
|
if(!strcmp(t->s_identifier, id))
|
|
return t;
|
|
}
|
|
|
|
if(create == 0)
|
|
return NULL;
|
|
|
|
if(id == NULL) {
|
|
va->va_tally++;
|
|
snprintf(buf, sizeof(buf), "%s_%d", va->va_identifier, va->va_tally);
|
|
id = buf;
|
|
} else {
|
|
va->va_tally = MAX(atoi(id + vaidlen + 1), va->va_tally);
|
|
}
|
|
|
|
t = service_create(id, SERVICE_TYPE_V4L, 0);
|
|
|
|
t->s_start_feed = v4l_service_start;
|
|
t->s_refresh_feed = v4l_service_refresh;
|
|
t->s_stop_feed = v4l_service_stop;
|
|
t->s_config_save = v4l_service_save;
|
|
t->s_setsourceinfo = v4l_service_setsourceinfo;
|
|
t->s_quality_index = v4l_service_quality;
|
|
t->s_grace_period = v4l_grace_period;
|
|
t->s_iptv_fd = -1;
|
|
t->s_v4l_adapter = va;
|
|
|
|
pthread_mutex_lock(&t->s_stream_mutex);
|
|
service_make_nicename(t);
|
|
t->s_video = service_stream_create(t, -1, SCT_MPEG2VIDEO);
|
|
t->s_audio = service_stream_create(t, -1, SCT_MPEG2AUDIO);
|
|
pthread_mutex_unlock(&t->s_stream_mutex);
|
|
|
|
LIST_INSERT_HEAD(&va->va_services, t, s_group_link);
|
|
|
|
return t;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
v4l_adapter_add(const char *path, const char *displayname,
|
|
const char *devicename, int file)
|
|
{
|
|
v4l_adapter_t *va;
|
|
int i, r;
|
|
|
|
va = calloc(1, sizeof(v4l_adapter_t));
|
|
|
|
va->va_identifier = strdup(path);
|
|
|
|
r = strlen(va->va_identifier);
|
|
for(i = 0; i < r; i++)
|
|
if(!isalnum((int)va->va_identifier[i]))
|
|
va->va_identifier[i] = '_';
|
|
|
|
va->va_displayname = strdup(displayname);
|
|
va->va_path = path ? strdup(path) : NULL;
|
|
va->va_devicename = devicename ? strdup(devicename) : NULL;
|
|
va->va_file = file;
|
|
|
|
TAILQ_INSERT_TAIL(&v4l_adapters, va, va_global_link);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
v4l_adapter_check(const char *path, int fd)
|
|
{
|
|
int r, i;
|
|
|
|
char devicename[100];
|
|
|
|
struct v4l2_capability caps;
|
|
|
|
r = ioctl(fd, VIDIOC_QUERYCAP, &caps);
|
|
|
|
if(r) {
|
|
tvhlog(LOG_WARNING, "v4l",
|
|
"%s: Can not query capabilities, device skipped", path);
|
|
return;
|
|
}
|
|
|
|
tvhlog(LOG_INFO, "v4l", "%s: %s %s %s capabilities: 0x%08x",
|
|
path, caps.driver, caps.card, caps.bus_info, caps.capabilities);
|
|
|
|
if(!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
|
tvhlog(LOG_WARNING, "v4l",
|
|
"%s: Device is not a video capture device, device skipped", path);
|
|
return;
|
|
}
|
|
|
|
/* Enum video standards */
|
|
|
|
for(i = 0;; i++) {
|
|
struct v4l2_standard standard;
|
|
memset(&standard, 0, sizeof(standard));
|
|
standard.index = i;
|
|
|
|
if(ioctl(fd, VIDIOC_ENUMSTD, &standard))
|
|
break;
|
|
|
|
tvhlog(LOG_INFO, "v4l",
|
|
"%s: Standard #%d: %016llx %s, frameperiod: %d/%d, %d lines",
|
|
path,
|
|
standard.index,
|
|
standard.id,
|
|
standard.name,
|
|
standard.frameperiod.numerator,
|
|
standard.frameperiod.denominator,
|
|
standard.framelines);
|
|
}
|
|
|
|
/* Enum video inputs */
|
|
|
|
for(i = 0;; i++) {
|
|
struct v4l2_input input;
|
|
memset(&input, 0, sizeof(input));
|
|
input.index = i;
|
|
|
|
if(ioctl(fd, VIDIOC_ENUMINPUT, &input))
|
|
break;
|
|
|
|
const char *type;
|
|
switch(input.type) {
|
|
case V4L2_INPUT_TYPE_TUNER:
|
|
type = "Tuner";
|
|
break;
|
|
case V4L2_INPUT_TYPE_CAMERA:
|
|
type = "Camera";
|
|
break;
|
|
default:
|
|
type = "Unknown";
|
|
break;
|
|
}
|
|
|
|
int f = input.status;
|
|
|
|
tvhlog(LOG_INFO, "v4l",
|
|
"%s: Input #%d: %s (%s), audio:0x%x, tuner:%d, standard:%016llx, "
|
|
"%s%s%s",
|
|
path,
|
|
input.index,
|
|
input.name,
|
|
type,
|
|
input.audioset,
|
|
input.tuner,
|
|
input.std,
|
|
f & V4L2_IN_ST_NO_POWER ? "[No power] " : "",
|
|
f & V4L2_IN_ST_NO_SIGNAL ? "[No signal] " : "",
|
|
f & V4L2_IN_ST_NO_COLOR ? "[No color] " : "");
|
|
}
|
|
|
|
|
|
int can_mpeg = 0;
|
|
|
|
/* Enum formats */
|
|
for(i = 0;; i++) {
|
|
|
|
struct v4l2_fmtdesc fmtdesc;
|
|
memset(&fmtdesc, 0, sizeof(fmtdesc));
|
|
fmtdesc.index = i;
|
|
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
|
|
if(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
|
|
break;
|
|
|
|
tvhlog(LOG_INFO, "v4l",
|
|
"%s: Format #%d: %s [%.4s] %s",
|
|
path,
|
|
fmtdesc.index,
|
|
fmtdesc.description,
|
|
&fmtdesc.pixelformat,
|
|
fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED ? "(compressed)" : "");
|
|
|
|
if(fmtdesc.pixelformat == V4L2_PIX_FMT_MPEG)
|
|
can_mpeg = 1;
|
|
}
|
|
|
|
|
|
if(!(caps.capabilities & V4L2_CAP_TUNER)) {
|
|
tvhlog(LOG_WARNING, "v4l",
|
|
"%s: Device does not have a tuner, device skipped", path);
|
|
return;
|
|
}
|
|
|
|
if(!can_mpeg) {
|
|
tvhlog(LOG_WARNING, "v4l",
|
|
"%s: Device lacks MPEG encoder, device skipped", path);
|
|
return;
|
|
}
|
|
snprintf(devicename, sizeof(devicename), "%s %s %s",
|
|
caps.card, caps.driver, caps.bus_info);
|
|
|
|
tvhlog(LOG_INFO, "v4l",
|
|
"%s: Using adapter", devicename);
|
|
|
|
v4l_adapter_add(path, devicename, devicename, 0);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
v4l_adapter_probe(const char *path)
|
|
{
|
|
int fd;
|
|
|
|
fd = tvh_open(path, O_RDWR | O_NONBLOCK, 0);
|
|
|
|
if(fd == -1) {
|
|
if(errno != ENOENT)
|
|
tvhlog(LOG_ALERT, "v4l",
|
|
"Unable to open %s -- %s", path, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
v4l_adapter_check(path, fd);
|
|
|
|
close(fd);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Save config for the given adapter
|
|
*/
|
|
static void
|
|
v4l_adapter_save(v4l_adapter_t *va)
|
|
{
|
|
htsmsg_t *m = htsmsg_create_map();
|
|
|
|
lock_assert(&global_lock);
|
|
|
|
htsmsg_add_str(m, "displayname", va->va_displayname);
|
|
htsmsg_add_u32(m, "logging", va->va_logging);
|
|
hts_settings_save(m, "v4ladapters/%s", va->va_identifier);
|
|
htsmsg_destroy(m);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
htsmsg_t *
|
|
v4l_adapter_build_msg(v4l_adapter_t *va)
|
|
{
|
|
htsmsg_t *m = htsmsg_create_map();
|
|
|
|
htsmsg_add_str(m, "identifier", va->va_identifier);
|
|
htsmsg_add_str(m, "name", va->va_displayname);
|
|
htsmsg_add_str(m, "type", "v4l");
|
|
|
|
if(va->va_path)
|
|
htsmsg_add_str(m, "path", va->va_path);
|
|
|
|
if(va->va_devicename)
|
|
htsmsg_add_str(m, "devicename", va->va_devicename);
|
|
|
|
if(va->va_current_service != NULL) {
|
|
char buf[100];
|
|
snprintf(buf, sizeof(buf), "%d Hz",
|
|
va->va_current_service->s_v4l_frequency);
|
|
htsmsg_add_str(m, "currentMux", buf);
|
|
} else {
|
|
htsmsg_add_str(m, "currentMux", "- inactive -");
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
v4l_adapter_notify(v4l_adapter_t *va)
|
|
{
|
|
notify_by_msg("tvAdapter", v4l_adapter_build_msg(va));
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
v4l_adapter_t *
|
|
v4l_adapter_find_by_identifier(const char *identifier)
|
|
{
|
|
v4l_adapter_t *va;
|
|
|
|
TAILQ_FOREACH(va, &v4l_adapters, va_global_link)
|
|
if(!strcmp(identifier, va->va_identifier))
|
|
return va;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void
|
|
v4l_adapter_set_displayname(v4l_adapter_t *va, const char *name)
|
|
{
|
|
lock_assert(&global_lock);
|
|
|
|
if(!strcmp(name, va->va_displayname))
|
|
return;
|
|
|
|
tvhlog(LOG_NOTICE, "v4l", "Adapter \"%s\" renamed to \"%s\"",
|
|
va->va_displayname, name);
|
|
|
|
tvh_str_set(&va->va_displayname, name);
|
|
v4l_adapter_save(va);
|
|
v4l_adapter_notify(va);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void
|
|
v4l_adapter_set_logging(v4l_adapter_t *va, int on)
|
|
{
|
|
if(va->va_logging == on)
|
|
return;
|
|
|
|
lock_assert(&global_lock);
|
|
|
|
tvhlog(LOG_NOTICE, "v4l", "Adapter \"%s\" detailed logging set to: %s",
|
|
va->va_displayname, on ? "On" : "Off");
|
|
|
|
va->va_logging = on;
|
|
v4l_adapter_save(va);
|
|
v4l_adapter_notify(va);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
v4l_service_create_by_msg(v4l_adapter_t *va, htsmsg_t *c, const char *name)
|
|
{
|
|
const char *s;
|
|
unsigned int u32;
|
|
|
|
service_t *t = v4l_service_find(va, name, 1);
|
|
|
|
if(t == NULL)
|
|
return;
|
|
|
|
s = htsmsg_get_str(c, "channelname");
|
|
if(htsmsg_get_u32(c, "mapped", &u32))
|
|
u32 = 0;
|
|
|
|
if(!htsmsg_get_u32(c, "frequency", &u32))
|
|
t->s_v4l_frequency = u32;
|
|
|
|
if(s && u32)
|
|
service_map_channel(t, channel_find_by_name(s, 1, 0), 0);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
v4l_service_load(v4l_adapter_t *va)
|
|
{
|
|
htsmsg_t *l, *c;
|
|
htsmsg_field_t *f;
|
|
|
|
if((l = hts_settings_load("v4lservices/%s", va->va_identifier)) == NULL)
|
|
return;
|
|
|
|
HTSMSG_FOREACH(f, l) {
|
|
if((c = htsmsg_get_map_by_field(f)) == NULL)
|
|
continue;
|
|
|
|
v4l_service_create_by_msg(va, c, f->hmf_name);
|
|
}
|
|
htsmsg_destroy(l);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void
|
|
v4l_init(void)
|
|
{
|
|
htsmsg_t *l, *c;
|
|
htsmsg_field_t *f;
|
|
char buf[256];
|
|
int i;
|
|
v4l_adapter_t *va;
|
|
|
|
TAILQ_INIT(&v4l_adapters);
|
|
|
|
for(i = 0; i < 8; i++) {
|
|
snprintf(buf, sizeof(buf), "/dev/video%d", i);
|
|
v4l_adapter_probe(buf);
|
|
}
|
|
|
|
l = hts_settings_load("v4ladapters");
|
|
if(l != NULL) {
|
|
HTSMSG_FOREACH(f, l) {
|
|
if((c = htsmsg_get_map_by_field(f)) == NULL)
|
|
continue;
|
|
|
|
if((va = v4l_adapter_find_by_identifier(f->hmf_name)) == NULL) {
|
|
/* Not discovered by hardware, create it */
|
|
|
|
va = calloc(1, sizeof(v4l_adapter_t));
|
|
va->va_identifier = strdup(f->hmf_name);
|
|
va->va_path = NULL;
|
|
va->va_devicename = NULL;
|
|
}
|
|
|
|
tvh_str_update(&va->va_displayname, htsmsg_get_str(c, "displayname"));
|
|
htsmsg_get_u32(c, "logging", &va->va_logging);
|
|
}
|
|
htsmsg_destroy(l);
|
|
}
|
|
|
|
TAILQ_FOREACH(va, &v4l_adapters, va_global_link)
|
|
v4l_service_load(va);
|
|
}
|