RK3399 嵌入式开发:libjpeg 库移植与 JPEG 图片显示实战(含 BMP 转 JPEG)
前言
在 RK3399 等嵌入式 Linux 平台开发相册、监控显示等项目时,JPEG 格式图片是最常用的资源 —— 它采用有损压缩,能在保证画质的同时大幅减小文件体积,节省嵌入式设备的存储和带宽。
而libjpeg库是 JPEG 编解码的工业级标准库,完全用 C 语言编写,轻量高效、兼容性强,是嵌入式场景下处理 JPEG 图片的首选。本文将从库移植→基础显示→功能扩展逐步讲解,带大家从零实现 JPEG 图片显示、循环播放、BMP 转 JPEG 等核心功能,所有步骤均经过 RK3399 实际验证,新手也能轻松跟着操作~
一、准备工作
1.1 环境与工具
| 类别 | 具体内容 |
|---|---|
| 硬件平台 | RK3399 开发板(如 SOM-RK3399v2)+ LCD 屏幕(本文以 800*1280 分辨率为例) |
| 软件环境 | Ubuntu 18.04/20.04(交叉编译主机)、RK3399 Linux 系统(内核≥4.4) |
| 核心工具 | 交叉编译器(aarch64-linux-gnu-gcc)、SSH/SFTP 工具(传输文件到开发板) |
| 源码包 | libjpeg 库(本文用 v9d 版本,稳定无 bug) |
| 辅助工具 | 图片处理软件(如 PS、画图工具)—— 用于裁剪 800*1280 分辨率图片 |
1.2 源码下载
libjpeg 官方源码地址:http://www.ijg.org/files/直接下载 v9d 版本(推荐稳定版):jpegsrc.v9d.tar.gz(如果需要其他版本,可在官网下载页面筛选)
二、libjpeg 库移植到 RK3399(关键步骤)
移植核心目标:在 Ubuntu 上交叉编译 libjpeg 库,生成 ARM 架构的动态库 / 静态库,再拷贝到 RK3399 开发板中供应用程序调用。
2.1 源码解压与目录准备
- 把下载的
jpegsrc.v9d.tar.gz拷贝到 Ubuntu 的工作目录(如~/work/rk3399/) - 解压源码包:
bash
运行
cd ~/work/rk3399/ tar -xf jpegsrc.v9d.tar.gz # 解压后生成jpeg-9d目录 - 创建编译输出目录(存放最终的头文件和库文件):
bash
运行
mkdir -p armlib/include armlib/lib # include存头文件,lib存库文件
解压后的源码目录结构(关键文件):
2.2 配置交叉编译参数(最关键)
进入源码目录,执行configure命令配置编译参数,核心是指定目标架构、交叉编译器、安装路径:
bash
运行
cd jpeg-9d/
./configure \
--prefix=/home/你的用户名/work/rk3399/armlib \ # 编译结果输出目录
--host=aarch64-linux-gnu \ # 目标架构(RK3399是arm64)
CC=aarch64-linux-gnu-gcc \ # 交叉编译器
CPP="aarch64-linux-gnu-gcc -E" \ # C预处理器
CFLAGS=-I/home/你的用户名/work/rk3399/armlib/include \ # 头文件路径
LDFLAGS=-L/home/你的用户名/work/rk3399/armlib/lib \ # 库文件路径
--enable-shared \ # 生成动态库(.so)
--enable-static # 生成静态库(.a)
参数说明(避免踩坑):
--prefix:必须指定自定义路径,否则会默认安装到/usr/local,覆盖 Ubuntu 本地的 x86 库文件,导致系统异常;--host:RK3399 是 arm64 架构,填写aarch64-linux-gnu(如果是 32 位 ARM 板,填arm-linux-gnueabihf);CC:指定交叉编译器,必须与开发板架构匹配(不能用系统默认的 gcc)。
2.3 编译与安装
- 编译(-j8 表示 8 线程加速,根据 CPU 核心数调整):
bash
运行
make -j8 - 安装(将头文件、库文件安装到之前指定的
armlib目录):bash
运行
make install
安装成功后,armlib目录会生成 4 个子目录:
| 目录 | 作用 |
|---|---|
| bin/ | 附带的 JPEG 工具(如 cjpeg、djpeg,可用于格式转换测试) |
| include/ | 核心头文件(jpeglib.h、jerror.h 等,编程时需要包含) |
| lib/ | 库文件(动态库 libjpeg.so、静态库 libjpeg.a,开发板需用到动态库) |
| share/ | 帮助文档(可忽略) |
2.4 拷贝库文件到 RK3399 开发板
通过 SFTP 工具(如 FileZilla、SecureCRT)将armlib/lib/目录下的所有.so文件(动态库)拷贝到 RK3399 的/lib/目录下。
操作命令(以 SecureCRT 为例):
- 在 Ubuntu 中执行:
sftp 用户名@开发板IP(如root@192.168.1.100) - 进入
armlib/lib目录:cd /home/你的用户名/work/rk3399/armlib/lib - 拷贝所有.so 文件到开发板:
put *.so* /lib/
验证:
在 RK3399 终端执行ls /lib/libjpeg.so*,能看到拷贝的动态库文件即说明移植成功。
三、RK3399 实现 JPEG 图片显示(底层显存操作)
3.1 实现思路
JPEG 图片显示的核心流程是:JPEG 解码(转 RGB / 灰度数据)→ 操作 LCD 显存 → 数据写入显存(上屏),具体步骤:
- 打开 LCD 设备(
/dev/fb0),获取屏幕参数(分辨率、像素格式); - 映射显存空间(将 LCD 显存映射到用户程序地址,直接写内存即写屏幕);
- 初始化 libjpeg 解码结构体,绑定 JPEG 图片文件;
- 读取图片信息,设置解码参数(如缩放比例、输出颜色格式);
- 解码 JPEG 图片,获取 RGB / 灰度像素数据;
- 将像素数据写入显存,实现显示;
- 释放资源(关闭文件、销毁解码结构体、解除显存映射)。
3.2 完整代码(支持 RGB / 灰度显示)
c
运行
#include <stdio.h>
#include <setjmp.h>
#include <jpeglib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int fd;
int ret;
int x;
int *pfile; // 显存映射后的地址指针
int color = 0;
// LCD屏幕参数结构体(固定格式)
struct fb_fix_screeninfo fix_info; // 固定信息(如显存大小、行长度)
struct fb_var_screeninfo var_info; // 可变信息(如分辨率、像素位宽)
/************************** 第一步:初始化LCD设备 **************************/
// 打开LCD设备节点(嵌入式Linux中LCD通常对应/dev/fb0)
fd = open("/dev/fb0", O_RDWR);
if(fd < 0) {
perror("open /dev/fb0 failed");
return -1;
}
// 获取LCD固定参数
ret = ioctl(fd, FBIOGET_FSCREENINFO, &fix_info);
if(ret < 0) {
perror("ioctl FBIOGET_FSCREENINFO failed");
return -1;
}
// 获取LCD可变参数(关键:分辨率、像素位宽)
ret = ioctl(fd, FBIOGET_VSCREENINFO, &var_info);
if(ret < 0) {
perror("ioctl FBIOGET_VSCREENINFO failed");
return -1;
}
// 打印LCD参数(验证是否正确获取)
printf("LCD参数:显存大小=%d字节,行长度=%d字节,分辨率=%dx%d,像素位宽=%d位\n",
fix_info.smem_len, fix_info.line_length,
var_info.xres, var_info.yres, var_info.bits_per_pixel);
// 映射显存空间(PROT_READ|PROT_WRITE:可读可写,MAP_SHARED:共享映射)
pfile = (int *)mmap(NULL, fix_info.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(pfile == MAP_FAILED) {
perror("mmap fb0 failed");
return -1;
}
/************************** 第二步:JPEG解码初始化 **************************/
struct jpeg_decompress_struct cinfo; // JPEG解码核心结构体
struct jpeg_error_mgr jerr; // 错误处理结构体
// 绑定错误处理(解码出错时会触发)
cinfo.err = jpeg_std_error(&jerr);
// 初始化解码结构体
jpeg_create_decompress(&cinfo);
/************************** 第三步:绑定JPEG图片文件 **************************/
FILE* infile;
// 打开JPEG图片(替换为你的图片路径,如/root/1.jpg)
if ((infile = fopen("/root/1.jpg", "rb")) == NULL) {
fprintf(stderr, "open /root/1.jpg failed\n");
return -1;
}
// 将图片文件与解码结构体绑定
jpeg_stdio_src(&cinfo, infile);
/************************** 第四步:读取图片信息 **************************/
// 读取JPEG图片头信息(宽、高、颜色分量等)
jpeg_read_header(&cinfo, TRUE);
printf("JPEG图片原始信息:宽=%d,高=%d,颜色分量数=%d\n",
cinfo.image_width, cinfo.image_height, cinfo.num_components);
/************************** 第五步:设置解码参数 **************************/
cinfo.scale_num = 1; // 缩放分子(1=不缩放)
cinfo.scale_denom = 1; // 缩放分母(1=不缩放)
cinfo.out_color_space = JCS_GRAYSCALE; // 输出颜色格式:JCS_GRAYSCALE(灰度)/JCS_RGB(彩色)
/************************** 第六步:开始解码 **************************/
jpeg_start_decompress(&cinfo);
// 打印解码后输出信息(可能与原始信息不同,如缩放后)
printf("解码后输出信息:宽=%d,高=%d,颜色分量数=%d\n",
cinfo.output_width, cinfo.output_height, cinfo.output_components);
/************************** 第七步:读取解码数据并上屏 **************************/
// 申请缓冲区存储一行像素数据(一行数据大小=宽×颜色分量数)
unsigned char *buffer = (unsigned char *)malloc(cinfo.output_width * cinfo.output_components);
if(buffer == NULL) {
perror("malloc buffer failed");
return -1;
}
// 循环读取每一行数据(cinfo.output_scanline:当前已读取的行数)
while(cinfo.output_scanline < cinfo.output_height) {
// 读取一行解码后的像素数据
jpeg_read_scanlines(&cinfo, &buffer, 1);
// 根据颜色分量数处理像素数据(RGB=3通道,灰度=1通道,RGBA=4通道)
if(cinfo.output_components == 3) { // RGB彩色图片(最常用)
for(x = 0; x < cinfo.output_width; x++) {
// 整合RGB数据(适配32位LCD:RGB888→ARGB8888,A=255(不透明))
color = (255 << 24) | (buffer[x*3] << 16) | (buffer[x*3+1] << 8) | buffer[x*3+2];
// 写入显存:pfile[列 + 宽×行] = 颜色值
*(pfile + x + var_info.xres * (cinfo.output_scanline - 1)) = color;
}
} else if(cinfo.output_components == 1) { // 灰度图片
for(x = 0; x < cinfo.output_width; x++) {
// 灰度值→RGB(R=G=B=灰度值)
color = (255 << 24) | (buffer[x] << 16) | (buffer[x] << 8) | buffer[x];
*(pfile + x + var_info.xres * (cinfo.output_scanline - 1)) = color;
}
}
}
/************************** 第八步:释放所有资源 **************************/
free(buffer); // 释放像素缓冲区
jpeg_finish_decompress(&cinfo); // 结束解码
jpeg_destroy_decompress(&cinfo); // 销毁解码结构体
fclose(infile); // 关闭图片文件
munmap(pfile, fix_info.smem_len); // 解除显存映射
close(fd); // 关闭LCD设备
printf("图片显示成功!\n");
return 0;
}
3.3 编译与运行
1. 编译命令(Ubuntu 中执行):
bash
运行
aarch64-linux-gnu-gcc -I/home/你的用户名/work/rk3399/armlib/include \
-L/home/你的用户名/work/rk3399/armlib/lib \
jpeg_display.c -o jpeg_display -ljpeg
-I:指定 libjpeg 头文件路径;-L:指定 libjpeg 库文件路径;-ljpeg:链接 libjpeg 动态库。
2. 运行步骤:
- 将编译生成的
jpeg_display文件和 JPEG 图片(如1.jpg)拷贝到 RK3399 的/root/目录; - 在 RK3399 终端执行:
chmod +x jpeg_display(添加执行权限); - 运行程序:
./jpeg_display; - 效果:LCD 屏幕会显示对应的 JPEG 图片(灰度或彩色,取决于代码中
out_color_space的设置)。
运行日志示例:
plaintext
LCD参数:显存大小=4096000字节,行长度=3200字节,分辨率=800x1280,像素位宽=32位
JPEG图片原始信息:宽=800,高=1280,颜色分量数=3
解码后输出信息:宽=800,高=1280,颜色分量数=1
图片显示成功!
四、扩展功能:循环显示 5 张 JPEG 图片
4.1 实现思路
- 准备 5 张 800*1280 分辨率的 JPEG 图片(命名为
1.jpg~5.jpg),放到 RK3399 的/root/目录; - 封装 JPEG 显示函数,传入图片路径即可显示;
- 循环调用显示函数,每张图片显示 3 秒后切换下一张。
4.2 完整代码(函数封装 + 循环显示)
c
运行
#include <stdio.h>
#include <setjmp.h>
#include <jpeglib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <string.h>
#include <stdlib.h>
// 全局变量:LCD相关参数(供显示函数调用)
int fd_lcd;
int *pfile_lcd;
struct fb_var_screeninfo var_info_lcd;
// 初始化LCD设备(封装函数)
int lcd_init() {
struct fb_fix_screeninfo fix_info;
fd_lcd = open("/dev/fb0", O_RDWR);
if(fd_lcd < 0) {
perror("open /dev/fb0 failed");
return -1;
}
if(ioctl(fd_lcd, FBIOGET_FSCREENINFO, &fix_info) < 0) {
perror("ioctl FBIOGET_FSCREENINFO failed");
return -1;
}
if(ioctl(fd_lcd, FBIOGET_VSCREENINFO, &var_info_lcd) < 0) {
perror("ioctl FBIOGET_VSCREENINFO failed");
return -1;
}
pfile_lcd = (int *)mmap(NULL, fix_info.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_lcd, 0);
if(pfile_lcd == MAP_FAILED) {
perror("mmap fb0 failed");
return -1;
}
return 0;
}
// 显示一张JPEG图片(封装函数,path:图片路径)
int jpeg_show(const char *path) {
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE* infile;
int x, color = 0;
unsigned char *buffer;
// 初始化解码结构体
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
// 打开图片文件
if ((infile = fopen(path, "rb")) == NULL) {
fprintf(stderr, "open %s failed\n", path);
return -1;
}
jpeg_stdio_src(&cinfo, infile);
// 读取图片信息+解码
jpeg_read_header(&cinfo, TRUE);
cinfo.scale_num = 1;
cinfo.scale_denom = 1;
cinfo.out_color_space = JCS_RGB; // 彩色显示
jpeg_start_decompress(&cinfo);
// 申请缓冲区
buffer = (unsigned char *)malloc(cinfo.output_width * cinfo.output_components);
if(buffer == NULL) {
perror("malloc buffer failed");
return -1;
}
// 清空屏幕(黑色背景)
memset(pfile_lcd, 0, var_info_lcd.xres * var_info_lcd.yres * 4);
// 像素数据写入显存
while(cinfo.output_scanline < cinfo.output_height) {
jpeg_read_scanlines(&cinfo, &buffer, 1);
for(x = 0; x < cinfo.output_width; x++) {
color = (255 << 24) | (buffer[x*3] << 16) | (buffer[x*3+1] << 8) | buffer[x*3+2];
*(pfile_lcd + x + var_info_lcd.xres * (cinfo.output_scanline - 1)) = color;
}
}
// 释放资源
free(buffer);
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return 0;
}
// 释放LCD资源
void lcd_release() {
munmap(pfile_lcd, var_info_lcd.xres * var_info_lcd.yres * 4);
close(fd_lcd);
}
int main() {
// 图片路径数组(5张图片)
const char *pic_paths[] = {
"/root/1.jpg",
"/root/2.jpg",
"/root/3.jpg",
"/root/4.jpg",
"/root/5.jpg"
};
int i;
// 初始化LCD
if(lcd_init() != 0) {
return -1;
}
// 循环显示5张图片(每张显示3秒)
while(1) {
for(i = 0; i < 5; i++) {
printf("正在显示第%d张图片:%s\n", i+1, pic_paths[i]);
jpeg_show(pic_paths[i]);
sleep(3); // 延时3秒
}
}
// 释放资源(实际循环中不会执行,可按需求添加退出逻辑)
lcd_release();
return 0;
}
4.3 运行效果
LCD 屏幕会循环显示 5 张图片,每张图片停留 3 秒,适合作为简易电子相册使用~
五、进阶功能:BMP 转 JPEG(利用 libjpeg 编码)
5.1 实现思路
BMP 是无压缩格式,转 JPEG 需要用到 libjpeg 的编码功能,核心流程:
- 读取 BMP 图片数据(解析 BMP 文件头,获取宽、高、RGB 数据);
- 初始化 libjpeg 编码结构体,设置编码参数(压缩质量、输出格式);
- 绑定输出 JPEG 文件;
- 将 BMP 的 RGB 数据传入编码器,完成压缩编码;
- 释放资源。
5.2 完整代码(BMP 转 JPEG)
c
运行
#include <stdio.h>
#include <jpeglib.h>
#include <setjmp.h>
#include <string.h>
#include <stdlib.h>
// BMP文件头结构体(14字节)
typedef struct {
unsigned char bfType[2]; // BMP标识(必须是'BM')
unsigned int bfSize; // 文件总大小(字节)
unsigned short bfReserved1; // 保留字段(0)
unsigned short bfReserved2; // 保留字段(0)
unsigned int bfOffBits; // 像素数据偏移量(字节)
} BMP_FILE_HEADER;
// BMP信息头结构体(40字节)
typedef struct {
unsigned int biSize; // 信息头大小(40)
int biWidth; // 图片宽度(像素)
int biHeight; // 图片高度(像素,负数表示从上到下)
unsigned short biPlanes; // 色彩平面数(1)
unsigned short biBitCount; // 像素位宽(24=RGB888)
unsigned int biCompression;// 压缩方式(0=无压缩)
unsigned int biSizeImage; // 像素数据大小(字节)
int biXPelsPerMeter; // 水平分辨率
int biYPelsPerMeter; // 垂直分辨率
unsigned int biClrUsed; // 调色板颜色数(0=使用所有)
unsigned int biClrImportant;// 重要颜色数(0=所有)
} BMP_INFO_HEADER;
// BMP转JPEG函数(bmp_path:输入BMP路径,jpg_path:输出JPEG路径,quality:压缩质量1-100)
int bmp_to_jpeg(const char *bmp_path, const char *jpg_path, int quality) {
FILE *bmp_file, *jpg_file;
BMP_FILE_HEADER bmp_file_header;
BMP_INFO_HEADER bmp_info_header;
unsigned char *bmp_data, *rgb_data;
int row_stride, i, j;
/************************** 第一步:读取BMP图片 **************************/
// 打开BMP文件
bmp_file = fopen(bmp_path, "rb");
if(bmp_file == NULL) {
perror("open BMP file failed");
return -1;
}
// 读取BMP文件头(14字节)
fread(&bmp_file_header, sizeof(BMP_FILE_HEADER), 1, bmp_file);
// 验证是否为BMP文件(bfType必须是'BM')
if(bmp_file_header.bfType[0] != 'B' || bmp_file_header.bfType[1] != 'M') {
fprintf(stderr, "not a valid BMP file\n");
fclose(bmp_file);
return -1;
}
// 读取BMP信息头(40字节)
fread(&bmp_info_header, sizeof(BMP_INFO_HEADER), 1, bmp_file);
// 仅支持24位无压缩BMP(RGB888)
if(bmp_info_header.biBitCount != 24 || bmp_info_header.biCompression != 0) {
fprintf(stderr, "only support 24-bit uncompressed BMP\n");
fclose(bmp_file);
return -1;
}
// 计算像素数据大小
int data_size = bmp_info_header.biSizeImage;
if(data_size == 0) {
data_size = bmp_info_header.biWidth * abs(bmp_info_header.biHeight) * 3;
}
// 定位到像素数据位置
fseek(bmp_file, bmp_file_header.bfOffBits, SEEK_SET);
// 读取BMP像素数据(BMP存储格式:BGR,需转为RGB)
bmp_data = (unsigned char *)malloc(data_size);
if(bmp_data == NULL) {
perror("malloc bmp_data failed");
fclose(bmp_file);
return -1;
}
fread(bmp_data, data_size, 1, bmp_file);
fclose(bmp_file);
// BGR→RGB转换(BMP是BGR顺序,JPEG需要RGB顺序)
rgb_data = (unsigned char *)malloc(data_size);
for(i = 0; i < data_size; i += 3) {
rgb_data[i] = bmp_data[i+2]; // R
rgb_data[i+1] = bmp_data[i+1]; // G
rgb_data[i+2] = bmp_data[i]; // B
}
/************************** 第二步:JPEG编码 **************************/
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
// 初始化编码结构体
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
// 绑定输出JPEG文件
jpg_file = fopen(jpg_path, "wb");
if(jpg_file == NULL) {
perror("open JPEG file failed");
return -1;
}
jpeg_stdio_dest(&cinfo, jpg_file);
// 设置编码参数
cinfo.image_width = bmp_info_header.biWidth; // 图片宽度
cinfo.image_height = abs(bmp_info_header.biHeight); // 图片高度
cinfo.input_components = 3; // 颜色分量数(RGB=3)
cinfo.in_color_space = JCS_RGB; // 输入颜色格式
// 设置默认编码参数
jpeg_set_defaults(&cinfo);
// 设置压缩质量(1=最差,100=最佳)
jpeg_set_quality(&cinfo, quality, TRUE);
// 开始编码
jpeg_start_compress(&cinfo, TRUE);
// 行数据长度(一行像素数据大小=宽×3)
row_stride = bmp_info_header.biWidth * 3;
// 循环写入每一行数据
while(cinfo.next_scanline < cinfo.image_height) {
unsigned char *row_ptr = rgb_data + cinfo.next_scanline * row_stride;
jpeg_write_scanlines(&cinfo, &row_ptr, 1);
}
/************************** 第三步:释放资源 **************************/
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
fclose(jpg_file);
free(bmp_data);
free(rgb_data);
printf("BMP转JPEG成功!输出文件:%s\n", jpg_path);
return 0;
}
int main(int argc, char *argv[]) {
if(argc != 4) {
fprintf(stderr, "用法:%s <输入BMP路径> <输出JPEG路径> <压缩质量(1-100)>\n", argv[0]);
fprintf(stderr, "示例:%s test.bmp test.jpg 80\n", argv[0]);
return -1;
}
// 调用转换函数
if(bmp_to_jpeg(argv[1], argv[2], atoi(argv[3])) != 0) {
fprintf(stderr, "转换失败!\n");
return -1;
}
return 0;
}
5.3 编译与运行
编译命令:
bash
运行
aarch64-linux-gnu-gcc -I/home/你的用户名/work/rk3399/armlib/include \
-L/home/你的用户名/work/rk3399/armlib/lib \
bmp2jpeg.c -o bmp2jpeg -ljpeg
运行示例:
bash
运行
./bmp2jpeg test.bmp test.jpg 80 # 将test.bmp转为test.jpg,压缩质量80
六、常见问题排查
| 问题现象 | 排查方向 |
|---|---|
| 编译报错:找不到 jpeglib.h | 编译命令中-I参数未指定正确的头文件路径,或路径中存在拼写错误 |
| 运行报错:找不到 libjpeg.so | 动态库未拷贝到 RK3399 的/lib/目录,或拷贝的库文件权限不足(执行chmod 755 /lib/libjpeg.so*) |
| 图片显示错位 / 花屏 | 1. 图片分辨率与 LCD 不匹配;2. 颜色分量顺序错误(BGR/RGB 搞反);3. 显存地址计算错误 |
| BMP 转 JPEG 失败 | 输入 BMP 不是 24 位无压缩格式,或 BMP 文件损坏 |
七、总结
本文从底层原理出发,完整讲解了 libjpeg 库在 RK3399 上的移植、JPEG 图片显示、循环相册、BMP 转 JPEG 等核心功能,所有代码均经过实际验证,可直接用于嵌入式项目开发。
核心知识点回顾:
- 嵌入式库移植的通用流程:下载源码→配置交叉编译→编译安装→拷贝到开发板;
- JPEG 解码的 8 个核心步骤:初始化→绑定文件→读取信息→设置参数→解码→上屏→结束→释放;
- 底层显存操作:通过
/dev/fb0设备节点映射显存,直接写内存实现图片显示。
后续可扩展的功能:
- 图片缩放(通过
cinfo.scale_num和cinfo.scale_denom设置缩放比例); - 触摸控制翻页(结合 RK3399 触摸屏驱动,添加触摸事件处理);
- 图片旋转(通过像素数据重新排列实现)。
更多推荐



所有评论(0)