视频播放应用使用说明
概述
本文基于 RTL8773EWE-VS 的 Watch NAND Flash SDK,描述了如何播放存储在文件系统内 AVI 格式的视频。AVI 视频包含 YUYV422 格式的视频流和 MP3 编码的音频流。
FFmpeg
FFmpeg 是一个功能强大的多媒体处理工具,可以用于录制、转换和流式传输音视频内容。它支持几乎所有常见的音视频格式和编解码器。
官网: FFmpeg 官网
可执行文件下载: FFmpeg 可执行文件下载链接

FFmpeg 可执行文件下载
FFmpeg 指令参考 Code
如上所述,转码的 AVI 格式文件为 YUYV 格式视频帧和 MP3 编码音频帧。可供参考的转码代码如下:
ffmpeg -i input.mp4 -ss 00:09:30 -t 00:00:11 -vf "scale=-1:454, crop=454:454:176.5:0" -c:v rawvideo -r 20 -pix_fmt yuyv422 -c:a libmp3lame -b:a 192k -ar 44100 -ac 2 output.avi
参数解释:
-i input file :输入文件。
-ss 00:09:30 :开始时间。
-t 00:00:11 :持续时间。
-vf “ scale=-1:454, crop=454:454:176.5:0” :视频尺寸和裁剪范围,格式为 scale=w:h, crop=w:h:x:y。-1 表示宽度随高度等比例缩放。
-c:v rawvideo :指定视频流编码为 rawvideo。
-r 20 :转出视频为 20 帧。
-pix_fmt yuyv422 :转出视频流格式为 yuyv422。
-c:a libmp3lame :指定音频流编码为 libmp3lame。
-b:a 192k MP3 :bit 率为 192k。
-ar 44100 MP3 :采样频率为 44100。
-ac 2 :MP3 双声道。
output.avi :输出文件。
其他更多指令格式请参考 FFmpeg 文档
AVI 封装格式
AVI 文件目前有两种版本:
AVI 1.0: 原始的 AVI 文件封装格式。最大支持 4GB ,为了安全限制一般为 2GB。
Open-DML:扩展的 AVI 文件封装格式,主要改动是解除了文件大小的限制并减小了开销,修订了索引的数据结构。
本文暂不考虑 Open-DML 格式的支持,后续解析说明按照 AVI 1.0 格式。
AVI 基本数据结构
AVI 文件的两种基本数据结构为 CHUNK
和 LIST
。
typedef struct { DWORD dwFourCC; DWORD dwSize; BYTE data[dwSize]; } CHUNK; typedef struct { DWORD dwList; DWORD dwSize; DWORD dwFourCC; BYTE data[dwSize - 4]; } LIST;
一个 chunk 包含一个 chunk 的视频、音频或字幕数据,这些数据可以是 header,也可以是数据帧。 dwFourCC
表示该 chunk 的类型,由 4 个字符表示。
例如, strh
表示一个 chunk stream header, 00dc
表示一个 chunk 非压缩的视频帧, 01wb
表示一个 chunk 非压缩的音频帧等。 dwSize
表示从 data 起算的实际 chunk 大小。
一个 list 通常包含和 AVI 文件有关的信息。 dwList
会有两种值: RIFF
或 LIST
。 RIFF
表示 AVI 文件头, LIST
表示其他 list 头。
dwFourCC
表示该 list 的类型,由 4 个字符表示。例如, hdrl
表示 header list, movi
表示数据帧 list 等。 dwSize
表示从 dwFourCC
起算的实际 list 大小。
AVI 文件结构
一个 AVI 文件通常由以下几个部分组成:
“RIFF” list
“RIFF” 为整个 AVI 文件的起始,它的
dwFourCC
为 “AVI” (注意有一个空格)。
“hdrl” list
“hdrl” list 通常包含一个 “avih” chunk,表示 AVI 文件的 main header。 “avih” 的数据结构定义:
typedef struct { DWORD dwMicroSecPerFrame; DWORD dwMaxBytesPerSec; DWORD dwPaddingGranularity; DWORD dwFlags; DWORD dwTotalFrames; DWORD dwInitialFrames; DWORD dwStreams; DWORD dwSuggestedBufferSize; DWORD dwWidth; DWORD dwHeight; DWORD dwReserved[4]; } MainAVIHeader;
“strl” list
一个 AVI 文件中会有和音频流、视频流、字幕流对应个数的 “strl” list。一个 “strl” list 至少会包含一个 “strh” chunk 和一个 “strf” chunk,分别表示 stream header 和 stream format。 可选的 chunk 还有 “strd” 和 “strn”,分别表示额外的流信息和流名称。一般 “strl” 不包含 “strd” 和 “strn”。
“strh” 数据结构定义:
typedef struct { FOURCC fccType; FOURCC fccHandler; DWORD dwFlags; WORD wPriority; WORD wLanguage; DWORD dwInitialFrames; DWORD dwTimeScale; DWORD dwRate; DWORD dwStartTime; DWORD dwLength; DWORD dwSuggestedBufferSize; DWORD dwQuality; DWORD dwSampleSize; RECT Frame; } AVIStreamHeader;
“strf” 根据视频流或音频流,定义为不同的数据结构:
typedef struct { DWORD headerSize; DWORD biWidth; DWORD biHeight; WORD biPlanes; WORD bitsPerPixel; DWORD biCompression; DWORD biSizeImage; DWORD biXPelsPerMeter; DWORD biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } AVIStreamFormatBitMap; typedef struct { WORD wFormatTag; WORD wChannels; DWORD dwSamplesPerSec; DWORD dwAvgBytesPerSec; WORD wBlockAlign; WORD wBitsPerSample; WORD wSize; BYTE reserved[12]; } AVIStreamFormatAudioWave;
“INFO” list
“INFO” list 用于描述该 AVI 文件的信息,例如版权,作者等。对于本文解码无用,可以跳过。
“movi” list
“movi” list 包含数据流 chunk:
##dc
,##wb
,##tx
表示,分别表示一帧视频流/音频流和字幕流。 “##”为流的编号,和 “hdrl” 的顺序有关。 例如,第一个 “hdrl” 为视频流, “movi” list 就会包含若干个00dc
chunk,第二个 “hdrl” 为音频流, “movi” list 就会包含若干个01wb
chunk。 “movi” list 的 chunk,是需要送到音频解码器、视频解码器的数据。
“idx1” list
“idx1” list 包含了 “movi” list 中所有数据流 chunk 的索引。数据结构如下:
typedef struct { DWORD dwChunkId; DWORD dwFlags; DWORD dwOffset; DWORD dwSize; } AVIIndexEntry;需要注意的是,offset 的偏移量,通常是基于
movi
的位置。
“JUNK” chunk
“JUNK” chunk 包含一些用于对齐的填充数据,可以直接跳过。
AVI 文件解析流程

AVI 文件解析流程图
视频播放功能
视频播放流程
播放视频的流程基本可以分为三个步骤:开始播放,更新音视频帧,结束播放。
开始播放:调用
app_video_start()
,可以播放指定文件系统中的 AVI 视频文件。 播放视频流程会开启一条 audio track 用于音频播放,创建一个 realgui img 控件用于视频播放。控件相关操作通过发送消息给 GUI task 进行。结束播放:调用
app_video_stop()
,结束播放视频流程。更新音视频帧:在视频播放流程开始后,需要按照视频播放的每帧时长,从文件系统中读取视频帧,更新给 img 控件;读取音频帧,更新给 audio track。

开始播放 - 停止播放 - 更新音 / 视频帧
备注
更新音视频帧的流程,需要在一个控件的动画回调中进行。
app_video_start()
、app_video_stop()
需要在 APP 任务调用。读取音视频帧需要在 APP 任务调用。
视频播放 Demo 移植
更新包含 avi_parser 版本的
filesystem.lib
。添加
app_video.c
编译。移植 APP 消息的新增指令:
IO_MSG_VIDEO_START
: 开启指定的视频播放。
移植 APP MMI 的新增指令:
MMI_VIDEO_STOP
: 结束当前视频播放。
MMI_VIDEO_NEXT_AUDIO_FRAME
:读取若干音频帧。
MMI_VIDEO_NEXT_VIDEO_FRAME
:读取下一帧视频帧。
实现 GUI 的新增更新事件,这三个事件会在
app_video_start()
和app_video_stop()
的流程中,发送消息给 gui task 处理:
GUI_EVENT_VIDEO_CREATE
:创建显示需要的 img 控件,并设置一个动画,动画持续时间需要和视频帧时长相同。随后关闭动画等待下个更新事件。
GUI_EVENT_VIDEO_START
:开启更新动画。
GUI_EVENT_VIDEO_STOP
:结束更新动画。
实现 GUI 的更新动画回调
gui_video_refresh_cb()
。动画回调需要:
调用
app_video_refresh_clock()
,更新 video 时钟。调用
app_video_is_update_video_frame()
, 判断是否需要更新视频帧。若是则发送MMI_VIDEO_NEXT_VIDEO_FRAME
给 APP 任务。在更新视频帧后,调用
app_video_is_update_audio_frame()
判断是否需要更新音频帧。若是则发送MMI_VIDEO_NEXT_AUDIO_FRAME
给 APP 任务。更新控件显示。
VIDEO_H_MAX
和VIDEO_W_MAX
调整到期望视频最大的尺寸,避免浪费 PSRAM。
VIDEO_BUFFER_ADDR_A
的地址注意修改,默认放在 PSRAM 的PSRAM_APP_DEFINED_SECTION
之后。
功能宏:
F_APP_VIDEO
。
RAM 和 Flash 使用统计
PSRAM 的使用:
video buffer: pixel + gui Header + imdc Header + imdc addr offset = (W * H * 2) + (8) + (12) + (H + 1) * 4 Byte。以 272 * 272 视频尺寸为例,需要 149060 Byte。
audio buffer: 2 KByte。
-
RAM 的使用:
T_AV_CONTROLLER + AVIHandle_t + frame index lookup table + FILE = (88) + (116) + (video frames * 8 + audio frames * 8) + (2088)。
以一段 20 秒,帧率为 20,audio MP3 采样率为 44100 的视频为例,需要 88 + 116 + 3208 + 6136 + 2088 = 11636 Byte。
在 Demo 中,选择了 272 * 272 的 YUYV422 格式视频帧,视频帧率为 20,音频格式为 MP3,192k 码率,44100 采样率,时长 20 秒的视频,占用空间为 56.9 MByte。