【RT-Thread Titan Board 开发板】显示SD卡上JPEG图片的EXIF信息

目录

引言

运行测试程序

EXIF信息的显示

模块设计

EXIF解析模块(exif_parser)

EXIF标签支持

工程集成说明

需要添加的文件

需要修改的文件

集成步骤

核心代码

exif_cmd.c

exif_parser.c

exif_parser.h

SConscript

关键实现要点

流式读取设计

堆分配避免栈溢出

精确的偏移量计算

IFD条目值定位

运行结果

总结


引言

测试套件中虽然有摄像头,但是没有屏幕,所以暂时还无法测试视觉相关的内容,只能等屏幕到货之后再说。今天测试一下SD卡的功能,这个也是我此次测试需要的。

SD卡的插槽在开发板的背面:

运行测试程序

官方提供了一个Titan_driver_sdcard的例子,我先编译运行一下。

程序运行之后,正确的识别了我的SD卡,进入sdcard目录后,也可以列出所有子目录。不过df命令没有实现,所以无法看剩余的空间。

EXIF信息的显示

接下来在当前的RT-Thread工程中实现一个EXIF信息显示命令,用于显示SD卡中图片文件的EXIF元数据信息。该命令支持JPEG格式图片,能够解析并显示相机参数、拍摄时间、GPS信息等EXIF标签。

有两个技术方案可以考虑:

  • 方案一:使用轻量级EXIF解析库(如libexif或tinyexif)
  • 方案二:实现简化的EXIF解析器,仅支持常用标签

这里采用方案二,实现精简版EXIF解析器,减少代码体积和内存占用。

我们将使用RT-Thread的MSH(Micro Shell)命令系统,增加一个命令exif,其命令格式:

复制代码
exif <图片文件路径>

模块设计

增加如下模块:

exif_cmd/

├── exif_parser.c # EXIF解析核心模块

├── exif_parser.h # EXIF解析头文件

├── exif_cmd.c # MSH命令实现

└── SConscript # 构建脚本

EXIF解析模块(exif_parser)

该模块功能是解析JPEG文件的EXIF数据段,其主要接口:

  • exif_parse_file(const char* filepath, exif_data_t* data):解析指定文件的EXIF信息
  • exif_print_info(const exif_data_t* data):打印EXIF信息

MSH命令模块(exif_cmd)

该模块功能是实现MSH命令接口,其主要接口:

  • exif(int argc, char** argv):命令处理函数
  • 使用 `MSH_CMD_EXPORT` 宏导出命令

EXIF标签支持

基础标签(IFD0)包括:

  • 相机厂商(Make)
  • 相机型号(Model)

EXIF子IFD标签包括:

  • 拍摄时间(DateTimeOriginal)
  • 曝光时间(ExposureTime)
  • 光圈值(FNumber)
  • ISO感光度(ISOSpeedRatings)
  • 焦距(FocalLength)
  • 闪光灯(Flash)

工程集成说明

需要添加的文件

复制代码
board/ports/exif_cmd/
├── exif_parser.c      # EXIF解析核心模块
├── exif_parser.h      # EXIF解析头文件
├── exif_cmd.c         # MSH命令实现
└── SConscript         # 构建脚本

需要修改的文件

board/Kconfig - 已添加以下配置项:

复制代码
config BSP_USING_EXIF_CMD
    bool "Enable EXIF command"
    select BSP_USING_FILESYSTEM
    default n
    help
        Enable EXIF information display command for image files

board/ports/SConscript - 无需修改(已自动扫描子目录)

集成步骤

  1. 确认文件已添加 board/ports/exif_cmd/ 目录及所有文件已创建

  2. 重新配置工程

    menuconfig

启用:Hardware Drivers Config → Onboard Peripheral Drivers → Enable EXIF command

  1. 重新编译

  2. 烧录并测试

核心代码

exif_cmd.c

cpp 复制代码
#include "exif_parser.h"
#include <rtthread.h>
#include <finsh.h>

static void exif(int argc, char **argv)
{
    if (argc != 2) {
        rt_kprintf("Usage: exif <image_file_path>\n");
        rt_kprintf("Example: exif /sdcard/photo.jpg\n");
        return;
    }
    
    const char *filepath = argv[1];
    
    exif_data_t exif_data;
    int ret = exif_parse_file(filepath, &exif_data);
    
    if (ret != RT_EOK) {
        switch (ret) {
            case -RT_ENOENT:
                rt_kprintf("Error: File not found: %s\n", filepath);
                break;
            case -RT_EIO:
                rt_kprintf("Error: Failed to read file: %s\n", filepath);
                break;
            case -RT_ENOMEM:
                rt_kprintf("Error: Not enough memory\n");
                break;
            case -RT_EINVAL:
                rt_kprintf("Error: Invalid JPEG file\n");
                break;
            case -RT_ERROR:
                rt_kprintf("Error: No EXIF data found or invalid EXIF format\n");
                break;
            default:
                rt_kprintf("Error: Failed to parse EXIF data (code: %d)\n", ret);
                break;
        }
        return;
    }
    
    ret = exif_print_info(&exif_data);
    if (ret != RT_EOK) {
        rt_kprintf("Error: Failed to print EXIF information\n");
    }
}
MSH_CMD_EXPORT(exif, Display EXIF information of image file);

exif_parser.c

cpp 复制代码
#include "exif_parser.h"
#include <dfs_file.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define JPEG_SOI 0xFFD8
#define JPEG_APP1 0xFFE1
#define EXIF_ID "Exif\x00\x00"
#define TIFF_LITTLE_ENDIAN 0x4949
#define TIFF_BIG_ENDIAN 0x4D4D
#define SCAN_BUFFER_SIZE 1024

typedef struct {
    int fd;
    uint8_t *buffer;
    uint32_t buffer_size;
    uint32_t buffer_offset;
    uint32_t file_offset;
    uint32_t buffer_start;
    int is_little_endian;
} exif_reader_t;

typedef struct {
    uint16_t tag;
    uint16_t type;
    uint32_t count;
    uint32_t value_offset;
} ifd_entry_t;

static int read_data(exif_reader_t *reader, uint32_t offset, uint8_t *data, uint32_t size)
{
    uint32_t file_offset = reader->file_offset + offset;
    
    if (lseek(reader->fd, file_offset, SEEK_SET) < 0) {
        return -RT_EIO;
    }
    
    if (read(reader->fd, data, size) != size) {
        return -RT_EIO;
    }
    
    return RT_EOK;
}

static uint16_t read_u16(exif_reader_t *reader, uint32_t offset)
{
    uint8_t data[2];
    if (read_data(reader, offset, data, 2) != RT_EOK) {
        return 0;
    }
    
    if (reader->is_little_endian) {
        return data[0] | (data[1] << 8);
    } else {
        return (data[0] << 8) | data[1];
    }
}

static uint32_t read_u32(exif_reader_t *reader, uint32_t offset)
{
    uint8_t data[4];
    if (read_data(reader, offset, data, 4) != RT_EOK) {
        return 0;
    }
    
    if (reader->is_little_endian) {
        return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
    } else {
        return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
    }
}

static int find_exif_marker(int fd, uint32_t *exif_offset, uint32_t *exif_size)
{
    uint8_t *buffer = rt_malloc(SCAN_BUFFER_SIZE);
    if (buffer == RT_NULL) {
        return -RT_ENOMEM;
    }
    
    uint32_t file_offset = 2;
    uint32_t bytes_read;
    int ret = -RT_ERROR;
    
    while (1) {
        if (lseek(fd, file_offset, SEEK_SET) < 0) {
            ret = -RT_EIO;
            break;
        }
        
        bytes_read = read(fd, buffer, SCAN_BUFFER_SIZE);
        if (bytes_read < 4) {
            break;
        }
        
        for (uint32_t i = 0; i + 4 <= bytes_read; ) {
            uint16_t marker = (buffer[i] << 8) | buffer[i + 1];
            
            if (marker == JPEG_APP1) {
                if (i + 10 <= bytes_read && 
                    memcmp(buffer + i + 4, EXIF_ID, 6) == 0) {
                    *exif_offset = file_offset + i + 10;
                    *exif_size = (buffer[i + 2] << 8) | buffer[i + 3];
                    if (*exif_size > 8) {
                        *exif_size -= 8;
                    }
                    ret = RT_EOK;
                    break;
                }
            }
            
            if (marker == 0xFFDA) {
                ret = -RT_ERROR;
                break;
            }
            
            if (i + 4 <= bytes_read) {
                uint16_t length = (buffer[i + 2] << 8) | buffer[i + 3];
                if (length < 2) {
                    length = 2;
                }
                i += length;
            } else {
                i++;
            }
        }
        
        if (ret == RT_EOK || ret == -RT_EIO) {
            break;
        }
        
        file_offset += bytes_read;
    }
    
    rt_free(buffer);
    return ret;
}

static int parse_tiff_header(exif_reader_t *reader, uint32_t tiff_offset, uint32_t *ifd_offset)
{
    uint16_t byte_order = read_u16(reader, tiff_offset);
    if (byte_order == TIFF_LITTLE_ENDIAN) {
        reader->is_little_endian = 1;
    } else if (byte_order == TIFF_BIG_ENDIAN) {
        reader->is_little_endian = 0;
    } else {
        return -RT_ERROR;
    }
    
    uint16_t magic = read_u16(reader, tiff_offset + 2);
    if (magic != 42) {
        return -RT_ERROR;
    }
    
    *ifd_offset = read_u32(reader, tiff_offset + 4);
    
    if (*ifd_offset == 0 || *ifd_offset > reader->buffer_size) {
        return -RT_ERROR;
    }
    
    return RT_EOK;
}

static void read_string_value(exif_reader_t *reader, uint32_t offset, uint32_t count, char *dest, uint32_t max_len)
{
    uint32_t len = count;
    if (len >= max_len) {
        len = max_len - 1;
    }
    
    if (read_data(reader, offset, (uint8_t *)dest, len) == RT_EOK) {
        dest[len] = '\0';
    } else {
        dest[0] = '\0';
    }
}

static void read_rational_value(exif_reader_t *reader, uint32_t offset, char *dest, uint32_t max_len)
{
    uint32_t numerator = read_u32(reader, offset);
    uint32_t denominator = read_u32(reader, offset + 4);
    
    if (denominator == 0) {
        rt_snprintf(dest, max_len, "0");
    } else {
        rt_snprintf(dest, max_len, "%u/%u", numerator, denominator);
    }
}

static void parse_ifd_entry(exif_reader_t *reader, uint32_t offset, ifd_entry_t *entry)
{
    entry->tag = read_u16(reader, offset);
    entry->type = read_u16(reader, offset + 2);
    entry->count = read_u32(reader, offset + 4);
    entry->value_offset = read_u32(reader, offset + 8);
}

static int parse_ifd(exif_reader_t *reader, uint32_t ifd_offset, exif_data_t *data, 
                     uint32_t *exif_ifd_offset, uint32_t *gps_ifd_offset)
{
    uint16_t num_entries = read_u16(reader, ifd_offset);
    
    for (uint16_t i = 0; i < num_entries; i++) {
        uint32_t entry_offset = ifd_offset + 2 + i * 12;
        ifd_entry_t entry;
        parse_ifd_entry(reader, entry_offset, &entry);
        
        uint32_t value_size = 0;
        switch (entry.type) {
            case 1: value_size = entry.count * 1; break;
            case 2: value_size = entry.count; break;
            case 3: value_size = entry.count * 2; break;
            case 4: value_size = entry.count * 4; break;
            case 5: value_size = entry.count * 8; break;
        }
        
        uint32_t value_offset = (value_size <= 4) ? entry_offset + 8 : entry.value_offset;
        
        switch (entry.tag) {
            case 0x010F:
                if (entry.type == 2 && entry.count < 64) {
                    read_string_value(reader, value_offset, entry.count, data->make, sizeof(data->make));
                }
                break;
            case 0x0110:
                if (entry.type == 2 && entry.count < 64) {
                    read_string_value(reader, value_offset, entry.count, data->model, sizeof(data->model));
                }
                break;
            case 0x8769:
                *exif_ifd_offset = entry.value_offset;
                break;
            case 0x8825:
                *gps_ifd_offset = entry.value_offset;
                break;
        }
    }
    
    return RT_EOK;
}

static int parse_exif_ifd(exif_reader_t *reader, uint32_t ifd_offset, exif_data_t *data)
{
    uint16_t num_entries = read_u16(reader, ifd_offset);
    
    for (uint16_t i = 0; i < num_entries; i++) {
        uint32_t entry_offset = ifd_offset + 2 + i * 12;
        ifd_entry_t entry;
        parse_ifd_entry(reader, entry_offset, &entry);
        
        uint32_t value_size = 0;
        switch (entry.type) {
            case 1: value_size = entry.count * 1; break;
            case 2: value_size = entry.count; break;
            case 3: value_size = entry.count * 2; break;
            case 4: value_size = entry.count * 4; break;
            case 5: value_size = entry.count * 8; break;
        }
        
        uint32_t value_offset = (value_size <= 4) ? entry_offset + 8 : entry.value_offset;
        
        switch (entry.tag) {
            case 0x829A:
                if (entry.type == 5) {
                    read_rational_value(reader, value_offset, data->exposure_time, sizeof(data->exposure_time));
                }
                break;
            case 0x829D:
                if (entry.type == 5) {
                    read_rational_value(reader, value_offset, data->f_number, sizeof(data->f_number));
                }
                break;
            case 0x8827:
                if (entry.type == 3) {
                    data->iso = (uint16_t)value_offset;
                }
                break;
            case 0x920A:
                if (entry.type == 5) {
                    read_rational_value(reader, value_offset, data->focal_length, sizeof(data->focal_length));
                }
                break;
            case 0x9003:
                if (entry.type == 2 && entry.count < 32) {
                    read_string_value(reader, value_offset, entry.count, data->datetime, sizeof(data->datetime));
                }
                break;
            case 0x9209:
                if (entry.type == 3) {
                    data->flash = (uint16_t)value_offset;
                }
                break;
        }
    }
    
    return RT_EOK;
}

static int parse_gps_ifd(exif_reader_t *reader, uint32_t ifd_offset, exif_data_t *data)
{
    uint16_t num_entries = read_u16(reader, ifd_offset);
    
    char lat_ref = 'N';
    char lon_ref = 'E';
    uint32_t lat_offset = 0;
    uint32_t lon_offset = 0;
    uint32_t alt_offset = 0;
    
    for (uint16_t i = 0; i < num_entries; i++) {
        uint32_t entry_offset = ifd_offset + 2 + i * 12;
        ifd_entry_t entry;
        parse_ifd_entry(reader, entry_offset, &entry);
        
        uint32_t value_size = 0;
        switch (entry.type) {
            case 1: value_size = entry.count * 1; break;
            case 2: value_size = entry.count; break;
            case 3: value_size = entry.count * 2; break;
            case 4: value_size = entry.count * 4; break;
            case 5: value_size = entry.count * 8; break;
        }
        
        uint32_t value_offset = (value_size <= 4) ? entry_offset + 8 : entry.value_offset;
        
        switch (entry.tag) {
            case 0x0001:
                if (entry.type == 2 && entry.count > 0) {
                    uint8_t ref;
                    if (read_data(reader, value_offset, &ref, 1) == RT_EOK) {
                        lat_ref = ref;
                    }
                }
                break;
            case 0x0002:
                if (entry.type == 5 && entry.count == 3) {
                    lat_offset = value_offset;
                }
                break;
            case 0x0003:
                if (entry.type == 2 && entry.count > 0) {
                    uint8_t ref;
                    if (read_data(reader, value_offset, &ref, 1) == RT_EOK) {
                        lon_ref = ref;
                    }
                }
                break;
            case 0x0004:
                if (entry.type == 5 && entry.count == 3) {
                    lon_offset = value_offset;
                }
                break;
            case 0x0006:
                if (entry.type == 5) {
                    alt_offset = value_offset;
                }
                break;
        }
    }
    
    if (lat_offset != 0) {
        uint32_t deg_num = read_u32(reader, lat_offset);
        uint32_t deg_den = read_u32(reader, lat_offset + 4);
        uint32_t min_num = read_u32(reader, lat_offset + 8);
        uint32_t min_den = read_u32(reader, lat_offset + 12);
        uint32_t sec_num = read_u32(reader, lat_offset + 16);
        uint32_t sec_den = read_u32(reader, lat_offset + 20);
        
        if (deg_den != 0 && min_den != 0 && sec_den != 0) {
            rt_snprintf(data->gps_latitude, sizeof(data->gps_latitude), 
                       "%c %u°%u'%u\"", lat_ref,
                       deg_num / deg_den,
                       min_num / min_den,
                       sec_num / sec_den);
        }
    }
    
    if (lon_offset != 0) {
        uint32_t deg_num = read_u32(reader, lon_offset);
        uint32_t deg_den = read_u32(reader, lon_offset + 4);
        uint32_t min_num = read_u32(reader, lon_offset + 8);
        uint32_t min_den = read_u32(reader, lon_offset + 12);
        uint32_t sec_num = read_u32(reader, lon_offset + 16);
        uint32_t sec_den = read_u32(reader, lon_offset + 20);
        
        if (deg_den != 0 && min_den != 0 && sec_den != 0) {
            rt_snprintf(data->gps_longitude, sizeof(data->gps_longitude), 
                       "%c %u°%u'%u\"", lon_ref,
                       deg_num / deg_den,
                       min_num / min_den,
                       sec_num / sec_den);
        }
    }
    
    if (alt_offset != 0) {
        uint32_t num = read_u32(reader, alt_offset);
        uint32_t den = read_u32(reader, alt_offset + 4);
        
        if (den != 0) {
            rt_snprintf(data->gps_altitude, sizeof(data->gps_altitude), "%um", num / den);
        }
    }
    
    return RT_EOK;
}

int exif_parse_file(const char *filepath, exif_data_t *data)
{
    int fd = open(filepath, O_RDONLY, 0);
    if (fd < 0) {
        return -RT_ENOENT;
    }
    
    uint8_t header[2];
    if (read(fd, header, 2) != 2) {
        close(fd);
        return -RT_EIO;
    }
    
    uint16_t soi = (header[0] << 8) | header[1];
    if (soi != JPEG_SOI) {
        close(fd);
        return -RT_EINVAL;
    }
    
    uint32_t exif_offset, exif_size;
    int ret = find_exif_marker(fd, &exif_offset, &exif_size);
    if (ret != RT_EOK) {
        close(fd);
        return ret;
    }
    
    exif_reader_t reader;
    reader.fd = fd;
    reader.buffer_size = exif_size;
    reader.buffer_offset = 0;
    reader.file_offset = exif_offset;
    reader.buffer_start = 0;
    reader.is_little_endian = 0;
    
    uint32_t ifd_offset;
    ret = parse_tiff_header(&reader, 0, &ifd_offset);
    if (ret != RT_EOK) {
        close(fd);
        return ret;
    }
    
    rt_memset(data, 0, sizeof(exif_data_t));
    
    uint32_t exif_ifd_offset = 0;
    uint32_t gps_ifd_offset = 0;
    
    parse_ifd(&reader, ifd_offset, data, &exif_ifd_offset, &gps_ifd_offset);
    
    if (exif_ifd_offset != 0) {
        parse_exif_ifd(&reader, exif_ifd_offset, data);
    }
    
    if (gps_ifd_offset != 0) {
        parse_gps_ifd(&reader, gps_ifd_offset, data);
    }
    
    close(fd);
    return RT_EOK;
}

int exif_print_info(const exif_data_t *data)
{
    rt_kprintf("EXIF Information:\n");
    rt_kprintf("==================\n");
    
    if (data->make[0] != '\0') {
        rt_kprintf("Make: %s\n", data->make);
    }
    
    if (data->model[0] != '\0') {
        rt_kprintf("Model: %s\n", data->model);
    }
    
    if (data->datetime[0] != '\0') {
        rt_kprintf("DateTime: %s\n", data->datetime);
    }
    
    if (data->exposure_time[0] != '\0') {
        rt_kprintf("ExposureTime: %s sec\n", data->exposure_time);
    }
    
    if (data->f_number[0] != '\0') {
        rt_kprintf("FNumber: f/%s\n", data->f_number);
    }
    
    if (data->iso != 0) {
        rt_kprintf("ISO: %u\n", data->iso);
    }
    
    if (data->focal_length[0] != '\0') {
        rt_kprintf("FocalLength: %s mm\n", data->focal_length);
    }
    
    if (data->flash != 0) {
        rt_kprintf("Flash: %s\n", data->flash & 1 ? "On" : "Off");
    }
    
    if (data->gps_latitude[0] != '\0' || data->gps_longitude[0] != '\0') {
        rt_kprintf("GPS: %s %s", data->gps_latitude, data->gps_longitude);
        if (data->gps_altitude[0] != '\0') {
            rt_kprintf(" Alt: %s", data->gps_altitude);
        }
        rt_kprintf("\n");
    }
    
    return RT_EOK;
}

exif_parser.h

cpp 复制代码
#ifndef __EXIF_PARSER_H__
#define __EXIF_PARSER_H__

#include <rtthread.h>
#include <stdint.h>

#define EXIF_MAX_FILE_SIZE (100 * 1024 * 1024)
#define EXIF_SCAN_BUFFER_SIZE 4096

typedef struct {
    char make[64];
    char model[64];
    char datetime[32];
    char exposure_time[32];
    char f_number[32];
    uint16_t iso;
    char focal_length[32];
    uint16_t flash;
    char gps_latitude[64];
    char gps_longitude[64];
    char gps_altitude[32];
} exif_data_t;

int exif_parse_file(const char *filepath, exif_data_t *data);
int exif_print_info(const exif_data_t *data);

#endif

SConscript

python 复制代码
from building import *

src   = []
cwd   = GetCurrentDir()

# add exif_cmd src files.
src += Glob('*.c')

# add exif_cmd include path.
path  = [cwd]

# add src and include to group.
group = DefineGroup('exif_cmd', src, depend = ['BSP_USING_EXIF_CMD'], CPPPATH = path)

Return('group')

关键实现要点

在资源受限的嵌入式系统中实现EXIF解析,主要面临以下挑战:

  1. 内存限制 :嵌入式系统内存有限,无法一次性加载大文件

  2. 栈空间不足 :大缓冲区会导致栈溢出

  3. 复杂的文件格式 :EXIF数据嵌套在JPEG文件中,需要多层解析

  4. 偏移量计算 :不同层级的偏移量基准点不同,容易出错

流式读取设计

传统方法会一次性读取整个文件到内存,这在嵌入式系统中是不可行的。我们采用流式读取方案:

cpp 复制代码
uint8_t *buffer = rt_malloc(BUFFER_SIZE);
while (read(fd, buffer, BUFFER_SIZE) > 0) {
    if (find_exif_marker(buffer, BUFFER_SIZE, &offset)) {
        break;
    }
}

优势 :

  • 内存占用恒定(1KB缓冲区)

  • 支持任意大小的文件

  • 避免内存溢出

堆分配避免栈溢出

初始实现使用栈分配大缓冲区,导致硬件故障:

cpp 复制代码
uint8_t buffer[4096];  // 栈溢出!

解决方案 :改用堆分配:

cpp 复制代码
uint8_t *buffer = rt_malloc(BUFFER_SIZE);
if (buffer == NULL) {
    return -RT_ENOMEM;
}
// 使用buffer...
rt_free(buffer);

关键点 :

  • 嵌入式系统栈空间通常只有几KB

  • 大缓冲区必须使用堆分配

  • 使用后必须及时释放

精确的偏移量计算

EXIF格式采用多层嵌套结构,每层都有自己的偏移量基准:

文件起始

└─ JPEG SOI标记

└─ APP1段(EXIF数据)

└─ TIFF头

└─ IFD0(基础IFD)

└─ 子IFD(EXIF IFD、GPS IFD)

cpp 复制代码
static int read_data(exif_reader_t *reader, uint32_t offset, 
                     uint8_t *data, uint32_t size)
{
    uint32_t file_offset = reader->file_offset + offset;
    
    if (lseek(reader->fd, file_offset, SEEK_SET) < 0) {
        return -RT_EIO;
    }
    
    if (read(reader->fd, data, size) != size) {
        return -RT_EIO;
    }
    
    return RT_EOK;
}

IFD条目值定位

EXIF IFD条目中的值有两种存储方式:

  • 值大小 ≤ 4字节:值直接存储在条目的最后4字节
  • 值大小 > 4字节:值存储在value_offset指向的位置
cpp 复制代码
uint32_t value_size = 0;
switch (entry.type) {
    case 1: value_size = entry.count * 1; break;  // BYTE
    case 2: value_size = entry.count; break;       // ASCII
    case 3: value_size = entry.count * 2; break;   // SHORT
    case 4: value_size = entry.count * 4; break;   // LONG
    case 5: value_size = entry.count * 8; break;   // RATIONAL
}

uint32_t value_offset = (value_size <= 4) ? 
    entry_offset + 8 : entry.value_offset;

运行结果

程序的运行结果如下:

cpp 复制代码
 \ | /
- RT -     Thread Operating System
 / | \     5.1.0 build Jan 26 2026 19:38:57
 2006 - 2024 Copyright by RT-Thread team
[I/SDIO] SD card capacity 15267840 KB.
found part[0], begin: 4194304, size: 14.570GB
[I/app.filesystem] SD card mount to '/sdcard'

Hello RT-Thread!
==================================================
This example project is the sdio routine!
==================================================
msh />exif /sdcard/DCIM/100MSDCF/DSC07468.JPG
EXIF Information:
==================
Make: SONY
Model: ILCE-5100
DateTime: 2025:02:15 17:21:32
ExposureTime: 1/40 sec
FNumber: f/56/10
ISO: 410
FocalLength: 380/10 mm
Flash: Off

总结

在嵌入式系统中实现EXIF解析,关键在于:

  1. 内存管理:使用流式读取和堆分配,避免栈溢出

  2. 偏移量计算:精确理解EXIF格式的多层嵌套结构

  3. 错误处理:完善的错误检查和资源释放

  4. 性能优化:平衡内存占用和解析效率

通过合理的架构设计和细致的实现,我们成功在资源受限的RT-Thread系统中实现了完整的EXIF信息解析功能,为嵌入式图像处理提供了有力支持。 未来的扩展方向包括支持更多图片格式(PNG、TIFF),添加更多EXIF标签,实现EXIF信息导出功能以及支持自定义标签解析。

相关推荐
茶栀(*´I`*)2 小时前
【OpenCV 实战】图像基础操作与算术运算:从像素访问到图像混合
人工智能·opencv·计算机视觉
橙露2 小时前
CGO调用OpenCV实现多角度模板匹配性能分析
人工智能·opencv·计算机视觉
STLearner2 小时前
MM 2025 | 时间序列(Time Series)论文总结【预测,分类,异常检测,医疗时序】
论文阅读·人工智能·深度学习·神经网络·算法·机器学习·数据挖掘
光羽隹衡2 小时前
计算机视觉--Opencv(边缘检测)
人工智能·opencv·计算机视觉
春日见2 小时前
Git 相关操作大全
linux·人工智能·驱动开发·git·算法·机器学习
柠檬叶子C2 小时前
解决 Keil MDK 编译报错:error: #5: cannot open source input file “xxx.h“
stm32·单片机·开源
Kingfar_12 小时前
高速列车驾驶员情境意识动态建模及生理反应机制研究
人工智能·机器学习
qq_429499572 小时前
STM32 SPI读取写入W25Q64JVSSIQ
stm32·单片机·嵌入式硬件
小二·2 小时前
Python Web 开发进阶实战:AI 原生硬件接口 —— 在 Flask + MicroPython 中构建边缘智能设备控制平台
前端·人工智能·python