SANE扫描仪驱动开发

SANE扫描仪驱动开发

一、前言

  Linux下的扫描仪驱动类型主要是sane协议,其他的还有跨平台的twain协议,但是在Linux上比较少见。
  SANE全称Scanner Access Now Easy,是一个开源项目,项目地址http://www.sane-project.org/source.html。项目总体分为两个部分,sane-backends和sane-frontends。sane-backends包括backends(后端,也就是sane的驱动模块),命令行工具scanimage,网络扫描守护进程(saned)和SANE_API。sane-frontends包含xscanimage和xcam两个图形化界面程序,和一个命令行扫描工具scanadf。分为两部分之后,开发者可以只需要关心需要实现的部分。比如驱动开发者只需要了解如何实现一个后端(backends)即可,而应用开发者只需要关注sane的API调用流程。下面重点介绍驱动开发部分,关于这些扫描工具可以查看帮助手册了解用法。

二、驱动框架流程详解

2.1、整体流程

  想要开发sane的后端,首先要了解应用调用后端API的流程,整体流程图如下:

sane流程图

  上述流程只是一般通用性扫描流程,不同扫描软件的实现可能会存在细微差异。

2.2、流程详解

2.2.1、sane_init

  原型如下:

1
2
SANE_Status
sane_init(SANE_Int *version_code, SANE_Auth_Callback authorize)

  这是驱动的入口,主要作用在于初始化设备。一般每个厂商的驱动会配置一个配置文件放在/etc/sane.d目录。对于usb设备,里面会记录设备的挂载路径、VID(厂商ID)和PID(产品ID)。sane_init时就会读取这个配置文件做一些检查。认证这个可以根据需要定制这个回调函数,一般sane的驱动没有实现这个。

sane的API返回值全部都是SANE_Status,所以在驱动开发时要注意每个细分的步骤都要返回对应的状态,这样扫描软件才能根据状态做出正确的判断。

2.2.2、sane_open

  原型如下:

1
2
SANE_Status
sane_open (SANE_String_Const name, SANE_Handle * h)

  此接口用于通过参数name建立与对应设备句柄的连接。遍历设备列表,根据name找到指定设备,并将此设备转换成句柄h。

2.2.3、sane_get_devices

  原型如下:

1
2
SANE_Status
sane_get_devices (const SANE_Device * **device_list, SANE_Bool local_only)

  应用调用这个接口获取这个驱动对应的设备列表,local_only参数表示仅获取本地设备,但是如果你的驱动没有提供远程设备,就可以忽略这个参数。设备的数量和设备对应的属性也是在sane_init阶段就可以确定。这里只用返回数据即可。

2.2.4、sane_get_option_descriptor

  原型如下:

1
2
const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)

  应用通过handle告诉驱动对应的设备,然后option是对应的属性列表的下标,一般驱动会在设备结构体里面维护一个属性数组,记录设备的各种支持的属性,比如色彩模式、进纸模式和分辨率等。驱动会在这里返回对应设备的指定属性给扫描软件。扫描软件的获取逻辑是循环调用这个接口直到驱动返回NULL,表示属性全部返回。

2.2.5、sane_control_option

  原型如下:

1
2
3
4
SANE_Status
sane_control_option (SANE_Handle handle,
SANE_Int option,
SANE_Action action, void *value, SANE_Int * info)

  这里是扫描软件获取属性默认值和设置属性值的入口。在前一个阶段软件已经获取到了支持的属性列表,这里就会获取默认值用于显示初始选项,用户后面更改属性的选项时也是通过这个入口。区分是设置还是获取属性就要通过action,action定义如下:

1
2
3
4
5
6
7
typedef enum
{
SANE_ACTION_GET_VALUE = 0,
SANE_ACTION_SET_VALUE,
SANE_ACTION_SET_AUTO
}
SANE_Action;

  从枚举值的命名方式就能很明显的知道每个action对应的意义。驱动在这里需要根据不同的action进入不同的逻辑。

2.2.6、sane_start

  原型如下:

1
2
SANE_Status
sane_start (SANE_Handle handle)

  这里是启动扫描的入口,根据handle映射到对应设备,然后根据前面设置的各种属性去从设备里面获取图像,缓存到内存中,并解析图片的信息。

2.2.7、sane_get_parameters

  原型如下:

1
2
SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)

  应用从这个接口获取上次sane_start阶段从设备那里获取的图片缓存的详细信息,params参数的定义如下:

1
2
3
4
5
6
7
8
9
10
typedef struct
{
SANE_Frame format;
SANE_Bool last_frame;
SANE_Int bytes_per_line;
SANE_Int pixels_per_line;
SANE_Int lines;
SANE_Int depth;
}
SANE_Parameters;

  params参数值取决于扫描时用户选择的参数,比如分辨率,色彩模式等。应用就是通过这些参数知道图片详细数据,然后对图片做对应的处理。

2.2.8、sane_read

  原型如下:

1
2
3
SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
SANE_Int * len)

  当应用在上一个步骤获取到图片的信息之后,就会开始调用这个API读取缓存的图片数据,驱动需要在没有超出max_len的上限内存大小的情况下不断循环从上次偏移值的地方继续给buf缓存区写入数据。当数据全部发送完成之后,给应用返回SANE_STATUS_EOF的结束标志,应用就会结束本次扫描,这就完成一次基本的扫描流程。

  当然不同类型的扫描仪循环结束条件不同,一般ADF(Automatic Document Feeder)自动进纸类型,会一直重复sane_start->sane_get_parameters->sane_read这个流程,直到纸张全部扫描完成,最终在sane_start的给应用返回SANE_STATUS_NO_DOCS,这样应用就会停止循环。FLATBED平板进纸类型则是一次扫描只会扫描一张纸。

2.2.9、sane_cancle

  原型如下:

1
2
void
sane_cancel(SANE_Handle handle)

  设置取消标志位为SANE_TRUE,在扫描的时候可以根据这个标志位做停止扫描和释放资源等操作。

2.2.10、sane_close

  原型如下:

1
2
void
sane_close(SANE_Handle handle)

  关闭扫描仪设备的文件描述符,针对不同的类型(SCSI或USB等)扫描仪可以调用不同的方法。关闭之后释放结构体资源即可。

2.2.11、sane_exit

  原型如下:

1
2
void
sane_exit (void)

  释放所有资源避免内存泄露等问题。

三、开发细则

  上述章节主要是讲解sane驱动的流程,以及每个流程需要实现的功能。但是实际开发的时候还是有很多细节需要注意,下面主要讲解开发中经常遇到的问题。

3.1、驱动配置

  驱动开发完成以后,会生成一个动态链接库,那么SANE是如何加载你的驱动呢?在/etc目录下有个sane.d文件夹,这是默认的用于存放SANE配置文件的位置,也可以通过SANE_CONFIG_DIR来设置一个指定的目录。/etc/sane.d/dll.conf是用来指定SANE动态加载后端的配置文件,可以手动在某一行前面加#来屏蔽不希望被加载的后端。
  第三方的后端文件也可以放在/etc/sane.d/dll.d/目录下,文件以后端名称命名。后端文件中写入我们自己实现的后端的名称,这样SANE就能动态加载到我们实现的后端了。

3.2、开发包

  在进行开发时,虽然安装了libsane-dev,但是其中只有sane.h和saneopts.h这两个头文件,还有很多其他头文件不在开发包里面,这个是这个SANE作者强制性要求的。所以如果需要其他的头文件,需要在官网下载项目,手动将SANE的一些头文件拷到项目中。

3.3、扫描参数列表

  扫描参数描述存在于后端的一个结构体中,这个结构体的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct
{
SANE_String_Const name;
SANE_String_Const title;
SANE_String_Const desc;
SANE_Value_Type type;
SANE_Unit unit;
SANE_Int size;
SANE_Int cap;

SANE_Constraint_Type constraint_type;
union
{
const SANE_String_Const *string_list;
const SANE_Word *word_list;
const SANE_Range *range;
}
constraint;
}
SANE_Option_Descriptor;

  下面的例子是已经实现的一个关于分辨率列表的属性结构体。通过此例子对此结构体的各项成员变量做一些说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static const SANE_Word s_RemoteScannerResolutionList[] = {
6, 75, 150, 300, 600, 1200, 2400
};

SANE_Option_Descriptor {
SANE_NAME_SCAN_RESOLUTION,
SANE_TITLE_SCAN_RESOLUTION,
SANE_DESC_SCAN_RESOLUTION,
SANE_TYPE_INT,
SANE_UNIT_DPI,
sizeof(SANE_Word),
SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_AUTOMATIC,
SANE_CONSTRAINT_WORD_LIST,
{
(SANE_String_Const *)s_RemoteScannerResolutionList
}
};

3.3.1、name&title&desc

  这三个成员变量是对当前扫描参数的描述,类型都是字符串,只需要注意如果使用自定义的描述,title和desc要使用SANE_I18N()将描述字符串包起来即可。

3.3.2、type

  type指定参数值的类型,共有以下几种类型。

1
2
3
4
5
6
7
8
9
10
typedef enum
{
SANE_TYPE_BOOL = 0,
SANE_TYPE_INT,
SANE_TYPE_FIXED,
SANE_TYPE_STRING,
SANE_TYPE_BUTTON,
SANE_TYPE_GROUP
}
SANE_Value_Type;

  例子中使用的是SANE_TYPE_INT,表示参数值是int型的数据。

3.3.3、unit

  unit指定参数值的单位,共有以下几种类型。

1
2
3
4
5
6
7
8
9
10
11
typedef enum
{
SANE_UNIT_NONE = 0,
SANE_UNIT_PIXEL,
SANE_UNIT_BIT,
SANE_UNIT_MM,
SANE_UNIT_DPI,
SANE_UNIT_PERCENT,
SANE_UNIT_MICROSECOND
}
SANE_Unit;

  例子中使用的是SANE_UNIT_DPI,表示参数值的单位是dots/inch,对应分辨率的单位。如果是色彩模式等无单位的属性值,则应选择SANE_UNIT_NONE。根据属性选择对应的单位即可。

3.3.4、size

  size表示单个属性值的长度,需要注意的是,如果size设定的较短,属性值会被截断,导致扫描属性值在传输的时候出现异常。例子中选择的是分辨率,数据不会太大,size设置成8就够了;如果是字符串型的数据,则需要至少将size设置成列表中最长字符串的长度。

3.3.5、cap

  cap表示此属性支持的属性,共有以下几种类型。

1
2
3
4
5
6
7
SANE_CAP_SOFT_SELECT   
SANE_CAP_HARD_SELECT
SANE_CAP_SOFT_DETECT
SANE_CAP_EMULATED
SANE_CAP_AUTOMATIC
SANE_CAP_INACTIVE
SANE_CAP_ADVANCED
  • SANE_CAP_SOFT_SELECT
    表示此属性可以通过sane_control_option()设置。
  • SANE_CAP_HARD_SELECT
    表示此属性可以通过硬件(例如拨动开关)设置。
  • SANE_CAP_SOFT_DETECT
    表示此属性可以被软件探测到。
  • SANE_CAP_EMULATED
    如果设置了此功能,则表示该设备不直接支持该选项,而是在后端中对其进行仿真。
  • SANE_CAP_AUTOMATIC
    如果设置了此功能,则表示后端(或设备)能够自动选择合理的选项值。
  • SANE_CAP_INACTIVE
    如果设置了此功能,则表示该选项当前未激活(例如,仅当另一个选项设置为其他值时,该选项才有意义)。
  • SANE_CAP_ADVANCED
    如果设置了此功能,则表明该选项应被视为“高级用户选项”。前端通常以比常规选项不那么显眼的方式显示此类选项。

  例子中的分辨率列表希望实现的效果是能通过软件设置以及能自动选择正确的选项值,因此将cap设置成SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_AUTOMATIC。其他属性根据实际需要进行类似的设置即可。

3.3.6、constraint_type

  constraint_type指定属性描述列表的类型,共有以下几种类型。

1
2
3
4
5
6
7
8
typedef enum
{
SANE_CONSTRAINT_NONE = 0,
SANE_CONSTRAINT_RANGE,
SANE_CONSTRAINT_WORD_LIST,
SANE_CONSTRAINT_STRING_LIST
}
SANE_Constraint_Type;

  例子中的分辨率列表是一个int型的数组,因此将constraint_type设置成SANE_CONSTRAINT_WORD_LIST。如果是色彩模式这种string型数组,则应该将constraint_type设置成SANE_CONSTRAINT_STRING_LIST。

3.3.7、constraint

  constraint是属性描述的列表,即属性列表的数据。列表有三种类型,分别是int型列表、string型列表和范围型列表。

  • int型列表
    int型列表可以用来描述分辨率等属性,赋值方式如下所示。

    1
    2
    3
    static const SANE_Word resolutionList[] = {
    8, 75, 150, 200, 240300, 400, 500, 600
    };

      resolutionList[0]表示列表中选项值的个数,显示效果如下图。
    int型列表显示效果图

  • string型列表
    string型列表可以用来描述色彩模式、纸张尺寸、进纸方式等属性,赋值方式如下所示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    SANE_String_Const pageSizeList[] = {
    "A3"
    "A4"
    "B4"
    "A5",
    "B5",
    "A6",
    "B6",
    nullptr
    };

      pageSizeList列表中最后一项必须是nullptr,表示列表选项值结束,否则会发生异常。显示效果如下图。
    string型列表显示效果图

  • range型列表
    range型列表可以用来描述对比度、亮度等属性,赋值方式如下所示。

    1
    2
    3
    4
    5
    SANE_Range resolutionRange = {
    50,
    300,
    0
    };

      resolutionRange第一项表示范围下限,第二项表示范围上限,第三项表示步进(0表示步进为1)。显示效果如下图。
    range型列表显示效果图

3.4、设备交互

  本小节重点介绍如何通过SANE提供的API和不同类型的设备进行交互。

3.4.1 、与USB扫描仪的交互

3.4.1.1、sane_init阶段
  • sanei_usb_init()
      首先调用此接口,获取所有可用的usb设备。
  • sanei_config_open(const char *filename)
      加载指定设备对应的配置文件配置文件。
  • sanei_config_read(char *str, int n, FILE *stream)
      读取配置文件,获取设备的VID(厂商ID)和PID(产品ID),跳过配置文件中的空行和以“#”开头的行,根据PID和VID对比获取本驱动支持的扫描设备列表。
  • sanei_usb_attach_matching_devices (const char *name,SANE_Status (*attach) (const char *dev))
      将扫描仪的VID、PID、设备名绑定,以便后面直接使用设备名能够访问指定设备。
3.4.1.2、sane_open阶段
  • sanei_usb_open (SANE_String_Const devname, SANE_Int * dn)
      根据sane_open传过来的设备名,传入此接口,打开指定设备,dn指向的是设备列表中该设备的下标。
3.4.1.3、sane_start阶段
  • sanei_usb_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size)
      在sane_start中重复调用这个接口,为指定的USB设备设置期望使用的扫描参数,size返回真实写入的长度。设置完所有参数后,再次调用这个接口,将buffer设置成对应的扫描指令,即可控制扫描仪开始扫描工作。
3.4.1.4 sane_close阶段
  • sanei_usb_close (SANE_Int dn)
      传入的参数是设备列表中该设备的下标,根据此下标关闭指定的usb设备。

3.4.2、与SCSI扫描仪的交互

3.4.2.1、sane_init阶段
  • sanei_config_open(const char *filename)
      加载指定设备对应的配置文件配置文件。
  • sanei_config_read(char *str, int n, FILE *stream)
      读取配置文件,跳过配置文件中的空行和以“#”开头的行。
  • sanei_usb_attach_matching_devices (const char *name,SANE_Status (*attach) (const char *dev))
      将扫描仪的VID、PID、设备名绑定,以便后面直接使用设备名能够访问指定设备。
3.4.2.2、sane_open阶段
  • sanei_scsi_open (const char *dev, int *fdp, SANEI_SCSI_Sense_Handler handler, void *handler_arg)
      根据sane_open传过来的设备名,打开一个指定的SCSI设备,并且返回设备的文件描述符。
3.4.2.3、sane_start阶段
  • sanei_scsi_cmd2 (int fd,const void *cmd, size_t cmd_size,const void *src, size_t src_size,void *dst, size_t * dst_size)

      在sane_start中重复调用这个接口,为指定的SCSI设备设置期望使用的扫描参数。这个接口比较便捷,相当于同时调用了sanei_scsi_req_enter2()和sanei_scsi_req_wait()这两个接口。设置完所有参数后,再次调用这个接口,将cmd设置成对应的扫描指令,即可控制扫描仪开始扫描工作。

3.4.2.4、sane_close阶段
  • sanei_usb_close (SANE_Int dn)
      传入的参数是设备列表中该设备的下标,根据此下标关闭指定的SCSI设备。

SANE扫描仪驱动开发
http://yoursite.com/2021/04/30/sane驱动开发/
作者
还在输入
发布于
2021年4月30日
许可协议