前言

在 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 源码解压与目录准备

  1. 把下载的jpegsrc.v9d.tar.gz拷贝到 Ubuntu 的工作目录(如~/work/rk3399/
  2. 解压源码包:

    bash

    运行

    cd ~/work/rk3399/
    tar -xf jpegsrc.v9d.tar.gz  # 解压后生成jpeg-9d目录
    
  3. 创建编译输出目录(存放最终的头文件和库文件):

    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 编译与安装

  1. 编译(-j8 表示 8 线程加速,根据 CPU 核心数调整):

    bash

    运行

    make -j8
    
  2. 安装(将头文件、库文件安装到之前指定的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 为例):
  1. 在 Ubuntu 中执行:sftp 用户名@开发板IP(如root@192.168.1.100
  2. 进入armlib/lib目录:cd /home/你的用户名/work/rk3399/armlib/lib
  3. 拷贝所有.so 文件到开发板:put *.so* /lib/
验证:

在 RK3399 终端执行ls /lib/libjpeg.so*,能看到拷贝的动态库文件即说明移植成功。

三、RK3399 实现 JPEG 图片显示(底层显存操作)

3.1 实现思路

JPEG 图片显示的核心流程是:JPEG 解码(转 RGB / 灰度数据)→ 操作 LCD 显存 → 数据写入显存(上屏),具体步骤:

  1. 打开 LCD 设备(/dev/fb0),获取屏幕参数(分辨率、像素格式);
  2. 映射显存空间(将 LCD 显存映射到用户程序地址,直接写内存即写屏幕);
  3. 初始化 libjpeg 解码结构体,绑定 JPEG 图片文件;
  4. 读取图片信息,设置解码参数(如缩放比例、输出颜色格式);
  5. 解码 JPEG 图片,获取 RGB / 灰度像素数据;
  6. 将像素数据写入显存,实现显示;
  7. 释放资源(关闭文件、销毁解码结构体、解除显存映射)。

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. 运行步骤:
  1. 将编译生成的jpeg_display文件和 JPEG 图片(如1.jpg)拷贝到 RK3399 的/root/目录;
  2. 在 RK3399 终端执行:chmod +x jpeg_display(添加执行权限);
  3. 运行程序:./jpeg_display
  4. 效果:LCD 屏幕会显示对应的 JPEG 图片(灰度或彩色,取决于代码中out_color_space的设置)。

运行日志示例:

plaintext

LCD参数:显存大小=4096000字节,行长度=3200字节,分辨率=800x1280,像素位宽=32位
JPEG图片原始信息:宽=800,高=1280,颜色分量数=3
解码后输出信息:宽=800,高=1280,颜色分量数=1
图片显示成功!

四、扩展功能:循环显示 5 张 JPEG 图片

4.1 实现思路

  1. 准备 5 张 800*1280 分辨率的 JPEG 图片(命名为1.jpg~5.jpg),放到 RK3399 的/root/目录;
  2. 封装 JPEG 显示函数,传入图片路径即可显示;
  3. 循环调用显示函数,每张图片显示 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 的编码功能,核心流程:

  1. 读取 BMP 图片数据(解析 BMP 文件头,获取宽、高、RGB 数据);
  2. 初始化 libjpeg 编码结构体,设置编码参数(压缩质量、输出格式);
  3. 绑定输出 JPEG 文件;
  4. 将 BMP 的 RGB 数据传入编码器,完成压缩编码;
  5. 释放资源。

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 等核心功能,所有代码均经过实际验证,可直接用于嵌入式项目开发。

核心知识点回顾:

  1. 嵌入式库移植的通用流程:下载源码→配置交叉编译→编译安装→拷贝到开发板;
  2. JPEG 解码的 8 个核心步骤:初始化→绑定文件→读取信息→设置参数→解码→上屏→结束→释放;
  3. 底层显存操作:通过/dev/fb0设备节点映射显存,直接写内存实现图片显示。

后续可扩展的功能:

  • 图片缩放(通过cinfo.scale_numcinfo.scale_denom设置缩放比例);
  • 触摸控制翻页(结合 RK3399 触摸屏驱动,添加触摸事件处理);
  • 图片旋转(通过像素数据重新排列实现)。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐