On Fri, 2008-03-21 at 22:04 -0400, Andy Walls wrote:
> On Sun, 2008-03-09 at 16:28 -0400, Andy Walls wrote:
> > Hans Verkuil wrote:
> > > On Sunday 09 March 2008 05:37:04 Andy Walls wrote:
> > > > Hans Verkuil wrote:
> > > > > > Here's my observation: There was no good reason for q_full to
> > > > > > stay empty for so long (> 5 seconds), just because data was
> > > > > > sitting queued in q_io. It was as if data stopped being moved
> > > > > > from the encoder to buffers and into q_full. What is both
> > > > > > fortunate and unfortunate is that draining q_full and q_io,
> > > seems
> > > > > > to restart the transfers from the encoder.
>
>
> > > > Argh. I'm giving up on hunting this down for now. Here are my
> > > > observations and speculations with the return value of
> > > > cx18_v4l2_enc_poll() does not depend on q_io.length:
> > > >
> > > > a) The most buffers I've ever seen in use in the driver are 1 in
> > > q_io
> > > > and 10 in q_full. That was when then encoder decided to give the
> > > > driver 10 buffers all at once.
>
>
> > > > b) I can always get the transfer from the encoder to stall.
> > > >
> > > > c) stopping the capture, reloading all the MDLs and restarting the
> > > > capture doesn't make transfers from the encoder work again.
> > > >
> > > > d) a common sequence of failure looked like the following, with the
> > > > encoder sending a small (2048 bytes) buffer, the driver returning 2
> > > > MDLs very close together, and then the encoder sending only one more
> > > > buffer:
> > >
> > > ...
> > >
> > > >
> > > > I changed the cx18_read() function to exit the loop and return once
> > > > it had returned 1 MDL to the encoder, but the problem still
> > > > persisted. In this case the short buffer in at least one trial was
> > > > 4096 bytes.
> > > >
> > > >
> > >
> > > Is it possible to reproduce it by writing a small capture program
> > > that
> > > acts similar to MythTV? If I had a program like this, then I could
> > > investigate.
> >
>
>
> > Yes, if I have time, I can write a program to emulate the critical
> > aspects of MythTV's operation. You will have to back out the fix to
> > cx18_v4l2_enc_poll() to purposely induce the stall. Also tuning to a
> > weak, snowy channel (but not all snow) and changing channels will induce
> > the problem more rapidly. I assume the encoder can't compress as well
> > under these circumstances, so it sends more data to the host.
>
> Hans,
>
> Attached is the test program for which you asked. With it, you should
> be able to reproduce select() timeouts with cx18_v4l2_enc_poll()
> modified to not watch q_io. So you can at least get the encoder
> transfers to stall for 5 seconds.
>
> I have not been able to get the test program to stall the encoder
> transfers long term yet. I'll have to make it more MythTV-like, I
> guess, by calling some of the ioctls that MythTV does.
Hans,
Attached is an updated version of the test program that sets up the
device with ioctl()s as MythTV would. The item that made the difference
on reproducing the "permanent" stall was the setup_vbi() function.
Note that upon further investigation the encoder isn't completely
non-responsive. After closing and reopening the stream, it will produce
one transfer into q_full. If nothing drains off q_full/q_io after that,
it will not produce another transfer until the stream is closed and
reopened again. For the MythTV user experience, this amount to a black
screen and repeated select timeouts.
Again, the fix to cx18_v4l2_enc_poll() to watch q_io now masks this bug.
Regards,
Andy
/*
* pollcx18 - exercise the cx18 driver in a manner similar to MythTV
* Copyright (C) 2008 Andy Walls
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Author: Andy Walls <[EMAIL PROTECTED]>
*
* Compile: gcc -Wall -O2 -o pollcx18 pollcx18.c
*
* Invoke: ./pollcx18 -d /dev/video1 -o foo.mpg
*
* To see the cx23418 encoder stall out for a while and a select() timeout,
* cx18_v4l2_enc_poll() should be modified not to check q_io, and you should
* tune to and away from a weak, snowy TV station (but not one that's all snow)
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/select.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
struct parsed_args
{
char *devpath;
char *outfile;
};
static int quit = 0;
void sig_handler (int signum, siginfo_t *info, void *ucontext)
{
quit = 1;
}
int parse_args (int argc, char **argv, struct parsed_args *args)
{
int c, retval;
args->devpath = "/dev/video";
args->outfile = NULL;
retval = 0;
while ((c = getopt(argc, argv, "d:o:")) != -1)
{
switch (c)
{
case 'd':
args->devpath = optarg;
break;
case 'o':
args->outfile = optarg;
break;
default:
fprintf (stderr, "Usage:\npollcx18 [-d videodev] [-o outfile]\n");
retval = -1;
break;
}
}
return retval;
}
void mainloop (int *readfd, int outfd, int *quit, char *devpath)
{
const int bufsize = 4 * 1024; /* 4 kB is what MythTV uses */
char buffer[bufsize];
int bytesused, n;
/* Set retry limit to > 0 to try a few times before closing and
* reopening the fd on a select() timeout */
const int retry_limit = 0;
int retries;
int got_data;
int nfds;
struct timeval timeout;
fd_set rfds;
retries = 0;
got_data = 0;
bytesused = 0;
while (!(*quit))
{
/*
* Attempt to reopen the capture device like MythTV, since
* we may close it later in this loop on timeout, like MythTV
*
* We reopen it without O_NONBLOCK, as does MythTV,
* but that's really a MythTV bug. We should really reopen with
* O_NONBLOCK set.
*
*/
if (*readfd < 0)
{
if ((*readfd =
open(devpath, O_RDWR /*|O_NONBLOCK*/)) < 0)
{
perror("open");
fprintf(stderr,
"Unable to re-open video capture device: %s\n",
devpath);
*quit = 1;
break;
}
else
{
got_data = 0;
retries = 0;
}
}
FD_ZERO(&rfds);
FD_SET(*readfd, &rfds);
timeout.tv_sec = 5; /* MythTV uses 5 seconds */
timeout.tv_usec = 0;
nfds = *readfd + 1;
nfds = select (nfds, &rfds, NULL, NULL, &timeout);
switch (nfds)
{
case -1:
if (errno == EINTR)
continue;
perror("select");
fprintf(stderr, "select failed on video "
"capture device: %s\n",
devpath);
*quit = 1;
retries++; /* this doesn't matter for now */
continue;
case 0:
fprintf(stderr, "select timeout on video "
"capture device: %s\n",
devpath);
/*
* MythTV tries to fix things by closing and
* reopening the readfd
*
* Well do few retries before doing so.
*/
retries++;
if (retries >= retry_limit)
{
close(*readfd);
*readfd = -1;
retries = 0;
got_data = 0;
}
continue;
default:
break;
}
retries = 0;
n = read(*readfd, &(buffer[bytesused]), bufsize-bytesused);
if (n == -1 && errno != EAGAIN && errno != EINTR)
{
perror("read");
fprintf(stderr,
"read failed on video capture device: %s\n",
devpath);
*quit = 1;
}
if (n > 0)
{
bytesused += n;
if (got_data == 0)
{
fprintf(stderr,
"received data on video capture device: %s\n",
devpath);
got_data = 1;
}
}
if (bytesused >= bufsize)
{
if (outfd > -1)
write(outfd, buffer, bufsize);
bytesused = 0;
}
}
/* write out the final partially filled buffer */
if (bytesused > 0 && outfd > -1)
write(outfd, buffer, bytesused);
return;
}
int set_picture_size (int chanfd, int width, int height, char *devpath)
{
struct v4l2_format f;
memset (&f, 0, sizeof (f));
f.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(chanfd, VIDIOC_G_FMT, &f) < 0)
{
perror ("ioctl");
fprintf (stderr, "couldn't get video format for device %s\n",
devpath);
return -1;
}
f.fmt.pix.width = width;
f.fmt.pix.height = height;
if (ioctl(chanfd, VIDIOC_S_FMT, &f) < 0)
{
perror("ioctl");
fprintf(stderr, "couldn't get video format for device %s\n",
devpath);
return -1;
}
return 0;
}
int set_volume (int chanfd, int level, char *devpath)
{
struct v4l2_control c;
c.id = V4L2_CID_AUDIO_VOLUME;
c.value = (65536*level)/100;
if (ioctl(chanfd, VIDIOC_S_CTRL, &c) < 0)
{
perror("ioctl");
fprintf(stderr, "couldn't set audio volume for device %s\n",
devpath);
return -1;
}
return 0;
}
int set_audio_fs (int chanfd, int rate, char *devpath)
{
struct v4l2_ext_controls e;
struct v4l2_ext_control c;
memset(&e, 0, sizeof(e));
memset(&c, 0, sizeof(c));
e.ctrl_class = V4L2_CTRL_CLASS_MPEG;
e.count = 1;
e.controls = &c;
c.id = V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ;
switch (rate)
{
case 32000:
c.value = V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000;
break;
case 44100:
c.value = V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100;
break;
case 48000:
default:
c.value = V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000;
break;
}
if (ioctl(chanfd, VIDIOC_S_EXT_CTRLS, &e) < 0)
{
perror("ioctl");
fprintf(stderr,
"couldn't set audio sample rate for device %s\n",
devpath);
return -1;
}
return 0;
}
int set_aspect_ratio (int chanfd, enum v4l2_mpeg_video_aspect aspect,
char *devpath)
{
struct v4l2_ext_controls e;
struct v4l2_ext_control c;
memset(&e, 0, sizeof(e));
memset(&c, 0, sizeof(c));
e.ctrl_class = V4L2_CTRL_CLASS_MPEG;
e.count = 1;
e.controls = &c;
c.id = V4L2_CID_MPEG_VIDEO_ASPECT;
c.value = aspect;
if (ioctl(chanfd, VIDIOC_S_EXT_CTRLS, &e) < 0)
{
perror("ioctl");
fprintf(stderr, "couldn't set aspect ratio for device %s\n",
devpath);
return -1;
}
return 0;
}
int set_audio_encoding (int chanfd, enum v4l2_mpeg_audio_encoding encoding,
char *devpath)
{
struct v4l2_ext_controls e;
struct v4l2_ext_control c;
memset(&e, 0, sizeof(e));
memset(&c, 0, sizeof(c));
e.ctrl_class = V4L2_CTRL_CLASS_MPEG;
e.count = 1;
e.controls = &c;
c.id = V4L2_CID_MPEG_AUDIO_ENCODING;
c.value = encoding;
if (ioctl(chanfd, VIDIOC_S_EXT_CTRLS, &e) < 0)
{
perror("ioctl");
fprintf(stderr, "couldn't set audio encoding for device %s\n",
devpath);
return -1;
}
return 0;
}
int set_audio_bitrate (int chanfd, enum v4l2_mpeg_audio_l2_bitrate rate,
char *devpath)
{
struct v4l2_ext_controls e;
struct v4l2_ext_control c;
memset(&e, 0, sizeof(e));
memset(&c, 0, sizeof(c));
e.ctrl_class = V4L2_CTRL_CLASS_MPEG;
e.count = 1;
e.controls = &c;
c.id = V4L2_CID_MPEG_AUDIO_L2_BITRATE;
c.value = rate;
if (ioctl(chanfd, VIDIOC_S_EXT_CTRLS, &e) < 0)
{
perror("ioctl");
fprintf(stderr, "couldn't set audio encoding for device %s\n",
devpath);
return -1;
}
return 0;
}
int set_video_bitrates (int chanfd, int peak, int nominal, char *devpath)
{
struct v4l2_ext_controls e;
struct v4l2_ext_control c[2];
memset(&e, 0, sizeof(e));
memset(&c, 0, sizeof(struct v4l2_ext_control)*2);
e.ctrl_class = V4L2_CTRL_CLASS_MPEG;
e.count = 2;
e.controls = c;
c[0].id = V4L2_CID_MPEG_VIDEO_BITRATE_PEAK;
c[0].value = peak;
c[1].id = V4L2_CID_MPEG_VIDEO_BITRATE;
c[1].value = nominal;
if (ioctl(chanfd, VIDIOC_S_EXT_CTRLS, &e) < 0)
{
perror("ioctl");
fprintf(stderr, "couldn't set video bitrates for device %s\n",
devpath);
return -1;
}
return 0;
}
int set_streamtype (int chanfd, enum v4l2_mpeg_stream_type type, char *devpath)
{
struct v4l2_ext_controls e;
struct v4l2_ext_control c;
memset(&e, 0, sizeof(e));
memset(&c, 0, sizeof(c));
e.ctrl_class = V4L2_CTRL_CLASS_MPEG;
e.count = 1;
e.controls = &c;
c.id = V4L2_CID_MPEG_STREAM_TYPE;
c.value = type;
if (ioctl(chanfd, VIDIOC_S_EXT_CTRLS, &e) < 0)
{
perror("ioctl");
fprintf(stderr, "couldn't set stream type for device %s\n",
devpath);
return -1;
}
return 0;
}
int setup_vbi (int chanfd, int vbitype, char *devpath)
{
struct v4l2_format f;
struct v4l2_ext_controls e;
struct v4l2_ext_control c;
memset (&f, 0, sizeof (f));
f.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE;
f.fmt.sliced.service_set = vbitype;
if (ioctl(chanfd, VIDIOC_S_FMT, &f) < 0)
{
perror("ioctl");
fprintf(stderr, "couldn't enable vbi for device %s\n",
devpath);
return -1;
}
memset(&e, 0, sizeof(e));
memset(&c, 0, sizeof(c));
e.ctrl_class = V4L2_CTRL_CLASS_MPEG;
e.count = 1;
e.controls = &c;
c.id = V4L2_CID_MPEG_STREAM_VBI_FMT;
c.value = V4L2_MPEG_STREAM_VBI_FMT_IVTV;
if (ioctl(chanfd, VIDIOC_S_EXT_CTRLS, &e) < 0)
{
perror("ioctl");
fprintf(stderr,
"couldn't set vbi stream format for device %s\n",
devpath);
return -1;
}
return 0;
}
int main (int argc, char **argv)
{
int chanfd, readfd, outfd;
struct parsed_args args;
struct sigaction sigact;
if (parse_args(argc, argv, &args) < 0)
exit(1);
outfd = -1;
if (args.outfile != NULL &&
(outfd = open(args.outfile, O_WRONLY|O_CREAT|O_TRUNC, 0660)) < 0)
{
perror("open");
fprintf(stderr, "Unable to open output file: %s\n",
args.outfile);
}
/* MythTV would use this fd for controlling the capture device */
if ((chanfd = open(args.devpath, O_RDWR)) < 0)
{
perror("open");
fprintf(stderr, "Unable to open video control device: %s\n",
args.devpath);
exit(2);
}
/* Setup like MythTV does on my NTSC-M system */
set_picture_size (chanfd, 704, 480, args.devpath);
set_volume (chanfd, 90, args.devpath);
set_audio_fs (chanfd, 48000, args.devpath);
set_aspect_ratio (chanfd, V4L2_MPEG_VIDEO_ASPECT_4x3, args.devpath);
set_audio_encoding (chanfd, V4L2_MPEG_AUDIO_ENCODING_LAYER_2,
args.devpath);
set_audio_bitrate (chanfd, V4L2_MPEG_AUDIO_L2_BITRATE_384K,
args.devpath);
set_video_bitrates (chanfd, 6000000, 4500000, args.devpath);
set_streamtype (chanfd, V4L2_MPEG_STREAM_TYPE_MPEG2_PS,
args.devpath);
/*
* This vbi setup makes the difference between the encoder permanently
* stalling or not
*/
setup_vbi (chanfd, V4L2_SLICED_VBI_525, args.devpath);
/* MythTV would use this fd for video capture */
if ((readfd = open(args.devpath, O_RDWR|O_NONBLOCK)) < 0)
{
perror("open");
fprintf(stderr, "Unable to open video capture device: %s\n",
args.devpath);
exit(3);
}
sigact.sa_flags = SA_SIGINFO;
sigact.sa_sigaction = sig_handler;
sigemptyset(&(sigact.sa_mask));
sigaction(SIGINT, &sigact, NULL); /* handle ^C to do a graceful exit */
quit = 0;
mainloop(&readfd, outfd, &quit, args.devpath);
close(readfd);
close(chanfd);
close(outfd);
exit(0);
}
_______________________________________________
ivtv-devel mailing list
[email protected]
http://ivtvdriver.org/mailman/listinfo/ivtv-devel