주요 내용으로 건너뛰기

비디오 디코딩

이 섹션에서는 비디오 디코딩 기능을 가진 TouchGFX HAL을 생성하기 위해 TouchGFX Generator를 구성하는 방법을 보여줍니다.

이 시나리오를 읽기 전에 먼저 TouchGFX의 MJPEG 비디오 사용에 대한 설명서와 비디오 위젯에 대한 설명서를 읽어보시기 바랍니다.

아래 시나리오는 TouchGFX HAL을 활성화하여 소프트웨어(LibJPEG)나 하드웨어(JPEG)를 통해 비디오 디코딩을 지원하는 방법을 자세히 설명한 것입니다.

특정 프로젝트에 대해 MCU를 구성하기 전에 이 문서의 모든 섹션을 읽어 보시기 바랍니다.

일반적으로 LibJPEG 및 JPEG 구성은 MiddlewareMultimedia 범주의 STM32CubeMX에서 확인할 수 있습니다.

STM32CubeMX의 JPEG 및 LibJPEG 설정

Tip
  • TouchGFX Designer의 여러 TouchGFX 보드 설정(TBS)에는 MJPEG 비디오 디코딩 지원이 포함되어 있습니다.
  • 이 예제 프로젝트에서 제공하는 STM32CubeMX 구성을 참고하여 특정 MCU에서 MJPEG 비디오 디코딩을 활성화하십시오.
  • 소프트웨어 디코딩의 예제는 STM32H735G-DK용 TBS에서 확인할 수 있으며, 하드웨어 디코딩은 STM32U5G9J-DK2용 TBS에서 사용됩니다.

RTOS 지원

Generator User Guide에는 Single-Double buffer 비디오 디코딩 전략에 FreeRTOS 같은 CMSIS 호환 RTOS가 필요하다고 명시되어 있습니다. TouchGFX Generator는 비디오 디코딩 테스크에 연결해야 하는 엔트리 포인트 함수 videoTaskFunc()를 생성합니다. STM32CubeMX는 FreeRTOS 미들웨어 구성의 Tasks and queues 탭에서 테스크 및 엔트리 포인트 함수를 정의하여 이 구성을 생성할 수 있습니다.

두 가지 중요한 요소로 비디오 테스크 스택 크기(CMSIS V2의 경우 단어로 정의)와 RTOS 힙 크기가 있습니다.

소프트웨어 디코딩의 경우, LibJPEG가 동적 메모리 할당을 사용하기 때문에 스택 크기를 신중하게 설정해야 합니다. 이는 FreeRTOS 의 heap이 일반 애플리케이션 + 0xA000(~41kB)을 수용할 수 있을 만큼 충분히 커야 함을 의미합니다. 하드웨어 디코딩의 경우에는 동적으로 메모리를 할당하는 소프트웨어 stack이 없기 때문에 stack 크기가 크게 줄어들 수 있습니다.

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 User Guide에 나와 있는대로, STM32CubeMX에서 LibJPEG 미들웨어가 활성화되어 있어야 합니다. 소프트웨어 디코딩 설정은 LibJPEG를 지원하는 MCU(예: STM32F4, STM32F7, STM32H7)에서 모두 동일합니다.

TouchGFX 소프트웨어 디코더는 LibJPEG에서 디코딩되는 데이터를 BGR 픽셀 순서로 설정합니다. 이 설정이 RGB로 남아 있다면 R 색상 성분과 B 색상 성분이 애플리케이션에서 서로 자리를 바꿉니다.

또한, 각 픽셀의 크기는 16비트 또는 24비트 비디오 RGB 버퍼를 사용하는 경우에는 3바이트, 32비트 비디오 RGB 버퍼를 사용하는 경우에는 4바이트여야 합니다.

16비트 또는 24비트 비디오 RGB 버퍼를 위한 LibJPEG 구성

Caution
  • RGB_ORDERING 설정은 BGR로 구성되어야 하며, 픽셀 크기는 사용되는 비디오 RGB 버퍼 형식에 따라 설정되어야 합니다.
  • 애플리케이션이 ARGB8888(32비트) 애플리케이션에서 Direct to Framebuffer 전략을 사용하는 경우에는 pixel size를 4로 설정해야 합니다.

비디오 데이터

TouchGFX는 비디오 데이터를 ExtFlashSection에 연결하며, 이는 링커 스크립트에서 정의되어야 합니다. ExtFlashSection이 메모리 매핑되지 않은 플래시에 배치된 경우, 데이터는 데이터 리더를 사용하여 플래시 메모리에서 읽어야 하며 디코더로 전달되어야 합니다.

비메모리 매핑 메모리에 비디오 데이터가 있는 프로젝트의 경우, 본 문서 후반부의 FileReader 섹션을 참조하십시오.

하드웨어 디코딩

Generator User Guide에는 하드웨어 디코딩을 활성화하려면 STM32CubeMX에서 JPEG IP가 활성화되어 있어야 한다고 명시되어 있습니다.

JPEG 구성에 있는 RGB_FORMAT 설정은 TouchGFX 프레임 버퍼의 픽셀 형식(아래 예제의 경우 JPEG_RGB565)을 준수해야 합니다.

JPEG IP 매개변수 설정

하드웨어 디코딩을 사용할 때, DMA를 통해 JPEG IP로 데이터를 전송(메모리-주변 장치)하고 JPEG IP로부터 데이터를 전송(주변 장치-메모리)합니다. 어떤 DMA를 사용할지, 어떻게 구성할지는 MCU 제품군에 따라 다릅니다.

Tip
다음은 TouchGFX Designer에서 사용할 수 있는 TouchGFX 보드 설정 예제들로, 다양한 유형의 DMA를 사용한 JPEG 디코딩 설정 방법을 보여줍니다.
  • STM32U5G9J-DK2: JPEG + GPDMA
  • STM32H750B-DK: JPEG + MDMA
  • STM32H7S78-DK: JPEG + HPDMA

STM32H750 예제

다음은 STM32H750에서 JPEG 디코딩을 위해 DMA를 설정하는 방법의 예제입니다. STM32H750의 경우에는 JPEG 주변 장치는 일반 DMA 주변 장치 대신 MDMA 주변 장치를 사용하도록 구성해야 합니다. 아래 이미지와 같이 input/output FIFO threshold 신호에 MDMA 구성을 추가하십시오. 입력 버퍼와 출력 버퍼의 구성은 동일합니다.

STM32H750용 JPEG DMA 설정

FileReader 인터페이스

개발자가 MJPEG 비디오를 메모리 매핑이 되지 않는 메모리에 저장하는 경우에는 TouchGFX 비디오 컨트롤러가 디코딩할 데이터를 구성된 디코더(소프트웨어/하드웨어)로 전달할 때 사용할 touchgfx::VideoDataReader 구현체를 지정할 수 있습니다. 아래는 비디오 데이터를 버퍼에서 다른 버퍼로 복사하는 인터페이스의 예시입니다.

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 Designer 버전에서 비디오 디코딩을 지원하는 MCU로 TouchGFX Board Setups(TBS)에서 생성된 프로젝트를 마이그레이션하고 싶고, 해당 프로젝트에서 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에서 사용되는 기본 영역이 포함되어 있지 않습니다. 이로 인해 비디오가 저장되는 'ExtFlashSection'은 기본적으로 내부 플래시에 배치됩니다. 내부 플래시 메모리 부족을 방지하기 위해 'ExtFlashSection'을 외부 플래시 메모리에 배치해야 하는 경우가 종종 발생합니다.

Tip
다음 TBS는 외부 메모리에서 비디오를 디코딩할 준비가 된 사전 구성된 TBS의 예제입니다.
  1. STM32F746-DISCO
  2. STM32H735G-DK
  3. STM32H750-DK
  4. STM32U5G9J-DK2

선택된 비디오 전략이 프레임 버퍼에 직접 출력되지 않는 경우, 비디오가 활성화되면 TouchGFX Generator에 의해 JPEG 디코딩 전용 RGB 버퍼가 생성됩니다. 정의는 링커에게 버퍼를 저장할 섹션을 알려주는 location pragma로 계측됩니다. 링커가 링커 스크립트에서 이 메모리 영역을 찾지 못하면 버퍼가 내부 메모리에 저장됩니다.

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

다음 컴파일러별 하위 섹션은 개발자가 버퍼를 STM32F746G-DISCO의 SDRAM에 배치하기 위해 적용할 수 있는 수정 사항의 예제를 설명한 것입니다. Video_RGB_Buffer는 비디오 디코딩에 사용되는 버퍼를 말합니다. 링커 스크립트 예제에서는 TouchGFX 프레임 버퍼를 위해 SDRAM에 일부 공간(0xC0000000에서 시작)을 남겨두었습니다.

  1. EWARM
  2. STM32CubeIDE
  3. MDK-ARM
Further reading
Generator User Guide의 비디오 디코딩을 위한 프로젝트 구성 섹션을 읽어보십시오.

다음 예제는 모두 STM32F746G-DISCO 보드에서 애플리케이션이 주소(예: 0xC0000000)로 프레임 버퍼를 참조할 수 있도록 SDRAM 시작 위치에 일부 공간(0xC0000000->0xC00FF000)을 남겨두어 링커가 프레임 버퍼 데이터를 덮어쓸 위험이 없습니다. 각 예제마다 링커가 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)