Skip to content

概念先知道

  • MJPEG(Motion JPEG):把视频按“一帧一帧 JPEG 图片”压缩的方式,每帧独立编码,简单可靠,常用于摄像头预览/存储。
  • JPEG 头(header):JPEG 文件以 SOI(0xFFD8)开头、EOI(0xFFD9)结尾,中间是图像数据。例程用 JpegHeadCreate 生成头并填到编码器。
  • YUV422:一种像素格式(亮度 Y + 两个色差 UV),MJPEG 编码器的输入格式为 MJPEG_FORMAT_YUV422_YUYV
  • 硬件编码器:BL616/BL618 内置 MJPEG 编码器外设(设备名 "mjpeg"),例程用 bflb_mjpeg_* API 驱动。

例程功能简介

本页对应博流官方 SDK 的 mjpeg_no_camera 例程(examples/peripherals/mjpeg/mjpeg_no_camera),演示 MJPEG 硬件编码器的基础用法:

  • 初始化 MJPEG 编码器(bflb_mjpeg_init),配置 YUV422 输入、质量与输出缓冲区;
  • 生成 JPEG 头(JpegHeadCreate)并写入编码器(bflb_mjpeg_fill_jpeg_header_tail);
  • 用内置 64×64 测试图触发压缩(bflb_mjpeg_sw_run),中断里取回一帧数据;
  • 打印帧地址/大小并十六进制输出,验证压缩结果。
  • 同族例程examples/peripherals/mjpeg/):mjpeg_cam_normal(摄像头采集)、mjpeg_cam_crop(裁剪)、mjpeg_cam_swap(像素交换)、mjpeg_recv_from_uart(串口接收)。

提示

本页是无摄像头版,先用测试图跑通编码流程;需要真实画面时改用 mjpeg_cam_* 例程并接 DVP 摄像头。

操作步骤

1
进入例程目录

本页无需摄像头(例程用内置测试图压缩 MJPEG)。在终端进入 SDK 的 MJPEG 无摄像头例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/peripherals/mjpeg/mjpeg_no_camera
2
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
3
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
运行验证

打开串口助手(波特率 2000000)。程序把内置 64×64 测试图压缩为 JPEG 后打印帧地址与大小(jpg addr:... ,jpg size:...),并以十六进制输出该 JPEG 数据,随后每 2 秒重复。

代码执行流程

例程从启动到输出 JPEG 的完整流程如下:

例程调用的 API 介绍

bflb_mjpeg_init(mjpeg, &config)

初始化 MJPEG 编码器。config 中需配置输入格式(MJPEG_FORMAT_YUV422_YUYV)、质量、分辨率、输入/输出缓冲区等。

参数

  • mjpeg:编码器设备句柄(bflb_device_get_by_name("mjpeg")
  • configbflb_mjpeg_config_s 配置结构体

返回值:成功返回 0;失败返回负值

JpegHeadCreate(mode, quality, x, y, buf)

生成 JPEG 文件头(SOI、量化表等),例程按 YUV422 模式生成后写入编码器。

参数

  • mode:色彩模式(例程 YUV_MODE_422
  • quality:压缩质量
  • x / y:图像宽高
  • buf:接收 JPEG 头的缓冲区

返回值:生成的头部长度

bflb_mjpeg_fill_jpeg_header_tail(mjpeg, buf, len)

把 JPEG 头写入编码器,之后的压缩帧会自动带上该头。

参数

  • mjpeg:编码器设备句柄
  • buf / len:JPEG 头数据与长度

返回值:成功返回 0

bflb_mjpeg_sw_run(mjpeg, 1)

软件触发一次压缩。压缩完成后产生中断(MJPEG_INTSTS_ONE_FRAME),在中断里用 bflb_mjpeg_get_frame_info 取帧。

参数

  • mjpeg:编码器设备句柄
  • run1 触发

返回值:无

bflb_mjpeg_get_frame_info / bflb_mjpeg_pop_one_frame(mjpeg, &addr) / (mjpeg)

get_frame_info 返回当前帧地址与长度;pop_one_frame 取走该帧释放缓冲。

参数

  • mjpeg:编码器设备句柄
  • addr:返回帧地址

返回值get_frame_info 返回帧长度

完整代码

以下为 mjpeg_no_camera/main.c 完整源码,与官方示例(examples/peripherals/mjpeg/mjpeg_no_camera)一致,默认折叠,点击展开:

📜 点击展开 mjpeg_no_camera/main.c 完整代码
c
#include "bflb_mtimer.h"
#include "bflb_mjpeg.h"
#include "board.h"
#include "../jpeg_head.h"
#include "test_64x64.h"

#define X 64
#define Y 64

uint8_t jpg_head_buf[800] = { 0 };
uint32_t jpg_head_len;

uint8_t MJPEG_QUALITY = 50;

static __attribute__((aligned(32))) ATTR_NOINIT_PSRAM_SECTION uint8_t mjpeg_buffer[50 * 1024];

void bflb_mjpeg_dump_hex(uint8_t *data, uint32_t len)
{
    uint32_t i = 0;

    for (i = 0; i < len; i++) {
        if (i % 16 == 0) {
            printf("\r\n");
        }

        printf("%02x ", data[i]);
    }

    printf("\r\n");
}

static struct bflb_device_s *mjpeg;

volatile uint32_t pic_count = 0;
volatile uint8_t *pic_addr;
volatile uint32_t pic_len;

void mjpeg_isr(int irq, void *arg)
{
    uint32_t intstatus = bflb_mjpeg_get_intstatus(mjpeg);
    if (intstatus & MJPEG_INTSTS_ONE_FRAME) {
        bflb_mjpeg_int_clear(mjpeg, MJPEG_INTCLR_ONE_FRAME);
        uint8_t *tmp_addr;
        pic_len = bflb_mjpeg_get_frame_info(mjpeg, &tmp_addr);
        pic_addr = tmp_addr;
        pic_count = 1;
        bflb_mjpeg_pop_one_frame(mjpeg);
    }
}

void mjpeg_init(uint32_t x, uint32_t y, uint8_t *yuv, uint8_t *jpeg, uint32_t size, uint8_t quality)
{
    struct bflb_mjpeg_config_s config;

    mjpeg = bflb_device_get_by_name("mjpeg");

    config.format = MJPEG_FORMAT_YUV422_YUYV;
    config.quality = quality;
    config.rows = y;
    config.resolution_x = x;
    config.resolution_y = y;
    config.input_bufaddr0 = (uint32_t)yuv;
    config.input_bufaddr1 = 0;
    config.output_bufaddr = (uint32_t)jpeg;
    config.output_bufsize = size;
    config.input_yy_table = NULL; /* use default table */
    config.input_uv_table = NULL; /* use default table */

    bflb_mjpeg_init(mjpeg, &config);
    jpg_head_len = JpegHeadCreate(YUV_MODE_422, quality, x, y, jpg_head_buf);
    bflb_mjpeg_fill_jpeg_header_tail(mjpeg, jpg_head_buf, jpg_head_len);
    bflb_mjpeg_tcint_mask(mjpeg, false);
    bflb_irq_attach(mjpeg->irq_num, mjpeg_isr, NULL);
    bflb_irq_enable(mjpeg->irq_num);
}

void mjpeg_compress(uint8_t *yuv, uint8_t *jpeg, uint32_t size)
{
    bflb_mjpeg_update_input_output_buff(mjpeg, yuv, NULL, jpeg, size);
    bflb_mjpeg_sw_run(mjpeg, 1);
}

int main(void)
{
    board_init();

    mjpeg_init(X, Y, NULL, NULL, 0, MJPEG_QUALITY);

    /* compress one pic */
    mjpeg_compress(test_64x64, mjpeg_buffer, 50 * 1024);

    while (pic_count != 1) {
        bflb_mtimer_delay_ms(200);
    }

    printf("jpg addr:%08x ,jpg size:%d\r\n", pic_addr, pic_len);
    bflb_mjpeg_dump_hex((uint8_t *)pic_addr, pic_len);

    while (1) {
        bflb_mtimer_delay_ms(2000);
    }
}

FAQ

程序卡在等待 pic_count

确认开发板具备 PSRAM(输出缓冲区放在 ATTR_NOINIT_PSRAM_SECTION)且初始化正常;压缩中断未产生时检查 bflb_irq_attach/enable 与编码器配置。

如何接摄像头取真实画面

改用 mjpeg_cam_normal 等例程,并接线 DVP 摄像头(数据/时钟/行场同步引脚按板子配置);摄像头采集的 YUV 数据作为编码器输入即可。

输出的 JPEG 打不开

确认 JpegHeadCreate 生成的头部长度与图像匹配,且使用的是同款 YUV422 输入格式;质量参数过低时画面会明显变糊,但不影响打开。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23