跳转到主要内容

MJPEG视频

从4.18版本开始,TouchGFX支持使用MJPEG视频。 视频可以用于创建更生动的用户界面或显示简短说明或用户指南。

视频通过Video Widget包含在用户界面中。 该控件在TouchGFX设计器中可用,可以作为任意其他控件添加到用户界面。

Note
Audio is not supported. It is therefore recommended to remove audio data from the video data.

在TouchGFX设计器中使用“视频(Video)”控件

在STM32微控制器上解码视频需要额外的支持软件包。 该软件在TouchGFX Generator中启用视频支持,从而轻松包含在项目中。 在已使能video功能的TouchGFX工程开发板上(见下面的列表),可以轻松运行视频,方法是像平常一样按下“运行目标(F6)”。

通过STM32F746Discovery使用“视频(Video)”控件

如果你的目标代码中没有视频支持,会得到编译或链接错误。

MJPEG视频

MJPEG视频由打包在容器文件(.avi)中的大量JPEG图像(帧)组成。 压缩后的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文件只有1,242,282字节,为位图大小的10.7%。

尺寸的缩减使得MJPEG视频文件对于小段视频非常有用。

尺寸的缩减会伴随一些压缩伪影(差错)。 这些对于真实画面通常是可以接受的,但对于高对比度图形是不可接受的。

部分STM32微控制器(如STM32F769或STM32H750)支持JPEG图像的硬件加速解码。 这加快了JPEG解码,并提高了视频的可能帧速率。

JPEG帧的解码很容易占用16ms以上的时间(取决于MCU的速度和视频分辨率)。 这意味着在大多数情况下,MJPEG视频的解码速率低于用户界面的正常帧速率。 对于某些应用,将整体帧速率降低到解码速率是可以接受的。 对于其他应用,即使视频以20 fps的速率运行,也需要保持用户界面60 fps的帧率。 有一个应用可作为示例:在视频旁边有一个具有动画效果的进度圆环。 60 fps的动画速率让进度圆环的效果达到最佳,即使视频只显示20 fps帧率的新帧。

上面关于STM32F746的示例在解码单个JPEG帧时耗费18-20 ms。

通过TouchGFX使用视频

有了TouchGFX,可以轻松将视频变为您用户界面的一部分。 需要三样东西:一个视频控件、一个视频控制器、当然还有MJPEG视频文件。

视频控件在用户界面中使用,与所有其他控件一样。 视频控制器是构成完整TouchGFX环境(HAL、操作系统、驱动程序等)的底层软件的一部分。

视频控件和视频控制器

视频控制器由控制MJPEG文件解码和缓冲区管理的软件组成。

TouchGFX设计器自动将视频控制器包含到所有模拟器项目中。 这样就可以在模拟器原型中轻松使用视频:只需添加一个视频控件,选择一个视频文件,然后按下 “Run Simulator(运行模拟器)”( F5).

如要在硬件上使用视频,还需目标项目(IAR、Keil、arm-gcc、CubeIDE)中有视频控制器。 这已经添加到一些TouchGFX板件规范包(见下面的列表),但您可以通过TouchGFX Generator向任意项目添加视频支持。 参见Generator用户指南

当您拥有支持视频的平台时,可以轻松在TouchGFX设计器中添加和配置视频控件。 此处详细介绍了如何在TouchGFX设计器中使用视频控件。

TouchGFX项目中的视频文件

当在TouchGFX设计器中包含视频文件后,它将.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

可在用户代码中通过这些声明将视频分配给视频控件。

从用户代码使用视频文件

在一些项目中,仅从TouchGFX设计器中选择一个视频是不够的。 例如, 您想在启动时选择不同的视频。 首先,必须添加视频文件到assets/videos

添加视频到assets/videos

生成代码(或运行make assets)时,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中,且手动将视频刷写到外部flash存储的专用区域。 然后使用flash地址传递地址和长度:

void Screen1View::setupScreen()
{
...
video.setVideoData((const uint8_t*)0xA0200000, 1242282);
...
}

限制

TouchGFX不支持纵向模式下的视频解码。

支持视频功能的开发套件列表

TouchGFX设计器中的TouchGFX板件设置包默认为这些开发套件启用了视频功能:

  • 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 -qscale 1 -an output.avi

MJPEG视频在output.avi文件中。 该文件可以复制到assets/videos中。

为了视频能保持正确的长宽比,可以以像素为单位指定宽度(这里是480),高度“-1”(-1):

ffmpeg -i input.mov -vf scale=480:-1 -vcodec mjpeg -qscale 1 -an output.avi

可以剪切视频:使用-ss指定开始时间,使用-t或-to指定持续时间或停止时间:

ffmpeg -ss 3 -i input.mov -t 3 -s 480x272 -vcodec mjpeg -qscale 1 -an output_section.avi

或:

ffmpeg -ss 3 -i input.mov -to 5 -s 480x272 -vcodec mjpeg -qscale 1 -an output_section.avi
选项说明
-s输出视频分辨率
-qscale质量等级范围1 - 31(从优到差)
-an去除音频
-vf设置过滤器图
-ss开始时间(以秒为单位)
-t持续时间
-to停止时间(以秒为单位)

解码策略

如上所述,TouchGFX需要将单独的MJPEG帧从JPEG转换为RGB,然后才在帧缓存区显示。 这种解码可以在需要时实时完成,也可以异步完成 - 提前将下一帧解码到视频缓冲区。

异步解码是由第二个任务(而不是UI任务)完成的。 这意味着在某些情况下,解码可以与UI任务的绘制并行进行。

TouchGFX有3种策略,每种策略都有优点和缺点:

策略说明
直接解码帧到缓冲区在需要时,将当前视频帧直接解码到帧缓存区
专用缓冲区将下一个视频帧解码到视频缓冲区, 从视频缓冲区复制到帧缓存区。
双缓冲区将下一个视频帧解码到第二视频缓冲区, 解码完成后,交换视频缓冲区。

Designer总是为模拟器项目选择“直接解码到帧缓存区”策略。 可以在Generator中配置对目标使用什么样的策略。

下文将详细讲解这些策略。

直接解码帧到缓冲区

“直接解码到帧缓存区”策略在TouchGFX引擎的渲染阶段解码JPEG帧(参见‘渲染’章节)

直接解码到帧缓冲区

在更新阶段(参见‘更新’章节),视频控件决定是否应该将电影提前到下一帧。 在接下来的渲染阶段,JPEG帧被逐行解码到一个“行缓冲区”。 然后对像素进行转换以匹配帧缓存区格式,并复制到帧缓存区。

If the Video widget is covered by other solid widgets it is redrawn in multiple blocks (the visible parts are split into rectangles).

Video frame with solid widget overlap decoded in multiple blocks.

Drawing each of these blocks requires repeated decompression work. This can make this strategy unsuitable for user interface where other solid UI elements like Buttons are put on top of the video as the decoding time is increased. There are design decisions which can limit the amount of extra decompression work needed. One approach is to change the solid widget to have some amount of opacity. This will result in only one block, i.e., the whole video frame. The widget will be blended with the contents of the frame.

Video frame with widget with some opacity decoded in one block.

Another approach is to disable the SMOC algorithm which partitions the areas to draw in the screen into blocks. When disabled, all widgets are drawn completely, even areas which are covered by other solid widgets. This is done with useSMOCDrawing(). The example below disables SMOC for the screen containing the video with solid widget(s) on top and enables it again when the screen is done rendering.

void Screen1View::setupScreen()
{
useSMOCDrawing(false);
Screen1ViewBase::setupScreen();
}

void Screen1View::tearDownScreen()
{
useSMOCDrawing(true);
Screen1ViewBase::tearDownScreen();
}

The call to useSMOCDrawing() can also be placed closer to where the video is rendered to avoid other widgets in the screen also being drawn completely.

该解决方案的优点是只使用了很少额外内存。

Caution
Disabling the SMOC drawing algorithm may unintendedly cause other widgets to have longer render times and should be used with caution.

专用缓冲区

专用或单缓冲区解码策略首先将JPEG帧解码到专用RGB缓冲区,然后再从该缓冲区复制到帧缓存区。

解码到单独的缓冲区

与直接策略相反,现在解码工作在一个单独任务(而不是通常的TouchGFX任务)中运行。 视频控件计算一个新的电影帧是否应该在下一个用户界面tick中显示,并通过信号告知解码任务开始解码下一帧。 在解码过程中,视频缓冲区不能被绘制到帧缓存区(它被部分更新)。 当解码任务正在运行时,用户界面不得重绘视频控件。 绘制完成后,它将继续。 如果用户界面不需要重绘视频,则用户界面可以照常运行。

新视频被完全解码后,将视频渲染到帧缓存区的成本与绘制位图的成本(低)相同。 通过该策略,在视频上放置按钮或文本不成问题。

该策略的缺点是任务和视频缓冲区占用内存。

双缓冲区

双缓冲区解码策略使用两个RGB缓冲区。 解码在这两个缓冲区中完成,而渲染到帧缓存区则发生于另一个RGB缓冲区中。

解码到两个视频缓冲区

当一个帧被解码后,解码任务等待UI显示该视频缓冲区并释放前一个缓冲区。 一旦用户界面改变了缓冲区,就可以开始解码下一帧。

该策略的一个明显缺点是内存使用是前一种策略的两倍。

视频帧速率和用户界面帧速率

不同的解码策略对用户界面帧速率的影响也不同。 用户界面帧速率是每秒在帧缓存区中产生的(不同)帧数量。

如果采用“直接解码到帧缓存区”策略,视频解码速度容易影响用户界面的有效帧速率。 假设解码一个JPEG帧需要28 ms,并且我们希望每秒显示20帧视频(20 fps)。 这是比较实际的,因为总解码时间是:28 ms x 20/s = 560 ms/s。 20 fps对应于每3帧(60 fps)有一个新视频帧。 所以在每三个UI帧中,我们需要一个新的视频帧,但是由于解码时间是渲染阶段的一部分,渲染一帧需要28 ms,加上渲染用户界面的其他部分需要(比如)2 ms。 总的帧渲染时间是30 ms,我们失去了一个tick,但我们准备为下一个tick生成一个新帧。 在下一帧中,我们没有解码视频,所以渲染时间低于16 ms,符合限制要求。 然后,我们可以在第四个tick中开始解码第二个视频帧。

20 fps视频

因此,视频帧速率是20 fps,用户界面帧速率是40 fps(共计60 fps)。

结果是我们不能让UI的其他元素具有60 fps的动画效果,因为视频解码限制了帧速率。

如果采用“双缓冲区”策略,这种问题得到缓解。 用户界面总是有一个可用的视频缓冲区用于最新的帧。 解码任务可以将该缓冲区与另一个缓冲区(包含下一帧)交换(当它可用时),而渲染线程没有主动绘制。 交换之后,解码任务可以立即开始解码下一帧:

20 fps视频

在UI帧1和UI帧2中,UI显示的是第一个解码的视频帧。 同时,解码器在生成帧2。 在UI帧3中,该帧已经准备就绪并将被使用。 解码器处于空闲状态,可以开始解码下一帧(图中没有显示)。

因此,即使视频解码只能每2个ticks中生成一个新帧,也可以在每帧中更新用户界面的其他元素。

如上所述,对目标项目的视频支持是在TouchGFX生成器中配置的。 参见Generator用户指南。 说明。

视频控件在TouchGFX设计器中可用。 此处详细介绍了如何在TouchGFX设计器中使用视频控件。