视频解码
本节展示如何配置TouchGFX Generator,以生成具有视频解码功能的TouchGFX HAL。
在了解该场景之前,请阅读关于在TouchGFX中使用MJPEG视频的文档和关于视频控件本身的文档。
以下场景详细说明了如何赋能TouchGFX HAL,以通过软件(LibJPEG)或硬件(JPEG)支持视频解码。 它涵盖STM32F7和STM32H7,因为在STM32CubeMX配置中,支持硬件(JPEG)解码时,STM32H7的配置与STM32F7略有不同。
在为您的特定项目配置MCU之前,请务必阅读本文中的所有章节。
一般情况下,LibJPEG和JPEG配置可以在STM32CubeMX的中间件和多媒体类别中找到:
Tip
支持RTOS
TouchGFX Generator用户指南提到,单和双缓冲区解码策略需要符合CMSIS标准的RTOS,比如FreeRTOS。 TouchGFX Generator 生成一个必须与视频解码任务关联的入口函数videoTaskFunc()
。 STM32CubeMX可以生成此配置,方法是在FreeRTOS中间件配置的任务和队列选项卡中定义任务和入口函数。
视频任务堆栈大小(以CMSIS V2的文字定义)和RTOS堆大小是两个重要因素。
对于软件解码,必须仔细设置堆栈大小,因为LibJPEG使用动态内存分配。 对于硬件解码,由于不存在动态分配存储器的软件堆栈,所以堆栈大小可显著降低。
FreeRTOS堆对于您的一般应用 + 0xA000
应该足够大。
基于上述配置,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字节。
Caution
视频数据
Caution
下面来自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配置中的DMA设置选项卡,来配置使用DMA将数据传输到JPEG外设(内存到外设),以及从JPEG外设接收数据(外设到内存)。 为IN
和OUT
各添加一个DMA请求将自动设置方向参数。
本节总结了支持JPEG功能的STM32F7(例如STM32F769)上使用硬件解码时TouchGFX HAL的配置。 从STM32CubeMX生成代码后,在从Designer使用视频控件时,应用程序将能够使用JPEG外设解码视频。
Caution
STM32H750-DK
唯一能将(例如)STM32H750和STM32F769上的硬件解码(支持JPEG)加以区分的因素是在STM32CubeMX中配置DMA传输的方式。 不仅UI不同,DMA概念也不同。
对于STM32H750,JPEG外设只能配置为使用MDMA外设(而不是常规DMA外设)。 为输入和输出FIFO阈值信号添加MDMA配置,如下图所示。
Note
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
- STM32F746-DISCO
- STM32F769-DISCO
- 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
开始)。
- EWARM
- STM32CubeIDE
- MDK-ARM
Further reading
下面的例子都在STM32F746G-DISCO板(0xC0000000
->>0xC00FF000
)上的SDRAM开始部分保留了一些空间,允许应用程序通过地址(例如0xC0000000
)引用帧缓存区,而不存在链接器覆盖帧缓存区数据的风险。 每个示例都允许链接器将Video_RGB_Buffer
放入已定义的SDRAM区域。
Tip
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)