这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » STM32H7S78-DK视频播放:Zephyr+LVGL+外部Flash存视频

共1条 1/1 1 跳转至页

STM32H7S78-DK视频播放:Zephyr+LVGL+外部Flash存视频

高工
2026-11-11 10:28:21     打赏
STM32H7S78-DK 视频播放:Zephyr + LVGL + 外部 Flash 存视频

本文记录在 STM32H7S78-DK(STM32H7S7L8,Cortex-M7 @600MHz,800×480 屏)上用 Zephyr 4.5 + LVGL 9.6 实现视频播放的方案:把一段 MP4 截取转码成裸帧序列,链入应用镜像存进外部 Octo-SPI Flash,运行时逐帧送 LVGL 显示。环境沿用系列第一篇《STM32H7S78-DK 上跑通 Zephyr:从 Blinky 到外部 Flash 启动》。

1. 路线选择:为什么是 RAW 裸帧

H7S7 带 JPEG 硬件编解码器,理论上最优方案是 MJPEG 硬解。但查证发现 Zephyr 的 video_stm32_jpeg 驱动当前只支持编码(源码注释明确标注 decode 待支持),硬解路线需要自己用 STM32 HAL 写 JPEG 解码 + DMA2D 色彩转换,工作量不小。

因此选择 RAW RGB565 裸帧路线:无解码负担、行为完全可控,视频数据放外部 Flash 内存映射区(0x70000000),不占片内 SRAM,CPU 直接读。这也是许多 MCU 端视频 demo 的通用做法。

2. 视频转码

源视频 1920×1280 H.264 30fps,截取前 4 秒,缩放到 400×300、12fps,转 RGB565 裸帧:

ffmpeg -y -i input.mp4 -t 4 -vf "scale=400:300,fps=12" -pix_fmt rgb565le -f rawvideo frames.bin

产物 11.5MB = 400×300×2 字节 × 48 帧。容量估算:60MB 的 MCUboot slot 能放约 20 秒 400×300@12fps,降分辨率/帧率可到分钟级。

3. 工程解析(app/video_player)

CMakeLists.txt —— 关键点是用 -I 给 .incbin 指定二进制文件的搜索路径:

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(video_player)

set_source_files_properties(src/video.S PROPERTIES
    COMPILE_OPTIONS "-I${CMAKE_CURRENT_SOURCE_DIR}/video")

target_sources(app PRIVATE src/main.c src/video.S)

src/video.S —— 用 .incbin 把二进制帧数据直接链入 rodata,避免生成 11MB 的 C 数组拖慢编译:

    .section .rodata
    .align 8
    .global video_frames_start
    .global video_frames_end
video_frames_start:
    .incbin "frames.bin"
video_frames_end:

由于应用是 ext_flash_app 变体,这段 rodata 落在外部 Octo-SPI Flash 的内存映射区,运行时通过 video_frames_start 指针直接访问。

src/main.c —— LVGL 图像控件逐帧换数据指针。注意一个细节:dsc 指针没变,LVGL 不会自动重绘,需要手动 lv_obj_invalidate:

#include <zephyr/kernel.h>
#include <zephyr/drivers/display.h>
#include <zephyr/logging/log.h>

#include <lvgl.h>
#include <lvgl_zephyr.h>

LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);

/* 视频参数:与 ffmpeg 转码命令对应(400x300 RGB565 @12fps,4 秒 48 帧) */
#define VIDEO_W     400
#define VIDEO_H     300
#define VIDEO_FPS   12
#define FRAME_SIZE  (VIDEO_W * VIDEO_H * 2)
#define FRAME_COUNT 48

/* 帧数据由 video.S 通过 .incbin 链入 rodata,位于外部 Octo-SPI Flash(内存映射) */
extern const uint8_t video_frames_start[];
extern const uint8_t video_frames_end[];

static lv_image_dsc_t video_dsc;

int main(void)
{
    const struct device *display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));

    if (!device_is_ready(display_dev)) {
        LOG_ERR("display device not ready");
        return 0;
    }

    lvgl_lock();

    lv_obj_set_style_bg_color(lv_screen_active(), lv_color_black(), 0);

    lv_obj_t *title = lv_label_create(lv_screen_active());
    lv_label_set_text(title, "Video 400x300@12fps from OSPI flash");
    lv_obj_set_style_text_font(title, &lv_font_montserrat_24, 0);
    lv_obj_set_style_text_color(title, lv_color_white(), 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 30);

    lv_obj_t *img = lv_image_create(lv_screen_active());
    lv_obj_align(img, LV_ALIGN_CENTER, 0, 30);

    video_dsc.header.cf = LV_COLOR_FORMAT_RGB565;
    video_dsc.header.w = VIDEO_W;
    video_dsc.header.h = VIDEO_H;
    video_dsc.header.stride = VIDEO_W * 2;
    video_dsc.data_size = FRAME_SIZE;
    video_dsc.data = video_frames_start;
    lv_image_set_src(img, &video_dsc);

    lv_timer_handler();
    lvgl_unlock();

    display_blanking_off(display_dev);
    LOG_INF("video player started, frames=%u size=%u", FRAME_COUNT, FRAME_SIZE);

    uint32_t frame = 0;
    int64_t next_tick = k_uptime_get();

    while (1) {
        next_tick += 1000 / VIDEO_FPS;

        int64_t now = k_uptime_get();
        int32_t wait_ms = (int32_t)(next_tick - now);

        if (wait_ms > 0) {
            k_msleep(wait_ms);
        }

        video_dsc.data = video_frames_start + frame * FRAME_SIZE;

        lvgl_lock();
        /* dsc 指针未变,需要手动失效触发重绘 */
        lv_obj_invalidate(img);
        lv_timer_handler();
        lvgl_unlock();

        frame = (frame + 1) % FRAME_COUNT;
    }
    return 0;
}

prj.conf 只需基础显示配置(帧源 RGB565,屏 RGB888,LVGL 渲染时自动转格式):

CONFIG_MAIN_STACK_SIZE=8192
CONFIG_LOG=y

CONFIG_DISPLAY=y

CONFIG_LVGL=y
CONFIG_LV_Z_MEM_POOL_SIZE=98304
CONFIG_LV_COLOR_DEPTH_24=y

CONFIG_LV_FONT_MONTSERRAT_24=y

4. 编译与烧录

cd /d D:\luglZephyrproject
set ZEPHYR_SDK_INSTALL_DIR=D:\luglZephyrproject\zephyr-sdk-1.0.1\zephyr-sdk-1.0.1
set ZEPHYR_TOOLCHAIN_VARIANT=zephyr

west build -b stm32h7s78_dk/stm32h7s7xx/ext_flash_app --sysbuild -s D:\STM32\stm32H7S78-dk\app\video_player -d D:\STM32\stm32H7S78-dk\app\video_player\build
west flash -d D:\STM32\stm32H7S78-dk\app\video_player\build --domain video_player

实测结果:

  • 应用镜像 11.8MB(代码 0.3MB + 视频 11.5MB),占 60MB slot 的 18.8%

  • 烧录 11.8MB 约 68 秒(外部 Flash 加载器写入速度 ~170KB/s)

  • 屏幕循环播放 400×300@12fps 视频,串口输出 video player started, frames=48

5. 延伸方向

  • 更长视频:降分辨率/帧率换时长,或把视频放 SD 卡走文件系统读取(板上 SDMMC 已在 DTS 使能)

  • 真解码:绕过 Zephyr 驱动,直接用 STM32 HAL 的 JPEG 硬解 + DMA2D 做 YCbCr→RGB 转换,可到 25~30fps 全屏

  • 性能叠加:结合第三篇的 NeoChrom 加速,RGB565→RGB888 的转换和缩放都可以卸载给 GPU2D





关键词: STM32H7S78-DK     视频     Zephyr    

共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]