Initial reincarnation attempt for V4L support.

Can only decode a single hardcoded channel. 

Not yet initialized from main.c
This commit is contained in:
Andreas Öman 2009-08-26 22:09:28 +00:00
parent 08db2b91c4
commit f588dead00
8 changed files with 490 additions and 285 deletions

View file

@ -66,6 +66,8 @@ SRCS = src/main.c \
src/htsstr.c \
src/rawtsinput.c \
src/iptv_input.c \
src/v4l.c
SRCS += src/dvr/dvr_db.c \
src/dvr/dvr_rec.c \

View file

@ -83,6 +83,9 @@ typedef void (aparser_t)(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt);
static void parse_video(th_transport_t *t, th_stream_t *st, uint8_t *data,
int len, vparser_t *vp);
static void parse_audio_with_lavc(th_transport_t *t, th_stream_t *st,
uint8_t *data, int len, aparser_t *ap);
static void parse_audio(th_transport_t *t, th_stream_t *st, uint8_t *data,
int len, int start, aparser_t *vp);
@ -108,8 +111,8 @@ static void parser_compute_duration(th_transport_t *t, th_stream_t *st,
* Parse raw mpeg data
*/
void
parse_raw_mpeg(th_transport_t *t, th_stream_t *st, uint8_t *data,
int len, int start, int err)
parse_mpeg_ts(th_transport_t *t, th_stream_t *st, uint8_t *data,
int len, int start, int err)
{
th_subscription_t *s;
@ -153,7 +156,42 @@ parse_raw_mpeg(th_transport_t *t, th_stream_t *st, uint8_t *data,
}
/**
* Parse program stream, as from V4L2, etc.
*
* Note: data does not include startcode and packet length
*/
void
parse_mpeg_ps(th_transport_t *t, th_stream_t *st, uint8_t *data, int len)
{
int hlen;
hlen = parse_pes_header(t, st, data, len);
#if 0
int i;
for(i = 0; i < 16; i++)
printf("%02x.", data[i]);
printf(" %d\n", hlen);
#endif
data += hlen;
len -= hlen;
if(len < 1)
return;
switch(st->st_type) {
case SCT_MPEG2AUDIO:
parse_audio_with_lavc(t, st, data, len, parse_mpegaudio);
break;
case SCT_MPEG2VIDEO:
parse_video(t, st, data, len, parse_mpeg2video);
break;
default:
break;
}
}
/**
@ -293,7 +331,6 @@ parse_video(th_transport_t *t, th_stream_t *st, uint8_t *data, int len,
}
st->st_startcode = sc;
st->st_startcode_offset = st->st_buffer_ptr - 4;
}
st->st_startcond = sc;
@ -314,11 +351,7 @@ static void
parse_audio(th_transport_t *t, th_stream_t *st, uint8_t *data,
int len, int start, aparser_t *ap)
{
int hlen, rlen;
uint8_t *outbuf;
int outlen;
th_pkt_t *pkt;
int64_t dts;
int hlen;
if(start) {
/* Payload unit start */
@ -356,6 +389,21 @@ parse_audio(th_transport_t *t, th_stream_t *st, uint8_t *data,
if(len == 0)
return;
}
parse_audio_with_lavc(t, st, data, len, ap);
}
/**
* Use libavcodec's parsers for audio parsing
*/
static void
parse_audio_with_lavc(th_transport_t *t, th_stream_t *st, uint8_t *data,
int len, aparser_t *ap)
{
uint8_t *outbuf;
int outlen, rlen;
th_pkt_t *pkt;
int64_t dts;
while(len > 0) {
@ -643,7 +691,6 @@ parse_mpeg2video(th_transport_t *t, th_stream_t *st, size_t len,
st->st_curpkt->pkt_frametype = pkt0.pkt_frametype;
st->st_curpkt->pkt_duration = st->st_frame_duration;
st->st_curpkt->pkt_commercial = t->tht_tt_commercial_advice;
break;
case 0x000001b3:
@ -983,12 +1030,13 @@ parser_deliver(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt)
pkt->pkt_pts =av_rescale_q(pts, st->st_tb, AV_TIME_BASE_Q);
pkt->pkt_duration=av_rescale_q(pkt->pkt_duration, st->st_tb, AV_TIME_BASE_Q);
#if 0
printf("%-12s %d %10"PRId64" %10"PRId64" %d\n",
printf("%-12s %d %10"PRId64" %10"PRId64" %10d %10d\n",
streaming_component_type2txt(st->st_type),
pkt->pkt_frametype,
pkt->pkt_dts,
pkt->pkt_pts,
pkt->pkt_duration);
pkt->pkt_duration,
pkt->pkt_payloadlen);
#endif
avgstat_add(&st->st_rate, pkt->pkt_payloadlen, dispatch_clock);

View file

@ -21,8 +21,10 @@
#include "packet.h"
void parse_raw_mpeg(th_transport_t *t, th_stream_t *st, uint8_t *data,
int len, int start, int err);
void parse_mpeg_ts(th_transport_t *t, th_stream_t *st, uint8_t *data,
int len, int start, int err);
void parse_mpeg_ps(th_transport_t *t, th_stream_t *st, uint8_t *data, int len);
void parser_enqueue_packet(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt);

View file

@ -140,6 +140,12 @@ stream_clean(th_stream_t *st)
st->st_buffer_ptr = 0;
st->st_startcode = 0;
free(st->st_buffer2);
st->st_buffer2 = NULL;
st->st_buffer2_size = 0;
st->st_buffer2_ptr = 0;
if(st->st_curpkt != NULL) {
pkt_ref_dec(st->st_curpkt);
st->st_curpkt = NULL;

View file

@ -128,7 +128,7 @@ ts_recv_packet0(th_transport_t *t, th_stream_t *st, uint8_t *tsb)
if(off > 188)
break;
parse_raw_mpeg(t, st, tsb + off, 188 - off, pusi, err);
parse_mpeg_ts(t, st, tsb + off, 188 - off, pusi, err);
break;
}
}

View file

@ -277,6 +277,11 @@ typedef struct th_stream {
int st_parser_ptr;
void *st_priv; /* Parser private data */
uint8_t *st_buffer2;
int st_buffer2_ptr;
int st_buffer2_size;
struct th_pkt *st_curpkt;
int64_t st_curpts;
int64_t st_curdts;
@ -542,6 +547,13 @@ typedef struct th_transport {
*/
struct psi_section *tht_pat_section;
struct psi_section *tht_pmt_section;
/**
* V4l members
*/
struct v4l_adapter *tht_v4l_adapter;
/*********************************************************

672
src/v4l.c
View file

@ -23,296 +23,78 @@
#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>
#define __user
#include <linux/videodev2.h>
#include <libhts/htscfg.h>
#include "tvhead.h"
#include "v4l.h"
#include "channels.h"
#include "dispatch.h"
#include "transports.h"
#include "v4l.h"
#include "parsers.h"
struct th_v4l_adapter_list v4l_adapters;
LIST_HEAD(v4l_adapter_list, v4l_adapter);
static void v4l_fd_callback(int events, void *opaque, int fd);
struct v4l_adapter_list v4l_adapters;
static int v4l_setfreq(th_v4l_adapter_t *tva, int frequency);
typedef struct v4l_adapter {
static void v4l_add_adapter(const char *path);
LIST_ENTRY(v4l_adapter) va_global_link;
char *va_path;
char *va_identifier;
struct v4l2_capability va_caps;
struct th_transport *va_current_transport;
static void v4l_stop_feed(th_transport_t *t);
int va_fd;
static int v4l_start_feed(th_transport_t *t, unsigned int weight, int status,
int force_start);
pthread_t va_thread;
/*
*
*/
void
v4l_init(void)
{
v4l_add_adapter("/dev/video0");
}
int va_pipe[2];
/** Mpeg stream parsing */
uint32_t va_startcode;
int va_lenlock;
} v4l_adapter_t;
/*
*
*/
int
v4l_configure_transport(th_transport_t *t, const char *muxname,
const char *channel_name)
{
config_entry_t *ce;
char buf[100];
if((ce = find_mux_config("v4lmux", muxname)) == NULL)
return -1;
t->tht_type = TRANSPORT_V4L;
t->tht_start_feed = v4l_start_feed;
t->tht_stop_feed = v4l_stop_feed;
t->tht_v4l_frequency =
atoi(config_get_str_sub(&ce->ce_sub, "frequency", "0"));
t->tht_video = transport_add_stream(t, -1, HTSTV_MPEG2VIDEO);
t->tht_audio = transport_add_stream(t, -1, HTSTV_MPEG2AUDIO);
snprintf(buf, sizeof(buf), "Analog: %s (%.2f MHz)", muxname,
(float)t->tht_v4l_frequency / 1000000.0f);
t->tht_name = strdup(buf);
t->tht_provider = strdup("Analog TV");
snprintf(buf, sizeof(buf), "analog_%u", t->tht_v4l_frequency);
t->tht_identifier = strdup(buf);
t->tht_chname = strdup(channel_name);
transport_map_channel(t, NULL);
return 0;
}
/*
/**
*
*/
static void
v4l_add_adapter(const char *path)
v4l_input(v4l_adapter_t *va)
{
int fd, r;
th_v4l_adapter_t *tva;
struct v4l2_capability caps;
fd = open(path, O_RDWR);
if(fd == -1)
return;
r = ioctl(fd, VIDIOC_QUERYCAP, &caps);
close(fd);
if(r < 0)
return;
tva = calloc(1, sizeof(th_v4l_adapter_t));
pthread_cond_init(&tva->tva_run_cond, NULL);
tva->tva_path = strdup(path);
LIST_INSERT_HEAD(&v4l_adapters, tva, tva_link);
tva->tva_name = strdup((char *)caps.card);
}
/*
*
*/
static int
v4l_setfreq(th_v4l_adapter_t *tva, int frequency)
{
struct v4l2_frequency vf;
struct v4l2_tuner vt;
int result;
memset(&vf, 0, sizeof(vf));
memset(&vt, 0, sizeof(vt));
vf.tuner = 0;
vf.type = V4L2_TUNER_ANALOG_TV;
vf.frequency = (frequency * 16) / 1000000;
result = ioctl(tva->tva_fd, VIDIOC_S_FREQUENCY, &vf);
if(result < 0) {
tvhlog(LOG_ERR, "v4l",
"%s: Unable to tune to %dHz\n", tva->tva_path, frequency);
return 1;
}
vt.index = 0;
result = ioctl(tva->tva_fd, VIDIOC_G_TUNER, &vt);
if(result < 0) {
tvhlog(LOG_ERR, "v4l", "%s: Unable read tuner status\n", tva->tva_path);
return 1;
}
tvhlog(LOG_DEBUG, "v4l", "%s: Tuned to %.3f MHz%s",
tva->tva_path, (float)frequency/1000000.0,
vt.signal ? " (Signal Detected)" : "");
return 0;
}
/*
*
*/
static void
v4l_stop(th_v4l_adapter_t *tva)
{
if(tva->tva_dispatch_handle != NULL) {
close(dispatch_delfd(tva->tva_dispatch_handle));
tva->tva_dispatch_handle = NULL;
}
tva->tva_startcode = 0;
}
/*
*
*/
static void
v4l_stop_feed(th_transport_t *t)
{
th_v4l_adapter_t *tva = t->tht_v4l_adapter;
t->tht_v4l_adapter = NULL;
LIST_REMOVE(t, tht_active_link);
t->tht_runstatus = TRANSPORT_IDLE;
if(LIST_FIRST(&tva->tva_transports) == NULL)
v4l_stop(tva);
}
/*
*
*/
static void
v4l_adapter_clean(th_v4l_adapter_t *tva)
{
th_transport_t *t;
while((t = LIST_FIRST(&tva->tva_transports)) != NULL)
v4l_stop_feed(t);
v4l_stop(tva);
}
/*
*
*/
static int
v4l_start_feed(th_transport_t *t, unsigned int weight, int status,
int force_start)
{
th_v4l_adapter_t *tva, *cand = NULL;
int w, fd;
LIST_FOREACH(tva, &v4l_adapters, tva_link) {
w = transport_compute_weight(&tva->tva_transports);
if(w < weight)
cand = tva;
if(tva->tva_frequency == t->tht_v4l_frequency)
break;
}
if(tva == NULL) {
if(cand == NULL)
return 1;
v4l_adapter_clean(cand);
tva = cand;
}
if(tva->tva_dispatch_handle == NULL) {
fd = open(tva->tva_path, O_RDWR);
if(fd == -1)
return 1;
tva->tva_dispatch_handle =
dispatch_addfd(fd, v4l_fd_callback, tva, DISPATCH_READ);
tva->tva_fd = fd;
}
tva->tva_frequency = t->tht_v4l_frequency;
if(v4l_setfreq(tva, tva->tva_frequency))
return 1;
LIST_INSERT_HEAD(&tva->tva_transports, t, tht_active_link);
t->tht_v4l_adapter = tva;
t->tht_runstatus = TRANSPORT_RUNNING;
return 0;
}
static void
v4l_fd_callback(int events, void *opaque, int fd)
{
th_v4l_adapter_t *tva = opaque;
th_transport_t *t;
th_transport_t *t = va->va_current_transport;
th_stream_t *st;
uint8_t buf[4000];
uint8_t *ptr, *pkt;
int len, l, r;
if(!(events & DISPATCH_READ))
return;
len = read(fd, buf, 4000);
len = read(va->va_fd, buf, 4000);
if(len < 1)
return;
t = LIST_FIRST(&tva->tva_transports);
if(t == NULL)
return;
ptr = buf;
pthread_mutex_lock(&t->tht_stream_mutex);
while(len > 0) {
switch(tva->tva_startcode) {
switch(va->va_startcode) {
default:
tva->tva_startcode = tva->tva_startcode << 8 | *ptr;
tva->tva_lenlock = 0;
va->va_startcode = va->va_startcode << 8 | *ptr;
va->va_lenlock = 0;
ptr++; len--;
continue;
@ -324,32 +106,390 @@ v4l_fd_callback(int events, void *opaque, int fd)
break;
}
if(tva->tva_lenlock == 2) {
l = st->st_buffer_size;
st->st_buffer = pkt = realloc(st->st_buffer, l);
if(va->va_lenlock == 2) {
l = st->st_buffer2_size;
st->st_buffer2 = pkt = realloc(st->st_buffer2, l);
r = l - st->st_buffer_ptr;
r = l - st->st_buffer2_ptr;
if(r > len)
r = len;
memcpy(pkt + st->st_buffer_ptr, ptr, r);
memcpy(pkt + st->st_buffer2_ptr, ptr, r);
ptr += r;
len -= r;
st->st_buffer_ptr += r;
if(st->st_buffer_ptr == l) {
// pes_packet_input(t, st, pkt, l);
st->st_buffer_size = 0;
tva->tva_startcode = 0;
st->st_buffer2_ptr += r;
if(st->st_buffer2_ptr == l) {
parse_mpeg_ps(t, st, pkt + 6, l - 6);
st->st_buffer2_size = 0;
va->va_startcode = 0;
} else {
assert(st->st_buffer2_ptr < l);
}
} else {
st->st_buffer_size = st->st_buffer_size << 8 | *ptr;
tva->tva_lenlock++;
if(tva->tva_lenlock == 2) {
st->st_buffer_ptr = 0;
st->st_buffer2_size = st->st_buffer2_size << 8 | *ptr;
va->va_lenlock++;
if(va->va_lenlock == 2) {
st->st_buffer2_size += 6;
st->st_buffer2_ptr = 6;
}
ptr++; len--;
}
}
pthread_mutex_unlock(&t->tht_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_transport_start(th_transport_t *t, unsigned int weight, int status,
int force_start)
{
v4l_adapter_t *va = t->tht_v4l_adapter;
int fd;
fd = open(va->va_path, O_RDWR | O_NONBLOCK);
if(fd == -1) {
tvhlog(LOG_ERR, "v4l",
"%s: Unable to open device: %s\n", va->va_path,
strerror(errno));
return -1;
}
int frequency = 182250000;
struct v4l2_frequency vf;
// struct v4l2_tuner vt;
int result;
v4l2_std_id std = 0xff;
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_DEBUG, "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_transport = t;
t->tht_status = status;
pthread_create(&va->va_thread, NULL, v4l_thread, va);
return 0;
}
/**
*
*/
static void
v4l_transport_refresh(th_transport_t *t)
{
}
/**
*
*/
static void
v4l_transport_stop(th_transport_t *t)
{
char c = 'q';
v4l_adapter_t *va = t->tht_v4l_adapter;
assert(va->va_current_transport != 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_transport = NULL;
t->tht_status = TRANSPORT_IDLE;
}
/**
*
*/
static void
v4l_transport_save(th_transport_t *t)
{
}
/**
*
*/
static int
v4l_transport_quality(th_transport_t *t)
{
return 100;
}
/**
* Generate a descriptive name for the source
*/
static htsmsg_t *
v4l_transport_sourceinfo(th_transport_t *t)
{
htsmsg_t *m = htsmsg_create_map();
#if 0
if(t->tht_v4l_iface != NULL)
htsmsg_add_str(m, "adapter", t->tht_v4l_iface);
htsmsg_add_str(m, "mux", inet_ntoa(t->tht_v4l_group));
#endif
return m;
}
/**
*
*/
static th_transport_t *
v4l_add_transport(v4l_adapter_t *va)
{
th_transport_t *t;
char id[256];
snprintf(id, sizeof(id), "%s_%s", va->va_identifier, "foo");
printf("Adding transport %s\n", id);
t = transport_create(id, TRANSPORT_V4L, 0);
t->tht_flags |= THT_DEBUG;
t->tht_start_feed = v4l_transport_start;
t->tht_refresh_feed = v4l_transport_refresh;
t->tht_stop_feed = v4l_transport_stop;
t->tht_config_save = v4l_transport_save;
t->tht_sourceinfo = v4l_transport_sourceinfo;
t->tht_quality_index = v4l_transport_quality;
t->tht_v4l_adapter = va;
pthread_mutex_lock(&t->tht_stream_mutex);
t->tht_video = transport_stream_create(t, -1, SCT_MPEG2VIDEO);
t->tht_audio = transport_stream_create(t, -1, SCT_MPEG2AUDIO);
pthread_mutex_unlock(&t->tht_stream_mutex);
transport_map_channel(t, channel_find_by_name("alpha", 1), 0);
//XXX LIST_INSERT_HEAD(&v4l_all_transports, t, tht_group_link);
return t;
}
/**
*
*/
static void
v4l_adapter_check(const char *path, int fd)
{
int r, i;
v4l_adapter_t *va;
struct v4l2_capability caps;
r = ioctl(fd, VIDIOC_QUERYCAP, &caps);
if(r) {
tvhlog(LOG_DEBUG, "v4l",
"Can not query capabilities on %s, device skipped", path);
return;
}
if(!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
tvhlog(LOG_DEBUG, "v4l",
"Device %s not a video capture device, device skipped", path);
return;
}
if(!(caps.capabilities & V4L2_CAP_TUNER)) {
tvhlog(LOG_DEBUG, "v4l",
"Device %s does not have a built-in tuner, 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;
printf("%3d: %016llx %24s %d/%d %d lines\n",
standard.index,
standard.id,
standard.name,
standard.frameperiod.numerator,
standard.frameperiod.denominator,
standard.framelines);
}
/* 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)) {
tvhlog(LOG_DEBUG, "v4l",
"Device %s has no suitable formats, device skipped", path);
return;
}
if(fmtdesc.pixelformat == V4L2_PIX_FMT_MPEG)
break;
}
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_path = strdup(path);
va->va_caps = caps;
LIST_INSERT_HEAD(&v4l_adapters, va, va_global_link);
tvhlog(LOG_INFO, "v4l", "Adding adapter %s: %s (%s) @ %s",
path, caps.card, caps.driver, caps.bus_info, caps.bus_info);
v4l_add_transport(va);
}
/**
*
*/
static void
v4l_adapter_probe(const char *path)
{
int fd;
fd = open(path, O_RDWR | O_NONBLOCK);
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);
}
void
v4l_init(void)
{
char buf[256];
int i;
for(i = 0; i < 1; i++) {
snprintf(buf, sizeof(buf), "/dev/video%d", i);
v4l_adapter_probe(buf);
}
}

View file

@ -19,11 +19,6 @@
#ifndef V4L_H_
#define V4L_H_
extern struct th_v4l_adapter_list v4l_adapters;
void v4l_init(void);
int v4l_configure_transport(th_transport_t *t, const char *muxname,
const char *channel_name);
#endif /* V4L_H */