全部博文(668)
分类:
2009-08-01 16:04:56
int device_init(char *dev, int channel, int norm)我们又把 device_init() 写的更完整了。粗体字的地方是我们初始化 mmap 的程序码, 一开始的程序可能又让人觉得一脸茫然:
{
int i;
if (dev == NULL) {
dev = "/dev/video0"; //set to default device
}
if (v4l_open(dev, &vd)) {
return -1;
} else {
v4l_grab_init(&vd, screen_width, screen_height); //wake up drivers!
v4l_close(&vd);
}
if (v4l_open(dev, &vd)) return -1;
if (v4l_get_channels(&vd)) return -1;
if (v4l_set_norm(&vd, norm)) return -1;
if (v4l_mmap_init(&vd)) return -1;
if (v4l_switch_channel(&vd, channel)) return -1;
printf("%s: initialization OK... %s\n"
"%d channels\n"
"%d audios\n\n", dev, vd.capability.name, vd.capability.channels, vd.capability.audios);
for (i = 0; i < vd.capability.channels; i++) {
printf("Channel %d: %s (%s)\n", i, vd.channel.name,
v4l_norms[vd.channel.norm].name);
}
printf("v4l: mmap's address = %p\n", vd.map);
printf("v4l: mmap's buffer size = 0x%x\n", vd.mbuf.size);
printf("v4l: mmap's frames = %d (%d max)\n", vd.mbuf.frames, VIDEO_MAX_FRAME);
for (i = 0; i < vd.mbuf.frames; i++) {
printf("v4l: frames %d's offset = 0x%x\n", i, vd.mbuf.offsets);
}
printf("v4l: channel switch to %d (%s)\n", channel, vd.channel[channel].name);
// start initialize grab
if (v4l_get_picture(&vd)) return -1;
if (v4l_set_palette(&vd, DEFAULT_PALETTE)) return -1;
if (v4l_grab_init(&vd, screen_width, screen_height)) return -1;
if (v4l_grab_sync(&vd)) return -1;
return 0;
}
if (v4l_open(dev, &vd)) {将 device 开启成功后, 做了一次 v4l_grab_init后再把 device 关掉, 用意何在呢? 其实, 是因为bttv 的 driver 是以 module 的方式安装到 Linuxkernel, 所以 bttv driver 会因为没有被使用,而「睡觉了」。
return -1;
} else {
v4l_grab_init(&vd, screen_width, screen_height); //wake up drivers!
v4l_close(&vd);
}
int v4l_mmap_init(v4l_device *vd)PROT_READ 表示可读取该 memory page , PROT_WRITE 则是可写入, MAP_SHARED则是让这块mapping 的区域和其它 process 分享。第一个参数旦 0 是启始位置, vd->mbuf.size则是长度(length)。vd->fd 则是 device 的 file description, 最后一个参数是 offset。
{
if (v4l_get_mbuf(vd) < 0)
return -1;
if ((vd->map = mmap(0, vd->mbuf.size, PROT_READ|PROT_WRITE, MAP_SHARED, vd->fd, 0)) < 0) {
perror("v4l_mmap_init:mmap");
return -1;
}
return 0;
}
int v4l_switch_channel(v4l_device *vd, int c)传入的 c 是 channel, 而 channel number 我们已经在 device_init() 里打印出来:
{
if (ioctl(vd->fd, VIDIOCSCHAN, &(vd->channel[c])) < 0) {
perror("v4l_switch_channel:");
return -1;
}
return 0;
}
Channel 0: Television我们可以看到 Composite1 位于 Channel 1 (由 0 算起), 所以 v4l_switch_channel() 的参数 c 要传入 1。
Channel 1: Composite1
Channel 2: S-Video
VIDEO_MODE_PAL这些参数都定义于 videodev.h 档案里。v4l_set_norm() 是我们用来设定 norm 的函数, 程序码如下:
VIDEO_MODE_NTSC
VIDEO_MODE_SECAM
VIDEO_MODE_AUTO
int v4l_set_norm(v4l_device *vd, int norm)要仔细注意, 我们是对所有的 channel 设定 norm, 设定完成后, 底下又做了一次 v4l_get_capability(), 主要目的是确保每个 channel 的设定都有被设定成功。然后呼叫 v4l_get_picture。
{
int i;
for (i = 0; i < vd->capability.channels; i++) {
vd->channel.norm = norm;
}
if (v4l_get_capability(vd)) {
perror("v4l_set_norm");
return -1;
}
if (v4l_get_picture(vd)) {
perror("v4l_set_norm");
}
return 0;
}
struct video_picture picture;初始化 picture 的意思就是要取得输入到影像捕捉卡的影像信息,我们设计 v4l_get_ picture() 函数来完成这件工作。
int v4l_get_picture(v4l_device *vd)传递VIDIOCGPICT 给 ioctl() 则会传回影像的属性 (image properties),这里则是将影像属性存放于 vd-> picture。这部份我们也曾经介绍过, 在这里要再捕充一点。如果是以 GREY 方式撷取影像, 那么我们可以利用 VIDIOCSPIC 来设定像素的亮度与灰阶度, 请参考 API.html 里的 struct video_picture 说明。
{
if (ioctl(vd->fd, VIDIOCGPICT, &(vd->picture)) < 0) {
perror("v4l_get_picture:");
return -1;
}
return 0;
}
if (v4l_get_picture(&vd)) return -1;v4l_get_picture() 与之前介绍的一样, 而 v4l_set_palette() 则是用来设定调色盘, 由于我们希望得到的是 RGB32, 所以 DEFAULT_PALETTE 定义成:
if (v4l_set_palette(&vd, DEFAULT_PALETTE)) return -1;
if (v4l_grab_init(&vd, screen_width, screen_height)) return -1;
if (v4l_grab_sync(&vd)) return -1;
#define DEFAULT_PALETTE VIDEO_PALETTE_RGB32如果没有硬件转换, 前一篇文章 (4) 我们也提到将 YUV (PAL) 转成 RGB 的方法了。再来将就是对 grab 做初始化, v4l_grab_init()
int v4l_grab_init(v4l_device *vd, int width, int height)初始化的目的是将 mmap 结构填入适当的值。针对 RGB32、NTSC 的 CCD 影像撷取, mmap 的大小不妨设定成 640*480 或 320*240 都可以, 给定 mmap 的大小后, 再来还要将 format 填入调色盘类型。
{
vd->mmap.width = width;
vd->mmap.height = height;
vd->mmap.format = vd->picture.palette;
vd->frame_current = 0;
vd->frame_using[0] = FALSE;
vd->frame_using[1] = FALSE;
return v4l_grab_frame(vd, 0);
}
/dev/video0: initialization OK... BT878(Chronos Video Shuttle I)v4l_grab_frame() 的用处
3 channels
3 audios
Channel 0: Television (NTSC)
Channel 1: Composite1 (NTSC)
Channel 2: S-Video (NTSC)
v4l: mmap's address = 0x40173000
v4l: mmap's buffer size = 0x410000
v4l: mmap's frames = 2 (32 max)
v4l: frames 0's offset = 0x0
v4l: frames 1's offset = 0x208000
v4l: channel switch to 1 (Composite1)
Image pointer: 0x4037b000
int v4l_grab_frame(v4l_device *vd, int frame)因为我们用 frame_using[] 数组来纪录那个 frame 已经被使用, 所以一开始当然要先判断目前的 frame 是否已经被使用:
{
if (vd->frame_using[frame]) {
fprintf(stderr, "v4l_grab_frame: frame %d is already used.\n", frame);
return -1;
}
vd->mmap.frame = frame;
if (ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) < 0) {
perror("v4l_grab_frame");
return -1;
}
vd->frame_using[frame] = TRUE;
vd->frame_current = frame;
return 0;
}
if (vd->frame_using[frame]) {如果没有被使用, 就把 mmap 的 frame 填入 frame 编号, 然后利用 VIDIOCMCAPTURE撷取出影像。结束前要把目前frame 的状态标示成使用中 (frame_using[]), 然后把 frame_current 指定成现在的frame, 完成工作后离开。
fprintf(stderr, "v4l_grab_frame: frame %d is already used.\n", frame);
return -1;
}
int device_grab_frame()device_next_frame() 是主要核心所在, 因为我们只有二个 frame, 所以 frame_current 不是 0 就是 1。
{
vd.frame_current = 0;
if (v4l_grab_frame(&vd, 0) < 0)
return -1;
return 0;
}
int device_next_frame()
{
vd.frame_current ^= 1;
if (v4l_grab_frame(&vd, vd.frame_current) < 0)
return -1;
return 0;
}
device_next_frame(); //Ok, grab a frame.这段程序就是我们的重点好戏, 当我们呼叫 device_next_frame() 撷取 frame 之后, 必须做一个等待的动作, 让 frame 撷取完成再取出影像。
device_grab_sync(); //Wait until captured.
img = device_get_address(); //Get image pointer.
printf("\nImage pointer: %p\n", img);
v4l_grab_sync() 程序码如下:利用 VIDIOCSSYNC 等待完成后, 别忘了将目前 frame 的状态改回未被使用。接下来我们要问, 撷出出来的 frame 到底放到那里去了呢? 答案就是之们利用 mmap() 将 device 所 map 的内存里, 因为我们是利用 mmap (flip-flop) 方式, 所以会有 2 个 (或以上) 的 frame, 这时就要计算一下 offset, 才知道到底目前的影像资料被放到那里了。算式如下:
int v4l_grab_sync(v4l_device *vd)
{
if (ioctl(vd->fd, VIDIOCSYNC, &(vd->frame_current)) < 0) {
perror("v4l_grab_sync");
}
vd->frame_using[vd->frame_current] = FALSE;
return 0;
}
vd.map + vd.mbuf.offsets[vd.frame_current]device_get_address() 函数就是这么回事。
FILE *fp;先利用 fprintf() 写入 PPM 档案的档头信息, 然后以 fwrite() 将传回的影像资料写到档案里。img 指向内存里的 frame 影像资料, 写入时, 请特别注意粗体字的地方, 因为我们是用 RGB32 的调色盘, 而 RGB 是以 3 个 sample 来表示一个 pixel, 所以要乘上 3。如果是 GREY 调色盘, 就不用再乘 3 了。最后将输出的 PPM 档案转换格式成 TIFF 就可以用一盘的绘图软件打开了:
fp = fopen("test.ppm", "w");
fprintf(fp, "P6\n%d %d\n255\n", NTSC_WIDTH, NTSC_HEIGHT);
fwrite(img, NTSC_WIDTH, 3*NTSC_HEIGHT, fp);
fclose(fp);
linux$ ppm2tiff test.ppm test.tiff将影像存成 JPEG 的方法
int write_jpeg(char *filename, unsigned char * img, int width, int height, int quality, int gray)利用 mpeglib 写入 JPEG 影像资料时, 必须分别对每行 scanline 写入。呼叫范例:
{
struct jpeg_compress_struct jcfg;
struct jpeg_error_mgr jerr;
FILE *fp;
unsigned char *line;
int line_length;
int i;
if ((fp = fopen(filename,"w")) == NULL) {
fprintf(stderr,"write_jpeg: can't open %s: %s\n", filename, strerror(errno));
return -1;
}
jcfg.image_width = width;
jcfg.image_height = height;
jcfg.input_components = gray ? 1: 3; // 3 sample per pixel (RGB)
jcfg.in_color_space = gray ? JCS_GRAYSCALE: JCS_RGB;
jcfg.err = jpeg_std_error(&jerr);
jpeg_create_compress(&jcfg);
jpeg_stdio_dest(&jcfg, fp);
jpeg_set_defaults(&jcfg);
jpeg_set_quality(&jcfg, quality, TRUE);
jpeg_start_compress(&jcfg, TRUE);
line_length = gray ? width : width * 3;
for (i = 0, line = img; i < height; i++, line += line_length)
jpeg_write_scanlines(&jcfg, &line, 1);
jpeg_finish_compress(&jcfg);
jpeg_destroy_compress(&jcfg);
fclose(fp);
return 0;
}
write_jpeg("test01.jpg", img, NTSC_WIDTH, NTSC_HEIGHT, 50, FALSE );第一个参数是图档名称, 第二个参数是影像资料, 然后第三、第四个参数接着影像的大小, 第五个参数 50 表示 JPEG 图档的压缩品质 (quality), 最后一个参数 FALSE 表示影像资料不是 grey (灰阶) 影像。灰阶影像与彩色影像的差别在于 input_components、in_color_space 与 scanline 的长度。