2009年8月3日星期一

opera for linux 版本的区别

opera-10.00-b1.gcc4-qt4.i386.rpm
opera-10.00-b1.gcc4-static-qt3.i386.rpm
一种是用Qt4开发,一种是用Qt3开发。如果你系统有Qt4/Qt3的话,安装第一种或者shared目录里的; 否则就安装static版的。

opera-10.00-b2.gcc4-bundled-qt4.i386. tar.gz
opera-10.00-b2.gcc4-qt4.i386.tar.gz
bundbled 里面有 usr/lib/opera/libQtGui.so.4 和 usr/lib/opera/libQtCore.so.4 这两个文件,并且 opera 里多了这么几行:
代码:
~% diff -u ./opera-10.00-4502.gcc4-qt4.x86_64/opera ./opera-10.00-4502.gcc4-bundled-qt4.x86_64/opera
--- ./opera-10.00-4502.gcc4-qt4.x86_64/opera 2009-07-21 18:50:22.000000000 +0800
+++ ./opera-10.00-4502.gcc4-bundled-qt4.x86_64/opera 2009-07-21 18:53:27.000000000 +0800
@@ -352,6 +352,19 @@
fi
done

+# Use Qt provided by the host, if available. Set OPERA_QT_LIBPATH to override the path to Qt shared libraries.
+if [ -z "$OPERA_QT_LIBPATH" ]; then
+ OPERA_QT_LIBPATH=`LD_PRELOAD=libQtGui.so.4 LD_TRACE_LOADED_OBJECTS=1 usr/lib/opera/works 2>/dev/null | sed -ne '/^.*libQtCore\.so\.4 => \(.*\)\/libQtCore\.so.*$/s//\1/p'`
+fi
+if [ -n "$OPERA_QT_LIBPATH" ]; then
+ if [ -r "$OPERA_QT_LIBPATH/libQtCore.so.4" -a -r "$OPERA_QT_LIBPATH/libQtCore.so.4" ]; then
+ LD_PRELOAD="$OPERA_QT_LIBPATH/libQtCore.so.4:$OPERA_QT_LIBPATH/libQtGui.so.4${LD_PRELOAD:+:}$LD_PRELOAD"
+ export LD_PRELOAD
+ else
+ echo "Cannot find Qt shared libraries in $OPERA_QT_LIBPATH" >&2
+ fi
+fi
+
# Setting environment relative to current working directory
# Bundled Qt will be found here unless preloaded by the stance above
LD_LIBRARY_PATH="$PWD/usr/lib/opera${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH"

看样子貌似是在系统里没有 Qt4 的时候用自带的,有的话还是用系统的~ 看来 qt4 版本的都不是静态链接的了~

投资基金当注重复利效应淡待大盘点位

  投资基金特别是以投资股票为对象的股票型和偏股型基金,一般而言,收益在一定时期内与大盘点位的涨跌升降有着同方向、紧密性的变化的;但从基金投资的长期复利效应观察看,则与大盘点位的关系就不哪么十分密切了,动辄有时还会出现与大盘方向相反的情况,也即大盘跌时它反涨。所以,对长期投资并欲深谙基金赚钱机理者,"注重复利效应而淡待大盘点位"才是理性明智的可取之态。这缘自于:

  一、投资基金的复利效应乃基金的本身内在特性所决定
  以投资股票为对象的股票型和偏股型基金,因内在配置资产的类型不同、股票资产的占比不同和个股的构成不同,便有了各自不同特性的蕴涵:诸如成长股票型的、价值股票型的、均衡股票型的、收益偏股型的、混合偏股型的、平衡偏股型的等不同品种的基金。在不同收益与风险的对称性中,虽因有几十到逾百只的不同数量、不同行业、不同盘别、不同板块、不同题材的不同个股构成及其仓位高低的不同,都在市场中呈相同、相近或不同的净值增长变化表现,但寻根究源的是:在其基金制度设计上的股票投资最高达95%、最低只有20%的可调控变换的十分宽泛性,既利于紧跟或应对市场的涨跌调整变化,又便于周而复始地把行情中赚得的前一波又一波的红利投入到后一波又一波的行情中去博弈,用赚得的钱再去赚钱,以此不断"滚动"的"利滚利",把基金投资的"复利效应"奥秘显示出来;进而随着时间上的日积月累,其"复利效应"的叠加递进,便又顺理成章地"升级"成了人们常说的"时间红利"了。这当然全有如此内在特性的基金,主动结合了市场的"涌动",才有的结果。
  故而,正是有了基金这种独有的自身特性,一方面由于其所有资产最高只有95%参与市场的运作和所投资单只个股的最高比例限制与精选全部个股有机组合的风险分散,常常是大盘涨时基金的净值涨,大盘跌时基金的净值跌得少;另一方面由于基金红利所产生的复利效果,既能抵消或摊薄大盘下跌时的基金市值的下跌,又能加速超赶大盘回调时的基金市值上升速度。所以,投资基金当注重其复利效应而淡待大盘的点位高低,并要与市场长厢斯守,才能尽享其复利效应带来的"超强"之回报。

  二、投资基金的复利效应乃基金管理人的主动运作所铸就
  股票型和偏股型基金在制度设计上,从资产的构成、股票的占比和个股的组合及只数的多少等的全方位的三个不同层次上,都赋予了基金管理人的"进可攻、退可守"的主动运作空间。基金管理人依基金设立时或更新后的投资理念、目标、策略、风格和业绩比较基准等要求,经对上市流通的股票及其上市公司的筛选和深入地调查研究,用不断更新形成的股票池,再结合适时地紧跟对市场地预测和把握:在市场正常运行和热点板块轮动的市场境况下,对于已投资的并赚到了"资本利得"的价格相对已经高估的股票卖掉,而为投资人把赚到的钱实实在在地拿到手,同时再将包括本利在内的钱买进被市场相对低估的或者增发与新上市的"股票池"中的股票;在市场的调整波动变化来临前,主动通过调整降低持股仓位,同样把投资股票赚到的钱巧妙地留下,再将本利投资于能暂时规避市场调整波动风险的工具与品种,一样能让红利的复利效应得到发挥,待市场调整波动结束前再一次地做基金全部资产的调动布局,以赢得更加可观的复利收获。对投资者而言,要想使投资基金的复利效应发挥的更充分,必须要经历基金管理人有利于既能保住红利、又能充分发挥红利的复利效应的多个主动运作的轮回,才能实现;倘若是时间短了,则是不行的;再倘若是逢市场已步入调整期,基金投资人若再去势单力薄"单挑独斗"地做赎回避险,显然是在"步"基金管理人的"后尘",而做有损红利、复利的无谓"自我牺牲"。
  因此由上述可知,有基金管理人用极专业的专家团队理财智慧,在前方一线"知己知彼"的与市场在博弈,一般情况下,早就为投资人做了应对与打理,什么大盘"点高点低"的也就不在话下了。

  三、投资基金的复利效应乃股票市场波浪式螺旋上升中所孕育
  股市的运行总是在涨涨跌跌、跌跌涨涨中进行,无论"熊市"还是"牛市",均概莫能外;因人气和资金的进退,便助推了一波又一波的市场涨跌行情,而呈波浪式螺旋上升的一种总态势。正是这波浪式螺旋上升之态势,融一波又一波的市场行情中,在持续不断地把基金投资的复利效应来孕育:热点板块的轮动而致市场中股票投资价值的高估或低估、老股增发而扩大了市场容量增加了供应、新股上市而在稀释市场风险中凭添了新生力量,获利盘的回吐在释放市场风险中酝酿健康、永无休止的技术修复在调整与掌控着市场的步骤、探底回调演义着多空双方"势不罢休"的能量与智慧的博弈……这"摸"不到但却能看到和感受得到股市中的一幕幕"波澜壮阔"的"场景",对市场研究透彻的基金管理人,在积极应对中,或"该出手时就出手"增加仓位,或保持仓位"按兵不动"蓄势待发,或"收兵回营"降低仓位寓"小憩"中"司机返扑",或"修筑工事"调仓换仓等,借以把未分红的红利与投资人的分红再投,融入前浪推后浪的波浪式的螺旋上升的市场大潮中,获得一波又一波的复利之复利。
  这同样不难看出,投资基金的复利获得,其"每一波"自然得多少借助市场的上行来实现,但就在整个持续不停的市场博弈中,则少不了基金管理人及基金经理们,能恰到好处地做到了将基金资产与市场行情的最佳结合,致此相比之下,大盘点位的是高是低就显得不那么重要了。
  以上的"基金、基金管理人、股市"的"三位一体"的有机融合,只是能从资本市场的不断运行变化中获得复利的一种机制,但欲求能从"这种机制"中真正得到回报,有两门"功课"的"作业",是投资者必须要做要交的:一是要投资于选中的基金特别是配置股票资产的基金;二是要长期坚持,否则就"前功后弃"得不偿失了。

这个时代的个十百千万

坐拥一百多万房产。

身背几十万元房贷。

手握几万元钱基金。

每月几千块钱工资。

身穿几百块钱名牌。

上班十几块的车钱。

天天几块钱的早饭。

Linux摄像头编程

这次是在linux下开发摄像头的程序,主要用的是video4linux来做的,界面用qt来实现,开始准备用framebuffer来直接写屏但是效果不怎么好,后来就用qt来做了,这样用起来效果还蛮好的,帧率也可以,可以上到30fps;运用v4l来编程主要掌握其api,要提高帧率最重要的是用到内存映射,其实用qt和framebuffer的时候都要用到内存映射来做,只有这样才可以达到较高的帧率,不过要注意资源的利用问题.mmap后一定要munmap.对于framebuffer是很有意思的一个东西,特别是驱动的设计.
代码如下:
1.open device:
video_dev = open("\dev\video0",O_RDWR));
2.get the information of the video device
struct video_capability video_cap;
memset(&video_cap,0,sizeof(video_cap));
if(ioctl(video_dev,VIDIOCGCAP,&video_cap) == -1)
{
perror("Cann't get the information of the video device");
close(video_dev);
exit(1);
}
3.get the information of the channels
struct video_channel video_chan;
memset(&video_chan,0,sizeof(video_chan));
for(channel = 0;channel < video_cap.channels;channel++)
{
video_chan.channel = channel;
if(ioctl(video_dev,VIDIOCGCHAN,&video_chan) == -1)
{
perror("Cann't get the information of the channels");
close(video_dev);
exit(3);
}
if(video_chan.type == VIDEO_TYPE_TV)
{
#ifdef DEBUG
printf("NO.%d channel is %s,type is
tv!\n",channel,video_chan.name);

在linux下使用视频采集卡

这一部分将会介绍如何在linux中对电视卡编程。
开始已经提到过,电视卡使用的是video for
linux驱动,简称v4l,实际上,现在已经有了video for linux two驱动
,即v4l2.它解决了v4l中存在的一些问题,并且提高了硬件性能。但是,目前来说,v4l2仍然没有集成到linux的内核中,要使用v4l2的话,只有去下载v4l2补丁了,以下如无特别说明,所涉及的内容只针对v4l设备而言。
我们都知道,在linux中,为了屏蔽用户对设备访问的复杂性,采用了设备文件,即可以通过像访问普通文件一样的方式来对设备进行访问读写。电视卡在linux中和打印机,鼠标一样,属于字符设备。其主设备号是81,在实际操作上,访问控制电视卡也和一般的设备文件没有什么不同。用open打开设备,
int fd;
fd = open("/dev/video0",O_RDWR);
用一系列的ioctl发命令控制设备。v4l支持的ioctl命令大概有二十几个,为了尽快的编出一个
简单的图象捕捉程序,让我们先来看看几个主要的命令:
1. ioctl(fd,VIDIOCGCAP,&cap);
该命令主要是为了获取电视卡的功能信息。例如电视卡的名称,类型,channel等。参数cap是一个结构,当ioctl命令返回时,结构的各成员就被赋值了,结构体的定义为:
struct video_capability
{
char name[32];
int type;
int channels; /* Num channels */
int audios; /* Num audio devices */
int maxwidth; /* Supported width */
int maxheight; /* And height */
int minwidth; /* Supported width */
int minheight; /* And height */
};
channel 指的是有几个信号输入源,例如television,composite,s-video等。
2.ioctl(fd,VIDIOCGCHAN,&vc)
3.ioctl(fd,VIDIOCSCHAN.&vc)
这两个命令用来取得和设置电视卡的channel信息,例如使用那个输入源,制式等。
vc 是一个video_channel结构,其定义为:
struct video_capability
{
char name[32];
int type;
int channels; /* Num channels */
int audios; /* Num audio devices */
int maxwidth; /* Supported width */
int maxheight; /* And height */
int minwidth; /* Supported width */
int minheight; /* And height */
};
struct video_channel
{
int channel;
char name[32];
int tuners;//number of tuners for this input
__u32 flags;
__u16 type;
__u16 norm;
};
成员channel代表输入源,通常,0: television 1:composite1 2:s-video
name 表示该输入源的名称。
norm 表示制式,通常,0:pal 1:ntsc 2:secam 3:auto
4. ioctl(fd,VIDIOCGMBUF,*mbuf)
获得电视卡缓存的信息,参数mbuf是video_mbuf结构。其定义如下:
struct video_mbuf
{
int size; /* Total memory to map */
int frames; /* Frames */
int offsets[VIDEO_MAX_FRAME];
};
size是缓存的大小,frames表明该电视卡的缓存可以容纳的帧数,数组offsets则表明
对应一帧的起始位置,0帧对应offsets[0],1帧对应offsets[1]....
执行完该命令后,就可以用mmap函数将缓存映射到内存中了。大致用法可以参考以下的代

struct video_mbuf mbuf;
unsigned char *buf1,*buf2;
if(ioctl(fd,VIDIOCGMBUF,&mbuf)<0)
{
perror("VIDIOCGMBUF");
return -1;
}
printf("the frame number is %d\n",mbuf.frames);
buf1 = (unsigned
char*)mmap(0,mbuf.size,PROT_READ|PROT_WRITE,MAP_SHARED,fd.0);
buf1 = buf1 + mbuf.offset[0];
buf2 = buf1 + mbuf.offset[1];//当然,如果mbuf.frames=1,就不需要下面的了。
......
5. ioctl(fd.VIDIOCMCAPTURE,&mm)
启动硬件去捕捉图象,mm 是video_mmap
结构,设置捕捉图象需要设置的信息。结构体
如下定义:
struct video_mmap
{
unsigned int frame; /* Frame (0 - n) for
double buffer */
int height,width;
unsigned int format; /* should be
VIDEO_PALETTE_* */
};
frame :设置当前是第几帧
height,width:设置图象的高和宽。
format :颜色模式
要注意的是,该命令是非阻塞的,也就是说,它仅仅设置了硬件,而不负责是否捕捉到图象。
要确定是否捕捉到图象,要用到下一个命令。
6. ioctl(fd,VIDIOCSYNC,&frame)
等待捕捉到这一帧图象。frame
是要等待的图象,它的值应和上一个命令中设置的frame相对应。
好了,说了这么多,读者大概也对视频捕捉有了一个了解,是不是想亲自动手试一下,那就让我们
开始实际程序的编写吧。
下面我们会编一个程序,将捕捉到的图象存为jpeg文件。为此,还要向大家介绍一个函数,
int write_jpeg(char *filename,unsigned char *buf,int quality,int width,
int height, int gray)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE *fp;
int i;
unsigned char *line;
int line_length;
if (NULL == (fp = fopen(filename,"w")))
{
fprintf(stderr,"grab: can't open %s:
%s\n",filename,strerror(errno));
return -1;
}
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_stdio_dest(&cinfo, fp);
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = gray ? 1: 3;
cinfo.in_color_space = gray ? JCS_GRAYSCALE: JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, TRUE);
jpeg_start_compress(&cinfo, TRUE);
line_length = gray ? width : width * 3;
for (i = 0, line = buf; i < height; i++, line += line_length)
jpeg_write_scanlines(&cinfo, &line, 1);
jpeg_finish_compress(&(cinfo));
jpeg_destroy_compress(&(cinfo));
fclose(fp);
return 0;
}
这个函数很通用,它的作用是把buf中的数据压缩成jpeg格式。
/* 下面是一个完整的程序 test.c
* gcc test.c -o test -ljpeg
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <errno.h>
#include <linux/videodev.h>
#include <jpeglib.h>
#define WIDTH 320
#define HEIGHT 240
#define V4L_DEVICE "/dev/video0"
main()
{
unsigned char* buf;
int i,j;
int fd;
int re;
struct video_capability vcap;
struct video_channel vc;
struct video_mbuf mbuf;
struct video_mmap mm;
fd = open(V4L_DEVICE, O_RDWR);
if(fd<=0)
{
perror("open");
exit(1);
}
if(ioctl(fd, VIDIOCGCAP, &vcap)<0)
{
perror("VIDIOCGCAP");
exit(1);
}
fprintf(stderr,"Video Capture Device Name : %s\n",vcap.name);
for(i=0;i<vcap.channels;i++)
{
vc.channel = i;
if(ioctl(fd, VIDIOCGCHAN, &vc)<0)
{
perror("VIDIOCGCHAN");
exit(1);
}
fprintf(stderr,"Video Source (%d) Name : %s\n",i, vc.name);
}
vc.channel =1;
vc.norm=1;
if(ioctl(fd, VIDIOCSCHAN, &vc) < 0)
{
perror("VIDIOCSCHAN");
exit(1);
}
if(ioctl(fd, VIDIOCGMBUF, &mbuf) < 0)
{
perror("VIDIOCGMBUF");
exit(1);
}
fprintf(stderr,"the frames number is %d\n",mbuf.frames);
buf = (unsigned char*)mmap(0, mbuf.size, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
if((int)buf < 0)
{
perror("mmap");
exit(1);
}
mm.frame = 0;
mm.height = HEIGHT;
mm.width = WIDTH;
mm.format = VIDEO_PALETTE_RGB24;
if(ioctl(fd, VIDIOCMCAPTURE, &mm)<0)
{
perror("VIDIOCMCAPTURE");
exit(1);
}
if(ioctl(fd, VIDIOCSYNC, &mm.frame)<0)
{
perror("VIDIOCSYNC");
exit(1);
}
if(-1 == (write_jpeg("./pic001.jpeg",buf,75,WIDTH,HEIGHT,0)))
{
printf("write_jpeg error\n");
exit(1);
}
munmap(buf,mbuf.size);
close(fd);
}

Linux的应用--Video Streaming探讨六

本期将是我们一系列 Video Streaming
专栏的结束,本期最重要的工作当然免不了是对之前的内容做系统性的整理,然后再对
Video Streaming 的应用就目前最被讨论的理论做简单介绍。
Video Streaming 系统概观
一个完整的 Video Streaming 系统应该包含四个部份:
Content (例如影像、声音、coded 资料)
Server
Client
Data network (例如 Internet,或任何链接 server-client 的媒介)
那么我们在这一系列 Video Streaming
主题介绍的有那些呢?从一开始的名词介绍、影像撷取、RTP通讯协定,我们将重心放在contente
与 data network
上。当然我们系列主题名为「Linux的应用」,最关键的地方则是在如何在
Linux环境下设计影像撷取程序。
对于 data network 的部份因为与 Linux 较无关,但也是 Video Streaming
的重点主题之一,所以也介绍了 jrtplib
这套程序库给大家,在底下的部份也会补充 Nsync 这套 toolkit。
而 server
端似乎是我们着墨不深的地方,但也没关系,在这最后一篇的文章里,我们针对理论方面做补充的介绍,我们将以VideoConferencing
实作时,实务上会面临的主题来做解,包括:多媒体资料的储存、OS的磁盘排程(SCAN-EDF),以做为您搜寻参考资料的起头。
在 client 方面,我们在最后的完整版范例程序时,示范了如何利用 SDL
将撷取下来的影像资料秀在屏幕上。这里也会配合了上一篇所介绍的 mmap
撷取方式,以将影像资料填入 SDL 的 display 结构方式秀图。
Video Streaming 的开始
我们一开始就介绍了 Video Streaming
是一种经由网络来拨放影音档案的技术,Video "Streaming"
的基本概念?「一边下载一边拨放」,我们称之?「Play as received」。
经由 Internet
如果要收看远端服务器的电影档案,最原始的做?是「下载后再拨放」,也就是经由
FTP 或 HTTP 将整个档案下载至本地端后再利用拨放程序来拨放,我们称之?「Play
after download」。
举个最简单的情况,你可能在下载 MP3
之前想要试听一下音乐的内容,而下载音乐片段的方式又显得不够友善,这时如果利用
Video Streaming
的技术来让网友视听,不但方便,而且不必浪费时间来下载不喜欢的音乐的。除此之外,大家最熟悉的莫过于在线上即时播放影片的
real player了。
Real Video 产品介绍
Real Video 是 Real Networks 公司的产品,Real Video 主要支援了
video-on-demand*1 的功能。Real Video 可以让我们经由网站来播放串流影像
(streaming video)。
由于我们的最终目的是实作出一个可以做 video streaming
的软件,所以在这里我们将以 Real Video 做为标竿,并以 Linux 为基础来设计
video streaming 的软件。
其它经典的 streaming 范例程序
网络上有几套很值得玩味研究的相关开放原始码软件,我们也曾经提过。VIC 和
VideoLAN 则是其中绝对优秀的教学范例。
VIC
VIC 也是属于 Open Source 的软件。VIC 全名为video
conferencing,故名其义,VIC 是一种视讯会议的软件。VIC 是由加州柏克来大学的
Network Research Group 所发展。
VIC 是相当棒非常适合用来研究 Video Streaming 的 Open Source
软件,主要是因为 VIC 几乎包含了 Video Streaming 相关的技术。
VIC 值得我们研究的原因是因为 VIC 支援了底下所列的功能:
IPv6
使用 video4linux 的影像撷取功能
H261、H263 与 H263+ codec
Software JPEG 与 BVC 编码
Raw YUV packetiser/codec
RTIP/RTP 通讯协定
the IP Multicast Backbone (MBone)
支援 video4linux 的 mmap
这些特色几乎已经包括 Video Streaming 所应具备的技术了,基于这些特点,VIC
的原始程序码相当吸引人,因此有意研究 Video Streaming 的 programmer
应该好好阅读一下 VIC 的原始程序码。
VideoLAN
VideoLAN 是一个可以做 MPEG 与 DVD 扩播 (broadcast) 播放的软件,VideoLAN
分成二个部份,一个是 VLAN server,另一个则是 vlc 用户端播放程序。
VLAN server 将 DVD 与 MPEG 影像利用 broadcast
方式扩播到区域网络上,使用者端再利用vlc接收封包并播放。这样做的好处是可以减少重覆的
I/O 动作,VLAN server
将影像扩播出去后,区域网络上的用户端再利用vlc接收封包并播放。
VideoLAN 支援 X11、SDL、Linux framebuffer、GGI、BeOS API、MacOS X API
播放方式,并且支援 DVD 与 AC3 (杜比音效)。
影像编码技术介绍 (coded)
目前学术界已经发展出许多处理影像讯号压缩及编码的技术
(codecs),谈到这些技术,应用最广泛的编码标准底下四种:
H.261
H.263
JPEG, MJPEG
MPEG
本文第一篇文章就是在对这四种编码技术做简单的介绍。
data network 的技术主题
Video 在做 Streaming
时,有三种方式可以应用:broadcasting、unicasting、multicasting。broadcasting
的方式比较单纯,他是在 LAN 上直接将一个个的影像封包丢到网络上 (server
端),再由client的应用程式自网络上取回封包播放。但网络硬件层上,仍有许多需要考虑的问题,例如在
Shared Non-SwitchedEnthernet上时,就会发生一些小问题。
unicasting 与 multicasting 都是属于 IP 的传输方式。unicasting 采取 1
对1的方向传影像给远端,称为 Video-on-Demand (VoD),multicasting 则是
1对多的传输方式,称为Near-Video-on-Demand (NVoD)。未来 IPv6 将支援 IP
Multicasting,因此VideoStreaming
的应用将更为广泛。在通讯协定方面,我们也介绍了 RTP。
RTP 全名为 Real-Time Protocol,RTP 是在 UDP 封包之前多加 10 bytes
的档头,里面记载有时间、序号、压缩型态等信息。RTP 是目前大多数 Video
Streaming 软件所使用的通讯协定。
RTP 可用来针对各种不同的多媒体格式做 Streaming
的工作,因为我们将影像分解成数个RTP封包再传送出去,因此会遇到许多网络技术常会遇到的问题。例如,因为封包送达的时间不一,造成播放时会画面不流畅的现像,因此,在播发时就必须使用一个缓冲区
(playout buffer) 来暂时存放并处理网络上接受到的封包。
由网络上接收的影像封包因为彼此之间到达的时间间隔不同 (Synchronous Data
Packets),所以必须利用缓冲区将这些封包做缓冲,让彼此之间的时间间隔一样
(Isochronous Data Packets)。
其它重要的通讯协定像是 SIP、或是 FEC (forward error correction)
除错技术,都是一定要去研究的主题。
影像撷取卡
在 Linux
下设计影像撷取程序,当然一定要配备有适合的影像撷取卡。我们曾经介绍给大家的是
Osprey 100 这张影像撷取卡。Osprey 100 是
RealNetworks公司所推荐配合他们产品的一张影像撷取卡,配合 Osprey 100 与
RealNetworks 的产品我们可以利用broadcast 或on-demand 做到实况转播 (live)
的功能。
Osprey 100 在硬件功能上可以支援到每秒 30 个画面 (fps -- frame per
second),并且支援 NTSC 与 PAL 输入。不过在实作上,笔者并不使用 Osprey
100。笔者使用的影像撷取卡是
,这张卡算是比较「俗」一点的卡,但是也有好处,因为在 Linux 上很容易安装。
以笔者这张卡为例,使用的是 Brooktree Corporation 的卡,所以只要安装 bttv
模组即可,同时,bttv模组在Linux kernel 2.2.17 下也会用到 i2c-old
与videodev两个模组,所以也要一并安装。在命令列下,安装这三个模组的命令为:
linux# insmod i2c-old
linux# insmod videodev
linux# insmod bttv
当然要确定 Linux kernel 有编译这三个模组的支援,然后再把这三个模组加到
/etc/modules.conf (Red Hat 7.0) 里。不同版本的 kernel
所要安装的模组不一定相同!还请注意,例如 i2c 相关模组就是如此。
Linux 上可用的影像撷取卡
http://www.linhardware.com/db/searchproduct.cgi?_catid=17
网页上可以找到在Linux 上支援程度比较好的几张影像撷取卡。而一般 Linux
上较受欢迎的影像撷取卡则是 Hauppauge 的几张卡, 笔者使用的也是Hauppauge
的卡。在 linhardware 网站上可以找到底下六张卡:
Hauppauge 401 WinTV-radio dbx-TV stereo
Hauppauge WinTV PCI TV Card
Hauppauge WinTV-GO PCI TV Card
Hauppauge WinTV-PCI Hauppauge
Hauppauge WinTV-Radio+NICAM
Hauppauge WinTV/PCI TV Card
关于 Linux 对于影像撷取卡支援的中文文件 (HOWTO) 可以在 CLDP 网站上取得:
http://www.linux.org.tw/CLDP/Hardware-HOWTO-22.html
影像撷取卡支援的视讯系统
大部份影像撷取卡都会具备一组视讯输入端子, 即 S-Video (Y/C) 端子或
Composite 端子。在台湾的标准当然是 NTSC 系统, 一般而言,
我们是希望一张影像撷取卡可以支援越多视讯系统越好, 包括:
NTSC/PAL/PALN/PLAM/SECAM。可使用的视讯装置有较常见的 CCD, 或是家用 V8、Hi8
皆可, 一般而言我们也是希望一张影像撷取卡可以接越多视讯装置越好。
BT 878 芯片
跟随在影像撷取卡之后的主题当然就是 BT878 芯片的介绍,因为支援 BT 8x8
芯片的 BTTV 躯动程序是我们设计影像撷取软件的核力主力!
目前大部份的数位影像撷取卡大部份都是以 BT878
单颗芯片为影像撷取卡之中心。BT878运作方式是以软件来进行影像解压缩工作,BT878
芯片负责将撷取之影像丢给 Linux 做影像处理, 而 BTTV 则是 Linuxkernel 的
BT878 芯片躯动程序。
由于影像是利用 BT878 撷取后交由软件来做影像处理,
因此在处理效能上自然就会比较差。如果是经由网络来传送影像的话,
我们就会再利用影像压缩技术 (H.261/H.263...等等) 来做影像处理。
什么是 BTTV
BTTV 是 Linux 上的 Bt848/849/878/879 芯片的躯动程序,
主要功能是做页框的截取 (frame grabber)。BTTV 是 video4linux
里重要的躯动程序, 目前分为二个版本:
0.8.x 的发展中版本
0.7.x 的稳定版本
BTTV 相关应用软体 - xawtv
官方网站: http://bytesex.org/xawtv/index.html
安装方式:
linux# ./configure
linux# make depend
linux# make
linux# make install
如果您有 Red Hat Linux 7.1 PowerTools 光盘片的话, 也可以直接由 PowerTools
光盘片安装 xawtv 套件:
linux# rpm -ivh xawtv-3.34-1.i386.rpm
安装 xawtv 需要 libjpeg 与 libjpeg-devel 套件, 如果您是使用 Red Hat Linux
7.1 的话, 应该安装底下二个套件:
libjpeg-6b-15.i386.rpm (Disc 1)
libjpeg-devel-6b-15.i386.rpm (Disc 2)
xawtv 整个架构可以分成 7 个部份如下:
xawtv: 主程序部份。
fbtv: linux console 模式的 TV 应用程式, 使用 linux kernel 2.2.x 的
framebuffer。
set-tv: 命令列模式的工具, 用来设定 video4linux 的参数。
streamer: 命令列模式的工具, 用来捉取动态影像与 avi 影像。
radio: radio 应用程式。
webcam: 将捉取的影像以 FTP 方式上传到 Web Server 端, 用来设计 Web
即时影像的工具。
alevtd: videotext pages 的 Web Server。
xawtv 的 video4linux
xawtv 是相当好的 video4linux 方面的教材,我们极力推荐读者研究 xawtv 的
video4linux 部份的原始码。将取回 xawtv 的原始程序码解开后, 在 libng/
目录下可以看到 grab-v4l.c 的档案, 另外还有一个 grab-v4l2.c 的档案, 这是
video4linux2 (version 2) 的版本。
在 xawtv 的 video4linux 主题现身之前,我们很详尽介绍了 video4linux
的基本设计方法,接下来在 xawtv 之后更是再进一步说明了 video4linux 的经典 ━
mmap 撷取技巧。
那么跨越三期内容的程序码那一个才是完整的呢?事实上都没有,不过请读者们放心,本期我们将列出所有我们曾经介绍过的主题所实作的程序,当然是完整的实作程序码!
video4linux 使用的设备档
Linux 下与 video4linux 相关的设备档与其用途:
/dev/video
Video
Capture Interface
/dev/radio
AM/FM
Radio Devices
/dev/vtx
Teletext
Interface Chips
/dev/vbi
Raw
VBI Data (Intercast/teletext)
video4linux 除了提供 programmer 与影像撷取有关的 API
外,也支援其它像是收音机装置。接下来介绍 video4linux 设计方式,所使用的
Linux kernel 版本为2.2.16。这篇文章将简单介绍实作video4linux
的方法,所以请准备好 Linux kernel
原始码下的Documentation/v4l/API.html文件并了解 What's video4linux。
Video Streaming 的其它关键议题
接下来的主题将介绍 Video Streaming
其它值得研究的主题,我们会在最后才提出来的原因是因为这些主题将不会影响我们之前的程序实作,但在设计完整的
Video Streaming 系统时,则是有必要加以考虑的。
Video Conferencing 应用的重要性
Video Conferencing 在多媒体设计上之所以重要,最重要的原因是因为 Video
Conferencing ?及的技术议题包括:
即时性问题 (real-time systems problem)
互动式应用程式的 latency 与 throughput 问题
这样的问题当然首先是发生在网络频宽的问题上,由于网络视讯会议系统耗费大量的频宽,而且网络视讯会议的品质也容易受网络品质与频宽影响,因此这是值得我们研究的问题之一。
Video Conferencing 另外一个迷人的地方是在于 Video
Conferencing提供良好的person-to-person
环境。VideoConferencing应用软体在多媒体程序设计上,常常也被视为「杀手级」的应用之一,可见
Video Conferencing应用的重要性。
Video Conferencing 的应用领域
Video Conferencing 目前的应用领域则是有:远距教学 (distance learning
systems)、远端诊视系统 (remote consultation systems)、游戏…等等。
就如同我们先前所讲的,解决网络、传输问题变成是下一代通讯应用的的关键。在软件的支援上也是如此,当然这其中有许多的解决方案是设计新的多媒体操作系统
(Multimedia Operating Systems) 来解决。
因为我们要实现 VOD
的技术,因此将会涉及网络的主题,所以要考虑的层面也会比较多。除了网络相关问题外,也会在底下一并讨论其它几个主要的大问题。
Video Streaming 的传送问题
Video Conferencing 所遭遇到的第一个问题是如何递送 (deliver) 影像串流
(video streams),这其中又要考虑到串流的管理、与互联网即时性 (real-time
over the Internet) 问题。
Video Conferencing 重要的关键之一是在于如何有效缩短
latency。所以我们也必须寻找一个有效的方便,来适应各种不同网络频宽的环境。
Video Streaming 的资料储存问题
Video Streaming 的应用还要考虑的问题则是储存设备 (storage) 的选择。Video
Streaming
的应用必须要有良好的储存环境,来储存各种型态的多媒体资料,包含:文字、影像、声音、图片等等,每种资料的特性都不相同。
档案系统 (filesystem)
对于多媒体物件的管理也是很重要的因素之一,必须要有一个可以快速存取并且有效管理多媒体物件的档案系统,才能满足效能的需求。
操作系统的磁盘储存
在现阶段 Video Streaming 以至于多媒体应用程式的设计上,对于 OS
支援的磁盘排程
(DiskScheduler)也被列入我们考虑研究的项目之一。传统上,一般我们设计 OS
时都会选择 SCAN
或是SSTF算法,不过这些传统的磁盘排程算法并无法满足我们的需求。
较先进的磁盘排程算法应考虑到 Video 与 Audio
的应用,而目前较普遍被选择用来设计 multimedia I/O 系统的磁盘排程算法则是
SCAN-EDF 算法。
SCAN-EDF 演算是结合 SCAN 与 EDF 优点的解决方案,SCAN 是众所皆知的
seekoptimizing磁盘排程算法;而 EDF (Earliest Deadline First) 则是属于
real-timescheduling 的算法。
磁盘排程对于 Video Streaming 应用的影响
引进 SCAN-EDF 磁盘排程算法的重要之处在于我们必须要能支援 real-time
request,其影响的范围包括:
Maximum allowable streams
Reponse time
SCAN-EDF 已被分析并证实可以改善以上的效能,那么,对于目前广受欢迎的 Linux
而言,由于SCAN-EDF已经早就在实作应用的范围内了,我们可以将 SCAN-EDF
磁盘排程算法加到 Linux kernel里。如此一来,Linux 在Video Streaming
的应用上也算是重要的效能改良。
Video Conferencing 的 Synchronization 问题
当我们进行多方 (n-way)
视讯会议时,程序总不能让每个人所看到的影像画面都不相同吧!就算无法真正做到每个人的画面同一时间都相同,但至少也要控制在合理可接受的范围之内。
Synchronization (同步) 问题的研究主要是在建立互动式
(interactive)的多媒体应用程式上,同步问题的解决是需要相当多的时间与精神的,好在目前有重量级的
toolkit 供我们使用,那就是 Nsync(in-sync)。 Nsync 共包含二大部份:
Synchronization definition language
Run-time presentation management system
当然我们的 Video Conferencing 应用程式当然也需要 Nsync 的帮忙!
程序补充包
底下我们将补充二个简单的函数,供读者使用,这二个函数与我们的范例程序并没有直接关系,但在测试时可能会有机会使用到:
jpeg.c:将撷取的影像资料利用 libjpeg 存成 JPEG 图档。
ppm.c:将撷取的影像直接写成 PPM 图档。
存成 JPEG 图档部份
/*
* JoTV - Video Streaming Systems
* (c) 2001 Jollen <jollen@o3.net>
*/
#ifndef _JPEG_H_
#define _JPEG_H_
int write_jpeg(char *filename, IMG *img, int width, int height,
int quality, int gray);

Linux的应用--Video Streaming探讨五

本期将以完整的程序范例为主,
说明之前未深入说明的地方。并且更详细地介绍video4linux 如何以 mmap
(filp-flop) 方式撷取影像资料, 同时也会展示如何将撷取出来的影像存成图档,
并且利用绘图软件开启。
mmap 的初始化从那里开始
继前四期介绍有关 Video Streaming 的内容后, 最近收到几位读者的来信,
询问有关 video4linux 利用mmap撷取影像的方法。video4linux 以 mmap
撷取影像的方法在本文第 4
篇曾经简单介绍过,但是有读者希望可以做更详细的介绍,因此笔者特别将相关的程序码完整列出供参考。
要提到 mmap 的初始化, 我们要配合第 2
篇文章的程序范例。底下是对影像撷取装置做初始化的程序码, 与第 2
篇文章的范例比较, 底下的函数设计的更完整:
int device_init(char *dev, int channel, int norm)
{
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;
}
我们又把 device_init() 写的更完整了。粗体字的地方是我们初始化 mmap
的程序码, 一开始的程序可能又让人觉得一脸茫然:
if (v4l_open(dev, &vd)) {
return -1;
} else {
v4l_grab_init(&vd, screen_width, screen_height); //wake up drivers!
v4l_close(&vd);
}
将 device 开启成功后, 做了一次 v4l_grab_init后再把 device 关掉,
用意何在呢? 其实, 是因为bttv 的 driver 是以 module 的方式安装到
Linuxkernel, 所以 bttv driver 会因为没有被使用,而「睡觉了」。
我们加上一次 v4l_grab_init() 的目的就是为了要「叫醒」bttv 的 driver,
其实这个动作可有可无, 但一般认为加上会比较好。
v4l_mmap_init() 是对 mmap 做初始化的工作, 不过要特别注意, 这个动作要在
channel 与 norm 都设定好后才进行, 底下会再说明一次。
v4l_mmap_init() 相当重要, 因为我们要利用 mmap() 函数将 v4l_deivce 结构里的
map「连接」起来。mmap() 是 POSIX.4 的标准函数, 用途是将 device 给 map
到内存, 也就是底下粗体字的地方:
int v4l_mmap_init(v4l_device *vd)
{
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;
}
PROT_READ 表示可读取该 memory page , PROT_WRITE 则是可写入,
MAP_SHARED则是让这块mapping 的区域和其它 process 分享。第一个参数旦 0
是启始位置, vd->mbuf.size则是长度(length)。vd->fd 则是 device 的 file
description, 最后一个参数是 offset。
v4l_get_mbuf() 和之前介绍过的没有什么出入。在新的 device_init() 函数里,
我们也把初始化好的 mmap 相关信息印出。
channel 与 norm
我们提过, 在做 v4l_mmap_init() 前要先做 channel 与 norm 的设定, 分别是
v4l_get_channels() 与 v4l_set_norm() 函数。
在这里要捕充说明一点, 以笔者的 CCD 头来讲, 和撷取卡是以 Composite1 连接,
所以在 channel 方面, 就要利用 v4l_switch_channel() 将 channel 切到
Composite1 端。
v4l_switch_channel() 程序码如下:
int v4l_switch_channel(v4l_device *vd, int c)
{
if (ioctl(vd->fd, VIDIOCSCHAN, &(vd->channel[c])) < 0) {
perror("v4l_switch_channel:");
return -1;
}
return 0;
}
传入的 c 是 channel, 而 channel number 我们已经在 device_init() 里打印出来:
Channel 0: Television
Channel 1: Composite1
Channel 2: S-Video
我们可以看到 Composite1 位于 Channel 1 (由 0 算起), 所以
v4l_switch_channel() 的参数 c 要传入 1。
如何设定 norm
norm 的话就比较单纯一点, 参数如下:
VIDEO_MODE_PAL
VIDEO_MODE_NTSC
VIDEO_MODE_SECAM
VIDEO_MODE_AUTO
这些参数都定义于 videodev.h 档案里。v4l_set_norm() 是我们用来设定 norm
的函数, 程序码如下:
int v4l_set_norm(v4l_device *vd, int norm)
{
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;
}
要仔细注意, 我们是对所有的 channel 设定 norm, 设定完成后, 底下又做了一次
v4l_get_capability(), 主要目的是确保每个 channel
的设定都有被设定成功。然后呼叫 v4l_get_picture。
v4l_get_capability() 会利用 ioctl()
取得设备档的相关信息,并且将取得的信息放到structvideo_capability
结构里。同理,v4l_get_picture() 也会呼叫
ioctl(),并将影像视窗信息放到struct video_picture 结构。
如何 get picture
取得设备信息后,我们还要再取得影像信息,所谓的影像信息指的是输入到影像捕捉卡的影像格式。在
_v4l_struct 结构里,我们宣告 channel 如下:
struct video_picture picture;
初始化 picture 的意思就是要取得输入到影像捕捉卡的影像信息,我们设计
v4l_get_ picture() 函数来完成这件工作。
v4l_get_ picture () 完整程序码如下:
int v4l_get_picture(v4l_device *vd)
{
if (ioctl(vd->fd, VIDIOCGPICT, &(vd->picture)) < 0) {
perror("v4l_get_picture:");
return -1;
}
return 0;
}
传递VIDIOCGPICT 给 ioctl() 则会传回影像的属性 (image
properties),这里则是将影像属性存放于 vd->
picture。这部份我们也曾经介绍过, 在这里要再捕充一点。如果是以 GREY
方式撷取影像, 那么我们可以利用 VIDIOCSPIC 来设定像素的亮度与灰阶度, 请参考
API.html 里的 struct video_picture 说明。
初始化 grab
初始化 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;
v4l_get_picture() 与之前介绍的一样, 而 v4l_set_palette()
则是用来设定调色盘, 由于我们希望得到的是 RGB32, 所以 DEFAULT_PALETTE
定义成:
#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)
{
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);
}
初始化的目的是将 mmap 结构填入适当的值。针对 RGB32、NTSC 的 CCD 影像撷取,
mmap 的大小不妨设定成 640*480 或 320*240 都可以, 给定 mmap 的大小后,
再来还要将 format 填入调色盘类型。
最后设定 frame_current 变数与 frame_using[] 数组, 这里等于上一篇 (4)
介绍的 frame 变数与 framestat[] 数组。如何所有的程序码都没有错误,
当装置正常躯动时, 就可以看到底下的初始化讯息,
这里的讯息比起之前的范例更清楚、完整:
/dev/video0: initialization OK... BT878(Chronos Video Shuttle I)
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
v4l_grab_frame() 的用处
读者可能还不明白 v4l_grab_frame() 的用途, v4l_grab_frame()
是真正将影像放到 mmap 里的函数。我们重写一次 v4l_grab_frame() 函数,
并且再说明一次:
int v4l_grab_frame(v4l_device *vd, int 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;
}
因为我们用 frame_using[] 数组来纪录那个 frame 已经被使用,
所以一开始当然要先判断目前的 frame 是否已经被使用:
if (vd->frame_using[frame]) {
fprintf(stderr, "v4l_grab_frame: frame %d is already used.\n",
frame);
return -1;
}
如果没有被使用, 就把 mmap 的 frame 填入 frame 编号, 然后利用
VIDIOCMCAPTURE撷取出影像。结束前要把目前frame 的状态标示成使用中
(frame_using[]), 然后把 frame_current 指定成现在的frame, 完成工作后离开。
mmap 如何做 filp-flop
这是一位读者问的问题。这个问题问的相当聪明, 每个人可能都有不同的方法来做
flip-flop 的动作, 这里笔者以 2 个 frame 为例, 我们可以再写一个函数来做
flip-flop:
int device_grab_frame()
{
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() 是主要核心所在, 因为我们只有二个 frame, 所以
frame_current 不是 0 就是 1。
撷取出来的影像放在那里
因为我们特别写了上面的函数来做 mmap 的 flip-flop, 所以在主程序里就改用
device_next_frame 来持续撷取影像。所以配合主程序, 我们的程序写法如下:
device_next_frame(); //Ok, grab a frame.
device_grab_sync(); //Wait until captured.
img = device_get_address(); //Get image pointer.
printf("\nImage pointer: %p\n", img);
这段程序就是我们的重点好戏, 当我们呼叫 device_next_frame() 撷取 frame
之后, 必须做一个等待的动作, 让 frame 撷取完成再取出影像。
v4l_grab_sync() 程序码如下:
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;
}
利用 VIDIOCSSYNC 等待完成后, 别忘了将目前 frame
的状态改回未被使用。接下来我们要问, 撷出出来的 frame 到底放到那里去了呢?
答案就是之们利用 mmap() 将 device 所 map 的内存里, 因为我们是利用 mmap
(flip-flop) 方式, 所以会有 2 个 (或以上) 的 frame, 这时就要计算一下
offset, 才知道到底目前的影像资料被放到那里了。算式如下:
vd.map + vd.mbuf.offsets[vd.frame_current]
device_get_address() 函数就是这么回事。
如何输出影像资料呢
输出影像资料的方法很多, 可以直接输出到 framebuffer 上, 或是利用 SDL
显示。在这里笔者要示范最原始的方法 ━ 输出到档案里。当我们利用
device_get_address() 取得 frame 的影像资料后, 再将 frame 的影像资料输出成
PPM 格式的档案。程序码如下:
FILE *fp;
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);
先利用 fprintf() 写入 PPM 档案的档头信息, 然后以 fwrite()
将传回的影像资料写到档案里。img 指向内存里的 frame 影像资料, 写入时,
请特别注意粗体字的地方, 因为我们是用 RGB32 的调色盘, 而 RGB 是以 3 个
sample 来表示一个 pixel, 所以要乘上 3。如果是 GREY 调色盘, 就不用再乘 3
了。最后将输出的 PPM 档案转换格式成 TIFF 就可以用一盘的绘图软件打开了:
linux$ ppm2tiff test.ppm test.tiff
将影像存成 JPEG 的方法
最后我们再完成一个功能, 就可以实作出一个完整的 Webcam
软件。之前我们将影像存成 PPM 格式的图档, 不过因为档案过太,
会造成传输的不便。因此, 我们势必要将影像资料存成更小的档案才具实用性。JPEG
或MJPEG 都是在本文第 1 篇介绍过的格式。以 JPEG 来存放图档,
相当容易可以实作出 Webcam 的功能, 但缺点就是无法传送声音资料。
我们使用 mpeglib 来完成这项任务, mpeglib 可至 www.ijg.org 下载。
将影像资料存成 JPEG 的方法在「各大」与 video streaming 有关的软件 (例如:
xawtv) 都可以看得到范例。不过因此这部份已脱离 v4l 的主,
所以笔者只列出底下的 write_jpeg() 完整函数, 供读者使用:
int write_jpeg(char *filename, unsigned char * img, int width, int
height, int quality, int gray)
{
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;
}
利用 mpeglib 写入 JPEG 影像资料时, 必须分别对每行 scanline 写入。呼叫范例:
write_jpeg("test01.jpg", img, NTSC_WIDTH, NTSC_HEIGHT, 50, FALSE );
第一个参数是图档名称, 第二个参数是影像资料,
然后第三、第四个参数接着影像的大小, 第五个参数 50 表示 JPEG 图档的压缩品质
(quality), 最后一个参数 FALSE 表示影像资料不是 grey (灰阶)
影像。灰阶影像与彩色影像的差别在于 input_components、in_color_space 与
scanline 的长度。
结语
在一连串的 Video Streaming 主题里, 我们学到 video4linux 撷取影像的方式, 以
mmap(flip-flop)来连续撷取影像, 并做到 VOD
的功能是我们的最终目的。到这里为止, 我们已经有能力实作出简单的
Webcam软件,类似这种取固定间隔传送影像的方式应用也很广,
例如路口交通状况回报。
利用到这里所学的方法, 将撷取的影像存成 JPEG, 然后放到 Web 上,
固定一段时间更新, 我们也可以设计一套简单的路口交通状况回报系统,
或是家里的监视系统。后面接着的主题, 将会以现有的程序为基础, 实作真正具有
VOD 能力的软件。