跳转到主要内容

视频解码

本节展示如何配置TouchGFX Generator,以生成具有视频解码功能的TouchGFX HAL。

在了解该场景之前,请阅读关于在TouchGFX中使用MJPEG视频的文档和关于视频控件本身的文档。

以下场景详细说明了如何赋能TouchGFX HAL,以通过软件(LibJPEG)或硬件(JPEG)支持视频解码。 它涵盖STM32F7和STM32H7,因为在STM32CubeMX配置中,支持硬件(JPEG)解码时,STM32H7的配置与STM32F7略有不同。

在为您的特定项目配置MCU之前,请务必阅读本文中的所有章节

一般情况下,LibJPEG和JPEG配置可以在STM32CubeMX的中间件多媒体类别中找到:

STM32CubeMX中的JPEG和LibJPEG设置

Tip
  • 最新版本的STM32H750-DK、STM32F769-DISCO和STM32F746G-DISCO TouchGFX硬件模板(TBS)通过各自的STM32CubeMX配置支持软件和JPEG硬件解码。
  • 将此类示例项目中的STM32CubeMX配置作为灵感,让您的特定MCU支持MJPEG视频解码。
  • 支持RTOS

    TouchGFX Generator用户指南提到,双缓冲区解码策略需要符合CMSIS标准的RTOS,比如FreeRTOS。 TouchGFX Generator 生成一个必须与视频解码任务关联的入口函数videoTaskFunc()。 STM32CubeMX可以生成此配置,方法是在FreeRTOS中间件配置的任务和队列选项卡中定义任务和入口函数。

    视频任务堆栈大小(以CMSIS V2的文字定义)和RTOS堆大小是两个重要因素。

    对于软件解码,必须仔细设置堆栈大小,因为LibJPEG使用动态内存分配。 对于硬件解码,由于不存在动态分配存储器的软件堆栈,所以堆栈大小可显著降低。

    FreeRTOS堆对于您的一般应用 + 0xA000应该足够大。

    FreeRTOS任务配置

    FreeRTOS堆大小配置

    基于上述配置,STM32CubeMX为视频任务生成以下代码:

    main.c
    /* Definitions for VideoTask */
    osThreadId_t VideoTaskHandle;
    const osThreadAttr_t VideoTask_attributes = {
    .name = "VideoTask",
    .stack_size = 1000 * 4,
    .priority = (osPriority_t) osPriorityLow,
    };
    ...
    void main()
    {
    ...
    /* creation of VideoTask */
    VideoTaskHandle = osThreadNew(videoTaskFunc, NULL, &VideoTask_attributes);
    ...

    }
    FreeRTOSConfig.h
    #define configTOTAL_HEAP_SIZE                    ((size_t)75000)

    软件解码

    软件解码解决方案要求按照Generator用户指南中指定的,从STM32CubeMX启用LibJPEG中间件。 软件解码设置对于所有具备LibJPEG功能的MCU(如STM32F4、STM32F7、STM32H7)都是相同的。

    TouchGFX软件解码器期望LibJPEG解码的数据按照BGR像素排序。 如果该设置保留为RGB,R和B颜色分量将在应用中互换。

    此外,如果使用16位或24位视频RGB缓冲区,则每个像素的大小应为3字节,如果使用32位视频RGB缓冲区,则为4字节。

    16位或24位视频RGB缓冲区的LibJPEG Configuration(配置)

    Caution
  • RGB_ORDERING设置必须配置为BGR,像素大小必须根据使用的视频RGB缓冲区格式进行设置。
  • 如果在ARGB8888(32位)应用程序中使用直接到帧缓存的策略,则像素大小必须设置为4
  • 视频数据

    Caution
    当前解码解决方案(使用STM32CubeMX、TouchGFX Generator、以及来自TouchGFX Designer的视频控件)目前只适用于意法半导体开发板,因为在应用程序链接时,视频数据被链接到内存映射的flash中已知的特定地址。

    下面来自EWARM项目的代码片段显示了由TouchGFX设计器创建的额外选项,以及它如何将数据放在ExtFlashSection中,后者是在所有TouchGFX板级包的链接器脚本中定义的。 这不适用于非内存映射的flash或未定义此部分的项目。

    ewarm_project.ewp
            <option>
    <name>IlinkExtraOptions</name>
    <state>--image_input $PROJ_DIR$\..\TouchGFX\generated\videos\bin\washerdryer.bin,video_washerdryer_bin_start,ExtFlashSection,4</state>
    <state>--keep video_washerdryer_bin_start</state>
    </option>

    对于视频数据位于非内存映射的flash中的项目,请阅读本文中的FileReader部分。

    硬件解码

    TouchGFX Generator用户指南提到,JPEG IP必须在STM32CubeMX中启用,以支持硬件解码。

    STM32F769-DISCO

    支持JPEG功能的STM32F7系列(例如STM32F769)的JPEG配置,与STM32H7系列略有不同。 JPEG配置中的RGB_FORMAT设置必须符合TouchGFX Framebuffer(帧缓存)的像素格式,即以下示例中的JPEG_RGB565

    JPEG参数设置

    通过JPEG配置中的DMA设置选项卡,来配置使用DMA将数据传输到JPEG外设(内存到外设),以及从JPEG外设接收数据(外设到内存)。 为INOUT添加一个DMA请求将自动设置方向参数。

    JPEG DMA设置

    本节总结了支持JPEG功能的STM32F7(例如STM32F769)上使用硬件解码时TouchGFX HAL的配置。 从STM32CubeMX生成代码后,在从Designer使用视频控件时,应用程序将能够使用JPEG外设解码视频。

    Caution
    JPEG外设配置的RGB_FORMAT必须遵守TouchGFX Framebuffer的格式

    STM32H750-DK

    唯一能将(例如)STM32H750和STM32F769上的硬件解码(支持JPEG)加以区分的因素是在STM32CubeMX中配置DMA传输的方式。 不仅UI不同,DMA概念也不同。

    对于STM32H750,JPEG外设只能配置为使用MDMA外设(而不是常规DMA外设)。 为输入和输出FIFO阈值信号添加MDMA配置,如下图所示。

    Note
    与DMA1/DMA2相比,MDMA是性能高很多的DMA引擎

    JPEG DMA设置

    JPEG DMA设置

    FileReader接口

    当在非内存映射的存储器中存储MJPEG视频时,开发人员可以指定touchgfx::VideoDataReader的实现,TouchGFX视频控制器可以使用它将需要解码的数据传输到配置的解码器(软件/硬件)。 下面是此接口的简单示例,它将视频数据从一个缓冲区复制到另一个缓冲区。

    VideoView.cpp
    class MyReader : public touchgfx::VideoDataReader
    {
    public:
    MyReader() : position(0) { }
    virtual uint32_t getDataLength() { return video_len; }
    virtual void seek(uint32_t pos) { position = pos; }
    virtual bool readData(void* dst, uint32_t bytes)
    {
    memcpy(dst, &video_data[position], bytes);
    position += bytes;
    return true;
    }
    private:
    uint32_t position;
    } myReader;

    开发人员可以配置控件以使用数据读取器,而不是将一个Video控件指向映射内存中的视频开始部分:

    VideoView.cpp
    video.setVideoData(myReader);

    迁移TBS,以支持视频解码

    如果您想在支持视频解码之前,将TouchGFX Board Setup(TBS)(带有支持视频解码的MCU)创建的项目从TouchGFX 设计器版本迁移,并且希望能够在TouchGFX-Designer中使用“Run Target(运行目标)”,则需要对GCC Makefile进行一些手动变更。

    以下部分将列出所需的变更、以及进行变更的原因。 根据开发人员应用程序使用的内容,必须始终应用一些常规变更以及一些LibJPEG(软件解码)和JPEG(硬件解码)特定变更。 这些更改是对旧TBS中已经存在的GCC Makefile的扩展。

    如上述场景所述,除了更新Makefile,您还必须在STM32CubeMX中设置视频解码。

    常规变更

    定义您项目中的LIBJPEG路径:

    # LibJPEG path
    libjpeg_path := $(cubemx_middlewares_path)/Third_Party/LibJPEG

    然后必须定义视频资源输入路径:

    asset_texts_input  := TouchGFX/assets/texts

    asset_videos_input := TouchGFX/assets/videos

    视频资源输出路径也必须定义在现有资源输出路径下面:

    asset_images_output := $(asset_root_path)/images
    asset_fonts_output := $(asset_root_path)/fonts
    asset_texts_output := $(asset_root_path)/texts
    asset_videos_output := $(asset_root_path)/videos

    将视频输出资源添加到组件列表中:

    all_components := $(components) \
    $(asset_fonts_output) \
    $(asset_images_output) \
    $(asset_texts_output)
    $(asset_texts_output) \
    $(asset_videos_output)

    必须定义视频对象文件。 视频对象文件与已经存在的对象分离:

    c_source_files := $(call find, $(source_paths),*.c) $(os_source_files) $(board_c_files)
    source_files += $(board_cpp_files)

    video_object_files := $(call find, $(asset_videos_output),*.o)

    必须定义视频转换器脚本的路径:

    textconvert_script_path := $(touchgfx_path)/framework/tools/textconvert
    textconvert_executable := $(call find, $(textconvert_script_path), *.rb)
    videoconvert_script_path := $(touchgfx_path)/framework/tools/videoconvert

    可以添加一个可选的Echo,以查看所有视频对象文件。 视频对象文件必须添加到链接阶段。 该行将$(video_object_files)和其他对象文件加在一起:

    $(binary_output_path)/$(target_executable): $(object_files) $(object_asm_files)
    @echo Video Objects: $(video_object_files)
    @echo Linking $(@)
    @mkdir -p $(@D)
    @mkdir -p $(object_output_path)
    @$(file >$(build_root_path)/objects.tmp) $(foreach F,$(object_files) $(video_object_files),$(file >>$(build_root_path)/objects.tmp,$F))

    将视频规则添加到现有资源和.PHONY:

    _assets_: BitmapDatabase TextKeysAndLanguages Videos

    .PHONY: BitmapDatabase TextKeysAndLanguages Videos

    添加视频转换后,接着添加视频规则:

    Videos:
    @ruby $(videoconvert_script_path)/videoconvert.rb $(asset_videos_input) $(asset_videos_output)

    最后更新清除规则,也删除与视频相关的输出。

    _clean_:
    @echo Cleaning: $(board_name)
    @rm -rf $(build_root_path)
    # Do not remove gui_generated
    @rm -rf $(asset_images_output)
    @rm -rf $(asset_fonts_output)
    @rm -rf $(asset_texts_output)
    @rm -rf $(asset_videos_output)
    # Create directory to avoid error if it does not exist
    @mkdir -p $(asset_root_path)
    # Remove assets folder if it is empty (i.e. no gui_generated folder)
    @rmdir --ignore-fail-on-non-empty $(asset_root_path)
    # Clean bootloader project
    @$(MAKE) -r -f ExtMem_Boot/gcc/Makefile -s $(MFLAGS) clean

    软件变化

    添加所有LIBJPEG路径到include路径:

    include_paths := $(library_includes) \
    $(foreach comp, $(all_components), $(comp)/include) \
    $(foreach comp, $(cubemx_components), $(comp)/Inc) \
    $(foreach comp, $(touchgfx_generator_components), $(comp)/generated) \
    $(framework_includes) \
    $(cubemx_middlewares_path) \
    $(touchgfx_middlewares_path) \
    $(touchgfx_generator_components) \
    LIBJPEG/Target \
    $(libjpeg_path)/include \
    LIBJPEG/App

    必须定义LIBJPEG源路径:

    c_source_files := $(call find, $(source_paths),*.c) $(os_source_files) $(board_c_files)
    source_files += $(board_cpp_files)

    libjpeg_source_path = Middlewares/Third_Party/LibJPEG/source

    接下来,所有LIBJPEG源文件必须添加到board_c_files:

    board_c_files := \
    $(Drivers_path)/BSP/STM32H750B-DK/stm32h750b_discovery_bus.c \
    $(Drivers_path)/BSP/STM32H750B-DK/stm32h750b_discovery_qspi.c \
    $(Drivers_path)/BSP/STM32H750B-DK/stm32h750b_discovery_sdram.c \
    $(Drivers_path)/BSP/STM32H750B-DK/stm32h750b_discovery_ts.c \
    $(Drivers_path)/BSP/Components/ft5336/ft5336.c \
    $(Drivers_path)/BSP/Components/ft5336/ft5336_reg.c \
    $(Drivers_path)/BSP/Components/mt25tl01g/mt25tl01g.c \
    $(Drivers_path)/BSP/Components/mt48lc4m32b2/mt48lc4m32b2.c \
    $(libjpeg_source_path)/jaricom.c \
    $(libjpeg_source_path)/jcomapi.c \
    $(libjpeg_source_path)/jdapimin.c \
    $(libjpeg_source_path)/jdapistd.c \
    $(libjpeg_source_path)/jdarith.c \
    $(libjpeg_source_path)/jdatasrc.c \
    $(libjpeg_source_path)/jdcoefct.c \
    $(libjpeg_source_path)/jdcolor.c \
    $(libjpeg_source_path)/jddctmgr.c \
    $(libjpeg_source_path)/jdhuff.c \
    $(libjpeg_source_path)/jdinput.c \
    $(libjpeg_source_path)/jdmainct.c \
    $(libjpeg_source_path)/jdmarker.c \
    $(libjpeg_source_path)/jdmaster.c \
    $(libjpeg_source_path)/jdmerge.c \
    $(libjpeg_source_path)/jdpostct.c \
    $(libjpeg_source_path)/jdsample.c \
    $(libjpeg_source_path)/jdtrans.c \
    $(libjpeg_source_path)/jerror.c \
    $(libjpeg_source_path)/jidctflt.c \
    $(libjpeg_source_path)/jidctfst.c \
    $(libjpeg_source_path)/jidctint.c \
    $(libjpeg_source_path)/jmemmgr.c \
    $(libjpeg_source_path)/jmemnobs.c \
    $(libjpeg_source_path)/jquant1.c \
    $(libjpeg_source_path)/jquant2.c \
    $(libjpeg_source_path)/jutils.c \
    LIBJPEG/App/libjpeg.c

    然后将LIBJPEG源文件添加到现有的目标文件中,方法与其他中间件源文件相同:

    # Start converting paths
    object_files := $(object_files:$(touchgfx_path)/%.cpp=$(object_output_path)/touchgfx/%.o)
    object_files := $(object_files:%.cpp=$(object_output_path)/%.o)
    object_files := $(object_files:$(touchgfx_middlewares_path)/%.c=$(object_output_path)/$(touchgfx_middlewares_path)/%.o)
    object_files := $(object_files:$(cubemx_middlewares_path)/%.c=$(object_output_path)/$(cubemx_middlewares_path)/%.o)
    object_files := $(object_files:$(libjpeg_source_path)/%.c=$(object_output_path)/$(libjpeg_source_path)/%.o)
    object_files := $(object_files:$(Drivers_path)/%.c=$(object_output_path)/Drivers/%.o)
    object_files := $(object_files:%.c=$(object_output_path)/%.o)

    硬件变化

    添加所有JPEG路径到include 路径:

    include_paths := $(library_includes) \
    $(foreach comp, $(all_components), $(comp)/include) \
    $(foreach comp, $(cubemx_components), $(comp)/Inc) \
    $(foreach comp, $(touchgfx_generator_components), $(comp)/generated) \
    $(framework_includes) \
    $(cubemx_middlewares_path) \
    $(touchgfx_middlewares_path) \
    $(touchgfx_generator_components) \
    Utilities/JPEG

    接下来,所有JPEG源文件必须添加到board_c_files:

    board_c_files := \
    $(Drivers_path)/BSP/STM32H750B-DK/stm32h750b_discovery_bus.c \
    $(Drivers_path)/BSP/STM32H750B-DK/stm32h750b_discovery_qspi.c \
    $(Drivers_path)/BSP/STM32H750B-DK/stm32h750b_discovery_sdram.c \
    $(Drivers_path)/BSP/STM32H750B-DK/stm32h750b_discovery_ts.c \
    $(Drivers_path)/BSP/Components/ft5336/ft5336.c \
    $(Drivers_path)/BSP/Components/ft5336/ft5336_reg.c \
    $(Drivers_path)/BSP/Components/mt25tl01g/mt25tl01g.c \
    $(Drivers_path)/BSP/Components/mt48lc4m32b2/mt48lc4m32b2.c \
    Utilities/JPEG/jpeg_utils.c

    将视频缓冲区置于外部存储器中

    当开发人员使用STM32CubeMX创建新项目时,与生成的项目相关联的链接器脚本不包含TouchGFX使用的任何可能的区域。 因此,用于解码MJPEG视频的特定缓冲区被链接器放置在内部flash中,直至开发人员修改他们的链接器脚本,将缓冲区放置到其他地方。 如果没有这样的修改,开发人员将会遇到大量内部存储被使用的情况,可能无法容纳大一点的视频缓冲区,比如全屏视频解码所需的缓冲区。

    Tip
    以下TBS是预先配置好的,可以从外部存储解码视频:
    1. STM32F746-DISCO
    2. STM32F769-DISCO
    3. STM32H750-DK

    启用视频解码以后,TouchGFX Generator生成用于JPEG解码的RGB缓冲区的定义。 该定义是用一个位置指示来实现的,它告诉链接器应该将缓冲区放在哪个部分。 如果链接器在链接器脚本中找不到该存储区域,缓冲区将被放在内部存储中。

    LOCATION_PRAGMA_NOLOAD("Video_RGB_Buffer")
    uint32_t videoRGBBuffer[57600] LOCATION_ATTRIBUTE_NOLOAD("Video_RGB_Buffer");

    下面特定于编译器的子章节描述了开发人员可以进行的修改,以实现在SDRAM中放置缓冲区。 Video_RGB_Buffer表示用于视频解码的缓冲区。 链接器脚本示例在SDRAM中为TouchGFX帧缓存区保留了一些空间(从0xC0000000开始)。

    1. EWARM
    2. STM32CubeIDE
    3. MDK-ARM
    Further reading
    请阅读TouchGFX Generator用户指南中关于工程中配置视频解码的内容。

    下面的例子都在STM32F746G-DISCO板(0xC0000000->>0xC00FF000)上的SDRAM开始部分保留了一些空间,允许应用程序通过地址(例如0xC0000000)引用帧缓存区,而不存在链接器覆盖帧缓存区数据的风险。 每个示例都允许链接器将Video_RGB_Buffer放入已定义的SDRAM区域。

    Tip
    如果你分配Touchgfx帧缓存区,而不是使用直接寻址,那么你的链接器脚本也应该将TouchGFX_Framebuffer放入SDRAM中。

    EWARM (IAR)

    stm32f746xx_flash.icf
    define symbol __ICFEDIT_region_SDRAM_start__   = 0xC00FF000;
    define symbol __ICFEDIT_region_SDRAM_end__ = 0xC0700FFF;

    define region SDRAM_region = mem:[from __ICFEDIT_region_SDRAM_start__ to __ICFEDIT_region_SDRAM_end__];

    place in SDRAM_region { first section Video_RGB_Buffer };

    链接之后,EWARM\STM32F746G_DISCO\List\STM32F746G_DISCO.map包含Video_RGB_Buffer的以下放置信息:

    STM32F746G_DISCO.map
    Video_RGB_Buffer        zero     0xc00f'f000   0x3'8400  TouchGFXGeneratedHAL.o [2]
    - 0xc013'7400 0x3'8400

    STM32CubeIDE

    STM32F746NGHX_FLASH.ld
    MEMORY
    {
    ...
    SDRAM (xrw) : ORIGIN = 0xC00FF000, LENGTH = 8M
    }

    BufferSection (NOLOAD) :
    {
    *(Video_RGB_Buffer Video_RGB_Buffer.*)
    *(.gnu.linkonce.r.*)
    . = ALIGN(0x4);
    } >SDRAM

    编译之后,STM32CubeIDE\Debug\STM32F746G_DISCO.map 包含Video_RGB_Buffer的以下放置信息:

    STM32F746G_DISCO.map
    BufferSection   0x00000000c00ff000    0x1c200
    *(Video_RGB_Buffer Video_RGB_Buffer.*)
    Video_RGB_Buffer
    0x00000000c00ff000 0x1c200 Application/User/TouchGFX/target/generated/TouchGFXGeneratedHAL.o
    0x00000000c00ff000 videoRGBBuffer

    MDK-ARM (Keil)

    STM32F746G_DISCO.sct
    LR_IROM1 0x08000000 0x00200000  {    ; load region size_region
    ER_IROM1 0x08000000 0x00200000 { ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
    }
    RW_IRAM1 0x20000000 0x00050000 { ; RW data
    .ANY (+RW +ZI)
    }
    RW_SDRAM 0xC00FF000 UNINIT 0xC0700FFF {
    *.o (Video_RGB_Buffer)
    }
    }

    确保在保存视频缓冲区的段中包含UNINIT属性 这确保了将内存区域保留为未初始化的数据。 链接之后,MDK-ARM\STM32F746G_DISCO\STM32F746G_DISCO.map包含Video_RGB_Buffer的以下放置信息:

    STM32F746G_DISCO.map
    Video_RGB_Buffer                         0xc00ff000   Section    115200  touchgfxgeneratedhal.o(Video_RGB_Buffer)