2009年8月12日星期三
全面的framebuffer详解
FrameBuffer 是出现在 2.2.xx 内核当中的一种驱动程序接口。
Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出
FrameBuffer这个设备来供用户态进程实现直接写屏。Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过
Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由
Framebuffer设备驱动来完成的。
但Framebuffer本身不具备任何运算数据的能力,就只好比是一个暂时存放水的水池.CPU将运算后的结果放到这个水池,水池再将结果流到显示器.
中间不会对数据做处理.
应用程序也可以直接读写这个水池的内容.在这种机制下,尽管Framebuffer需要真正的显卡驱动的支持,但所有显示任务都有CPU完成,因此CPU
负担很重
framebuffer的设备文件一般是 /dev/fb0、/dev/fb1 等等。
可以用命令: #dd if=/dev/zero of=/dev/fb 清空屏幕.
如果显示模式是 1024x768-8 位色,用命令:$ dd if=/dev/zero of=/dev/fb0
bs=1024 count=768 清空屏幕;
用命令: #dd if=/dev/fb of=fbfile 可以将fb中的内容保存下来;
可以重新写回屏幕: #dd if=fbfile of=/dev/fb;
在使用Framebuffer时,Linux是将显卡置于图形模式下的.
在应用程序中,一般通过将 FrameBuffer
设备映射到进程地址空间的方式使用,比如下面的程序就打开 /dev/fb0
设备,并通过 mmap 系统调用进行地址映射,随后用 memset
将屏幕清空(这里假设显示模式是 1024x768-8 位色模式,线性内存模式):
int fb;
unsigned char* fb_mem;
fb = open ("/dev/fb0", O_RDWR);
fb_mem = mmap (NULL, 1024*768, PROT_READ|PROT_WRITE,MAP_SHARED,fb,0);
memset (fb_mem, 0, 1024*768); //这个命令应该只有在root可以执行
FrameBuffer 设备还提供了若干 ioctl
命令,通过这些命令,可以获得显示设备的一些固定信息(比如显示内存大小)、与显示模式相关的可变信息(比如分辨率、象素结构、每扫描线的字节宽度),以及伪彩色模式下的调色板信息等等。
通过 FrameBuffer
设备,还可以获得当前内核所支持的加速显示卡的类型(通过固定信息得到),这种类型通常是和特定显示芯片相关的。比如目前最新的内核(2.4.9)中,就包含有对
S3、Matrox、nVidia、3Dfx
等等流行显示芯片的加速支持。在获得了加速芯片类型之后,应用程序就可以将 PCI
设备的内存I/O(memio)映射到进程的地址空间。这些 memio
一般是用来控制显示卡的寄存器,通过对这些寄存器的操作,应用程序就可以控制特定显卡的加速功能。
PCI
设备可以将自己的控制寄存器映射到物理内存空间,而后,对这些控制寄存器的访问,给变成了对物理内存的访问。因此,这些寄存器又被称为"memio"。一旦被映射到物理内存,Linux
的普通进程就可以通过 mmap 将这些内存 I/O
映射到进程地址空间,这样就可以直接访问这些寄存器了。
当然,因为不同的显示芯片具有不同的加速能力,对memio
的使用和定义也各自不同,这时,就需要针对加速芯片的不同类型来编写实现不同的加速功能。比如大多数芯片都提供了对矩形填充的硬件加速支持,但不同的芯片实现方式不同,这时,就需要针对不同的芯片类型编写不同的用来完成填充矩形的函数。
FrameBuffer
只是一个提供显示内存和显示芯片寄存器从物理内存映射到进程地址空间中的设备。所以,对于应用程序而言,如果希望在
FrameBuffer 之上进行图形编程,还需要自己动手完成其他许多工作。
二、FrameBuffer在Linux中的实现和机制
Framebuffer对应的源文件在linux/drivers/video/目录下。总的抽象设备文件为fbcon.c,在这个目录下还有与各种显卡驱动相关的源文件。
//这个文件要好好看看
(一)、分析Framebuffer设备驱动
需要特别提出的是在INTEL平台上,老式的VESA 1.2
卡,如CGA/EGA卡,是不能支持Framebuffer的,因为Framebuffer要求显卡支持线性帧缓冲,即CPU可以访问显缓冲中的每一位,但是VESA
1.2 卡只能允许CPU一次访问64K的地址空间。
FrameBuffer设备驱动基于如下两个文件:
1) linux/include/linux/fb.h
2) linux/drivers/video/fbmem.c
下面分析这两个文件。
1、fb.h
几乎主要的结构都是在这个中文件定义的。这些结构包括:
1)fb_var_screeninfo
这个结构描述了显示卡的特性:
NOTE:::: __u32 是表示 unsigned 不带符号的 32 bits
的数据类型,其余类推。这是 Linux
内核中所用到的数据类型,如果是开发用户空间(user-space)的程序,可以根据具体计算机平台的情况,用
unsigned long 等等来代替
struct fb_var_screeninfo
{
__u32 xres; /* visible resolution */ //可视区域
__u32 yres;
__u32 xres_virtual; /* virtual resolution */ //可视区域
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible resolution */
//可视区域的偏移
__u32 yoffset;
__u32 bits_per_pixel; /* guess what */ //每一象素的bit数
__u32 grayscale; /* != 0 Gray levels instead of colors *///等于零就成黑白
struct fb_bitfield red; /* bitfield in fb mem if true color,
*/真彩的bit机构
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency */ 透明
__u32 nonstd; /* != 0 Non standard pixel format */ 不是标准格式
__u32 activate; /* see FB_ACTIVATE_* */
__u32 height; /* height of picture in mm */ 内存中的图像高度
__u32 width; /* width of picture in mm */ 内存中的图像宽度
__u32 accel_flags; /* acceleration flags (hints) */ 加速标志
/* Timing: All values in pixclocks, except pixclock (of course) */
时序-_-这些部分就是显示器的显示方法了,可以找相关的资料看看
__u32 pixclock; /* pixel clock in ps (pico seconds) */
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */ 水平可视区域
__u32 vsync_len; /* length of vertical sync */ 垂直可视区域
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 reserved[6]; /* Reserved for future compatibility */ 备用-以后开发
};
2) fb_fix_screeninfon
这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改;比如FrameBuffer内存的起始地址。它依赖于被设定的模式,当一个模式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */ID
unsigned long smem_start; /* Start of frame buffer mem */ 内存起始
/* (physical address) */ 物理地址
__u32 smem_len; /* Length of frame buffer mem */ 内存大小
__u32 type; /* see FB_TYPE_* */
__u32 type_aux; /* Interleave for interleaved Planes */插入区域?
__u32 visual; /* see FB_VISUAL_* */
__u16 xpanstep; /* zero if no hardware panning */没有硬件设备就为零
__u16 ypanstep; /* zero if no hardware panning */
__u16 ywrapstep; /* zero if no hardware ywrap */
__u32 line_length; /* length of a line in bytes */ 一行的字节表示
unsigned long mmio_start; /* Start of Memory Mapped I/O */内存映射的I/O起始
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */ I/O的大小
__u32 accel; /* Type of acceleration available */ 可用的加速类型
__u16 reserved[3]; /* Reserved for future compatibility */
};
3) fb_cmap
描述设备无关的颜色映射信息。可以通过FBIOGETCMAP 和 FBIOPUTCMAP
对应的ioctl操作设定或获取颜色映射信息.
struct fb_cmap {
__u32 start; /* First entry */ 第一个入口
__u32 len; /* Number of entries */ 入口的数字
__u16 *red; /* Red values */ 红
__u16 *green;
__u16 *blue;
__u16 *transp; /* transparency, can be NULL */ 透明,可以为零
};
4) fb_info
定义当显卡的当前状态;fb_info结构仅在内核中可见,在这个结构中有一个fb_ops指针,
指向驱动设备工作所需的函数集。
struct fb_info {
char modename[40]; /* default video mode */ 默认的视频卡类型
kdev_t node;
int flags;
int open; /* Has this been open already ? */ 被打开过么?
#define FBINFO_FLAG_MODULE 1 /* Low-level driver is a module */
struct fb_var_screeninfo var; /* Current var */ 现在的视频信息
struct fb_fix_screeninfo fix; /* Current fix */ 修正的信息
struct fb_monspecs monspecs; /* Current Monitor specs */ 现在的显示器模式
struct fb_cmap cmap; /* Current cmap */ 当前优先级
struct fb_ops *fbops;
char *screen_base; /* Virtual address */ 物理基址
struct display *disp; /* initial display variable */初始化
struct vc_data *display_fg; /* Console visible on this display */
char fontname[40]; /* default font name */默认的字体
devfs_handle_t devfs_handle; /* Devfs handle for new name */
devfs_handle_t devfs_lhandle; /* Devfs handle for compat. symlink */兼容
int (*changevar)(int); /* tell console var has changed */
告诉console变量修改了
int (*switch_con)(int, struct fb_info*);
/* tell fb to switch consoles */ 告诉fb选择consoles
int (*updatevar)(int, struct fb_info*);
/* tell fb to update the vars */ 告诉fb更新变量
void (*blank)(int, struct fb_info*); /* tell fb to (un)blank the screen
*/告诉fb使用黑白模式(或者不黑)
/* arg = 0: unblank */arg=0的时候黑白模式
/* arg > 0: VESA level (arg-1) */ arg>0时候选择VESA模式
void *pseudo_palette; /* Fake palette of 16 colors and
the cursor's color for non
palette mode */ 修正调色板
/* From here on everything is device dependent */ 现在就可以使用了
void *par;
};
5) struct fb_ops
用户应用可以使用ioctl()系统调用来操作设备,这个结构就是用一支持ioctl()的这些操作的。
struct fb_ops {
/* open/release and usage marking */
struct module *owner;
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
/* get non settable parameters */
int (*fb_get_fix)(struct fb_fix_screeninfo *fix, int con,
struct fb_info *info);
/* get settable parameters */
int (*fb_get_var)(struct fb_var_screeninfo *var, int con,
struct fb_info *info);
/* set settable parameters */
int (*fb_set_var)(struct fb_var_screeninfo *var, int con,
struct fb_info *info);
/* get colormap */
int (*fb_get_cmap)(struct fb_cmap *cmap, int kspc, int con,
struct fb_info *info);
/* set colormap */
int (*fb_set_cmap)(struct fb_cmap *cmap, int kspc, int con,
struct fb_info *info);
/* pan display (optional) */
int (*fb_pan_display)(struct fb_var_screeninfo *var, int con,
struct fb_info *info);
/* perform fb specific ioctl (optional) */
int (*fb_ioctl)(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg, int con, struct fb_info *info);
/* perform fb specific mmap */
int (*fb_mmap)(struct fb_info *info, struct file *file, struct
vm_area_struct *vma);
/* switch to/from raster image mode */
int (*fb_rasterimg)(struct fb_info *info, int start);
};
6) structure map
struct fb_info_gen | struct fb_info | fb_var_screeninfo
| | fb_fix_screeninfo
| | fb_cmap
| | modename[40]
| | fb_ops ---|--->ops on var
| | ... | fb_open
| | | fb_release
| | | fb_ioctl
| | | fb_mmap
| struct fbgen_hwswitch
\-----|-> detect
| encode_fix
| encode_var
| decode_fix
| decode_var
| get_var
| set_var
| getcolreg
| setcolreg
| pan_display
| blank
| set_disp
[编排有点困难,第一行的第一条竖线和下面的第一列竖线对齐,第一行的第二条竖线和下面的第二列竖线对齐就可以了]
这个结构 fbgen_hwswitch抽象了硬件的操作.虽然它不是必需的,但有时候很有用.
2、 fbmem.c
fbmem.c
处于Framebuffer设备驱动技术的中心位置.它为上层应用程序提供系统调用也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系统内核注册它们自己.
fbmem.c 为所有支持FrameBuffer的设备驱动提供了通用的接口,避免重复工作.
1) 全局变量
struct fb_info *registered_fb[FB_MAX];
int num_registered_fb;
这两变量记录了所有fb_info 结构的实例,fb_info
结构描述显卡的当前状态,所有设备对应的fb_info
结构都保存在这个数组中,当一个FrameBuffer设备驱动向系统注册自己时,其对应的fb_info
结构就会添加到这个结构中,同时num_registered_fb 为自动加1.
static struct {
const char *name;
int (*init)(void);
int (*setup)(void);
} fb_drivers[] __initdata= { ....};
如果FrameBuffer设备被静态链接到内核,其对应的入口就会添加到这个表中;如果是动态加载的,即使用insmod/rmmod,就不需要关心这个表。
static struct file_operations fb_ops ={
owner: THIS_MODULE,
read: fb_read,
write: fb_write,
ioctl: fb_ioctl,
mmap: fb_mmap,
open: fb_open,
release: fb_release
};
这是一个提供给应用程序的接口.
2)fbmem.c 实现了如下函数.
register_framebuffer(struct fb_info *fb_info);
unregister_framebuffer(struct fb_info *fb_info);
这两个是提供给下层FrameBuffer设备驱动的接口,设备驱动通过这两函数向系统注册或注销自己。几乎底层设备驱动所要做的所有事情就是填充fb_info结构然后向系统注册或注销它。
(二)一个LCD显示芯片的驱动实例
以Skeleton LCD
控制器驱动为例,在LINUX中存有一个/fb/skeleton.c的skeleton的Framebuffer驱动程序,很简单,仅仅是填充了
fb_info结构,并且注册/注销自己。设备驱动是向用户程序提供系统调用接口,所以我们需要实现底层硬件操作并且定义file_operations
结构来向系统提供系统调用接口,从而实现更有效的LCD控制器驱动程序。
1)在系统内存中分配显存
在fbmem.c文件中可以看到, file_operations
结构中的open()和release()操作不需底层支持,但read()、write()和
mmap()操作需要函数fb_get_fix()的支持.因此需要重新实现函数fb_get_fix()。另外还需要在系统内存中分配显存空间,大多数的LCD控制器都没有自己的显存空间,被分配的地址空间的起始地址与长度将会被填充到fb_fix_screeninfo
结构的smem_start 和smem_len 的两个变量中.被分配的空间必须是物理连续的。
2)实现 fb_ops 中的函数
用户应用程序通过ioctl()系统调用操作硬件,fb_ops
中的函数就用于支持这些操作。(注: fb_ops结构与file_operations
结构不同,fb_ops是底层操作的抽象,而file_operations是提供给上层系统调用的接口,可以直接调用.
ioctl()系统调用在文件fbmem.c中实现,通过观察可以发现ioctl()命令与fb_ops's
中函数的关系:
FBIOGET_VSCREENINFO fb_get_var
FBIOPUT_VSCREENINFO fb_set_var
FBIOGET_FSCREENINFO fb_get_fix
FBIOPUTCMAP fb_set_cmap
FBIOGETCMAP fb_get_cmap
FBIOPAN_DISPLAY fb_pan_display
如果我们定义了fb_XXX_XXX
方法,用户程序就可以使用FBIOXXXX宏的ioctl()操作来操作硬件。
文件linux/drivers/video/fbgen.c或者linux/drivers/video目录下的其它设备驱动是比较好的参考资料。在所有的这些函数中fb_set_var()是最重要的,它用于设定显示卡的模式和其它属性,下面是函数fb_set_var()的执行步骤:
1)检测是否必须设定模式
2)设定模式
3)设定颜色映射
4) 根据以前的设定重新设置LCD控制器的各寄存器。
第四步表明了底层操作到底放置在何处。在系统内存中分配显存后,显存的起始地址及长度将被设定到
LCD控制器的各寄存器中(一般通过fb_set_var()
函数),显存中的内容将自动被LCD控制器输出到屏幕上。另一方面,用户程序通过函数mmap()将显存映射到用户进程地址空间中,然后用户进程向映射空间发送的所有数据都将会被显示到LCD显示器上。
三、FrameBuffer的应用
(一)、一个使用FrameBuffer的例子
FrameBuffer主要是根据VESA标准的实现的,所以只能实现最简单的功能。
由于涉及内核的问题,FrameBuffer是不允许在系统起来后修改显示模式等一系列操作。(好象很多人都想要这样干,这是不被允许的,当然如果你自己写驱动的话,是可以实现的).
对FrameBuffer的操作,会直接影响到本机的所有控制台的输出,包括XWIN的图形界面。
在struct fb_info 中的char fontname[40]; /* default font name */默认的字体
就可以实现显示的中文化----难道 篮点linux就是这样搞得??
好,现在可以让我们开始实现直接写屏:
1、打开一个FrameBuffer设备
2、通过mmap调用把显卡的物理内存空间映射到用户空间
3、直接写内存。
/********************************
File name : fbtools.h
*/
#ifndef _FBTOOLS_H_
#define _FBTOOLS_H_
#include <linux/fb.h>
//a framebuffer device structure;
typedef struct fbdev{
int fb;
unsigned long fb_mem_offset;
unsigned long fb_mem;
struct fb_fix_screeninfo fb_fix;
struct fb_var_screeninfo fb_var;
char dev[20];
} FBDEV, *PFBDEV;
//open & init a frame buffer
//to use this function,
//you must set FBDEV.dev="/dev/fb0"
//or "/dev/fbX"
//it's your frame buffer.
int fb_open(PFBDEV pFbdev);
//close a frame buffer
int fb_close(PFBDEV pFbdev);
//get display depth
int get_display_depth(PFBDEV pFbdev);
//full screen clear
void fb_memset(void *addr, int c, size_t len);
用终端来观看星球大战
Linux 上的终端来观看 ASCII 版本的星球大战。
事实上,这是挺火星的一件事了,从1997年开始,一个叫 Simon Jansen
的小伙子就开始做这个星球大战了,在2008年4月,他完成了最后一个镜头。
所以,这个很老了,但是如果你还没有看过,那么今天就用你的终端来感受一下
ASCII 版本的星球大战吧。
好玩不,你要做的只是在终端中输入如下命令:
telnet towel.blinkenlights.nl
男人们并不是特意惹你生气,我们只是被误解了
根据国家统计局的调查(这总不会是假的吧),在68%的离婚案件中,女性胜诉。她们最流行使用的理由是行为,如果这词儿也流行的话(其实不然)。这暗示着两件可能人尽皆知的事情:第一,男人行为不捡;第二,很多女人在婚后几个月甚至几年后才慢慢明白这一点。这里看起来有不小的知识空白,没关系,简单普及一下,我就让你们明白了。我能够解释我们为什么会行为不检——或者,更确切的说,为什么你们认为我们行为不检,然而事实上,对我们来说,我们的行为是完全可以理解并符合逻辑的。
忘记东西
对于我们无法记住那些你们让我们记得的事情,有科学的原因可以解释。虽然女性大脑比男人的要小10%,其拥有的白质却比灰质多。白质主要用于记忆,而灰质更善于激活大脑进行重复而劳累的Xbox游戏。
这是基因构成,懂了吗?
你要记得:记不得不一定是件坏事儿。我们忘记说"我爱你",我们忘记生日礼物。我们忘记去超市要买的东西,但是这些不代表我们不爱你们、不喜欢生日礼物或对买那些东西没兴趣,原因很简单,只能怪灰质。
看地图
"就是这条路。"
"为什么不停下来找个人问问呢?
"因为就是这条路。"
"可是我们已经走遍这个村子了。"
"我们没有。"
"有。"
有什么问题吗,姑奶奶们?没有啊,这不是我们在耍犟。是你们。你们和你们匮乏的同情心。我们知道自己错了,我们当然知道。但是我们要面子,我们的思维很简单,这一点,你们知道,我们也知道。承认输了,对我们来说是天大的耻辱,这与承认勃起障碍一样难。最重要的是,我们应该被允许兜圈子,直到我们自己走运或一次次的错了后,找到对的路。记住,永远一定不要说:"我告诉过你停下来问问路的。"
你们就当我们是白痴,这样我们会对你们感激不缴,或许还会为你们准备不错的生日礼物——如果我们记得的话。不幸的是,我们通常记不得。
穿上这个我的屁股看起来丰满吗?
这也许是所有婚姻冲突中最不公正的事儿。你们不应该指望一个男人跟你们一整天的逛街,呆在过热的购物中心,周围充满了出着臭汗、无聊的人。有时候甚至等上几小时,站在试衣间外面,对这同样汗流浃背的无聊的男人,无奈的耸耸肩,而你却试穿了同样款式的19条裤子,还每次都问我们:"穿这条,我的屁股是不是看起来大一些?"
虽然有着漫长而痛苦的回答这个恶魔问题的经历,我至今还未发现正确的回答。如果我们如实回答("不是特别明显,我觉得还行"),你们会不说话,因我们不够积极。如果我们如实回答("我不在乎你的屁股看起来怎么样,我们现在可以回去了吗?"),你会生气。如果我们撒谎("天啊,真是太美了,你的屁股看起来好丰满啊!"),你会怀疑,然后再问我们问题,比如:"你的意思是说我们可以回家了,是吧?"我们眼神的慌乱暴露了我们的意图,然后你们又让我们去另外一家商店。额的神啊!
请不要带我们去购物。你们穿什么我们都爱你们。只要正当的性感就行,但我们不想参与到购买的过程中。
DIY,以及为什么是我们的错?
在宜家之前,男人就能够DIY,现在我们也自认为可以。只是由于可拆装的瑞士货的到来,那些用一把艾伦六角把手无法解决的技术就失传了。但是问到关于奥克斯博湖,毕达哥拉斯定理或者Patsy
Kensit主演的电影中她的野兽使用的致命武器,我们可以立马告诉你。问到我们如何把插头装进非建筑墙内,我们也可以告诉你,只是我们的回答会不对。
在这里重要的是我们的热情。每年,在银行周末假期前,事故预防皇家协会都会公布一些令人震惊的统计数字,说明有多少男人在DIY时,经历了血淋淋的事故。今年,这些事故的第一号杀手工具是:刀子和解剖刀(导致了20000例急诊),锯子(15000例)最后是令人牙齿打颤的碾磨机(6500例)。"过度自信和知识的缺乏,"事故预防皇家协会的报道说,:"是DIY事故的主要原因。"可是这不就是让你们对我们一见钟情的可爱特点之一吗?难道你们就不能不看功劳看苦劳吗?
看暴力电影
目前为止,你应该对婚姻关系中男人每天要做的事情所经历的那些害怕、紧张和痛苦有一点认识了吧?所以我们需要时间来放松。你们可以一遍遍念咒语似的说"能给我搓搓背吗?"来放松,我们就是看暴力电影。你们若是不想看,我们没意见。我们甚至不介意在浴室的小电视上看,以便让你看那该死的《飞黄腾达》(美国一档热门商业真人秀节目)。只是不要让我们看你们那些情节悲惨的古装剧,因为我们可能会哭,而男人是从不掉泪的。那样我们必须得面对自己的脆弱情感,那我们男人的面子往哪儿放?还是让我们赶紧往下说吧。
做爱以及其频率
最好是细水长流、毛毛雨常有,受不了要么旱死要么涝死。就像记忆一样,这个也是有科学根据的,而且是我们控制范围之外的。
看其他女人
还是科学的缘故,同样是我们不由自主的。根据《那不是因为你,而是生物原因》的作者Joe
Quirk的看法,男人和女人拥有相互排斥的繁殖系统。女人生成一个卵子需要29.5天,如果这个卵子受孕了,她们又得十月怀胎,然后是数月或数年的母乳喂养。然而,男人几秒钟内就可以产生300万只精子,如果观察野牛、鸟、猿类和你自己的狗,一般情况下,你会发现雄性大多淫荡而雌性比较挑剔。当代的男人,Silvio
Berlusconi(新当选的意大利总统,传出众多绯闻)除外,已经完成了从猿到人的转变,除了两个方面:不盖马桶盖,看其他女人。可是我们也为之付出了沉重的代价:一次次的去购物,和那些被扔在被子外面的漫漫寒夜。
总之,你们赢了,现在我们结案。
推荐一个bash小技巧
表示上一个命令的最后一个参数。如果你首次登陆终端,那么它会取history的最后一行命令的最后一个参数。
试试看!
ls -hl /rocrocket/software/program/git/bin/
这时,你就可以用cd !$来进入到这个深层目录啦!
ps: 如果你最后一个参数是双引号括起来的,那么!$也会很聪明的看出来。
Socket中如何设置连接超时
要想对connect进行超时处理,就必须按如下步骤:
1. 采用fcntl设置非阻塞式连接以实现connect超时处理;
2. 采用select方法来设置socket connect超时;
3. 采用fcntl将socket设置回阻塞式;
如下是Linux下实现源码:
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char* argv[])
{
int fd;
int tos = 20; //timeout second
struct sockaddr_in sa;
time_t cur;
if(argc!=3){
printf("%s IP PORTn",argv[0]);
exit(-1);
}
fd=socket(AF_INET, SOCK_STREAM, 0);
if(fd<0){
perror("socket fail");
exit(-1);
}
//select 模型,即设置超时
int ret;
int err;
socklen_t len = sizeof(err);
int flags;
struct timeval timeout;
fd_set rset, wset, exset;
sa.sin_family=AF_INET;
sa.sin_addr.s_addr=inet_addr(argv[1]);
sa.sin_port=htons(atoi(argv[2]));
if(fcntl(fd, F_GETFL, 0) < 0)
perror("fcntl F_GETFL fail");
if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
perror("fcntl O_NONBLOCK fail");
else
printf("fcntl O_NONBLOCK success.\n");
cur=time(NULL);
printf("before connect: %s", ctime(&cur));
//因为套接字设为NONBLOCK,通常情况下,连接在connect()返回之前是不会建立的,因此它会返回EINPROGRESS错误,如果返回任何其他错误,则要进行错误处理
//if ((ret = connect(fd, (struct sockaddr*)&sa, sizeof(sa))) < 0){
ret = connect(fd, (struct sockaddr*)&sa, sizeof(sa));
cur=time(NULL);
printf("after connect: %s", ctime(&cur));
printf("connect ret=%d, errno=%d.\n", ret, errno);
if(ret < 0){
if(errno != EINPROGRESS) {
printf("errno=%d, EINPROGRESS=%d.\n", errno, EINPROGRESS);
goto DONE;
}
} else
printf("connect success.\n");
//printf("select time=%ds\n", tos);
cur=time(NULL);
printf("before select: %s", ctime(&cur));
FD_ZERO(&rset);
FD_SET(fd, &rset);
wset = rset;
exset = rset;
timeout.tv_sec = tos; //连接超时(秒)
timeout.tv_usec =0;
//select返回值:num 就绪文件数,0:超时,-1:出错
//if((ret = select(fd+1, &rset, &wset, NULL, tos ? &timeout : NULL))
== 0){
//if((ret = select(fd+1, &rset, &wset, &exset, &timeout)) == 0){
ret = select(fd+1, &rset, &wset, &exset, &timeout);
cur=time(NULL);
printf("after select: %s", ctime(&cur));
printf("select ret=%d.\n",ret);
if(ret == 0){
perror("select timeout.\n");
goto DONE;
}else if(ret == -1) {
perror("select error");
goto DONE;
} else{
perror("select success");
//套接字已经准备好
if (FD_ISSET(fd, &rset) || FD_ISSET(fd, &wset)) {
//connect()失败,进行错处理
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
//getsockopt()失败,进行错处理
perror("getsockopt err.\n");
}
if(err){
printf("connect err=%d\n", err);
goto DONE;
} else
printf("connect success.\n");
} else {
//connect()失败,进行错处理
printf("select err: socket not set.\n");
}
}
DONE:
//到这里说明connect()正确返回
//下面恢复套接字阻塞状态
if (fcntl(fd, F_SETFL, flags) < 0)
perror("fcntl F_SETFL fail");
else
printf("fcntl O_BLOCK success.\n");
close(fd);
exit(0);
}
Page cache和buffer cache的区别与联系
cache一直以来是两个比较容易混淆的概念,在网上也有很多人在争辩和猜想这两个cache到底有什么区别,讨论到最后也一直没有一个统一和正确的结论,在我工作的这一段时间,page
cache和buffer
cache的概念曾经困扰过我,但是仔细分析一下,这两个概念实际上非常的清晰。如果能够了解到这两个cache的本质,那么我们在分析io问题的时候可能会更加得心应手。
Page
cache实际上是针对文件系统的,是文件的缓存,在文件层面上的数据会缓存到page
cache。文件的逻辑层需要映射到实际的物理磁盘,这种映射关系由文件系统来完成。当page
cache的数据需要刷新时,page cache中的数据交给buffer
cache,但是这种处理在2.6版本的内核之后就变的很简单了,没有真正意义上的cache操作。
Buffer
cache是针对磁盘块的缓存,也就是在没有文件系统的情况下,直接对磁盘进行操作的数据会缓存到buffer
cache中,例如,文件系统的元数据都会缓存到buffer cache中。
简单说来,page cache用来缓存文件数据,buffer
cache用来缓存磁盘数据。在有文件系统的情况下,对文件操作,那么数据会缓存到page
cache,如果直接采用dd等工具对磁盘进行读写,那么数据会缓存到buffer cache。
让工资不重要的话的鬼去吧!
"因为这个国家的大多数人都是靠工资生活的,他们生活水平的提高决定着这个国家的繁荣"。
"工资担负着工人在车间之外的全部负担,以及年老后他不能劳动时的生活。同时他也是一个有家庭的人,他也许是孩子们的父亲,他必须凭他挣的钱把孩子们培养成才。我们必须考虑到所有这些事实,让孩子们有衣可穿、有房可住,让他们受到教育,给他们生活的各种小享受"
可能很多人想象不到这些话会出自近百年前、老牌"民营企业家"美国福特汽车公司创始人福特先生之口。福特先生这些话现在读来仍然让人对他肃然起敬并富有意义!
一、工资是天大的事情
1、工资问题是检验是否真正落实科学发展观、致力于和谐社会建设的试金石。
科学发展观、和谐社会的核心是"以人为本",它准确的回答了"为谁发展"的问题。中国改革开放近30多年,GDP每年增长10%左右,但如何让普通百姓都分享到发展的利益确实是一个现实问题。尤其是大量低收入阶层的工资和福利水平实在太低,如农民工加班加点平均工资只有700元/月,像武汉这样的城市最低工资标准不到600元/月,这样的薪酬标准只能解决基本的生存问题,根本谈不上分享经济发展的成果。让人民分享发展的成果是科学发展观最基本的要求,而分享成果的基本形式应该是尽可能提高国民的薪酬和福利。
2、工资"决定着这个国家的繁荣"。
"因为这个国家的大多数人都是靠工资生活的,他们生活水平的提高决定着这个国家的繁荣"。
100多年前,福特先生就深刻认识到了的问题,并且身体力行的把自己员工的工资提高到5美元/天,而当时的社会平均工资是2美元/天。
国民的薪酬水平决定了一个国家的消费能力,国内需求长期乏力的根本原因在于国民的收入水平太低,并导致中国经济对出口的过度依赖。而不合理的外贸结构,给
我们留下的是污染、贫穷、资源(自然、人力及政策)的透支,大量的廉价产品的出口换来的却是国际社会的一片责难,当然,还有那一堆绿花花的烫手的美元!学过国际贸易的人都知道,适度的外贸积余是必要的但并非越多越好,因为外贸积余是以牺牲国内居民的生活质量为代价的。
中国经济的可持续发展,必须靠扩大内需。只有这样经济增长才有意义,因为经济发展的根本目的在于提高国民的生活水平,也只有这样中国经济也才有前途,因为
象中国这样的大国,经济发展不能过份依赖国际市场!中国有广阔的潜在市场,基巨大消费群体超过现有发达国家人口总和,潜力足可以实现中国企业家任何梦想并
可以推动中国乃至世界的经济的繁荣。而实现这一切的关键在于工资!因为只有高工资才能解决内需问题,所以"工资决定了这个国家的繁荣"。
二、解决工资问题必须走出几个误区
1、市场决定论。
有人认为,工资应由市场决定;有人甚至说,低工资你不干,中国有的是劳动力,你不干有人干。这些都是错误的,市场的基本特质是竞争,而弱势群体是竞争的必然输家,竞争无法解决弱势群体的利益问题,全世界所有国家的发展经验证明,弱势群体的利益必须依靠国家立法来保证!
我在
2007年6月1日写的一篇名为《国家应立法大幅提高低收入阶层工资》的博客中倡议:国家应通过立法提高低收入阶层的工资和福利。保护弱势群体的利益是人类文明与进步的重要标志。
2、削弱竞争力论。
有人认为,大幅提高员工工资会削弱中国经济的竞争力。对此观点本人有不同看法:
1)、它可能会削弱竞争力,但以牺牲国民合理利益换来的竞争力是没有意义的。
2)、合理提高低收入阶层工资,未必会从根本上削弱中国经济的竞争力。
中国出口产品的价格竞争力可以说是巨大的。事实上,中国企业从使用低成本的劳动力上所获取的超额利润既没有变成企业赢利,也没有变成国库收入,而是变成了中国企业相互恶性竞争的本钱!所以中国的摩托车论斤卖,电脑利润不如洗脚城!而普
遍性、强制性的提高员工工资,只是平等的提高了中国企业的成本,由于中国低价产品与国际产品之间巨大的价差,劳动力成本的适度提高不可能达到让中国企业失
去价格竞争力的程度。如果确实有产业在中国薪酬水平下无法生存,那只能说明这个产业在中国为无效产业,对中国而言可有可无。如这些产业属属国家战略产业,
国家必须予以财政补贴(如农业),而不能靠低工资生存。
最近,因为中国政府采取提高工资水平与社会保障、取消外资优惠政策等措施,有些外企出现了"外逃"现象。我认为,这没有什么值得大惊小怪的,这类"候鸟型企业",掠夺式的迁移是这类企业的显著特点,这些企业的外迁是中国进步与发展的标志也是发展的必然结果。
3)、长远来看提高工资和福利有利于竞争力的提高。因为中国经济的未来不能长期依托在低成本低素质的劳动力基础之上,而劳动力素质的提高必须让劳动力有自我提升的能力,这种能力首先是建立在合理收入基础之上的。
三、什么是最低工资标准
"工资担负着工人在车间之外的全部负担,以及年老后他不能劳动时的生活。同时他也是一个有家庭的人,他也许是孩子们的父亲,他必须凭他挣的钱把孩子们培养成才。我们必须考虑到所有这些事实,让孩子们有衣可穿、有房可住,让他们受到教育,给他们生活的各种小享受"
工资和福利应足可以解决工人养老,住房、子女哺养和生活的小享受问题,这是百年前福特的最低工资标准。尽管各地经济发展程度不同,但今天的中国至少应该达到当年福特的水平!我在去年6月份的文章中曾武断提出:最低工资水平不能低于1500元/月!
四、中国有足够的能力解决薪酬及福利问题
1、大多数企业有能力提高员工工资,而不会削弱企业竞争能力。
提高员工工资首先是企业的责任,这个观点,前面已经论述。另外,福特大幅提高员工工资非但没有削弱其竞争力反而使竞争力得到大幅提升!三一希望做中国的福特!
2、国民的工资和社会保障问题,除了企业之外国家也有足够能力进行统筹。
中国是一个拥有特殊发展历史的国家,有许多遗留问题需要政府统筹解决。中国是有能力逐步解决这些问题的。
首先,每年大幅增加的财政收入,为国家解决国民基本社会保障和社会公益问题提供了强大保障;
其次,中国有大量国有企业,去年国有企业赢利超过万亿元,应可提取部分利润用于国民生计。国有企业长期不分红甚至不交税,其超额利润也没有变成企业利润和
国库收入而部分变成了国有企业恶性竞争的本钱!也成了破坏市场秩序的主要原因,是中国经济健康发展的负面影响不可小视。中国家电行业业绩滑落至如此程度与
国有企业之间的恶斗不无关系。国家应对国有企业进行利润考核,并作为出资人分享企业红利,这既可以达到规范市场秩序的目的,同时也可以解决民生问题。不交
利润、不交税的国有企业是没有价值的企业!
工资问题是天大的事,因为它决定了国家的繁荣和未来。
video for linux 编程概述
Video4linux(简称V4L),是linux中关于视频设备的内核驱动,现在已有Video4linux2,还未加入linux内核,使用需自己下载补丁。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写,摄像头在/dev/video0下。
2.Video4linux下视频编程的流程
(1)打开视频设备:
(2) 读取设备信息
(3)更改设备当前设置(没必要的话可以不做)
(4)进行视频采集,两种方法:
a.内存映射
b.直接从设备读取
(5)对采集的视频进行处理
(6)关闭视频设备。
为程序定义的数据结构
typedef struct v4l_struct
{
int fd;
struct video_capability capability;
struct video_channel channel[4];
struct video_picture picture;
struct video_window window;
struct video_capture capture;
struct video_buffer buffer;
struct video_mmap mmap;
struct video_mbuf mbuf;
unsigned char *map;
int frame;
int framestat[2];
}vd;
3.Video4linux支持的数据结构及其用途
(1) video_capability
包含设备的基本信息(设备名称、支持的最大最小分辨率、信号源信息等),包含的分量:
•name[32] //设备名称
•maxwidth ,maxheight,minwidth,minheight
•Channels //信号源个数
•type //是否能capture,彩色还是黑白,是否能裁剪等等。值如VID_TYPE_CAPTURE等
•
(2)video_picture 设备采集的图象的各种属性
•brightness 0~65535
•hue
•colour
•contrast
•whiteness
•depth // 24
•palette //VIDEO_PALETTE_RGB24
(3)video_channel 关于各个信号源的属性
Channel //信号源的编号
name
tuners
Type VIDEO_TYPE_TV | IDEO_TYPE_CAMERA
Norm制式
(4)video_window //包含关于capture area的信息
xx windows 中的坐标.
y x windows 中的坐标.
width The width of the image capture.
height The height of the image capture.
chromakey A host order RGB32 value for the chroma key.
flags Additional capture flags.
clips A list of clipping rectangles. (Set only)
clipcount The number of clipping rectangles. (Set only)
(5)video_mbuf //利用mmap进行映射的帧的信息
size //每帧大小
Frames //最多支持的帧数
Offsets //每帧相对基址的偏移
(6)video_buffer 最底层对buffer的描述
void *baseBase physical address of the buffer
int heightHeight of the frame buffer
int widthWidth of the frame buffer
int depthDepth of the frame buffer
int bytesperlineNumber of bytes of memory between the start of two
adjacent lines
实际显示的部分一般比它描述的部分小
(7)video_mmap //用于mmap
4.关键步骤介绍
(1)打开视频:
Open("/dev/video0",vdàfd);
关闭视频设备用close("/dev/video0",vdàfd);
(2)读video_capability 中信息
ioctl(vd->fd, VIDIOCGCAP, &(vd->capability))
成功后可读取vd->capability各分量 eg.
(3)读video_picture中信息
ioctl(vd->fd, VIDIOCGPICT, &(vd->picture));
(4)改变video_picture中分量的值 (可以不做的)
先为分量赋新值,再调用VIDIOCSPICT
Eg.
•vd->picture.colour = 65535;
•if(ioctl(vd->fd, VIDIOCSPICT, &(vd->picture)) < 0)
•{
•perror("VIDIOCSPICT");
•return -1;
•}
(5)初始化channel (可以不做的)
•必须先做得到vd->capability中的信息
•for (i = 0; i < vd->capability.channels; i++)
• {
• vd->channel[i].channel = i;
• if (ioctl(vd->fd, VIDIOCGCHAN, &(vd->channel[i])) < 0)
• {
• perror("v4l_get_channel:");
• return -1;
• }
• }
重点:截取图象的两种方法
1,用mmap(内存映射)方式截取视频
•mmap(
)系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。
•两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然
•采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝
(1)设置picture的属性
(2) 初始化video_mbuf,以得到所映射的buffer的信息
ioctl(vd->fd, VIDIOCGMBUF, &(vd->mbuf))
(3)可以修改video_mmap和帧状态的当前设置
• Eg. vd->mmap.format = VIDEO_PALETTE_RGB24
• vd->framestat[0] = vd->framestat[1] = 0; vd->frame = 0;
(4)将mmap与video_mbuf绑定
•void* mmap ( void * addr , size_t len , int prot , int flags , int fd ,
off_t offset )
•len
//映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起
•Prot //指定共享内存的访问权限 PROT_READ(可读), PROT_WRITE (可写),
PROT_EXEC (可执行)
•flags // MAP_SHARED MAP_PRIVATE中必选一个 // MAP_ FIXED不推荐使用addr
//共内存享的起始地址,一般设0,表示由系统分配
•Mmap( ) 返回值是系统实际分配的起始地址
•if((vd->map = (unsigned char*)mmap(0, vd->mbuf.size,
PROT_READ|PROT_WRITE, MAP_SHARED, vd->fd, 0)) < 0)
•{
•perror("v4l_mmap mmap:");
•return -1;
•}
(5)Mmap方式下真正做视频截取的 VIDIOCMCAPTURE
ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) ;
•若调用成功,开始一帧的截取,是非阻塞的,
•是否截取完毕留给VIDIOCSYNC来判断
(6)调用VIDIOCSYNC等待一帧截取结束
•if(ioctl(vd->fd, VIDIOCSYNC, &frame) < 0)
•{
•perror("v4l_sync:VIDIOCSYNC");
•return -1;
•}
若成功,表明一帧截取已完成。可以开始做下一次 VIDIOCMCAPTURE
•frame是当前截取的帧的序号。
****关于双缓冲:
•video_bmuf bmuf.frames = 2;
•一帧被处理时可以采集另一帧
•int frame; //当前采集的是哪一帧
•int framestat[2]; //帧的状态 没开始采集|等待采集结束
•帧的地址由vd->map + vd->mbuf.offsets[vd->frame]得到
•采集工作结束后调用munmap取消绑定
•munmap(vd->map, vd->mbuf.size)
2,视频截取的第二种方法:直接读设备
关于缓冲大小,图象等的属性须由使用者事先设置
•调用read();
•int read (要访问的文件描述符;指向要读写的信息的指针;应该读写的字符数);
•返回值为实际读写的字符数
•int len ;
•unsigned char *vd->map= (unsigned char *)
malloc(vdàcapability.maxwidth*vdàcapability.maxheight );
•len = read(vdàfd,vdà vd->map,
• vdàcapability.maxwidth*vdàcapability.maxheight*3 );
一个在LINUX下生成BMP的程序
GENERATOR,写了个比较简单的版本,仅针对24位真彩,现把代码公布
#include <iostream>
using namespace std;
typedef long BOOL;
typedef long LONG;
typedef unsigned char BYTE;
typedef unsigned long DWORD;
typedef unsigned short WORD;
typedef struct {
WORD bfType;//2
DWORD bfSize;//4
WORD bfReserved1;//2
WORD bfReserved2;//2
DWORD bfOffBits;//4
}__attribute__((packed))FileHead;
typedef struct{
DWORD biSize;//4
LONG biWidth;//4
LONG biHeight;//4
WORD biPlanes;//2
WORD biBitCount;//2
DWORD biCompress;//4
DWORD biSizeImage;//4
LONG biXPelsPerMeter;//4
LONG biYPelsPerMeter;//4
DWORD biClrUsed;//4
DWORD biClrImportant;//4
}__attribute__((packed))Infohead;
/*typedef struct
{
unsigned char rgbBlue;
unsigned char rgbGreen;
unsigned char rgbRed;
unsigned char rgbReserved;
}RGBQuad;//it may be useless*/
typedef struct
{
BYTE b;
BYTE g;
BYTE r;
}RGB_data;//RGB TYPE
int bmp_generator(char * filename,int width,int height,unsigned char *data)
{
FileHead bmp_head;
Infohead bmp_info;
int size = width*height*3;
//Test data
/* RGB_Test bmp_data[width][height];
int i,j;
for (i=0;i<width;i++)
for (j=0;j<height;j++)
{
bmp_data[i][j].g=bmp_data[i][j].b=0;
bmp_data[i][j].r=0xff;
}
*/
bmp_head.bfType=0x4d42;
bmp_head.bfSize=size+sizeof(FileHead)+sizeof(Infohead);//24+head+info
no quad
bmp_head.bfReserved1=bmp_head.bfReserved2=0;
bmp_head.bfOffBits=bmp_head.bfSize-size;
//finish the initial of head
bmp_info.biSize=40;
bmp_info.biWidth=width;
bmp_info.biHeight=height;
bmp_info.biPlanes=1;
bmp_info.biBitCount = 24;
bmp_info.biCompress=0;
bmp_info.biSizeImage=size;
bmp_info.biXPelsPerMeter=0;
bmp_info.biYPelsPerMeter=0;
bmp_info.biClrUsed=0;
bmp_info.biClrImportant=0;
//finish the initial of infohead;
//copy the data
// fstream file3(filename);
FILE *fp;
if(!(fp=fopen(filename,"wb"))) return 0;
//zhunan 2.6.00:58
fwrite(&bmp_head,1,sizeof(FileHead),fp);
fwrite(&bmp_info,1,sizeof(Infohead),fp);
fwrite(data,1,size,fp);
fclose(fp);
return 1;
}
int main(int argc,char **argv)
{
int i,j;
RGB_data buffer[512][512];
// cout<<"usage:bmp_generator width height"<<endl;
cout<<"programmed by zhunan"<<endl;
cout<<"API Guide:"<<endl;
cout<<"bmp_generator(char * filename,int width,int height,unsigned
char * data);"<<endl;
memset(buffer, 0, sizeof(buffer));
//please edit width and height here
//zhunan testdata
//hard coding
for (i=0;i<256;i++)
for (j=0;j<256;j++)
{
// buffer[i][j].g=buffer[i][j].b=0x00;
buffer[i][j].r=0xff;
}
bmp_generator("/home/zhunan/1234.bmp",512,512,(BYTE*)buffer);
return 1;
}
去交错(deinterlace)
因為裝置處理速度以及頻寬的限制下,廣播電視系統,例如NTSC或是PAL,都是使用交錯式訊號取代漸進式訊號。但是現代新型的顯示設備例如液晶顯示器、電漿顯示器、數位投影機或是數位微型反射鏡(DLP)等,都只支持逐行掃描(progressive
scan),因此在這些設備上需要有去交錯的功能以將交錯式訊號轉換為逐行信号。
描述
一個動態影像是由一連串連續的靜態影像所組成的,其中每一個靜態影像稱為幀(frame),而動態影像中每秒所包含靜態影像的數量則稱為幀(速)率(frame
per second, fps)。
而在顯示器上顯示動態影像的方式有兩種:
漸進掃描:或稱為逐行掃描。將每一幀從左至右、由上至下,逐一的將所有的畫素顯示出來。
交錯掃描:或稱為隔行掃描。將一幀圖像的奇數行畫素及偶數行畫素分開,分成為兩個場(field)。輪流掃描奇數行所構成的場及偶數行所構成的場。
因為一個場只有一個幀一半的資訊,因此在裝置處理速度無法即時的處理整個幀的資訊以及傳輸頻寬不夠即時傳輸整個幀的情形下,使用交錯掃描可以節省一
半的資訊量且可以為持相同的更新率。在以往陰極射線管顯示器(Cathode Ray
Tube,
CRT)很難一次掃描整個螢幕,因此無法使用漸進掃描。但是因為屏幕上螢光的餘暉加上視覺暫留效應,使得交錯掃描在陰極射線管顯示器上運作的相當順利。所
以廣播電視系統例如NTSC每秒59.94場,PAL則為每秒50場。
現在新式的顯示設備的速度已經夠快可以即時的處理且掃描整個幀,因此都是使用漸進掃描。但是在這些新型的顯示設備上直接播放交錯式影像會產生嚴重的
閃爍現象,且因為交錯式訊號兩行只有一行有影像另一行則是全黑的,所以亮度看起來會減少一半。由於有上述這些問題,所有使用漸進掃描的新式顯示設備都需要
有去交錯的功能。
去交錯方法
根據影像來源的不同,去交錯的方法可以分為以下兩類:
經過3:2
Pulldown後的電影:電影的拍攝是紀錄在底片上的,影像被紀錄在整張底片上,每秒24帧(Frame),因此電影是每秒24幀(24fps)的漸進
式影像。而3:2
Pulldown則是一個將每秒24幀的漸進式影像轉換為每秒60場的交錯式影像的程序,為的是將電影轉換為NTSC的規格,若為PAL或SCAEM規格
的電視則應轉為每秒50場。由於電影本身就是漸進式影像,因此若是我們經由NTSC電視收看一齣電影,我們是可以完美的將影像去交錯還原成原本的每秒24
幀的電影。
拍攝交錯式影像的攝影機:一般數位攝影機由於硬體速度及緩衝記憶體大小的限制,沒辦法連續的拍攝漸進式的影像,因此一般數位攝影機都是拍攝交錯式
影像,由於交錯式影像比漸進式影像少了一半的資訊量,因此可以降低硬體速度及緩衝記憶體大小的需求接近一半。但是每個場被拍攝的時間並不一樣,代表我們永
遠沒辦法完美的去交錯。例如有一台每秒拍攝六十個場的數位攝影機,第一個場是在1/60秒被拍攝的,而第二個場是在2/60秒被拍攝的,我們將兩個場結合
在一起,若是被拍攝的物體沒有任何移動,那麼結合出來的影像看起來是很完美的;但是如果被拍攝的物體有移動,兩個場的內容會有相當的不同,那麼結合出來的
影像會產生一種「鋸齒」的效果。
上圖為數位攝影機拍攝的交錯式影像的一個範例,這是兩個連續的場,每個場都只有一半的行有圖像。可以看的出來人物的動作及相對位置都有所不同,因此若我們直接將這兩個場結合在一起將產生「鋸齒」的效果。''
去交錯源自電影的影像
3:2
Pulldown是將每秒24幀的漸進式影像轉換為每秒60場的交錯式影像的程序;先將每個幀拆開成為兩個場,場A與場B;接下來以「第一幀的場A、第一
幀的場B、第一幀的場A、第二幀的場B、第二幀的場A、第三幀的場B、第三幀的場A、第三幀的場B、第四幀的場A、第四幀的場B」的順序排列,這樣四個幀
就可以拆解成十個場,而接下來的每四個幀也是像上面那個順序排列,這樣就能產生出每秒60場的交錯式影像。
而要將源自電影的交錯式影像去交錯是相當簡單的,只要去交錯的裝置偵測到第一個場與第三個場是一樣的,那麼去交錯裝置就會轉換到解3:2
Pulldown的模式。「只要將收到的前兩個場合併為一個幀,第三個場丟棄,第四個與第五個場合併成為第二個幀,第六個場丟棄,第七個與第八個場合併成
為第三個幀,第九個與第十個場合併成為第四個幀。」然後一直重複以上的順序就可以完美的重建原來的每秒24幀的電影影像。
去交錯交錯式影像
跟源自電影的影像不同的是,電影原本就是漸近式影像,因此可以完美的去交錯;但是原本就紀錄成交錯式影像,在之後無論用任何方法都無法完美的回覆失
去的一半資訊。在這裡去交錯的方法可以分為四大類,根據顯示器的大小、去交錯的時間以及價格因素,不同的去交錯裝置會在這些去交錯方法中選擇最適合的一
個。
單一場去交錯(intra-field deinterlacing)
這是種非常容易且非常節省資源的一種去交錯方法,通常只需要一行像素的緩衝記憶體以及簡單的內插。例如「line
doubling」,這是一種最常見的單一場去交錯方法,簡單來說就是將一個場放大成為一個幀的大小再播出,若是影像來源是每秒60場的交錯式影像,使用
這個去交錯方法將可以得到每秒60幀的漸進式影像。使用這個去交錯方法的好處為非常簡單且非常快速,硬體的成本將會非常低,但是缺點是畫質會看起來比較鬆
散,不銳利;且若是影像中含有橫向的細線,在某些場可能會剛好沒有被掃描到,因此重建出來的影像細線的部份看起來會有閃爍的感覺。
上圖是一個使用「line
doubling」去交錯方法的範例,使用簡單的內插演算法,看以看得出來畫質相當鬆散;若是改使用更複雜的內插演算法將可以提昇一些內插的品質。''
場間去交錯(inter-field deinterlacing)
場間去交錯就是將連續的兩個場結合為一個幀的方法。例如「weave」,他是將連續的兩個場直接結合成為一個幀,不做任何修改;由於在垂直方向保留
了全部的解析度(不像line
doubling只有一半的解析度),因此使用「weave」去交錯得到的畫質比使用「line
doubling」好,但是只有在畫面靜止不動的地方,在畫面有移動的地方會有明顯的橫向條紋以及鋸齒;若是連續的兩個場剛好是屬於影像中場景變換的部
份,那麼使用「weave」會發生將兩個不同場景合併成為一個幀的所謂鬼影的現象。另外使用「weave」去交錯將會把每秒60場的交錯式影像轉換為每秒
30幀的漸進式影像。場間去交錯方法需要一個場大小的緩衝記憶體,比起單一場去交錯方法所需要的略多,但硬體還是相當的簡單及便宜。
上圖是使用「weave」去錯方法的範例,在畫面靜止不動的地方畫質比起使用「line
doubling」去交錯銳利的多,例如在觀眾席的部份。但是在畫面中移動快速的網球選手身上出現了許多惱人的橫向線條,這是由於兩個場拍攝的時間不同所造成的現象。''
動態適應性去交錯(motion adaptive deinterlacing)
動態適應性去交錯方法是偵測影像中何處是動態的,以及何處是靜態的;在畫面中靜態的部份使用場間去交錯以得到垂直方向完整的解析度,而在動態的部份
使用單一場去交錯以避免鋸齒以及鬼影的現象。使用這個方法偵測動態的演算法是相當重要的,不好的演算法也會導致一些偵測錯誤使得畫面中出現一些惱人的線
條。使用動態適應性去交錯方法需要比較快速的硬體去計算動態偵測演算法,另外也需要一或多個場的緩衝記憶體,演算法使用越多的場來偵測動態將會越準確,但
是相對的需要更好更昂貴的硬體。
動態補償去交錯(motion compensated deinterlacing)
動態補償去交錯方法根據鄰近的場使用動態估計(motion
estimation)去預測鄰近的場之間畫面中物體的移動,藉由動態估計可以得到的畫面中每一個方塊(macroblock)的動態向量(motion
vector),然後使用前一個場以及動態向量可以重建出一個新的場,在將此兩個場合併完成去交錯。使用這個去交錯方法將會得到非常好的影像品質,因為這
個去交錯方法作了非常複雜且精準的預測;但是動態估計需要非常大量的計算,且也需要非常大的緩衝記憶體去暫存每個方塊估計的結果,這使得使用動態補償去交
錯方法的去交錯裝置非常的昂貴且速度緩慢,使得它目前無法應用在消費性產品以及有即時需求的顯示設備上。
去交錯的時機
一個影片從被拍攝到被使用者觀看經過了許多的程序與不同的途徑;最終使用者得到的影像品質會因為去交錯時間的不同而有所改變。
影片在電影公司、工作室或是發行商就進行去交錯,那麼將可以得到最好的品質,因為在這些地方有專業的人員以及足夠的經費與足夠的時間可以使用威力強大卻昂貴且緩慢的去交錯裝置,例如使用動態補償去交錯方法的裝置。
影片在被廣播時去交錯,那麼會有不錯的品質,因為電視公司通常會有專業的人員以及足夠的經費去購買昂貴的裝置,但是廣播必須是即時的,因此在這種情形下必須考慮去交錯裝置的執行速度,運作太慢的去交錯裝置將不能被考慮使用。
影片在個人電腦上使用軟體去交錯,最終的品質變動會相當大;現今有相當多的去交錯軟體可以被使用者使用,有些效果相當好且使用者在個人電腦上去交
錯並沒有即時的需求。但是大部分的去交錯方法的表現是與影像的內容相關的,一個在影片A運作相當好的去交錯方法在影片B卻不一定適用;而一般的使用者並沒
有足夠的知識去選擇去交錯的方法。
影片在消費性的電子產品上去交錯,產品的價錢將會決定去交錯的品質。在這些電子產品例如數位電視、DVD播放器等,去交錯必須是即時的,速度快效
果又好的去交錯裝置是很昂貴的,但是消費性電子商品上有嚴格的硬體價格限制,因此最終的品質是被價格所決定的。例如目前大部分的小尺寸顯示器都只使用了簡
單的單一場去交錯方法。
投资之道是:“三勤、三静、三淡、三乐”
一是"三勤"。即脑勤、手勤、脚勤。脑勤就是指投资者的头脑要勤于思考。勤思考才能辨是非、明事理,投资前多问自己几个为什么?得到的信息是真是伪?做到有的放矢;手勤就是指投资者要勤于动手,找到第一手材料,计算有关数据,切不可人云亦云;脚勤就是指投资者要迈开双脚,多到企业实地走一走,多到市场走一走,多到企业竞争对手的地方走一走。尽可能参加股东大会,多参加上市公司组织的投资者交流会。
二是"三静"即静心、静气、静行。静心就是指投资者在投资过程中要静下心来,面对市场的起伏,面对市值上下,心情始终保持平静,尽管做到这一点很难,但还是应由易而难循序渐进;静气就是指投资者在投资过程中不管发生何种情况,都应神气安静,切记患得患失神气紊乱;静行就是指投资者在投资道路上按部就班平静前行,不为邪门歪道吸引诱惑,不为所谓暴利滑入泥潭。
三是"三淡"即看淡权力、看淡金钱、淡忘年龄。看淡权力就是指不管投资者职位高低权力大小,都应把自己看成是普通投资者中的一员,自己并不比别人聪明,投资的成败与权力无关(腐败、违法除外),只有这样才能回归自我;看淡金钱就是指投资者千万别把赚钱当成投资的第一目标,投资的重中之重应该是学习投资的知识,掌握投资的技能,摸清投资的规律,赚钱只是投资成功的副产品。多少人一开始就把赚钱当成投资的第一目标,赚快钱、快赚钱成为投资的思考方式,结果离目标越来越远;淡忘年龄就是指投资者别太在意自己的年龄,投资的成败与年龄的高低不成正比。尤其是年长的投资者千万别倚老卖老,凭老经验办事,凭相当然下单。
四是"三乐"即要助人为乐、知足常乐、自得其乐。助人为乐就是指投资者在投资过程中,特别是有所收获后,要助人为乐,关心家人,资助族人,回报社会。只有这样才能使投资之路走得更远;知足常乐就是指投资者在投资生活中,要保持乐观向上的精神状态。面对暂时挫折切忌闷闷不乐,应多看看身边还有不少弱者,还有许多不如自己的人;自得其乐就是指投资者在生活中要自寻快乐,注重身体锻炼,保持良好的投资状态,自觉地把投资当作获取快乐的一种方式。
video for linux-----v4l v4l(VideoforLinux标准)
Video4Linux
其中用到的数据结构有:
2 -28,I was sitting just by them.
nothing romantic.what on hell happened?
◆ video_capability 包含摄像头的基本信息
设备名称 n a m e [ 3
2 ]
最大最小分辨率信 m a x w i d t h
号源信息 channels
/type
◆ video_picture 包含设备采集图象的各种属性
brightness(亮度)、
hue (色调)、
contrast(对比度)、
whiteness(色度)、
depth(深度)
◆ video_mmap 用于内存映射
◆ video_mbuf 利用mmap 进行映射的帧信息
v4l-----资料之与usb相关
Linux核心中,视频部分的标准是VideoforLinux(简称V4L)
标准定义了一套接口,
内核、驱动、应用程序以这个接口为标准进行交流
USB摄像头的驱动应当与内核提供的视频驱动挂钩
驱动和核心之间的通信过程:
声明-----------------指定-----------------调用---------------传递
声明video_device结构&&指定 文件操作 函数指针数组
(应用程序 发出 文件操作 的命令时)
核心 根据 指针 调用 相应函数
将该结构作为参数传递给应用程序
扩大URB的缓冲/建立两个URB(usb request block,简称urb)
(usb总线就像一条高速公路,数据可以看成是系统与设备交互的货物,而urb就可以看成是交通工具)
(endpoint有4种不同类型,于是能在这条高速公路上流动的数据也就有四种,但对车是没有要求的。)
(struct
urb的具体内容:要运什么,目的地是什么)
{
USB的基本特性:
每一个设备(device)会有一个或者多个的逻辑连接点在里面,每个连接点叫endpoint.每个endpoint有四种数据传送方式:控制(Control)方式传送;同步(isochronous)方式传送;中断(interrupt)方式传送;大量(bulk)传送.但是所有的endpoint都被用来传送配置和控制信息。在host和endpoint设备的之间的连接叫作管道"pipe"。
}
系统中/驱动中
核态、户态
在需要处理大批量数据的图像处理过程中,要用内存映射而不是拷贝的方式,实现系统调用。
首先使用vmalloc()申请足够大的核态内存,将其作为图像数据缓冲空间,两个URB带回的图像数据在这里暂存;
然后使用remap_page_range()函数将其逐页映射到用户空间中。
户态的图像处理程序使用mmap()函数,直接读写核态图像缓冲内存,大大减少额外开销。
这就是linux中usb相关的那个结构体
keep away from wine.
Linux的内核是用c来编写的。用结构化的思想分析问题。
usb_skeleton是USB的骨架。结构体名称是USB_SKEL.
struct usb_skel {
struct usb_device * udev; /* the usb device for this
device */
struct usb_interface * interface; /* the interface for this
device */
struct semaphore limit_sem; /* limiting the number of
writes in progress */
unsigned char * bulk_in_buffer; /* the buffer to
receive data */
size_t bulk_in_size; /* the size of the receive
buffer */
__u8 bulk_in_endpointAddr; /* the address of the bulk in
endpoint */
__u8 bulk_out_endpointAddr; /* the address of the bulk out
endpoint */
struct kref kref;
};
据说,USB的驱动分为两块,bus驱动 和 设备驱动
设备----配置(configuration)----接口(interface)----端点(endpoint)
通过端点进行数据交换,主机与端点之间建立起单向管道交换数据。
对于那篇文章的笔记
Linux内核用C语言编写,所以基本从面向对象和结构化的角度来实现。
定义的结构体 包含了驱动程序所有资源,即属性和成员。
我们所关心的
usb驱动或者规范是linux内核中的一部分,包括两个部分:一部分由Linux内核来实现,指的是当USB设备连接后,usb_core监测到设备的信息并确定调用什么驱动处理该设备。另一部分由我们实现,usb设备驱动,指的是当usb_core调用到我们写的驱动时,驱动开始工作。
这篇文章分成三个部分来叙述,1)usb的协议规范细节2)驱动框架3)源代码分析
2)驱动框架:
usb的设备驱动会被编译成模块,需要时被挂载到内核。
一个linux模块的例子:
#include<linux/init.h>
#include<linux/module.h>
MODULE_LICENSE("GPL")
//向linux内核告知该模块的版权信息
static int hello_init(void)
//初始化函数本身
{
printk(KERN_ALERT "hello world.\n");
return 0;
}
static int hello_exit(void)
//退出函数本身
{//never feel it this way.maybe ever have,but that's long ago.
printk(KERN_ALERT "GOODBYE\n");
}
module_init(hello_init);
//向内核注册模块的初始化函数
module_exit(hello_exit);
//向内核注册模块的退出函数
这个简单的例子说明了模块的写法。
要编译一个模块,需要用到内核源码树中的makefile
Makefile是用来进行项目配置和管理的。我们要把Linux编译、链接最后生成可执行的内核映像,Makefile文件是必不可少的。
一般的内核开发者只需要知道如何使用配置系统(除非是配置系统的维护者)无须了解配置系统的原理,只需要知道如何编写
Makefile 和配置文件。
1)USB的协议规范细节
从面向对象(OO)的角度,我们注意封装、继承等概念,同时要注重c语言中的结构化思想。
usb_skeleton
struct usb_skel{
struct usb_device* udev;
struct usb_interface* interface;
struct semaphore limit_sem;
unsigned char* bulk_in_buffer;
size_t bulk_in_size;
_u8 bulk_in_endpointAddr;
_u8 bulk_out_endpointAddr;
struct kref kref;
}
这个结构体描述的是该驱动所拥有的资源及状态。
结构体udev用来描述usb设备,semaphore
limit_sem用于访问控制,kref是一个内核使用的引用计数器。
据前面所述,usb设备有若干配置(configuration),每个配置又有多个接口(interface),接口本身可以没有端点或者有不止一个端点(endpoint)。
linux用结构体 usb_host_endpoint来描述USB端点(endpoint)
3)源码分析
static int_init usb_skel_init(void)
//初始化函数
{
int result;
result=usb_register(&skel_driver)
if(result)
err("usb_register failed.Error number %d",result);
return result;
}
static void_exit usb_skel_exit(void) //退出函数
{
usb_deregister(&skel_driver);
}
module_init(usb_skel_init);
//模块向内核注册初始化函数
module_exit(usb_skel_exit); //模块向内核注册退出函数
MODULE_LICENSE("GPL"); //版权信息
在这段程序中,有两三处关键字:usb_register(struct
*usb_driver),usb_deregister(struct *usb_driver),skel_driver;
其中,skel_driver是结构体usb_driver的一个实现。
前面提到的两个函数是用来注册和注销驱动程序
而结构体的作用是向系统提供函数入口、驱动的名字。
这个结构体是:
static struct usb_driver skel_driver={
.name="skeleton",
.probe=skel_probe,
.disconnect=skel_disconnect,
.id_table=skel_table,
};
------id_table 用来告诉内核 该模块支持的设备
#define USB_SKEL_VENDOR_ID oxfff0
#define USB_SKEL_PRODUCT_ID oxfff0
static struct usb_device_id skel_table[]={
{USB_DEVICE(USB_SKEL_VENDOR_ID,USB_SKEL_PRODUCT_ID)},
{}
//设备表的最后一个元素
};
MODULE_DEVICE_TABLE{usb,skel_table};
其中,最后一个函数的两个参数分别是:设备类型和设备表,而且设备表的最后一个元素是空的,用于表示结束。
------probe 是usb子系统自动调用的一个函数,当有usb设备接到硬件集线器时。
有待补充。
得到usb_device之后,
RGB与YUV图像视频格式的相互转换
通过本文您可以学习到如何把图像转换为电视视频格式,笔者以一张24位BMP图像为例实现RGB与YUV相互转换。如果您对图像转换成电视场制的视频格式有疑惑,相信本文能使您了解更多关于图像与视频格式转换的细节。
目录:
显示器图像显示概述
电视图像显示概述
RGB介绍
YUV介绍
隔行读取BMP
RGB转YUV
YUV转RGB
结束语
显示器图像显示概述:
我们知道普通彩色CRT显示器内部有三支电子枪,电子枪去激活显示器屏幕的荧光粉,三种荧光粉发射出的光生成一个像素位置的颜色点,这就是我们人眼能看到的一个像素。每个像素对应红、绿、蓝(R、G、B)三个强度等级,每个像素占用24位,可以显示近1700
万种颜色,这就是我们所说的真彩色。
普通彩色CRT显示器是基于电视技术的光栅扫描,电子束一次扫描一行,从顶到底依次扫描,整个屏幕扫描一次(我们称它为1帧),电子束扫描完一帧后回到最初位置进行下一次扫描。
电视图像显示概述:
电视显示原理与CRT相似,不过采用的是隔行扫描,我国的广播电视采用的是625行隔行扫描方式。隔行扫描是将一帧图像分两次(场)扫描。第一场先扫出1、3、5、7…等奇数行光栅,第二场扫出2、4、6、8…等偶数行光栅。通常将扫奇数行的场叫奇数场(也称上场),扫偶数行的场叫偶数场(也称下场)。为什么电视会选择隔行扫描,这是因为会使显示运动图像更平滑。下面两图为一帧图像的上场和下场的扫描过程。
(图1 上场扫描)
(图2 下场扫描)
常见的电视的制式有三种:NTSC、PAL、SECAM,我国的广播电视采用PAL制式,我国电视制式的帧频只有50HZ和我们日常使用的电流频率一样,PAL帧频为25fps,在文章后面我会以一张720x576的图像转换为720x
576 PAL隔行扫描的电视场视频格式作详细描述。
RGB介绍:
在记录计算机图像时,最常见的是采用RGB(红、绿,蓝)颜色分量来保存颜色信息,例如非压缩的24位的BMP图像就采用RGB空间来保存图像。一个像素24位,每8位保存一种颜色强度(0-255),例如红色保存为
0xFF0000。
YUV介绍:
YUV是被欧洲电视系统所采用的一种颜色编码方法,我国广播电视也普遍采用这类方法。其中"Y"表示明亮度(Luminance或Luma),也就是灰阶值;而"U"和"V"表示的则是色度(Chrominance或Chroma)。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。
隔行读取BMP:
下面我说明如何隔行读取BMP图像,为什么我以BMP图像来作演示,因为BMP可以说是最简单的一种图像格式,最容易用它说明原理,那公为什么要用BMP来演示隔行读取呢,因为要实现RGB转电视场制图像,首先就要知识如何隔行读取。
BMP图像颜色信息的保存顺序是由左到右,由下往上,您可以执行一下附带程序的
(功能菜单->读取RGB)
看到图像的读取和显示过程。代码首先依次显示奇数行像素,如(1,3,5,7,9….行),完成后再依次显示偶数行像素,代码实现如下:
// 隔行显示BMP
void CRGB2YUVView::OnReadBmp()
{
// TODO: Add your command handler code here
CDC *pDC = GetDC();
CRect rect;
CBrush brush(RGB(128,128,128));
GetClientRect(&rect);
pDC->FillRect(&rect, &brush);
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER bmih;
char strFileName[MAX_PATH]="720bmp.bmp";
CFile* f;
f = new CFile();
f->Open(strFileName, CFile::modeRead);
f->SeekToBegin();
f->Read(&bmfh, sizeof(bmfh));
f->Read(&bmih, sizeof(bmih));
// 分配图片像素内存
RGBTRIPLE *rgb;
rgb = new RGBTRIPLE[bmih.biWidth*bmih.biHeight];
f->SeekToBegin();
f->Seek(54,CFile::begin); // BMP 54个字节之后的是像素数据
f->Read(rgb, bmih.biWidth * bmih.biHeight * 3); //
这里只读24位RGB(r,g,b)图像
// 显示上场 (奇数行组成的奇数场)
for (int i = 0; i<bmih.biHeight; i++) {
for (int j = 0; j<bmih.biWidth; j++) {
if(!(i%2))
pDC->SetPixel(j, bmih.biHeight-i,
RGB(rgb[i*bmih.biWidth+j].rgbtRed,
rgb[i*bmih.biWidth+j].rgbtGreen,rgb[i*bmih.biWidth+j].rgbtBlue));
for (int k=0; k<1000; k++) ; //延时
}
}
Sleep(500);
// 显示下场 (偶数行组成的偶数场)
for (int i_ = 0; i_<bmih.biHeight; i_++) {
for (int j_ = 0; j_<bmih.biWidth; j_++) {
if(i_%2)
pDC->SetPixel(j_, bmih.biHeight-i_,
RGB(rgb[i_*bmih.biWidth+j_].rgbtRed,
rgb[i_*bmih.biWidth+j_].rgbtGreen,
rgb[i_*bmih.biWidth+j_].rgbtBlue));
for (int k=0; k<1000; k++) ; //延时
}
}
// 显示24位BMP信息
LONG dwWidth = bmih.biWidth;
LONG dwHeight = bmih.biHeight;
WORD wBitCount = bmih.biBitCount;
char buffer[80];
sprintf(buffer,"图像宽为:%ld 高为:%ld 像数位数:%d", dwWidth, dwHeight,
wBitCount);
MessageBox(buffer, "每个像素的位数", MB_OK | MB_ICONINFORMATION);
f->Close();
delete f;
delete rgb;
}
RGB转YUV
在整个视频行业中,定义了很多 YUV 格式,我以UYVY格式标准来说明,4:2:2
格式UYVY每像素占16 位,UYVY字节顺序如下图:
(图3 UYVY字节顺序)
其中第一个字节为U0,每二个字节为Y0,依次排列如下:
[U0,Y0,U1,Y1] [U1,Y2,V1,Y3] [U2,Y4,V2,Y5] ……
经过仔细分析,我们要实现RGB转YUV格式的话,一个像素的RGB占用三个节,而UYVY每像素占用两个字节,我演示直接把UYVY字节信息保存到*.pal格式中(这是我自己写来测试用的^_^),*.pal格式中,先保存上场像素,接着保存下场像素,如果是720x576的一张图像转换为YUV格式并保存的话,文件大小应该是829,440字节(720*576*2)。您可以执行本文附带的程序
(功能菜单->转换并写入YUV两场) 查看转换过程。
RGB转UYVY公式如下:
公式:(RGB => YCbCr)
Y = 0.257R′ + 0.504G′ + 0.098B′ + 16
Cb = -0.148R′ - 0.291G′ + 0.439B′ + 128
Cr = 0.439R′ - 0.368G′ - 0.071B′ + 128
代码实现:
// RGB转换为YUV
void CRGB2YUVView::RGB2YUV(byte *pRGB, byte *pYUV)
{
byte r,g,b;
r = *pRGB; pRGB++;
g = *pRGB; pRGB++;
b = *pRGB;
*pYUV = static_cast<byte>(0.257*r + 0.504*g + 0.098*b + 16); pYUV++;
// y
*pYUV = static_cast<byte>(-0.148*r - 0.291*g + 0.439*b + 128); pYUV++;
// u
*pYUV = static_cast<byte>(0.439*r - 0.368*g - 0.071*b + 128);
// v
}
像素转换实现:
// 转换RGB
void CRGB2YUVView::OnConvertPAL()
{
CDC *pDC = GetDC();
CRect rect;
CBrush brush(RGB(128,128,128));
GetClientRect(&rect);
pDC->FillRect(&rect, &brush);
// PAL 720x576 : 中国的电视标准为PAL制
int CurrentXRes = 720;
int CurrentYRes = 576;
int size = CurrentXRes * CurrentYRes;
// 分配内存
byte *Video_Field0 = (byte*)malloc(CurrentXRes*CurrentYRes);
byte *Video_Field1 = (byte*)malloc(CurrentXRes*CurrentYRes);
// 保存内存指针
byte *Video_Field0_ = Video_Field0;
byte *Video_Field1_ = Video_Field1;
byte yuv_y0, yuv_u0, yuv_v0, yuv_v1; // {y0, u0, v0, v1};
byte bufRGB[3]; // 临时保存{R,G,B}
byte bufYUV[3]; // 临时保存{Y,U,V}
// 初始化数组空间
ZeroMemory(bufRGB, sizeof(byte)*3);
ZeroMemory(bufYUV, sizeof(byte)*3);
// 初始化内存
ZeroMemory(Video_Field0, CurrentXRes*CurrentYRes);
ZeroMemory(Video_Field1, CurrentXRes*CurrentYRes);
// BMP 位图操作
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER bmih;
char strFileName[MAX_PATH]="720bmp.bmp";
CFile* f;
f = new CFile();
f->Open(strFileName, CFile::modeRead);
f->SeekToBegin();
f->Read(&bmfh, sizeof(bmfh));
f->Read(&bmih, sizeof(bmih));
// 分配图片像素内存
RGBTRIPLE *rgb;
rgb = new RGBTRIPLE[bmih.biWidth*bmih.biHeight];
f->SeekToBegin();
f->Seek(54,CFile::begin); // BMP 54个字节之后的是位像素数据
f->Read(rgb, bmih.biWidth * bmih.biHeight * 3); //
这里只读24位RGB(r,g,b)图像
// 上场 (1,3,5,7...行)
for (int i = bmih.biHeight-1; i>=0; i--) {
for (int j = 0; j<bmih.biWidth; j++) {
if(!(i%2)==0)
{
bufRGB[0] = rgb[i*bmih.biWidth+j].rgbtRed; // R
bufRGB[1] = rgb[i*bmih.biWidth+j].rgbtGreen; // G
bufRGB[2] = rgb[i*bmih.biWidth+j].rgbtBlue; // B
// RGB转换为YUV
RGB2YUV(bufRGB,bufYUV);
yuv_y0 = bufYUV[0]; // y
yuv_u0 = bufYUV[1]; // u
yuv_v0 = bufYUV[2]; // v
for (int k=0; k<1000; k++) ; //延时
// 视图中显示
pDC->SetPixel(j, (bmih.biHeight-1)-i, RGB(bufRGB[0], bufRGB[1],
bufRGB[2]));
// UYVY标准 [U0 Y0 V0 Y1] [U1 Y2 V1 Y3] [U2 Y4 V2 Y5]
// 每像素点两个字节,[内]为四个字节
if ((j%2)==0)
{
*Video_Field0 = yuv_u0;
Video_Field0++;
yuv_v1 = yuv_v0; // v保存起来供下一字节使用
}
else
{
*Video_Field0 = yuv_v1;
Video_Field0++;
}
*Video_Field0 = yuv_y0;
Video_Field0++;
}// end if i%2
}
}
// 下场 (2,4,6,8...行)
for (int i_ = bmih.biHeight-1; i_>=0; i_--) {
for (int j_ = 0; j_<bmih.biWidth; j_++) {
if((i_%2)==0)
{
bufRGB[0] = rgb[i_*bmih.biWidth+j_].rgbtRed; // R
bufRGB[1] = rgb[i_*bmih.biWidth+j_].rgbtGreen; // G
bufRGB[2] = rgb[i_*bmih.biWidth+j_].rgbtBlue; // B
// RGB转换为YUV
RGB2YUV(bufRGB,bufYUV);
yuv_y0 = bufYUV[0]; // y
yuv_u0 = bufYUV[1]; // u
yuv_v0 = bufYUV[2]; // v
for (int k=0; k<1000; k++) ; //延时
// 视图中显示
pDC->SetPixel(j_, (bmih.biHeight-1)-i_, RGB(bufRGB[0], bufRGB[1],
bufRGB[2]));
// UYVY标准 [U0 Y0 V0 Y1] [U1 Y2 V1 Y3] [U2 Y4 V2 Y5]
// 每像素点两个字节,[内]为四个字节
if ((j_%2)==0)
{
*Video_Field1 = yuv_u0;
Video_Field1++;
yuv_v1 = yuv_v0; // v保存起来供下一字节使用
}
else
{
*Video_Field1 = yuv_v1;
Video_Field1++;
}
*Video_Field1 = yuv_y0;
Video_Field1++;
}
}
}
// 关闭BMP位图文件
f->Close();
WriteYUV(Video_Field0_, Video_Field1_, size);
// 释放内存
free( Video_Field0_ );
free( Video_Field1_ );
delete f;
delete rgb;
}
YUV转RGB
关于YUV转换为RGB公式,我直接使用一篇文章提供的公式,经过思考,我发觉要想实现准确无误的把YUV转换为原有的RGB图像很难实现,因为我从UYVY的字节顺序来分析没有找到反变换的方法(您找到了记得告诉我哟:
liyingjiang@21cn.com
),例如我做了一个简单的测试:假设有六个像素的UYVY格式,要把这12个字节的UYVY要转换回18个字节的RGB,分析如下:
12个字节的UYVY排列方式:
[U0 Y0 V0 Y1] [U1 Y2 V1 Y3] [U2 Y4 V2 Y5]
完全转换为18个字节的RGB所需的UYVY字节排列如下:
[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3] [Y4 U4 V4] [Y5 U5 V5]
我们可以看到,12个字节的UYVY无法实现,缺少U3 V3 U4
V4。于是我抛开准确无误地把UYVY转换回RGB的想法,直接使用最近的UV来执行转换,结果发觉转换回来的RGB图像用肉眼根本分辩不出原有RGB图像与反变换回来的RGB图像差别,您可以执行本文附带的程序
(功能菜单->读取YUV并显示) 查看效果,下面是反变换公式和代码的实现:
// 反变换公式
R= 1.0Y + 0 +1.402(V-128)
G= 1.0Y - 0.34413 (U-128)-0.71414(V-128)
B= 1.0Y + 1.772 (U-128)+0
代码实现:
void CRGB2YUVView::YUV2RGB(byte *pRGB, byte *pYUV)
{
byte y, u, v;
y = *pYUV; pYUV++;
u = *pYUV; pYUV++;
v = *pYUV;
*pRGB = static_cast<byte>(1.0*y + 8 + 1.402*(v-128));
pRGB++; // r
*pRGB = static_cast<byte>(1.0*y - 0.34413*(u-128) - 0.71414*(v-128));
pRGB++; // g
*pRGB = static_cast<byte>(1.0*y + 1.772*(u-128) +
0); // b
}
// 读取PAL文件转换为RGB并显示
void CRGB2YUVView::OnReadPAL()
{
// TODO: Add your command handler code here
CDC *pDC = GetDC();
CRect rect;
CBrush brush(RGB(128,128,128));
GetClientRect(&rect);
pDC->FillRect(&rect, &brush);
// PAL 720x576 : 中国的电视标准为PAL制
int CurrentXRes = 720;
int CurrentYRes = 576;
int size = CurrentXRes * CurrentYRes;
// 分配内存
byte *Video_Field0 = (byte*)malloc(CurrentXRes*CurrentYRes);
byte *Video_Field1 = (byte*)malloc(CurrentXRes*CurrentYRes);
// 保存内存指针
byte *Video_Field0_ = Video_Field0;
byte *Video_Field1_ = Video_Field1;
// 初始化内存
ZeroMemory(Video_Field0, CurrentXRes*CurrentYRes);
ZeroMemory(Video_Field1, CurrentXRes*CurrentYRes);
byte yuv_y0, yuv_u0, yuv_v0; // yuv_v1; // {y0, u0, v0, v1};
byte r, g, b;
byte bufRGB[3]; // 临时保存{R,G,B}
byte bufYUV[3]; // 临时保存{Y,U,V}
// 初始化数组空间
memset(bufRGB,0, sizeof(byte)*3);
memset(bufYUV,0, sizeof(byte)*3);
char strFileName[MAX_PATH]="720bmp.pal";
// 分配图片像素内存
RGBTRIPLE *rgb;
rgb = new RGBTRIPLE[CurrentXRes*CurrentYRes];
memset(rgb,0, sizeof(RGBTRIPLE)*CurrentXRes*CurrentYRes); //
初始化内存空间
CFile* f;
f = new CFile();
f->Open(strFileName, CFile::modeRead);
f->SeekToBegin();
f->Read(Video_Field0, CurrentXRes*CurrentYRes);
f->Read(Video_Field1, CurrentXRes*CurrentYRes);
// 上场 (1,3,5,7...行)
for ( int i = CurrentYRes-1; i>=0; i--) {
for ( int j = 0; j<CurrentXRes; j++) {
if(!(i%2)==0)
{
// UYVY标准 [U0 Y0 V0 Y1] [U1 Y2 V1 Y3] [U2 Y4 V2 Y5]
// 每像素点两个字节,[内]为四个字节
if ((j%2)==0)
{
yuv_u0 = *Video_Field0;
Video_Field0++;
}
else
{
yuv_v0 = *Video_Field0;
Video_Field0++;
}
yuv_y0 = *Video_Field0;
Video_Field0++;
bufYUV[0] = yuv_y0; // Y
bufYUV[1] = yuv_u0; // U
bufYUV[2] = yuv_v0; // V
// RGB转换为YUV
YUV2RGB(bufRGB,bufYUV);
r = bufRGB[0]; // y
g = bufRGB[1]; // u
b = bufRGB[2]; // v
if (r>255) r=255; if (r<0) r=0;
if (g>255) g=255; if (g<0) g=0;
if (b>255) b=255; if (b<0) b=0;
for (int k=0; k<1000; k++) ; //延时
// 视图中显示
pDC->SetPixel(j, CurrentYRes-1-i, RGB(r, g, b));
}// end if i%2
}
}
// 下场 (2,4,6,8...行)
for ( int i_ = CurrentYRes-1; i_>=0; i_--) {
for ( int j_ = 0; j_<CurrentXRes; j_++) {
if((i_%2)==0)
{
// UYVY标准 [U0 Y0 V0 Y1] [U1 Y2 V1 Y3] [U2 Y4 V2 Y5]
// 每像素点两个字节,[内]为四个字节
if ((j_%2)==0)
{
yuv_u0 = *Video_Field1;
Video_Field1++;
}
else
{
yuv_v0 = *Video_Field1;
Video_Field1++;
}
yuv_y0 = *Video_Field1;
Video_Field1++;
bufYUV[0] = yuv_y0; // Y
bufYUV[1] = yuv_u0; // U
bufYUV[2] = yuv_v0; // V
// RGB转换为YUV
YUV2RGB(bufRGB,bufYUV);
r = bufRGB[0]; // y
g = bufRGB[1]; // u
b = bufRGB[2]; // v
if (r>255) r=255; if (r<0) r=0;
if (g>255) g=255; if (g<0) g=0;
if (b>255) b=255; if (b<0) b=0;
for (int k=0; k<1000; k++) ; //延时
// 视图中显示
pDC->SetPixel(j_, CurrentYRes-1-i_, RGB(r, g, b));
}
}
}
// 提示完成
char buffer[80];
sprintf(buffer,"完成读取PAL文件:%s ", strFileName);
MessageBox(buffer, "提示信息", MB_OK | MB_ICONINFORMATION);
// 关闭PAL电视场文件
f->Close();
// 释放内存
free( Video_Field0_ );
free( Video_Field1_ );
delete f;
delete rgb;
}
结束语:
通过阅读本文,希望能让您理解一些RGB及YUV转换的细节,其实在一些开发包里已经提供了一些函数实现转换,本文只在于说明转换原理,没有对代码做优化,也没有对读取和写入格式做一些异常处理,希望您能体凉。YUV的格式非常多且复杂,本文只以UYVY为例,希望能起到抛砖引玉的作用。写本文之前笔者阅读了不少相关的文章不能一一列出,在此对他们无私的把自己的知识拿出来共享表示感谢。本文所带源码您可以直接到我的个人网站下载http://www.cgsir.com
。另外本人专门写了一个AVI转换为YUV视频格式的工具,如果您有需要,可以直接到我个人网站下载或直接与我联系。
基于嵌入式linux和s32410平台的视频采集续
Linux操作系统的宿主机PC机上进行的,下面是具体论述。
(1)程序中定义的数据结构
struct voide_capability grab_cap;
struct voide_picture grab_pic;
struct voide_mmap grab_buf;
struct voide_mbuf grab_vm;
这些数据结构都是由Video4Linux支持的,它们的用途如下:
*video_capability包含摄像头的基本信息,例如设备名称、支持的最大最小分辨率、信号源信息等,分别对应着结构体中成员变量
name[32]、maxwidth、maxheight、minwidth、minheight、channels(信号源个数)、type等;
*voide_picture包含设备采集图像的各种属性,如brightness(亮度)、hue(色调)、contrast(对比度)、whiteness(色度)、depth(深度)等;
*video_mmap用于内存映射;
*voido_mbuf利用mmap进行映射的帧信息,实际上是输入到摄像头存储器缓冲 中的帧信息,包括size(帧的大小)、frames(最多支持的帧数)、offsets(每帧相对基址的偏移)。
程序中用到的主要系统调用函数有:open("/dev/voideo0",int
flags)、close(fd)、mmap(void *start,size_t length,int prot,int flags,int
fd,off_t offset)、munmap(void *start,size_tlength)和ioctl(int fd,int
cmd,…)。
前面提到Linux系统中把设备看成设备文件,在用户空间可以通过标准的I/O系统调用函数操作设备文件,从而达到与设备通信交互的目的。当然,在设备驱动中要提供对这些函数的相应支持。这里说明一下ioctl(int
fd,int
cmd,…)函数,它在用户程序中用来控制I/O通道,其中,fd代表设备文件描述符,cmd代表用户程序对设备的控制命令,省略号一般是一个表示类型长度的参数,也可没有。
(2)采集程序实现过程
首先打开视频设备,摄像头在系统中对应的设备文件为/dev/video0,采用系统调用函数grab_fd=open
("/dev/video0",O_RDWR),grab_fd是设备打开后返回的文件描述符(打开错误返回-1),以后的系统调用函数就可使用它来对设备文件进行操作了。接着,利用ioct1(grab_fd,VIDIOCGCAP,&grab_cap)函数读取struct
video_capability中有关摄像头的信息。该函数成功返回后,这些信息从内核空间拷贝到用户程序空间grab_cap各成员分量中,使用
printf函数就可得到各成员分量信息,例如printf("maxheight=%d",grab_fd.maxheight)获得最大垂直分辨率的大小。不规则用ioct1(grab_fd,VIDIOCGPICT,&grab_pic)函数读取摄像头缓冲中voideo_picture信息。在用户空间程序中可以改变这些信息,具体方法为先给分量赋新值,再调用VIDIOCSPICT
ioct1函数,例如:
grab_fd.depth=3;
if(ioct1(grab_fd,VIDIOCSPICT,&grab_pic)<0)
{perror("VIDIOCSPICT");return -1;};
完成以上初始化设备工作后,就可以对视频图像截取了,有两种方法:一种 是read()直接读取;另外一种mmap()内存映射。Read
()通过内核缓冲区来读取数据;而mmap()通过把设备文件映射到内存中,绕过了内核缓冲区,最快的磁盘访问往往还是慢于最慢的内存访问,所以mmap
()方式加速了I/O访问。另外,mmap()系统调用使得进程之间通过映射同一文件实现共享内存,各进程可以像访问普通内存一样对文件进行访问,访问时只需要使用指针而不用调用文件操作函数。因为mmap()的以上优点,所以在程序实现中采用了内存映射方式,即mmap()方式。
利用mmap()方式视频裁取具体进行操作如下。
①先使用ioct1(grab_fd,VIDIOCGMBUF,&grab_vm)函数获得摄像头存储缓冲区的帧信息,之后修改voideo_mmap中的设置,例如重新设置图像帧的垂直及水平分辨率、彩色显示格式。可利用如下语句
grab_buf.height=240;
grab_buf.width=320;
grab_buf.format=VIDEO_PALETTE_RGB24;
②接着把摄像头对应的设备文件映射到内存区,具体使用grab_data=(unsigned
char*)mmap(0,grab_vm.size,PROT_READ|PROT_WRITE,MAP_SHARED,grad_fd,0)操作。这样设备文件的内容就映射到内存区,该映射内容区可读可写并且不同进程间可共享。该函数成功时返回映像内存区的指针,挫败时返回值为-1。
下面对单帧采集和连续帧采集进行说明:
*单帧采集。在上面获取的摄像头存储缓冲区帧信息中,最多可支持的帧数(frames的值)一般为两帧。对于单帧采集只需设置
grab_buf.frame=0,即采集其中的第一帧,使用ioctl(grab_fd,VIDIOCMCAPTURE,&grab_buf)
函数,若调用成功,则激活设备真正开始一帧图像的截取,是非阻塞的。接着使用ioct1(grab_fd,VIDIOCSYNC,&frame)
函数判断该帧图像是否截取完毕,成功返回表示截取完毕,之后就可把图像数据保存成文件的形式。
*连续帧采集。在单帧的基础上,利用grab_fd.frames值确定采集完毕摄像头帧缓冲区帧数据进行循环的次数。在循环语句中,也是使用
VIDIOCMCCAPTURE ioct1和VIDIOCSYNC
ioctl函数完成每帧截取,但要给采集到的每帧图像赋地址,利用语句buf=grab_data+grab_vm.offsets[frame],然后保存文件的形式。若要继续采集可再加一个外循环,在外循环语句只要给原来的内循环再赋frame=0即可。
4 小结
笔者最后在宿主机PC上使用交叉编译器编译链接连续帧采集程序(以双帧采集为例并保存成bmp文件文件形式)使之生成可执行代码,并完成了向目标平台的移植。为了进一步观察采集的图像效果,笔者在目标平台带网络支持的基础上,编写了一个简单的网络通信程序,把采集到并保存为bmp的图像文件通过网络传输到PC机上进行显示,把采集到并保存为bmp的图像文件通过网络传输到PC机上进行显示,通过对效果的分析,再回到采集程序中重新设置
video_picture中的信息,如亮度、对比度等和voide_mmap中的分辨率,重新移植以达到最好效果为准。
嵌入式系统平台上,应用本文所述方法完成视频采集工作,再加上相关的视频处理并接入网络,就构成了一个智能终端设备,可用于工厂、银行及小区等场合全天候的智能监控,具有广阔的市场和应用前景.
Converting 8-bit YUV to RGB888
Copy Code
C = Y - 16
D = U - 128
E = V - 128
Using the previous coefficients and noting that clip() denotes clipping a
value to the range of 0 to 255, the following formulas provide the
conversion from YUV to RGB:
Copy Code
R = clip(( 298 * C + 409 * E + 128) >> 8)
G = clip(( 298 * C - 100 * D - 208 * E + 128) >> 8)
B = clip(( 298 * C + 516 * D + 128) >> 8)
These formulas use some coefficients that require more than 8 bits of
precision to produce each 8-bit result, and intermediate results require
more than 16 bits of precision.
Note All units range from 0 (zero) to 1.0 (one). In DirectDraw, they
range from 0 to 255. Overflow and underflow can (and does) occur, and the
results must be saturated.
To convert 4:2:0 or 4:2:2 YUV to RGB, convert the YUV data to 4:4:4 YUV,
and then convert from 4:4:4 YUV to RGB.
video4linux笔记
2。 号称是支持multiple
opens,当然前提是这多个open最好不要是在都要进行数据传输的情况下的,比如一个process可以控制它的一些参数,一个process在进行在线的video
recoding。
3。 Shared Data Streams
数据流的共享,建议是在应用级去实现,在底层我们用一个proxy单独去管理获取数据,再上层,多个人去与proxy通信,达到数据流的共享。与底层透明。
4。主要使用的操作就是open, close, ioctl
5. Querying Capabilities
虽然这是个标准,但不是强制性的,因此不同的设备对功能的支持不同,所以地提供一个功能查询机制,而这个功能查询机制应该是必需的。All
V4L2 drivers must support VIDIOC_QUERYCAP. Applications should always call
this ioctl after opening the device
6。Application Priority
既然允许multiple
opens,那么不同的任务之间应该有个优先级,来处理一些具有conflict的情况,可以使用
VIDIOC_S_PRIORITY和VIDIOC_G_PRIORITY
的ioctl操作分别来设置和查询当前任务的优先级。据个例子,如果你要通过
VIDIOC_S_INPUT修改driver的属性,你可能由于已经存在了另一个具有较高优先级的任务而导致,你这次修改失败,你总不能把高优先级的任务影响了吧。
7。Video Inputs and Outputs
一个device可以接好多个connectors,如RGB,DVI, CVBS,
S-Video接口,因此它有多个输入或输出,那么有几个可用,当前在用哪个?他是什么类型呢?这些都是可以查看的。下面是CVBS的概念:
中文解释:复合视频广播信号 或 复合视频消隐和同步
全称:Composite Video Broadcast Signal 或Composite Video Blanking and Sync
它是的一个模拟电视节目(图片)信号在与声音信号结合,并调制到射频载波之前的一种格式。
CVBS是"Color, Video, Blank and Sync", "Composite Video Baseband Signal",
"Composite Video Burst Signal", or "Composite Video with Burst and
Sync".的缩写
CVBS
是被广泛使用的标准,也叫做基带视频或RCA视频,是全国电视系统委员会(NTSC)电视信号的传统图像数据传输方法,它以模拟波形来传输数据。复合视频包含色差(色调和饱和度)和亮度(光亮)信息,并将它们同步在消隐脉冲中,用同一信号传输。
在快速扫描的NTSC电视中,甚高频(VHF)或超高频(UHF)载波是复合视频所使用的调整振幅,这使产生的信号大约有6MHz宽。一些闭路电视系统使用同轴电缆近距离传输复合视频,一些DVD播放器和视频磁带录像机(VCR)通过拾音插座提供复合视频输入和输出,这个插座也叫做RCA连接器。
复合视频中,色差和亮度信息的干涉是不可避免的,特别是在信号微弱的时候。这就是为何远距离的使用VHF或UHF的NTFS电视台用老旧的鞭形天线,"兔子耳朵",或世外的"空中"经常包含假的或上下摇动的颜色。CVBS又叫RCA是一种比较低级的模拟信号的传输标准,用一条线传输视频,亮度和色度混合,失真严重,我们常看到的三接头的电视中的黄色接头就是CVBS接头,其他两个对应2个声道的音频。注意,色度应该是可以分离出色差和饱和度信号的,具体有待进一步研究。
言归正传,我们可以通过VIDIOC_ENUMINPUT and VIDIOC_ENUMOUTPUT
分别列举一个input或者output的信息,我们使用一个v4l2_input结构体来乘放查询结果,这个结构体中有一个index域用来指定你索要查询的是第几个input/ouput,如果你所查询的这个input是当前正在使用的,那么在v4l2_input还会包含一些当前的状态信息,如果所查询的input/output不存在,那么回返回EINVAL错误,所以,我们通过循环查找,直到返回错误来遍历所有的input/output.
VIDIOC_G_INPUT and VIDIOC_G_OUTPUT 返回当前的video input和output的index.
Example 1-1. Information about the current video input
struct v4l2_input input; int index; if (-1 == ioctl (fd, VIDIOC_G_INPUT,
&index)) { perror ("VIDIOC_G_INPUT"); exit (EXIT_FAILURE); } memset
(&input, 0, sizeof (input)); input.index = index; if (-1 == ioctl (fd,
VIDIOC_ENUMINPUT, &input)) { perror ("VIDIOC_ENUMINPUT"); exit
(EXIT_FAILURE); } printf ("Current input: %s\n", input.name);
Example 1-2. Switching to the first video input
int index; index = 0; if (-1 == ioctl (fd, VIDIOC_S_INPUT, &index)) {
perror ("VIDIOC_S_INPUT"); exit (EXIT_FAILURE); }
Video standards
当然世界上现在有多个视频标准,如NTSC和PAL,他们又细分为好多种,那么我们的设备输入/输出究竟支持什么样的标准呢?我们的当前在使用的输入和输出正在使用的是哪个标准呢?我们怎么设置我们的某个输入输出使用的标准呢?这都是有方法的。
(1)查询,我们的输入支持什么标准,首先就得找到当前的这个输入的index,然后查出它的属性,在其属性里面可以得到该输入所支持的标准,将它所支持的各个标准与所有的标准的信息进行比较,就可以获知所支持的各个标准的属性。一个输入所支持的标准应该是一个集合,而这个集合是用bit与的方式使用一个64位数字表示的。因此我们所查到的是一个数字。
Example 1-5. Information about the current video standard
v4l2_std_id std_id; //这个就是个64bit得数 struct v4l2_standard standard;
//VIDIOC_G_STD就是获得当前输入使用的standard,不过这里只是得到了该标准的id即flag,还没有得到其具体的属性信息,具体的属性信息要通过列举操作来得到。
if (-1 == ioctl (fd, VIDIOC_G_STD, &std_id)) {
//获得了当前输入使用的standard /* 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 (EXIT_FAILURE); } memset (&standard, 0, sizeof (standard));
standard.index = 0; //从第一个开始列举
//VIDIOC_ENUMSTD用来列举所支持的所有的video标准的信息,不过要先给standard结构的index域制定一个数值,所列举的标准的信息属性包含在standard
里面,如果我们所列举的标准和std_id有共同的bit,那么就意味着这个标准就是当前输入所使用的标准,这样我们就得到了当前输入使用的标准的属性信息
while (0 == ioctl (fd, VIDIOC_ENUMSTD, &standard)) { if (standard.id &
std_id) { printf ("Current video standard: %s\n", standard.name); exit
(EXIT_SUCCESS); } standard.index++; } /* EINVAL indicates the end of the
enumeration, which cannot be empty unless this device falls under the USB
exception. */ if (errno == EINVAL || standard.index == 0) { perror
("VIDIOC_ENUMSTD"); exit (EXIT_FAILURE); }
Example 1-6. Listing the video standards supported by the current input
struct v4l2_input input; struct v4l2_standard standard; memset (&input, 0,
sizeof (input));
//首先获得当前输入的index,注意只是index,要获得具体的信息,就的调用列举操作
if (-1 == ioctl (fd, VIDIOC_G_INPUT, &input.index)) { perror
("VIDIOC_G_INPUT"); exit (EXIT_FAILURE); }
//调用列举操作,获得input.index对应的输入的具体信息 if (-1 == ioctl (fd,
VIDIOC_ENUMINPUT, &input)) { perror ("VIDIOC_ENUM_INPUT"); exit
(EXIT_FAILURE); } printf ("Current input %s supports:\n", input.name);
memset (&standard, 0, sizeof (standard)); standard.index = 0;
//列举所有的所支持的standard,如果standard.id与当前input的input.std有共同的bit
flag,意味着当前的输入支持这个standard,这样将所有驱动所支持的standard列举一个遍,就可以找到该输入所支持的所有
standard了。 while (0 == ioctl (fd, VIDIOC_ENUMSTD, &standard)) { if
(standard.id & input.std) printf ("%s\n", standard.name);
standard.index++; } /* EINVAL indicates the end of the enumeration, which
cannot be empty unless this device falls under the USB exception. */ if
(errno != EINVAL || standard.index == 0) { perror ("VIDIOC_ENUMSTD"); exit
(EXIT_FAILURE); }
Example 1-7. Selecting a new video standard
struct v4l2_input input; v4l2_std_id std_id; memset (&input, 0, sizeof
(input)); //获得当前input的index if (-1 == ioctl (fd, VIDIOC_G_INPUT,
&input.index)) { perror ("VIDIOC_G_INPUT"); exit (EXIT_FAILURE); }
//列举出下标为input.index的input的属性到input里 if (-1 == ioctl (fd,
VIDIOC_ENUMINPUT, &input)) { perror ("VIDIOC_ENUM_INPUT"); exit
(EXIT_FAILURE); } //如果该input所支持的标准里不包含V4L2_STD_PAL_BG,就退出
if (0 == (input.std & V4L2_STD_PAL_BG)) { fprintf (stderr, "Oops. B/G PAL
is not supported.\n"); exit (EXIT_FAILURE); } /* Note this is also
supposed to work when only B or G/PAL is supported. */ std_id =
V4L2_STD_PAL_BG;
//如果当前input支持V4L2_STD_PAL_BG,就将其设置为V4L2_STD_PAL_BG if (-1 ==
ioctl (fd, VIDIOC_S_STD, &std_id)) { perror ("VIDIOC_S_STD"); exit
(EXIT_FAILURE); }
1。User
controlls其实就是一些用户可以用来进行设置的一些属性,如视频中的brightness等,video4linux就提取出了最常见的一些设置,给他们分配了ID,这样大家对于这些常见的设置,就是用这些ID就可以了,可以察看当前设备对该设置的值,也可以给该设置新值,此外,由于某些设置包含很多子设置项,因此就又有了menu的含义,即对于一个具体的control,我们在列举他的属性时,发现其类型是包含了menu的,那么我们就可以以这个control的id为参数,察看其menu及各自的值。当然用户可以由自定义的control以及extended
control。好像是Camera Control ID中就有可以设置focus聚焦的control
id,这个可以看一看。
2。Data format
应用是可以和device针对通信的数据进行谈判的,即可以设置device所使用的数据的格式,可以获得设备所使用的数据的格式,也可以尝试一下某种格式的数据设备是否支持。使用
VIDIOC_G_FMT and VIDIOC_S_FMT ioctls,而VIDIOC_TRY_FMT
就是用来试一下某设置是否被设备支持,而且只是测试,并不会起作用。我们还是可以用VIDIOC_ENUM_FMT来列举设备所支持的所有的image的格式的。关于数据格式,在video中就会涉及到image的格式,大小(宽度,高度),等信息。
3. crapping和scaling
就是把得到的数据作一定的剪裁,和伸缩,剪裁可以只取样我们可以得到的图像大小的一部分,剪裁的主要参数是位置和长度以及宽度,而scale的设置是通过VIDIOC_G_FMT
and VIDIOC_S_FMT 来获得和设置当前的image的长度,宽度来实现的。看下图
我们可以假设bounds是最大的能捕捉到的图像范围,defrect是我们的设备能够得到的最大的范围,这个可以通过VIDIOC_CROPCAP的ioctl来获得设备的crap相关的属性
v4l2_cropcap,其中的bounds就是这个bounds,其实就是上限。每个设备都有个默认的取样范围,就是defrect,就是default
rect的意思,它比bounds要小一些。这个范围也是通过VIDIOC_CROPCAP的ioctl来获得的
v4l2_cropcap结构中的defrect来表示的,我们可以通过 VIDIOC_G_CROP and
VIDIOC_S_CROP 来获取和设置设备当前的crop设置。
问题:难道设置VIDIOC_S_FMT只是设置了伸缩?即到的图像还是一样的?具体拍的哪部分要依靠设置crop的设置?
如何在LINUX下处理bmp位图
3.0中的画笔(Paintbrush)工具软件为用户提供了强有力的图形绘制和编辑功能,例如图形的旋转、缩放、拼接等[1].利用这些功能可以非常方便地生成所需要的各种复杂的彩色画面.生成的画面以位图文件的格式存储在磁盘上.以此文件为资源,在Mircosoft
Windows 3.0的支持下,可以在任何需要的时候在任何设备上再现该画面[2].
在实际工作中经常遇到这样的问题:在利用画笔工具软件生成了所需的画面之后,能否脱离开Windows的支持,在其它系统中使用这些画面呢?解决这一问题的关键在于了解位图文件的记录格式.只要了解位图文件的记录格式,这一问题是容易解决的.基于这一目的,文本将详细介绍Mircosoft
Windows 3.0位图文件的记录格式.
Microsoft Windows 3.0位图文件(以.BMP为扩展名)由以下三个部分组成:
* 位图文件头(BITMAPFILEHEADER) 数据结构
* 位图信息(BITMAPINFO)数据结构
* 位图阵列
一. 位图文件头的结构
位图文件头数据结构含有位图文件的类型、大小和打印格式等信息.在Windows.h中对其进行了定义:
Typedef struct tagBITMAPFILEHEADER
{
WORD bfType; /*位图文件的类型,必须为BM. */
DWORD bfSize; /*位图文件的大小,以字节为单位. */
WORD bfReserved1; /*位图文件保留字,必须为0. */
WORD bfReserved2; /* 位图文件保留字,必须为0. */
DWORD bfoffbits; /*位图阵列的起始位置,以相对于位图文件*/
/*头的偏移量表示,以字节为单位.*/
}BITMAPFILEHEADER;
二. 位图信息的结构
位图信息数据结构含有位图文件的尺寸和颜色等信息.在Windows.h中对其进行了定义:
typedef stuc tagBITMAPINFO
{
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColor[];
}BITMAPINFO;
1.bmiHeader是一个位图信息头(BIMMAPINFOHEADER)类型的数据结构,用于说明位图的尺寸.
BITMAPINFOHEADER的定义:
typedef struct tagBITMAPINFOHEADER
{
DWORD BiSize; /*bmiHeader的长度,以字节为单位.*/
DWORD biWidth; /*位图的宽度,以象素为单位.*/
DWORD biHight; /*位图的高度,以象素为单位.*/
WORD biPlanes; /*目标设备的级别,必须为1.*/
WORD biBitCount; /*每个象素所需的位数,必须是1(单色).*/
/*4(16色),8(256色),或24(2^24色)之一. */
DWORD biCompress; /*位图的压缩类型,必须是0(不压缩).*/
/*1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一.*/
DWORD biSizeImage; /*位图的大小,以字节为单位.*/
DOWRD biXPeIsPerMeter;
/*位图的目标设备水平分辨率,以每米象素数为单位.*/
DWORD biYPeIsPerMeter;
/*位图的目标设备水平分辨率,以每米象素数为单位.*/
DWORD biCIrUsed;
/*位图实际使用的颜色表中的颜色变址数,详见[3].*/
DWORD biCIrImprotant;
/*位图显示过程中被认为重要颜色的变址数,详见[3]*/
}BITMAPINFOHEADER;
2.
bimColor[]是一个颜色表,用于说明位图中的颜色.它有若干个表项,每一表项是一个RGBQUAD类型的结构,定义了一种颜色.RGBQUAD的定义:
Typedef tagRGBQUAD
{
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
}RGBQUAD;
在RGBQUAD定义的颜色中,蓝色的亮度由rgbBlue来定,绿色的亮度由rgbGreen来定,红色的亮度由rgbred来定,rgbRserved必须为0.
例如: 若某表项为00,00,FF,00, 那么它定义的颜色为纯红色.
bimColor[]表项的个数由bmBitCount来定:
当bmBitCount=1,4,8时,
bimColor[]分别有2,16,256个项.若某点的象素值为n,则该象素的颜色为bimColor[]所定义的颜色.
当bmBitCount=24时,
bimColor[]的表项为空.位图阵列的每3个字节代表一个像素,这3个字节直接定义了象素颜色中蓝、绿、红的相对亮度,因此省去了bimColor[]颜色表.
三. 位图阵列的结构
位图阵列记录了位图的每一个象素值.在生成位图文件时,Windows从位图的左下角开始(即从左到右从上到下)逐行扫描位图,将位图的象素值一一记录下来.这些记录象素值的字节组成了位图阵列.
位图阵列有压缩和非压缩两种存储格式.
1.非压缩格式
在非压缩格式中,位图的每一点的象素值值一对应于位图阵列的若干位(bit),位图阵列的大小由位图的宽度,高度及位图的颜色数(bitBITCount)决定.
(1) 位图扫描行与位图阵列的关系
设记录一个扫描行的象素值需n个字节,则:
位图阵列的0~n-1个字节记录了位图的第一个扫描行的象素值;位图阵列的n~2n-1个字节记录了位图的第二个扫描行的象素值;依些类推,位图阵列的(m-1)*n~m*n-1个字节记录了位图的第m个扫描行的象素值.位图阵列的大小为n*biHight.
当(biWith*bitBITCount)mod32=0时:
n=(biWith*biBITCount)/8
当(biWith*bitBITCount)mod32!=0时:
n=(biWith*biBITCount)/8+4
上式中的+4而不+1的原因是为了使一个扫描行的象素值占用位图阵列的字节数为4的倍数(Windows规定其必须在long边界结束),不足的位用0填充.
(2)位图象素值与位图阵列的关系(以第m扫描行为例)
设记录第m个扫描行的象素值的n个字节分别为:a0,a1,a2,...,则:
当bitBITCount=1时:a0的D7位记录了位图的第m个扫描行的第1个象素值,D6位记录了位图的第m个扫描行的第1个象素值,...,
D0位记录了位图的第m个扫描行的第8个象素值,
a1的D7位记录了位图的第m个扫描行的第9个象素值,D6位记录了位图的第m个扫描行的第10个象素值,...
当bitBITCount=4时:a0的D7-D4位记录了位图的第m个扫描行的第1个象素值,D3-D0位记录了位图的第m个扫描行的第2个象素值,
a1的D7-D4位记录了位图的第m个扫描行的第3个象素值,...
当bitBITCount=8时:a0的D7-D4位记录了位图的第m个扫描行的第1个象素值,a1记录了位图的第m个扫描行的第2个象素值,...
当bitBITCount=24时:a0,a1,a2位记录了位图的第m个扫描行的第1个象素值,a3,a4,a5记录了位图的第m个扫描行的第2个象素值,...
位图其它扫描行的象素值与位图阵列的对应关系与此类似.
2. 压缩格式
Windows支持BI_RLE8及BI_RLE4压缩位图存储格式,减少了位图阵列所占用的磁盘空间.
(1)BI_RLE8压缩格式
当bicompression=1时,位图文件采用此压缩编码格式.压缩编码以两个字节为基本单位.其中第一个字节规定了用两个字节指定的颜色重复画出的连续象素的数目.
例如,压缩编码05
04表示从当前位置开始连续显示5个象素,这5个象素的象素值均为04.
在第一字节为零时,第二字节有特殊的含义:
0: 行末
1: 图末
2:
转义后面的两个字节,用这俩个字节分别表示下一个象素从当前位置开始的水平位移和垂直位移.
n(0x03<n<xff):转义后面的n个字节,其后的n象素分别用这n个字节所指定的颜色画出.注意:实际编码时必须保证后面的字节数是4的倍数.不足的位用0补齐.
例如,压缩编码00 00表示开始新的扫描行,压缩编码00
01表示压缩位图阵列结束,压缩编码00 02 05
01表示从当前位置开始向右移5个象素,向下移1行后再画下一个象素,压缩编码00 03
05 06 07
00表示从当前位置开始连续画3个象素,3个象素的颜色分别为05,06,07,最后面的00是为了保证被转义的字节数是4的倍数.
(2)BI_RLE4压缩格式
当bicompression=4时,位图文件采用此后压缩编码格式.BI_RLE4的压缩编码格式与BI_RLE8的编码方式类似,维一的不同是:BI_RLE4的一个字节包含了两个象素的颜色.
当连续显示时,第一个象素按字节高四位规定的颜色画出,第二个象素按字节低似位规定的颜色画出,第三个象素按字节高四位规定的颜色画出,...,直到所有象素都画出为止.
例如:压缩编码06
67表示从当前位置开始连续画5个象素,5个象素的颜色分别为6,7,6,7,6.
压缩编码00 04 45 67 00表示从当前位置开始连续画4个象素,
4个象素的颜色分别为4,5,6,7.最后面的00是为了保证被转义的字节数是4的倍数.
四. 实例分析
为了更清楚地说明位图文件的格式,下面对存储一个简单划面(如附图所示)的位图文件进行具体分析.画面的底色是蓝色,上面的直线的颜色是黄色,下面的直线的颜色是红色.位图以16色位映象方式存储[1],文件名为DEMO.BMP.用debug分析位图文件的内容如下:
186C:0100 42 4D 3E 01 00 00 00 00-00 00 76 00 00 00 28 00
186C:0110 00 00 26 00 00 00 0A 00-00 00 01 00 04 00 00 00
186C:0120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
186C:0130 00 00 00 00 00 00 00 00-00 00 00 00 80 00 00 80
186C:0140 00 00 00 80 80 00 80 00-00 00 80 00 80 00 80 80
186C:0150 00 00 80 80 80 00 40 40-40 00 00 00 FF 00 00 FF
186C:0160 00 00 00 FF FF 00 FF 00-00 00 FF 00 FF 00 FF
FF
186C:0170 00 00 FF FF FF 00 CC CC-CC CC CC CC CC CC CC CC
186C:0180 CC CC CC CC CC CC CC CC-CC 00 CC CC CC CC CC CC
186C:0190 CC CC CC CC CC CC CC CC-CC CC CC CC CC CC CC 99
186C:01A0 99 99 99 99 99 99 99 99-99 99 99 99 99 99 99 CC
186C:01B0 CC 00 CC CC CC CC CC CC-CC CC CC CC CC CC CC CC
186C:01C0 CC CC CC CC CC 00 CC CC-CC CC CC CC CC CC CC CC
186C:01D0 CC CC CC CC CC CC CC CC-CC 00 CC CB BB BB BB BB
186C:01E0 BB BB BB BB BB BB BB BB-BB BB BB BC CC 00 CC CC
186C:01F0 CC CC CC CC CC CC CC CC-CC CC CC CC CC CC CC CC
186C:0200 CC 00 CC CC CC CC CC CC-CC CC CC CC CC CC CC CC
186C:0210 CC CC CC CC CC 00 CC CC-CC CC CC CC CC CC CC CC
186C:0220 CC CC CC CC CC CC CC CC-CC 00 CC CC CC CC CC CC
186C:0230 CC CC CC CC CC CC CC CC-CC 00 CC CC CC 00 00 00
(0,0)
┌───────────┐
│(3,4) (黄色) (35,4)│
│_____________________ │
│ │
│ │
│(2,7) (红色) (34,7)│
│_____________________ │
└───────────┘
(38,10)
附图 实例画面
其中:
0100-010D是BITMAPFILEHEADER数据结构,它说明了如下的信息:位图文件共318B
(bfSize=0x013E),位图阵列从0176处开始(bfoffBits=0x76).
010E-0135是BITMAPFILEHEADER数据结构,它说明了如下的信息:BITMAPFILEHEADER共28B(biSize=0x28),位图的大小为38X10(biWith00x26,
biHight=0x0A,以象素为单位)
位图阵列的每四位表示一个象素,位图有16种颜色,
bmiColor[]有16个表项(bitBITCount=4),位图采用非压缩存储方式(bitCompression=0);
0136-0175是bmiColor[]颜色表,每四个字节定义一种颜色,共64B,定义了16种颜色.其中第九个表项(015A-015D)定义了红色,第11表项(0162-0165)定义了黄色,第12个表项(0166-0169)定义了蓝色.
0176-023D是位图阵列,位图每个扫描行有38个象素,对应位图阵列中的20个字节,其中第20个字节被填0,以保证字节数是4的倍数.黄线在位图的第6扫描行,此行对应位图阵列的01DA-01ED字节,红线在位图的第3行扫描,此行对应位图阵列的0192-01B1字节,位图共有10个扫描行.
关于驱动开发中mmap函数的实现
书上介绍主要是利用
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr,
unsigned long pfn, unsigned long size, pgprot_t prot) 函数或者
int io_remap_page_range(struct vm_area_struct *vma, unsigned long
virt_addr, unsigned long phys_addr, unsigned long size, pgprot_t
prot)函数来实现,它们负责建立新的页表.这两个函数的区别是第一个函数是在参数pfn指向实际系统RAM的时候使用,而第二个函数是在phys_addr指向I/O内存的时候使用.对于ARM平台来说,系统内存和端口应该是统一编址的,所以两个函数是等价的.
还有另外一个函数就是nopage函数,是VMA结构体中可以填充的一个函数,在虚拟页没有所对应的物理页的时候,会调用此函数,来分配一个物理页给虚拟页.
int remap_page_range(unsigned long from, unsigned long phys_addr,
unsigned long size, pgprot_t prot)
其中from是映射开始的虚拟地址。这个函数为虚拟地址空间from和from+size之间的范围构造页表;
phys_addr是虚拟地址应该映射到的物理地址;
size是被映射区域的大小;
prot是保护标志。
remap_page_range的处理过程是对from到form+size之间的每一个页面,查找它所在的页目录和页表(
必要时建立页表),清除页表项旧的内容,重新填写它的物理地址与保护域。
remap_page_range可以对多个连续的物理页面进行处理。<<Linux设备驱动程序>>指出,
remap_page_range只能给予对保留的页和物理内存之上的物理地址的访问,当对非保留的页使用
remap_page_range时,缺省的nopage处理控制映射被访问的虚地址处的零页。所以在分配内存后,就要对所分配的内存置保留位,它是通过函数mem_map_reserve实现的,它就是对相应物理页面置
PG_reserved标志位。
remap_pfn_range 通过你的页帧号来建立页表, 并映射到用户空间!
一般情况是你的驱动分配一块内存,然后在驱动的mmap中使用这块内存的
物理地址转成页帧号, 再调用remap_pfn_range!
假设1你是通过kmalloc(),get_free_pages()等分配的,这种内存页是不能通过remap_pfn_range()映射出去的,要对每个页面调用SetPageReserverd()标记为"保留"才可以,virt_to_phys()函数只是得到其物理地址,remap_pfn_range()中的第三个参数是要求物理页便的"帧"号,即pfn,所以你的phys还要">
> PAGE_SHIFT"操作
假设2你是通过vmalloc分配得来的,同上,不同的是要用vmalloc_to_pfn
3,用kmalloc,get_free_pages,vmalloc分配的物理内存页面最好还是不要用remap_pfn_page方法,建议使用VMA的nopage方法
4,对于这样的设备内存,最好对调用pgprot_nocached(vma->
vm_page_prot)后传给remap_pfn_range,防止处理器缓存
RGB/YUV的来历及其相互转换
人类眼睛的色觉,具有特殊的特性,早在上世纪初,Young(1809)和Helmholtz(1824)就提出了视觉的三原色学说,即:视网膜存在三种视锥细胞,分别含有对红、绿、蓝三种光线敏感的视色素,当一定波长的光线作用于视网膜时,以一定的比例使三种视锥细胞分别产生不同程度的兴奋,这样的信息传至中枢,就产生某一种颜色的感觉。
70年代以来,由于实验技术的进步,关于视网膜中有三种对不同波长光线特别敏感的视锥细胞的假说,已经被许多出色的实验所证实。例如:①有人用不超过单个视锥直径的细小单色光束,逐个检查并绘制在体(最初实验是在金鱼和蝾螈等动物进行,以后是人)视锥细胞的光谱吸收曲线,发现所有绘制出来的曲线不外三种类型,分别代表了三类光谱吸收特性不同的视锥细胞,一类的吸收峰值在420nm处,一类在534nm处,一类在564nm处,差不多正好相当于蓝、绿、红三色光的波长。与上述视觉三原色学说的假设相符。②用微电极记录单个视锥细胞感受器电位的方法,也得到了类似的结果,即不同单色光所引起的不同视锥细胞的超极化型感受器电位的大小也不同,峰值出现的情况符合于三原色学说。
于是,在彩色显示器还没有发明的时候,人类已经懂得使用三原色光调配出所有颜色的光。并不是说三原色混合后产生了新的频率的光,而是给人眼睛的感觉是这样。
在显示器发明之后,从黑白显示器发展到彩色显示器,人们开始使用发出不同颜色的光的荧光粉(CRT,等离子体显示器),或者不同颜色的滤色片(LCD),或者不同颜色的半导体发光器件(OLED和LED大型全彩显示牌)来形成色彩,无一例外的选择了Red,Green,Blue这3种颜色的发光体作为基本的发光单元。通过控制他们发光强度,组合出了人眼睛能够感受到的大多数的自然色彩。
计算机显示彩色图像的时候也不例外,最终显示的时候,要控制一个像素中Red,Green,Blue的值,来确定这个像素的颜色。计算机中无法模拟连续的存储从最暗到最亮的量值,而只能以数字的方式表示。于是,结合人眼睛的敏感程度,使用3个字节(3*8位)来分别表示一个像素里面的Red,Green和Blue的发光强度数值,这就是常见的RGB格式。我们可以打开画图板,在自定义颜色工具框中,输入r,g,b值,得到不同的颜色。
但是对于视频捕获和编解码等应用来讲,这样的表示方式数据量太大了。需要想办法在不太影响感觉的情况下,对原始数据的表示方法进行更改,减少数据量。
无论中间处理过程怎样,最终都是为了展示给人观看,这样的更改,也是从人眼睛的特性出发,和发明RGB三原色表示方法的出发点是一样的。
于是我们使用Y,Cb,Cr模型来表示颜色。Iain的书中写道:The human
visual system (HVS) is less sensitive to colour than to luminance
(brightness).人类视觉系统(其实就是人的眼睛)对亮度的感觉比对颜色更加敏感。
在RGB色彩空间中,三个颜色的重要程度相同,所以需要使用相同的分辨率进行存储,最多使用RGB565这样的形式减少量化的精度,但是3个颜色需要按照相同的分辨率进行存储,数据量还是很大的。所以,利用人眼睛对亮度比对颜色更加敏感,将图像的亮度信息和颜色信息分离,并使用不同的分辨率进行存储,这样可以在对主观感觉影响很小的前提下,更加有效的存储图像数据。
YCbCr色彩空间和它的变形(有时被称为YUV)是最常用的有效的表示彩色图像的方法。Y是图像的亮度(luminance/luma)分量,使用以下公式计算,为R,G,B分量的加权平均值:
Y = kr R + kgG + kbB
其中k是权重因数。
上面的公式计算出了亮度信息,还有颜色信息,使用色差(color
difference/chrominance或chroma)来表示,其中每个色差分量为R,G,B值和亮度Y的差值:
Cb = B -Y
Cr = R -Y
Cg = G- Y
其中,Cb+Cr+Cg是一个常数(其实是一个关于Y的表达式),所以,只需要其中两个数值结合Y值就能够计算出原来的RGB值。所以,我们仅保存亮度和蓝色、红色的色差值,这就是(Y,Cb,Cr)。
相比RGB色彩空间,YCbCr色彩空间有一个显著的优点。Y的存储可以采用和原来画面一样的分辨率,但是Cb,Cr的存储可以使用更低的分辨率。这样可以占用更少的数据量,并且在图像质量上没有明显的下降。所以,将色彩信息以低于量度信息的分辨率来保存是一个简单有效的图像压缩方法。
在COLOUR SPACES .17 ITU-R recommendation BT.601
中,建议在计算Y时,权重选择为kr=0.299,kg=0.587,kb=0.114。于是常用的转换公式如下:
Y = 0.299R + 0.587G + 0.114B
Cb = 0.564(B - Y )
Cr = 0.713(R - Y )
R = Y + 1.402Cr
G = Y - 0.344Cb - 0.714Cr
B = Y + 1.772Cb
有了这个公式,我们就能够将一幅RGB画面转换成为YUV画面了,反过来也可以。下面将画面数据究竟是以什么形式存储起来的。
在RGB24格式中,对于宽度为w,高度为h的画面,需要w*h*3个字节来存储其每个像素的rgb信息,画面的像素数据是连续排列的。按照r(0,0),g(0,0),b(0,0);r(0,1),g(0,1),b(0,1);…;r(w-1,0),g(w-1,0),b(w-1,0);…;r(w-1,h-1),g(w-1,h-1),b(w-1,h-1)这样的顺序存放起来。
在YUV格式中,以YUV420格式为例。宽度为w高度为h的画面,其亮度Y数据需要w*h个字节来表示(每个像素点一个亮度)。而Cb和Cr数据则是画面中4个像素共享一个Cb,Cr值。这样Cb用w*h/4个字节,Cr用w*h/4个字节。
YUV文件中,把多个帧的画面连续存放。就是YUV YUV
YUV…..这样的不断连续的形式,而其中每个YUV,就是一幅画面。
在这单个YUV中,前w*h个字节是Y数据,接着的w*h/4个字节是Cb数据,再接着的w*h/4个字节为Cr数据。
在由这样降低了分辨率的数据还原出RGB数据的时候,就要依据像素的位置找到它对应的Y,Cb,Cr值,其中Y值最好找到,像素位置为x,y的话,Y数据中第y*width+x个数值就是它的Y值。Cb和Cr由于是每2x2像素的画面块拥有一个,这样Cb和Cr数据相当于两个分辨率为w/2
*
h/2的画面,那么原来画面中的位置为x,y的像素,在这样的低分辨率画面中的位置是x/2,y/2,属于它的Cb,Cr值就在这个地方:(y/2)*(width/2)+(x/2)。
为了直观起见,再下面的图中,分别将Y画面(Cb,Cr=0)和Cb,Cr画面(Y=128)显示出来,可见Cb,Cr画面的分辨率是Y画面的1/4。但是合成一个画面之后,我们的眼睛丝毫感觉不到4个像素是共用一个Cb,Cr的。
Y画面 Cb,Cr画面
这个是验证公式的程序,可以在VS2005下编译,在Windows Mobile 2003和Mobile
5.0上执行。
之所以写这个是因为常见的视频编解码,Mpeg系列、H.26X系列,都是以YUV作为原始数据来处理的。所以了解这个基础知识非常重要。
V4L接口
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);
}
哈佛图书馆的二十条训言
哈佛老师经常给学生这样的告诫:如果你想在进入社会后,在任何时候任何场合下都能得心应手并且得到应有的评价,那么你在哈佛的学习期间,就没有晒太
阳的时间。在哈佛广为流传的一句格言是"忙完秋收忙秋种,学习,学习,再学习。"人的时间和精力都是有限的,所以,要利用时间抓紧学习,而不是将所有的业
余时间都用来打瞌睡。有的人会这样说:"我只是在业余时间打盹而已,业余时间干吗把自己弄得那么紧张?"爱因斯坦就曾提出:"人的差异在于业余时间。"我
的一位在哈佛任教的朋友也告诉我说,只要知道一个青年怎样度过他的业余时间,就能预言出这个青年的前程怎样。
20世纪初,在数学界有这样一道难题,那就是2的76次方减去1的结果是不是人们所猜想的质数。很多科学家都在努力地攻克这一数学难关,但结果并不
如愿。
1903年,在纽约的数学学会上,一位叫做科尔的科学家通过令人信服的运算论证,成功地证明了这道难题。人们在惊诧和赞许之余,向科尔问道:"您论证这个
课题一共花了多少时间?"科尔回答:"3年内的全部星期天。"同样,加拿大医学教育家奥斯勒也是利用业余时间作出成就的典范。奥斯勒对人类最大的贡献,就
是成功地研究了第三种血细胞。他为了从繁忙的工作中挤出时间读书,规定自己在睡觉之前必须读15分钟的书。不管忙碌到多晚,都坚持这一习惯不改变。这个习
惯他整整坚持了半个世纪,共读了1000多本书,取得了令人瞩目的成绩。
我荒废的今日,正是昨天殒身之人祈求的明日
闻名于世的约翰霍普金斯学院的创始人、牛津大学医学院的讲座教授、被英国国王册封为爵士的威廉·奥斯勒在年轻时,也曾为自己的前途感到迷茫。一次,
他在读书时看到了一句话,给了他很大的启发。这句话是"最重要的就是不要去看远方模糊的事,而是做手边清楚的事。"对此,哈佛提醒学生说"我荒废的今日,
正是昨天殒身之人祈求的明日"。明天再美好,也不如抓住眼下的今天多做点实事。
获得哈佛大学荣誉学位的发明家、科学家本杰明·富兰克林有一次接到一个年轻人的求教电话,并与他约好了见面的时间和地点。当年轻人如约而至时,本杰
明的房门大敞着,而眼前的房子里却乱七八糟、一片狼藉,年轻人很是意外。没等他开口,本杰明就招呼道:"你看我这房间,太不整洁了,请你在门外等候一分
钟,我收拾一下,你再进来吧。"然后本杰明就轻轻地关上了房门。不到一分钟的时间,本杰明就又打开了房门,热情地把年轻人让进客厅。这时,年轻人的眼前展
现出另一番景象———房间内的一切已变得井然有序,而且有两杯倒好的红酒,在淡淡的香气里漾着微波。年轻人在诧异中,还没有把满腹的有关人生和事业的疑难
问题向本杰明讲出来,本杰明就非常客气地说道:"干杯!你可以走了。"手持酒杯的年轻人一下子愣住了,带着一丝尴尬和遗憾说:"我还没向您请教呢……""
这些……
难道还不够吗?"本杰明一边微笑一边扫视着自己的房间说,"你进来又有一分钟了。""一分钟……"年轻人若有所思地说,"我懂了,您让我明白用一分钟的时
间可以做许多事情,可以改变许多事情的深刻道理。"珍惜眼前的每一分每一秒,也就珍惜了所拥有的今天。哈佛的这句话实际上揭示了一种人生哲学,那就是人生
要以珍惜的态度把握时间,从今天开始,从现在做起。
觉得为时已晚的时候,恰恰是最早的时候
安曼曾经是纽约港务局的工程师,工作多年后按规定退休。开始的时候,他很是失落。但他很快就高兴起来,因为他有了一个伟大的想法。他想创办一家自己
的工程公司,要把办公楼开到全球各个角落。安曼开始一步一个脚印地实施着自己的计划,设计的建筑遍布世界各地。在退休后的三十多年里,他实践着自己在工作
中没有机会尝试的大胆和新奇的设计,不停地创造着一个又一个令世人瞩目的经典:埃塞俄比亚首都亚的斯亚贝巴机场,华盛顿杜勒斯机场,伊朗高速公路系统,宾
夕法尼亚州匹兹堡市中心建筑群……这些作品被当作大学建筑系和工程系教科书上常用的范例,也是安曼伟大梦想的见证。86岁的时候,他完成最后一个作品
———当时世界上最长的悬体公路桥———纽约韦拉扎诺海峡桥。
生活中,很多事情都是这样,如果你愿意开始,认清目标,打定主意去做一件事,永远不会嫌晚。
今天不走,明天要跑
在哈佛,教授们会时常提醒学生们要做好时间管理,并列举如下事例:当今世界上最大的化学公司———杜邦公司的总裁格劳福特·格林瓦特,每天挤出一小
时来研究蜂鸟,并用专门的设备给蜂鸟拍照。权威人士把他写的关于蜂鸟的书称为自然历史丛书中的杰出作品。休格·布莱克在进入美国议会前,并未受过高等教
育。他从百忙中每天挤出一小时到国会图书馆去博览群书,包括政治、历史、哲学、诗歌等方面的书,数年如一日,就是在议会工作最忙的日子里也从未间断过。后
来他成了美国最高法院的法官。一位名叫尼古拉的希腊籍电梯维修工对现代科学很感兴趣,他每天下班后到晚饭前,总要花一小时时间来攻读核物理学方面的书籍。
随着知识的积累,一个念头跃入他的脑海。1948年,他提出了建立一种新型粒子加速器的计划。这种加速器比当时其他类型的加速器造价便宜而且更强有力。他
把计划递交给美国原子能委员会做试验,又再经改进,这台加速器为美国节省了7000万美元。尼古拉得到了1万美元的奖励,还被聘请到加州大学放射实验室工
作。
在人生的道路上,你停步不前,但有人却在拼命赶路。也许当你站立的时候,他还在你的后面向前追赶,但当你再一回望时,已看不到他的身影了,因为,他已经跑到你的前面了,现在需要你来追赶他了。所以,你不能停步,你要不断向前,不断超越。
狗一样地学,绅士一样地玩
我们说要珍惜时间,努力为实现理想而打拼,但有一点要注意,那就是不要一味地拼命,也要有适度的休息和放松。对此,哈佛有个很贴切的说法,叫做"狗
一样地学,绅士一样地玩"。话虽略显粗俗,但揭示的道理却很深刻。在哈佛,虽然学习强度很大,学生们承受着很大的学习压力,但他们也不提倡学生把所有的时
间都用来学习。他们认为,学要尽力,玩也不能忽视。哈佛的学生也说,哈佛的课余生活要胜过正规学习。而哈佛也意识到适度的课外活动不但不会背离教育使命,
而且还会给教育使命以支持。因此,他们提出要像"绅士一样地玩"。在哈佛,学生们除了紧张地学习,还会参加学校组织的多种艺术活动,比如音乐会、戏剧演
出、舞蹈表演及各种艺术展览等,此外,哈佛每年还会举办艺术节,以活跃学生的业余生活。这些充满着浓厚艺术氛围的活动不仅让学生接受了艺术教育和熏陶,而
且提高了学生的艺术修养和审美能力。
哈佛的理念就是要求你在紧张的学习和工作后,能够暂时地完全忘记它们,像投入工作那样投入玩耍,尽情地放松。的确,在你尽心休闲的时候,所得到的体力和精力的恢复会为你下一阶段的奋斗增添无穷的动力。所以,在前进的路上,你不仅要勤奋努力,更要学会放松。
现在流的口水,将成为明天的眼泪
成功与安逸是不可兼得的,选择了其一,就必定放弃了另一结局。正像哈佛所提醒的那样:现在流的口水,将成为明天的眼泪。今天不努力,明天必定遭罪。
我的邻居查尔斯曾经在哈佛度过4年的大学时光,他现在就职于纽约的一家软件公司,做他最擅长的行政管理工作。不久前,他的公司被一家法国公司兼并了。在兼
并合同签订的当天,公司的新总裁宣布:"我们不会随意裁员,但如果你的法语太差,导致无法和其他员工交流,那么,不管是多高职位的人,我们都不得不请你离
开。这个周末我们将进行一次法语考试,只有考试及格的人才能继续在这里工作。"散会后,几乎所有的人都拥向了图书馆,他们这时才意识到要赶快补习法语了。
只有查尔斯像平常一样直接回家了,同事们都认为他已经准备放弃这份工作了,毕竟,哈佛的学习背景和公司管理层的工作经验会帮助他轻而易举地找到另一份不错
的工作。然而,令所有人都想不到的是,考试结果出来后,这个在大家眼中没有希望的人却考了最高分。原来,查尔斯在毕业后来到这家公司后,他在工作中发现与
法国人打交道的机会特别多,不会法语会使自己的工作受到很大的限制,所以,他很早就开始自学法语了。他利用可利用的一切时间,每天坚持学习,最终学有所
获。
在哈佛,你从来看不到学生在偷懒,在消磨时间。当若干年后回想起曾经的梦想时,希望带给你的是无尽的欣慰笑容,而不是因蹉跎而流下的悔恨泪水。
投资未来的人,是忠于现实的人
作为世界知名的学府,哈佛十分强调要有长远眼光,为未来投资。要投资未来,就要定好未来的投资方向,也就是要及早地设定人生目标。没有目标,就谈不
到发展,更谈不上成功。哈佛大学曾进行过这样一项跟踪调查,对象是一群在智力、学历和环境等方面条件差不多的年轻人。调查结果发现:27%的人没有目
标;60%的人目标模糊;10%的人有着清晰但比较短期的目标;其余3%的人有着清晰而长远的目标。以后的岁月,他们行进在各自的人生旅途中。25年后,
哈佛再次对这群学生进行了跟踪调查。结果是这样的:3%的人,在25年间朝着一个方向不懈努力,几乎都成为社会各界的成功人士,其中不乏行业领袖和社会精
英;10%的人,他们的短期目标不断地实现,成为各个领域中的专业人士,大都生活在社会的中上层;60%的人,他们安稳地生活与工作,但都没有什么特别成
绩,几乎都生活在社会的中下层;剩下27%的人,他们的生活没有目标,过得很不如意,并且常常在抱怨他人,抱怨社会,当然,也抱怨自己。
其实,他们之间的差别仅仅在于:25年前,他们中的一些人就已经知道自己最想要做的是什么,而另一些人则不清楚或不很清楚。这个调查生动地说明了明确生活目标对于人生成功的重要意义。
论机遇和学习能力
昨天和一个网友见面.聊了很多.包括所有的对社会国家的看法,对人生的看法,对机会的看法,
对以后的看法.等等等等…都有点发现自己老了,不在象以前一样那么老想这种事情了.
所以得静静..除了技术上的事情外,得给自己一点时间来思考.
在昨天谈的问题中,有关一个人学习能力和机会这个谈的起多.人生起起浮浮啊…一直以来,有不少的喜欢计算机的朋友.都向我请教学习过计算机,有的都希望我做他们的老师…但发现自从以前,一直到现在…自己教的人中发现,奇怪没有一个坚持的时间超过三个月…可笑吗?心痛啊…
不过啊,有时换换心,让自己做一个事情,能不能做三个月,也是个问题.但总的来讲,自己是坚持下来了,直到现在,现在学习什么都很快了,不象以前学习什么都不会.慢得让自己都没有兴趣.所以现在了很多问题.
第一,我不聪明.其实大家都一样,但……….大家的成就,就象我前几天的签名一样,人和不同就在业余时间…在平时,你比别人坚持的更久,更快,就是成就.就是历害.这样坚持个几年,分别就出来了.
第二,有个目标,不要过分远大,我也有很历害的朋友,有很远大的"梦想",为什么讲是梦想,主要是就是因理想还是能成为现实,但梦想基本不行.因为目标大了,所以没有心静下来做一样事,所以变得浮燥.所以多年后还是那个样子.人要有自知.要知道自己什么地方好,什么地方不好.好好的做好自己的事情.这个就得讲到什么目标合适,远大点要有,近点也要有,合适的目标就是跳一下能拿到,但不跳拿不到的近期目标是最合适的,不然你老是达不到目标,会打击你的自信心.
第三,找点能证明自己的事情,人物.自信心还是相当重要的,如果你学完一个什么事情,超有成就感,你要找的就是那种感觉,有那种感觉时,你学习东西最快.当有人问你相关的问题,你一下就能搞定,那种快感最重要.
第四.机会,太多的人讲我一身的本事,但没有机会了,其实啊,这种人大多是没有本事,但自认自己有本事,就是我前面讲的,没有自知.有的人喜欢讲,钱和本事,钱更加重要.其实错了.为什么?你如果真很有本事(常识,做人都是本事的一部分).自然会有人投资你.有人讲没有工作机会,有本事也自然有工作来找你的.我手上就有几个朋友让帮忙找的工作机会,但是没有人合适啊….
记的,人生本来是可以非常非常精彩,你的人生本来就可以有很多很多的成果.决定在于,你心动的那一刻,是否立即行动.还是选择了另一条路,告诉自己,晚点在做.