MJPEG Video
TouchGFX는 4.18 버전부터 MJPEG 비디오 사용을 지원합니다. 비디오를 사용하면 생동감 넘치는 사용자 인터페이스를 개발하거나, 짧은 지침 또는 사용자 가이드를 표시할 수 있습니다.
비디오는 Video 위젯을 통해 사용자 인터페이스에 포함이 됩니다. 이 위젯은 TouchGFX Designer에서 사용할 수 있으며, 다른 위젯으로 사용자 인터페이스에 추가하는 것도 가능합니다.
Note
STM32 마이크로컨트롤러에서 비디오를 디코딩하려면 이를 지원하는 소프트웨어가 추가로 필요합니다. TouchGFX Generator에서 비디오 지원을 활성화해서 이 소프트웨어를 프로젝트에 손쉽게 포함시킬 수 있습니다. TouchGFX Board Setup에서 비디오가 활성화되어 있으면(아래 목록 참조) Run Target(F6
)을 눌러 타겟에서 비디오를 쉽게 실행할 수 있습니다.
타겟 코드에 비디오 지원이 없으면 컴파일 또는 링커 오류 메시지가 표시됩니다.
MJPEG 비디오
MJPEG 비디오는 다수의 JPEG 이미지(프레임)가 컨테이너 파일(.avi)로 압축되어 있습니다. JPEG 프레임은 압축되어 있기 때문에 프레임 버퍼에 바로 복사할 수 없습니다. 개별 프레임은 먼저 RGB 형식(16비트, 24비트 또는 32비트)으로 압축 해제해야만 디스플레이에 표시를 할 수 있습니다.
이러한 압축 해제는 계산 용량을 많이 차지하기 때문에 RGB 비트맵을 사용할 때보다 성능(초당 프레임 수)이 크게 저하됩니다.
하지만 JPEG 압축은 데이터의 크기를 크게 줄일 수 있다는 점에서 유용합니다.
위 스크린샷에서 사용된 비디오는 240 x 135 픽셀입니다. 즉, 16비트 RGB 형식에서 각 프레임은 240 x 135 x 2바이트 = 64,800바이트를 차지합니다. 이 비디오의 프레임 수는 178개(약 7초 분량)입니다. 따라서 비트맵으로 저장된 비디오의 총 크기는 178 x 64,800바이트 = 11,534,400바이트입니다. MJPEG AVI 파일은 비트맵의 10.7%인 1,242,282바이트에 불과합니다.
MJPEG 비디오 파일은 크기가 작기 때문에 작은 비디오 시퀀스에 특히 유용합니다.
크기가 감소하면서 압축 아티팩트(오류)도 발생합니다. 실제 동영상에서는 이러한 오류가 흔히 용인되지만 고대비 그래픽에서는 허용되지 않습니다.
일부 STM32 마이크로컨트롤러는 JPEG 이미지에 대한 하드웨어 가속 디코딩을 지원합니다(예: STM32F769 또는 STM32H750). 덕분에 JPEG 디코딩 속도가 빨라져서 가능한 비디오 프레임 속도도 증가합니다.
JPEG 프레임 디코딩은 16ms를 쉽게 넘을 수 있습니다(MCU 속도와 비디오 해상도에 따라 다름). 이 말은 대부분의 경우 MJPEG 비디오의 디코딩 속도가 일반적인 사용자 인터페이스의 프레임 속도보다 느리다는 것을 의미합니다. 일부 애플리케이션에서는 전체 프레임 속도가 디코딩 속도로 떨어지더라도 괜찮습니다. 하지만 비디오가 예를 들어 20fps의 속도로 실행되더라도 사용자 인터페이스의 프레임 속도는 60fps로 유지해야 하는 애플리케이션도 있는데, 비디오 옆에 애니메이션 처리된 프로그레스 원이 있는 애플리케이션이 여기에 해당합니다. 이 원은 비디오가 오직 20fps의 속도로 새 프레임을 표시하더라도 애니메이션 처리 속도는 60fps를 유지할 때 가장 적합하게 보입니다.
위에서 언급한 STM32F746 예는 각 JPEG 프레임을 모두 디코딩할 때까지 18~20ms가 걸립니다.
TouchGFX에서 비디오 사용하기
TouchGFX에서는 비디오를 사용자 인터페이스에 손쉽게 추가할 수 있습니다. 세 가지만 있으면 됩니다. Video 위젯, VideoController 그리고 MJPEG 비디오 파일입니다.
Video 위젯은 여느 위젯과 마찬가지로 사용자 인터페이스에서 사용됩니다. 비디오 컨트롤러는 전체 TouchGFX 환경을 구성하는 로우 레벨 소프트웨어(HAL, 운영 체제, 드라이버 등)의 한 부분입니다.
비디오 컨트롤러는 MJPEG 파일 디코딩과 버퍼 관리를 제어하는 소프트웨어로 구성됩니다.
TouchGFX Designer는 모든 시뮬레이터 프로젝트에 비디오 컨트롤러를 자동으로 추가합니다. 이에 따라 Video 위젯을 추가하고, 비디오 파일을 선택하고, "Run Simulator"(F5
).
하드웨어에서 비디오를 사용할 때도 타겟 프로젝트에 비디오 컨트롤러(IAR, Keil, arm-gcc, CubeIDE)가 필요합니다. 이러한 비디오 컨트롤러는 이미 일부 TouchGFX Board Specification 패키지(아래 목록 참조)에 추가되어 있지만 어떤 프로젝트든지 TouchGFX Generator를 사용해 비디오 지원 기능을 추가할 수 있습니다. 자세한 내용은 TouchGFX Generator 사용자 가이드를 참조하십시오.
플랫폼에서 비디오 기능이 활성화되어 있으면 Designer에서 비디오 위젯을 손쉽게 추가하여 구성할 수 있습니다. Designer의 비디오 위젯 사용 방법은 여기에 자세히 나와 있습니다.
TouchGFX 프로젝트의 비디오 파일
비디오 파일을 TouchGFX Designer에 추가하면 .avi 파일이 assets/vidoes
폴더로 복사됩니다. 코드가 생성되는 과정에서 비디오가 .bin 파일로 generated/videos/bin
에, 그리고 .o 파일로 generated/videos/obj
에 복사됩니다. .o 파일과 .bin 파일에는 동일한 데이터가 포함되어 있지만 .o 파일은 ELF 형식(일부 컴파일러와 IDE에서 선호하는 형식)입니다.
프로젝트 업데이터는 코드를 생성할 때 실행되어 비디오 파일을 타겟 프로젝트에 추가합니다. 이 말은 비디오 파일이 실행 파일에 연결되어 있어서 애플리케이션에서 사용할 수 있다는 것을 의미합니다.
또한 애플리케이션 프로그래머는 다른 비디오를 assets/videos
폴더에 추가할 수 있습니다. 이 비디오는 프로젝트에도 추가됩니다.
generated/videos/include/videos/VideoDatabase.hpp
파일에는 애플리케이션으로 컴파일되는 비디오에 대한 상징적 정보가 포함됩니다.
const uint32_t video_SampleVideo1_240x135_bin_length = 1242282;
#ifdef SIMULATOR
extern const uint8_t* video_SampleVideo1_240x135_bin_start;
#else
extern const uint8_t video_SampleVideo1_240x135_bin_start[];
#endif
이러한 선언 파일은 비디오를 사용자 코드로 Video 위젯에 할당하는 데 사용됩니다.
사용자 코드를 통한 비디오 파일 사용
일부 프로젝트의 경우 TouchGFX Designer에서 비디오를 선택하는 것만으로는 부족할 때가 있습니다. 예를 들어 시작할 때 여러 가지 비디오를 선택하려는 경우를 가정하겠습니다. 먼저 비디오 파일을 assets/videos
에 추가해야 합니다.
assets/videos
폴더에 추가된 비디오 파일은 코드 생성 시(또는 애셋 만들기 실행 시) VideoDatabase.hpp
에 추가됩니다.
const uint32_t video_myVideo_bin_length = 1242282;
#ifdef SIMULATOR
extern const uint8_t* video_myVideo_bin_start;
#else
extern const uint8_t video_myVideo_bin_start[];
#endif
이제 이러한 사용자 코드에서 이러한 선언 파일을 사용해 비디오 위젯으로 영상을 재생할 수 있습니다.
Screen1View.cpp
#include <gui/screen1_screen/Screen1View.hpp>
#include <videos/VideoDatabase.hpp>
Screen1View::Screen1View()
{
}
void Screen1View::setupScreen()
{
Screen1ViewBase::setupScreen();
video.setVideoData(video_myVideo_bin_start, video_myVideo_bin_length);
video.setWidthHeight(240, 136);
video.play();
}
이제 비디오 데이터가 애플리케이션에 연결되었습니다. 비디오를 assets/videos
에 추가하지 않고 이러한 연결을 피해서 비디오를 외부 플래시의 전용 영역으로 직접 플래싱하는 방법도 있습니다. 그런 다음 다음과 같이 플래시 주소를 사용해 주소와 길이를 전달하면 됩니다.
void Screen1View::setupScreen()
{
...
video.setVideoData((const uint8_t*)0xA0200000, 1242282);
...
}
제한 사항
TouchGFX는 세로 모드에서 비디오 디코딩을 지원하지 않습니다.
TouchGFX는 오디오를 지원하지 않습니다. 따라서 비디오 데이터에서 오디오 데이터를 제거하는 것이 좋습니다.
비디오가 활성화되어 있는 개발 키트 목록
다음은 TouchGFX Designer에서 제공되는 TouchGFX Board Setup 패키지에서 비디오가 기본적으로 활성화되어 있는 개발 키트입니다.
- STM32F769Discovery(하드웨어 가속 디코딩)
- STM32H750BDiscovery(하드웨어 가속 디코딩)
- STM32U5F9Discovery(하드웨어 가속 디코딩)
- STM32F746Discovery(소프트웨어 기반 디코딩)
다른 개발 키트나 맞춤형 하드웨어를 사용하는 경우에는 TouchGFX Generator에서 비디오 지원 기능을 활성화해야 합니다.
MJPEG AVI 파일 생성하기
비디오가 MJPEG AVI 파일로 저장되는 경우는 많지 않은데, 그 이유는 이 형식이 흔히 사용되는 비디오 형식이 아니기 때문입니다. 따라서 보통은 TouchGFX 프로젝트에서 사용하기 위해 비디오 파일을 MJPEG 형식으로 변환하는 경우가 많습니다.
예를 들어 FFMPEG를 사용하면 쉽게 변환할 수 있습니다. 다른 애플리케이션이나 온라인 서비스도 이용할 수 있습니다.
FFMPEG 사용하기
FFMPEG에 필요한 Windows 바이너리 파일은 여기에 있습니다.
비디오 파일인 input.mov를 MJPEG로 변환하려면 다음 명령을 사용합니다.
ffmpeg -i input.mov -s 480x272 -vcodec mjpeg -q:v 1 -pix_fmt yuv420p -color_range 1 -strict -1 -an output.avi
MJPEG 비디오는 output.avi 파일에 있습니다. 이 파일은 assets/videos
로 복사할 수 있습니다.
다음과 같이 가로를 픽셀 수(여기에서 480)로, 그리고 세로를 '-1'(마이너스 1)로 지정하여 비디오의 종횡비를 올바르게 유지할 수 있습니다.
ffmpeg -i input.mov -vf scale=480:-1 -vcodec mjpeg -q:v 1 -pix_fmt yuv420p -color_range 1 -strict -1 -an output.avi
또한 -ss를 사용해 시작 시간을 지정하고, -t 또는 -to를 사용해 지속 시간이나 정지 시간을 지정하여 비디오 구간을 절단하는 것도 가능합니다.
ffmpeg -ss 3 -i input.mov -t 3 -s 480x272 -vcodec mjpeg -q:v 1 -pix_fmt yuv420p -color_range 1 -strict -1 -an output_section.avi
또는:
ffmpeg -ss 3 -i input.mov -to 5 -s 480x272 -vcodec mjpeg -q:v 1 -pix_fmt yuv420p -color_range 1 -strict -1 -an output_section.avi
옵션 | 표현 |
---|---|
-s | 출력 비디오 해상도입니다. |
-q:v | 1부터 31까지(좋음부터 나쁨까지)의 비디오 품질 척도 |
-an | 오디오를 제거합니다. |
-vf | 필터 그래프를 설정합니다. |
-ss | 시작 시간(초)입니다. |
-t | 지속 시간(초)입니다. |
-to | 정지 시간(초)입니다. |
Note
[swscaler @ xxxxxxxxxxxxxxxx] deprecated pixel format used, make sure you did set range correctly
이 경고는 중요하지 않으므로 고려하지 않아도 됩니다.
디코딩 전략
위에서 언급한 것처럼 TouchGFX에서 각 MJPEG 프레임을 프레임 버퍼로 보여주려면 먼저 JPEG에서 RGB로 변환해야 합니다. 이러한 디코딩은 필요할 때마다 빠르게, 혹은 다음 프레임을 비디오 버퍼로 미리 디코딩하는 비동기 방식으로도 가능합니다.
비동기식 디코딩은 UI 태스크가 아닌 2차 태스크로 이루어집니다. 이 말은 경우에 따라서 디코딩을 UI 태스크의 그리가 작업과 병렬로 실행할 수 있다는 것을 의미합니다.
TouchGFX에는 세 가지 전략이 있는데, 각각 장단점이 있습니다.
전략 | 표현 |
---|---|
Direct To Frame Buffer | 필요할 때마다 현재 비디오 프레임을 프레임 버퍼로 바로 디코딩합니다. |
Dedicated Buffer | 다음 비디오 프레임을 비디오 버퍼로 디코딩합니다. 그런 다음 비디오 버퍼에서 프레임 버퍼로 복사합니다. |
Double Buffer | 다음 비디오 프레임을 2차 비디오 버퍼로 디코딩합니다. 그런 다음 디코딩이 완료되면 비디오 버퍼를 스와핑합니다. |
Designer는 시뮬레이터 프로젝트에 항상 Direct To Frame Buffer 전략을 선택합니다. 대상에 사용되는 전략은 TouchGFX Generator에서 구성이 가능합니다.
각 전략에 대한 설명은 아래에서 자세히 다루겠습니다.
Direct To Frame Buffer
Direct To Frame Buffer 전략은 TouchGFX 엔진의 렌더링 단계(렌더링 참조)에서 JPEG 프레임을 디코딩합니다.
업데이트 단계(업데이트 참조)에서 비디오 위젯이 다음 프레임으로의 영상 이동 여부를 결정합니다. 이어지는 렌더링 단계에서 JPEG 프레임이 한 줄씩 "라인 버퍼"로 디코딩됩니다. 그런 다음 픽셀이 프레임 버퍼 형식과 동일하게 변환되어 프레임 버퍼로 복사됩니다.
Video 위젯 위에 다른 불투명 위젯으로 덮여 있으면 여러 블록으로 다시 그려집니다(가시 영역이 직사각형으로 분할됨).
각 블록을 그리기 위해서는 반복적인 압축 작업이 필요합니다. 이 전략은 디코딩 시간이 늘어남에 따라 버튼과 같은 다른 불투명한 UI 요소가 비디오 위에 배치되는 사용자 인터페이스에는 적합하지 않을 수 있습니다. 필요한 추가 압축 해제 작업량을 제한할 수 있는 설계 방법이 있습니다 한 가지 방법은 일정량의 불투명도를 갖도록 불투명 위젯을 변경하는 것입니다. 이렇게 하면 예를 들어 전체 비디오 프레임과 같은 하나의 블록이 됩니다. 해당 위젯은 프레임 콘텐츠와 블렌딩됩니다.다른 방법은 화면에서 그릴 영역을 블록으로 나누는 SMOC 알고리즘을 비활성화하는 것입니다. 비활성화되면 다른 불투명 위젯으로 덮인 부분에서도 모든 위젯이 완전히 그려집니다. 이는 useSMOCDrawing()
을 사용하여 수행됩니다. 아래 예제에서는 상단에 불투명 위젯이 있는 비디오를 포함하는 화면에서 SMOC를 비활성화하고 화면 렌더링이 끝나면 다시 활성화합니다.
void Screen1View::setupScreen()
{
useSMOCDrawing(false);
Screen1ViewBase::setupScreen();
}
void Screen1View::tearDownScreen()
{
useSMOCDrawing(true);
Screen1ViewBase::tearDownScreen();
}
useSMOCDrawing()
에 대한 호출을 비디오가 렌더링되는 곳 가까이에 배치해도 화면에 다른 위젯이 완전히 그려지는 것을 방지할 수 있습니다.
반면, 추가로 사용되는 메모리가 매우 적다는 장점이 있습니다.
Caution
Dedicated Buffer
Dedicated Buffer, 즉 단일 버퍼 디코딩 전략은 먼저 JPEG 프레임을 전용 RGB 버퍼로 디코딩한 후 나중에 해당 버퍼에서 프레임 버퍼로 복사합니다.
직접 전략과 달리 일반적인 TouchGFX 태스크가 아닌 별도의 태스크에서 디코딩이 실행됩니다. 비디오 위젯이 다음 사용자 인터페이스 실행 이벤트(tick)에 대한 새 영상 프레임의 표시 여부를 결정한 후 디코딩 태스크에게 다음 프레임을 디코딩하라는 신호를 전송합니다. 이러한 디코딩 과정에서는 비디오 버퍼가 프레임 버퍼에 그려지지는 않습니다(부분적으로 업데이트됨). 디코딩이 실행되는 동안에는 사용자 인터페이스가 차단되어 비디오 위젯을 다시 그리지 못하기 때문입니다. 그리기 작업이 먼저 완료되어야 이어서 계속됩니다. 사용자 인터페이스가 비디오를 다시 그려야 할 필요가 없어지면 정상적으로 계속 실행됩니다.
새 비디오가 완전히 디코딩되면 비디오를 프레임 버퍼로 렌더링하는 비용이 비트맵을 그리는 비용(낮음)과 같아집니다. 따라서 이러한 전략에서는 버튼이나 텍스트를 비디오 위에 추가하더라도 문제가 되지 않습니다.
다만 태스크와 비디오 버퍼에서 메모리를 사용한다는 단점이 있습니다.
Double Buffer
Double Buffer 디코딩 전략에는 RGB 버퍼가 2개 있습니다. 디코딩은 두 버퍼 중 하나로 실행되는 반면 나머지 RGB 버퍼에서는 프레임 버퍼에 대한 렌더링이 실행됩니다.
프레임이 디코딩되면 디코딩 태스크는 UI가 해당 비디오 버퍼를 표시하고 이전 버퍼를 해제할 때까지 기다립니다. 사용자 인터페이스가 버퍼를 변경하자마자 다음 프레임에 대한 디코딩이 시작됩니다.
다만 이 전략에서는 이전 전략에 비해 메모리 사용량이 두 배가 된다는 단점이 있습니다.
비디오 프레임 속도와 사용자 인터페이스 프레임 속도
디코딩 전략마다 사용자 인터페이스 프레임 속도에 미치는 영향이 다릅니다. 사용자 인터페이스 프레임 속도란 프레임 버퍼에서 초당 생성되는 (여러) 프레임 수를 말합니다.
Direct to Frame Buffer 전략에서는 비디오의 디코딩 속도가 효과적인 사용자 인터페이스 프레임 속도에 손쉽게 영향을 미칩니다. 예를 들어 JPEG 프레임 하나를 디코딩하는 데 28ms가 걸리는데, 여기서 비디오 프레임을 초당 20개씩(20fps) 표시하려고 합니다. 그러면 실제로 총 디코딩 시간은 28ms x 20/s, 즉 560ms/s가 됩니다. 20fps는 60fps에서 1/3프레임마다 새롭게 시작되는 비디오 프레임에 해당합니다. 따라서 1/3 UI 프레임마다 새 비디오 프레임을 표시하는 셈입니다. 하지만 디코딩 시간이 렌더링 단계에 포함되기 때문에 해당 프레임을 렌더링하는 데 28ms+ 나머지 사용자 인터페이스 렌더링 시간(예: 2ms)이 걸립니다. 그러면 총 프레임 렌더링 시간이 30ms이고, 1 tick이 지났지만 다음 tick을 위한 새로운 프레임을 생성할 수 있습니다. 다음 프레임에서는 비디오를 디코딩하지 않기 때문에 렌더링 속도가 데 16ms를 넘지 않아 제한 시간을 넘기지 않습니다. 이후 네 번째 tick에서 두 번째 비디오 프레임에 대한 디코딩을 시작할 수 있습니다.
따라서 비디오 프레임 속도는 20fps이고, 사용자 인터페이스 프레임 속도는 40입니다(60 이후부터 가능함).
결과적으로 비디오 디코딩이 프레임 속도를 제한하기 때문에 60fps로는 다른 UI 요소를 애니메이션 처리하지 못하게 됩니다.
Double Buffer 디코딩 전략에서는 이러한 문제를 개선할 수 있습니다. 사용자 인터페이스가 항상 최신 프레임으로 비디오 버퍼를 사용할 수 있기 때문입니다. 디코더 태스크가 가능한 경우 이 버퍼를 나머지 버퍼(다음 프레임 포함)와 스와핑할 수 있어서 렌더링 스레드가 동적으로 그리지 않습니다. 스와핑을 마치면 디코딩 태스크가 다음 프레임의 디코딩을 바로 시작할 수 있습니다.
UI 프레임 1과 2에서는 UI가 디코딩된 첫 번째 비디오 프레임을 표시합니다. 한편 디코더는 프레임 2를 생성 중입니다. UI 프레임 3에서는 프레임 2가 사용됩니다. 그림에는 보이지 않지만 디코더가 다음 프레임의 디코딩을 자유롭게 시작할 수 있습니다.
따라서 비디오 디코딩이 오직 2 tick마다 새 프레임을 생성할 수 있지만 사용자 인터페이스는 프레임마다 다른 요소와 함께 업데이트됩니다.
관련 섹션
위에서도 언급했지만 타겟 프로젝트에 대한 비디오 지원은 TouchGFX Generator에서 구성됩니다. 자세한 내용은 TouchGFX Generator 사용자 가이드를 참조하십시오. 참조하십시오.
비디오 위젯은 Designer에서 사용할 수 있습니다. Designer의 비디오 위젯 사용 방법은 여기에 자세히 나와 있습니다.