4. 关闭摄像头设备

任务说明

本案例实现Camera HAL层设备关闭流程:通过close()函数释放已打开的设备资源(包括内存映射、请求队列、硬件连接),并将设备状态置为关闭。该函数对应HAL接口中的close_device()回调,是摄像头设备生命周期管理的收尾环节。

核心实现

// camera_hal_device.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>

#define CAMERA_DEVICE_PATH   "/dev/video0"
#define CAMERA_BUFFER_COUNT  4
#define CAMERA_BUFFER_SIZE   (1920 * 1080 * 2)  // YUYV格式

typedef struct {
    int fd;                    // 设备文件描述符
    void *buffers[4];          // mmap缓冲区地址
    size_t buffer_lengths[4];  // 各缓冲区长度
    int buffer_count;          // 已映射缓冲区数量
    int is_streaming;          // 是否处于流式传输状态
    int is_initialized;        // 设备是否已初始化
} camera_device_t;

// 停止视频流采集
static int camera_stop_streaming(camera_device_t *dev) {
    if (!dev || !dev->is_streaming) {
        return 0;
    }
    
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(dev->fd, VIDIOC_STREAMOFF, &type) < 0) {
        perror("VIDIOC_STREAMOFF failed");
        return -1;
    }
    
    dev->is_streaming = 0;
    printf("Video streaming stopped\n");
    return 0;
}

// 释放mmap缓冲区
static void camera_release_buffers(camera_device_t *dev) {
    if (!dev) {
        return;
    }
    
    for (int i = 0; i < dev->buffer_count; i++) {
        if (dev->buffers[i] != MAP_FAILED && dev->buffers[i] != NULL) {
            munmap(dev->buffers[i], dev->buffer_lengths[i]);
            dev->buffers[i] = NULL;
            dev->buffer_lengths[i] = 0;
            printf("Buffer %d unmapped\n", i);
        }
    }
    dev->buffer_count = 0;
}

// 关闭摄像头设备(HAL层close接口)
int camera_device_close(camera_device_t *dev) {
    if (!dev) {
        return -EINVAL;
    }
    
    if (!dev->is_initialized) {
        printf("Device already closed\n");
        free(dev);
        return 0;
    }
    
    printf("Closing camera device...\n");
    
    // 1. 停止流式传输(若正在采集)
    if (camera_stop_streaming(dev) < 0) {
        // 即使停止失败也继续执行资源释放
        fprintf(stderr, "Warning: failed to stop streaming\n");
    }
    
    // 2. 释放mmap缓冲区
    camera_release_buffers(dev);
    
    // 3. 关闭设备文件描述符
    if (dev->fd >= 0) {
        close(dev->fd);
        dev->fd = -1;
        printf("Device fd closed\n");
    }
    
    // 4. 标记设备为未初始化
    dev->is_initialized = 0;
    
    // 5. 释放设备结构体内存
    free(dev);
    printf("Camera device closed successfully\n");
    
    return 0;
}

// 辅助函数:打开设备(用于测试close的对称性)
camera_device_t *camera_device_open(void) {
    camera_device_t *dev = calloc(1, sizeof(camera_device_t));
    if (!dev) {
        return NULL;
    }
    
    dev->fd = open(CAMERA_DEVICE_PATH, O_RDWR);
    if (dev->fd < 0) {
        perror("Failed to open camera device");
        free(dev);
        return NULL;
    }
    
    // 实际项目中此处会完成:查询能力、设置格式、请求缓冲区、mmap映射
    // 本示例仅模拟初始化状态,便于验证close流程
    dev->is_initialized = 1;
    dev->is_streaming = 0;
    dev->buffer_count = 0;
    
    printf("Camera device opened (fd=%d)\n", dev->fd);
    return dev;
}

// 测试入口
int main(void) {
    // 打开设备
    camera_device_t *dev = camera_device_open();
    if (!dev) {
        return -1;
    }
    
    // 执行关闭操作
    int ret = camera_device_close(dev);
    if (ret == 0) {
        printf("Test PASSED: device closed successfully\n");
    } else {
        printf("Test FAILED: close returned %d\n", ret);
    }
    
    return ret;
}

关键设计说明

camera_device_close()按以下顺序完成资源释放:

  1. 停止流式传输:若设备正在采集视频流,先发送VIDIOC_STREAMOFF停止硬件采集,避免关闭过程中仍有DMA操作访问缓冲区。
  2. 释放mmap缓冲区:调用munmap()解除用户空间与内核缓冲区的映射关系,防止内存泄漏。
  3. 关闭文件描述符:通过close()释放设备节点占用的文件描述符,断开与内核驱动的连接。
  4. 释放结构体内存:使用free()回收HAL层维护的设备上下文。

函数通过is_initialized标志防止重复关闭,并在每个步骤输出日志便于跟踪执行路径。

编译与验证

# 编译(需Linux内核头文件支持V4L2定义)
gcc -o camera_close_test camera_hal_device.c -I/usr/include

# 运行测试(需root权限访问/dev/video0)
sudo ./camera_close_test

预期输出(在设备节点存在且可访问时):

Camera device opened (fd=3)
Closing camera device...
Device fd closed
Camera device closed successfully
Test PASSED: device closed successfully

判定标准:

  • 程序退出码为0,且打印Test PASSED;
  • 若设备节点不存在,open()失败返回错误信息,测试终止;
  • 若设备正在采集时关闭,日志中应出现Video streaming stopped;
  • 使用valgrind --leak-check=full ./camera_close_test检查无内存泄漏。