MTK CAMERA驱动揭秘:10个关键技巧助你入门及精通(入门指南与实战进阶)
发布时间: 2024-12-20 07:53:51 阅读量: 7 订阅数: 11
MTK_FAQ_Camera-Driver.rar_camera_camera 驱动 mtk_mtk_mtk CAMERA
![MTK CAMERA驱动揭秘:10个关键技巧助你入门及精通(入门指南与实战进阶)](https://programmer.group/images/article/deecdf5fe7cec890daf05a686e640573.jpg)
# 摘要
MTK CAMERA驱动是智能手机中重要的图像处理模块,本文全面概述了MTK CAMERA驱动的基础架构及其工作原理。深入分析了驱动的关键组件,包括硬件抽象层(HAL)、内核模块、数据流管理和同步机制。通过探讨驱动的配置与优化技巧,本文为开发者提供了实用的性能调节和问题诊断方法。同时,本文还分享了MTK CAMERA驱动开发中的实战技巧,包括开发流程、工具链使用、常见问题解决以及驱动的扩展与定制化。最后,展望了驱动的进阶应用与未来技术演进方向,特别关注了人工智能与机器学习技术在驱动优化中的潜力。整体而言,本文为专业人士提供了全面的MTK CAMERA驱动开发指南和优化策略。
# 关键字
MTK CAMERA驱动;驱动架构;数据流管理;同步技术;性能优化;驱动开发工具链
参考资源链接:[MTK平台CAMERA驱动详解:调试、配置与问题解决](https://wenku.csdn.net/doc/7m4bfnf291?spm=1055.2635.3001.10343)
# 1. MTK CAMERA驱动概述与基础
## 1.1 MTK CAMERA驱动的重要性
移动设备中的摄像头模块对于用户体验至关重要,而驱动程序则是连接硬件和操作系统的关键组件。MTK(MediaTek)作为一家全球知名的半导体公司,其为移动设备提供的CAMERA驱动,不仅需要保证影像捕获的高质量,还要确保与不同硬件平台和操作系统的兼容性。开发MTK CAMERA驱动是一项要求高度专业知识和经验的任务,需要对底层硬件细节以及上层软件架构有深入的理解。
## 1.2 MTK CAMERA驱动的组成概述
MTK CAMERA驱动主要由以下几个部分组成:硬件抽象层(HAL),内核模块,以及控制路径。HAL层负责与应用层进行接口对接,而内核模块则负责与硬件直接通信。控制路径描述了从应用层发起请求到最终驱动硬件操作的完整流程。为了深入理解MTK CAMERA驱动,接下来我们将详细探讨这些组成部分及其之间的交互。
# 2. 深入了解MTK CAMERA驱动架构
MTK CAMERA驱动是一个复杂的系统,它的核心作用是连接硬件和软件,使手机相机模块能够正常工作。本章将深入探讨MTK CAMERA驱动的内部架构,剖析其关键组件及其作用,并分析其同步与并发控制机制。
## 2.1 MTK CAMERA驱动的基本组件
### 2.1.1 硬件抽象层(HAL)与内核模块
硬件抽象层(HAL)是MTK CAMERA驱动与硬件直接交互的接口层。HAL负责与相机传感器、闪光灯和其他硬件组件进行通信,执行如打开/关闭相机、调整焦距、曝光等操作。这些操作需要与底层的内核模块紧密配合,内核模块负责管理和控制相机硬件资源,如DMA(直接内存访问)通道和中断。
```c
/* 示例代码:硬件抽象层与内核模块的交互 */
/* 这段代码展示了如何通过内核模块的API来获取相机硬件的信息 */
int get_camera_info(int camera_id) {
struct camera_info cam_info;
memset(&cam_info, 0, sizeof(cam_info));
if (camera_kernel_module_get_info(camera_id, &cam_info)) {
/* 错误处理 */
return -1;
}
/* 使用cam_info中的数据 */
return 0;
}
```
上述代码中的`camera_kernel_module_get_info`函数假设是一个内核模块提供的API,用于获取相机硬件的信息。这段代码的核心逻辑是:使用内核模块提供的函数来填充一个`camera_info`结构体,其中包含了诸如分辨率、最大帧率、像素格式等信息。
### 2.1.2 驱动的初始化流程和控制路径
MTK CAMERA驱动的初始化流程涉及加载必要的驱动模块、配置硬件资源以及准备数据缓冲区等工作。驱动初始化后,控制路径将负责处理来自应用层的请求,如拍照、录像等。
在控制路径中,驱动需要处理多个并发的请求,因此必须实现一种机制来确保资源被合理分配和释放,防止资源冲突和死锁。
## 2.2 驱动的关键数据结构
### 2.2.1 数据流和缓冲区管理
在MTK CAMERA驱动中,数据流管理涉及到图像数据从传感器流向CPU或DSP(数字信号处理器)的过程。缓冲区管理是确保这一过程高效且稳定的关键,它需要处理数据缓冲区的分配、回收和同步。
```c
/* 示例代码:缓冲区管理 */
struct buffer_node {
struct list_head list;
void *buffer;
size_t size;
};
void init_buffer_pool(struct buffer_pool *pool, size_t buffer_size, int num_buffers) {
INIT_LIST_HEAD(&pool->free_buffers);
for (int i = 0; i < num_buffers; ++i) {
struct buffer_node *node = kmalloc(sizeof(struct buffer_node), GFP_KERNEL);
node->buffer = kzalloc(buffer_size, GFP_KERNEL);
node->size = buffer_size;
list_add(&node->list, &pool->free_buffers);
}
}
void get_buffer_from_pool(struct buffer_pool *pool, void **buffer, size_t *buffer_size) {
struct buffer_node *node = list_first_entry(&pool->free_buffers, struct buffer_node, list);
list_del(&node->list);
*buffer = node->buffer;
*buffer_size = node->size;
/* 逻辑处理 */
}
void free_buffer_to_pool(struct buffer_pool *pool, void *buffer) {
struct buffer_node *node = container_of(buffer, struct buffer_node, buffer);
list_add(&node->list, &pool->free_buffers);
/* 清理工作 */
}
```
上述代码展示了如何管理缓冲区池子,包括初始化缓冲池、从缓冲池获取缓冲区以及将缓冲区返回到缓冲池的逻辑。这段代码通过链表管理缓冲区节点,并使用`list_add`和`list_del`来维护这些节点。
### 2.2.2 配置参数和格式转换机制
MTK CAMERA驱动需要支持多种格式和分辨率,为了处理这些转换,驱动中会实现一套复杂的格式转换机制。配置参数决定了这些转换的规则,包括图像编码、分辨率、颜色空间转换等。
```c
/* 示例代码:图像格式转换 */
void convert_format(struct image_data *input, struct image_data *output) {
/* 输入参数和输出参数的格式可能不同,这里进行了格式转换 */
/* 假设input和output是两种不同格式的图像数据 */
/* 转换逻辑 */
/* ... */
}
```
在上述代码段中,`convert_format`函数接受两种图像数据格式作为输入和输出,负责在两者之间进行转换。在实际实现中,转换逻辑可能涉及到颜色空间转换(如RGB到YUV)、缩放和裁剪等操作。
## 2.3 驱动的同步与并发控制
### 2.3.1 多线程环境下的同步技术
为了提高MTK CAMERA驱动的性能,可能会使用多线程技术。在这种环境下,需要通过适当的同步机制来保证数据的一致性。常见的同步技术包括互斥锁(mutex)、信号量(semaphore)等。
```c
/* 示例代码:使用互斥锁保证数据一致性 */
struct mutex lock;
void capture_image() {
mutex_lock(&lock);
/* 关键区,执行与数据相关的操作 */
mutex_unlock(&lock);
}
```
代码中使用了互斥锁`mutex`来确保在多线程环境下,对于关键数据区的访问是互斥的,防止并发访问导致的数据不一致问题。
### 2.3.2 中断处理和任务调度策略
在中断处理方面,MTK CAMERA驱动需要能够响应硬件中断,并迅速执行必要的任务。同时,任务调度策略将决定这些任务的执行顺序和优先级,保证驱动的响应性和效率。
```c
/* 示例代码:中断处理和任务调度 */
irqreturn_t camera_irq_handler(int irq, void *dev_id) {
struct camera_dev *dev = (struct camera_dev *)dev_id;
/* 读取中断状态 */
/* 根据中断类型进行处理 */
schedule_task(dev);
return IRQ_HANDLED;
}
void schedule_task(struct camera_dev *dev) {
/* 根据任务优先级进行调度 */
/* 执行任务 */
}
```
在该代码段中,`camera_irq_handler`函数负责处理来自相机硬件的中断。通过`schedule_task`函数来调度和执行中断处理任务,确保任务能按优先级顺序执行。
通过本章内容的详细分析,我们已经对MTK CAMERA驱动的架构有了更深入的理解。下一章我们将探讨如何对MTK CAMERA驱动进行配置与优化,以提高相机模块的性能和效率。
# 3. MTK CAMERA驱动的配置与优化技巧
深入理解和掌握MTK CAMERA驱动的配置与优化技巧是提升相机应用性能和稳定性的关键。这一章将详细分析配置文件的解析、性能优化实践,以及调试与问题诊断的方法。
## 3.1 驱动配置文件解析
### 3.1.1 配置参数的设置和解析方法
MTK CAMERA驱动的配置文件通常是通过一系列的键值对来设定驱动的行为和参数。这些参数覆盖了从图像分辨率、帧率到曝光和对焦设置等方方面面。正确地理解和设置这些参数对于相机的最终表现至关重要。
在配置文件中,我们可以看到如下格式的设置:
```plaintext
[CameraConfig]
KEY1=VALUE1
KEY2=VALUE2
```
这里的`KEY`代表了不同的配置项,而`VALUE`则是用户或系统预设的参数值。例如:
```plaintext
[CameraConfig]
sensor_output_format=2
max_resolution=3840x2160
```
在解析过程中,驱动程序会根据配置文件中的参数,调整内部的数据结构以适应不同的硬件和性能需求。
### 3.1.2 常见的性能调节技巧
对于性能调节,关键在于找到性能与图像质量之间的平衡点。通常,我们会关注以下参数:
- **帧率(Frame Rate)**:控制图像捕获的速度。帧率过高可能导致图像处理跟不上,过低则影响用户体验。
- **分辨率(Resolution)**:图像的尺寸大小。高分辨率需要更多的带宽和处理能力。
- **压缩比例(Compression Ratio)**:影响图像文件的大小。压缩比例越大,占用存储空间越小,但可能牺牲图像质量。
- **曝光时间(Exposure Time)**:决定图像的亮度。太长或太短的曝光时间都会影响拍摄效果。
对于这些参数的调整,通常需要反复测试,以便找到最优的配置。我们可以创建一系列的测试脚本,自动化地改变配置文件中的参数,收集反馈,并据此进行调整。
## 3.2 性能优化实践
### 3.2.1 缓冲区优化和内存管理
性能优化的一个重要方面是缓冲区的管理和优化。MTK CAMERA驱动需要高效地分配和管理图像数据缓冲区以确保流畅的图像捕获与处理。
- **缓冲区池(Buffer Pooling)**:通过预先分配一定数量的缓冲区来减少动态分配内存时的开销。
- **内存对齐(Memory Alignment)**:确保缓冲区的起始地址按照特定的字节对齐,可以提高内存访问的效率。
- **零拷贝(Zero-Copy)技术**:减少数据在内存中复制的次数,通过直接内存访问(DMA)来提高数据传输的速度。
### 3.2.2 硬件加速与软件算法的平衡
现代相机驱动通常依赖于硬件加速来提高性能,但是某些算法可能在软件层面实现更优。如何平衡硬件加速与软件算法是性能优化的另一关键。
- **硬件加速**:利用ISP(Image Signal Processor)、DSP(Digital Signal Processor)等硬件模块加速图像处理流程。
- **软件算法**:在CPU上运行更复杂的图像处理算法,如高级去噪、锐化等。
在实际应用中,通常需要根据硬件的能力和实际应用场景来决定硬件加速与软件算法的使用比例。
## 3.3 调试与问题诊断
### 3.3.1 日志系统和调试输出
为了有效地调试和诊断问题,日志系统必须能够提供足够详细的信息。MTK CAMERA驱动通常包括多种日志级别,例如:
- **Debug Level**:提供丰富的调试信息,有助于开发人员定位问题。
- **Error Level**:输出错误和异常信息,用于问题追踪。
- **Warning Level**:记录可能潜在问题的信息,但不会阻止程序运行。
合理地配置日志系统输出对于提高开发效率和问题诊断至关重要。
### 3.3.2 性能瓶颈的定位与解决方案
性能瓶颈可能发生在驱动的任何部分,定位这些瓶颈通常需要结合日志、性能监控工具以及实际的测试结果。
- **分析日志**:寻找执行延迟或错误的迹象。
- **CPU和内存监控**:观察在特定操作下CPU和内存的使用情况。
- **压力测试**:通过模拟高负载情况来找出潜在的瓶颈。
一旦确定了瓶颈,可能需要优化算法、调整参数或者重新设计驱动的一部分以解决性能问题。
下一章节将继续探讨MTK CAMERA驱动开发实战技巧,为开发者提供深入的开发流程、工具链使用以及开发中常见问题的解决方案。
# 4. MTK CAMERA驱动开发实战技巧
## 4.1 驱动开发流程与工具链
### 4.1.1 开发环境搭建和编译流程
MTK CAMERA驱动的开发是一个涉及到底层硬件和操作系统级别的复杂过程。搭建一个高效、稳定的开发环境是提升开发效率和质量的关键。对于MTK平台而言,开发者需要准备的工具有:交叉编译器、MTK提供的SDK、内核源码、驱动模板等。
#### 工具链安装与配置
首先,确保你的开发机器已经安装了Linux环境。然后,下载并安装交叉编译器,这是编译目标平台代码的必要工具。对于MTK设备,常用的交叉编译器是`arm-linux-gnueabihf-`系列。
接下来,下载并解压MTK提供的SDK,该SDK包括了开发CAMERA驱动所需的所有头文件、库文件以及硬件抽象层HAL的接口定义。另外,内核源码是驱动开发的核心,需要从MTK的官方网站或者提供该设备技术支持的渠道获取。
在安装好上述工具后,开发环境的搭建就完成了一半。剩下的部分是配置你的编译环境,包括设置环境变量,确保编译工具链能够找到必要的依赖库。
#### 编译流程
MTK CAMERA驱动的编译流程遵循标准的Linux内核驱动编译流程。首先,你需要准备好驱动的源代码文件和Makefile。Makefile文件定义了编译规则,包括编译目标、依赖关系以及编译选项。
一般情况下,编译工作会遵循以下步骤:
1. 导入内核配置文件:通常,内核配置文件是一个`.config`文件,包含了内核编译时需要的参数设置。
2. 执行`make`命令:这个命令会根据Makefile以及内核配置来编译整个内核以及附加的驱动模块。
3. 安装模块:使用`make modules_install`安装编译好的模块到指定的目录,通常是`/lib/modules/<kernel-version>/extra`。
在编译过程中,经常会遇到编译错误或警告,这时需要根据提示对源代码或Makefile进行修改,然后重新编译,直到成功。
```sh
# 示例Makefile片段
obj-m += camera_driver.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
```
### 4.1.2 调试工具和性能分析工具的使用
在开发过程中,调试是不可或缺的环节。MTK提供了专门的调试工具,如MTK Logger、MTK Logview等,这些工具可以用来捕捉驱动运行过程中的日志信息,帮助开发者理解驱动的行为。
#### 使用MTK Logger
MTK Logger可以捕捉系统层面的日志,包括 CAMERA 驱动的运行日志。使用方法如下:
1. 连接设备,并在设备上启动MTK Logger。
2. 执行你的应用程序或操作,生成日志。
3. 使用Logview解析日志文件,查看详细信息。
```sh
# 示例命令
mtklogger -l camera_log.log
```
#### 使用性能分析工具
性能分析工具帮助开发者识别和优化性能瓶颈。例如,使用`perf`工具可以对驱动模块进行性能剖析,查看CPU资源的使用情况,找出运行缓慢或消耗资源较多的函数。
```sh
# 示例使用perf进行性能分析
sudo perf top -p <pid> # 查看指定进程的性能热点
```
## 4.2 开发中的常见问题与解决
### 4.2.1 硬件兼容性问题的排查与解决
在MTK CAMERA驱动开发过程中,硬件兼容性问题是最常见的挑战之一。不同厂商或不同型号的相机模块可能会在接口和性能上有所差异。当遇到兼容性问题时,首先需要确认问题是由硬件故障引起,还是驱动代码不匹配所导致。
#### 硬件故障排查
硬件故障排查一般遵循以下步骤:
1. 检查物理连接:确认所有的连接器和接口都已正确安装并且没有松动或损坏。
2. 使用硬件诊断工具:如果有可能,使用MTK提供的硬件诊断工具,对相机模块进行检测。
```sh
# 示例使用MTK硬件诊断工具的命令
mtkhwdiag -d /dev/video0 -t camera
```
#### 驱动代码匹配
如果确认硬件工作正常,那么问题很可能出在驱动代码上。此时,需要检查以下几个方面:
1. 确认驱动代码是否与硬件规格书完全匹配。
2. 使用调试工具查看驱动日志,看是否有异常的错误输出。
3. 尝试更新驱动中的固件版本,因为有些问题可能已经在固件层面得到修复。
### 4.2.2 驱动与应用层接口对接问题
驱动开发不仅要关注底层硬件和内核的交互,还要确保与应用层的接口对接正确。应用层通常通过设备文件进行与驱动的通信,因此确保设备文件的创建和配置正确是关键。
#### 设备文件的创建与配置
设备文件是应用层与内核驱动通信的桥梁。创建设备文件需要在驱动的初始化代码中进行:
```c
// 创建设备文件示例代码
int major = register_chrdev(0, DEVICE_NAME, &fops);
if (major < 0) {
printk(KERN_ALERT DEVICE_NAME ": failed to register a major number\n");
return major;
}
dev_class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(dev_class)) {
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_ALERT DEVICE_NAME ": Failed to register device class\n");
return PTR_ERR(dev_class);
}
// 创建设备
if (device_create(dev_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME) == NULL) {
class_destroy(dev_class);
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_ALERT DEVICE_NAME ": Failed to create the device\n");
return -1;
}
```
对于设备文件的配置,通常需要在驱动中定义操作函数集(file_operations),这些函数将响应来自应用层的调用。
```c
// file_operations结构示例
const struct file_operations fops = {
.owner = THIS_MODULE,
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write,
// ... 其他必要的函数指针
};
```
## 4.3 驱动的扩展与定制化
### 4.3.1 驱动功能的扩展和模块化设计
随着技术的发展和用户需求的增加,驱动功能的扩展和定制化需求也日益增多。模块化设计是驱动开发中常采用的方式,它允许驱动在不修改核心代码的前提下增加新功能。
#### 驱动功能扩展实践
在模块化设计中,开发者可以将驱动功能拆分成多个独立模块,比如图像处理模块、格式转换模块、缓冲区管理模块等。这样做的好处是,每个模块可以单独开发和测试,大大降低了复杂度。
```c
// 示例:模块化的驱动结构
struct camera_driver {
struct camera_image_processing_module image_processing;
struct camera_format_conversion_module format_conversion;
struct camera_buffer_management_module buffer_management;
// ... 其他模块
};
```
在进行模块化设计时,应当遵循以下原则:
1. 高内聚:每个模块应当是单一职责的,完成一个明确的任务。
2. 低耦合:模块之间应尽可能减少依赖,便于独立开发和替换。
3. 稳定的接口:模块之间交互的接口应当稳定,减少因接口变动带来的风险。
### 4.3.2 定制化需求的开发实践
定制化需求通常来自于设备制造商或者终端用户。开发者需要根据需求进行分析,然后在现有驱动的基础上进行调整和优化。
#### 定制化开发步骤
1. 需求分析:详细了解定制化需求的具体内容和目标。
2. 方案设计:设计出能够满足需求的解决方案,包括改动驱动的哪些部分。
3. 代码实现:根据设计进行代码编写和实现。
4. 测试验证:在目标设备上进行充分的测试,确保定制化功能的稳定和性能。
```c
// 示例:根据定制化需求修改驱动配置
int custom_configuration(struct camera_driver *driver) {
// 根据需求调整图像处理参数
driver->image_processing.param = CUSTOM_PARAM_VALUE;
return 0;
}
```
在定制化开发过程中,代码的可维护性是需要特别关注的问题。修改驱动代码时应当尽量保持代码的清晰和结构化,以便后续的维护和升级。
# 5. MTK CAMERA驱动进阶应用与展望
## 驱动的进阶功能实现
### 高级图像处理算法的集成
随着用户对于拍照和视频录制功能要求的不断提高,MTK CAMERA驱动需要集成更高级的图像处理算法以提供更好的拍摄体验。这些算法通常包括自动曝光(AE)、自动白平衡(AWB)、自动对焦(AF)、降噪、HDR、夜景模式等。集成这些算法,可以极大地增强图像质量,提升用户的满意度。
例如,自动曝光算法需要实时分析场景亮度,并动态调整传感器的曝光时间,以获得正确的曝光。而自动白平衡算法则需要根据光源的色温和场景内容,动态调整颜色增益,使图像色彩更自然。
```c
// 伪代码示例:高级图像处理算法集成
void integrate_image_processing_algorithms(struct camera_data *data) {
ae_algorithm(data);
awb_algorithm(data);
af_algorithm(data);
// 其他算法集成
}
```
开发者需要与图像算法工程师紧密合作,确保这些算法能够在MTK CAMERA驱动中得到高效的执行。算法的集成可能涉及到修改驱动的初始化流程,添加新的算法处理模块,以及优化算法处理以减少对系统性能的影响。
### 驱动安全性加固和漏洞修复
随着移动设备在日常生活中扮演越来越重要的角色,设备的安全性变得至关重要。MTK CAMERA驱动作为相机功能的重要组成部分,其安全性同样不容忽视。驱动安全性加固包括但不限于防止缓冲区溢出、防范权限提升攻击、保护用户隐私和数据。
漏洞修复则是指识别并修补驱动中的已知漏洞。由于驱动程序通常运行在内核空间,任何安全漏洞都可能导致系统级别的安全威胁。因此,需要周期性地进行代码审查、漏洞扫描和安全测试。
```c
// 伪代码示例:驱动安全性检查
bool check_driver_security(struct camera_data *data) {
bool is_secure = true;
is_secure = buffer_overflow_check(data);
if (!is_secure) {
// 修复缓冲区溢出问题
}
is_secure = privilege_escalation_check(data);
if (!is_secure) {
// 修复权限提升问题
}
// 其他安全检查
return is_secure;
}
```
## 未来趋势与技术演进
### 人工智能与机器学习在驱动中的应用
人工智能(AI)和机器学习(ML)技术的快速发展为MTK CAMERA驱动带来了新的可能性。例如,通过机器学习算法,相机可以学习并识别拍摄场景,从而智能地调整拍摄参数。这种智能拍摄模式通常比传统的拍摄模式更加准确和高效。
此外,AI还可以用于图像识别和场景理解,如人脸识别、场景分类、对象跟踪等。这些功能可以极大地增强用户体验,例如,通过场景理解自动选择最佳的拍摄模式,或者通过人脸识别技术来增强照片效果。
```c
// 伪代码示例:AI场景理解
void ai_scene_recognition(struct camera_data *data) {
enum scene_type detected_scene = AI_analyze_scene(data);
// 根据检测到的场景类型调整相机参数
adjust_camera_settings_for_scene(data, detected_scene);
}
```
### 驱动架构的未来发展与设计原则
未来MTK CAMERA驱动的发展,将更加注重模块化、灵活性和可扩展性。设计时要遵循一些基本原则,比如:
1. 模块化:驱动应设计成模块化,以便于未来的功能扩展和维护。
2. 兼容性:必须保证驱动对不同硬件平台的支持,包括新的硬件技术。
3. 性能:提高数据处理的效率,减少CPU的负载,降低功耗。
4. 安全:加强安全机制,防止潜在的安全威胁和漏洞利用。
5. 用户体验:优化用户界面,提高易用性,提供丰富的拍摄模式和自定义选项。
驱动架构的设计需要前瞻性地考虑未来技术的发展,确保驱动能够适应技术进步和市场变化的需求。随着新技术的不断涌现,MTK CAMERA驱动也需要不断地进行迭代和优化,以满足日益增长的用户需求。
0
0