Hi,

I have a WinTV-HVR-950Q which I am using to capture composite video. I have two cameras: a rear-view cam for a car, and a security camera.

When I plug either of these cameras into the video plug on a plain old TV, they work great. When I plug either camera into the 950Q on Windows using the supplied WinTV software, they work great. When I plug the rear-view camera into the 950Q on Linux, it works great. But when I plug the security camera into 950Q on Linux, it mostly works and then the picture starts randomly jumping sideways (like it is having trouble keeping a horizontal sync on the signal) and then will suddenly flip to a green-grayscale image with all bright areas as purple-grayscale. Once turned green/purple, it remains like this until I reset the camera, but the video is full framerate, low latency, and looks flawless aside from the bizarre colors.

For the tests under Linux, I am using the v4l2 API directly in a simple demo C program I wrote. It is attached. I tried both the "read" API, and the mmap API. Both produce identical results.

My other attempts on Linux had been to use v4l2-ctl to select the composite channel, and then play the device with VLC or Cheese. Neither were successful (no video at all) but I need to do this from C in the long run, anyway.

Anyone seen anything like this before?

Thanks in advance.
-Mike

------------

Some details:

Linux Mint 12, 3.0.0-12-generic #20-Ubuntu SMP Fri Oct 7 14:56:25 UTC 2011 x86_64 x86_64 x86_64 GNU/Linux

Bus 001 Device 007: ID 2040:7200 Hauppauge
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x2040 Hauppauge
  idProduct          0x7200
  bcdDevice            0.05
  iManufacturer           1
  iProduct                2
  iSerial                10
  bNumConfigurations      1

Relevant loaded modules:

tuner                  27428  1
au8522                 27916  2
au0828                 48363  0
dvb_core              110616  1 au0828
videobuf_vmalloc       13589  1 au0828
videobuf_core          26390  2 au0828,videobuf_vmalloc
tveeprom               21249  1 au0828
v4l2_common            16454  3 tuner,au8522,au0828
videodev               93004  4 tuner,au8522,au0828,v4l2_common
v4l2_compat_ioctl32    17083  1 videodev

Relevant kernel messages (only from bootup, nothing new shows while playing)

[   10.467852] Linux video capture interface: v2.00
[   10.677764] au0828 driver loaded
[   11.036080] au0828: i2c bus registered
[ 11.294654] tveeprom 0-0050: Hauppauge model 72001, rev B4F0, serial# 8455749
[   11.294658] tveeprom 0-0050: MAC address is 00:0d:fe:81:06:45
[ 11.294661] tveeprom 0-0050: tuner model is Xceive XC5000 (idx 150, type 76) [ 11.294664] tveeprom 0-0050: TV standards NTSC(M) ATSC/DVB Digital (eeprom 0x88)
[   11.294666] tveeprom 0-0050: audio processor is AU8522 (idx 44)
[   11.294669] tveeprom 0-0050: decoder processor is AU8522 (idx 42)
[ 11.294672] tveeprom 0-0050: has no radio, has IR receiver, has no IR transmitter
[   11.294674] hauppauge_eeprom: hauppauge eeprom: model=72001
[   11.321442] nvidia: module license 'NVIDIA' taints kernel.
[   11.321446] Disabling lock debugging due to kernel taint
[ 12.006495] nvidia 0000:01:00.0: PCI INT A -> GSI 16 (level, low) -> IRQ 16
[   12.006503] nvidia 0000:01:00.0: setting latency timer to 64
[ 12.006508] vgaarb: device changed decodes: PCI:0000:01:00.0,olddecodes=io+mem,decodes=none:owns=io+mem [ 12.006583] NVRM: loading NVIDIA UNIX x86_64 Kernel Module 280.13 Wed Jul 27 16:53:56 PDT 2011
[   12.020865] au8522 0-0047: creating new instance
[   12.020867] au8522_decoder creating new instance...
[   12.061240] i2c-core: driver [tuner] using legacy suspend method
[   12.061242] i2c-core: driver [tuner] using legacy resume method
[   12.061662] tuner 0-0061: Tuner -1 found with type(s) Radio TV.
[   12.089530] xc5000 0-0061: creating new instance
[   12.094274] xc5000: Successfully identified at address 0x61
[   12.094277] xc5000: Firmware has not been loaded previously
[   12.094536] au8522 0-0047: attaching existing instance
[   12.101901] xc5000 0-0061: attaching existing instance
[   12.112396] xc5000: Successfully identified at address 0x61
[   12.112398] xc5000: Firmware has not been loaded previously
[   12.112400] DVB: registering new adapter (au0828)
[ 12.112402] DVB: registering adapter 0 frontend 0 (Auvitek AU8522 QAM/8VSB Frontend)...
[   12.112699] Registered device AU0828 [Hauppauge HVR950Q]
[   12.631925] usbcore: registered new interface driver snd-usb-audio
[   12.632064] usbcore: registered new interface driver au0828

Output from my program:

Driver : au0828
Card   : Hauppauge HVR950Q
Bus    : au0828 1-5.1.2:1.0
Version: 0.0.1
Caps : V4L2_CAP_VIDEO_CAPTURE V4L2_CAP_VBI_CAPTURE V4L2_CAP_TUNER V4L2_CAP_AUDIO V4L2_CAP_READWRITE V4L2_CAP_STREAMING
Control 9963776: Brightness           (0-255) slider
Control 9963777: Contrast             (0-255) slider
Control 9963778: Saturation           (0-255) slider
Control 9963779: Hue                  (-32768-32768) slider
4 controls
Input Television: tuner 0  status:
Input Composite: camera status:
Input S-Video: camera status:
3 inputs.
Set input to 1 (new val = 1)
Current video standard: NTSC-M
Image Format: 720 x 480 UYVY field=4 pitch=1440 size=691200 colorspace=1
End of formats
Received 691200 bytes
... (repeated for each frame)
//#include "config.h"
#include <SDL/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <string.h>
#include <assert.h>

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include <malloc.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

#include <asm/types.h>
#include <linux/videodev2.h>
typedef long long unsigned UINT64_C;
extern "C" {
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>
}

#include <fcntl.h>
#include <sys/ioctl.h>

void showcaps(int fd);
void showcontrols(int fd);
void show_inputs(int fd);
void set_input(int fd, int idx);
void query_standard(int fd);
void query_imgfmt(int fd, v4l2_pix_format *fmt);
void query_formats(int fd);
bool show_frame(uint8_t *frame, PixelFormat fmt, int stride, int w, int h);
void init_mmap(int fd);
void* acquire_mmap_frame(int fd, struct v4l2_buffer *buffer);
void release_mmap_frame(int fd, struct v4l2_buffer *buffer);
void finalize_mmap(int fd);

uint8_t *framebuf;
uint8_t *rgbbuf;
struct mmap_buffer_item {
	void *start;
	size_t length;
	bool locked;
} *mmap_buffers= NULL;
int mmap_buffer_count= 0;

int main() {
	v4l2_pix_format fmt;
	struct v4l2_buffer buffer;
	//int lines;
	int got= 0;
	
	int fd= open("/dev/video0", O_RDWR);
	if (fd < 0) { perror("open(/dev/video0)"); exit(2); }
	
	showcaps(fd);
	showcontrols(fd);
	show_inputs(fd);
	
	set_input(fd, 1);
	query_standard(fd);
	query_imgfmt(fd, &fmt);
	query_formats(fd);
	printf("End of formats\n");
	
	int framesize= fmt.sizeimage;
	int imglines= fmt.height; // account for interlace
	//framebuf= (uint8_t*) malloc(framesize);
	//if (!framebuf) { perror("malloc"); return 1; }
	rgbbuf= (uint8_t*) malloc(fmt.width * imglines * 3);
	if (!rgbbuf) { perror("malloc"); return 1; }
	
	init_mmap(fd);
	while (1) {
		#if 0
		got= read(fd, framebuf, framesize);
		if (got < 0) { perror("read"); return 2; }
		#else
		framebuf= (uint8_t*) acquire_mmap_frame(fd, &buffer);
		got= framesize= buffer.bytesused;
		#endif
		printf("Received %d bytes\n", got);
	
	
		if (got != framesize) {
			printf("didn't get frame size\n");
		} else if (imglines * (int)fmt.bytesperline > framesize) {
			printf("frame not large enough for image\n");
		} else if (!show_frame(framebuf, PIX_FMT_UYVY422, fmt.bytesperline, fmt.width, imglines)) {
			abort();
		}
		
		release_mmap_frame(fd, &buffer);
	}
	
	//unlink("frame");
	//int out= open("frame", O_RDWR|O_CREAT, 777);
	//if (out < 0) { perror("open(frame)"); exit(2); }
	//write(out, framebuf, got);
	//close(out);

	close(fd);
	return 0;
}

struct SwsContext *convparam= NULL;
SDL_Surface *window= NULL;
SDL_Event event;

bool show_frame(uint8_t *frame, PixelFormat fmt, int stride, int w, int h) {
	if (!window) {
		if (SDL_Init(SDL_INIT_VIDEO) < 0) {
			fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
			return false;
		}
		atexit(SDL_Quit);
		
		window = SDL_SetVideoMode(w, h, 24, SDL_SWSURFACE);
		if (!window) {
			fprintf(stderr, "Couldn't set create window: %s\n", SDL_GetError());
			return false;
		}
	}
	
	convparam= sws_getCachedContext(convparam, w, h, fmt, w, h, PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);
	if (!convparam) {
		printf("Unable to convert image format\n");
		return false;
	}
	
	//void* pixels;
	//int pitch;
	if (SDL_LockSurface(window) < 0) {
		fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError());
		return false;
	}
	
	uint8_t* s_data[4]= { frame, NULL, NULL, NULL };
	int s_linesize[4]= { stride, 0, 0, 0 };
	uint8_t* d_data[4]= { (uint8_t*)window->pixels, NULL, NULL, NULL };
	int d_linesize[4]= { window->pitch, 0, 0, 0 };
	sws_scale(convparam, s_data, s_linesize, 0, h, d_data, d_linesize);
	
	SDL_UnlockSurface(window);

	SDL_UpdateRect(window, 0, 0, w, h);

	bool done= false;
	while (SDL_PollEvent(&event)) {
		switch (event.type) {
		case SDL_KEYDOWN:
			if (event.key.keysym.sym == SDLK_ESCAPE) {
				done= true;
			}
			break;
		case SDL_QUIT:
			done= true;
			break;
		}
	}
	
	if (done) {
		SDL_Quit();
		window= NULL;
	}
	
	return true;
}

typedef struct {
	long long code;
	const char* name;
} const_entry_t;
/*const char * find_constant(struct const_entry_t *array, int array_len, int code) {
	for (int i=0; i<array_len; i++) {
		if (array[i].code == code)
			return array[i].name;
	}
	return "??";
}*/
#define ARRAY_LENGTH(array) ((int)(sizeof(array)/sizeof(*array)))
#define F(x) { x, #x }
static const_entry_t
v4l_cap_decode[]= {
	F(V4L2_CAP_VIDEO_CAPTURE),
	F(V4L2_CAP_VIDEO_OUTPUT),
	F(V4L2_CAP_VIDEO_OVERLAY),
	F(V4L2_CAP_VBI_CAPTURE),
	F(V4L2_CAP_VBI_OUTPUT),
	F(V4L2_CAP_SLICED_VBI_CAPTURE),
	F(V4L2_CAP_SLICED_VBI_OUTPUT),
	F(V4L2_CAP_RDS_CAPTURE),
	F(V4L2_CAP_VIDEO_OUTPUT_OVERLAY),
	F(V4L2_CAP_TUNER),
	F(V4L2_CAP_AUDIO),
	F(V4L2_CAP_RADIO),
	F(V4L2_CAP_READWRITE),
	F(V4L2_CAP_ASYNCIO),
	F(V4L2_CAP_STREAMING)
},
v4l_input_status[]= {
	F(V4L2_IN_ST_NO_POWER),
	F(V4L2_IN_ST_NO_SIGNAL),
	F(V4L2_IN_ST_NO_COLOR),
	F(V4L2_IN_ST_NO_H_LOCK),
	F(V4L2_IN_ST_COLOR_KILL),
	F(V4L2_IN_ST_NO_SYNC),
	F(V4L2_IN_ST_NO_EQU),
	F(V4L2_IN_ST_NO_CARRIER),
	F(V4L2_IN_ST_MACROVISION),
	F(V4L2_IN_ST_NO_ACCESS),
	F(V4L2_IN_ST_VTR),
},
v4l_vid_std[]= {
	F(V4L2_STD_PAL_B),
	F(V4L2_STD_PAL_B1),
	F(V4L2_STD_PAL_G),
	F(V4L2_STD_PAL_H),
	F(V4L2_STD_PAL_I),
	F(V4L2_STD_PAL_D),
	F(V4L2_STD_PAL_D1),
	F(V4L2_STD_PAL_K),
	F(V4L2_STD_PAL_M),
	F(V4L2_STD_PAL_N),
	F(V4L2_STD_PAL_Nc),
	F(V4L2_STD_PAL_60),
	F(V4L2_STD_NTSC_M),
	F(V4L2_STD_NTSC_M_JP),
	F(V4L2_STD_NTSC_443),
	F(V4L2_STD_NTSC_M_KR),
	F(V4L2_STD_SECAM_B),
	F(V4L2_STD_SECAM_D),
	F(V4L2_STD_SECAM_G),
	F(V4L2_STD_SECAM_H),
	F(V4L2_STD_SECAM_K),
	F(V4L2_STD_SECAM_K1),
	F(V4L2_STD_SECAM_L),
	F(V4L2_STD_SECAM_LC),
	F(V4L2_STD_ATSC_8_VSB),
	F(V4L2_STD_ATSC_16_VSB)
};
#undef F

void showcaps(int fd) {
	struct v4l2_capability caps;
	int ret= ioctl(fd, VIDIOC_QUERYCAP, &caps);
	if (ret < 0) { perror("ioctl(QUERYCAP)"); exit(2); }
	
	printf("Driver : %s\nCard   : %s\nBus    : %s\nVersion: %u.%u.%u\nCaps   :",
		caps.driver,
		caps.card,
		caps.bus_info,
		(caps.version>>16)&0xFF, (caps.version>>8)&0xFF, caps.version&0xFF);
	for (int i=0; i < ARRAY_LENGTH(v4l_cap_decode); i++) {
		if (caps.capabilities & v4l_cap_decode[i].code)
			printf(" %s", v4l_cap_decode[i].name);
	}
	printf("\n");
}

void showcontrols(int fd) {
	struct v4l2_queryctrl ctrl;
	int cnt= 0;
	for (int i=V4L2_CID_BASE; i < V4L2_CID_LASTP1; i++) {
		memset(&ctrl, 0, sizeof(ctrl));
		ctrl.id= i;
		int ret= ioctl(fd, VIDIOC_QUERYCTRL, &ctrl);
		if (ret < 0) {
			if (errno == EINVAL) continue;
			perror("ioctl(QUERYCTRL)"); exit(2);
		}
		cnt++;
		
		printf("Control %3d: %-20s (%d-%d)%s%s%s%s%s%s\n", i, ctrl.name, ctrl.minimum, ctrl.maximum,
			(ctrl.flags&V4L2_CTRL_FLAG_DISABLED)? " disabled":"",
			(ctrl.flags&V4L2_CTRL_FLAG_GRABBED)? " grabbed":"",
			(ctrl.flags&V4L2_CTRL_FLAG_READ_ONLY)? " r/o":"",
			(ctrl.flags&V4L2_CTRL_FLAG_UPDATE)? " update":"",
			(ctrl.flags&V4L2_CTRL_FLAG_INACTIVE)? " inact":"",
			(ctrl.flags&V4L2_CTRL_FLAG_SLIDER)? " slider":""
		);
	}
	printf("%d controls\n", cnt);
}

void show_inputs(int fd) {
	struct v4l2_input inp;
	int i= 0;
	while (1) {
		memset(&inp, 0, sizeof(inp));
		inp.index= i;
		int ret= ioctl(fd, VIDIOC_ENUMINPUT, &inp);
		if (ret < 0) {
			if (errno == EINVAL) break;
			perror("ioctl(ENUMINPUT)");
			exit(2);
		}
		i++;
		
		if (inp.type == V4L2_INPUT_TYPE_TUNER) {
			printf("Input %s: tuner %d  status:", inp.name, inp.tuner);
		} else if (inp.type == V4L2_INPUT_TYPE_CAMERA) {
			printf("Input %s: camera status:", inp.name);
		} else {
			printf("Input %s: (unknown) status:", inp.name);
		}
		for (int f= 0; f < ARRAY_LENGTH(v4l_input_status); f++) {
			if (inp.status & v4l_input_status[f].code)
				printf(" %s", v4l_input_status[i].name);
		}
		printf("\n");
	}
	printf("%d inputs.\n", i);
}

void set_input(int fd, int idx) {
	if (-1 == ioctl(fd, VIDIOC_S_INPUT, &idx)) {
		perror("VIDEOC_S_INPUT");
		exit(2);
	}
	int newVal= -1;
	if (-1 == ioctl(fd, VIDIOC_G_INPUT, &newVal)) {
		perror("VIDEOC_G_INPUT");
		exit(2);
	}
	printf("Set input to %d (new val = %d)\n", idx, newVal);
}

void query_standard(int fd) {
	v4l2_std_id std_id;
	struct v4l2_standard standard;

	if (-1 == ioctl (fd, VIDIOC_G_STD, &std_id)) {
			/* Note when VIDIOC_ENUMSTD always returns EINVAL this
			   is no video device or it falls under the USB exception,
			   and VIDIOC_G_STD returning EINVAL is no error. */

			perror ("VIDIOC_G_STD");
			exit (2);
	}

	memset (&standard, 0, sizeof (standard));
	standard.index = 0;

	while (0 == ioctl (fd, VIDIOC_ENUMSTD, &standard)) {
			if (standard.id & std_id) {
				   printf ("Current video standard: %s\n", standard.name);
				   return;
			}

			standard.index++;
	}
	printf("Unknown standard\n");
}

void query_formats(int fd) {
	v4l2_fmtdesc fmtd;
	int i=0;
	do {
		memset(&fmtd, 0, sizeof(fmtd));
		fmtd.index= i++;
		int ret= ioctl(fd, VIDIOC_ENUM_FMT, &fmtd);
		if (ret < 0) {
			if (errno == EINVAL) return;
			perror("ioctl(ENUM_FMT)");
		}
		
		printf("Pixel Format %s: type=%d flags=%d pixelformat=%d (%4.4s)\n", fmtd.description, fmtd.type, fmtd.flags, fmtd.pixelformat, (char*)&fmtd.pixelformat);
	} while (1);
}

void query_imgfmt(int fd, v4l2_pix_format *fmt) {
	v4l2_format vfmt;
	v4l2_pix_format *pix= &vfmt.fmt.pix;
	memset(&vfmt, 0, sizeof(vfmt));
	vfmt.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (ioctl(fd, VIDIOC_G_FMT, &vfmt) < 0) {
		perror("ioctl(G_FMT)");
		exit(1);
	}
	printf("Image Format: %d x %d %4.4s field=%d pitch=%d size=%d colorspace=%d\n",
		pix->width, pix->height, (char*)&pix->pixelformat, pix->field, pix->bytesperline, pix->sizeimage, pix->colorspace);
	
	if (fmt) memcpy(fmt, pix, sizeof(*fmt));
}

void init_mmap(int fd) {
	struct v4l2_requestbuffers reqbuf;
	struct v4l2_buffer buffer;
	unsigned int i;
	
	memset(&reqbuf, 0, sizeof(reqbuf));
	reqbuf.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
	reqbuf.memory= V4L2_MEMORY_MMAP;
	reqbuf.count= 8;
	
	if (-1 == ioctl(fd, VIDIOC_REQBUFS, &reqbuf)) {
		if (errno == EINVAL)
			printf("Video capturing or mmap-streaming is not supported\n");
		else
			perror("VIDIOC_REQBUFS");

		exit(2);
	}
	
	/* We want at least five buffers. */
	mmap_buffer_count= reqbuf.count;
	if (mmap_buffer_count < 5) {
		/* You may need to free the buffers here. */
		printf("Not enough buffer memory\n");
		exit(2);
	}

	mmap_buffers= (struct mmap_buffer_item*) realloc(mmap_buffers, mmap_buffer_count * sizeof(*mmap_buffers));
	assert(mmap_buffers != NULL);
	memset(mmap_buffers, 0, mmap_buffer_count * sizeof(*mmap_buffers));

	for (i = 0; i < mmap_buffer_count; i++) {
		memset(&buffer, 0, sizeof(buffer));
		buffer.type= reqbuf.type;
		buffer.memory= V4L2_MEMORY_MMAP;
		buffer.index= i;

		if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buffer)) {
			perror("VIDIOC_QUERYBUF");
			exit(2);
		}
		
		mmap_buffers[i].length= buffer.length; /* remember for munmap() */

		mmap_buffers[i].start= mmap(NULL, buffer.length,
			PROT_READ | PROT_WRITE, /* recommended */
			MAP_SHARED,             /* recommended */
			fd, buffer.m.offset);

		if (MAP_FAILED == mmap_buffers[i].start) {
			/* If you do not exit here you should unmap() and free()
			   the buffers mapped so far. */
			perror("mmap");
			exit(2);
		}
		
		if (-1 == ioctl(fd, VIDIOC_QBUF, &buffer)) {
			perror("VIDIOC_QBUF");
			exit(2);
		}
	}
}

void* acquire_mmap_frame(int fd, struct v4l2_buffer *buffer) {
	int i;
	
	memset(buffer, 0, sizeof(*buffer));
	buffer->type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buffer->memory= V4L2_MEMORY_MMAP;
	buffer->index= i;
	if (-1 == ioctl(fd, VIDIOC_DQBUF, buffer)) {
		perror("VIDIOC_DQBUF");
		exit(2);
	}
	if (buffer->index < 0 || buffer->index >= mmap_buffer_count) {
		printf("Buffer index out of bounds! %d\n", buffer->index);
		exit(2);
	}
	return mmap_buffers[buffer->index].start;
}

void release_mmap_frame(int fd, struct v4l2_buffer *buffer) {
	if (-1 == ioctl(fd, VIDIOC_QBUF, buffer)) {
		perror("VIDIOC_QBUF");
		exit(2);
	}
}

void finalize_mmap(int fd) {
	int i;
	for (i = 0; i < mmap_buffer_count; i++)
		munmap(mmap_buffers[i].start, mmap_buffers[i].length);
	free(mmap_buffers);
	mmap_buffers= NULL;
}

Reply via email to