| /* |
| decode_tm6000.c - decode multiplexed format from TM5600/TM6000 USB |
| |
| Copyright (C) 2007 Mauro Carvalho Chehab <mchehab@kernel.org> |
| |
| 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 version 2. |
| |
| 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, write to the Free Software |
| Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA. |
| */ |
| #include "../utils/libv4l2util/v4l2_driver.h" |
| #include <stdio.h> |
| #include <string.h> |
| #include <argp.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| |
| const char *argp_program_version="decode_tm6000 version 0.0.1"; |
| const char *argp_program_bug_address="Mauro Carvalho Chehab <mchehab@kernel.org>"; |
| const char doc[]="Decodes tm6000 proprietary format streams"; |
| const struct argp_option options[] = { |
| {"verbose", 'v', 0, 0, "enables debug messages", 0}, |
| {"device", 'd', "DEV", 0, "uses device for reading", 0}, |
| {"output", 'o', "FILE", 0, "outputs raw stream to a file", 0}, |
| {"input", 'i', "FILE", 0, "parses a file, instead of a device", 0}, |
| {"freq", 'f', "Freq", 0, "station frequency, in MHz (default is 193.25)", 0}, |
| {"nbufs", 'n', "quant",0, "number of video buffers", 0}, |
| {"audio", 'a', 0, 0, "outputs audio on stdout", 0}, |
| {"read", 'r', 0, 0, "use read() instead of mmap method", 0}, |
| { 0, 0, 0, 0, 0, 0 } |
| }; |
| |
| static char outbuf[692224]; |
| static int debug=0, audio=0, use_mmap=1, nbufs=4; |
| static float freq_mhz=193.25; |
| static char *devicename="/dev/video0"; |
| static char *filename=NULL; |
| static enum { |
| NORMAL, |
| INPUT, |
| OUTPUT |
| } mode = NORMAL; |
| |
| static FILE *fout; |
| |
| //const char args_doc[]="ARG1 ARG2"; |
| |
| static error_t parse_opt (int key, char *arg, struct argp_state *state) |
| { |
| switch (key) { |
| case 'a': |
| audio++; |
| break; |
| case 'r': |
| use_mmap=0; |
| break; |
| case 'v': |
| debug++; |
| break; |
| case 'd': |
| devicename=arg; |
| break; |
| case 'i': |
| case 'o': |
| if (mode!=NORMAL) { |
| argp_error(state,"You can't use input/output options simultaneously.\n"); |
| break; |
| } |
| if (key=='i') |
| mode=INPUT; |
| else |
| mode=OUTPUT; |
| |
| filename=arg; |
| break; |
| case 'f': |
| freq_mhz=atof(arg); |
| break; |
| case 'n': |
| nbufs=atoi(arg); |
| if (nbufs<2) |
| nbufs=2; |
| break; |
| default: |
| return ARGP_ERR_UNKNOWN; |
| } |
| return 0; |
| } |
| |
| static struct argp argp = { |
| .options=options, |
| .parser=parse_opt, |
| .args_doc=NULL, |
| .doc=doc, |
| }; |
| |
| #define TM6000_URB_MSG_LEN 180 |
| enum { |
| TM6000_URB_MSG_VIDEO=1, |
| TM6000_URB_MSG_AUDIO, |
| TM6000_URB_MSG_VBI, |
| TM6000_URB_MSG_PTS, |
| TM6000_URB_MSG_ERR, |
| }; |
| |
| const char *tm6000_msg_type[]= { |
| "unknown(0)", //0 |
| "video", //1 |
| "audio", //2 |
| "vbi", //3 |
| "pts", //4 |
| "err", //5 |
| "unknown(6)", //6 |
| "unknown(7)", //7 |
| }; |
| |
| #define dprintf(fmt,arg...) \ |
| if (debug) fprintf(stderr, fmt, ##arg) |
| |
| static int recebe_buffer (struct v4l2_buffer *v4l2_buf, struct v4l2_t_buf *buf) |
| { |
| dprintf("Received %zd bytes\n", buf->length); |
| fflush(stdout); |
| memcpy (outbuf,buf->start,buf->length); |
| return buf->length; |
| } |
| |
| |
| static int prepare_read (struct v4l2_driver *drv) |
| { |
| struct v4l2_format fmt; |
| double freq; |
| int rc; |
| |
| memset (drv,0,sizeof(*drv)); |
| |
| if (v4l2_open (devicename, 1,drv)<0) { |
| perror ("Error opening dev"); |
| return -1; |
| } |
| |
| memset (&fmt,0,sizeof(fmt)); |
| |
| uint32_t pixelformat=V4L2_PIX_FMT_TM6000; |
| |
| if (v4l2_gettryset_fmt_cap (drv,V4L2_SET,&fmt, 720, 480, |
| pixelformat,V4L2_FIELD_ANY)) { |
| perror("set_input to tm6000 raw format"); |
| return -1; |
| } |
| |
| if (freq_mhz) { |
| freq=freq_mhz * 1000 * 1000; |
| rc=v4l2_getset_freq (drv,V4L2_SET, &freq); |
| if (rc<0) |
| printf ("Cannot set freq to %.3f MHz\n",freq_mhz); |
| } |
| |
| if (use_mmap) { |
| printf("Preparing for receiving frames on %d buffers...\n",nbufs); |
| fflush (stdout); |
| |
| rc=v4l2_mmap_bufs(drv, nbufs); |
| if (rc<0) { |
| printf ("Cannot mmap %d buffers\n",nbufs); |
| return -1; |
| } |
| |
| // v4l2_stop_streaming(&drv); |
| rc=v4l2_start_streaming(drv); |
| if (rc<0) { |
| printf ("Cannot start streaming\n"); |
| return -1; |
| } |
| } |
| printf("Waiting for frames...\n"); |
| |
| return 0; |
| } |
| |
| static int read_stream (struct v4l2_driver *drv, int fd) |
| { |
| if (use_mmap) { |
| fd_set fds; |
| struct timeval tv; |
| int r; |
| |
| FD_ZERO (&fds); |
| FD_SET (fd, &fds); |
| |
| /* Timeout. */ |
| tv.tv_sec = 2; |
| tv.tv_usec = 0; |
| |
| r = select (fd + 1, &fds, NULL, NULL, &tv); |
| if (-1 == r) { |
| if (EINTR == errno) { |
| perror ("select"); |
| return -errno; |
| } |
| } |
| |
| if (0 == r) { |
| fprintf (stderr, "select timeout\n"); |
| return -errno; |
| } |
| |
| return v4l2_rcvbuf(drv, recebe_buffer); |
| } else { |
| int size=read(fd, outbuf, sizeof(outbuf)); |
| return size; |
| } |
| |
| return 0; |
| } |
| |
| static int read_char (struct v4l2_driver *drv, int fd) |
| { |
| static int sizebuf=0; |
| static unsigned char *p=NULL; |
| unsigned char c; |
| |
| if (sizebuf<=0) { |
| sizebuf=read_stream(drv,fd); |
| if (sizebuf<=0) |
| return -1; |
| p=(unsigned char *)outbuf; |
| } |
| c=*p; |
| p++; |
| sizebuf--; |
| |
| return c; |
| } |
| |
| |
| int main (int argc, char*argv[]) |
| { |
| int fd; |
| unsigned int i; |
| unsigned char buf[TM6000_URB_MSG_LEN], img[720*2*480] = {}; |
| unsigned int cmd, size, field, block, line, pos=0; |
| unsigned long header=0; |
| int linesize=720*2,skip=0; |
| struct v4l2_driver drv; |
| |
| argp_parse (&argp, argc, argv, 0, 0, 0); |
| |
| if (mode!=INPUT) { |
| if (prepare_read (&drv)<0) |
| return -1; |
| fd=drv.fd; |
| } else { |
| /*mode == INPUT */ |
| |
| fd=open(filename,O_RDONLY); |
| if (fd<0) { |
| perror ("error opening a file for parsing"); |
| return -1; |
| } |
| dprintf("file %s opened for parsing\n",filename); |
| use_mmap=0; |
| } |
| |
| if (mode==OUTPUT) { |
| fout=fopen(filename,"w"); |
| if (!fout) { |
| perror ("error opening a file to write"); |
| return -1; |
| } |
| dprintf("file %s opened for output\n",filename); |
| |
| do { |
| size=read_stream (&drv,fd); |
| |
| if (size<=0) { |
| close (fd); |
| return -1; |
| } |
| dprintf("writing %d bytes\n",size); |
| fwrite(outbuf,1, size,fout); |
| // fflush(fout); |
| } while (1); |
| } |
| |
| |
| while (1) { |
| skip=0; |
| header=0; |
| do { |
| int c; |
| c=read_char (&drv,fd); |
| if (c<0) { |
| perror("read"); |
| return -1; |
| } |
| |
| header=(header>>8)&0xffffff; |
| header=header|(c<<24); |
| skip++; |
| } while ( (((header>>24)&0xff) != 0x47) ); |
| |
| /* split the header fields */ |
| size = (((header & 0x7e)<<1) -1) * 4; |
| block = (header>>7) & 0xf; |
| field = (header>>11) & 0x1; |
| line = (header>>12) & 0x1ff; |
| cmd = (header>>21) & 0x7; |
| |
| /* Read the remaining buffer */ |
| for (i=0;i<sizeof(buf);i++) { |
| int c; |
| c=read_char (&drv,fd); |
| if (c<0) { |
| perror("read"); |
| return -1; |
| } |
| buf[i]=c; |
| } |
| |
| /* FIXME: Mounts the image as field0+field1 |
| * It should, instead, check if the user selected |
| * entrelaced or non-entrelaced mode |
| */ |
| pos=((line<<1)+field)*linesize+ |
| block*TM6000_URB_MSG_LEN; |
| |
| /* Prints debug info */ |
| dprintf("0x%08x (skip %d), %s size=%d, line=%d, field=%d, block=%d\n", |
| (unsigned int)header, skip, |
| tm6000_msg_type[cmd], |
| size, line, field, block); |
| |
| /* Don't allow to write out of the buffer */ |
| if (pos+sizeof(buf) > sizeof(img)) |
| cmd = TM6000_URB_MSG_ERR; |
| |
| /* handles each different URB message */ |
| switch(cmd) { |
| case TM6000_URB_MSG_VIDEO: |
| /* Fills video buffer */ |
| memcpy(buf,&img[pos],sizeof(buf)); |
| case TM6000_URB_MSG_AUDIO: |
| if (audio) |
| fwrite(buf,sizeof(buf),1,stdout); |
| // case TM6000_URB_MSG_VBI: |
| // case TM6000_URB_MSG_PTS: |
| break; |
| } |
| } |
| close(fd); |
| return 0; |
| } |